récupérer - supprimer une branche git




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

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?

Ie Comment puis-je partir de cela

master A - B - C - D - E

pour ça?

newbranch     C - D - E
             /
master A - B 

En général...

La méthode exposée par sykora est la meilleure option dans ce cas. Mais parfois, ce n'est pas le 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 commande du maître que vous voulez sur une nouvelle newbranch

Exécuter

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 le hachage de validation complet.

Étape 2 - Mettez-les sur la nouvelle 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.


Solution beaucoup plus simple utilisant git stash

Si:

  • Votre but principal est de faire reculer le master , et
  • Vous voulez garder les changements mais ne vous souciez pas particulièrement des commits individuels, et
  • Vous n'avez pas encore poussé, et
  • Vous voulez que ce soit facile et pas compliqué avec des branches temporaires et d'autres maux de tête

La procédure suivante est beaucoup plus simple (à partir du master branche 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) à master , mais laisse tous les fichiers de travail intacts
  2. Cache toutes les modifications de fichier de travail, rendant l'arbre de travail master égal à l'état HEAD ~ 3
  3. Basculer vers une nouvelle branche existante
  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 validation, vous devez donc ajouter un nouveau message de validation à ce nouveau commit.

Buts

Le PO a déclaré que l'objectif était de "reprendre le contrôle avant que ces commits ne soient faits" sans perdre de modifications et cette solution le fait.

Je le fais au moins une fois par semaine lorsque je commets accidentellement de nouveaux engagements à master au lieu de develop . En général, 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 tiré ces changements. Si vous ne faites que réécrire votre maître local, l'impact en amont n'est pas impactant. Toutefois, le fait de transmettre un historique réécrit aux collaborateurs peut entraîner des problèmes.


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

Comme la prochaine fois que vous exécuterez git rebase (ou git pull --rebase ), ces 3 commits seront silencieusement newbranch 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 [email protected]{2}
  • Tout d'abord, il rejette les 3 derniers commits ( --keep est comme - --hard , mais plus sûr, car échoue plutôt que de jeter les modifications non validées).
  • Ensuite, il newbranch .
  • Ensuite, il sélectionne ces 3 engagements sur newbranch . Comme ils ne sont plus référencés par une branche, il utilise pour reflog le reflog de reflog de git: [email protected]{2} est le commit que HEAD utilisait pour faire référence à 2 opérations auparavant, c'est-à-dire avant 1. l' newbranch et 2. avec git reset utilisé. git reset pour éliminer les 3 commits.

Attention: le reflog est activé par défaut, mais si vous l'avez désactivé manuellement (par exemple en utilisant un référentiel git "nu"), vous ne pourrez pas récupérer les 3 git reset --keep HEAD~3 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 oldbranch - au lieu de oldbranch ).

Explication technique

Pourquoi est-ce que git rebase éliminerait 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 ramifié origine / maître quand il contenait les commits M1, M2, M3, puis vous vous êtes engagé trois fois:

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

mais alors quelqu'un réécrit l'histoire en forçant origine / maître pour supprimer M2:

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

En utilisant votre refog local, git rebase peut voir que vous avez dérivé d'une incarnation antérieure de la branche origin / master, et donc que les commits M2 et M3 ne font pas vraiment partie de votre branche topic. 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 est logique et constitue 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 démarré la branche en amont avec une révision incluant les 3 commits, puis la reset --hard réécrit l’historique du reset --hard amont pour supprimer les commits. Ainsi, la prochaine fois que vous exécuterez git rebase il les supprimera comme tout autre commet a été retiré de l'amont.

Mais dans ce cas particulier, nous souhaitons que ces 3 commits soient considérés comme faisant partie de la branche thématique. Pour atteindre cet objectif, nous devons identifier les modifications en amont lors de la révision précédente, qui n'inclut pas les 3 commits. C’est ce que mes solutions suggérées font, de sorte qu’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 documents git rebase et git merge-base .


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

git checkout -b new_branch

2) Ensuite, 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) Changer le pick pour 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

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)

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 à . C'est un bon truc à savoir.


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

Vous souhaitez revenir à 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 la git branch newBranch :

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

Après la git reset --hard HEAD~2 :

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

Puisqu’une branche n’est qu’un pointeur, le maître s’est dirigé vers le dernier commit. Lorsque vous avez créé newBranch , vous avez simplement créé un nouveau pointeur sur le dernier commit. Puis, avec git reset vous avez déplacé le pointeur principal de deux commits. Mais puisque vous n'avez pas déplacé newBranch , il pointe toujours sur le commit qu'il a fait à l'origine.


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

1) créer une nouvelle branche sur laquelle vous souhaitez valider votre 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







branching-and-merging