Tham chiếu Git bằng hình ảnh

Ngôn ngữ khác:

Trang này đề cập đến tham chiếu bằng hình ảnh ngắn gọn cho các lệnh thông thường nhất được sử dụng trong git. Nếu bạn biết một chút về cách làm việc của git thì trang này có thể sẽ củng cố thêm sự hiểu biết của bạn. Nếu bạn quan tâm trang này được tạo ra như thế nào, mời xem GitHub repository của tôi.

Nội dung

  1. Cách dùng cơ bản
  2. Quy ước
  3. Các lệnh chi tiết
    1. Diff
    2. Commit
    3. Checkout
    4. Commit khi HEAD bị tách rời (detached)
    5. Reset
    6. Merge
    7. Cherry Pick
    8. Rebase
  4. Ghi chú kỹ thuật

Cách dùng cơ bản

Bốn lệnh trên sao chép các tệp tin giữa thư mục làm việc (working directory), vùng chuyển tiếp (stage) - hay còn gọi là chỉ mục (index) và lịch sử (history) (dưới dạng các "commit").

Bạn có thể sử dụng git reset -p, git checkout -p, hoặc git add -p thay vì phải (hoặc phải làm thêm bước) chỉ rõ các tệp tin cụ thể để lựa chọn thực hiện thao tác với số lượng tệp tin lớn.

Cũng có thể bỏ qua khu chuyển tiếp và "check out" các tệp tin trực tiếp từ lịch sử hoặc "commit" các tệp tin mà không phải đưa vào vùng chuyển tiếp trước.

Quy ước

Trong toàn bộ tài liệu này chúng ta sẽ sử dụng các biểu đồ có dạng như sau.

Các commit được biểu thị bằng màu xanh có các id là 5 ký tự và chúng trỏ tới commit cha. Các nhánh (branch) được biểu thị bằng màu cam và chúng trỏ tới các commit cụ thể. Nhánh hiện tại được xác định bằng tham chiếu đặc biệt HEAD được "gắn (attached)" vào nhánh đó. Trong hình này, có 5 "commit" mới nhất được hiển thị, trong đó ed489 là commit mới nhất. Nhánh main (nhánh hiện tại) trỏ tới commit này, trong khi đó nhánh stable (một nhánh khác) trỏ tới "commit" tổ tiên (ancestor) của "commit" trên nhánh main

Các lệnh chi tiết

Diff

Có rất nhiều cách để so sánh sự khác biệt giữa các "commit". Dưới đây là một số ví dụ thông thường. Các lệnh này có thể tuỳ chọn truyền thêm các đối số là tên tập tin để giới hạn sự khác biệt chỉ trên những tệp tên được chỉ ra.

Commit

Khi bạn "commit", git tạo một đối tượng "commit" mới sử dụng các tệp tin từ khu chuyển tiếp và đặt "commit" hiện tại làm cha. Sau đó nó trỏ nhánh hiện tại tới "commit" mới này. Trong hình dưới đây, nhánh hiện tại là main. Trước khi lệnh được chạy, main trỏ tới ed489. Sau đó một "commit" mới, f0cec, được tạo với cha là ed489, và cuối cùng main được dịch chuyển đến "commit" mới.

Quá trình này cũng được thực hiện tương tự khi nhánh hiện tại là tổ tiên của nhánh khác. Theo hình dưới đây, một "commit" xảy ra tại nhánh stable, đây là nhánh tổ tiên của main, tạo thành 1800b. Sau đó, stable không còn là nhánh tổ tiên của main nữa. Để hợp hai lịch sử này cần dùng lệnh merge (hoặc rebase).

Đôi khi một "commit" bị lỗi, nhưng có thể dễ dàng sửa lỗi với git commit --amend. Khi bạn sử dụng lệnh này, git tạo ra một "commit" mới và lấy "commit" hiện tại làm cha. ("Commit" cũ sẽ bị loại bỏ nếu không có tham chiếu nào tới nó.)

Trường hợp thứ tư là "commit" khi HEAD bị tách rời sẽ được giải thích sau.

Checkout

Lệnh "checkout" được sử dụng để sao chép các tệp tin từ lịch sử (hay khu chuyển tiếp) tới thư mục làm việc, và có thể tuỳ chọn chuyển nhánh.

Khi chỉ ra tên tệp tin (và/hoặc -p), git sao chép các tệp tin này từ "commit" được chỉ định tới khu chuyển tiếp và thư mục làm việc. Ví dụ, git checkout HEAD~ foo.c sẽ sao chép tệp tin foo.c từ "commit" HEAD~ (cha của "commit" hiện tại) tới thư mục làm việc, và cũng đưa nó vào khu chuyển tiếp. (Nếu không chỉ ra tên của "commit", các tệp tin được sao chép từ khu chuyển tiếp.) Chú ý rằng nhánh hiện tại sẽ không có bất kỳ thay đổi gì.

Khi không chỉ ra tên tệp tin và tham chiếu là một nhánh nội vùng (local), HEAD được dịch chuyển tới nhánh đó (hay, hiểu theo cách khác là "chuyển sang" nhánh đó), và khi đó khu chuyển tiếp và thư mục làm việc được thiết lập khớp nội dung của "commit" đó. Bất kì tệp tin nào tồn tại trong "commit" mới (a47c3 theo hình phía dưới) sẽ được sao chép; bất kì tệp tin nào tồn tại trong "commit" cũ (ed489) nhưng không trong "commit" mới sẽ bị xoá bỏ; và bất kì tệp tin nào không tồn tại trong cả hai sẽ được bỏ qua.

Khi không chỉ ra tên tệp tin và tham chiếu không phải là nhánh nội vùng — có thể là một thẻ (tag), một nhánh ở xa (remote), một SHA-1 ID, hoặc có thể main~3 — chúng ta sẽ được chuyển qua nhánh vô danh (anonymous) và trường hợp này được gọi là HEAD bị tách rời. Trường hợp này rất hữu ích khi bạn muốn xem lịch sử. Ví dụ như bạn muốn biên dịch (compile) git phiên bản 1.6.6.1. Bạn có thể git checkout v1.6.6.1 (đây là thẻ, không phải nhánh), biên dịch, cài đặt (install), và sau đó chuyển tới một nhánh khác, có thể là git checkout main. Tuy nhiên, cách làm việc của "commit" hơi khác với HEAD bị tách rời; điều này sẽ được nói tới sau đây.

Commit khi HEAD bị tách rời

Khi HEAD bị tách rời, "commit" hoạt động như bình thường, ngoại trừ việc không có nhánh có tên cụ thể nào được cập nhật. (Bạn có thể coi đây là một nhánh vô danh.)

Một khi bạn "check out" cái gì khác, có thể main, "commit" đó (giả sử) không được tham chiếu ở bất kì đâu thì sẽ bị xoá bỏ. Chú ý rằng sau lệnh này, 2eecb không được tham chiếu từ bất kì đâu.

Mặt khác nếu bạn muốn lưu trữ trạng thái này, bạn có thể tạo một nhánh mới có tên cụ thể sử dụng lệnh git checkout -b name.

Reset

Lệnh "reset" sẽ chuyển nhánh hiện tại tới một vị trí khác, và có thể tuỳ chọn cập nhật khu chuyển tiếp và thư mục làm việc. Nó cũng được sử dụng để sao chép các tệp tin từ lịch sử tới khu chuyển tiếp mà không ảnh hưởng gì tới thư mục làm việc.

Nếu một "commit" được chỉ định mà không có tên các tệp tin, nhánh hiện tại sẽ được chuyển tới "commit" đó, và sau đó khu chuyển tiếp được cập nhật để khớp với "commit" này. Nếu --hard được cung cấp, thư mục làm việc cũng được cập nhật. Nếu --soft được cung cấp, cả hai đều không được cập nhật.

Nếu một "commit" không được chỉ định, "commit" mặc định là HEAD. Trong trường hợp này, nhánh không được chuyển dịch, nhưng khu chuyển tiếp (và có thể thư mục làm việc nếu --hard được cung cấp) được thiết lập lại với nội dung của "commit" mới nhất.

Nếu tên tệp tin (và/hoặc -p) được cung cấp thì lệnh này hoạt động tương tự checkout với tên tệp tin, ngoại trừ việc chỉ có khu chuyển tiếp (và không phải thư mục làm việc) được cập nhật. (Bạn cũng có thể chỉ rõ lấy các tệp tin từ "commit" cụ thể nào thay vì HEAD.)

Merge

Lệnh "merge" tạo một "commit" mới sáp nhập những thay đổi từ các "commit" khác. Trước khi tiến hành "merge", khu chuyển tiếp phải khớp với "commit" hiện tại. Có một trường hợp đặc biệt khi các "commit" khác là tổ tiên của "commit" hiện tại thì không xảy ra điều gì. Trường hợp khác đơn giản hơn là nếu "commit" hiện tại là tổ tiên của "commit" khác, khi đó sẽ tạo ra "fast-forward" "merge"và đơn giản chỉ dịch chuyển tham chiếu và "commit" mới được "check out".

Trong các trường hợp khác, "merge thực sự" phải xảy ra. Bạn có thể lựa chọn các chiến lược khác nhau nhưng mặc định sẽ phải thực hiện "merge đệ quy", về cơ bản sẽ lấy "commit" hiện tại (ed489), "commit" khác (33104), và tổ tiên chung (b325c), và sau đó thực hiện "merge ba-hướng". Kết quả được lưu trữ vào thư mục làm việc và khu chuyển tiếp, và sau đó thực hiện thêm một "commit" nữa và "commit" mới này tham chiếu thêm một "commit" cha (33104.)

Cherry Pick

Lệnh "cherry-pick" sẽ sao chép một "commit", tạo một "commit" mới trên nhánh hiện tại với nội dung giống hệt và "patch" thành một "commit" khác.

Rebase

Lệnh "rebase" là một lựa chọn khác thay vì "merge" để kết hợp nhiều nhánh khác nhau. Trong khi "merge" tạo một "commit" mới với hai cha và tạo ra lịch sử phi tuyến tính (non-linear) thì "rebase" tái tạo lại các "commit" từ nhánh hiện tại lên một nhánh khác và tạo ra lịch sử tuyến tính ("linear"). Về bản chất, đây là phương pháp thực hiện tự động các lệnh cherry-pick liên tiếp.

Lệnh trên lấy tất cả các "commit" tồn tại trong topic nhưng không có trong main, (các "commit" 169a62c33a), tái tạo lên main, và sau đó chuyển đầu nhánh tới đỉnh mới. Chú ý rằng các "commit" cũ sẽ bị xoá bỏ nếu chúng không được tham chiếu nữa.

Để giới hạn quay trở lại bao xa, sử dụng thêm --onto. Lệnh sau tái tạo lên main từ các "commit" mới nhất trên nhánh hiện tại tính từ 169a6 (không bao gồm), là "commit" 2c33a.

Cũng có thêm lệnh git rebase --interactive, lệnh này cho phép bạn thực hiện các thao tác phức tạp hơn thay vì đơn giản tái tạo lại các "commit", ví dụ như loại bỏ, sắp xếp lại, sửa đổi, và gộp nhóm (squash) các "commit". Rất khó để biểu diễn các thao tác này bằng hình ảnh; mời bạn xem git-rebase(1) để biết thêm chi tiết.

Chú ý kỹ thuật

Nội dung của các tệp tin thật ra không được lưu trữ trong chỉ mục (.git/index) hay trong các đối tượng "commit" mà mỗi tệp tin được lưu trữ trong cơ sở dữ liệu đối tượng (.git/objects) thành một blob, và được định danh bởi hàm băm SHA-1 của chính nó. Tệp tin chỉ mục liệt kê các tên tệp tin cùng với các định danh của "blob" tương ứng cùng với một số dữ liệu khác. Đối với các "commit", có thêm một kiểu dữ liệu nữa là tree cũng được định danh bởi hàm băm của nó. Các "tree" tương ứng với các thư mục trong thư mục làm việc và chứa danh sách các "tree" và "blob" tương ứng với mỗi tệp tin trong thư mục đó. Mỗi "commit" lưu định danh của "tree" mức đầu, điều đó có nghĩa nó chứa tất cả các "blob" và các "tree" khác liên quan tới "commit" đó.

Nếu bạn tạo một "commit" sử dụng HEAD bị tách rời, "commit" cuối thật ra được tham chiếu bởi cái được gọi là: "reflog" cho HEAD. Tuy nhiên, nó sẽ không có hiệu lực sau một khoảng thời gian nhất định, do đó cuối cùng "commit" sẽ bị xoá bỏ, tương tự như các "commit" bị xoá bỏ với git commit --amend hoặc git rebase.


Bản quyền © 2010, Mark Lodato. Bản dịch tiếng Việt © 2013, Hoat Le.

Tài liệu được phát hành dưới giấy phép Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.

Want to translate into another language?