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:]}
- dlaczego Git duplikuje te commity podczas rebasingu? Czy jest jakiś szczególny powód, aby to zrobić, zamiast po prostu stosować
C5
iC6
poC7
?
Jak mogę tego uniknąć? Czy byłoby rozsądnie to zrobić?
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
iC6
są chwilowo wyciągane zdev
-
C7
stosuje się dodev
-
C5
iC6
są odtwarzane z powrotem naC7
, 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.
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 zmaster
, zamiast uruchomićgit rebase master dev
, Uruchomgit merge master
podczasdev
(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 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
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
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:
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:
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
.
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
Lub pokazane w inny sposób:
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
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.
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
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