Đây không chính xác là một câu trả lời thực sự, nhưng tôi cần quyền truy cập vào định dạng và rất nhiều không gian. Tôi sẽ cố gắng mô tả lý thuyết đằng sau những gì tôi cho là hai câu trả lời tốt nhất: câu trả lời được chấp nhận và câu trả lời được xếp hạng hàng đầu (ít nhất là hiện tại) . Nhưng trên thực tế, họ trả lời các câu hỏi khác nhau .
Các cam kết trong Git rất thường "trên" nhiều nhánh tại một thời điểm. Thật vậy, đó là phần lớn những gì câu hỏi là về. Được:
...--F--G--H <-- master
\
I--J <-- develop
trong đó các chữ cái viết hoa đại diện cho ID băm Git thực tế, chúng tôi thường tìm kiếm chỉ cam kếtH
hoặc chỉ cam kếtI-J
trong git log
đầu ra của chúng tôi . Cam kết thông qua G
đều nằm trên cả hai nhánh, vì vậy chúng tôi muốn loại trừ chúng.
(Lưu ý rằng trong các biểu đồ được vẽ như thế này, các cam kết mới hơn hướng về bên phải. Các tên chọn một cam kết ngoài cùng bên phải trên dòng đó. Mỗi cam kết đó có một cam kết mẹ, là cam kết ở bên trái của chúng: cha của H
là G
, và cha mẹ của J
là I
. Cha mẹ của lại I
là G
. Cha mẹ của G
là F
và F
có cha mẹ đơn giản không được hiển thị ở đây: nó là một phần của ...
phần.)
Đối với trường hợp đặc biệt đơn giản này, chúng ta có thể sử dụng:
git log master..develop # note: two dots
để xem I-J
, hoặc:
git log develop..master # note: two dots
chỉ để xem H
. Tên bên phải, sau hai dấu chấm, nói với Git: vâng, những cam kết này . Tên bên trái, trước hai dấu chấm, nói với Git: không, không phải những cam kết này . Git bắt đầu ở cuối —at commit H
hoặc commit J
—và hoạt động ngược lại . Để biết thêm (nhiều) về điều này, hãy xem Think Like (a) Git .
Cách câu hỏi ban đầu được diễn đạt, mong muốn là tìm các cam kết có thể truy cập được từ một tên cụ thể, nhưng không phải từ bất kỳ tên nào khác trong cùng danh mục chung đó. Đó là, nếu chúng ta có một đồ thị phức tạp hơn:
O--P <-- name5
/
N <-- name4
/
...--F--G--H--I---M <-- name1
\ /
J-----K <-- name2
\
L <-- name3
chúng ta có thể chọn một trong những tên này, chẳng hạn như name4
hoặc name3
, và hỏi: tên đó có thể tìm thấy cam kết nào, nhưng không phải tên nào khác? Nếu chúng tôi chọn name3
câu trả lời là cam kết L
. Nếu chúng ta chọn name4
, câu trả lời là không có cam kết nào cả: cam kết mà name4
tên là cam kết N
nhưng N
có thể tìm thấy cam kết bằng cách bắt đầu tại name5
và làm việc ngược lại.
Câu trả lời được chấp nhận hoạt động với tên theo dõi từ xa, thay vì tên chi nhánh và cho phép bạn chỉ định một tên — tên được đánh vần origin/merge-only
— làm tên đã chọn và xem xét tất cả các tên khác trong không gian tên đó. Nó cũng tránh hiển thị các hợp nhất: nếu chúng tôi chọn name1
là "tên thú vị" và nói cho tôi biết các cam kết có thể truy cập được từ name1
chứ không phải bất kỳ tên nào khác , chúng tôi sẽ thấy cam kết hợp nhất M
cũng như cam kết thông thường I
.
Câu trả lời phổ biến nhất là khá khác nhau. Đó là tất cả về việc duyệt qua biểu đồ cam kết mà không theo cả hai chân của hợp nhất và không hiển thị bất kỳ cam kết nào được hợp nhất. name1
Ví dụ: nếu chúng tôi bắt đầu với , chúng tôi sẽ không hiển thị M
(đó là hợp nhất), nhưng giả sử cha mẹ đầu tiên của hợp nhất M
là cam kết I
, chúng tôi thậm chí sẽ không xem xét các cam kết J
và K
. Chúng tôi sẽ kết thúc cho thấy cam kết I
, và cũng cam kết H
, G
, F
, và vân vân-không ai trong số đó là những cam kết hợp nhất và tất cả đều có thể truy cập bằng cách bắt đầu ở M
và làm việc ngược, tham quan chỉ đầu tiên mẹ của mỗi hợp nhất cam kết.
Ví dụ, câu trả lời phổ biến nhất khá phù hợp với việc xem xét thời master
điểm master
dự định trở thành một nhánh chỉ hợp nhất. Nếu tất cả "công việc thực sự" được thực hiện trên các nhánh phụ mà sau đó được hợp nhất vào master
, chúng ta sẽ có một mẫu như sau:
I---------M---------N <-- master
\ / \ /
o--o--o o--o--o
trong đó tất cả các o
cam kết không có tên chữ cái là các cam kết thông thường (không hợp nhất) M
và N
là các cam kết hợp nhất. Cam kết I
là cam kết ban đầu: cam kết đầu tiên từng được thực hiện và là cam kết duy nhất phải ở chế độ chính mà không phải là cam kết hợp nhất. Nếu git log --first-parent --no-merges master
hiển thị bất kỳ cam kết nào khác I
, chúng tôi có một tình huống như sau:
I---------M----*----N <-- master
\ / \ /
o--o--o o--o--o
nơi chúng tôi muốn xem cam kết *
đã được thực hiện trực tiếp master
, không phải bằng cách hợp nhất một số nhánh tính năng.
Nói tóm lại, câu trả lời phổ biến là tuyệt vời để xem master
khi nào master
được dùng để chỉ hợp nhất, nhưng không tuyệt vời cho các tình huống khác. Câu trả lời được chấp nhận phù hợp với những tình huống khác này.
Tên theo dõi từ xa có giống như tên origin/master
chi nhánh không?
Một số phần của Git nói rằng chúng không phải là:
git checkout master
...
git status
nói on branch master
, nhưng:
git checkout origin/master
...
git status
nói HEAD detached at origin/master
. Tôi thích đồng ý với git checkout
/ git switch
: origin/master
không phải là tên chi nhánh bởi vì bạn không thể "trên" nó.
Câu trả lời được chấp nhận sử dụng tên theo dõi từ xa origin/*
làm "tên chi nhánh":
git log --no-merges origin/merge-only \
--not $(git for-each-ref --format="%(refname)" refs/remotes/origin |
grep -Fv refs/remotes/origin/merge-only)
Dòng giữa, gọi git for-each-ref
, lặp lại các tên theo dõi từ xa cho điều khiển từ xa được đặt tên origin
.
Lý do đây là một giải pháp tốt cho vấn đề ban đầu là ở đây chúng tôi quan tâm đến tên chi nhánh của người khác , hơn là tên chi nhánh của chúng tôi . Nhưng điều đó có nghĩa là chúng tôi đã định nghĩa chi nhánh là một cái gì đó khác với tên chi nhánh của chúng tôi . Tốt thôi: chỉ cần lưu ý rằng bạn đang làm điều này, khi bạn làm điều đó.
git log
duyệt qua một số phần của biểu đồ cam kết
Những gì chúng tôi thực sự đang tìm kiếm ở đây là một loạt những gì tôi đã gọi là con dao nhỏ: hãy xem Chính xác chúng ta có nghĩa là gì khi "chi nhánh"? Đó là, chúng tôi đang tìm kiếm các đoạn trong một số tập hợp con của biểu đồ cam kết tổng thể .
Bất cứ khi nào chúng ta có Git xem xét tên chi nhánh như master
, tên thẻ v2.1
, hoặc tên theo dõi từ xa origin/master
, chúng ta có xu hướng muốn Git cho chúng ta biết về cam kết đó và mọi cam kết mà chúng ta có thể nhận được từ cam kết đó: bắt đầu từ đó và làm việc ngược lại.
Trong toán học, điều này được gọi là đi bộ đồ thị . Đồ thị cam kết của Git là Đồ thị vòng tròn được hướng dẫn hoặc DAG và loại đồ thị này đặc biệt thích hợp để đi bộ. Khi xem một biểu đồ như vậy, người ta sẽ ghé thăm từng đỉnh của biểu đồ có thể truy cập được thông qua đường dẫn đang được sử dụng. Các đỉnh trong biểu đồ Git là các cam kết, với các cạnh là các cung — liên kết một chiều — đi từ mỗi con đến mỗi cha. (Đây là lúc Think Like (a) Git xuất hiện. Bản chất một chiều của cung có nghĩa là Git phải hoạt động ngược lại, từ con đến cha.)
Hai lệnh Git chính để đi đồ thị là git log
và git rev-list
. Các lệnh này cực kỳ giống nhau - trên thực tế, chúng hầu hết được xây dựng từ các tệp nguồn giống nhau - nhưng đầu ra của chúng khác nhau: git log
tạo ra đầu ra cho con người đọc, trong khi git rev-list
tạo đầu ra dành cho các chương trình Git khác đọc. 1 Cả hai lệnh đều thực hiện kiểu đi bộ đồ thị này.
Hành trình biểu đồ mà họ thực hiện cụ thể là: đưa ra một số tập hợp các cam kết điểm bắt đầu (có lẽ chỉ một cam kết, có lẽ là một loạt các ID băm, có lẽ là một loạt các tên phân giải thành ID băm), đi qua biểu đồ, truy cập các cam kết . Các lệnh cụ thể, chẳng hạn như --not
hoặc một tiền tố ^
, hoặc --ancestry-path
, hoặc --first-parent
, sửa đổi đường đi của biểu đồ theo một cách nào đó.
Khi họ thực hiện bước biểu đồ, họ truy cập từng cam kết. Nhưng họ chỉ in một số tập hợp con được chọn của các cam kết đã đi bộ. Các chỉ thị chẳng hạn như --no-merges
hoặc --before <date>
cho mã đi bộ đồ thị cam kết in .
Để thực hiện việc truy cập này, hãy cam kết một lần, hai lệnh này sử dụng một hàng đợi ưu tiên . Bạn chạy git log
hoặc git rev-list
và cung cấp cho nó một số cam kết xuất phát điểm. Họ đặt những cam kết đó vào hàng đợi ưu tiên. Ví dụ, một đơn giản:
git log master
biến tên master
thành một ID băm thô và đặt một ID băm đó vào hàng đợi. Hoặc là:
git log master develop
biến cả hai tên thành ID băm và — giả sử đây là hai ID băm khác nhau — đặt cả hai vào hàng đợi.
Mức độ ưu tiên của các cam kết trong hàng đợi này được xác định bởi nhiều đối số hơn. Ví dụ: đối số --author-date-order
cho biết git log
hoặc git rev-list
sử dụng dấu thời gian của tác giả , thay vì dấu thời gian của người xác nhận. Mặc định là sử dụng dấu thời gian của người xác nhận và chọn lần cam kết mới nhất theo ngày: cái có số ngày cao nhất. Vì vậy, với master develop
giả sử những cam kết này giải quyết cho hai cam kết khác nhau, Git sẽ hiển thị bất kỳ cái nào đến sau trước, bởi vì cái đó sẽ ở đầu hàng đợi.
Trong mọi trường hợp, mã đi bộ sửa đổi hiện chạy trong một vòng lặp:
- Trong khi có các cam kết trong hàng đợi:
- Loại bỏ mục nhập hàng đợi đầu tiên.
- Quyết định có in cam kết này hay không. Ví dụ
--no-merges
,: không in gì nếu nó là một cam kết hợp nhất; --before
: không in gì nếu ngày của nó không đến trước thời gian đã định. Nếu quá trình in không bị chặn, hãy in commit: for git log
, show log của nó; cho git rev-list
, in ID băm của nó.
- Đặt một số hoặc tất cả các commit gốc của commit này vào hàng đợi (miễn là nó không có ở đó bây giờ và chưa được truy cập 2 ). Mặc định bình thường là đặt tất cả các bậc cha mẹ. Việc sử dụng
--first-parent
ngăn chặn tất cả ngoại trừ cha đầu tiên của mỗi hợp nhất.
(Cả hai git log
và đều git rev-list
có thể thực hiện đơn giản hóa lịch sử có hoặc không có viết lại của cha mẹ vào thời điểm này, nhưng chúng tôi sẽ bỏ qua điều đó ở đây.)
Đối với một chuỗi đơn giản, như bắt đầu tại HEAD
và hoạt động ngược lại khi không có cam kết hợp nhất, hàng đợi luôn có một cam kết trong đó ở đầu vòng lặp. Có một cam kết, vì vậy chúng tôi bật nó ra và in nó ra và đưa cha mẹ (đơn) của nó vào hàng đợi và đi vòng lại, và chúng tôi theo chuỗi ngược lại cho đến khi đạt được cam kết đầu tiên hoặc người dùng cảm thấy mệt mỏi với git log
đầu ra và thoát chương trình. Trong trường hợp này, không có tùy chọn đặt hàng nào quan trọng: chỉ có một cam kết hiển thị.
Khi có các hợp nhất và chúng tôi theo dõi cả cha mẹ — cả hai “chân” của hợp nhất — hoặc khi bạn cung cấp git log
hoặc git rev-list
nhiều hơn một cam kết bắt đầu, các tùy chọn sắp xếp sẽ quan trọng.
Cuối cùng, hãy xem xét hiệu ứng của --not
hoặc ^
trước một trình xác định cam kết. Có một số cách để viết chúng:
git log master --not develop
hoặc là:
git log ^develop master
hoặc là:
git log develop..master
tất cả đều có nghĩa giống nhau. Các --not
giống như tiền tố ^
ngoại trừ việc nó được áp dụng cho nhiều hơn một tên:
git log ^branch1 ^branch2 branch3
có nghĩa là không phải nhánh1, không phải nhánh2, có nhánh3; nhưng:
git log --not branch1 branch2 branch3
có nghĩa là không phải nhánh1, không phải nhánh2, không phải nhánh3 và bạn phải sử dụng một giây --not
để tắt nó:
git log --not branch1 branch2 --not branch3
đó là một chút khó xử. Hai lệnh "not" được kết hợp thông qua XOR, vì vậy nếu bạn thực sự muốn, bạn có thể viết:
git log --not branch1 branch2 ^branch3
có nghĩa là không phải nhánh1, không phải nhánh2 , có nhánh3 , nếu bạn muốn xáo trộn .
Tất cả những điều này đều hoạt động bằng cách ảnh hưởng đến bước đi của đồ thị. Khi git log
hoặc git rev-list
đi qua biểu đồ, nó đảm bảo không đưa vào hàng đợi ưu tiên bất kỳ cam kết nào có thể truy cập được từ bất kỳ tham chiếu phủ định nào . (Trên thực tế, chúng cũng ảnh hưởng đến thiết lập bắt đầu: các cam kết bị phủ định không thể đi vào hàng đợi ưu tiên ngay từ dòng lệnh, vì vậy git log master ^master
, chẳng hạn như không hiển thị gì.)
Tất cả các cú pháp ưa thích được mô tả trong tài liệu gitrevisions đều sử dụng điều này và bạn có thể hiển thị điều này bằng một cuộc gọi đơn giản tới git rev-parse
. Ví dụ:
$ git rev-parse origin/pu...origin/master # note: three dots
b34789c0b0d3b137f0bb516b417bd8d75e0cb306
fc307aa3771ece59e174157510c6db6f0d4b40ec
^b34789c0b0d3b137f0bb516b417bd8d75e0cb306
Cú pháp ba chấm có nghĩa là các cam kết có thể truy cập được từ bên trái hoặc bên phải, nhưng loại trừ các cam kết có thể truy cập được từ cả hai . Trong trường hợp này, bản origin/master
cam kết, b34789c0b
có thể truy cập được từ origin/pu
( fc307aa37...
), do đó, origin/master
mã băm xuất hiện hai lần, một lần với một phủ định, nhưng trên thực tế, Git đạt được cú pháp ba chấm bằng cách đưa vào hai tham chiếu tích cực — hai ID băm không bị phủ định — và một âm một, được biểu thị bằng ^
tiền tố.
Simiarly:
$ git rev-parse master^^@
2c42fb76531f4565b5434e46102e6d85a0861738
2f0a093dd640e0dad0b261dae2427f2541b5426c
Các ^@
phương tiện cú pháp tất cả các bậc cha mẹ của các trao cam kết , và master^
bản thân-phụ huynh đầu tiên của các cam kết được lựa chọn theo ngành tên master
-là một hợp nhất cam kết, vì vậy nó có hai cha mẹ. Đây là hai phụ huynh. Và:
$ git rev-parse master^^!
0b07eecf6ed9334f09d6624732a4af2da03e38eb
^2c42fb76531f4565b5434e46102e6d85a0861738
^2f0a093dd640e0dad0b261dae2427f2541b5426c
Các ^!
phương tiện hậu tố cam kết chính nó, nhưng không ai trong số các bậc cha mẹ của nó . Trong trường hợp này, master^
là 0b07eecf6...
. Chúng tôi đã thấy cả cha và mẹ với ^@
hậu tố; chúng ở đây một lần nữa, nhưng lần này, bị phủ định.
1 Nhiều chương trình Git chạy theo đúng nghĩa đen git rev-list
với các tùy chọn khác nhau và đọc đầu ra của nó, để biết những gì cam kết và / hoặc các đối tượng Git khác sẽ sử dụng.
2 Bởi vì biểu đồ có dạng xoay vòng , nên có thể đảm bảo rằng chưa có ai được truy cập, nếu chúng ta thêm ràng buộc sẽ không bao giờ hiển thị cha mẹ trước khi hiển thị tất cả các con của nó ở mức ưu tiên. --date-order
, --author-date-order
và --topo-order
thêm ràng buộc này. Thứ tự sắp xếp mặc định — không có tên — không. Nếu dấu thời gian cam kết không ổn định — ví dụ: nếu một số cam kết được thực hiện "trong tương lai" bởi một máy tính có đồng hồ bị tắt — điều này trong một số trường hợp có thể dẫn đến kết quả trông kỳ lạ.
Nếu bạn đã làm được như vậy, bây giờ bạn biết rất nhiều về git log
Tóm lược:
git log
là hiển thị một số cam kết đã chọn trong khi xem một số hoặc tất cả một số phần của biểu đồ.
- Các
--no-merges
đối số, được tìm thấy trong cả hai chấp nhận và các câu trả lời hiện-top xếp hạng, ngăn chặn hiển thị một số cam kết rằng là đi.
- Đối
--first-parent
số, từ câu trả lời hiện đang được xếp hạng cao nhất, ngăn chặn việc đi một số phần của biểu đồ, trong chính quá trình đi bộ của biểu đồ.
- Các
--not
tiền tố để đối số dòng lệnh, như được sử dụng trong các câu trả lời được chấp nhận, ngăn chặn từng đến thăm một số bộ phận của đồ thị ở tất cả, ngay từ đầu.
Chúng tôi nhận được câu trả lời mà chúng tôi thích, cho hai câu hỏi khác nhau, bằng cách sử dụng các tính năng này.
git log ^branch1 ^branch2 merge-only-branch
cú pháp?