Déplacer le plus récent commit(s) à une nouvelle branche avec Git

j'aimerais déplacer les derniers commits je me suis engagé à maîtriser à une nouvelle branche et de reprendre maître avant que ces commits ont été faites. Malheureusement, mon Git-fu n'est pas encore assez fort, de l'aide?

i. e. Comment puis-je partir de ce

master A - B - C - D - E

à ça?

newbranch     C - D - E
             /
master A - B 
3860
demandé sur BIBIN K ONANKUNJU 2009-10-27 06:07:34

11 réponses

déménagement dans une nouvelle succursale

AVERTISSEMENT: Cette méthode fonctionne parce que vous êtes la création d'une nouvelle branche avec le premier commandement: git branch newbranch . Si vous voulez déplacer s'engage à une branche existante vous devez fusionner vos changements dans la branche existante avant d'exécuter git reset --hard HEAD~3 (voir se déplacer à une branche existante ci-dessous). si vous ne fusionnez pas vos modifications tout d'abord, ils seront perdus.

à moins que d'autres circonstances ne soient en cause, il est facile de le faire en ramifiant et en retournant.

# 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

Mais assurez-vous combien s'engage à revenir en arrière. Alternativement, vous pouvez au lieu de HEAD~3 , simplement fournir le hachage de la propagation (ou la référence comme origine/maître ) vous voulez "revenir à" sur le maître (/current) branch, E. g:

git reset --hard a1b2c3d4

*1 seulement être "perdant" commits de la branche master, mais ne vous inquiétez pas, vous aurez ceux qui s'engage dans newbranch!

avertissement: avec la version 2.0 de Git et plus tard, si vous plus tard git rebase la nouvelle branche sur la branche originale ( master ), vous pouvez avoir besoin d'une option explicite --no-fork-point pendant le rebase pour éviter de perdre la reprise engager. Avoir branch.autosetuprebase always ensemble rend cela plus probable. Voir réponse de John Mellor pour plus de détails.

déménagement dans une succursale existante

si vous voulez déplacer vos commits vers une branche existante , il ressemblera à ceci:

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
répondu sykora 2018-07-23 08:07:38

Pour ceux qui se demandent pourquoi ça marche (comme je l'ai été au début):

vous voulez retourner à C, et déplacer D et E à la nouvelle branche. Voici à quoi il ressemble au début:

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

après git branch newBranch :

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

après git reset --hard HEAD~2 :

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

Puisqu'une branche n'est qu'un pointeur, master pointait vers le dernier commit. Quand vous avez fait newBranch , vous avez simplement fait un nouveau pointeur vers le dernier commit. Puis en utilisant git reset vous avez déplacé le maître pointeur deux commits. Mais comme vous n'avez pas bougé newBranch , il indique toujours le commit qu'il a fait à l'origine.

861
répondu Ryan Lundy 2015-12-31 00:29:20

En Général...

La méthode exposée par sykora est la meilleure option dans ce cas. Mais parfois n'est pas facile et ce n'est pas une méthode générale. Pour une méthode générale, utiliser git cherry-pick :

pour réaliser ce que L'OP veut, c'est un processus en 2 étapes:

Étape 1 - Notez ce qui s'engage à partir de maître que vous voulez sur un newbranch

Exécuter

git checkout master
git log

notez les hachures de (disons 3) commits que vous voulez sur newbranch . Ici je vais utiliser:

C commettre: 9aa1233

D engager: 453ac3d

E s'engager: 612ecb3

Note: vous pouvez utiliser les sept premiers caractères ou toute la Commission

Étape 2 - Mettez-les sur le newbranch

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

OU (sur Git 1.7.2+, utilisent des plages)

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

git cherry-pick s'applique à ces trois s'engage à newbranch.

349
répondu Ivan 2018-02-28 18:13:52

Encore une autre façon de le faire, en utilisant seulement 2 commandes. Garde également votre arbre de travail intact.

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

ancienne version - avant que j'ai appris à propos de git branch -f

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

être capable de push à . est un bon truc à savoir.

256
répondu aragaer 2018-06-21 14:17:32

la plupart des réponses précédentes sont dangereusement fausses!

ne faites pas ceci:

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

comme la prochaine fois que vous exécutez git rebase (ou git pull --rebase ) ces 3 commits seraient silencieusement écartés de newbranch ! (voir explication ci-dessous)

à la place, faites ceci:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • tout d'abord, il écarte les 3 plus récentes commits ( --keep est comme --hard , mais plus sûr, comme échoue plutôt que de jeter les changements non engagés).
  • Puis il bifurque newbranch .
  • puis il sélectionne ces 3 sur newbranch . Comme elles ne sont plus référencées par une branche, elle le fait en utilisant le de git : HEAD@{2} est le commit que HEAD utilisé pour se référer à 2 opérations Il ya, c.-à-d. avant nous 1. vérifié newbranch et 2. utilisé git reset jeter les 3 commits.

Avertissement: le reflog est activé par défaut, mais si vous l'avez désactivée manuellement (en utilisant par exemple un "nu" dépôt git), vous ne serez pas en mesure d'obtenir les 3 s'engage en arrière après l'exécution de git reset --keep HEAD~3 .

une alternative qui ne s'appuie pas sur le refrog est:

# 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

(si vous préférez, vous pouvez écrire @{-1} - la branche précédemment cochée - au lieu de oldbranch ).


explication technique

pourquoi git rebase jeter le 3 commet après le premier exemple? C'est parce que git rebase sans arguments permet l'option --fork-point par défaut, qui utilise le reflog local pour essayer d'être robuste contre la branche amont étant forcée.

supposez que vous vous êtes ramifié hors origine / master quand il contient commits M1, M2, M3, puis fait trois s'engage vous-même:

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

mais alors quelqu'un réécrit l'histoire en poussant l'origine/maître à supprimer M2:

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

en utilisant votre reflog local, git rebase peut voir que vous avez bifurqué d'une incarnation antérieure de la branche origine/master, et donc que les commits M2 et M3 ne font pas vraiment partie de votre branche thématique. Par conséquent, il est raisonnable de supposer que depuis M2 a été retiré de la branche en amont, vous ne voulez-le dans votre branche de thème soit une fois que la branche de thème est rebasé:

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

ce comportement fait sens, et est généralement la bonne chose à faire lors du rebasement.

donc la raison pour laquelle les commandes suivantes échouent:

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

est parce qu'ils laissent le reflet dans le mauvais état. Git voit newbranch comme ayant bifurqué de la branche amont à une révision qui inclut les 3 commits, puis le reset --hard réécrit l'histoire de l'amont pour supprimer les propagations, et donc la prochaine fois que vous exécutez git rebase il les écarte comme n'importe quelle autre propagation qui a été supprimée de l'amont.

mais dans ce cas précis nous voulons que ces 3 commits soient considérés comme faisant partie de la branche topic. Pour y parvenir, nous devons bifurquer l'amont à la révision précédente qui n'inclut pas les 3 propagations. C'est ce que font mes solutions suggérées, donc elles laissent toutes les deux le reflet dans le bon état.

pour plus de détails, voir la définition de --fork-point dans le git rebase et git merge-base docs.

225
répondu John Mellor 2016-09-16 01:34:57

cela ne les "déplace" pas au sens technique mais a le même effet:

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
répondu Sukima 2014-08-17 10:23:48

Pour ce faire, sans réécriture de l'histoire (c'est à dire si vous avez déjà poussé les commits):

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

les deux branches peuvent alors être poussées sans force!

19
répondu teh_senaus 2016-01-21 16:10:36

beaucoup plus simple solution utilisant git stash

si:

  • votre but principal est de faire reculer master , et
  • Vous souhaitez conserver les modifications, mais ne se soucie particulièrement de l'individu s'engage, et
  • Vous n'avez pas poussé encore, et
  • Vous voulez que ce soit facile et pas compliqué avec le temp les branches et autres maux de tête

alors ce qui suit est beaucoup plus simple (à partir de la branche master qui a trois erreurs commet):

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

ce Qu'il fait, par numéro de ligne

  1. annule les trois derniers commits (et leurs messages) à master , mais laisse tous les fichiers de travail intacts
  2. cache toutes les modifications du fichier de travail, ce qui fait de master l'arbre de travail égal à l'état de la tête~3
  3. Commute sur une branche newbranch
  4. applique les modifications cachées à votre répertoire de travail et efface la cachette

Vous pouvez maintenant utiliser git add et git commit comme vous le feriez normalement. Toutes les nouvelles propagations seront ajoutées à newbranch .

Que ce n'est pas faire

  • Il ne laisse pas de hasard temporaire branches qui encombrent votre arbre de noël
  • Il ne préserve pas l'erreur de les validations et les messages de commit, de sorte que vous aurez besoin d'ajouter un nouveau message de validation de ce nouveau commit

Objectifs

l'OP a déclaré que le but était de" ramener maître avant que ces commits ont été faites " sans perdre des changements et cette solution le fait.

je fais cela au moins une fois par semaine quand je fais accidentellement de nouvelles commits à master à la place de develop . En général, je n'ai qu'un seul commit à lancer, auquel cas l'utilisation de git reset HEAD^ sur la ligne 1 est un moyen plus simple de lancer un seul commit.

ne faites pas cela si vous avez poussé les changements de maître en amont

Quelqu'un d'autre a pu tirer ces changements. Si vous ne réécrivez que votre maître local, il n'y a aucun impact quand il est poussé en amont, mais le fait de transmettre une histoire réécrite à des collaborateurs peut causer des maux de tête.

17
répondu Slam 2018-09-26 23:36:43

avait justement cette situation:

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

j'ai effectué:

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

Je m'attendais à ce que commit soit la tête, mais commit est maintenant...

Pour être sûr d'atterrir au bon endroit dans l'histoire, il est plus facile de travailler avec le hash de le commettre

git branch newbranch 
git reset --hard #########
git checkout newbranch
12
répondu Darkglow 2013-09-20 10:17:35

1) créez une nouvelle branche, qui déplace toutes vos modifications vers new_branch.

git checkout -b new_branch

2) puis retourner à old branch.

git checkout master

3) Ne git rebase

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

4) alors l'éditeur ouvert contient les 3 dernières informations de propagation.

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

5) Remplacer pick par drop dans toutes ces 3 propagations. Puis enregistrez et fermez l'éditeur.

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

6) les 3 dernières propagations sont retirées de la branche courante ( master ). Maintenant poussez la branche avec force, avec le signe + avant le nom de la branche.

git push origin +master
2
répondu rashok 2018-06-01 05:40:11

Vous pouvez faire ce qui est juste 3 étape simple que j'ai utilisé.

1) Créer une nouvelle branche où vous voulez vous engager mise à jour récente.

git branch <branch name>

2) Trouver Commit Id pour commit on new branch.

git log

3) Copie que commit id notez que la liste de commit la plus récente se trouve en haut. de sorte que vous pouvez trouvez votre validation. vous trouvez aussi ceci via un message.

git cherry-pick d34bcef232f6c...

vous pouvez également fournir un certain rang de commit id.

git cherry-pick d34bcef...86d2aec

votre travail est terminé. Si vous avez choisi l'id correct et la branche correcte alors vous réussirez. Alors avant de faire ça, soyez prudent. sinon, un autre problème peut survenir.

maintenant vous pouvez appuyer sur votre code

git push

0
répondu Pankaj Kumar 2018-05-25 14:14:27