web-dev-qa-db-fra.com

Déplacer le ou les derniers commit vers une nouvelle branche avec Git

J'aimerais déplacer les derniers commits que je me suis engagé à maîtriser dans une nouvelle branche et à les ramener avant que ces commits ne soient effectués. Malheureusement, mon Git-fu n'est pas encore assez fort, aucune aide?

C'est à dire. Comment puis-je partir de ça

master A - B - C - D - E

pour ça?

newbranch     C - D - E
             /
master A - B 
4108
Mark A. Nicolosi

Déménagement dans une nouvelle succursale

WARNING: Cette méthode fonctionne parce que vous créez une nouvelle branche avec la première commande: git branch newbranch. Si vous souhaitez déplacer les validations vers une branche existante, vous devez fusionner vos modifications dans la branche existante avant d'exécuter git reset --hard HEAD~3 (voir Passage vers une branche existante ci-dessous). Si vous ne fusionnez pas vos modifications en premier, elles seront perdues.

À moins que d’autres circonstances ne soient impliquées, cela peut être facilement fait en ramifiant et en rétrogradant.

# 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 de personnes s’engagent à revenir en arrière. Vous pouvez également, au lieu de HEAD~3, fournir simplement le hachage de la validation (ou la référence telle que Origine/maître) que vous souhaitez "rétablir" sur le maître (/ current ) branche, par exemple:

git reset --hard a1b2c3d4

* 1 Vous seulement serez "perdant" les commits de la branche principale, mais ne vous inquiétez pas, vous les aurez dans newbranch!

AVERTISSEMENT: Avec Git version 2.0 et ultérieure, si vous définissez plus tard git rebase la nouvelle branche sur la branche d'origine (master), vous aurez peut-être besoin d'une option explicite --no-fork-point lors de la refonte pour éviter de perdre les validations reportées. Si branch.autosetuprebase always est défini, cela est plus probable. Voir Réponse de John Mellor pour plus de détails.

Déplacement vers une branche existante

Si vous souhaitez déplacer vos commits vers une branche existante, cela 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
5244
sykora

Pour ceux qui se demandent pourquoi cela fonctionne (comme je l'étais au début):

Vous souhaitez revenir en C et déplacer D et E dans la nouvelle branche. Voici à quoi ça 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 a indiqué le dernier commit. Lorsque vous avez créé newBranch , vous avez simplement créé un nouveau pointeur sur le dernier commit. Ensuite, en utilisant git reset, vous avez déplacé le pointeur master de deux commits. Mais puisque vous n'avez pas déplacé newBranch , il pointe toujours sur le commit qu'il a fait à l'origine.

893
Ryan Lundy

En général...

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

Pour réaliser ce que veut OP, il s’agit d’un processus en deux étapes:

Étape 1 - Notez quelle est la validation du maître que vous voulez sur une newbranch

Execute

git checkout master
git log

Notez les hachages de (disons 3) que vous voulez sur newbranch. Ici je vais utiliser:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3 

Remarque: Vous pouvez utiliser les sept premiers caractères ou l'ensemble commet le hachage

Étape 2 - Mettez-les sur la newbranch

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

OU (sur Git 1.7.2+, plages d'utilisation)

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

git cherry-pick applique ces trois commits à newbranch.

372
Ivan

Encore une autre façon de faire cela, en utilisant seulement 2 commandes. Garde également votre arbre de travail actuel 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 d'avoir entendu parler 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 une bonne astuce à connaître.

275
aragaer

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

Ne faites pas cela:

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

Lors de la prochaine exécution de git rebase (ou git pull --rebase), ces 3 commits seraient ignorés de newbranch! (voir explication ci-dessous)

Au lieu de cela, faites ceci:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Tout d'abord, il rejette les 3 dernières mises à jour (--keep est semblable à --hard, mais plus sûr, car échoue plutôt que de jeter les modifications non validées).
  • Ensuite, il biffe newbranch.
  • Ensuite, il sélectionne ces 3 commits sur newbranch. Puisqu'elles ne sont plus référencées par une branche, elle le fait en utilisant reflog : HEAD@{2} de git est le commit que HEAD utilisait pour désigner 2 opérations déjà, c'est-à-dire avant que 1. nous ayons extrait newbranch et 2. used git reset. éliminer les 3 commits.

Attention: le reflog est activé par défaut, mais si vous l'avez désactivé manuellement (en utilisant par exemple un référentiel git "nu"), vous ne pourrez pas récupérer les 3 validations après avoir exécuté git reset --keep HEAD~3.

Une alternative qui ne repose pas sur le reflog 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 extraite - au lieu de oldbranch).


Explication technique

Pourquoi git rebase écarterait-il les 3 commits après le premier exemple? C'est parce que git rebase sans argument active l'option --fork-point par défaut, qui utilise le reflog local pour essayer d'être robuste contre la branche en amont étant forcée.

Supposons que vous ayez dérivé de Origin/Master quand il contenait les commits M1, M2, M3, puis que vous avez effectué trois commits:

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

mais alors quelqu'un réécrit l'histoire en forçant Origin/master à supprimer M2:

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

En utilisant votre refog local, git rebase peut voir que vous avez hérité d’une incarnation antérieure de la branche Origin/master et que, par conséquent, les commits M2 et M3 ne font pas vraiment partie de votre branche de sujet. Par conséquent, il est raisonnable de supposer que, puisque M2 a été supprimé de la branche en amont, vous ne le souhaitez plus dans votre branche de sujet non plus lorsque la branche de sujet est rebasée:

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

Ce comportement a du sens et est généralement la bonne chose à faire lors du changement de base.

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 reflog dans le mauvais état. Git considère que newbranch a jeté la branche amont dans une révision qui inclut les 3 commits, puis le reset --hard réécrit l'historique du amont pour supprimer les commits, et la prochaine fois que vous exécuterez git rebase, il les supprimera comme tout autre commit qui a été supprimé de l'amont.

Mais dans ce cas particulier, nous voulons que ces 3 commits soient considérés comme faisant partie de la branche thématique. Pour atteindre cet objectif, nous avons besoin de bifurquer en amont lors de la précédente révision qui n'incluait pas les 3 commits. C’est ce que mes solutions suggérées font, donc elles laissent toutes deux le bon état dans le bon état.

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

251
John Mellor

Solution beaucoup plus simple utilisant git stash

Si: 

  • Votre but principal est de restaurer master, et 
  • Vous souhaitez conserver les modifications de fichier mais ne vous souciez pas particulièrement des messages erronés, et
  • Vous n'avez pas encore poussé, et
  • Vous voulez que ce soit facile et pas compliqué avec des branches temporaires, des hachages de validation et d'autres maux de tête

La procédure suivante est beaucoup plus simple (à partir de la branche master comportant trois commits erronés):

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

Ce que cela fait, par numéro de ligne

  1. Annule les trois derniers commits (et leurs messages) sur master, tout en laissant tous les fichiers de travail intacts
  2. Cache toutes les modifications de fichier de travail, rendant l'arbre de travail master exactement égal à l'état HEAD ~ 3
  3. Basculer vers une branche existante 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. Tous les nouveaux commits seront ajoutés à newbranch

Qu'est-ce que cela ne fait pas

  • Il ne laisse pas de branches temporaires aléatoires encombrer votre arbre
  • Il ne préserve pas les commits erronés ni les messages de commit, vous aurez donc besoin d'ajouter un nouveau message de commit à ce nouveau commit

Buts

Le PO a déclaré que l'objectif était de "ramener le maître avant que ces commissions ne soient effectuées" sans perdre de modifications et cette solution le fait.

Je le fais au moins une fois par semaine lorsque je commets accidentellement de nouveaux commits sur master au lieu de develop. Habituellement, je n'ai qu'un seul commit à annuler, auquel cas utiliser git reset HEAD^ sur la ligne 1 est un moyen plus simple d'annuler un commit. 

Ne faites pas cela si vous avez poussé les modifications du maître en amont

Quelqu'un d'autre peut avoir retiré ces changements. Si vous ne faites que réécrire votre maître local, l'impact en amont n'est pas impactant, mais le fait de transmettre un historique réécrit aux collaborateurs peut entraîner des problèmes. 

46
Slam

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)
28
Sukima

Pour faire cela sans réécrire l’historique (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!

21
teh_senaus

Avait juste cette situation:

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

J'ai joué:

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

Je m'attendais à ce que je commette je serais le HEAD, mais commettez L est-ce maintenant ...

Pour être sûr d’atterrir au bon endroit de l’histoire, il est plus facile de travailler avec le hash du commit

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

1) Créez une nouvelle branche, qui transfère toutes vos modifications vers new_branch.

git checkout -b new_branch

2) Puis retournez à l'ancienne branche.

git checkout master

3) Est-ce que git rebase 

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

4) Ensuite, l'éditeur ouvert contient les 3 dernières informations de validation.

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

5) Remplacez pick par drop dans ces 3 commits. Puis enregistrez et fermez l'éditeur. 

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

6) Les 3 derniers commits sont maintenant supprimés de la branche actuelle (master). Maintenant, forcez la branche avec le signe + avant le nom de la branche.

git Push Origin +master
2
rashok

Comment puis-je partir de ça

A - B - C - D - E 
                |
                master

pour ça?

A - B - C - D - E 
    |           |
    master      newbranch

Avec deux commandes

  • git branch -m master newbranch

donnant

A - B - C - D - E 
                |
                newbranch

et

  • git branche master B

donnant

A - B - C - D - E
    |           |
    master      newbranch
1
user3070485

Si vous avez juste besoin de déplacer tous vos non pressés s’engage dans une nouvelle branche , alors il vous suffit de le faire,

  1. créer a nouvelle branche de l'actuelle: git branch new-branch-name

  2. Push votre nouvelle branche : git Push Origin new-branch-name

  3. revenez votre ancienne branche (actuelle) vers le dernier état poussé/stable: git reset --hard Origin/old-branch-name

Certaines personnes ont aussi un autre upstreams plutôt que Origin, elles devraient utiliser un upstream approprié

0

Vous pouvez faire cela est juste 3 étapes simples que j'ai utilisées. 

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

git branch <branch name> 

2) Trouver l'ID de validation récente pour la validation sur la nouvelle branche. 

git log

3) Copiez cette ID de validation en notant que la liste de validation la plus récente est placée en haut. afin que vous puissiez trouver votre commit. vous trouvez aussi cela via un message. 

git cherry-pick d34bcef232f6c... 

vous pouvez également fournir des identifiants de numéro de commit.

git cherry-pick d34bcef...86d2aec

Maintenant, votre travail est fait. Si vous avez choisi l'id correct et la branche correcte, alors vous réussirez. Alors avant cela, faites attention. sinon un autre problème peut survenir. 

Maintenant, vous pouvez pousser votre code 

git Push

0
Pankaj Kumar