Jak w przeszłości wstrzyknąć zatwierdzenie między jakieś dwa arbitralne zatwierdzenia?

128

Załóżmy, że mam następującą historię zatwierdzeń w mojej gałęzi tylko lokalnej:

A -- B -- C

Jak wstawić nowe zatwierdzenie między Aa B?

BartoszKP
źródło
3
zatwierdzenie o nazwie Ą ? :)
Antek
Mam bardzo podobne pytanie, jak wstawić nową wersję w przeszłości zamiast zatwierdzenia.
Pavel P

Odpowiedzi:

178

To jeszcze łatwiejsze niż w odpowiedzi OP.

  1. git rebase -i <any earlier commit>. Spowoduje to wyświetlenie listy zatwierdzeń w skonfigurowanym edytorze tekstu.
  2. Znajdź zatwierdzenie, po którym chcesz wstawić (załóżmy, że jest a1b2c3d). W edytorze dla tego wiersza zmień pickna edit.
  3. Rozpocznij rebase, zamykając edytor tekstu (zapisz zmiany). To pozostawia cię w wierszu polecenia z zatwierdzeniem, które wybrałeś wcześniej ( a1b2c3d), tak jakby zostało właśnie zatwierdzone .
  4. Wprowadź zmiany i git commit( NIE zmieniaj, w przeciwieństwie do większości edit). Tworzy to nowe zatwierdzenie po tym, które wybrałeś.
  5. git rebase --continue. Powoduje to odtworzenie kolejnych zatwierdzeń, pozostawiając nowe zatwierdzenie wstawione we właściwym miejscu.

Uważaj, bo to odmieni historię i złamie każdego, kto spróbuje pociągnąć.

SLaks
źródło
1
Dodało to nowe zatwierdzenie po zatwierdzeniu, czyli po tym, do którego ponownie bazowałem (także ostatnim zatwierdzeniu), zamiast zaraz po tym, na którym ponownie bazowałem. Rezultat był taki sam, jak gdybym po prostu dokonał nowego zatwierdzenia na końcu ze zmianami, które chciałem wstawić. Moja historia stała się A -- B -- C -- Dpożądana A -- D -- B -- C.
XedinUnknown
2
@XedinUnknown: Wtedy nie użyłeś poprawnie Rebase.
SLaks
3
Teraz Dmoże być zatwierdzeniem wszędzie. Załóżmy, że mamy A - B - Ci mamy pewne zmiany, Dktórych nie ma nawet w tej gałęzi. Znamy jego SHA, jednak możemy to zrobić git rebase -i HEAD~3. Teraz między wierszami Ai B pickwstawiamy nową pick linię, która mówi pick SHA, podając skrót żądanego D. Nie musi to być pełny hash, tylko skrócony. git rebase -ipo prostu wiśnia wybiera wszystkie zatwierdzenia wymienione w pickwierszach w buforze; nie muszą to być oryginalne, wymienione dla Ciebie.
Kaz
1
@Kaz To wygląda na inną, prawidłową odpowiedź.
BartoszKP
3
Jeszcze łatwiej, możesz użyć breaksłowa kluczowego w edytorze w osobnym wierszu między dwoma zatwierdzeniami (lub w pierwszym wierszu, aby wstawić zatwierdzenie przed określonym zatwierdzeniem).
SimonT
30

Okazuje się być całkiem proste, odpowiedź znaleźć tutaj . Załóżmy, że jesteś na gałęzi branch. Wykonaj następujące kroki:

  • utwórz tymczasową gałąź z zatwierdzenia po tym, jak chcesz wstawić nowy zatwierdzenie (w tym przypadku zatwierdzenie A):

    git checkout -b temp A
    
  • wprowadź zmiany i zatwierdź je, tworząc zatwierdzenie, nazwijmy to N:

    git commit -a -m "Message"
    

    (lub git addnastępuje git commit)

  • przebuduj zatwierdzenia, które chcesz mieć po nowym zatwierdzeniu (w tym przypadku zatwierdzeniach Bi C) na nowe zatwierdzenie:

    git rebase temp branch
    

(być może trzeba użyć -p, aby zachować scala, gdyby nie było w ogóle - dzięki a już istniejącym komentarzu przez Ciekawy )

  • usuń tymczasową gałąź:

    git branch -d temp
    

Po tym historia wygląda następująco:

A -- N -- B -- C

Jest oczywiście możliwe, że podczas przebudowy pojawią się pewne konflikty.

Jeśli twoja gałąź nie jest lokalna - wprowadzi to przepisywanie historii, więc może spowodować poważne problemy.

BartoszKP
źródło
2
Nie mogłem zastosować się do zaakceptowanej przez SLaków odpowiedzi, ale to zadziałało. Po uzyskaniu żądanej historii git push --forcezmian musiałem zmienić zdalne repozytorium.
znak ucieczki
1
Podczas korzystania z rebase z opcją -Xtheirs automatycznie rozwiązuje konflikty, więc git rebase temp branch -Xtheirs. Pomocna odpowiedź na wstrzyknięcie w skrypcie!
David C
Dla noobów takich jak ja chciałbym to dodać później git rebase temp branch, ale wcześniej git branch -d tempwszystko, co musisz zrobić, to naprawić i scalić konflikty i problemy git rebase --continue, tj. Nie trzeba niczego zatwierdzać itp.
Pugsley
19

Jeszcze łatwiejsze rozwiązanie:

  1. Utwórz nowe zatwierdzenie na końcu, D. Teraz masz:

    A -- B -- C -- D
    
  2. Następnie uruchomić:

    $ git rebase -i hash-of-A
    
  3. Git otworzy twój edytor i będzie wyglądał tak:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Po prostu przenieś D na górę w ten sposób, a następnie zapisz i zakończ

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Teraz będziesz mieć:

    A -- D -- B -- C
    
Mateusz
źródło
6
Niezły pomysł, jednak wprowadzenie D na C może być trudne, jeśli zamierzasz wprowadzić te zmiany. do A.
BartoszKP
Mam sytuację, w której mam 3 zmiany, które chcę razem zmienić, i zatwierdzenie w środku, które jest niezwiązane. To bardzo fajne, że można po prostu przesunąć to zatwierdzenie wcześniej lub później w dół wiersza zatwierdzeń.
unflores
13

Zakładając, że historia zatwierdzeń to preA -- A -- B -- C, jeśli chcesz wstawić zatwierdzenie między Ai B, kroki są następujące:

  1. git rebase -i hash-of-preA

  2. Git otworzy twój edytor. Treść może wyglądać następująco:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Zmień pierwszą pickna edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Zapisz i wyjdź.

  3. Zmodyfikuj swój kod, a następnie git add . && git commit -m "I"

  4. git rebase --continue

Teraz twoja historia zatwierdzania Git to preA -- A -- I -- B -- C


Jeśli napotkasz konflikt, Git zatrzyma się na tym zatwierdzeniu. Możesz użyć git diffdo zlokalizowania znaczników konfliktów i ich rozwiązania. Po rozwiązaniu wszystkich konfliktów musisz użyć, git add <filename>aby powiedzieć Gitowi, że konflikt został rozwiązany, a następnie ponownie uruchomić git rebase --continue.

Jeśli chcesz cofnąć zmianę bazy, użyj git rebase --abort.

haolee
źródło
11

Oto strategia, która pozwala uniknąć robienia „hackowania edycji” podczas rebase, co jest widoczne w innych odpowiedziach, które przeczytałem.

Używając git rebase -i, otrzymujesz listę zatwierdzeń od tego zatwierdzenia. Po prostu dodaj „przerwę” na początku pliku, co spowoduje, że rebase przestanie działać w tym momencie.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Po uruchomieniu git rebasezatrzyma się teraz w miejscu „przerwy”. Możesz teraz edytować swoje pliki i normalnie tworzyć zmiany. Następnie możesz kontynuować rebase za pomocą git rebase --continue. Może to powodować konflikty, które będziesz musiał naprawić. Jeśli się zgubisz, nie zapomnij, że zawsze możesz przerwać używanie git rebase --abort.

Tę strategię można uogólnić, aby wstawić zatwierdzenie w dowolnym miejscu, po prostu wstaw „przerwę” w miejscu, w którym chcesz wstawić zatwierdzenie.

Po przepisaniu historii nie zapomnij o tym git push -f. Obowiązują zwykłe ostrzeżenia, że ​​inne osoby pobierają Twój oddział.

axerologementy
źródło
Przepraszamy, ale mam problem ze zrozumieniem, jak to się ma do „unikania ponownej bazy”. Ci wyświetlane rebasetutaj. Nie ma dużej różnicy, czy utworzysz zatwierdzenie podczas rebase, czy wcześniej.
BartoszKP
Ups, miałem na myśli unikanie "hackowania edycji" podczas rebase, myślę, że źle to sformułowałem.
axerologementy
Dobrze. Moja odpowiedź również nie korzysta z funkcji „edytuj” w rebase. Jest to jednak kolejne ważne podejście - dzięki! :-)
BartoszKP
To zdecydowanie najlepsze rozwiązanie!
Theodore R. Smith
6

Wiele dobrych odpowiedzi już tutaj. Chciałem tylko dodać rozwiązanie „bez rebase” w 4 prostych krokach.


Podsumowanie

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Wyjaśnienie

(Uwaga: jedną z zalet tego rozwiązania jest to, że nie dotykasz swojej gałęzi aż do ostatniego kroku, kiedy jesteś w 100% pewien, że jesteś w porządku z końcowym wynikiem, więc masz bardzo przydatny krok „wstępnego potwierdzenia” pozwalające na testowanie AB .)


Stan początkowy (założyłem masternazwę twojego oddziału)

A -- B -- C <<< master <<< HEAD

1) Zacznij od wskazania HEAD we właściwym miejscu

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Opcjonalnie tutaj, zamiast odłączać HEAD, moglibyśmy utworzyć tymczasową gałąź, z git checkout -b temp Aktórą musielibyśmy usunąć na końcu procesu. Oba warianty działają, rób tak, jak wolisz, ponieważ wszystko inne pozostaje takie samo)


2) Utwórz nowe zatwierdzenie D do wstawienia

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Następnie przynieś kopie ostatnich brakujących zatwierdzeń B i C (byłaby ta sama linia, gdyby było więcej zatwierdzeń)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(w razie potrzeby wygodne badanie AB)

Nadszedł czas, aby sprawdzić swój kod, przetestować wszystko, co powinno zostać przetestowane, a także porównać / porównać / sprawdzić, co masz i co otrzymasz po operacjach.


4) W zależności od wyników testów pomiędzy Ci C', albo jest OK, albo jest KO.

(ALBO) 4-OK) Na koniec przesuń ref zmaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(LUB) 4-KO) Po prostu zostaw masterbez zmian

Jeśli utworzyłeś tymczasową gałąź, po prostu usuń ją za pomocą git branch -d <name>, ale jeśli wybrałeś odłączoną trasę HEAD, w tym momencie nie jest wymagana żadna akcja, nowe zatwierdzenia będą kwalifikować się do czyszczenia pamięci zaraz po ponownym podłączeniu HEADzgit checkout master

W obu tych przypadkach (OK lub KO), w tym momencie po prostu sprawdź masterponownie, aby ponownie podłączyć HEAD.

RomainValeri
źródło