Znaleźć punkt rozgałęzienia za pomocą Git?

453

Mam repozytorium z gałęziami master i A i dużą ilością działań scalających między nimi. Jak znaleźć zatwierdzenie w moim repozytorium, gdy gałąź A została utworzona na podstawie wzorca?

Moje repozytorium wygląda następująco:

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

Szukam wersji A, której nie git merge-base (--all)znajdzie.

Jochen
źródło

Odpowiedzi:

497

Szukałem tego samego i znalazłem to pytanie. Dziękujemy za pytanie!

Stwierdziłem jednak, że odpowiedzi, które tu widzę, wydają się nie dawać odpowiedzi, o które prosiłeś (lub których szukałem) - wydają się Graczej zatwierdzać, niż Azatwierdzać.

Stworzyłem następujące drzewo (litery przypisane w porządku chronologicznym), aby przetestować:

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

Wygląda to nieco inaczej niż twoje, ponieważ chciałem się upewnić, że mam (odnosząc się do tego wykresu, nie twojego) B, ale nie A (a nie D lub E). Oto litery dołączone do prefiksów SHA i wiadomości zatwierdzających (moje repo można stąd sklonować , jeśli jest to interesujące dla każdego):

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

Tak więc, cel: znaleźć B . Oto trzy sposoby, które znalazłem po drobnym majsterkowaniu:


1. wizualnie, z gitk:

Powinieneś zobaczyć drzewo takie jak to (oglądane z poziomu master):

zrzut ekranu gitk z mistrza

lub tutaj (patrząc z tematu):

zrzut ekranu gitk z tematu

w obu przypadkach wybrałem zatwierdzenie znajdujące się Bna moim wykresie. Po kliknięciu jego pełna wartość SHA jest wyświetlana w polu wprowadzania tekstu tuż pod wykresem.


2. wizualnie, ale z terminala:

git log --graph --oneline --all

(Edycja / uwaga: dodawanie --decoratemoże być również interesujące; dodaje wskazanie nazw gałęzi, znaczników itp. Nie dodawanie tego do powyższego wiersza poleceń, ponieważ poniższe dane wyjściowe nie odzwierciedlają jego zastosowania).

który pokazuje (zakładając git config --global color.ui auto):

wyjście git log --graph --oneline --all

Lub zwykłym tekstem:

* Łączenie a9546a2 z tematu z powrotem do wzorca
| \  
| * 648ca35 połączenie wzorca w temat
| | \  
| * | 132ee2a pierwsze zatwierdzenie w gałęzi tematu
* | | e7c863d zatwierdza na master po scaleniu master z tematem
| | /  
| / |   
* | Zatwierdzenie po rozgałęzieniu 37ad159 na master
| /  
* 6aafd7f drugie zatwierdzenie na master przed rozgałęzieniem
* 4112403 początkowe zatwierdzenie na master

w obu przypadkach widzimy zatwierdzenie 6aafd7f jako najniższy wspólny punkt, tj. Bna moim wykresie lub Aw twoim.


3. Z magią powłoki:

W pytaniu nie określasz, czy chcesz czegoś takiego jak powyższe, czy też pojedynczego polecenia, które da ci tylko jedną wersję i nic więcej. Cóż, oto ten drugi:

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

Które możesz również umieścić w swoim ~ / .gitconfig jako (uwaga: myślnik końcowy jest ważny; dziękuję Brianowi za zwrócenie na to uwagi) :

[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' -

Można to zrobić za pomocą następującego wiersza poleceń (zawiłego w cudzysłowie):

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'\'' -'

Uwaga: zshrównie łatwo mogło być bash, ale niesh będzie działać - składnia nie istnieje w wanilii . (Jeszcze raz dziękuję, @conny, za poinformowanie mnie o tym w komentarzu do innej odpowiedzi na tej stronie!)<()sh

Uwaga: alternatywna wersja powyższego:

Dzięki liori za zwrócenie uwagi na to , że powyższe może spaść przy porównywaniu identycznych gałęzi, i wymyślenie alternatywnej formy diff, która usuwa formę sed z mieszanki i czyni ją „bezpieczniejszą” (tj. Zwraca wynik (a mianowicie ostatnie zatwierdzenie), nawet jeśli porównujesz wzorzec do wzorca):

Jako wiersz .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' -

Z muszli:

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'\'' -'

Tak więc w moim drzewie testowym (które było przez chwilę niedostępne, przepraszam; wróciło), to działa teraz zarówno na wzorcu, jak i temacie (podając odpowiednio G i B). Jeszcze raz dziękuję, Liori, za alternatywną formę.


Tak właśnie wymyśliłem [i liori]. Wydaje mi się, że to działa. Pozwala również na dodanie kilku aliasów, które mogą się przydać:

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

Miłego zabawy!

Lindes
źródło
5
Dzięki Lindes, opcja powłoki jest świetna w sytuacjach, w których chcesz znaleźć punkt rozgałęzienia długo działającej gałęzi konserwacji. Kiedy szukasz wersji, która w przeszłości może być tysiącem zmian, opcje wizualne naprawdę nie zamierzają jej obcinać. * 8 ')
Mark Booth,
4
W trzeciej metodzie zależysz od tego, że kontekst pokaże pierwszą niezmienioną linię. Nie zdarza się to w niektórych przypadkach brzegowych lub jeśli masz nieco inne wymagania (np. Potrzebuję tylko jednej z historii - - pierwszy rodzic, i używam tej metody w skrypcie, który czasami może używać tego samego gałęzie po obu stronach). Uważam, że to bezpieczniejsze w użyciu diff„s if-then-else tryb i erase zmienione / usunięte linie z jego wyjściu zamiast liczenia na tyle duże, posiadające kontekst, przez.: 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)~też będzie działać, myślę
Tobias Schulte
4
@ JakubNarębski: Czy masz sposób na git merge-base --fork-point ...zatwierdzenie B (zatwierdzenie 6aafd7f) dla tego drzewa? Byłem zajęty innymi rzeczami, kiedy to opublikowałeś, i brzmiało to dobrze, ale w końcu po prostu spróbowałem i nie dostaję do pracy ... albo dostaję coś nowszego, albo po prostu cichą awarię (bez błędu) wiadomość, ale status wyjścia 1), starając się jak argumenty 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(dla każdej kasie), itd. czy coś robię źle lub brakuje?
Lindes
25
@ JakubNarębski @lindes --fork-pointopiera się na logowaniu, więc zadziała tylko wtedy, gdy wprowadzisz zmiany lokalnie. Nawet wtedy wpisy ponownego logowania mogły wygasnąć. Jest to przydatne, ale w ogóle nie jest niezawodne.
Daniel Lubarov,
131

Być może szukasz git merge-base:

git merge-base znajduje najlepszego wspólnego przodka (przodków) między dwoma zatwierdzeniami do użycia w łączeniu trójstronnym. Jeden wspólny przodek jest lepszy od drugiego wspólnego przodka, jeśli ten drugi jest przodkiem tego pierwszego. Wspólny przodek, który nie ma lepszego wspólnego przodka, jest najlepszym wspólnym przodkiem , tj . Bazą scalającą . Zauważ, że może istnieć więcej niż jedna baza scalania dla pary zatwierdzeń.

Greg Hewgill
źródło
10
Zwróć też uwagę na --allopcję „ git merge-base
Jakub Narębski,
10
To nie odpowiada na pierwotne pytanie, ale większość ludzi zadaje znacznie prostsze pytanie, na które jest odpowiedź :)
FelipeC
6
powiedział, że nie jest wynikiem git merge-base
Tom Tanner
9
@TomTanner: Właśnie spojrzałem na historię pytań i oryginalne pytanie zostało zredagowane w celu dołączenia tej notatki około git merge-basepięć godzin po opublikowaniu mojej odpowiedzi (prawdopodobnie w odpowiedzi na moją odpowiedź). Niemniej jednak pozostawię tę odpowiedź taką, jaka jest, ponieważ może być nadal przydatna dla kogoś, kto znajdzie to pytanie za pomocą wyszukiwania.
Greg Hewgill
Wierzę, że możesz użyć wyników z merge-base - all, a następnie przeszukać listę zatwierdzeń zwrotnych za pomocą merge-base --is-ancestor, aby znaleźć najstarszego wspólnego przodka z listy, ale to nie jest najprostszy sposób .
Matt_G
42

Użyłem git rev-listtego rodzaju rzeczy. Na przykład (zwróć uwagę na 3 kropki)

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

wypluje punkt rozgałęzienia. Teraz nie jest idealny; ponieważ kilka razy scaliłeś wzorzec z odgałęzieniem A, podzieli to kilka możliwych punktów rozgałęzienia (w zasadzie oryginalny punkt rozgałęzienia, a następnie każdy punkt, w którym scaliłeś wzorzec z odgałęzieniem A). Powinno to jednak przynajmniej zawęzić możliwości.

Dodałem to polecenie do moich aliasów ~/.gitconfigjako:

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

więc mogę to nazwać:

$ git diverges branch-a master
mipadi
źródło
Uwaga: wydaje się, że daje to pierwsze zatwierdzenie w gałęzi, a nie wspólnego przodka. (tzn. daje Gzamiast A, według wykresu w pierwotnym pytaniu.) Mam odpowiedź, która się pojawi A, że będę publikować w tej chwili.
Lindes
@lindes: Daje wspólnego przodka w każdym przypadku, gdy go wypróbowałem. Czy masz przykład, w którym nie ma?
mipadi
Tak. Na moją odpowiedź (która ma link do repo można sklonować, git checkout topica następnie uruchomić ten ze topiczamiast branch-a) Wymienia 648ca357b946939654da12aaf2dc072763f3caeei 37ad15952db5c065679d7fc31838369712f0b338- jak 37ad159i 648ca35są w ancestory obecnych oddziałów (ten ostatni jest obecny szef topic), ale nie ma też sensu przed rozgałęzieniem. Czy dostajesz coś innego?
Lindes
@lindes: Nie udało mi się sklonować twojego repozytorium (być może problem z uprawnieniami?).
mipadi
UPS przepraszam! Dziękuję za informację. Zapomniałem uruchomić git update-server-info. Warto już iść. :)
lindes
31

Jeśli lubisz krótkie polecenia,

git rev-list $ (git rev-list --pierwszy-rodzic ^ nazwa_gałęzi master | tail -n1) ^^! 

Oto wyjaśnienie.

Poniższe polecenie podaje listę wszystkich zatwierdzeń w systemie głównym, które wystąpiły po utworzeniu nazwa_gałęzi

git rev-list - pierwszy-rodzic ^ nazwa_gałęzi master 

Ponieważ zależy Ci tylko na najwcześniejszym z tych zatwierdzeń, chcesz mieć ostatni wiersz wyniku:

git rev-list ^ nazwa_gałęzi - pierwszy-główny master | ogon -n1

Rodzicem najwcześniejszego zatwierdzenia, który nie jest przodkiem „nazwa_ gałęzi”, z definicji jest w „nazwa_gałęzi” i jest w „głównym”, ponieważ jest przodkiem czegoś w „głównym”. Masz więc najwcześniejsze zatwierdzenie w obu gałęziach.

Komenda

git rev-list commit ^^!

jest tylko sposobem pokazania referencji zatwierdzenia nadrzędnego. Możesz użyć

git log -1 commit ^

lub cokolwiek.

PS: Nie zgadzam się z argumentem, że kolejność przodków jest nieistotna. To zależy od tego, czego chcesz. Na przykład w tym przypadku

_C1___C2_______ master
  \ \ _XXXXX_ gałąź A (Xs oznaczają dowolne przejścia między urządzeniem głównym a A)
   \ _____ / oddział B

ma sens wysyłać C2 jako zatwierdzenie „rozgałęzienia”. To wtedy deweloper rozgałęził się na „master”. Kiedy się rozgałęził, gałąź „B” nawet się nie połączyła! Oto, co daje rozwiązanie w tym poście.

Jeśli chcesz, aby ostatnie zatwierdzenie C było takie, że wszystkie ścieżki od początku do ostatniego zatwierdzenia w gałęzi „A” przechodzą przez C, to chcesz zignorować kolejność przodków. Jest to czysto topologiczne i daje wyobrażenie o tym, kiedy masz dwie wersje kodu działające w tym samym czasie. To wtedy wybrałeś podejście oparte na scalaniu, a to zwróci C1 w moim przykładzie.

Lionel
źródło
3
To zdecydowanie najczystsza odpowiedź, postawmy ją na szczycie. Sugerowana edycja: git rev-list commit^^!można uprościć jakogit rev-parse commit^
Russell Davis,
To powinna być odpowiedź!
trinth
2
Ta odpowiedź jest miły, po prostu zastąpić git rev-list --first-parent ^branch_name masterz git rev-list --first-parent branch_name ^masterbo jeżeli oddział główny jest 0 commity wyprzedza innego oddziału (szybko), którego przesłanie do niego, nie ma wyjścia będzie tworzony. Dzięki mojemu rozwiązaniu nie powstają żadne dane wyjściowe, jeśli master jest ściśle przed nami (tzn. Gałąź została całkowicie scalona), czego chcę.
Michael Schmeißer,
3
To nie zadziała, chyba że czegoś mi brakuje. Schematy są łączone w obu kierunkach w przykładowych gałęziach. Wygląda na to, że próbowałeś to wziąć pod uwagę, ale wierzę, że spowoduje to, że Twoja odpowiedź nie powiedzie się. git rev-list --first-parent ^topic masterzajmie tylko ty z powrotem do pierwszego popełnić po ostatnim scalania mastersię topic(jeśli w ogóle istnieje).
jerry
1
@jerry Masz rację, ta odpowiedź to śmieci; na przykład, w przypadku, gdy właśnie nastąpił backmerge (scalono wzorzec w temat), a następnie master nie ma żadnych nowych zatwierdzeń, pierwsza komenda git rev-list - first-parent w ogóle nic nie wyświetla.
TamaMcGlinn
19

Biorąc pod uwagę, że tak wiele odpowiedzi w tym wątku nie zawiera odpowiedzi, o którą pytało pytanie, oto podsumowanie wyników każdego rozwiązania, wraz ze skryptem, którego użyłem do replikacji repozytorium podanego w pytaniu.

Dziennik

Tworząc repozytorium z podaną strukturą, otrzymujemy dziennik git:

$ 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

Moim jedynym dodatkiem jest tag, który wyraźnie mówi o punkcie, w którym utworzyliśmy gałąź, a tym samym zatwierdzenie, które chcemy znaleźć.

Rozwiązanie, które działa

Jedynym działającym rozwiązaniem jest rozwiązanie dostarczone przez Lindes poprawnie zwraca A:

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

Jako Charles Bailey zauważa , to rozwiązanie jest bardzo kruche.

Jeśli branch_Ado master, a następnie połączyć mastersię branch_Abez interwencji zobowiązuje następnie roztwór Lindes' daje tylko Najnowsze Rozbieżność .

Oznacza to, że w moim przepływie pracy myślę, że będę musiał trzymać się tagowania punktu rozgałęzienia długo działających gałęzi, ponieważ nie mogę zagwarantować, że można je później znaleźć w wiarygodny sposób.

To wszystko sprowadza się do gitbraku hgwywołań nazwanych gałęzi . Bloger jhw nazywa te rodowody vs. rodziny w swoim artykule Dlaczego lubię Mercurial bardziej niż Git oraz w swoim kolejnym artykule More on Mercurial vs. Git (z wykresami!) . Polecam ludzie czytają im zrozumieć, dlaczego niektórzy nawróceni Nie przegap rtęciowe o nazwie oddziały w git.

Rozwiązania, które nie działają

Rozwiązanie dostarczone przez mipadi zwraca dwie odpowiedzi Ii C:

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

Rozwiązanie według Greg Hewgill powrotuI

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

Rozwiązanie dostarczone przez Karla zwraca X:

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

Scenariusz

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"

Wątpię, czy wersja git ma na to duży wpływ, ale:

$ git --version
git version 1.7.1

Dzięki Charlesowi Baileyowi za pokazanie mi bardziej zwartego sposobu pisania skryptów w przykładowym repozytorium.

Mark Booth
źródło
2
Rozwiązanie przez Karl jest łatwe do ustalenia: diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1. Dziękujemy za podanie instrukcji tworzenia repozytorium :)
FelipeC
Myślę, że masz na myśli „Oczyszczona odmiana rozwiązania dostarczonego przez Karla zwraca X”. Oryginał działał dobrze, był po prostu brzydki :-)
Karl
Nie, twój oryginał nie działa dobrze. To prawda, że ​​wariacja działa jeszcze gorzej. Ale dodanie opcji - najlepsze zamówienie powoduje, że Twoja wersja działa :)
FelipeC
@felipec - Zobacz mój ostatni komentarz do odpowiedzi Charlesa Baileya . Niestety nasz czat (a więc wszystkie stare komentarze) zostały teraz usunięte. Spróbuję zaktualizować swoją odpowiedź, kiedy będę miał czas.
Mark Booth
Ciekawy. Przyjąłem, że domyślna jest topologia. Silly me :-)
Karl
10

Zasadniczo nie jest to możliwe. W historii gałęzi rozgałęzienie i scalenie przed odgałęzieniem nazwanym rozgałęziono, a oddział pośredni dwóch nazwanych rozgałęzień wygląda tak samo.

W git gałęzie to tylko bieżące nazwy końcówek części historii. Nie mają tak naprawdę silnej tożsamości.

Nie jest to zwykle duży problem, ponieważ baza do scalania (patrz odpowiedź Grega Hewgilla) dwóch zatwierdzeń jest zwykle znacznie bardziej użyteczna, dając ostatnie zatwierdzenie, które zostały udostępnione przez dwa oddziały.

Rozwiązanie oparte na kolejności rodziców zatwierdzenia oczywiście nie zadziała w sytuacjach, w których oddział został w pełni zintegrowany w pewnym momencie historii oddziału.

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"

Ta technika również upada, jeśli scalenie integracji zostało wykonane z odwróconymi elementami nadrzędnymi (np. Tymczasowa gałąź została użyta do przeprowadzenia testu połączenia z master, a następnie szybko przesłana do gałęzi funkcji, aby dalej budować).

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"
CB Bailey
źródło
3
Dzięki Charles, przekonałeś mnie, że jeśli chcę poznać punkt, w którym gałąź początkowo się rozdzieliła , będę musiał to oznaczyć. Naprawdę chciałbym, aby gitmiał odpowiednik hgnazwanych oddziałów, dzięki czemu zarządzanie wieloletnimi oddziałami serwisowymi byłoby o wiele łatwiejsze.
Mark Booth,
1
„W Git gałęzie to tylko aktualne nazwy końcówek części historii. Nie mają tak naprawdę silnej tożsamości”. To przerażające stwierdzenie i przekonało mnie, że muszę lepiej zrozumieć gałęzie Git - dzięki (+ 1)
dumbledad,
W historii gałęzi rozgałęzienie i scalenie przed odgałęzieniem nazwanym zostało rozgałęzione, a oddział pośredni dwóch nazwanych oddziałów wygląda tak samo. Tak. +1.
user1071847
5

Co powiesz na coś takiego

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`^
Karl
źródło
To działa. To naprawdę kłopotliwe, ale to jedyna rzecz, którą znalazłem, która wydaje się wykonywać tę pracę.
GaryO
1
Odpowiednik aliasu Git: diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'(bez plików tymczasowych)
conny
@conny: Oh, wow - nigdy nie widziałem składni <(foo) ... to jest niezwykle przydatne, dzięki! (Działa również w
Zsh
wydaje mi się, że daje to pierwsze zatwierdzenie na gałęzi, a nie wspólnego przodka. (tzn. daje Gzamiast A, według wykresu w pierwotnym pytaniu.) Myślę, że znalazłem odpowiedź, którą wkrótce opublikuję.
Lindes
Zamiast „git log --pretty = oneline” możesz po prostu zrobić „git rev-list”, a następnie możesz również pominąć cięcie, co więcej, daje to rodzicowi zatwierdzenie punktu rozbieżności, więc po prostu ogon -2 | głowa 1. Więc:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
FelipeC
5

na pewno coś mi brakuje, ale IMO, wszystkie powyższe problemy są spowodowane, ponieważ zawsze staramy się znaleźć punkt rozgałęzienia w historii, a to powoduje różnego rodzaju problemy z powodu dostępnych kombinacji scalania.

Zamiast tego zastosowałem inne podejście, oparte na tym, że obie gałęzie mają wiele historii, dokładnie cała historia przed rozgałęzieniem jest w 100% taka sama, więc zamiast wracać, moja propozycja dotyczy kontynuacji (od 1 commit), szukając pierwszej różnicy w obu gałęziach. Punkt rozgałęzienia będzie po prostu rodzicem pierwszej znalezionej różnicy.

W praktyce:

#!/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

I rozwiązuje wszystkie moje zwykłe przypadki. Pewnie, że są granice nieobjęte, ale ... ciao :-)

stronk7
źródło
diff <(git rev-list "$ {1: -master}" --pierwszy-rodzic) <(git rev-list "$ {2: -HEAD}" --pierwszy-rodzic) -U1 | ogon -1
Alexander Bird
Odkryłem, że jest to szybsze (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

Po wielu badaniach i dyskusjach jasne jest, że nie ma magicznej kuli, która działałaby we wszystkich sytuacjach, a przynajmniej nie w obecnej wersji Git.

Dlatego napisałem kilka łatek, które dodają koncepcję tailgałęzi. Za każdym razem, gdy tworzona jest gałąź, tworzony jest również wskaźnik do oryginalnego punktu, tailref. To odniesienie jest aktualizowane za każdym razem, gdy gałąź jest ponownie wydawana.

Aby znaleźć punkt rozgałęzienia gałęzi deweloperskiej, wystarczy użyć devel@{tail}, to wszystko.

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

FelipeC
źródło
1
Może to być jedyne stabilne rozwiązanie. Widziałeś, czy to może dostać się do git? Nie widziałem żądania ściągnięcia.
Alexander Klimetschek
@AlexanderKlimetschek Nie wysłałem łatek i nie sądzę, by zostały zaakceptowane. Jednak wypróbowałem inną metodę: hak „rozgałęzienia aktualizacji”, który robi coś bardzo podobnego. W ten sposób Git domyślnie nic nie zrobiłby, ale można włączyć hak, aby zaktualizować gałąź ogona. Nie miałbyś jednak devel @ {tail}, ale nie byłoby tak źle, gdybyś używał zamiast niego tails / devel.
FelipeC
3

Oto ulepszona wersja mojej poprzedniej odpowiedzi poprzedniej odpowiedzi . Opiera się na komunikatach zatwierdzeń z fuzji, aby znaleźć miejsce, w którym gałąź została utworzona po raz pierwszy.

Działa na wszystkich wymienionych tutaj repozytoriach, a nawet zająłem się kilkoma trudnymi, które pojawiły się na liście mailingowej . Do tego napisałem również testy .

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
}
FelipeC
źródło
3

Następujące polecenie ujawni SHA1 dla Commit A.

git merge-base --fork-point A

Gayan Pathirage
źródło
Nie będzie to miało znaczenia, jeśli oddziały nadrzędne i podrzędne mają między sobą pośrednie połączenia.
nawfal
2

Wydaje mi się, że czerpię radość

git rev-list branch...master

Ostatnia linia, którą otrzymujesz, jest pierwszym zatwierdzeniem w gałęzi, więc kwestia uzyskania tego jest nadrzędna. Więc

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

Wydaje się, że działa dla mnie i nie potrzebuje różnic itp. (Co jest pomocne, ponieważ nie mamy tej wersji różnic)

Korekta: To nie działa, jeśli jesteś w gałęzi master, ale robię to w skrypcie, więc to mniejszy problem

Tom Tanner
źródło
2

Aby znaleźć zatwierdzenia z punktu rozgałęzienia, możesz użyć tego.

git log --ancestry-path master..topicbranch
Andor
źródło
To polecenie nie działa dla mnie na podanym przykładzie. Co podasz jako parametry dla zakresu zatwierdzeń?
Jesper Rønn-Jensen
2

Czasami jest to praktycznie niemożliwe (z pewnymi wyjątkami, gdzie możesz mieć dodatkowe dane), a rozwiązania tutaj nie działają.

Git nie zachowuje historii odwołań (w tym gałęzi). Przechowuje tylko aktualną pozycję dla każdej gałęzi (głowy). Oznacza to, że z czasem możesz stracić trochę historii gałęzi. Na przykład za każdym razem, gdy rozgałęziasz się, natychmiast tracisz, która gałąź była oryginalna. Wszystko, co robi oddział, to:

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

Możesz założyć, że pierwszy zaangażowany w to oddział. Tak bywa, ale nie zawsze tak jest. Nic nie stoi na przeszkodzie, abyś po rozpoczęciu powyższej operacji najpierw skierował się do któregoś z oddziałów. Ponadto nie można zagwarantować niezawodności znaczników czasu git. Dopiero gdy zobowiążesz się do tego, że naprawdę staną się strukturalnie gałęziami.

Podczas gdy na diagramach mamy tendencję do numerowania zatwierdzeń koncepcyjnie, git nie ma naprawdę stabilnej koncepcji sekwencji, gdy gałęzie drzewa zatwierdzenia. W takim przypadku możesz założyć, że liczby (wskazujące kolejność) są określane przez znacznik czasu (fajnie jest zobaczyć, jak interfejs użytkownika git obsługuje rzeczy, gdy ustawisz wszystkie znaczniki czasu na to samo).

Oto, czego człowiek oczekuje koncepcyjnie:

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

Oto, co faktycznie otrzymujesz:

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

Zakładasz, że B1 jest oryginalną gałęzią, ale może w rzeczywistości być po prostu martwą gałęzią (ktoś zrobił kasę -b, ale nigdy się jej nie zobowiązał). Dopiero po zaakceptowaniu obu uzyskasz prawidłową strukturę gałęzi w git:

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

Zawsze wiesz, że C1 pojawił się przed C2 i C3, ale nigdy nie wiesz, czy C2 pojawił się przed C3, czy C3 przed C2 (ponieważ możesz na przykład ustawić czas na stacji roboczej na cokolwiek). B1 i B2 również wprowadzają w błąd, ponieważ nie wiadomo, która gałąź była pierwsza. W wielu przypadkach można bardzo dobrze i zazwyczaj dokładnie zgadnąć. To trochę jak tor wyścigowy. Wszystko jest na ogół równe samochodom, wtedy możesz założyć, że samochód, który wyprzedza okrążenie, rozpoczął okrążenie z tyłu. Mamy również konwencje, które są bardzo niezawodne, na przykład mistrz prawie zawsze reprezentuje najdłużej żyjące gałęzie, chociaż niestety widziałem przypadki, w których nawet tak nie jest.

Podany tutaj przykład to przykład zachowania historii:

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)

Prawdziwe tutaj jest również mylące, ponieważ my, ludzie, czytamy to od lewej do prawej, od korzenia do liścia (odnośnik). Git tego nie robi. Tam, gdzie robimy (A-> B) w naszych głowach, robi git (A <-B lub B-> A). Czyta to od ref do root. Refs mogą być gdziekolwiek, ale zwykle są to liście, przynajmniej dla aktywnych gałęzi. Ref wskazuje na zatwierdzenie i zatwierdzenie zawiera tylko znak podobny do ich rodziców / rodziców, a nie do dzieci. Gdy zatwierdzenie jest zatwierdzeniem scalania, będzie miało więcej niż jednego rodzica. Pierwszy element nadrzędny jest zawsze oryginalnym zatwierdzeniem, z którym został scalony. Pozostali rodzice są zawsze zatwierdzeniami, które zostały połączone w pierwotne zatwierdzenie.

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)))))

Nie jest to bardzo wydajna reprezentacja, a raczej wyrażenie wszystkich ścieżek, które git może pobrać z każdego odwołania (B1 i B2).

Pamięć wewnętrzna Gita wygląda mniej więcej tak (nie że A jako rodzic pojawia się dwukrotnie):

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

Jeśli zrzucisz surowe zatwierdzenie git, zobaczysz zero lub więcej pól nadrzędnych. Jeśli jest zero, to znaczy, że nie ma rodzica, a zatwierdzenie jest rootem (możesz mieć wiele katalogów głównych). Jeśli jest taki, oznacza to, że nie było scalenia i nie jest to zatwierdzenie główne. Jeśli jest więcej niż jeden, oznacza to, że zatwierdzenie jest wynikiem scalenia, a wszyscy rodzice po pierwszym są zobowiązaniami do scalenia.

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)

Kiedy oba trafią A, ich łańcuch będzie taki sam, wcześniej ich łańcuch będzie zupełnie inny. Pierwsze popełnienie dwóch kolejnych wspólnych zobowiązań jest wspólnym przodkiem i skąd się rozeszły. mogą występować pewne nieporozumienia między warunkami zatwierdzenia, rozgałęzienia i ref. Możesz w rzeczywistości scalić zatwierdzenie. To właśnie robi scalanie. Ref po prostu wskazuje na zatwierdzenie, a gałąź jest niczym innym jak ref w folderze .git / refs / heads, lokalizacja folderu określa, że ​​ref jest gałąź, a nie czymś innym, jak tag.

Kiedy tracisz historię, połączenie polega na zrobieniu jednej z dwóch rzeczy w zależności od okoliczności.

Rozważać:

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

W takim przypadku scalenie w dowolnym kierunku spowoduje utworzenie nowego zatwierdzenia z pierwszym nadrzędnym jako zatwierdzonym wskazanym przez bieżącą wypisaną gałąź, a drugim nadrzędnym jako zatwierdzonym na końcu gałęzi, którą scaliłeś w bieżącą gałąź. Musi utworzyć nowe zatwierdzenie, ponieważ obie gałęzie mają zmiany od czasu ich wspólnego przodka, które należy połączyć.

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

W tym momencie D (B1) ma teraz oba zestawy zmian z obu gałęzi (siebie i B2). Jednak druga gałąź nie ma zmian w stosunku do B1. Jeśli scalisz zmiany z B1 do B2, aby zostały zsynchronizowane, możesz spodziewać się czegoś, co wygląda następująco (możesz zmusić git merge, aby zrobił to w ten sposób, używając --no-ff):

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

Otrzymasz to, nawet jeśli B1 ma dodatkowe zatwierdzenia. Dopóki nie zostaną wprowadzone zmiany w B2, których B1 nie ma, oba odgałęzienia zostaną połączone. Robi szybkie przewijanie do przodu, które jest jak rebase (rebazy także jedzą lub linearyzują historię), z wyjątkiem tego, że w przeciwieństwie do rebase, ponieważ tylko jedna gałąź ma zestaw zmian, nie musi stosować zestawu zmian z jednej gałęzi na drugą względem drugiej.

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

Jeśli przestaniesz pracować nad B1, wszystko będzie dobrze dla zachowania historii na dłuższą metę. Tylko B1 (który może być mistrzem) będzie się zwykle rozwijał, więc lokalizacja B2 w historii B2 z powodzeniem reprezentuje punkt, w którym została scalona z B1. Właśnie tego oczekuje od ciebie git, aby rozgałęzić B z A, a następnie możesz scalić A w B tak, jak chcesz, w miarę gromadzenia się zmian, jednak po scaleniu B z powrotem w A nie oczekuje się, że będziesz pracował na B i dalej . Jeśli kontynuujesz pracę nad swoim oddziałem po szybkim przeniesieniu go z powrotem do gałęzi, nad którą pracowałeś, to za każdym razem poprzednia historia twojego B. Naprawdę tworzysz nową gałąź za każdym razem po szybkim zatwierdzeniu do źródła, a następnie do gałęzi.

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

Od 1 do 3 i od 5 do 8 są gałęziami strukturalnymi, które pojawiają się, jeśli śledzisz historię dla 4 lub 9. Nie ma sposobu, aby wiedzieć, do której z tych nienazwanych i niepowiązanych gałęzi strukturalnych należą gałęzie nazwane i odniesienia jako koniec konstrukcji. Możesz założyć na tym rysunku, że 0 do 4 należy do B1, a 4 do 9 należy do B2, ale oprócz 4 i 9 nie mogłem wiedzieć, która gałąź należy do której gałęzi, po prostu narysowałem to w sposób, który daje iluzja tego. 0 może należeć do B2, a 5 może należeć do B1. W tym przypadku istnieje 16 różnych możliwości, do których nazwanych gałęzi może należeć każda gałąź strukturalna.

Istnieje wiele strategii git, które działają w ten sposób. Możesz wymusić, aby git merge nigdy nie przewijał do przodu i zawsze tworzył gałąź scalania. Strasznym sposobem na zachowanie historii gałęzi jest użycie tagów i / lub gałęzi (tagi są naprawdę zalecane) zgodnie z wybraną konwencją. Naprawdę nie poleciłbym pustego zatwierdzenia pustego w gałęzi, w której się łączysz. Bardzo powszechną konwencją jest to, aby nie łączyć się z gałęzią integracji, dopóki nie chce się naprawdę zamknąć gałęzi. Jest to praktyka, do której ludzie powinni się stosować, ponieważ w przeciwnym razie pracujesz nad punktem posiadania oddziałów. Jednak w prawdziwym świecie ideał nie zawsze jest praktyczny, co oznacza, że ​​właściwe postępowanie nie jest wykonalne w każdej sytuacji. Jeśli co

jgmjgm
źródło
„Git nie zachowuje historii odwołań” Tak, ale nie domyślnie i nie przez długi czas. Zobacz man git-reflogi część o datach: „master@{one.week.ago} oznacza„ gdzie master wskazywał tydzień temu w tym lokalnym repozytorium ””. Lub dyskusja na temat <refname>@{<date>}w man gitrevisions. I core.reflogExpirew man git-config.
Patrick Mevzek,
2

Nie do końca rozwiązanie tego pytania, ale pomyślałem, że warto zwrócić uwagę na podejście, które stosuję, gdy mam długą gałąź życia:

W tym samym czasie tworzę gałąź, tworzę również tag o tej samej nazwie, ale z -initprzyrostkiem, na przykład feature-branchi feature-branch-init.

(To trochę dziwne, że na tak trudne pytanie należy odpowiedzieć!)

Stephen Darlington
źródło
1
Biorąc pod uwagę oszałamiającą głupotę zaprojektowania koncepcji „gałęzi” bez pojęcia, kiedy i gdzie została stworzona ... plus ogromne komplikacje innych sugerowanych rozwiązań - przez ludzi próbujących prześcignąć w ten sposób - zbyt sprytnie myślę, że wolałbym twoje rozwiązanie. Tylko to obciąża potrzebę PAMIĘTAJ, aby robić to za każdym razem, gdy tworzysz oddział - coś, co użytkownicy git robią bardzo często. Ponadto - czytałem gdzieś, że „tag ma karę bycia„ ciężkim ”. Mimo to myślę, że tak właśnie zrobię.
Motti Shneor,
1

Prostym sposobem, aby ułatwić zobaczenie punktu rozgałęzienia, git log --graphjest skorzystanie z tej opcji --first-parent.

Na przykład weź repo z zaakceptowanej odpowiedzi :

$ 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

Teraz dodaj --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

To ułatwia!

Uwaga: jeśli repozytorium ma wiele gałęzi, będziesz chciał określić 2 gałęzie, które porównujesz zamiast używać --all:

$ git log --decorate --oneline --graph --first-parent master origin/topic
binaryfunt
źródło
0

Problem wydaje się polegać na znalezieniu najnowszego cięcia pojedynczego zatwierdzenia między obiema gałęziami z jednej strony, a najwcześniejszym wspólnym przodkiem z drugiej (prawdopodobnie początkowego zatwierdzenia repo). Jest to zgodne z moją intuicją dotyczącą tego, czym jest punkt „rozgałęzienia”.

Biorąc to pod uwagę, wcale nie jest to łatwe do obliczenia za pomocą normalnych poleceń powłoki git, ponieważ git rev-list- nasze najpotężniejsze narzędzie - nie pozwala nam ograniczyć ścieżki, przez którą osiągany jest zatwierdzenie. Najbliżej mamy git rev-list --boundary, która może dać nam zestaw wszystkich zatwierdzeń, które „zablokowały nam drogę”. (Uwaga: git rev-list --ancestry-pathjest interesujące, ale nie wiem, jak to zrobić, żeby było przydatne).

Oto skrypt: https://gist.github.com/abortz/d464c88923c520b79e3d . Jest to stosunkowo proste, ale ze względu na pętlę jest wystarczająco skomplikowane, aby uzasadnić istnienie istoty.

Zauważ, że większość innych proponowanych tutaj rozwiązań nie może działać we wszystkich sytuacjach z prostego powodu: git rev-list --first-parentnie jest wiarygodna w linearyzacji historii, ponieważ można łączyć z dowolnym porządkiem.

git rev-list --topo-orderz drugiej strony jest bardzo przydatny - do chodzenia zatwierdzeń w kolejności topograficznej - ale wykonywanie różnic jest kruche: istnieje wiele możliwych kolejności topograficznych dla danego wykresu, więc zależy to od pewnej stabilności uporządkowania. To powiedziawszy, rozwiązanie strongk7 prawdopodobnie działa cholernie przez większość czasu. Jednak wolniej jest w tej kopalni, ponieważ muszę przejść całą historię repo ... dwa razy. :-)

Andrew Bortz
źródło
0

Poniżej zaimplementowano git równoważny svn log - stop-on-copy i można go również użyć do znalezienia początku gałęzi.

Podejście

  1. Udaj się do wszystkich gałęzi
  2. zbieraj mergeBase dla gałęzi docelowej nawzajem
  3. git.log i iteracja
  4. Zatrzymaj przy pierwszym zatwierdzeniu, który pojawia się na liście mergeBase

Podobnie jak wszystkie rzeki biegną do morza, wszystkie gałęzie biegną do opanowania, dlatego też znajdujemy bazę scalającą między pozornie niezwiązanymi gałęziami. Gdy wracamy od głowy gałęzi przez przodków, możemy zatrzymać się przy pierwszej potencjalnej bazie scalania, ponieważ teoretycznie powinien to być punkt początkowy tej gałęzi.

Notatki

  • Nie próbowałem takiego podejścia, w którym gałęzie rodzeństwa i kuzyna łączyły się ze sobą.
  • Wiem, że musi być lepsze rozwiązanie.

szczegóły: https://stackoverflow.com/a/35353202/9950

Peter Kahn
źródło
-1

Możesz użyć następującego polecenia, aby zwrócić najstarsze zatwierdzenie w gałęzi_a, które nie jest dostępne z poziomu master:

git rev-list branch_a ^master | tail -1

Być może dzięki dodatkowej kontroli poczytalności, że rodzic tego zatwierdzenia jest rzeczywiście osiągalny od mistrza ...

Marc Richarme
źródło
1
To nie działa Jeśli branch_a zostanie raz scalony w master, a następnie będzie kontynuowany, zatwierdzenia na tym scaleniu będą uważane za część master, więc nie pojawią się w ^ master.
FelipeC
-2

Możesz przejrzeć dziennik oddziału A, aby dowiedzieć się, z którego zatwierdzenia został utworzony, a także pełną historię zatwierdzeń wskazanych przez tę gałąź. Rejestruje się .git/logs.

Paggas
źródło
2
Nie sądzę, że to działa ogólnie, ponieważ dziennik logowania może zostać przycięty. I nie sądzę, aby (?) Reflogi były wypychane, więc działałoby to tylko w sytuacji pojedynczego repo.
GaryO
-2

Wydaje mi się, że znalazłem sposób, który rozwiązuje wszystkie wymienione tutaj przypadki narożne:

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 ma rację, że rozwiązania oparte na porządku przodków mają jedynie ograniczoną wartość; na koniec dnia potrzebujesz jakiegoś zapisu „to zatwierdzenie pochodzi z gałęzi X”, ale taki zapis już istnieje; domyślnie „git merge” użyłby komunikatu zatwierdzenia, takiego jak „Scal gałąź” gałąź_A „w master”, to mówi, że wszystkie zmiany z drugiego elementu nadrzędnego (zatwierdzenie ^ 2) pochodziły z „gałęzi_A” i zostały scalone z pierwszym rodzic (commit ^ 1), który jest „master”.

Uzbrojeni w tę informację możesz znaleźć pierwsze scalenie „branch_A” (czyli wtedy, gdy naprawdę powstało „branch_A”) i znaleźć bazę scalającą, która byłaby punktem rozgałęzienia :)

Próbowałem z repozytoriami Marka Bootha i Charlesa Baileya i rozwiązanie działa; jak to nie mogło? Jedynym sposobem, aby to nie zadziałało, jest ręczna zmiana domyślnego komunikatu zatwierdzenia dla scalania, tak aby informacje o oddziale zostały naprawdę utracone.

Przydatność:

[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'

Następnie możesz zrobić ' git branch-point branch_A'.

Cieszyć się ;)

FelipeC
źródło
4
Poleganie na komunikatach o scalaniu jest bardziej kruche niż hipoteza o kolejności rodziców. To nie tylko sytuacja hipotetyczna; Często używam git merge -mdo powiedzenia tego , w co się połączyłem, a nie nazwy potencjalnie efemerycznej gałęzi (np. „Łącz zmiany głównych linii w refaktor funkcji xyz”). Załóżmy, że -mw moim przykładzie byłam mniej pomocna ? Problem jest po prostu nierozstrzygalny w całości, ponieważ mogę stworzyć tę samą historię z jedną lub dwiema gałęziami tymczasowymi i nie ma sposobu, aby powiedzieć różnicę.
CB Bailey
1
@CharlesBailey To jest twój problem. Nie powinieneś usuwać tych wierszy z wiadomości zatwierdzenia, resztę wiadomości należy dodać poniżej oryginalnej. Obecnie „git merge” automatycznie otwiera edytor, w którym możesz dodać cokolwiek chcesz, a dla starych wersji git możesz zrobić „git merge - edit”. Tak czy inaczej, możesz użyć haka zatwierdzania, aby dodać „zatwierdzenie na gałęzi 'foo'” do każdego zatwierdzenia, jeśli tego naprawdę chcesz. Jednak to rozwiązanie działa dla większości ludzi .
FelipeC
2
Nie działało dla mnie. Branch_A został rozwidlony z mastera, który miał już wiele połączeń. Ta logika nie dała mi dokładnego skrótu zatwierdzenia, w którym utworzono gałąź_A.
Venkat Kotra