Commity Git są duplikowane w tej samej gałęzi po wykonaniu rebase

Rozumiem scenariusz przedstawiony w Pro Git o ryzyku git rebase. Autor w zasadzie mówi, jak uniknąć powielonych commitów: {]}

Nie zmieniaj zmian wprowadzonych do publicznego repozytorium.

Opowiem ci o mojej konkretnej sytuacji, ponieważ myślę, że nie pasuje ona do scenariusza Pro Git i nadal kończę z duplikowanymi commitami.

Powiedzmy, że mam dwa odległe oddziały z lokalnym "odpowiedniki": {]}

origin/master    origin/dev
|                |
master           dev

Wszystkie cztery gałęzie zawierają te same commity i zamierzam rozpocząć rozwój w dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

Po kilku commitach wciskam zmiany do origin/dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

Muszę wrócić do master, aby szybko naprawić:

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6
[18]} i powrót do dev zmieniam zmiany, aby uwzględnić szybką poprawkę w moim rzeczywistym rozwoju:
origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

Jeśli wyświetlę historię commitów z GitX / gitk to zauważam, że origin/dev zawiera teraz dwa identyczne commity C5' i C6', które różnią się od Gita. Teraz jeśli wcisnę zmiany do origin/dev to jest to wynik:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

Może nie do końca rozumiem wyjaśnienie w Pro Git, więc chciałbym wiedzieć dwie rzeczy:]}

  1. dlaczego Git duplikuje te commity podczas rebasingu? Czy jest jakiś szczególny powód, aby to zrobić, zamiast po prostu stosować C5 i C6 po C7?
  2. Jak mogę tego uniknąć? Czy byłoby rozsądnie to zrobić?
Author: Whymarrh, 2012-02-13

4 answers

Nie powinieneś używać rebase tutaj, proste scalanie wystarczy. Książka Pro Git, którą podlinkowałeś, wyjaśnia dokładnie tę sytuację. Wewnętrzne działanie może być nieco inne, ale oto jak to wizualizuję:]}

  • C5 i C6 są chwilowo wyciągane z dev
  • C7 stosuje się do dev
  • C5 i C6 są odtwarzane z powrotem na C7, tworząc nowe diffy i tym samym nowe commity

Więc w Twojej dev gałęzi, C5 i C6 faktycznie już nie istnieją: są teraz C5' i C6'. Kiedy naciskasz origin/dev, git widzi C5' i {[12] } jako nowe commity i przypisuje je do końca historii. W rzeczy samej, jeśli spojrzysz na różnice pomiędzy C5 i C5' w origin/dev, zauważysz, że chociaż zawartość jest taka sama, numery linii są prawdopodobnie różne-co sprawia, że hash commita jest inny.

Powtórzę regułę Pro Git: nigdy nie zmieniaj zmian, które kiedykolwiek istniały anywhere but your local repozytorium . Zamiast tego użyj merge.

 61
Author: Justin ᚅᚔᚈᚄᚒᚔ,
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-02-13 17:03:38

Krótka odpowiedź

W tym momencie, gdy uruchomiłeś stronę, zauważyłeś następujący błąd, a następnie uruchomiłeś stronę: git pull:
To [email protected]:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Pomimo tego, że Git stara się być pomocny, jego porady "git pull" najprawdopodobniej nie są tym, co chcesz zrobić .

Jeśli jesteś:

  • praca na" feature branch "lub" developer branch " samodzielnie , możesz uruchomić git push --force, aby zaktualizować pilota za pomocą commitów post-rebase (zgodnie z user4405677 odpowiedź).
  • pracując nad gałęzią z wieloma deweloperami jednocześnie, to prawdopodobnie nie powinieneś używać git rebase Po pierwsze. Aby zaktualizować dev ze zmianami z master, zamiast uruchomić git rebase master dev, Uruchom git merge master podczas dev (zgodnie z odpowiedzią Justina ).

Nieco dłuższe Wyjaśnienie

Każdy hash commita w Git opiera się na kilku czynnikach, z których jednym jest hash commita, który pojawia się przed to.

Jeśli zmienisz kolejność zatwierdzeń, zmienisz skróty zatwierdzeń; rebasing (gdy coś zrobi) zmieni skróty zatwierdzeń. Dzięki temu, wynik uruchomienia git rebase master dev, gdzie dev nie jest zsynchronizowany z master, utworzy nowe commity (i tym samym skróty) o tej samej zawartości, co te na dev, ale z commitami na master wstawionymi przed nimi.

Możesz skończyć w takiej sytuacji na wiele sposobów. Two ways I can think of:
  • możesz mieć commity na master, na których chcesz oprzeć swoją pracę dev
  • możesz mieć commity na dev, które zostały już wysłane do pilota, który następnie należy zmienić (przeformułować komunikaty commitów, zmienić kolejność commitów, squash commity, itp.)

Lepiej zrozumieć, co się stało - oto przykład:

Masz repozytorium:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

Początkowy zestaw liniowych commitów w repozytorium

Następnie przechodzisz do zmiany commitów.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(tutaj musisz mi uwierzyć na słowo dla niego: istnieje wiele sposobów na zmianę commitów w Git. W tym przykładzie zmieniłem czas C3, ale wstawiasz nowe commity, zmieniasz komunikaty commitów, zmieniasz kolejność commitów, zgniatasz commity razem,itp.)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

Te same commity z nowymi hashami

Tutaj należy zauważyć, że skróty commitów są różne. Jest to oczekiwane zachowanie, ponieważ zmieniłeś coś (cokolwiek) w nich. To jest ok, ale:

Dziennik wykresu pokazujący, że master nie jest zsynchronizowany z pilotem

Próbuje wcisnąć will pokazuje błąd (i podpowiedź, że należy uruchomić git pull).

$ git push origin master
To [email protected]:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Jeśli uruchomimy git pull, widzimy ten log:

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Lub pokazane w inny sposób:

Dziennik wykresu pokazujący commit scalający

I teraz mamy duplikaty commitów lokalnie. Gdybyśmy mieli uruchomić git push, wysłalibyśmy je na serwer.

Aby uniknąć dostania się do tego etapu, mogliśmy pobiegać git push --force (gdzie zamiast pobiegliśmy git pull). To wysłałoby nasze commity z nowymi hashami na serwer bez problemu. Aby rozwiązać problem na na tym etapie możemy zresetować się przed uruchomieniem git pull:

Spójrz na reflog (git reflog) aby zobaczyć jaki był hash commit zanim uruchomiliśmy git pull.

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

Powyżej widzimy, że ba7688a to commit, w którym byliśmy przed uruchomieniem git pull. Z tym Hashem commita w ręku możemy zresetować do niego (git reset --hard ba7688a) i uruchomić git push --force.

I skończyliśmy.

Ale czekaj, kontynuowałem pracę bazową na zduplikowanych commitach

Jeśli jakoś nie zauważyłeś, że commity zostały zduplikowane i kontynuowały pracę na szczycie duplikatów commitów, naprawdę narobiłeś sobie bałaganu. Rozmiar bałaganu jest proporcjonalny do liczby commitów, które masz na szczycie duplikatów.

Jak to wygląda:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Git log pokazujący liniowe commity na szczycie zduplikowanych commitów

Lub pokazane w inny sposób:

Wykres dziennika pokazujący liniowe commity na duplikowanych commitach

W tym scenariuszu chcemy usunąć zduplikowane commity, ale zachować commity, które na nich bazujemy-chcemy zachować C6 do C10. Jako z większości rzeczy, istnieje wiele sposobów, aby to zrobić:

Albo:

  • Utwórz nową gałąź przy ostatnim zduplikowanym zatwierdzeniu1, cherry-pick każdy commit (od C6 do C10 włącznie) na nową gałąź i traktować tę nową gałąź jako kanoniczną.
  • Uruchom git rebase --interactive $commit, gdzie $commit jest zatwierdzeniem przed do obu zduplikowanych zatwierdzeń2. Tutaj możemy wprost usunąć linie do duplikaty.

1 nie ma znaczenia, który z tych dwóch wybrać, albo ba7688a lub 2a2e220 działa dobrze.

2 w przykładzie będzie to 85f59ab.

TL;DR

Zestaw advice.pushNonFastForward do false:

git config --global advice.pushNonFastForward false
 63
Author: Whymarrh,
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-12-18 15:23:13

Myślę, że pominąłeś ważny szczegół, opisując swoje kroki. Dokładniej mówiąc, twój ostatni krok, git push w dev, rzeczywiście dałby ci błąd, ponieważ nie możesz normalnie wprowadzać zmian nieszybkich.

Więc zrobiłeś git pull przed ostatnim naciśnięciem, co spowodowało commit scalający z c6 i C6' jako rodzicami, dlatego oba pozostaną wymienione w dzienniku. Ładniejszy format dziennika mógł sprawić, że stało się bardziej oczywiste, że są to połączone gałęzie zduplikowanych commitów.

Lub ty zamiast tego wykonałem git pull --rebase (lub bez jawnego --rebase, jeśli jest to sugerowane przez twój config), który ściągnął oryginalne C5 I C6 z powrotem do lokalnego dev (i dalej ponownie przerobił poniższe do nowych hashów, C7' C5 "C6").

Jednym ze sposobów wyjścia z tego mogło być git push -f wymusić naciśnięcie, gdy podało błąd i wytrzeć C5 C6 z origin, ale gdyby ktoś inny również je wyciągnął, zanim je wyczyścisz, będziesz miał o wiele więcej kłopotów... w zasadzie każdy kto ma C5 C6 musiałby zrobić specjalne kroki, aby się ich pozbyć. I właśnie dlatego mówią, że nigdy nie powinieneś rebase niczego, co już zostało opublikowane. Jest to nadal wykonalne, jeśli powiedziane "publikowanie" jest w małym zespole.

 9
Author: user4405677,
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-08 03:23:10

Dowiedziałem się, że w moim przypadku problem ten jest konsekwencją problemu z konfiguracją Gita. (Włączając pull and merge)

Opis problemu:

Symphoms: commity zduplikowane na gałęzi potomnej po rebase, sugerując liczne scalenia podczas i po rebase.

Workflow: Oto etapy przepływu pracy, który wykonywałem:

  • prace na " funkcje-oddział "(Filia "rozwój-oddział")
  • Commit i Push zmian na "Features-branch"
  • Sprawdź "Develop-branch" (gałąź macierzysta funkcji) i pracuj z nią.
  • Zatwierdź i wypchnij zmiany na "rozwijaj-gałąź"
  • W tym celu należy pobrać zmiany z repozytorium (na wypadek, gdyby ktoś inny zlecił pracę).]} W ten sposób można uzyskać więcej informacji na ten temat.]}
  • Siła pchania zmian na "Feature-branch"

Jako następstwa tego przepływu pracy, powielanie wszystkich commitów "Feature-branch" od poprzedniego rebase... :-(

Problem wynikał z ciągnięcia zmian gałęzi potomnej przed rebase. domyślna konfiguracja pull Git to "merge". Jest to zmiana indeksów commitów wykonywanych w gałęzi potomnej.

Rozwiązanie: w pliku konfiguracyjnym Git skonfiguruj pull do pracy w trybie rebase:

...
[pull]
    rebase = preserve
...

Mam nadzieję, że to pomoże JN Grx

 1
Author: JN Gerbaux,
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-05-26 13:14:24