Git: Refs và Reflog
Git là tất cả về các cam kết: bạn thực hiện các cam kết, tạo các cam kết, xem các cam kết cũ và chuyển các cam kết giữa các kho lưu trữ bằng nhiều lệnh Git khác nhau. Phần lớn các lệnh này hoạt động trên một cam kết ở dạng này hay dạng khác và nhiều trong số chúng chấp nhận một tham chiếu cam kết như là một tham số. Ví dụ: bạn có thể sử dụng git checkout
để xem một cam kết cũ bằng cách chuyển vào một hàm băm cam kết hoặc bạn có thể sử dụng nó để chuyển các nhánh bằng cách chuyển vào một tên nhánh.
Bằng cách hiểu nhiều cách để đề cập đến một cam kết, bạn thực hiện tất cả các lệnh này mạnh hơn nhiều. Trong chương này, chúng tôi sẽ làm sáng tỏ về các hoạt động nội bộ của các lệnh phổ biến như git checkout
, git branch
, và git push
bằng cách khám phá những phương pháp nhiều đề cập đến một cam kết.
Chúng ta cũng sẽ tìm hiểu làm thế nào để hồi sinh những cam kết có vẻ như đã bị mất bằng cách truy cập chúng thông qua cơ chế reflog của Git.
Băm
Cách trực tiếp nhất để tham chiếu một cam kết là thông qua hàm băm SHA-1 của nó. Điều này hoạt động như ID duy nhất cho mỗi cam kết. Bạn có thể tìm thấy hàm băm của tất cả các cam kết của bạn trong git log
đầu ra.
commit 0c708fdec272bc4446c6cabea4f0022c2b616eba
Author: Mary Johnson <mary@example.com>
Date: Wed Jul 9 16:37:42 2014 -0500
Some commit message
Khi chuyển giao cam kết cho các lệnh Git khác, bạn chỉ cần chỉ định đủ các ký tự để xác định duy nhất cam kết. Ví dụ: bạn có thể kiểm tra cam kết trên bằng git show
cách chạy lệnh sau:
git show 0c708f
Đôi khi cần phải giải quyết một nhánh, thẻ hoặc tham chiếu gián tiếp khác vào hàm băm tương ứng. Đối với điều này, bạn có thể sử dụng git rev-parse
lệnh. Sau đây trả về hàm băm của cam kết được chỉ ra bởi master
nhánh:
git rev-parse master
Điều này đặc biệt hữu ích khi viết các tập lệnh tùy chỉnh chấp nhận tham chiếu cam kết. Thay vì phân tích cú pháp tham chiếu cam kết bằng tay, bạn có thể để git rev-parse
bình thường hóa đầu vào cho bạn.
Tham chiếu
Một ref là một cách gián tiếp để đề cập đến một cam kết. Bạn có thể nghĩ về nó như một bí danh thân thiện với người dùng cho hàm băm cam kết. Đây là cơ chế nội bộ của Git đại diện cho các chi nhánh và thẻ.
Tham chiếu được lưu trữ dưới dạng tệp văn bản bình thường trong .git/refs
thư mục, nơi .git
thường được gọi .git
. Để khám phá các ref trong một trong các kho của bạn, hãy điều hướng đến .git/refs
. Bạn sẽ thấy cấu trúc sau, nhưng nó sẽ chứa các tệp khác nhau tùy thuộc vào các nhánh, thẻ và điều khiển từ xa mà bạn có trong repo của mình:
.git/refs/
heads/
master
some-feature
remotes/
origin/
master
tags/
v0.9
Thư mục heads
xác định tất cả các nhánh cục bộ trong kho lưu trữ của bạn. Mỗi tên tệp khớp với tên của nhánh tương ứng và bên trong tệp bạn sẽ tìm thấy hàm băm cam kết. Cam kết băm này là vị trí của đỉnh của chi nhánh. Để xác minh điều này, hãy thử chạy hai lệnh sau từ thư mục gốc của kho Git:
# Output the contents of `refs/heads/master` file:
cat .git/refs/heads/master
# Inspect the commit at the tip of the `master` branch:
git log -1 master
Băm xác nhận được trả về bởi cat
lệnh phải khớp với ID cam kết được hiển thị bởi git log
.
Để thay đổi vị trí của master
chi nhánh, tất cả Git phải làm là thay đổi nội dung của refs/heads/master
tệp. Tương tự như vậy, việc tạo một nhánh mới chỉ đơn giản là việc viết một hàm băm cam kết vào một tệp mới. Đây là một phần lý do tại sao các chi nhánh Git rất nhẹ so với SVN.
Thư mục tags
hoạt động theo cùng một cách chính xác, nhưng nó chứa các thẻ thay vì các nhánh. Thư remotes
mục liệt kê tất cả các kho lưu trữ từ xa mà bạn đã tạo với git remote
các thư mục con riêng biệt. Trong mỗi cái, bạn sẽ tìm thấy tất cả các nhánh từ xa đã được tìm nạp vào kho lưu trữ của bạn.
Chỉ định giới thiệu
Khi chuyển một ref cho lệnh Git, bạn có thể xác định tên đầy đủ của ref hoặc sử dụng tên ngắn và để Git tìm kiếm một ref phù hợp. Bạn đã quen thuộc với các tên ngắn cho giới thiệu, vì đây là những gì bạn đang sử dụng mỗi khi bạn đề cập đến một chi nhánh theo tên.
git show some-feature
Đối some-feature
số trong lệnh trên thực sự là một tên ngắn cho nhánh. Git giải quyết điều này refs/heads/some-feature
trước khi sử dụng nó. Bạn cũng có thể chỉ định tham chiếu đầy đủ trên dòng lệnh, như vậy:
git show refs/heads/some-feature
Điều này tránh bất kỳ sự mơ hồ liên quan đến vị trí của ref. Điều này là cần thiết, ví dụ, nếu bạn có cả thẻ và nhánh được gọi some-feature
. Tuy nhiên, nếu bạn đang sử dụng các quy ước đặt tên thích hợp, sự mơ hồ giữa các thẻ và các nhánh thường không phải là một vấn đề.
Chúng ta sẽ thấy nhiều tên ref đầy đủ hơn trong phần Refspecs.
Tham khảo đóng gói
Đối với các kho lưu trữ lớn, Git sẽ định kỳ thực hiện một bộ sưu tập rác để loại bỏ các đối tượng không cần thiết và nén refs vào một tệp để có hiệu suất cao hơn. Bạn có thể buộc nén này bằng lệnh thu gom rác:
git gc
Thao tác này sẽ chuyển tất cả các tệp nhánh và thẻ riêng lẻ trong refs
thư mục thành một tệp duy nhất được gọi packed-refs
nằm ở đầu .git
thư mục. Nếu bạn mở tệp này, bạn sẽ tìm thấy ánh xạ băm cam kết tới refs:
00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature
0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/master
bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9
Ở bên ngoài, chức năng Git bình thường sẽ không bị ảnh hưởng theo bất kỳ cách nào. Nhưng, nếu bạn đang tự hỏi tại sao .git/refs
thư mục của bạn trống, thì đây là nơi giới thiệu đã đi.
Giới thiệu đặc biệt
Ngoài refs
thư mục, có một vài ref đặc biệt nằm trong .git
thư mục cấp cao nhất . Chúng được liệt kê dưới đây:
HEAD
- Cam kết / chi nhánh hiện đang thanh toán.FETCH_HEAD
- Chi nhánh được tải gần đây nhất từ một repo từ xa.ORIG_HEAD
- Một tài liệu tham khảo sao lưuHEAD
trước khi thay đổi mạnh mẽ cho nó.MERGE_HEAD
- Các cam kết mà bạn đang hợp nhất vào chi nhánh hiện tạigit merge
.CHERRY_PICK_HEAD
- Cam kết rằng bạn đang chọn anh đào.
Những ref này đều được Git tạo và cập nhật khi cần thiết. Ví dụ, git pull
lệnh đầu tiên chạy git fetch
, cập nhật FETCH_HEAD
tham chiếu. Sau đó, nó chạy git merge FETCH_HEAD
để hoàn thành việc kéo các nhánh được tìm nạp vào kho lưu trữ. Tất nhiên, bạn có thể sử dụng tất cả những thứ này giống như bất kỳ ref nào khác, vì tôi chắc chắn rằng bạn đã hoàn thành HEAD
.
Các tệp này chứa nội dung khác nhau tùy thuộc vào loại của chúng và trạng thái của kho lưu trữ của bạn. Tham chiếu HEAD
có thể chứa một tham chiếu tượng trưng , chỉ đơn giản là một tham chiếu đến một tham chiếu khác thay vì băm xác nhận hoặc băm xác nhận. Ví dụ: hãy xem nội dung HEAD
khi bạn ở master
chi nhánh:
git checkout master
cat .git/HEAD
Điều này sẽ xuất ra ref: refs/heads/master
, có nghĩa là HEAD
trỏ đến refs/heads/master
ref. Đây là cách Git biết rằng master
chi nhánh hiện đang được kiểm tra. Nếu bạn chuyển sang chi nhánh khác, nội dung của HEAD
sẽ được cập nhật để phản ánh chi nhánh mới. Nhưng, nếu bạn kiểm tra một cam kết thay vì một nhánh, HEAD
sẽ chứa một hàm băm cam kết thay vì một tham chiếu tượng trưng. Đây là cách Git biết rằng nó ở trạng thái CHÍNH tách rời.
Đối với hầu hết các phần, HEAD
là tài liệu tham khảo duy nhất mà bạn sẽ sử dụng trực tiếp. Những cái khác thường chỉ hữu ích khi viết các tập lệnh cấp thấp hơn cần nối vào hoạt động nội bộ của Git.
Giới thiệu
Một refspec ánh xạ một nhánh trong kho lưu trữ cục bộ thành một nhánh trong một kho lưu trữ từ xa. Điều này cho phép quản lý các nhánh từ xa bằng cách sử dụng các lệnh Git cục bộ và định cấu hình một số hành vi git push
và nâng cao git fetch
.
Một refspec được chỉ định là [+]<src>:<dst>
. Các <src>
tham số là chi nhánh nguồn trong kho địa phương, và các <dst>
tham số là chi nhánh đích trong kho lưu trữ từ xa. +
Dấu hiệu tùy chọn là để buộc kho lưu trữ từ xa thực hiện cập nhật không chuyển tiếp nhanh.
Refspecs có thể được sử dụng với git push
lệnh để đặt tên khác cho nhánh từ xa. Ví dụ, lệnh sau sẽ đẩy master
nhánh tới origin
repo từ xa như bình thường git push
, nhưng nó sử dụng qa-master
làm tên cho nhánh trong origin
repo. Điều này rất hữu ích cho các nhóm QA cần đẩy chi nhánh của mình đến một repo từ xa.
git push origin master:refs/heads/qa-master
Bạn cũng có thể sử dụng refspec để xóa các nhánh từ xa. Đây là một tình huống phổ biến cho các luồng công việc nhánh tính năng đẩy các nhánh tính năng đến một repo từ xa (ví dụ: cho mục đích sao lưu). Các nhánh tính năng từ xa vẫn nằm trong repo từ xa sau khi chúng bị xóa khỏi repo cục bộ, do đó bạn có thể xây dựng các nhánh tính năng chết khi dự án của bạn tiến triển. Bạn có thể xóa chúng bằng cách đẩy một refspec có <src>
tham số trống , như vậy:
git push origin :some-feature
Điều này rất thuận tiện, vì bạn không cần phải đăng nhập vào kho lưu trữ từ xa và xóa thủ công chi nhánh từ xa. Lưu ý rằng kể từ Git v1.7.0, bạn có thể sử dụng --delete
cờ thay vì phương pháp trên. Sau đây sẽ có tác dụng tương tự như lệnh trên:
git push origin --delete some-feature
Bằng cách thêm một vài dòng vào tệp cấu hình Git, bạn có thể sử dụng refspec để thay đổi hành vi của git fetch
. Theo mặc định, git fetch
tìm nạp tất cả các nhánh trong kho lưu trữ từ xa. Lý do cho điều này là phần sau của .git/config
tập tin:
[remote "origin"]
url = https://git@github.com:mary/example-repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Các fetch
dòng kể git fetch
để tải tất cả các chi nhánh từ origin
repo. Nhưng, một số quy trình công việc không cần tất cả chúng. Ví dụ, nhiều quy trình tích hợp liên tục chỉ quan tâm đến master
chi nhánh. Để chỉ tìm nạp master
chi nhánh, thay đổi fetch
dòng cho phù hợp với các mục sau:
[remote "origin"]
url = https://git@github.com:mary/example-repo.git
fetch = +refs/heads/master:refs/remotes/origin/master
Bạn cũng có thể cấu hình git push
theo cách tương tự. Chẳng hạn, nếu bạn muốn luôn đẩy master
nhánh tới qa-master
trong origin
điều khiển từ xa (như chúng tôi đã làm ở trên), bạn sẽ thay đổi tệp cấu hình thành:
[remote "origin"]
url = https://git@github.com:mary/example-repo.git
fetch = +refs/heads/master:refs/remotes/origin/master
push = refs/heads/master:refs/heads/qa-master
Refspec cung cấp cho bạn toàn quyền kiểm soát cách các lệnh Git khác nhau chuyển các nhánh giữa các kho lưu trữ. Chúng cho phép bạn đổi tên và xóa các nhánh khỏi kho lưu trữ cục bộ của bạn, tìm nạp / đẩy đến các nhánh có tên khác nhau và định cấu hình git push
và git fetch
chỉ hoạt động với các nhánh mà bạn muốn.
Tham chiếu tương đối
Bạn cũng có thể tham khảo các cam kết liên quan đến một cam kết khác. Các ~
nhân vật cho phép bạn đạt được cam kết cha mẹ. Ví dụ: phần sau đây hiển thị ông bà của HEAD
:
git show HEAD~2
Nhưng, khi làm việc với các cam kết hợp nhất, mọi thứ trở nên phức tạp hơn một chút. Vì các cam kết hợp nhất có nhiều hơn một cha mẹ, nên có nhiều hơn một đường dẫn mà bạn có thể đi theo. Đối với hợp nhất 3 chiều, cha mẹ đầu tiên là từ nhánh mà bạn đã bật khi bạn thực hiện hợp nhất và cha mẹ thứ hai là từ nhánh mà bạn đã truyền cho git merge
lệnh.
Nhân ~
vật sẽ luôn theo cha mẹ đầu tiên của một cam kết hợp nhất. Nếu bạn muốn theo cha mẹ khác, bạn cần chỉ định cái nào có ^
ký tự. Ví dụ: nếu HEAD
là một cam kết hợp nhất, thì sau đây trả về cha mẹ thứ hai của HEAD
.
git show HEAD^2
Bạn có thể sử dụng nhiều hơn một ^
ký tự để di chuyển nhiều hơn một thế hệ. Chẳng hạn, cái này hiển thị ông bà của HEAD
(giả sử đó là một cam kết hợp nhất) nằm trên cha mẹ thứ hai .
git show HEAD^2^1
Để làm rõ cách thức ~
và ^
hoạt động, hình dưới đây chỉ cho bạn cách đạt được bất kỳ cam kết nào từ A
việc sử dụng các tham chiếu tương đối. Trong một số trường hợp, có nhiều cách để đạt được cam kết.
Các ref tương đối có thể được sử dụng với cùng các lệnh mà một ref thông thường có thể được sử dụng. Ví dụ: tất cả các lệnh sau sử dụng tham chiếu tương đối:
# Only list commits that are parent of the second parent of a merge commit
git log HEAD^2
# Remove the last 3 commits from the current branch
git reset HEAD~3
# Interactively rebase the last 3 commits on the current branch
git rebase -i HEAD~3
Các giới thiệu
Các reflog là mạng lưới an toàn của Git. Nó ghi lại hầu hết mọi thay đổi bạn thực hiện trong kho lưu trữ của mình, bất kể bạn có cam kết ảnh chụp nhanh hay không. Bạn có thể nghĩ về nó như một lịch sử theo thời gian của tất cả mọi thứ bạn đã thực hiện trong repo địa phương của bạn. Để xem reflog, chạy git reflog
lệnh. Nó sẽ xuất ra một cái gì đó trông như sau:
400e4b7 HEAD@{0}: checkout: moving from master to HEAD~2
0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `master`
00f5425 HEAD@{2}: commit (merge): Merge branch ';feature';
ad8621a HEAD@{3}: commit: Finish the feature
Điều này có thể được dịch như sau:
- Bạn vừa kiểm tra
HEAD~2
- Trước đó bạn đã sửa đổi một thông điệp cam kết
- Trước đó bạn đã sáp nhập
feature
chi nhánh vàomaster
- Trước đó bạn đã cam kết một ảnh chụp nhanh
Các HEAD{<n>}
cú pháp cho phép bạn tham chiếu cam kết lưu trong reflog. Nó hoạt động rất giống như các HEAD~<n>
tài liệu tham khảo từ phần trước, nhưng <n>
đề cập đến một mục trong reflog thay vì lịch sử cam kết.
Bạn có thể sử dụng điều này để trở lại trạng thái sẽ bị mất. Ví dụ: giả sử bạn vừa loại bỏ một tính năng mới git reset
. Reflog của bạn có thể trông giống như thế này:
ad8621a HEAD@{0}: reset: moving to HEAD~3
298eb9f HEAD@{1}: commit: Some other commit message
bbe9012 HEAD@{2}: commit: Continue the feature
9cb79fa HEAD@{3}: commit: Start a new feature
Ba cam kết trước git reset
giờ đang lơ lửng, điều đó có nghĩa là không có cách nào để tham khảo chúng, ngoại trừ thông qua các reflog. Bây giờ, giả sử bạn nhận ra rằng bạn không nên vứt bỏ tất cả công việc của mình. Tất cả bạn phải làm là kiểm tra HEAD@{1}
cam kết để trở lại trạng thái của kho lưu trữ của bạn trước khi bạn chạy git reset
.
git checkout HEAD@{1}
Điều này đặt bạn vào HEAD
trạng thái tách rời . Từ đây, bạn có thể tạo một chi nhánh mới và tiếp tục làm việc trên tính năng của mình.
Tóm lược
Bây giờ bạn sẽ khá thoải mái khi tham khảo các cam kết trong kho Git. Chúng tôi đã tìm hiểu cách các nhánh và thẻ được lưu trữ dưới dạng ref trong .git
thư mục con, cách đọc packed-refs
tệp, cách trình HEAD
bày, cách sử dụng refspecs để đẩy và tìm nạp nâng cao và cách sử dụng họ hàng ~
và ^
toán tử để vượt qua hệ thống phân cấp.
Chúng tôi cũng đã xem qua reflog, đây là một cách để tham chiếu các cam kết không có sẵn thông qua bất kỳ phương tiện nào khác. Đây là một cách tuyệt vời để phục hồi từ những Oops nhỏ đó, tôi không nên thực hiện những tình huống đó.
Điểm quan trọng của tất cả những điều này là có thể chọn ra chính xác cam kết mà bạn cần trong bất kỳ kịch bản phát triển nhất định nào. Nó rất dễ dàng để tận dụng những kỹ năng bạn đã học trong bài viết này để chống lại kiến thức Git hiện tại của bạn, như một số các lệnh phổ biến nhất chấp nhận refs như các đối số, bao gồm git log
, git show
, git checkout
, git reset
, git revert
, git rebase
, và nhiều người khác.