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

Chciałbym przenieść kilka ostatnich commitów, które wprowadziłem do mastera do nowej gałęzi i przenieść master z powrotem do niej zanim te commity zostały wprowadzone. Niestety, mój Git-fu nie jest jeszcze wystarczająco silny, jakaś pomoc?

Tzn. jak Mogę przejść z tego

master A - B - C - D - E
Do tego?
newbranch     C - D - E
             /
master A - B 
Author: BIBIN K ONANKUNJU, 2009-10-27

11 answers

Przeniesienie do nowego oddziału

WARNING: ta metoda działa, ponieważ tworzysz nową gałąź za pomocą pierwszego polecenia: git branch newbranch. Jeśli chcesz przenieść commity do istniejącej gałęzi, musisz scalić zmiany do istniejącej gałęzi przed wykonaniem git reset --hard HEAD~3 (Zobacz przeniesienie do istniejącego oddziału poniżej). jeśli najpierw nie połączysz swoich zmian, zostaną one utracone.

O ile nie ma innych okoliczności, może to łatwo zrobić, rozgałęziając się i cofając.

# 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 commitów ma wrócić. Alternatywnie, możesz zamiast HEAD~3, po prostu podać hash commita (lub referencji jak origin / master ), do którego chcesz "powrócić" w gałęzi master (/current), np:

git reset --hard a1b2c3d4

*1 będziesz tylko "tracić" commity z gałęzi master, ale nie martw się, będziesz miał te commity w New Branch!

WARNING: z Gitem w wersji 2.0 i nowszej, jeśli później git rebase nowa gałąź po oryginalnej (master) gałęzi, możesz potrzebować jawnej opcji --no-fork-point podczas rebase, aby uniknąć utraty przeniesionych zmian. Posiadanie branch.autosetuprebase always zestawu sprawia, że jest to bardziej prawdopodobne. Zobacz odpowiedź Johna Mellora Po szczegóły.

Przeniesienie do istniejącego oddziału

Jeśli chcesz przenieść swoje commity do istniejącej gałęzi , będzie to wyglądało 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
 4914
Author: sykora,
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-07-23 08:07:38

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

Chcesz wrócić do C i przenieść D i E do nowej gałęzi. Oto jak to 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łąź jest tylko wskaźnikiem, master wskazuje na ostatni commit. Kiedy tworzysz newBranch , po prostu tworzysz nowy wskaźnik do ostatniego commita. Następnie za pomocą git resetprzesunąłeś wskaźnik master do tyłu o dwa commity. Ale ponieważ nie przeniosłeś newBranch , nadal wskazuje na commit, który pierwotnie wykonał.

 861
Author: Ryan Lundy,
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
2015-12-31 00:29:20

Ogólnie...

Metoda wystawiona przez sykorę jest najlepszą opcją w tym przypadku. Ale czasami nie jest to najłatwiejsze i nie jest to ogólna metoda. Dla metody ogólnej użyj git cherry-pick :

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

Krok 1-Uwaga, który commit z master chcesz na newbranch

Wykonaj

git checkout master
git log

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

Uwaga: możesz użyć pierwszych siedmiu znaków lub cały hash commit

Krok 2-umieść je na newbranch

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

Lub (w Git 1.7.2+, użyj zakresów)

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

git cherry-pick stosuje te trzy commity do newbranch.

 349
Author: Ivan,
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-28 18:13:52

Jeszcze jeden sposób, aby to zrobić, używając tylko 2 komend. Utrzymuje również obecne działające drzewo w stanie nienaruszonym.

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 dowiedziałem się o git branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 
Bycie zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym do bycia zdolnym.
 256
Author: aragaer,
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-06-21 14:17:32

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

Nie rób tego:

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

Przy następnym uruchomieniu git rebase (lub git pull --rebase) Te 3 commity zostaną po cichu odrzucone z newbranch! (Patrz wyjaśnienie poniżej)

Zamiast tego zrób to:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • najpierw odrzuca 3 najnowsze commity (--keep jest jak --hard, ale bezpieczniejsze, ponieważ nie powiedzie się, a nie wyrzuci niezarejestrowane zmiany).
  • potem rozwidla się newbranch.
  • / Align = "left" / te 3 commity wracają do newbranch. Ponieważ nie są już referowane przez gałąź, robi to używając reflog Gita: HEAD@{2} to commit, który HEAD odnosił się do 2 operacji temu, tzn. przed 1. sprawdzone newbranch i 2. używane git reset do odrzucenia 3 commitów.

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), nie będziesz w stanie odzyskać 3 commitów po uruchomieniu git reset --keep HEAD~3.

Alternatywą, która nie opiera się 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 napisać @{-1} - wcześniej sprawdzoną gałąź-zamiast oldbranch).


Wyjaśnienie techniczne

Dlaczego git rebase miałby odrzucić 3 commity po pierwszym przykładzie? To dlatego, że git rebase bez argumentów domyślnie włącza opcję --fork-point, która używa lokalnego reflog, aby spróbować być solidna w stosunku do gałęzi upstream siłą.

Załóżmy, że rozgałęziłeś origin / master, gdy zawierało commity M1, M2, M3, a następnie wykonałeś trzy commity:

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

Ale potem ktoś przepisuje historię przez wymuszanie origin / master aby usunąć M2:

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

Używając lokalnego refloga, git rebase możesz zobaczyć, że oddzieliłeś się od wcześniejszej inkarnacji gałęzi origin/master, a więc commity M2 i M3 nie są tak naprawdę częścią twojej gałęzi tematycznej. Stąd rozsądnie zakłada się, że skoro m2 było po usunięciu z gałęzi upstream, nie chcesz już jej w swojej gałęzi tematycznej ani po rebasowaniu gałęzi tematycznej:

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

To zachowanie ma sens i jest generalnie słuszne podczas rebasingu.

Więc powód, dla którego następujące polecenia nie działają:

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

To dlatego, że zostawiają reflog w złym stanie. Git widzi newbranch jako rozwidlenie gałęzi upstream przy rewizji, która zawiera 3 commity, a następnie reset --hard przepisuje na nowo gałąź upstream Historia, aby usunąć commity, więc następnym razem, gdy uruchomisz git rebase, odrzuca je tak, jak każdy inny commit, który został usunięty z programu upstream.

Ale w tym konkretnym przypadku chcemy, aby te 3 commity były traktowane jako część gałęzi tematycznej. Aby to osiągnąć, musimy oddzielić się od wcześniejszych wersji, która nie zawiera 3 commitów. To właśnie robią moje proponowane rozwiązania, stąd oba pozostawiają reflog we właściwym stanie.

Aby uzyskać więcej informacji, zobacz definicja --fork-pointw Git rebasei Git merge-base docs.

 225
Author: John Mellor,
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-09-16 01:34:57

To nie" porusza " 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
Author: Sukima,
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-08-17 10:23:48

Aby zrobić to bez przepisywania historii (tzn. jeśli już wypchnąłeś commity):

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

Obie gałęzie mogą zostać popchnięte bez użycia siły!

 19
Author: teh_senaus,
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-01-21 16:10:36

Znacznie prostsze rozwiązanie przy użyciu git stash

Jeśli:

  • Twoim głównym celem jest powrót master i
  • chcesz zachować zmiany, ale nie dbasz szczególnie o poszczególne commity i
  • jeszcze nie nacisnąłeś i
  • chcesz, aby to było łatwe i nie skomplikowane z gałęziami temp i innymi bólami głowy

Następnie poniższe jest o wiele prostsze (począwszy od gałęzi master, która ma trzy błędne "commit"): {]}

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

Co to robi, po numerze linii

  1. wycofuje ostatnie trzy commity (i ich Wiadomości) do master, ale pozostawia wszystkie pliki robocze nienaruszone
  2. usuwa wszystkie zmiany pliku roboczego, czyniąc drzewo robocze master równym stanowi HEAD~3
  3. przełącza się na istniejącą gałąź newbranch
  4. stosuje Ukryte zmiany do katalogu roboczego i czyści skrytkę

Możesz teraz używać git add i git commit jak zwykle would. Wszystkie nowe commity zostaną dodane do newbranch.

Czego to nie robi

    Nie zostawia przypadkowych tymczasowych gałęzi zaśmiecających twoje drzewo]}
  • Nie zachowuje błędnych commitów i komunikatów commitów, {[49] } więc musisz dodać nowy commit do tego nowego commita

Bramki

OP stwierdził, że celem jest "przywrócenie master' a do czasów, zanim te commity zostały wprowadzone " bez utraty zmian, a to rozwiązanie robi to.

Robię to co najmniej raz w tygodniu, gdy przypadkowo robię nowe commity do master zamiast develop. Zwykle mam tylko jeden commit do rollback, w którym to przypadku użycie git reset HEAD^ w linii 1 jest prostszym sposobem na wycofanie tylko jednego commita.

Nie rób tego, jeśli pchnąłeś zmiany mistrza pod prąd

Ktoś inny mógł usunąć te zmiany. Jeśli przepisujesz tylko lokalnego mistrza, nie ma to wpływu, gdy jest on popychany pod prąd, ale popychanie przepisanej historii do kolaborantów może powodować bóle głowy.

 17
Author: Slam,
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-09-26 23:36:43

Miałem taką sytuację:

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

Wykonałem:

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

Spodziewałem się, że commit I będzie głową, ale commit L jest teraz...

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

git branch newbranch 
git reset --hard #########
git checkout newbranch
 12
Author: Darkglow,
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-20 10:17:35

1) Utwórz nową gałąź, która przeniesie wszystkie zmiany do new_branch.

git checkout -b new_branch

2) potem wróć do starej gałęzi.

git checkout master

3) Do git rebase

git rebase -i <short-hash-of-B-commit>

4) następnie otwarty edytor zawiera Ostatnie 3 Informacje o zatwierdzeniu.

...
pick <C's hash> C
pick <D's hash> D
pick <E's hash> E
...

5) Zmień pick na drop we wszystkich tych 3 commitach. Następnie zapisz i zamknij Edytor.

...
drop <C's hash> C
drop <D's hash> D
drop <E's hash> E
...

6) teraz Ostatnie 3 commity są usuwane z bieżącej gałęzi (master). Teraz mocno wciśnij gałąź ze znakiem + przed gałąź nazwisko.

git push origin +master
 2
Author: rashok,
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-06-01 05:40:11

Możesz to zrobić to tylko 3 proste kroki, które użyłem.

1) Utwórz nową gałąź, w której chcesz zatwierdzić najnowszą aktualizację.

git branch <branch name>

2) Znajdź najnowszy identyfikator zatwierdzenia dla nowej gałęzi.

git log

3) Skopiuj ID zmian zauważ, że ostatnia lista zmian znajduje się na górze. więc możesz znaleźć swoje zobowiązanie. można to również znaleźć za pośrednictwem wiadomości.

git cherry-pick d34bcef232f6c...

Możesz również podać kilka rang commit dowód osobisty.

git cherry-pick d34bcef...86d2aec

Wykonałeś swoją pracę. Jeśli wybrałeś poprawne id i poprawną gałąź, to odniesiesz sukces. Więc zanim to zrobisz, bądź ostrożny. w przeciwnym razie może wystąpić inny problem.

Teraz możesz wcisnąć swój kod

git push

 0
Author: Pankaj Kumar,
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-05-25 14:14:27