Git: Refs và Reflog


Các khóa học miễn phí qua video:
Lập trình C Java C# SQL Server PHP HTML5-CSS3-JavaScript

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.

Nhiều cách để đề cập đến một cam kết

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 checkoutgit branch, và git pushbằ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 showcá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-parselệnh. Sau đây trả về hàm băm của cam kết được chỉ ra bởi masternhá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-parsebì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/refsthư mục, nơi .gitthườ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 headsxá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 catlệ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 masterchi nhánh, tất cả Git phải làm là thay đổi nội dung của refs/heads/mastertệ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 tagshoạ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ư remotesmục liệt kê tất cả các kho lưu trữ từ xa mà bạn đã tạo với git remotecá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-featuresố 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-featuretrướ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 refsthư mục thành một tệp duy nhất được gọi packed-refsnằm ở đầu .gitthư 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/refsthư 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 refsthư mục, có một vài ref đặc biệt nằm trong .gitthư 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ưu HEADtrướ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ại git 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 pulllệnh đầu tiên chạy git fetch, cập nhật FETCH_HEADtham 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 HEADcó 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 HEADkhi bạn ở masterchi nhánh:

git checkout master
cat .git/HEAD

Điều này sẽ xuất ra ref: refs/heads/master, có nghĩa là HEADtrỏ đến refs/heads/masterref. Đây là cách Git biết rằng masterchi 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 HEADsẽ đượ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, HEADsẽ 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, HEADlà 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 pushvà 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 pushlệnh để đặt tên khác cho nhánh từ xa. Ví dụ, lệnh sau sẽ đẩy masternhánh tới originrepo từ xa như bình thường git push, nhưng nó sử dụng qa-masterlàm tên cho nhánh trong originrepo. Đ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 --deletecờ 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 fetchtì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/configtập tin:

[remote "origin"]
url = https://git@github.com:mary/example-repo.git
fetch = +refs/heads/*:refs/remotes/origin/*

Các fetchdòng kể git fetchđể tải tất cả các chi nhánh từ originrepo. 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 masterchi nhánh. Để chỉ tìm nạp masterchi nhánh, thay đổi fetchdò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 pushtheo cách tương tự. Chẳng hạn, nếu bạn muốn luôn đẩy masternhánh tới qa-mastertrong 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 pushvà git fetchchỉ 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 mergelệ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 HEADlà 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ừ Aviệ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.

Truy cập các cam kết bằng cách sử dụng ref

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 refloglệ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 featurechi 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 resetgiờ đ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 HEADtrạ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 .gitthư mục con, cách đọc packed-refstệp, cách trình HEADbà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 loggit showgit checkoutgit resetgit revertgit rebase, và nhiều người khác.


Các khóa học miễn phí qua video:
Lập trình C Java C# SQL Server PHP HTML5-CSS3-JavaScript
« Prev: Git: Nhật ký Git nâng cao
» Next: Git: Git LFS
Copied !!!