Làm thế nào để tôi tìm thấy cam kết tiếp theo trong git? (con / con của ref)


236

ref^đề cập đến các cam kết trước ref, còn về việc nhận các cam kết sau ref thì sao?

Ví dụ, nếu tôi git checkout 12345làm thế nào để kiểm tra cam kết tiếp theo?

Cảm ơn.

PS Có, git là cây cấu trúc con trỏ nút DAG bất cứ điều gì. Làm thế nào để tôi tìm thấy cam kết sau khi này?



2
Xem thêm " git children-of"!
VonC

Câu trả lời:


185

Để liệt kê tất cả các cam kết, bắt đầu từ hiện tại, và sau đó là con của nó, v.v. - về cơ bản là nhật ký git tiêu chuẩn, nhưng đi theo cách khác trong thời gian, sử dụng một cái gì đó như

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

trong đó 894e8b4e93d8f3 là cam kết đầu tiên bạn muốn thể hiện.


3
Đối với trường hợp cụ thể trong câu hỏi ban đầu, chỉ cần thay thế HEAD^cho 894e8b4e93d8f3^.
Søren Løvborg

2
Có lẽ, add --oneline là kết quả đầu ra ngắn gọn tốt hơn.
firo

1
Tôi cần sử dụng ..., ^..thất bại trong âm thầm
TankorSmash

1
Bắt đầu fatal: unrecognized argument: --ancestry-pathphiên bản git 1.7.1
user151841

6
Điều này sẽ chỉ hoạt động nếu mastertrên đường dẫn tổ tiên của cam kết hiện tại. Xem đoạn mã thứ hai của câu trả lời của tôi để biết giải pháp sẽ hoạt động trong mọi trường hợp.
Tom Hale

37

Tác giả của Hudson (nay là Jenkins) Kohsuke Kawaguchi vừa xuất bản (tháng 11 năm 2013):
kohsuke / git-children-of :

Đưa ra một cam kết, tìm con ngay lập tức của cam kết đó.

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

Như được minh họa bởi chủ đề này , trong một VCS dựa trên lịch sử được đại diện bởi DAG (Đồ thị theo chu kỳ có hướng) , không có "một phụ huynh" hay "một đứa trẻ".

        C1 -> C2 -> C3
      /               \
A -> B                  E -> F
      \               /
        D1 -> D2 ----/

Việc đặt hàng các cam kết được thực hiện bằng "thứ tự hàng đầu" hoặc "thứ tự ngày" (xem sách GitPro )

Nhưng kể từ Git1.6.0 , bạn có thể liệt kê các con của một cam kết.

git rev-list --children
git log --children

Lưu ý: đối với các xác nhận gốc , bạn có cùng một vấn đề, với hậu tố ^cho một tham số sửa đổi có nghĩa là cha mẹ đầu tiên của đối tượng cam kết đó. ^<n>có nghĩa là <n>cha mẹ thứ (nghĩa rev^ là tương đương với rev^1).

Nếu bạn ở chi nhánh foovà phát hành " git merge bar" thì foosẽ là cha mẹ đầu tiên.
Tức là: Cha mẹ đầu tiên là chi nhánh bạn đã tham gia khi bạn sáp nhập và thứ hai là cam kết trên chi nhánh mà bạn đã hợp nhất.


6
git rev-list --childrenchắc chắn trông giống như những gì tôi muốn, nhưng nó không phải là DWIM. Nó xuất hiện để liệt kê tất cả các bậc cha mẹ và con cái của họ. Tôi cho rằng tôi có thể liệt kê tất cả chúng và phân tích thông qua chúng ... bleh, nhưng đó là một cái gì đó.
Schwern

@Schwern: đúng, git rev-list --childrenkhông phải chỉ liệt kê trẻ em, mà là liệt kê cha mẹ với con cái của bạn ... bạn luôn cần phân tích cú pháp.
VonC

Tôi đã thử mã đó trên một cam kết với hai đứa trẻ: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. Bạn nhận được kết quả đầu ra nào?
Tom Hale

@TomHale Tôi không thể kiểm tra nó ngay bây giờ, nhưng hỏi một câu hỏi mới (với phiên bản OS và Git), theo cách đó mọi người đều có thể kiểm tra.
VonC

1
Vấn đề duy nhất git-children-oflà nó sử dụng mô tả git cố gắng định dạng SHA là có thể đọc được bằng con người, có thể thất bại với lỗi của @ TomHale và đưa ra kết quả như v1.0.4-14-g2414721vậy là khó hiểu nếu bạn mong đợi SHA. Thay thế nó bằng một đơn giản echolàm cho điều này trở thành một công cụ tuyệt vời, cảm ơn!
Nickolay

18

những gì tôi tìm thấy là

git rev-list --ancestry-path commit1..commit2

nơi tôi đặt commit1làm cam kết hiện tại và commit2cho người đứng đầu hiện tại. Điều này trả về cho tôi một danh sách tất cả các cam kết xây dựng một đường dẫn giữa commit1commit2 .

Dòng cuối cùng của đầu ra là con của commit1 (trên đường dẫn tới commit2).


3
Vì vậy, chỉ cần thêm | tail -1để có được đứa trẻ.
Jesse Glick

8

Tôi hiểu bạn muốn nói gì. Thật khó chịu khi có cú pháp dồi dào để đi đến các cam kết trước đó, nhưng không có cú pháp nào để đi đến các lần tiếp theo. Trong một lịch sử phức tạp, vấn đề "cam kết tiếp theo" trở nên khá khó khăn, nhưng sau đó, trong sự hợp nhất phức tạp, độ cứng tương tự cũng xuất hiện với các cam kết 'trước đó'. Trong trường hợp đơn giản, bên trong một nhánh duy nhất có lịch sử tuyến tính (thậm chí chỉ cục bộ đối với một số cam kết giới hạn), sẽ rất tốt và có ý nghĩa để tiến lên và lùi lại.

Tuy nhiên, vấn đề thực sự với điều này là các trẻ em không được tham chiếu, đó chỉ là một danh sách liên kết ngược. Việc tìm kiếm cam kết của con mất một tìm kiếm, điều đó không quá tệ, nhưng có lẽ không phải thứ gì đó git muốn đưa vào logic refspec.

Bằng mọi giá, tôi đã gặp phải câu hỏi này bởi vì tôi chỉ đơn giản muốn bước tiếp trong lịch sử từng cam kết, thực hiện các bài kiểm tra và đôi khi bạn phải bước về phía trước và không lùi bước. Vâng, với một số suy nghĩ nhiều hơn, tôi đã đưa ra giải pháp này:

Chọn một cam kết trước nơi bạn đang ở. Đây có thể là một người đứng đầu chi nhánh. Nếu bạn đang ở chi nhánh ~ 10, thì "chi nhánh kiểm tra git ~ 9" rồi "chi nhánh kiểm tra git ~ 8" để có được chi nhánh tiếp theo sau đó, sau đó "chi nhánh kiểm tra git ~ 7", v.v.

Giảm số nên thực sự dễ dàng trong một kịch bản nếu bạn cần nó. Dễ dàng hơn nhiều so với phân tích cú pháp git rev-list.


Mặc dù hữu ích, nó không tìm thấy cam kết tiếp theo. Nó đi về phía cam kết hiện tại.
Schwern

1
Vậy thì tôi cho rằng bạn có thể làm được: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Bạn phải đi về phía một chi nhánh, không còn cách nào khác.
vontrapp

8

Hai câu trả lời thực tế:

Một đứa trẻ

Dựa trên câu trả lời của @ Michael , tôi đã hack childbí danh trong.gitconfig .

Nó hoạt động như mong đợi trong trường hợp mặc định, và cũng linh hoạt.

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

Nó mặc định cho con của TRƯỚC (trừ khi đưa ra một đối số xác nhận cam kết khác) bằng cách theo dõi tổ tiên một bước về phía đỉnh của nhánh hiện tại (trừ khi một cam kết khác được đưa ra làm đối số thứ hai).

Sử dụng %hthay vì %Hnếu bạn muốn mẫu băm ngắn.

Nhiều con

Với một ĐẦU tách ra (không có chi nhánh) hoặc để có được tất cả trẻ em bất kể chi nhánh:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

Thay đổi $1để $*in tất cả các trẻ em.

--allNói cách khác, bạn cũng có thể thay đổi thành một cam kết để chỉ hiển thị những đứa trẻ là tổ tiên của cam kết đó, nói cách khác, chỉ hiển thị các trẻ em theo hướng của Cam kết đã cho. Điều này có thể giúp bạn thu hẹp sản lượng từ nhiều trẻ em xuống chỉ còn một.


7

Không có "cam kết tiếp theo" duy nhất. Bởi vì lịch sử trong Git là một DAG và không phải là một dòng, nhiều xác nhận có thể có một cha mẹ chung (các nhánh) và các xác nhận có thể có nhiều hơn một cha mẹ (hợp nhất).

Nếu bạn có một chi nhánh cụ thể, bạn có thể xem nhật ký của nó và xem những gì cam kết liệt kê hiện tại là cha mẹ của nó.


37
Theo logic đó, cũng không có "cam kết trước", nhưng có rất nhiều cú pháp để nhận (các) cha mẹ.
Schwern

7
@Schwern: Không có "cam kết trước"; <rev>^là "cam kết gốc" ('cha mẹ đầu tiên' đối với các cam kết hợp nhất).
Jakub Narębski

6

Tôi đã thử nhiều giải pháp khác nhau và không có giải pháp nào phù hợp với tôi. Phải đến với riêng tôi.

tìm cam kết tiếp theo

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

tìm cam kết trước

function p() {
    git checkout HEAD^1
}

6

Trong trường hợp bạn không có một cam kết "đích" cụ thể, nhưng thay vào đó, muốn xem các cam kết con có thể nằm trên bất kỳ nhánh nào , bạn có thể sử dụng lệnh này:

git rev-list --children --all | grep ^${COMMIT}

Nếu bạn muốn nhìn thấy tất cả trẻ em và cháu , bạn phải sử dụng rev-list --childrenđệ quy, như vậy:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
           grep ^${COMMIT} | \
           sed 's/ /|/g')\)

(Phiên bản chỉ dành cho các cháu lớn sẽ sử dụng phức tạp hơn sedvà / hoặc cut.)

Cuối cùng, bạn có thể đưa nó vào một log --graphlệnh để xem cấu trúc cây, như vậy:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
  egrep ^\($(git rev-list --children --all | \
             grep ^${COMMIT} | \
             sed 's/ /|/g')\))

Lưu ý : tất cả các lệnh trên đều cho rằng bạn đã đặt biến shell ${COMMIT}thành một số tham chiếu (nhánh, thẻ, sha1) của cam kết mà con bạn quan tâm.


2
điều này trả lời câu hỏi tôi không biết cách xây dựng cho google và stackoverflow, nhưng đã cố gắng hỏi. cảm ơn bạn đã chủ động nhận ra nhu cầu
Tommy knowlton

đối với tôi ${COMMIT}không thực tế, nhưng bạn có thể sử dụng $(git rev-parse HEAD) thay vào đó
Radon8472 24/03/19

xin lỗi, thêm một lưu ý về ${COMMIT}.
Matt McHenry

5

(Điều này bắt đầu như một câu trả lời cho một câu hỏi trùng lặp. Tôi đã thực hiện một chút chỉnh sửa ánh sáng để làm sạch nó.)

Tất cả các mũi tên bên trong của Git là một chiều, chỉ về phía sau. Do đó, không có cú pháp thuận tiện ngắn để di chuyển về phía trước: điều đó là không thể.

Đó khả năng "động thái chống lại các mũi tên", nhưng cách để làm điều đó là ngạc nhiên nếu bạn chưa nhìn thấy nó trước đây, và sau đó hiển nhiên sau đó. Hãy nói rằng chúng ta có:

A <-B <-C <-D <-E   <-- last
        ^
        |
         \--------- middle

Sử dụng middle~2theo mũi tên hai lần từ Ctrở lại A. Vậy làm thế nào để chúng ta di chuyển từ Cđến D? Câu trả lời là: chúng tôi bắt đầu E, sử dụng tên lastvà làm việc ngược lại cho đến khi chúng tôi đến middle, ghi lại các điểm chúng tôi ghé thăm trên đường đi . Sau đó, chúng tôi chỉ cần di chuyển xa như chúng tôi muốn theo hướng last: di chuyển một bước tới D, hoặc hai đếnE .

Điều này đặc biệt quan trọng khi chúng ta có các chi nhánh:

          D--E   <-- feature1
         /
...--B--C   <-- master
         \
          F--G   <-- feature2

Cam kết nào là một bước sau C? Không có câu trả lời đúng cho đến khi bạn thêm vào câu hỏi: theo hướng của tính năng ___ (điền vào chỗ trống).

Để liệt kê các cam kết giữa C(không bao gồm C) chính nó và, giả sử G, chúng tôi sử dụng:

git rev-list --topo-order --ancestry-path master..feature2

Điều --topo-orderchắc chắn rằng ngay cả khi có sự phân nhánh và hợp nhất phức tạp, các cam kết được đưa ra theo thứ tự cấu trúc liên kết. Điều này chỉ được yêu cầu nếu chuỗi không tuyến tính. Các --ancestry-pathphương tiện hạn chế rằng khi chúng tôi làm việc ngược từ feature2, chúng tôi chỉ danh sách cam kết có cam kết Clà một trong những tổ tiên của mình. Đó là, nếu đồ thị xếp hạng hay phần có liên quan của nó thì dù sao thì thực tế thì trông giống như thế này:

A--B--C   <-- master
 \     \
  \     F--G--J   <-- feature2
   \         /
    H-------I   <-- feature3

một yêu cầu đơn giản của mẫu feature2..masterliệt kê các cam kết J, GI, FHtheo một số thứ tự. Với --ancestry-pathchúng tôi loại ra HI: họ không phải là hậu duệ của C, chỉ của A. Với --topo-orderchúng tôi đảm bảo rằng thứ tự liệt kê thực tế là J, sau đó G, sau đóF .

Các git rev-listlệnh tràn các ID băm ra trên đầu ra tiêu chuẩn của nó, mỗi dòng một. Để tiến lên một bước theo hướng feature2, sau đó, chúng tôi chỉ muốn cuối cùng dòng .

Có thể (và hấp dẫn và có thể hữu ích) để thêm vào --reverseđể git rev-listin các cam kết theo thứ tự đảo ngược sau khi tạo chúng. Điều này không hoạt động, nhưng nếu bạn sử dụng nó trong một đường ống như thế này:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

để chỉ nhận "cam kết tiếp theo theo hướng id2" và có một danh sách các cam kết rất dài, git rev-listlệnh có thể nhận được một đường ống bị hỏng khi nó cố gắng ghi vào headđó đã ngừng đọc đầu vào và thoát. Do lỗi đường ống thường bị bỏ qua bởi vỏ, điều này chủ yếu hoạt động. Chỉ cần đảm bảo rằng chúng bị bỏ qua trong việc sử dụng của bạn .

Nó cũng hấp dẫn để thêm -n 1vào git rev-listlệnh, cùng với --reverse. Đừng làm thế! Điều đó làm cho git rev-listdừng lại sau khi đi một bước trở lại , và sau đó đảo ngược danh sách (một mục) các cam kết được truy cập. Vì vậy, điều này chỉ sản xuất <id2>mỗi lần.

Lưu ý bên quan trọng

Lưu ý rằng với các mảnh đồ thị "kim cương" hoặc "vòng benzen":

       I--J
      /    \
...--H      M--...  <-- last
      \    /
       K--L

di chuyển một cam kết "chuyển tiếp" từ Hđối lastsẽ giúp bạn có một trong hai I hoặc K . Không có gì bạn có thể làm về điều đó: cả hai cam kết là một bước tiến! Nếu sau đó bạn bắt đầu từ cam kết kết quả và đi thêm một bước nữa, thì bây giờ bạn đã cam kết với bất kỳ con đường nào bạn đã bắt đầu.

Cách chữa trị cho điều này là tránh di chuyển từng bước một và bị khóa vào các chuỗi phụ thuộc vào đường dẫn. Thay vào đó, nếu bạn có kế hoạch đến thăm toàn bộ chuỗi đường dẫn tổ tiên, trước khi làm bất cứ điều gì khác , hãy tạo một danh sách đầy đủ tất cả các cam kết trong chuỗi:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

Sau đó, truy cập từng cam kết trong danh sách này, từng lần một và bạn sẽ nhận được toàn bộ chuỗi. Điều --topo-ordernày sẽ đảm bảo bạn đạt được - Ivà - Jtheo thứ tự đó, và - KLtheo thứ tự đó (mặc dù không có cách nào dễ dàng để dự đoán liệu bạn sẽ thực hiện cặp IJ trước hay sau cặp KL).


" Không có câu trả lời đúng cho đến khi bạn thêm vào câu hỏi: theo hướng của tính năng ___ " Đây là một điểm rất tốt. Cảm ơn câu trả lời của bạn.
Schwern

4

Tôi có bí danh này trong ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"


f()gì Và nó cần phải là head -1đứa trẻ đầu tiên, nếu không điều này chỉ đơn giản là báo cáo TRỤ.
Xerus

@Xerus Bởi vì nó sử dụng một số cú pháp shell "phức tạp" và git sẽ không nhận ra nó nếu không được bao bọc f(). Vâng, head -1là một dự đoán dũng cảm.
yếu

Đẹp! Tinh chỉnh của tôi để: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"vì vậy bạn có thể chọn cách xa về phía trước, tùy ý
Z. Khullah

2

Tôi đã tìm được đứa trẻ tiếp theo theo cách sau:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)

1
Đối với tôi điều này cho thấy không phải là đứa trẻ đầu tiên, nó cho thấy cam kết hiện tại
Radon8472

2

Nếu đứa trẻ cam kết là tất cả trên một số chi nhánh, bạn có thể sử dụng gitk --all commit^.., trong đó "cam kết" là một cái gì đó xác định cam kết. Ví dụ: nếu SHA-1 viết tắt của cam kết là c6661c5, thì hãy nhậpgitk --all c6661c5^..

Bạn có thể sẽ cần nhập SHA-1 đầy đủ vào ô "SHA1 ID:" của gitk. Bạn sẽ cần SHA-1 đầy đủ, ví dụ này có thể lấy thông quagit rev-parse c6661c5

Ngoài ra, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'sẽ tạo ra một dòng chứa tất cả các con của cam kết này, có lẽ có hay không có chi nhánh liên quan.


0

Mỗi cam kết lưu trữ một con trỏ tới cha mẹ của nó (cha mẹ, trong trường hợp cam kết hợp nhất (tiêu chuẩn)).

Vì vậy, không có cách nào để chỉ ra một cam kết con (nếu có) từ cha mẹ.


Cam kết không thể lưu trữ con trỏ cho con của nó, vì các cam kết con bổ sung (điểm phân nhánh) có thể được thêm vào điểm sau.
Jakub Narębski

@Jakub Tôi không thực sự làm theo. Họ không thể được thêm sau?
Schwern

1
Jakub: Đó chính xác là những gì tôi đã nói. 'Mỗi cam kết lưu trữ con trỏ chỉ cho cha mẹ của nó.'
Lakshman Prasad

@Schwern: Cam kết trong git là bất biến (có kết quả tốt đẹp về trách nhiệm), vì vậy, con trỏ cho trẻ em không được "thêm vào sau". Một trong những lý do là định danh của cam kết (được sử dụng trong các liên kết "cha mẹ") phụ thuộc vào nội dung của comit; đây là giải pháp duy nhất trong hệ thống phân tán, không có cơ quan đánh số trung tâm. Ngoài ra "con" của các xác nhận phụ thuộc vào các nhánh bạn có và lần lượt điều này có thể khác nhau từ kho lưu trữ đến kho lưu trữ (và các xác nhận giống nhau trong mỗi kho lưu trữ).
Jakub Narębski

4
@becomingGuru Tôi đã bình chọn nó. Nó có thể đúng, nhưng nó không trả lời câu hỏi của tôi. Câu hỏi là "làm thế nào để tôi tìm thấy cam kết tiếp theo trong git?" Nó không phải là "một git commit lưu trữ một con trỏ cho con của nó?"
Schwern


0

Các câu trả lời hiện tại cho rằng bạn có một nhánh chứa cam kết mà bạn đang tìm kiếm.

Trong trường hợp của tôi, cam kết mà tôi đang tìm kiếm không phải là git rev-list --all có vì không có chi nhánh nào chứa điều đó.

Tôi cuối cùng đã xem qua gitk --reflogbằng tay.

Nếu bạn không thể tìm thấy cam kết của mình ngay cả trong reflog, hãy thử:

  • git fsck --full để liệt kê dangling (nghĩa là không ở bất kỳ chi nhánh nào), hoặc
  • git fsck --lost-found để làm cho các ref chỉ vào các cam kết lơ lửng để áp dụng các kỹ thuật trong các câu trả lời khác.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.