Pytanie Przenieś najnowsze zatwierdzenia do nowej gałęzi za pomocą Git


Chciałbym przenieść ostatnie zatwierdzone przeze mnie zobowiązanie do opanowania nowego oddziału i zabrać mistrza z powrotem przed tymi zobowiązaniami. Niestety, mój Git-fu nie jest jeszcze wystarczająco silny, jakakolwiek pomoc?

To znaczy. Jak mogę od tego odejść?

master A - B - C - D - E

do tego?

newbranch     C - D - E
             /
master A - B 

3762
2017-10-27 03:07


pochodzenie


Uwaga: Zadałem przeciwne pytanie tutaj - Benjol
możliwy duplikat Przenieś zatwierdzenia z mastera na gałąź przy użyciu git - Chris Moschini
eddmann.com/posts/... ten działa - Sagar Naliyapara
Czy komentarze zostały tutaj usunięte? Pytam, ponieważ podczas mojej dwumiesięcznej wizyty na to pytanie zawsze przewijam ten komentarz. - Tejas Kale


Odpowiedzi:


Przeprowadzka do nowego oddziału

OSTRZEŻENIE: Ta metoda działa, ponieważ tworzysz nową gałąź za pomocą pierwszego polecenia: git branch newbranch. Jeśli chcesz przenieść commit na istniejący oddział musisz scalić zmiany w istniejącej gałęzi przed wykonaniem git reset --hard HEAD~3 (widzieć Przeprowadzka do istniejącej gałęzi poniżej). Jeśli nie dokonasz scalenia zmian w pierwszej kolejności, zostaną one utracone.

O ile nie ma innych okoliczności, można to łatwo zrobić poprzez rozgałęzienie i wycofanie.

# Note: Any changes not committed will be lost.
git branch newbranch      # Create a new branch, saving the desired commits
git reset --hard HEAD~3   # Move master back by 3 commits (GONE from master)
git checkout newbranch    # Go to the new branch that still has the desired commits

Ale upewnij się, ile razy wrócisz. Zamiast tego możesz zamiast HEAD~3, po prostu podaj wartość skrótu commit (lub odniesienie podobne) pochodzenie / kapitan) chcesz "powrócić do" na mistrz (/ current) branch, np .:

git reset --hard a1b2c3d4

* 1 Będziesz tylko "przegrywaj" zatwierdzenia z głównego oddziału, ale nie martw się, będziesz mieć te poprawki w nowej gałęzi!

OSTRZEŻENIE: Z Git w wersji 2.0 i późniejszych, jeśli później git rebase nowa gałąź na oryginale (master), możesz potrzebować wyraźnego --no-fork-point opcja w trakcie reorganizacji, aby uniknąć utraty przeniesionych zatwierdzeń. Mający branch.autosetuprebase always set czyni to bardziej prawdopodobnym. Widzieć Odpowiedź Johna Mellora dla szczegółów.

Przeprowadzka do istniejącej gałęzi

Jeśli chcesz przenieść swoje zatwierdzenia do istniejący oddział, będzie wyglądać tak:

git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch

4795
2017-07-22 22:37



I w szczególności, nie rób tego postaraj się cofnąć dalej niż punkt, w którym ostatnio pchnąłeś commit do innego repozytorium, z którego ktoś inny mógł wyciągnąć. - Greg Hewgill
Zastanawiasz się, czy możesz wyjaśnić DLACZEGO to działa. Dla mnie tworzysz nowy oddział, usuwając 3 zatwierdzenia ze starej gałęzi, w której nadal jesteś, a następnie sprawdzasz utworzoną gałąź. Jak więc magiczne poprawki, które usunąłeś, pojawiają się w nowym oddziale? - Jonathan Dumaine
@ Jonathan Dumaine: Ponieważ stworzyłem nowy oddział przed usunięciem zatwierdzeń ze starego oddziału. Wciąż są w nowym oddziale. - sykora
gałęzie w git to tylko markery wskazujące na popełnione w historii, nic nie jest klonowane, tworzone ani usuwane (oprócz znaczników) - knittl
Uwaga: nie rób tego z niezatwierdzonymi zmianami w kopii roboczej! To mnie ugryzło! :( - Adam Tuttle


Dla tych, którzy zastanawiają się, dlaczego to działa (jak na początku):

Chcesz wrócić do C i przenieść D i E do nowego oddziału. Oto, jak wygląda na początku:

A-B-C-D-E (HEAD)
        ↑
      master

Po git branch newBranch:

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

Po git reset --hard HEAD~2:

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

Ponieważ gałąź to tylko wskaźnik, mistrz wskazał na ostatnie zatwierdzenie. Kiedy zrobiłeś Nowa gałąź, po prostu stworzyłeś nowy wskaźnik do ostatniego zatwierdzenia. Następnie za pomocą git reset przesunąłeś mistrz wskaźnik z powrotem dwa zatwierdzenia. Ale ponieważ się nie ruszałeś Nowa gałąź, nadal wskazuje na zatwierdzenie, które pierwotnie zrobił.


847
2018-02-07 16:58



Potrzebujesz - mocno, bo w przeciwnym razie git zostawia zmiany z resetowania w katalogu roboczym (a przynajmniej dla mnie). - Andrew
Musiałem również zrobić git push origin master --force aby zmiana pojawiła się w głównym repozytorium. - Dženan
Ta odpowiedź powoduje utratę commitów: następnym razem git rebase, 3 zatwierdzenia zostaną po cichu odrzucone newbranch. Widzieć moja odpowiedź po szczegóły i bezpieczniejsze alternatywy. - John Mellor
@John, to nonsens. Rebasing bez wiedzy o tym, co robisz, powoduje utratę zobowiązań. Jeśli straciłeś zobowiązania, przepraszam za ciebie, ale ta odpowiedź nie straciła twoich zobowiązań. Zauważ, że origin/master nie pojawia się na powyższym schemacie. Jeśli popchnąłeś origin/master a potem dokonałem powyższych zmian, jasne, wszystko pójdzie dziwnie. Ale to jest "Doktor, to boli, gdy robię to" rodzaj problemu. Jest poza tym, co pierwotne pytanie zadawane. Proponuję napisać własne pytanie, aby zbadać swój scenariusz zamiast go porwać. - Ryan Lundy
@John, w twojej odpowiedzi powiedziałeś "Nie rób tego! git branch -t newbranchWróć i ponownie przeczytaj odpowiedzi. Nikt zasugerowałem to. - Ryan Lundy


Ogólnie...

Metoda przedstawiona przez sykora jest najlepszą opcją w tym przypadku. Ale czasami nie jest najłatwiejszy i nie jest to metoda ogólna. Ogólne zastosowanie metody git cherry-pick:

Aby osiągnąć to, co OP chce, jest to proces dwuetapowy:

Krok 1 - Uwaga, która zatwierdza od mistrza na newbranch

Wykonać

git checkout master
git log

Zwróć uwagę na skróty (powiedzmy 3), które chcesz włączyć newbranch. Tutaj użyję:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3 

Uwaga: Możesz użyć pierwszych siedmiu znaków lub   całe hash zatwierdzenia

Krok 2 - Połóż je na newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

LUB (w Git 1.7.2+, zakresy użycia)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

git cherry-pick stosuje te trzy poprawki do nowej gałęzi.


343
2018-03-26 08:13



Czy OP nie próbował się ruszyć? od mistrz do Nowa gałąź? Jeśli wybierzesz cherry na master, dodasz do master branch - dodając już zatwierdzenia, które już miałeś. I nie cofa się ani głowy gałęzi do B. Czy jest coś subtelnego i fajnego, czego nie dostaję? - RaveTheTadpole
Działa to bardzo dobrze, jeśli przypadkowo popełniłeś zły, nie-główny oddział, kiedy powinieneś utworzyć nową gałąź funkcji. - julianc
+1 za przydatne podejście w niektórych sytuacjach. Jest to dobre, jeśli chcesz tylko przenieść swoje własne commity (które przeplatają się z innymi) do nowej gałęzi. - Tyler V.
To jest lepsza odpowiedź. W ten sposób możesz przenieść zatwierdzenia do dowolnej gałęzi. - skywinder
Czy kolejność wybierania wiśni jest ważna? - kon psych


Jeszcze inny sposób, aby to zrobić, używając tylko 2 poleceń. Utrzymuje również twoje bieżące drzewo robocze.

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

Stara wersja - zanim się dowiedziałem git branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

Mogąc push do . to fajna sztuczka, którą trzeba znać.


252
2018-04-06 22:38



Aktualny katalog. Myślę, że działałoby to tylko, jeśli jesteś w górnym katalogu. - aragaer
Lokalne popychanie wywołuje uśmieszek, ale po refleksji, jak to jest inne git branch -f tutaj? - jthill
@GerardSexton . jest obecnym dyrektorem. git może naciskać na adresy URL REMOTES lub GIT. path to local directory jest obsługiwana przez składnię adresów Git. Zobacz sekcję GIT URLS w git help clone. - weakish
Nie wiem, dlaczego nie jest to wyższa ocena. Martwe proste i bez małego, ale potencjalnego niebezpieczeństwa resetowania gita - trudne. - Godsmith
@Godsmith Przypuszczam, że ludzie wolą trzy proste polecenia od dwóch nieco bardziej niejasnych poleceń. Ponadto, odpowiedzi z najważniejszymi głosami zyskują więcej upvotes ze względu na to, że są wyświetlane jako pierwsze. - JS_Riddler


Większość poprzednich odpowiedzi jest niebezpiecznie błędna!

Nie rób tego:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

Gdy następnym razem uciekniesz git rebase (lub git pull --rebase) te 3 zobowiązania zostaną po cichu odrzucone newbranch! (patrz wyjaśnienie poniżej)

Zamiast tego:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Najpierw odrzuca 3 ostatnie zatwierdzenia (--keep jest jak --hard, ale bezpieczniejsze, ponieważ zawiedzie, a nie wyrzuci niezatwierdzone zmiany).
  • Potem się rozwidla newbranch.
  • Następnie ponownie wybiera te 3 poprawki newbranch. Ponieważ nie są już przywoływane przez gałąź, robi to za pomocą git reflog: HEAD@{2} to jest zobowiązanie HEAD odnosi się do 2 operacji wcześniej, tj. zanim 1 się wymeldowaliśmy newbranch i 2. używane git reset aby odrzucić 3 zatwierdzenia.

Ostrzeżenie: reflog jest domyślnie włączony, ale jeśli ręcznie go wyłączyłeś (np. Używając "gołego" repozytorium git), po uruchomieniu nie będzie można odzyskać 3 zatwierdzeń git reset --keep HEAD~3.

Alternatywą, która nie polega na reflogu jest:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(Jeśli wolisz, możesz pisać @{-1} - poprzednio wyrejestrowany oddział - zamiast oldbranch).


Objaśnienie techniczne

Dlaczego miałby git rebase odrzucić 3 poprawki po pierwszym przykładzie? To dlatego, że git rebase bez argumentów włącza --fork-point opcja domyślnie, która używa lokalnego reflog, aby spróbować być odpornym na skierowanie siły w stronę odgałęzionej gałęzi.

Załóżmy, że rozgałęziłeś pochodzenie / wzorzec, gdy zawierał on zatwierdzenia M1, M2, M3, a następnie sam wykonałeś trzy poprawki:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

ale potem ktoś przepisuje historię, przesuwając w kierunku początkowym / głównym, aby usunąć M2:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Korzystając z lokalnego rejestru, git rebase Widać, że rozwidliliście się z wcześniejszej inkarnacji gałęzi początkowej / głównej, a zatem, że zatwierdzenia M2 i M3 nie są częścią twojej gałęzi tematycznej. W związku z tym rozsądnie zakłada, że ​​ponieważ M2 został usunięty z gałęzi upstream, nie chcesz już go w swojej gałęzi tematów, albo po odradzeniu gałęzi tematu:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

Takie zachowanie ma sens i zwykle jest to właściwe postępowanie podczas ponownego tworzenia.

Tak więc, ponieważ następujące polecenia kończą się niepowodzeniem:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

ponieważ opuszczają reflog w złym stanie. Git widzi newbranch jako mający rozgałęzienie oddziału rozgałęzionego w wersji zawierającej 3 zatwierdzenia, a następnie reset --hard przepisuje historię wyższego szczebla, aby usunąć zatwierdzenia, a więc następnym razem git rebase odrzuca je tak, jak każde inne zatwierdzenie, które zostało usunięte z wyższego poziomu.

Ale w tym konkretnym przypadku chcemy, aby te trzy zobowiązania zostały uznane za część gałęzi tematycznej. Aby to osiągnąć, musimy odłączyć się od wcześniejszej wersji, która nie obejmuje 3 zatwierdzeń. Tak postąpiły moje sugerowane rozwiązania, dlatego obydwa opuszczają reflog we właściwym stanie.

Aby uzyskać więcej informacji, zobacz definicję --fork-point w git rebase i git merge-basedocs.


210
2017-10-19 14:12



Ta odpowiedź brzmi "NIE rób tego!" nad czymś, czego nikt nie zasugerował. - Ryan Lundy
@Kyralessa, the -t odnosisz się do git branch dzieje się niejawnie, jeśli masz git config --global branch.autosetuprebase always zestaw. Nawet jeśli nie, ja już wyjaśnione Tobie, że ten sam problem występuje, jeśli skonfigurujesz śledzenie po wykonaniu tych poleceń, ponieważ OP prawdopodobnie zamierza to zrobić, biorąc pod uwagę ich pytanie. - John Mellor
@RockLee, tak, ogólny sposób naprawienia takich sytuacji polega na utworzeniu nowego oddziału (newbranch2) z bezpiecznego punktu startowego, a następnie wybierz wszystkie zatwierdzenia, które chcesz zachować (od badnewbranch do newbranch2). Selekcja wiśni da commits nowe skróty, więc będziesz mógł bezpiecznie od nowa od nowa newbranch2 (i teraz możesz usunąć badnewbranch). - John Mellor
@Walf, źle Cię zrozumiałeś: git rebase został zaprojektowany tak, aby był odporny na zagrożenia w porównaniu z poprzednią historią. Niestety skutki uboczne tej odporności dotykają wszystkich, nawet jeśli ani oni, ani ich upstream nigdy nie przepisują historii. - John Mellor
Możesz także użyć --no-fork-point kiedy uciekasz git rebase. - torek


To nie "przenosi" ich w sensie technicznym, ale ma ten sam efekt:

A--B--C  (branch-foo)
 \    ^-- I wanted them here!
  \
   D--E--F--G  (branch-bar)
      ^--^--^-- Opps wrong branch!

While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)

A--B--C  (branch-foo)
 \
  \
   D-(E--F--G) detached
   ^-- (branch-bar)

Switch to branch-foo
$ git cherry-pick E..G

A--B--C--E'--F'--G' (branch-foo)
 \   E--F--G detached (This can be ignored)
  \ /
   D--H--I (branch-bar)

Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:

A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
 \
  \
   D--H--I--J--K--.... (branch-bar)

27
2018-01-21 16:10



Nie możesz tego użyć rebase na to samo? - Bergi
Tak, alternatywnie możesz użyć rebase w oderwanej oddziału w powyższym scenariuszu. - Sukima


Aby to zrobić bez przepisywania historii (to znaczy, jeśli już pchnąłeś zatwierdzenia):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

Obie gałęzie można następnie popychać bez użycia siły!


19
2017-09-20 10:17



Ale musisz poradzić sobie z odwróconym scenariuszem, który w zależności od okoliczności może być znacznie trudniejszy. Jeśli przywrócisz zatwierdzenie w oddziale, Git będzie nadal widzieć te zatwierdzenia, które miały miejsce, więc aby to cofnąć, musisz cofnąć przywrócenie. Spala to sporo osób, zwłaszcza gdy odwracają scalenie i próbują scalić gałąź z powrotem, ale okazuje się, że Git uważa, że ​​jest już połączona z tą gałęzią (co jest całkowicie prawdziwe). - Makoto
Właśnie dlatego wybieram na końcu zobowiązanie, na nowy oddział. W ten sposób git widzi je jako nowe zatwierdzenia, które rozwiązują twój problem. - teh_senaus
Jest to bardziej niebezpieczne, niż początkowo wydaje się, ponieważ zmieniasz stan historii repozytorium bez zrozumienia skutków tego stanu. - Makoto
Nie podążam za twoją argumentacją - celem tej odpowiedzi jest to, że nie zmieniasz historii, po prostu dodając nowe zatwierdzenia (które skutecznie cofają ponowienie zmian). Te nowe zatwierdzenia można pchać i scalać w zwykły sposób. - teh_senaus


Gdyby taka sytuacja:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

Występowałem:

git branch newbranch 
git reset --hard HEAD~8 
git checkout newbranch

Spodziewałem się, że popełnię, bym był HEAD, ale popełnij L jest teraz ...

Aby mieć pewność, że wylądujesz we właściwym miejscu w historii, łatwiej będzie pracować z hashem zatwierdzenia

git branch newbranch 
git reset --hard #########
git checkout newbranch

12
2018-05-01 07:50





Znacznie prostsze rozwiązanie

Gdyby:

  1. Twoim głównym celem jest wycofanie się master, i
  2. Chcesz zachować zmiany, ale nie przejmuj się specjalnie tym indywidualne zatwierdzenia, oraz
  3. Jeszcze nie pchnąłeś,

Następnie poniższe jest znacznie prostsze (zaczynając od gałęzi master z błędnymi zobowiązaniami):

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

Co to robi:

  1. Cofa ostatnie trzy zatwierdzenia (i ich wiadomości) do master, ale pozostawia wszystkie pliki robocze nienaruszone
  2. Usuwa wszystkie zmiany plików roboczych, dzięki czemu master drzewo robocze równe stanowi HEAD ~ 3.
  3. Przełącza do istniejącej gałęzi newbranch
  4. Wyciąga zmienione pliki ze skrytki do katalogu roboczego.

Możesz teraz użyć git add i git commit jak zwykle. Wszystkie zmiany zostaną zatwierdzone w newbranch.

Czego to nie robi:

  • Nie pozostawia przypadkowych tymczasowych gałęzi zaśmiecających twoje drzewo.
  • Nie zachowuje błędnych commitów i nie przekazuje wiadomości. Będziesz musiał dodać nową wiadomość zatwierdzenia do tego nowego zatwierdzenia. Jednak PO stwierdził, że celem było "opanowanie się, zanim te poprawki zostały wprowadzone" bez utraty zmian i to rozwiązanie to robi.

Robię to co najmniej raz w tygodniu, kiedy przypadkowo dokonuję nowych zobowiązań master zamiast develop. Zazwyczaj w takim przypadku mam tylko jedno zatwierdzenie wycofania git reset HEAD^ działa, aby wycofać tylko jedno zatwierdzenie.


8
2018-06-01 05:40