Tìm một điểm nhánh với Git?


454

Tôi có một kho lưu trữ với các nhánh master và A và rất nhiều hoạt động hợp nhất giữa hai. Làm cách nào để tìm cam kết trong kho lưu trữ của tôi khi nhánh A được tạo dựa trên chủ?

Kho lưu trữ của tôi về cơ bản trông như thế này:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

Tôi đang tìm kiếm bản sửa đổi A, đó không phải là những gì git merge-base (--all)tìm thấy.


Câu trả lời:


498

Tôi đang tìm kiếm điều tương tự, và tôi tìm thấy câu hỏi này. Cảm ơn bạn đã hỏi nó!

Tuy nhiên, tôi thấy rằng các câu trả lời tôi thấy ở đây dường như không hoàn toàn đưa ra câu trả lời mà bạn đã yêu cầu (hoặc tôi đang tìm kiếm) - chúng dường như đưa ra Gcam kết, thay vì Acam kết.

Vì vậy, tôi đã tạo cây sau (các chữ cái được gán theo thứ tự thời gian), vì vậy tôi có thể kiểm tra mọi thứ:

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

Cái này trông hơi khác so với của bạn, vì tôi muốn chắc chắn rằng tôi đã nhận được (tham khảo biểu đồ này, không phải của bạn) B, nhưng không phải A (và không phải D hoặc E). Dưới đây là các chữ cái được đính kèm với tiền tố SHA và thông báo cam kết (repo của tôi có thể được sao chép từ đây , nếu điều đó thú vị với bất kỳ ai):

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

Vì vậy, mục tiêu: tìm B . Đây là ba cách mà tôi tìm thấy, sau một chút mày mò:


1. trực quan, với gitk:

Trực quan bạn sẽ thấy một cây như thế này (khi nhìn từ chủ):

chụp màn hình gitk từ chủ

hoặc ở đây (như được xem từ chủ đề):

chụp màn hình gitk từ chủ đề

trong cả hai trường hợp, tôi đã chọn cam kết Btrong biểu đồ của mình. Khi bạn nhấp vào nó, SHA đầy đủ của nó được trình bày trong trường nhập văn bản ngay bên dưới biểu đồ.


2. trực quan, nhưng từ thiết bị đầu cuối:

git log --graph --oneline --all

(Chỉnh sửa / ghi chú bên: thêm --decoratecũng có thể thú vị; nó thêm một dấu hiệu của tên nhánh, thẻ, v.v. Không thêm phần này vào dòng lệnh ở trên vì đầu ra bên dưới không phản ánh việc sử dụng nó.)

cho thấy (giả sử git config --global color.ui auto):

đầu ra của git log --graph --oneline --all

Hoặc, trong văn bản thẳng:

* a9546a2 hợp nhất từ ​​chủ đề trở lại chủ
|  
| * 648ca35 hợp nhất chủ vào chủ đề
| |  
| * | 132ee2a cam kết đầu tiên về chi nhánh chủ đề
* | | e7c863d cam kết về chủ sau khi chủ được hợp nhất vào chủ đề
| | /  
| / |   
* | 37ad159 cam kết sau chi nhánh trên chủ
| /  
* Cam kết thứ hai 6aafd7f trên master trước khi phân nhánh
* 4112403 cam kết ban đầu về chủ

trong cả hai trường hợp, chúng tôi thấy cam kết 6aafd7f là điểm chung thấp nhất, tức là Btrong biểu đồ của tôi hoặc Atrong của bạn.


3. Với phép thuật vỏ:

Bạn không chỉ định trong câu hỏi của mình cho dù bạn muốn một cái gì đó như ở trên, hoặc một lệnh duy nhất sẽ giúp bạn sửa đổi, và không có gì khác. Vâng, đây là cái sau:

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

Mà bạn cũng có thể đặt vào ~ / .gitconfig của bạn dưới dạng (lưu ý: dấu gạch ngang rất quan trọng; cảm ơn Brian vì đã chú ý đến điều đó) :

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

Điều này có thể được thực hiện thông qua dòng lệnh sau (kết hợp với trích dẫn):

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

Lưu ý: zshcó thể dễ dàng như vậy bash, nhưng shsẽ không hoạt động - <()cú pháp không tồn tại trong vanilla sh. (Cảm ơn bạn một lần nữa, @conny, vì đã khiến tôi biết về nó trong một bình luận về một câu trả lời khác trên trang này!)

Lưu ý: Phiên bản thay thế ở trên:

Nhờ liori cho chỉ ra rằng ở trên có thể giảm xuống khi so sánh chi nhánh giống hệt nhau, và đến với một hình thức khác thay thế mà loại bỏ các hình thức sed từ sự pha trộn, và làm cho điều này "an toàn hơn" (tức là nó sẽ trả về một kết quả (cụ thể là, cam kết gần đây nhất) ngay cả khi bạn so sánh chủ với chủ):

Là một dòng .git-config:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

Từ vỏ:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

Vì vậy, trong cây thử nghiệm của tôi (không có sẵn trong một thời gian, xin lỗi; nó đã hoạt động trở lại), hiện tại nó hoạt động trên cả chủ và chủ đề (lần lượt đưa ra các cam kết G và B). Cảm ơn một lần nữa, liori, cho hình thức thay thế.


Vì vậy, đó là những gì tôi [và liori] nghĩ ra. Nó dường như làm việc cho tôi. Nó cũng cho phép thêm một vài bí danh có thể chứng minh tiện dụng:

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

Chúc mừng git-ing!


5
Cảm ơn lindes, tùy chọn shell rất phù hợp cho các tình huống mà bạn muốn tìm điểm nhánh của nhánh bảo trì chạy dài. Khi bạn đang tìm kiếm một bản sửa đổi có thể là một ngàn cam kết trong quá khứ, các tùy chọn trực quan thực sự sẽ không cắt giảm. * 8 ')
Đánh dấu gian hàng

4
Trong phương pháp thứ ba của bạn, bạn phụ thuộc vào bối cảnh đó sẽ hiển thị dòng không thay đổi đầu tiên. Điều này sẽ không xảy ra trong các trường hợp cạnh nhất định hoặc nếu bạn có các yêu cầu hơi khác nhau (ví dụ: tôi chỉ cần một trong các lịch sử là - cha mẹ đầu tiên và tôi đang sử dụng phương pháp này trong một tập lệnh đôi khi có thể sử dụng cùng một kịch bản nhánh ở hai bên). Tôi thấy an toàn hơn khi sử dụng diffchế độ if-then-other và xóa các dòng đã thay đổi / xóa khỏi đầu ra của nó thay vì tính đến việc có bối cảnh đủ lớn., Bởi : diff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1.
liori

3
git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~tôi cũng sẽ làm việc như vậy
Tobias Schulte

4
@ JakubNarębski: Bạn có cách nào git merge-base --fork-point ...để đưa ra cam kết B (cam kết 6aafd7f) cho cây này không? Tôi đã bận rộn với những thứ khác khi bạn đăng nó, và nó nghe có vẻ tốt, nhưng cuối cùng tôi cũng đã thử nó và tôi không làm cho nó hoạt động ... Tôi hoặc nhận được một cái gì đó gần đây hơn, hoặc chỉ là một lỗi im lặng (không có lỗi tin nhắn, nhưng lối ra tình trạng 1), cố gắng lập luận như git co master; git merge-base --fork-point topic, git co topic; git merge-base --fork-point master, git merge-base --fork-point topic master(đối với một trong hai thanh toán), vv có cái gì tôi đang làm sai hoặc thiếu?
lindes

25
@ JakubNarębski @lindes --fork-pointdựa trên reflog, vì vậy nó sẽ chỉ hoạt động nếu bạn thực hiện các thay đổi cục bộ. Thậm chí sau đó, các mục reflog có thể đã hết hạn. Nó hữu ích nhưng không đáng tin cậy chút nào.
Daniel Lubarov

131

Bạn có thể đang tìm kiếm git merge-base:

git merge-base tìm thấy (các) tổ tiên chung tốt nhất giữa hai cam kết sử dụng trong hợp nhất ba chiều. Một tổ tiên chung tốt hơn một tổ tiên chung khác nếu tổ tiên sau là tổ tiên của trước. Một tổ tiên chung không có tổ tiên chung tốt hơn là một tổ tiên chung tốt nhất , tức là một cơ sở hợp nhất . Lưu ý rằng có thể có nhiều hơn một cơ sở hợp nhất cho một cặp xác nhận.


10
Cũng lưu ý --alltùy chọn " git merge-base"
Jakub Narębski

10
Điều này không trả lời câu hỏi ban đầu, nhưng hầu hết mọi người hỏi câu hỏi đơn giản hơn nhiều, đây là câu trả lời :)
FelipeC

6
ông nói rằng ông không phải là kết quả của cơ sở hợp nhất git
Tom Tanner

9
@TomTanner: Tôi chỉ nhìn vào lịch sử câu hỏi và câu hỏi ban đầu đã được chỉnh sửa để bao gồm ghi chú đó khoảng git merge-basenăm giờ sau khi câu trả lời của tôi được đăng (có lẽ là để trả lời câu trả lời của tôi). Tuy nhiên, tôi sẽ để lại câu trả lời này vì nó vẫn có thể hữu ích cho những người khác tìm thấy câu hỏi này thông qua tìm kiếm.
Greg Hewgill

Tôi tin rằng bạn có thể sử dụng các kết quả từ merge-base --all và sau đó sàng lọc danh sách các xác nhận hoàn trả bằng cách sử dụng merge-base --is-tổ tiên để tìm tổ tiên chung lâu đời nhất từ ​​danh sách, nhưng đây không phải là cách đơn giản nhất .
Matt_G

42

Tôi đã sử dụng git rev-listcho loại điều này. Ví dụ: (lưu ý 3 dấu chấm)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

sẽ nhổ ra điểm nhánh. Bây giờ, nó không hoàn hảo; vì bạn đã sáp nhập chủ vào nhánh A một vài lần, điều đó sẽ tách ra một vài điểm nhánh có thể (về cơ bản, điểm nhánh ban đầu và sau đó mỗi điểm bạn sáp nhập chủ vào nhánh A). Tuy nhiên, ít nhất nó nên thu hẹp các khả năng.

Tôi đã thêm lệnh đó vào bí danh của mình ~/.gitconfignhư:

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

vì vậy tôi có thể gọi nó là:

$ git diverges branch-a master

Lưu ý: điều này dường như đưa ra cam kết đầu tiên trên chi nhánh, chứ không phải là tổ tiên chung. (tức là nó đưa ra Gthay vì A, theo biểu đồ trong câu hỏi ban đầu.) Tôi có một câu trả lời nhận được Arằng tôi sẽ đăng ngay.
lindes

@lindes: Nó mang lại cho tổ tiên chung trong mọi trường hợp tôi đã thử nó. Bạn có một ví dụ mà nó không?
mipadi

Đúng. Trong câu trả lời của tôi (có liên kết đến một repo bạn có thể sao chép;git checkout topic và sau đó chạy nó topicthay thế branch-a), nó liệt kê 648ca357b946939654da12aaf2dc072763f3caee37ad15952db5c065679d7fc31838369712f0b338- cả hai 37ad159648ca35đều nằm trong tổ tiên của các nhánh hiện tại (sau này là ĐẦU của hiện tại topic), nhưng không phải là điểm trước khi phân nhánh xảy ra. Bạn có nhận được một cái gì đó khác nhau?
lindes

@lindes: Tôi không thể sao chép repo của bạn (có thể là vấn đề về quyền?).
mipadi

Ối xin lỗi! Cảm ơn bạn đã cho tôi biết. Tôi quên chạy git update-server-information. Nó nên được tốt để đi bây giờ. :)
lindes

31

Nếu bạn thích các lệnh ngắn gọn,

git rev-list $ (git rev-list --first-Parent ^ Branch_name master | tail -n1) ^^! 

Đây là một lời giải thích.

Lệnh sau cung cấp cho bạn danh sách tất cả các xác nhận đã xảy ra sau khi Branch_name được tạo

git rev-list --first-Parent ^ Branch_name master 

Vì bạn chỉ quan tâm đến những cam kết sớm nhất mà bạn muốn dòng cuối cùng của đầu ra:

git rev-list ^ Branch_name --first-Parent master | đuôi -n1

Phụ huynh trong những đầu tiên cam kết đó không phải là tổ tiên của "branch_name" là, theo định nghĩa, trong "branch_name," và đang trong "bậc thầy" vì nó là tổ tiên của một cái gì đó trong "bậc thầy". Vì vậy, bạn đã có cam kết sớm nhất trong cả hai chi nhánh.

Lệnh

git rev-list cam kết ^^!

chỉ là một cách để hiển thị tham chiếu cam kết cha mẹ. Bạn đã có thể sử dụng

git log -1 cam kết ^

hay bất cứ cái gì.

PS: Tôi không đồng ý với lập luận rằng trật tự tổ tiên là không liên quan. Nó phụ thuộc vào những gì bạn muốn. Ví dụ, trong trường hợp này

_C1___C2_____ chủ
  \ \ _XXXXX_ nhánh A (Xs biểu thị sự giao thoa tùy ý giữa chủ và A)
   \ _____ / chi nhánh B

nó có ý nghĩa hoàn hảo đối với đầu ra C2 như là cam kết "phân nhánh". Đây là khi nhà phát triển phân nhánh từ "chủ". Khi anh ấy phân nhánh, chi nhánh "B" thậm chí không được hợp nhất trong chi nhánh của anh ấy! Đây là những gì giải pháp trong bài này cung cấp.

Nếu điều bạn muốn là lần xác nhận C cuối cùng sao cho tất cả các đường dẫn từ điểm gốc đến lần xác nhận cuối cùng trên nhánh "A" đi qua C, thì bạn muốn bỏ qua thứ tự tổ tiên. Đó hoàn toàn là cấu trúc liên kết và cung cấp cho bạn một ý tưởng kể từ khi bạn có hai phiên bản mã đi cùng một lúc. Đó là khi bạn đi với các cách tiếp cận dựa trên cơ sở hợp nhất và nó sẽ trả về C1 trong ví dụ của tôi.


3
Đây là câu trả lời rõ ràng nhất, hãy để nó được bình chọn lên hàng đầu. Một chỉnh sửa được đề xuất: git rev-list commit^^!có thể được đơn giản hóa nhưgit rev-parse commit^
Russell Davis

Đây nên là câu trả lời!
thứ chín

2
Câu trả lời này là tốt đẹp, tôi chỉ thay thế git rev-list --first-parent ^branch_name mastervới git rev-list --first-parent branch_name ^mastervì nếu chi nhánh tổng thể là 0 cam kết trước các chi nhánh khác (nhanh forwardable với nó), không có đầu ra sẽ được tạo ra. Với giải pháp của tôi, không có đầu ra nào được tạo nếu master hoàn toàn đi trước (tức là nhánh đã được hợp nhất hoàn toàn), đó là điều tôi muốn.
Michael Schmeißer

3
Điều này sẽ không hoạt động trừ khi tôi hoàn toàn thiếu một cái gì đó. Có sự hợp nhất theo cả hai hướng trong các nhánh ví dụ. Có vẻ như bạn đã cố gắng tính đến điều đó, nhưng tôi tin rằng điều này sẽ khiến câu trả lời của bạn thất bại. git rev-list --first-parent ^topic mastersẽ chỉ đưa bạn trở lại cam kết đầu tiên sau lần hợp nhất cuối cùng mastervào topic(nếu điều đó còn tồn tại).
jerry

1
@jerry Bạn nói đúng, câu trả lời này là rác rưởi; chẳng hạn, trong trường hợp một backmerge vừa diễn ra (sáp nhập chủ vào chủ đề) và master không có cam kết mới nào sau đó, lệnh git rev-list đầu tiên - lệnh cha -first đầu ra không có gì cả.
TamaMcGlinn

19

Cho rằng rất nhiều câu trả lời trong chủ đề này không đưa ra câu trả lời cho câu hỏi đang hỏi, đây là tóm tắt kết quả của từng giải pháp, cùng với tập lệnh tôi đã sử dụng để sao chép kho lưu trữ được đưa ra trong câu hỏi.

Nhật ký

Tạo một kho lưu trữ với cấu trúc đã cho, chúng tôi nhận được nhật ký git của:

$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

Bổ sung duy nhất của tôi, là thẻ làm cho nó rõ ràng về điểm mà chúng tôi đã tạo chi nhánh và do đó cam kết chúng tôi muốn tìm.

Giải pháp nào hiệu quả

Giải pháp duy nhất hoạt động là giải pháp được cung cấp bởi lindes trả về đúng A:

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

Như Charles Bailey chỉ ra, giải pháp này rất dễ vỡ.

Nếu bạn branch_Atham gia mastervà sau đó hợp nhất mastervào branch_Amà không can thiệp vào các cam kết thì giải pháp của lindes chỉ cung cấp cho bạn sự phân kỳ đầu tiên gần đây nhất .

Điều đó có nghĩa là đối với quy trình làm việc của tôi, tôi nghĩ rằng tôi sẽ phải gắn bó với việc gắn thẻ điểm nhánh của các nhánh chạy dài, vì tôi không thể đảm bảo rằng chúng có thể được tìm thấy sau này.

Điều này thực sự tất cả sôi sục gitvì thiếu những gì được hggọi là chi nhánh . Blogger jhw gọi những dòng dõi này so với các gia đình trong bài viết của mình Tại sao tôi thích Mercurial hơn Git và bài viết tiếp theo của anh ấy về On Mercurial so với Git (với đồ thị!) . Tôi muốn giới thiệu mọi người đọc chúng để xem lý do tại sao một số người chuyển đổi đồng bóng bỏ lỡ không có tên chi nhánh trong git.

Các giải pháp không hoạt động

Giải pháp được cung cấp bởi mipadi trả về hai câu trả lời IC:

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

Giải pháp được cung cấp bởi Greg Hewgill trở lạiI

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

Giải pháp được cung cấp bởi Karl trả về X:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

Kịch bản

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

Tôi nghi ngờ phiên bản git tạo ra nhiều khác biệt cho điều này, nhưng:

$ git --version
git version 1.7.1

Cảm ơn Charles Bailey đã chỉ cho tôi một cách gọn nhẹ hơn để kịch bản kho lưu trữ mẫu.


2
Giải pháp của Karl rất dễ khắc phục : diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1. Cảm ơn đã cung cấp hướng dẫn để tạo repo :)
FelipeC

Tôi nghĩ bạn có nghĩa là "Biến thể được làm sạch của giải pháp được cung cấp bởi Karl trả về X". Bản gốc hoạt động tốt, nó chỉ xấu xí :-)
Karl

Không, bản gốc của bạn không hoạt động tốt. Cấp, các biến thể hoạt động thậm chí tồi tệ nhất. Nhưng việc thêm tùy chọn - thứ tự hàng đầu làm cho phiên bản của bạn hoạt động :)
FelipeC

@felipec - Xem bình luận cuối cùng của tôi về câu trả lời của Charles Bailey . Than ôi cuộc trò chuyện của chúng tôi (và do đó tất cả các bình luận cũ) hiện đã bị xóa. Tôi sẽ cố gắng cập nhật câu trả lời của mình khi tôi có thời gian.
Đánh dấu gian hàng

Hấp dẫn. Tôi sắp xếp cấu trúc liên kết giả định là mặc định. Ngớ ngẩn với tôi :-)
Karl

10

Nói chung, điều này là không thể. Trong lịch sử chi nhánh, một nhánh và hợp nhất trước khi một nhánh có tên được phân nhánh và một nhánh trung gian của hai nhánh được đặt tên trông giống nhau.

Trong git, các nhánh chỉ là tên hiện tại của các mẹo của các phần của lịch sử. Họ không thực sự có một bản sắc mạnh mẽ.

Đây thường không phải là một vấn đề lớn vì cơ sở hợp nhất (xem câu trả lời của Greg Hewgill) về hai cam kết thường hữu ích hơn nhiều, đưa ra cam kết gần đây nhất mà hai chi nhánh chia sẻ.

Một giải pháp dựa trên thứ tự của cha mẹ của một cam kết rõ ràng sẽ không hoạt động trong các tình huống mà một chi nhánh đã được tích hợp hoàn toàn tại một số điểm trong lịch sử của chi nhánh.

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

Kỹ thuật này cũng rơi xuống nếu hợp nhất tích hợp đã được thực hiện với cha mẹ đảo ngược (ví dụ: một nhánh tạm thời được sử dụng để thực hiện hợp nhất thử nghiệm thành chủ và sau đó chuyển nhanh vào nhánh tính năng để xây dựng thêm).

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"


3
Cảm ơn Charles, bạn đã thuyết phục tôi, nếu tôi muốn biết điểm mà chi nhánh ban đầu chuyển hướng , tôi sẽ phải gắn thẻ nó. Tôi thật sự mong rằng gitcó một tương đương với hg's tên chi nhánh, nó sẽ làm cho việc quản lý các chi nhánh bảo trì dài sống nên dễ dàng hơn nhiều.
Đánh dấu gian hàng

1
"Trong git, các nhánh chỉ là tên hiện tại của các mẹo trong lịch sử. Chúng không thực sự có bản sắc mạnh mẽ" Đó là một điều đáng sợ để nói và đã thuyết phục tôi rằng tôi cần hiểu rõ hơn về các nhánh Git - cảm ơn (+ 1)
dumbledad

Trong lịch sử chi nhánh, một nhánh và hợp nhất trước khi một nhánh có tên được phân nhánh và một nhánh trung gian của hai nhánh được đặt tên trông giống nhau. Vâng. +1.
dùng1071847

5

Thế còn một cái gì đó như

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^

Những công việc này. Nó thực sự cồng kềnh nhưng đó là điều duy nhất tôi thấy dường như thực sự làm được việc.
GaryO

1
Git bí danh tương đương: diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'(không có tập tin tạm thời)
Conny

@conny: Ồ, wow - Tôi chưa bao giờ thấy cú pháp <(foo) ... thật hữu ích, cảm ơn! (Hoạt động trong zsh cũng vậy, FYI.)
lindes

điều này dường như mang lại cho tôi cam kết đầu tiên trên chi nhánh, chứ không phải là tổ tiên chung. (tức là nó đưa ra Gthay vì A, theo biểu đồ trong câu hỏi ban đầu.) Tôi nghĩ rằng tôi đã tìm thấy câu trả lời, tuy nhiên, hiện tại tôi sẽ đăng.
lindes

Thay vì 'git log --pretty = oneline', bạn chỉ có thể thực hiện 'git rev-list', sau đó bạn có thể bỏ qua phần cắt, hơn nữa, điều này mang lại cho cha mẹ cam kết về điểm phân kỳ, vì vậy chỉ cần đuôi -2 | đầu 1. Vì vậy:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
FelipeC

5

chắc chắn tôi đang thiếu một cái gì đó, nhưng IMO, tất cả các vấn đề ở trên là do chúng tôi luôn cố gắng tìm điểm nhánh đi vào lịch sử và điều đó gây ra tất cả các vấn đề do các kết hợp hợp nhất có sẵn.

Thay vào đó, tôi đã theo một cách tiếp cận khác, dựa trên thực tế là cả hai nhánh chia sẻ rất nhiều lịch sử, chính xác tất cả lịch sử trước khi phân nhánh giống nhau 100%, vì vậy thay vì quay lại, đề xuất của tôi là về phía trước (từ ngày 1 cam kết), tìm kiếm sự khác biệt thứ 1 trong cả hai chi nhánh. Điểm nhánh sẽ đơn giản là cha mẹ của sự khác biệt đầu tiên được tìm thấy.

Trong thực tế:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

Và nó giải quyết tất cả các trường hợp thông thường của tôi. Chắc chắn có những cái viền không được bảo hiểm nhưng ... ciao :-)


diff <(git rev-list "$ {1: -master}" --first-Parent) <(git rev-list "$ {2: -HEAD}" --first-Parent) -U1 | đuôi -1
Alexander Bird

tôi thấy điều này nhanh hơn (2-100x):comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1
Andrei Neculau


4

Sau rất nhiều nghiên cứu và thảo luận, rõ ràng không có viên đạn ma thuật nào có thể hoạt động trong mọi tình huống, ít nhất là không phải trong phiên bản Git hiện tại.

Đó là lý do tại sao tôi đã viết một vài bản vá có thêm khái niệm về một tailnhánh. Mỗi khi một nhánh được tạo, một con trỏ tới điểm ban đầu cũng được tạo, tailref. Ref này được cập nhật mỗi khi chi nhánh bị từ chối.

Để tìm ra điểm nhánh của nhánh phát, tất cả những gì bạn phải làm là sử dụng devel@{tail}, đó là nó.

https://github.com/felipec/git/commits/fc/tail


1
Có thể là giải pháp ổn định duy nhất. Bạn có thấy nếu điều này có thể vào git? Tôi đã không thấy một yêu cầu kéo.
Alexander Klimetschek

@AlexanderKlimetschek Tôi đã không gửi các bản vá và tôi không nghĩ chúng sẽ được chấp nhận. Tuy nhiên, tôi đã thử một phương pháp khác: một hook "nhánh cập nhật" thực hiện một cái gì đó rất giống nhau. Cách này theo mặc định Git sẽ không làm gì cả, nhưng bạn có thể kích hoạt hook để cập nhật nhánh đuôi. Mặc dù vậy, bạn sẽ không có devel @ {tail}, nhưng sẽ không tệ khi sử dụng đuôi / devel thay thế.
FelipeC

3

Đây là phiên bản cải tiến của câu trả lời trước của tôi câu trả lời trước . Nó dựa vào các thông điệp cam kết từ các phép hợp nhất để tìm nơi nhánh đầu tiên được tạo.

Nó hoạt động trên tất cả các kho lưu trữ được đề cập ở đây, và tôi thậm chí đã giải quyết một số kho lưu trữ khó khăn xuất hiện trong danh sách gửi thư . Tôi cũng đã viết bài kiểm tra cho việc này.

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}

3

Lệnh sau sẽ tiết lộ SHA1 của Cam kết A

git merge-base --fork-point A


Điều này sẽ không xảy ra nếu các nhánh cha và con có sự hợp nhất trung gian của nhau ở giữa.
nawfal

2

Tôi dường như nhận được một số niềm vui với

git rev-list branch...master

Dòng cuối cùng bạn nhận được là cam kết đầu tiên trên chi nhánh, do đó, vấn đề là nhận được cha mẹ của điều đó. Vì thế

git rev-list -1 `git rev-list branch...master | tail -1`^

Có vẻ như hoạt động với tôi và không cần diffs và cứ thế (điều này hữu ích vì chúng tôi không có phiên bản diff đó)

Khắc phục: Điều này không hoạt động nếu bạn ở nhánh chính, nhưng tôi đang thực hiện điều này trong một kịch bản nên ít xảy ra sự cố


2

Để tìm các xác nhận từ điểm phân nhánh, bạn có thể sử dụng điều này.

git log --ancestry-path master..topicbranch

Lệnh này không hoạt động đối với tôi trên exmple đã cho. Xin vui lòng những gì bạn sẽ cung cấp như là tham số cho phạm vi cam kết?
Jesper Rønn-Jensen 17/12/14

2

Đôi khi điều đó thực sự không thể (với một số ngoại lệ về nơi bạn có thể may mắn có thêm dữ liệu) và các giải pháp ở đây sẽ không hoạt động.

Git không lưu giữ lịch sử ref (bao gồm các chi nhánh). Nó chỉ lưu trữ vị trí hiện tại cho mỗi chi nhánh (người đứng đầu). Điều này có nghĩa là bạn có thể mất một số lịch sử chi nhánh trong git theo thời gian. Bất cứ khi nào bạn phân nhánh chẳng hạn, nó ngay lập tức bị mất nhánh nào là nhánh ban đầu. Tất cả một chi nhánh làm là:

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

Bạn có thể cho rằng người đầu tiên được cam kết là chi nhánh. Điều này có xu hướng là trường hợp nhưng không phải lúc nào cũng như vậy. Không có gì ngăn bạn cam kết với một trong hai chi nhánh đầu tiên sau hoạt động trên. Ngoài ra, dấu thời gian git không được đảm bảo là đáng tin cậy. Mãi cho đến khi bạn cam kết với cả hai rằng chúng thực sự trở thành các nhánh có cấu trúc.

Mặc dù trong các sơ đồ, chúng ta có xu hướng đánh số cam kết về mặt khái niệm, git không có khái niệm ổn định thực sự về chuỗi khi các nhánh cây cam kết. Trong trường hợp này, bạn có thể giả sử các số (chỉ thị thứ tự) được xác định bởi dấu thời gian (có thể rất vui khi thấy giao diện người dùng git xử lý mọi thứ khi bạn đặt tất cả các dấu thời gian giống nhau).

Đây là những gì một con người mong đợi về mặt khái niệm:

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

Đây là những gì bạn thực sự nhận được:

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

Bạn sẽ cho rằng B1 là nhánh ban đầu nhưng nó có thể chỉ đơn giản là một nhánh chết (ai đó đã thanh toán -b nhưng không bao giờ cam kết với nó). Mãi cho đến khi bạn cam kết với cả hai bạn sẽ có được cấu trúc nhánh hợp pháp trong git:

Either:
      / - C2 (B1)
    -- C1
      \ - C3 (B2)
Or:
      / - C3 (B1)
    -- C1
      \ - C2 (B2)

Bạn luôn biết rằng C1 đến trước C2 và C3 nhưng bạn không bao giờ biết chắc chắn rằng C2 đến trước C3 hay C3 đến trước C2 (vì bạn có thể đặt thời gian trên máy trạm của mình thành bất kỳ thứ gì chẳng hạn). B1 và ​​B2 cũng gây hiểu nhầm vì bạn không thể biết chi nhánh nào đến trước. Bạn có thể đoán rất chính xác và thường chính xác về nó trong nhiều trường hợp. Nó hơi giống một đường đua. Tất cả mọi thứ thường tương đương với những chiếc xe sau đó bạn có thể giả định rằng một chiếc xe đi vào lòng phía sau bắt đầu một vòng phía sau. Chúng tôi cũng có những quy ước rất đáng tin cậy, ví dụ, chủ nhân sẽ hầu như luôn đại diện cho những nhánh tồn tại lâu nhất mặc dù thật đáng buồn là tôi đã thấy những trường hợp mà ngay cả điều này không phải là trường hợp.

Ví dụ được đưa ra ở đây là một ví dụ bảo tồn lịch sử:

Human:
    - X - A - B - C - D - F (B1)
           \     / \     /
            G - H ----- I - J (B2)
Real:
            B ----- C - D - F (B1)
           /       / \     /
    - X - A       /   \   /
           \     /     \ /
            G - H ----- I - J (B2)

Thực tế ở đây cũng là sai lầm bởi vì chúng ta như con người đọc nó từ trái sang phải, từ gốc đến lá (ref). Git không làm điều đó. Nơi chúng ta làm (A-> B) trong đầu chúng ta git (A <-B hoặc B-> A). Nó đọc nó từ ref đến root. Refs có thể ở bất cứ đâu nhưng có xu hướng là lá, ít nhất là cho các nhánh hoạt động. Một tham chiếu trỏ đến một cam kết và cam kết chỉ chứa một lượt thích cho cha mẹ của họ, không phải cho con cái của họ. Khi một cam kết là một cam kết hợp nhất, nó sẽ có nhiều hơn một cha mẹ. Cha mẹ đầu tiên luôn là cam kết ban đầu được sáp nhập vào. Các cha mẹ khác luôn luôn là các cam kết đã được hợp nhất vào các cam kết ban đầu.

Paths:
    F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
    J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))

Đây không phải là một đại diện rất hiệu quả, thay vào đó là một biểu thức của tất cả các đường dẫn git có thể đi từ mỗi ref (B1 và ​​B2).

Bộ nhớ trong của Git trông giống như thế này (không phải là A với tư cách là cha mẹ xuất hiện hai lần):

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

Nếu bạn kết xuất một cam kết git thô, bạn sẽ thấy không hoặc nhiều trường cha. Nếu có 0, điều đó có nghĩa là không có cha mẹ và cam kết là một gốc (bạn thực sự có thể có nhiều gốc). Nếu có, điều đó có nghĩa là không có hợp nhất và đó không phải là một cam kết gốc. Nếu có nhiều hơn một, điều đó có nghĩa là cam kết là kết quả của hợp nhất và tất cả các cha mẹ sau lần đầu tiên là các cam kết hợp nhất.

Paths simplified:
    F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
    F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
    J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
    F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
    F->D->C->B->A | J->I->->G->A | A->X
Topological:
    - X - A - B - C - D - F (B1)
           \
            G - H - I - J (B2)

Khi cả hai đánh A, chuỗi của họ sẽ giống nhau, trước đó chuỗi của họ sẽ hoàn toàn khác nhau. Cam kết đầu tiên hai cam kết khác có điểm chung là tổ tiên chung và từ đó họ chuyển hướng. có thể có một số nhầm lẫn ở đây giữa các điều khoản cam kết, chi nhánh và ref. Trong thực tế bạn có thể hợp nhất một cam kết. Đây là những gì hợp nhất thực sự làm. Một ref chỉ đơn giản là trỏ đến một cam kết và một nhánh không có gì khác hơn là một ref trong thư mục .git / refs / Heads, vị trí thư mục là thứ xác định rằng ref là một nhánh chứ không phải là một thứ khác như thẻ.

Trường hợp bạn mất lịch sử là hợp nhất sẽ làm một trong hai điều tùy thuộc vào hoàn cảnh.

Xem xét:

      / - B (B1)
    - A
      \ - C (B2)

Trong trường hợp này, một sự hợp nhất theo một trong hai hướng sẽ tạo ra một cam kết mới với cha mẹ đầu tiên như là một cam kết được chỉ ra bởi nhánh đã kiểm tra hiện tại và cha mẹ thứ hai là cam kết ở đầu nhánh bạn đã sáp nhập vào nhánh hiện tại của bạn. Nó phải tạo ra một cam kết mới vì cả hai nhánh đều có những thay đổi do tổ tiên chung của chúng phải được kết hợp.

      / - B - D (B1)
    - A      /
      \ --- C (B2)

Tại thời điểm này, D (B1) hiện có cả hai bộ thay đổi từ cả hai nhánh (chính nó và B2). Tuy nhiên, nhánh thứ hai không có thay đổi từ B1. Nếu bạn hợp nhất các thay đổi từ B1 thành B2 để chúng được đồng bộ hóa thì bạn có thể mong đợi một cái gì đó trông như thế này (bạn có thể buộc git merge thực hiện như thế này với --no-ff):

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

Bạn sẽ nhận được điều đó ngay cả khi B1 có các cam kết bổ sung. Miễn là không có thay đổi trong B2 mà B1 không có, hai nhánh sẽ được hợp nhất. Nó thực hiện một chuyển tiếp nhanh giống như một cuộc nổi loạn (cuộc nổi loạn cũng ăn hoặc lịch sử tuyến tính hóa), ngoại trừ không giống như một cuộc nổi loạn vì chỉ có một nhánh có một thay đổi, nó không phải áp dụng một thay đổi từ một nhánh trên đỉnh này từ nhánh khác.

From:
      / - B - D - E (B1)
    - A      /
      \ --- C (B2)
To:
      / - B - D - E (B1) (B2)
    - A      /
      \ --- C

Nếu bạn ngừng làm việc trên B1 thì mọi thứ phần lớn đều ổn để bảo tồn lịch sử trong thời gian dài. Chỉ B1 (có thể là chủ) sẽ tiến lên một cách điển hình để vị trí của B2 trong lịch sử của B2 thể hiện thành công điểm mà nó được sáp nhập vào B1. Đây là những gì git mong đợi bạn làm, để phân nhánh B từ A, sau đó bạn có thể hợp nhất A vào B bao nhiêu tùy thích khi các thay đổi được tích lũy, tuy nhiên khi hợp nhất B trở lại A, bạn sẽ không làm việc trên B và hơn thế nữa . Nếu bạn tiếp tục làm việc trên chi nhánh của mình sau khi chuyển tiếp nhanh chóng hợp nhất nó trở lại vào chi nhánh mà bạn đang làm việc thì lịch sử xóa B của bạn mỗi lần. Bạn thực sự đang tạo một chi nhánh mới mỗi lần sau khi chuyển tiếp nhanh cam kết với nguồn rồi cam kết với chi nhánh.

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

1 đến 3 và 5 đến 8 là các nhánh cấu trúc xuất hiện nếu bạn theo dõi lịch sử cho 4 hoặc 9. Không có cách nào để biết nhánh nào trong số các nhánh cấu trúc không tên và không được phân loại này thuộc về các nhánh được đặt tên và tham chiếu như kết thúc cấu trúc. Bạn có thể cho rằng từ bản vẽ này rằng 0 đến 4 thuộc về B1 và ​​4 đến 9 thuộc về B2 nhưng ngoài 4 và 9 thì không thể biết nhánh nào thuộc về nhánh nào, tôi chỉ đơn giản vẽ nó theo cách mang lại cho ảo tưởng về điều đó. 0 có thể thuộc về B2 và 5 có thể thuộc về B1. Có 16 khả năng khác nhau trong trường hợp này được đặt tên là nhánh mà mỗi nhánh cấu trúc có thể thuộc về.

Có một số chiến lược git hoạt động xung quanh này. Bạn có thể buộc git merge để không bao giờ chuyển tiếp nhanh và luôn tạo một nhánh hợp nhất. Một cách khủng khiếp để bảo tồn lịch sử chi nhánh là với các thẻ và / hoặc các nhánh (các thẻ thực sự được khuyến nghị) theo một số quy ước bạn chọn. Tôi thực sự sẽ không đề xuất một cam kết trống giả trong chi nhánh mà bạn đang hợp nhất. Một quy ước rất phổ biến là không hợp nhất thành một nhánh tích hợp cho đến khi bạn thực sự muốn đóng chi nhánh của mình. Đây là một thực tế mà mọi người nên cố gắng tuân thủ nếu không bạn đang làm việc xung quanh điểm có chi nhánh. Tuy nhiên, trong thế giới thực, lý tưởng không phải lúc nào cũng có ý nghĩa thực tế, làm điều đúng đắn là không khả thi cho mọi tình huống. Nếu những gì bạn '


"Git không lưu giữ lịch sử tham chiếu" Nó có, nhưng không theo mặc định và không dành cho thời gian dài. Xem man git-reflogvà phần về ngày tháng: "master @ nbone.week.ago} có nghĩa là" nơi chủ được sử dụng để chỉ đến một tuần trước trong kho lưu trữ cục bộ này "". Hoặc các cuộc thảo luận trên <refname>@{<date>}trong man gitrevisions. Và core.reflogExpiretrong man git-config.
Patrick Mevzek

2

Không hoàn toàn là một giải pháp cho câu hỏi nhưng tôi nghĩ rằng nó đáng chú ý đến cách tiếp cận tôi sử dụng khi tôi có một chi nhánh lâu đời:

Đồng thời tôi tạo chi nhánh, tôi cũng tạo một thẻ có cùng tên nhưng có -inithậu tố, ví dụ feature-branchfeature-branch-init.

(Thật là kỳ quái khi đây là một câu hỏi khó trả lời!)


1
Xem xét sự ngu ngốc đáng kinh ngạc của việc thiết kế một khái niệm "chi nhánh" mà không có bất kỳ khái niệm nào về thời điểm và nơi nó được tạo ra ... cộng với sự phức tạp to lớn của các giải pháp được đề xuất khác - bởi những người cố gắng thông minh theo cách quá thông minh này điều, tôi nghĩ rằng tôi thích giải pháp của bạn. Chỉ có nó gánh nặng một người có nhu cầu NHỚ để làm điều này mỗi khi bạn tạo một chi nhánh - một điều mà người dùng git đang thực hiện rất thường xuyên. Ngoài ra - tôi đã đọc ở đâu đó rằng 'thẻ có hình phạt là' nặng '. Tuy nhiên, tôi nghĩ đó là những gì tôi nghĩ tôi sẽ làm.
Motti Shneor

1

Một cách đơn giản để dễ nhìn thấy điểm phân nhánh hơn git log --graphlà sử dụng tùy chọn--first-parent .

Ví dụ: lấy repo từ câu trả lời được chấp nhận :

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

Bây giờ thêm --first-parent:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

Điều đó làm cho nó dễ dàng hơn!

Lưu ý nếu repo có nhiều nhánh bạn muốn chỉ định 2 nhánh bạn đang so sánh thay vì sử dụng --all:

$ git log --decorate --oneline --graph --first-parent master origin/topic

0

Vấn đề dường như là tìm ra lần cắt gần nhất, một lần xác nhận giữa hai nhánh ở một bên và sớm nhất tổ tiên chung ở bên kia (có thể là cam kết ban đầu của repo). Điều này phù hợp với trực giác của tôi về điểm "phân nhánh" là gì.

Điều đó trong tâm trí, điều này hoàn toàn không dễ dàng để tính toán với các lệnh git shell thông thường, vì git rev-list- công cụ mạnh nhất của chúng tôi - không cho phép chúng tôi giới hạn đường dẫn đạt được cam kết. Gần nhất chúng ta có git rev-list --boundary, có thể cung cấp cho chúng ta một tập hợp tất cả các cam kết "chặn đường chúng ta". (Lưu ý: git rev-list --ancestry-pathrất thú vị nhưng tôi không làm thế nào để làm cho nó hữu ích ở đây.)

Đây là kịch bản: https://gist.github.com/abortz/d464c88923c520b79e3d . Nó tương đối đơn giản, nhưng do một vòng lặp, nó đủ phức tạp để đảm bảo một ý chính.

Lưu ý rằng hầu hết các giải pháp khác được đề xuất ở đây không thể hoạt động trong mọi tình huống vì một lý do đơn giản: git rev-list --first-parentkhông đáng tin cậy trong việc tuyến tính hóa lịch sử vì có thể hợp nhất với một trong hai thứ tự.

git rev-list --topo-ordermặt khác, rất hữu ích - đối với các cam kết đi bộ theo thứ tự địa hình - nhưng thực hiện khác biệt là dễ vỡ: có nhiều thứ tự địa hình có thể có cho một biểu đồ nhất định, do đó bạn phụ thuộc vào sự ổn định nhất định của các thứ tự. Điều đó nói rằng, giải pháp của Strongk7 có thể hoạt động tốt hầu hết thời gian. Tuy nhiên, nó chậm hơn do tôi phải đi lại toàn bộ lịch sử của repo ... hai lần. :-)


0

Sau đây thực hiện git tương đương với nhật ký svn --stop-on-copy và cũng có thể được sử dụng để tìm nguồn gốc chi nhánh.

Tiếp cận

  1. Nhận đầu cho tất cả các chi nhánh
  2. thu thập mergeBase cho nhánh đích mỗi nhánh khác
  3. git.log và lặp đi lặp lại
  4. Dừng lại ở lần xác nhận đầu tiên xuất hiện trong danh sách mergeBase

Giống như tất cả các con sông chạy ra biển, tất cả các nhánh chạy đến chủ và do đó chúng tôi tìm thấy cơ sở hợp nhất giữa các nhánh dường như không liên quan. Khi chúng ta đi bộ từ đầu chi nhánh qua tổ tiên, chúng ta có thể dừng lại ở cơ sở hợp nhất tiềm năng đầu tiên vì theo lý thuyết, nó phải là điểm gốc của chi nhánh này.

Ghi chú

  • Tôi đã không thử cách tiếp cận này, nơi các nhánh anh chị em và anh em họ hợp nhất với nhau.
  • Tôi biết phải có một giải pháp tốt hơn.

chi tiết: https://stackoverflow.com/a/35353202/9950


-1

Bạn có thể sử dụng lệnh sau để trả về cam kết cũ nhất trong nhánh_a, không thể truy cập được từ master:

git rev-list branch_a ^master | tail -1

Có lẽ với một kiểm tra sự tỉnh táo bổ sung mà phụ huynh về điều đó cam kết thực sự truy cập từ bậc thầy ...


1
Điều này không hoạt động. Nếu Branch_a được hợp nhất thành chủ một lần, và sau đó tiếp tục, các cam kết trong hợp nhất đó sẽ được coi là một phần của chủ, vì vậy chúng sẽ không hiển thị trong ^ master.
FelipeC

-2

Bạn có thể kiểm tra reflog của nhánh A để tìm ra từ đó cam kết được tạo ra, cũng như toàn bộ lịch sử cam kết mà nhánh đó đã chỉ ra. Reflogs đang ở .git/logs.


2
Tôi không nghĩ rằng nó hoạt động nói chung bởi vì reflog có thể được cắt tỉa. Và tôi cũng không nghĩ (?) Các reflog bị đẩy, vì vậy điều này sẽ chỉ hoạt động trong một tình huống repo đơn.
GaryO

-2

Tôi tin rằng tôi đã tìm thấy một cách giải quyết tất cả các trường hợp góc được đề cập ở đây:

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

Charles Bailey hoàn toàn đúng khi các giải pháp dựa trên thứ tự của tổ tiên chỉ có giá trị giới hạn; vào cuối ngày bạn cần một số loại bản ghi "cam kết này đến từ chi nhánh X", nhưng bản ghi đó đã tồn tại; theo mặc định 'git merge' sẽ sử dụng một thông điệp cam kết, chẳng hạn như "Hợp nhất nhánh 'nhánh_A' thành chủ", điều này cho bạn biết rằng tất cả các cam kết từ cha mẹ thứ hai (cam kết ^ 2) đến từ 'Branch_A' và được hợp nhất với đầu tiên cha mẹ (cam kết ^ 1), đó là 'chủ nhân'.

Được trang bị thông tin này, bạn có thể tìm thấy sự hợp nhất đầu tiên của 'Branch_A' (đó là khi 'Branch_A' thực sự ra đời) và tìm cơ sở hợp nhất, sẽ là điểm nhánh :)

Tôi đã thử với kho lưu trữ của Mark Booth và Charles Bailey và giải pháp hoạt động; Làm thế nào không thể? Cách duy nhất điều này sẽ không hoạt động là nếu bạn đã thay đổi thủ công thông điệp cam kết mặc định để hợp nhất để thông tin chi nhánh thực sự bị mất.

Đối với tính hữu ích:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

Sau đó, bạn có thể làm ' git branch-point branch_A'.

Thưởng thức ;)


4
Dựa vào những thông điệp hợp nhất là hơn mong manh hơn hypothesising về đơn đặt hàng mẹ. Đó không chỉ là một tình huống giả định; Tôi thường sử dụng git merge -mđể nói những gì tôi đã hợp nhất thay vì tên của một nhánh phù du có chủ ý (ví dụ: "hợp nhất các thay đổi dòng chính thành tính năng tái cấu trúc xyz"). Giả sử tôi đã ít hữu ích hơn với -mví dụ của tôi? Vấn đề đơn giản là không thể hòa tan trong toàn bộ tính tổng quát của nó bởi vì tôi có thể tạo ra cùng một lịch sử với một hoặc hai nhánh tạm thời và không có cách nào để phân biệt sự khác biệt.
CB Bailey

1
@CharlesBailey Đó là vấn đề của bạn rồi. Bạn không nên xóa những dòng đó khỏi tin nhắn cam kết, bạn nên thêm phần còn lại của tin nhắn bên dưới tin nhắn gốc. Ngày nay, 'git merge' tự động mở trình chỉnh sửa để bạn thêm bất cứ thứ gì bạn muốn và đối với các phiên bản cũ của git, bạn có thể thực hiện 'git merge --edit'. Dù bằng cách nào, bạn có thể sử dụng móc cam kết để thêm "Cam kết trên nhánh 'foo'" cho mỗi cam kết nếu đó là điều bạn thực sự muốn. Tuy nhiên, giải pháp này hiệu quả với hầu hết mọi người .
FelipeC

2
Không làm việc cho tôi. Nhánh_A đã bị tách ra khỏi chủ đã có rất nhiều sự hợp nhất. Logic này không cung cấp cho tôi hàm băm cam kết chính xác nơi Branch_A được tạo.
Venkat Kotra
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.