Nếu các hình không hiển thị tốt, bạn có thể xem phiên bản Không-SVG của trang này.
Các ảnh SVG không được cho phép hiển thị. (Hiển thị SVG)
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.
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.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ó 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.
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.
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.
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
.
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.)
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.)
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.
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.
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.