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
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
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.
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.
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.
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 queHEAD
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.
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)
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!
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
- annule les trois derniers commits (et leurs messages) à
master
, mais laisse tous les fichiers de travail intacts - 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 - Commute sur une branche
newbranch
- 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.
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
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
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