Znalezienie punktu rozgałęzienia z Gitem?

Mam repozytorium z gałęziami master i A i wieloma aktywnościami scalania między nimi. Jak mogę znaleźć commit w repozytorium, gdy gałąź a została utworzona na podstawie wzorca?

Moje repozytorium wygląda w zasadzie tak:

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

Szukam rewizji A, która nie jest tym, co git merge-base (--all) znajduje.

 366
Author: Matt Ball, 2009-10-06

21 answers

Szukałam tego samego i znalazłam to pytanie. Dziękuję, że pytasz!

Jednak odkryłem, że odpowiedzi, które tu widzę, nie wydają się całkiem dawać odpowiedź, o którą prosiłeś (lub której szukałem) - wydają się dawać G commit, zamiast A commit.

Więc, stworzyłem następujące drzewo (litery przypisane w porządku chronologicznym), więc mogłem przetestować rzeczy:

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

To wygląda trochę inaczej niż Twoje, ponieważ chciałem się upewnić ,że mam (odnosząc się do tego grafu, nie do twojego) B, ale nie A (i nie D lub E). Oto litery dołączone do przedrostków SHA i komunikatów commit (moje repo można sklonować z tutaj , jeśli to kogoś interesuje):

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: Znajdź B. Oto trzy sposoby, które znalazłem, po odrobinie majsterkowania: {]}


1. wizualnie, z gitk:

Powinieneś wizualnie zobaczyć takie drzewo (jak oglądane z mistrz): {]}

gitk screen capture from master

Lub tutaj (jak z tematu):

gitk screen capture from topic

W obu przypadkach wybrałem commit B na moim wykresie. Po kliknięciu na niego, jego pełny SHA jest wyświetlany w polu tekstowym tuż pod wykresem.


2. wizualnie, ale z terminala:

git log --graph --oneline --all

(Edit / side-note: dodanie --decorate może być również interesujące; dodaje wskazanie nazw gałęzi, znaczników itp. Nie dodawanie tego do wiersz poleceń powyżej, ponieważ wynik poniżej nie odzwierciedla jego użycia.)

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

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

Lub w tekście prostym:

*   a9546a2 merge from topic back to master
|\  
| *   648ca35 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

W obu przypadkach widzimy commit 6aafd7f jako najniższy wspólny punkt, tzn. {[11] } w moim wykresie, lub A w Twoim.


3. Z magią muszli:

Nie podajesz w pytaniu, czy chcesz coś takiego jak wyżej, czy pojedyncze polecenie, które po prostu ci to rewizja i nic więcej. No to jest to drugie:

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

Które można również umieścić w swoim~/.gitconfig jako (uwaga: końcówka jest ważna; dzięki Brian 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' -

Które można wykonać za pomocą następującej (połączonej z cytowaniem) linii poleceń:

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: zsh równie łatwo mogło być bash, Ale shbędzie nie będzie działać -- składnia <() Nie istnieje w vanilla sh. (Dziękuję znowu ty, @conny, za uświadomienie mi tego w komentarzu do innej odpowiedzi na tej stronie!)

Uwaga: alternatywna wersja powyższego:

Dzięki liori za wskazywanie że powyższe może spaść przy porównywaniu identycznych gałęzi i wymyślaniu alternatywnego formularza diff, który usuwa formularz sed z miksu i czyni go "bezpieczniejszym" (tzn. zwraca wynik (a mianowicie najnowszy commit), nawet jeśli porównasz master z mistrz): {]}

Jakolinia 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 niedostępne przez jakiś czas, przepraszam; wróciło), które teraz działa zarówno na master jak i topic(podając commity G i B, odpowiednio). Jeszcze raz dziękuję, liori, za alternatywną formę.


Więc, to jest to, co wymyśliłem [i liori]. Wydaje mi się, że to działa. Pozwala również na dodatkowe kilka aliasów, które mogą okazać się przydatne:

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

Happy git-ing!

 415
Author: lindes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-03 03:12:53

You may be looking for git merge-base:

git merge-base znajduje najlepszy wspólny przodek (- y) pomiędzy dwoma commitami do wykorzystania w trójstronnym scalaniu. Jeden wspólny przodek jest lepszy niż inny wspólny przodek, jeśli ten ostatni jest przodkiem pierwszego. Wspólny przodek, który nie ma lepszego wspólnego przodka, to najlepszy wspólny przodek , tzn. baza scalająca . Zauważ, że może być więcej niż jedna baza scalająca dla pary zatwierdzeń.

 114
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-26 12:22:52

Używałem git rev-list do tego typu rzeczy. Na przykład (zwróć uwagę na 3 kropki)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-
Wypluwa punkt rozgałęzienia. Teraz, to nie jest idealne; ponieważ połączyłeś master w gałąź a kilka razy, to podzieli się kilka możliwych punktów gałęzi(zasadniczo, oryginalny punkt gałęzi, a następnie każdy punkt, w którym połączyłeś master w gałąź a). Powinno to jednak przynajmniej zawęzić możliwości.

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

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

Więc mogę to nazwać:

$ git diverges branch-a master
 31
Author: mipadi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-25 03:00:38

Jeśli lubisz krótkie polecenia,

git rev-list $(git rev-list --first-parent ^branch_name master | tail -n1)^^! 
Oto Wyjaśnienie.

Poniższe polecenie wyświetla listę wszystkich commitów w master, które wystąpiły po utworzeniu branch_name

git rev-list --first-parent ^branch_name master 

Ponieważ zależy Ci tylko na najwcześniejszym z tych commitów, chcesz mieć ostatnią linię wyjścia:

git rev-list ^branch_name --first-parent master | tail -n1

Rodzic najwcześniejszego commita, który nie jest przodkiem "branch_name", jest z definicji W "branch_name" i jest w "master", ponieważ jest przodkiem coś w " Mistrzu."Więc masz najwcześniejszy commit, który jest w obu gałęziach.

Komenda

git rev-list commit^^!

Jest tylko sposobem na pokazanie odnośnika do zatwierdzania przez rodzica. You could use

git log -1 commit^
Albo cokolwiek.

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

_C1___C2_______ master
  \    \_XXXXX_ branch A (the Xs denote arbitrary cross-overs between master and A)
   \_____/ branch B

To ma sens wypisywać C2 jako commit "rozgałęziający". To jest, gdy deweloper rozgałęził się od " master."Kiedy on rozgałęziony, gałąź " B " nie została nawet połączona w jego gałąź! To jest to, co daje rozwiązanie w tym poście.

Jeśli chcesz, aby ostatni commit C był taki, aby wszystkie ścieżki od origin do ostatniego commita w gałęzi "A" przechodziły przez c, to chcesz zignorować kolejność przodków. To jest czysto topologiczne i daje wyobrażenie o tym, od kiedy masz dwie wersje kodu działające w tym samym czasie. Wtedy można przejść z podejściami merge-base i zwróci C1 w moim przykładzie.

 25
Author: Lionel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-31 13:49:59

Biorąc pod uwagę, że tak wiele odpowiedzi w tym wątku nie daje odpowiedzi, o którą pytano, 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 o podanej strukturze, otrzymujemy git log:

$ 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 znacznik, który jasno określa punkt, w którym stworzyliśmy gałąź, a tym samym commit, który chcemy znajdź.

Rozwiązanie, które działa

Jedynym rozwiązaniem, które działa jest to, 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
Jak zauważa Charles Bailey, rozwiązanie to jest bardzo kruche.

Jeśli branch_A do master i połączysz master do branch_A bez ingerowania w commity, To rozwiązanie lindesa daje tylkoostatnią pierwszą rozbieżność .

To oznacza, że dla mojego przepływu pracy, myślę, że idę muszę trzymać się tagowania punktu gałęzi długich gałęzi, ponieważ nie mogę zagwarantować, że można je niezawodnie znaleźć później.

To naprawdę wszystko sprowadza się do gitbraku tego, co hg nazywa się gałęziami . Bloger JHW nazywa te linie vs. rodziny w swoim artykule dlaczego bardziej lubię Mercurial niż Git i jego kolejny artykuł {52]} więcej na temat Mercurial vs. Git (z Grafami!). Polecam ludziom przeczytać je do zobacz, dlaczego niektóre Konwertery mercurial nie mają nazwanych gałęzi w git.

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

Rozwiązanie dostarczone przez mipadi zwraca dwie odpowiedzi, I i C:

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

Rozwiązanie dostarczone przez Greg Hewgill return I

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

Rozwiązanie dostarczone przez Karl zwraca X:

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

Skrypt

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ę, aby wersja git zrobiła wiele różnicy w tym, ale:

$ git --version
git version 1.7.1

Podziękowania dla Charlesa Baileya za pokazanie mi bardziej zwartego sposobu skryptowania przykładowego repozytorium.

 18
Author: Mark Booth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:54

Ogólnie rzecz biorąc, nie jest to możliwe. W historii gałęzi gałąź-i-merge przed nazwaną gałęzią została rozgałęziona i gałąź pośrednia dwóch nazwanych gałęzi wygląda tak samo.

W git, gałęzie są tylko bieżącymi nazwami końcówek sekcji historii. Nie mają silnej tożsamości.

Zwykle nie jest to duży problem, ponieważ baza scalająca (zobacz odpowiedź Grega Hewgilla) dwóch commitów jest zwykle dużo bardziej użyteczna, dając najnowszy commit, który / align = "left" /

Rozwiązanie opierające się na kolejności rodziców commita oczywiście nie będzie działać w sytuacjach, w których gałąź została w pełni zintegrowana w pewnym momencie historii gałęzi.

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 upada również, jeśli scalenie integracyjne zostało wykonane z odwróconymi rodzicami(np. tymczasowa gałąź została użyta do wykonania testowego scalenia do master, a następnie szybko przekazana do gałęzi feature, 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"
 10
Author: CB Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-03 21:58:00

A może 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`^
 5
Author: Karl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-05 10:30:49

Ostatnio też musiałem rozwiązać ten problem i skończyło się na napisaniu skryptu Ruby do tego: https://github.com/vaneyckt/git-find-branching-point

 4
Author: Reck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-29 14:57:25

Z pewnością czegoś mi brakuje, ale IMO wszystkie powyższe problemy są spowodowane, ponieważ zawsze staramy się znaleźć punkt rozgałęzienia w historii, a to powoduje wszelkiego rodzaju problemy z powodu dostępnych kombinacji łączenia.

Zamiast tego zastosowałem inne podejście, polegające na tym, że obie gałęzie mają wspólną historię, dokładnie cała historia przed rozgałęzieniem jest w 100% taka sama, więc zamiast wracać, Moja propozycja polega na pójściu do przodu (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 sprawy. Jasne, że są te graniczne, które nie są objęte, ale... ciao : -)
 3
Author: stronk7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-01 11:47:13

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

Dlatego napisałem kilka łatek, które dodają pojęcie gałęzi tail. Za każdym razem, gdy tworzona jest gałąź, tworzony jest również wskaźnik do oryginalnego punktu, tail ref. Ten ref jest aktualizowany za każdym razem, gdy gałąź jest rebasowana.

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

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

 3
Author: FelipeC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-01 17:12:21

Następujące polecenie ujawni SHA1 z Commit a

git merge-base --fork-point A

 3
Author: Gayan Pathirage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-03 12:01:02

Oto poprawiona wersja mojej poprzedniej odpowiedzi poprzednia odpowiedź . Opiera się na komunikatach commit z merges, aby znaleźć miejsce, w którym gałąź została utworzona po raz pierwszy.

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

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
}
 2
Author: FelipeC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:26

I seem to be getting some joy with

git rev-list branch...master

Ostatnia linijka, którą otrzymujesz, to pierwszy commit na gałęzi, więc jest to kwestia uzyskania rodzica tej gałęzi. Więc

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

Wydaje się działać dla mnie i nie potrzebuje diffów itp. (co jest pomocne, ponieważ nie mamy tej wersji diff)

Poprawka: to nie działa, jeśli jesteś na gałęzi master, ale robię to w skrypcie, więc to mniej problem

 2
Author: Tom Tanner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-21 09:45:50

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

git log --ancestry-path master..topicbranch
 2
Author: Andor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-11 16:32:01

Czasami jest to praktycznie niemożliwe (z pewnymi wyjątkami, gdzie możesz mieć szczęście, aby mieć dodatkowe dane) i rozwiązania tutaj nie będą działać.

Git nie zachowuje historii ref (która zawiera gałęzie). Przechowuje tylko aktualną pozycję dla każdej gałęzi (głowy). Oznacza to, że z czasem możesz stracić trochę historii gałęzi w git. Ilekroć na przykład rozgałęziasz się, od razu tracisz, która gałąź była oryginalna. Wszystko co robi gałąź to:

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

You might Załóżmy, że pierwszym z nich jest gałąź. Wydaje się, że tak jest, ale nie zawsze tak jest. Nic nie stoi na przeszkodzie, aby przejść do którejś z gałęzi najpierw po powyższej operacji. Dodatkowo, znaczniki czasu git nie gwarantują niezawodności. Dopiero wtedy, gdy zobowiązujesz się do obu, staną się one strukturalnie gałęziami.

Podczas gdy na diagramach mamy tendencję do koncepcyjnego numerowania commitów, git nie ma prawdziwej stabilnej koncepcji sekwencji, gdy drzewo commitów rozgałęzia się. W tym przypadku można założyć, że liczby (wskazujące kolejność) są określone przez znacznik czasu (może być fajnie zobaczyć, jak interfejs Gita obsługuje rzeczy, gdy ustawisz wszystkie znaczniki czasu na to samo).

Tego człowiek oczekuje pojęciowo:

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

To właśnie dostajesz:

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

Można by założyć, że B1 jest oryginalną gałęzią, ale może być po prostu martwą gałęzią(ktoś zrobił checkout-b, ale nigdy się do niej nie zobowiązał). Nie jest to, dopóki nie zobowiązują się do obu, że masz uzasadnione struktura gałęzi w git:

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

Zawsze wiesz, że C1 było przed C2 i C3, ale nigdy nie wiadomo, czy C2 było przed C3 lub C3 było przed C2 (ponieważ możesz ustawić czas na stacji roboczej na cokolwiek na przykład). B1 i B2 są również mylące, ponieważ nie możesz wiedzieć, która gałąź była pierwsza. W wielu przypadkach można zrobić bardzo dobre i zazwyczaj dokładne odgadnięcie. Jest trochę jak tor wyścigowy. Wszystkie rzeczy na ogół są równe z samochodami, wtedy można założyć, że samochód to przychodzi w okrążeniu z tyłu zaczął okrążenie z tyłu. Mamy również konwencje, które są bardzo wiarygodne, na przykład master będzie prawie zawsze reprezentować najdłużej żyjące gałęzie, chociaż niestety widziałem przypadki, w których nawet tak nie jest.

Podany tutaj przykład jest przykładem 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 jest również mylące, ponieważ my jako ludzie czytamy je od lewej do prawej, od korzeni do liści (ref). Git tego nie robi. Gdzie robimy (A- > B) w naszych głowach git robi (A A). Czyta od ref do roota. Refy mogą być wszędzie, ale wydają się być liśćmi, przynajmniej dla aktywnych gałęzi. Ref wskazuje na commit, a commity zawierają tylko podobne do ich rodziców, a nie do ich dzieci. Gdy commit jest commitem scalającym, będzie miał więcej niż jeden rodzic. Pierwszym rodzicem jest zawsze oryginalny commit, do którego został scalony. Pozostałe rodzice są zawsze commitami, które zostały połączone z oryginalnym commitem.

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 zbyt skuteczna reprezentacja, raczej wyrażenie wszystkich ścieżek, które git może przyjąć z każdego ref (B1 i B2).

Wewnętrzna pamięć Git wygląda bardziej tak (nie, że a jako rodzic pojawia się dwa razy):

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

Jeśli zrzucisz surowy commit git, zobaczysz zero lub więcej pól nadrzędnych. Jeśli jest zero, oznacza to, że nie ma rodzica, a commit jest rootem(możesz mieć wiele korzeni). Jeśli istnieje, oznacza to, że nie było scalenia i nie jest to root commit. Jeśli jest więcej niż jeden oznacza to, że commit jest wynikiem a merge i wszystkie rodzice po pierwszym są merge commits.

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 obaj uderzą w ich łańcuch będzie taki sam, wcześniej ich łańcuch będzie zupełnie inny. Pierwszy commit ma dwa commity wspólne i jest wspólnym przodkiem, skąd się rozeszły. może być tu trochę zamieszania między terminami commit, branch i ref. W rzeczywistości można scalić commit. To właśnie robi merge. Ref po prostu wskazuje na commit, a gałąź jest niczym innym jak ref w folderze .Git/refs / heads, lokalizacja folderu określa, że ref jest gałęzią, a nie czymś innym, np. tagiem.

Gdy tracisz historię, to scalanie zrobi jedną z dwóch rzeczy w zależności od okoliczności.

Rozważmy:

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

W tym przypadku merge w obu kierunkach utworzy nowy commit z pierwszym rodzicem jako commitem wskazywanym przez bieżącą sprawdzoną gałąź, a drugim rodzicem jako commitem na końcu gałęzi, do której / align = "left" / Musi utworzyć nowy commit, ponieważ obie gałęzie mają zmiany od czasu ich wspólnego przodka, które muszą być połączone.

      / - 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 połączysz zmiany z B1 do B2 tak, aby były zsynchronizowane, możesz spodziewać się czegoś, co wygląda tak (możesz wymusić git merge, aby zrobił to tak jednak z --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 commity. Tak długo, jak nie ma zmian w B2, że B1 nie ma, dwie gałęzie zostaną połączone. Robi szybkie przewijanie do przodu, które jest jak rebase (rebase również eat lub historia linearyzacji), z tym wyjątkiem, że w przeciwieństwie do rebase, ponieważ tylko jedna gałąź ma zestaw zmian, nie musi stosować zestawu zmian z jednej gałęzi na górze, że z innej.

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

Jeśli przestaniesz pracować nad B1, to wszystko będzie w porządku dla zachowania historia na dłuższą metę. Tylko B1 (który może być mistrzem) awansuje typowo, więc lokalizacja B2 w historii B2 z powodzeniem reprezentuje punkt, który został scalony w B1. To jest to, czego oczekuje od Ciebie git, aby rozgałęzić B z A, wtedy możesz scalić A do B tyle, ile chcesz w miarę gromadzenia się zmian, jednak podczas scalania B z powrotem do A, nie oczekuje się, że będziesz pracował nad B i dalej. Jeśli kontynuujesz pracę nad swoją gałęzią po szybkim połączeniu jej z powrotem do gałęzi, którą byłeś praca nad usunięciem poprzedniej historii B za każdym razem. Naprawdę tworzysz nową gałąź za każdym razem po szybkim zatwierdzeniu do źródła, a następnie zatwierdzeniu do gałęzi. Kończy się to, gdy przewijasz commit jest dużo gałęzi / mergów, które możesz zobaczyć w historii i strukturze, ale bez możliwości określenia, jaka była nazwa tej gałęzi lub czy to, co wygląda jak dwie oddzielne gałęzie, jest naprawdę tą samą gałęzią.

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

1 do 3 i 5 do 8 są gałęziami strukturalnymi, które wykazują w górę, jeśli prześlesz historię dla 4 lub 9. Nie ma sposobu, aby w git dowiedzieć się, która z tych nienazwanych i niefermentowanych gałęzi strukturalnych należy do gałęzi nazwanych i referencji jako końca struktury. Możesz założyć z tego rysunku, że 0 do 4 należy do B1 i 4 do 9 należy do B2, ale oprócz 4 i 9 nie było wiadomo, która gałąź należy do której gałęzi, po prostu narysowałem to w sposób, który daje złudzenie, że. 0 może należeć do B2, a 5 może należeć do B1. Tam 16 różnych możliwości w tym przypadku, do których nazwanej gałęzi może należeć każda z gałęzi strukturalnych. Zakłada się, że żadna z tych strukturalnych gałęzi nie pochodzi z usuniętej gałęzi lub w wyniku połączenia gałęzi w siebie podczas pobierania z master(ta sama nazwa gałęzi na dwóch repozytoriach to w rzeczywistości dwie gałęzie, oddzielne repozytorium jest jak rozgałęzianie wszystkich gałęzi).

Istnieje wiele strategii Gita, które działają wokół tego problemu. Możesz wymusić git merge, aby nigdy nie był szybki do przodu i zawsze utworzyć gałąź scalania. Okropnym sposobem na zachowanie historii gałęzi jest używanie tagów i / lub gałęzi (znaczniki są naprawdę zalecane) zgodnie z wybraną przez Ciebie konwencją. Naprawdę nie polecam pustego commita w gałęzi, do której się łączysz. Bardzo powszechną konwencją jest nie scalanie się w gałąź integracyjną, dopóki nie chcesz naprawdę zamknąć swojej gałęzi. Jest to praktyka, którą ludzie powinni próbować stosować, ponieważ w przeciwnym razie pracujesz wokół punktu posiadające gałęzie. Jednak w realnym świecie ideał nie zawsze jest praktyczny, Co oznacza, że robienie właściwych rzeczy nie jest realne w każdej sytuacji. Jeśli to, co robisz na gałęzi jest odizolowane, może działać, ale w przeciwnym razie możesz być w sytuacji, w której gdy wielu programistów pracuje nad jednym czymś, muszą szybko udostępnić swoje zmiany (najlepiej możesz naprawdę chcieć pracować nad jedną gałęzią, ale nie wszystkie sytuacje pasują do tego, że albo i ogólnie dwie osoby pracujące nad gałęzią są czymś chcesz uniknąć).

 2
Author: jgmjgm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-09 16:15:55

Problem polega na znalezieniu najnowszego, pojedynczego commita przeciętego pomiędzy obiema gałęziami po jednej stronie, a najwcześniejszego wspólnego przodka po drugiej stronie (prawdopodobnie początkowego commita repo). To pasuje do mojej intuicji, co to jest" rozgałęzienie " punkt.

Mając to na uwadze, nie jest to wcale łatwe do obliczenia przy użyciu zwykłych komend git shell, ponieważ git rev-list - nasze najpotężniejsze narzędzie-nie pozwala nam ograniczaćścieżki, po której zostanie osiągnięty commit. Na najbliższy jest git rev-list --boundary, który może dać nam zbiór wszystkich commitów, które "zablokowały nam drogę". (Uwaga: git rev-list --ancestry-path jest interesujące, ale nie wiem, jak to zrobić użytecznym tutaj.)

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ć gist.

Zauważ, że większość innych rozwiązań zaproponowanych tutaj nie może działać we wszystkich sytuacjach z prostego powodu: git rev-list --first-parent nie jest wiarygodna w linearyzacji historii, ponieważ mogą być łączone z jednym lub dwoma zamówieniami.

git rev-list --topo-order, z drugiej strony, jest bardzo użyteczne -- dla kroczenia commitów w porządku topograficznym -- ale robienie diffów jest kruche: istnieje wiele możliwych porządków topograficznych dla danego grafu, więc jesteś zależny od pewnej stabilności porządków. To powiedziawszy, rozwiązanie strongk7 prawdopodobnie działa cholernie dobrze przez większość czasu. Jednak wolniej, że moja w wyniku konieczności chodzenia po całej historii repo... dwa razy. :-)

 0
Author: Andrew Bortz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:59

Poniższy implementuje git odpowiednik svn log --stop-on-copy i może być również użyty do odnalezienia pochodzenia gałęzi.

Podejście

  1. Zdobądź głowę dla wszystkich gałęzi
  2. Zbierz mergeBase dla docelowej gałęzi każda inna gałąź
  3. git.log and iterate
  4. Zatrzymaj przy pierwszym commicie, który pojawi się na liście mergeBase

Jak wszystkie rzeki biegną do morza, wszystkie gałęzie biegną do Pana i dlatego znajdujemy między pozornie niepowiązane gałęzie. Wracając od gałęzi przez przodków, możemy zatrzymać się na pierwszej potencjalnej bazie scalania, ponieważ teoretycznie powinna ona być punktem początkowym tej gałęzi.

Uwagi

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

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

 0
Author: Peter Kahn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:36

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ługowieczną gałąź: {]}

W tym samym czasie tworzę gałąź, tworzę również znacznik o tej samej nazwie, ale z przyrostkiem -init, Na przykład feature-branch i feature-branch-init.

(to trochę dziwne, że tak trudno odpowiedzieć na to pytanie!)

 0
Author: Stephen Darlington,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-31 13:14:22

Możesz użyć następującego polecenia, aby zwrócić najstarszy commit w branch_a, który nie jest osiągalny z master:

git rev-list branch_a ^master | tail -1

Być może z dodatkowym sprawdzeniem poczytalności, że rodzic tego commita jest w rzeczywistości osiągalny z master...

 -1
Author: Marc Richarme,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 13:05:32

Możesz sprawdzić reflog gałęzi a, aby dowiedzieć się, z którego commita został utworzony, jak również pełną historię, na które commity ta gałąź wskazywała. Reflogi są w .git/logs.

 -2
Author: Paggas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-06 23:42:46

Wydaje mi się, że znalazłem sposób, który radzi sobie ze wszystkimi wspomnianymi tutaj narożnikami:

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 kolejności przodków mają tylko ograniczoną wartość; na koniec dnia potrzebujesz jakiegoś zapisu "this commit came from branch X", ale taki rekord już istnieje; domyślnie 'Git merge' używałby Komunikatu o zatwierdzeniu, takiego jak "Merge branch' branch_A 'into master", to mówi ci, że wszystkie commity z drugiego rodzica (commit^2) zostały wysłane. z 'branch_A' i został scalony do pierwszego rodzica (commit^1), którym jest 'master'.

Uzbrojony w te informacje możesz znaleźć pierwsze scalenie 'branch_A' (czyli wtedy, gdy 'branch_A' naprawdę powstało), i znaleźć merge-base, która byłaby punktem gałęzi:)

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

Dla użyteczności:

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

Wtedy możesz zrobić " git branch-point branch_A".

Enjoy;)

 -2
Author: FelipeC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-27 11:50:16