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

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

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

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”).

  • git add files sao chép các tệp tin (ở
    trạng thái hiện tại) tới vùng chuyển tiếp.
  • git commit lưu trữ bản ghi của vùng chuyển tiếp thành một
    “commit”.
  • git reset -- files bỏ các tệp tin vào khu chuyển
    tiếp; cụ thể lệnh này sẽ sao chép các tệp tin từ “commit” mới nhất
    tới khu chuyển tiếp. Sử dụng lệnh này để “huỷ bỏ” (undo) lệnh git
    add files
    . Bạn cũng có thể sử dụng lệnh git
    reset
    để bỏ mọi thứ vào khu chuyển tiếp.
  • git checkout -- files sao chép các tệp
    tin từ khu chuyển tiếp tới thư mục làm việc. Sử dụng lệnh này để bỏ hết
    những thay đổi hiện tại ở thư mục làm việc.

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.

  • git commit -a tương đương với việc chạy lệnh
    git add trên tất cả các tệp tin tồn tại trong “commit” mới nhất,
    sau đó chạy lệnh git commit.
  • git commit files tạo một “commit” mới chứa nội dung
    của “commit” mới nhất, cùng với bản ghi các tệp tin được lấy từ thư
    mục làm việc. Ngoài ra, các tệp tin cũng được sao chép tới khu
    chuyển tiếp.
  • git checkout HEAD -- files sap chép
    các tệp tin từ “commit” mới nhất tới cả hai khu chuyển tiếp và thư
    mục làm việ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” 169a6 và 2c33a), 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?