Comment puis-je fusionner deux commits en un si j'ai déjà commencé le rebase?

1159

J'essaie de fusionner 2 commits en 1, j'ai donc suivi "squashing commits with rebase" de git ready .

L'Iran

git rebase --interactive HEAD~2

Dans l'éditeur résultant, je passe pickà squash, puis je sauvegarde-quitte, mais le rebase échoue avec l'erreur

Impossible de 'squash' sans un commit précédent

Maintenant que mon arbre de travail a atteint cet état, j'ai du mal à récupérer.

La commande git rebase --interactive HEAD~2échoue avec:

Le rebase interactif a déjà commencé

et git rebase --continueéchoue avec

Impossible de 'squash' sans un commit précédent

Michael
la source
22
J'ai frappé ça aussi. Mon erreur a été causée par le fait que git rebase -i répertorie les commits dans l'ordre opposé de git log; le dernier commit est en bas!
lmsurprenant

Réponses:

1734

Sommaire

Le message d'erreur

Impossible de 'squash' sans un commit précédent

signifie que vous avez probablement tenté de «vous écraser vers le bas». Git écrase toujours un commit plus récent dans un commit plus ancien ou «vers le haut» comme affiché sur la liste de tâches de rebase interactive, c'est-à-dire dans un commit sur une ligne précédente. Changer la commande sur la toute première ligne de votre liste de tâches squashproduira toujours cette erreur car il n'y a rien pour le premier commit à écraser.

The Fix

Revenez d'abord à votre point de départ

$ git rebase --abort

Dis que ton histoire est

$ git log --pretty=oneline
a931ac7c808e2471b22b5bd20f0cad046b1c5d0d c
b76d157d507e819d7511132bdb5a80dd421d854f b
df239176e1a2ffac927d8b496ea00d5488481db5 a

Autrement dit, a était le premier commit, puis b et enfin c. Après avoir commis c, nous décidons d'écraser b et c ensemble:

(Remarque: l'exécution par défaut git logachemine sa sortie vers un pageur, lesspar défaut sur la plupart des plates-formes. Pour quitter le pageur et revenir à votre invite de commande, appuyez sur la qtouche.)

La course à pied git rebase --interactive HEAD~2vous donne un éditeur avec

pick b76d157 b
pick a931ac7 c

# Rebase df23917..a931ac7 onto df23917
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

(Notez que cette liste de tâches est dans l'ordre inverse par rapport à la sortie de git log .)

Modification de B pickà squashentraînera l'erreur que vous avez vu, mais si au contraire vous de squash c en b (dans le récent engagement plus ou « vers le haut squashing ») en modifiant la liste des tâches à

pick   b76d157 b
squash a931ac7 c

et en sauvegardant votre éditeur, vous obtiendrez un autre éditeur dont le contenu est

# This is a combination of 2 commits.
# The first commit's message is:

b

# This is the 2nd commit message:

c

Lorsque vous enregistrez et quittez, le contenu du fichier modifié devient le message de validation du nouveau commit combiné:

$ git log --pretty=oneline
18fd73d3ce748f2a58d1b566c03dd9dafe0b6b4f b and c
df239176e1a2ffac927d8b496ea00d5488481db5 a

Remarque sur l'historique de réécriture

Le rebase interactif réécrit l'histoire. Tenter de pousser vers une télécommande contenant l'ancien historique échouera car il ne s'agit pas d'une avance rapide.

Si la branche que vous avez rebasée est une branche de sujet ou de fonctionnalité dans laquelle vous travaillez par vous - même , ce n'est pas grave. Pousser vers un autre référentiel nécessitera la--force option, ou bien vous pourrez, selon les autorisations du référentiel distant, supprimer d'abord l'ancienne branche, puis pousser la version rebasée. Des exemples de ces commandes susceptibles de détruire le travail sont hors de portée de cette réponse.

La réécriture de l'historique déjà publié sur une branche dans laquelle vous travaillez avec d'autres personnes sans très bonne raison, comme la fuite d'un mot de passe ou d'autres détails sensibles, force le travail sur vos collaborateurs et est antisocial et ennuiera les autres développeurs. La section «Récupération à partir d'une rebase en amont» dans la git rebasedocumentation explique, avec un accent supplémentaire.

Rebaptiser (ou toute autre forme de réécriture) une branche sur laquelle d'autres ont basé leur travail est une mauvaise idée: toute personne en aval est obligée de corriger manuellement son historique. Cette section explique comment effectuer la correction du point de vue en aval. La vraie solution, cependant, serait d'éviter en premier lieu de rebaser l'amont.

Greg Bacon
la source
Si j'utilise rebase pour écraser un commit, un nouveau commit "combiné" est créé contenant les deux changesets mais le hachage est différent. les commits originaux sont-ils également conservés par git?
fabsenet
@fabsenet Oui et non. Les commits originaux sont toujours accessibles mais ne sont probablement plus accessibles à partir d'aucune référence (selon les détails de votre histoire). Les validations non référencées sont finalement supprimées via le processus de récupération de place.
Greg Bacon
je jouais juste autour ... je l'ai fait git log hashoftheoldcommitet cela a fonctionné, mais git log --graph
j'étais
ce squash est un bon outil pour organiser les validations avant le push, mais si j'en pousse un, je ne peux pas le compresser? git dit: HEAD détaché rebasé et mis à jour avec succès.
Sérgio
Je ne reçois pas l'éditeur sur la deuxième instance, le git bash, semble figé dans un processus. Que faire?
411

S'il y a plusieurs validations, vous pouvez utiliser git rebase -ipour écraser deux validations en une seule.

S'il n'y a que deux validations que vous souhaitez fusionner, et qu'il s'agit des «deux plus récentes», les commandes suivantes peuvent être utilisées pour combiner les deux validations en une seule:

git reset --soft "HEAD^"
git commit --amend
user3828059
la source
6
Quel est l'inconvénient par rapport au rebase? J'en trouve un beaucoup plus simple à utiliser.
Guillaume86
17
Vous ne pouvez pas vous joindre à un ordre arbitraire - seuls les deux derniers commits .
dr0i
50
@ dr0i Vous pouvez fusionner autant de validations que vous le souhaitez, tant qu'il s'agit des dernières validations X, et non quelque part au milieu. Exécutez simplement git reset --soft HEAD~10, où 10 est le nombre de validations que vous souhaitez fusionner.
fregante
2
Cela peut être utilisé si vous ne disposez pas d'un ensemble d'origine distant et que vous n'avez que deux validations.
atedja
8
Vous pouvez également réinitialiser un commit spécifique si vous ne voulez pas en compter le nombre HEADen utilisant l' git reset --soft 47b5c5...emplacement de 47b5c5...l'ID SHA1 du commit.
dguay
112

Rebase: vous n'en aurez pas besoin:

Un moyen plus simple pour le scénario le plus fréquent.

Dans la plupart des cas:

En fait, si tout ce que vous voulez, c'est simplement combiner plusieurs validations récentes en une seule, mais que vous n'avez pas besoin drop, rewordet que d'autres rebases fonctionnent.

vous pouvez simplement faire:

git reset --soft "HEAD~n"
  • En supposant ~nest le nombre de commits à voix basse un-commit (c. -à ~1, ~2...)

Utilisez ensuite la commande suivante pour modifier le message de validation.

git commit --amend

qui est à peu près la même chose qu'une longue portée de squashet un pick.

Et cela fonctionne pour n commits mais pas seulement pour deux commits comme indiqué ci-dessus.

pambda
la source
3
C'est bien si vous voulez faire un nettoyage supplémentaire en plus de simplement écraser les commits, comme supprimer 1 commit au milieu ou changer une ligne de code.
styfle
1
En supposant ~nest le nombre de commits à un-commit doucement (ie ~1, ~2...)
Ray
1
Que se passe-t-il si je veux fusionner non pas les ndernières validations, mais les nvalidations au milieu? Puis-je le faire facilement?
chumakoff
1
Ensuite, git rebase -ic'est ce que vous devez faire pour squashtravailler. @chumakoff
pambda
3
Donc, pour rejoindre nles commits les plus récents en un, première utilisation git reset --soft @~m, oùm = n - 1
Łukasz Rajchel
55

Vous devez d'abord vérifier le nombre de commits que vous avez:

git log

Il existe deux statuts:

La première est qu'il n'y a que deux commits:

Par exemple:

commit A
commit B

(Dans ce cas, vous ne pouvez pas utiliser git rebase pour le faire), vous devez faire ce qui suit.

$ git reset --soft HEAD^1

$ git commit --amend

Un autre est qu'il y a plus de deux commits; vous voulez fusionner les commit C et D.

Par exemple:

commit A
commit B
commit C
commit D

(sous cette condition, vous pouvez utiliser git rebase)

git rebase -i B

Et que d'utiliser "squash" pour le faire. Le reste aminci est très facile. Si vous ne savez toujours pas, veuillez lire http://zerodie.github.io/blog/2012/01/19/git-rebase-i/

Haimei
la source
La réinitialisation --soft et commit --amend est le seul moyen qui fonctionne si vous avez déjà un rebase en cours (et choisissez 'edit' au lieu de 'squash' pour ce commit). +1
Jacek Lach
1
Fusionner le premier et seulement deux commits dans un dépôt, exactement mon cas de bord :-)
Chris Huang-Leaver
1
Veuillez ajouter que cela git push -f origin masterpourrait être nécessaire.
Rishabh Agrahari
33

En supposant que vous étiez dans votre propre branche de sujet. Si vous souhaitez fusionner les 2 dernières validations en une seule et ressembler à un héros, dérivez la validation juste avant d'avoir effectué les deux dernières validations.

git checkout -b temp_branch HEAD^2

Squash valide ensuite l'autre branche dans cette nouvelle branche:

git merge branch_with_two_commits --squash

Cela apportera les changements mais ne les engagera pas. Alors, engagez-les et vous avez terminé.

git commit -m "my message"

Vous pouvez maintenant fusionner cette nouvelle branche de sujet dans votre branche principale.

Homan
la source
5
C'était en fait la réponse la plus utile pour moi, car elle ne nécessitait pas de rebasage manuel, mais à la place, elle écrase toutes les validations d'une branche entière en une seule validation. Très agréable.
Robert
Merci pour cela! Voici comment faire git faire comme j'imagine les commits de squashing dans ma tête!
Marjan Venema
réponse étonnante, tellement plus simple que les alternatives
Apparemment, cette réponse ne convient pas au cas où aelle cdoit être fusionnée et conservée btelle quelle.
Talha Ashraf
2
Quelque chose a-t-il changé dans les versions récentes de git? Lorsque j'essaie la première commande ( git checkout -b combine-last-two-commits "HEAD^2") dans la version 2.17 de git, j'obtiens une erreur:fatal: 'HEAD^2' is not a commit and a branch 'combine-last-two-commits' cannot be created from it
mhucka
23

vous pouvez annuler le rebase avec

git rebase --abort

et lorsque vous exécutez à nouveau la commande interactive rebase, le 'squash; la validation doit être inférieure à la sélection de validation dans la liste

Leom Burke
la source
16

J'utilise souvent git reset --mixed pour rétablir une version de base avant plusieurs validations que vous souhaitez fusionner, puis je fais un nouveau commit, de cette façon pourrait laisser votre commit le plus récent, assurez-vous que votre version est HEAD après avoir poussé sur le serveur.

commit ac72a4308ba70cc42aace47509a5e
Author: <[email protected]>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <[email protected]>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <[email protected]>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

Si je veux fusionner deux commits en un seul, j'utilise d'abord:

git reset --mixed 249cf9392da197573a17c8426c282

"249cf9392da197573a17c8426c282" était la troisième version, est également votre version de base avant de fusionner, après cela, je fais un nouveau commit:

git add .
git commit -m 'some commit message'

C'est tout, l'espoir est une autre façon pour tout le monde.

Pour info, de git reset --help:

 --mixed
     Resets the index but not the working tree (i.e., the changed files are
     preserved but not marked for commit) and reports what has not been
     updated. This is the default action.
VinceStyling
la source
Je n'ai pas lu les documents pour '--mixed' mais je suis sûr que d'autres personnes ont lu le post et se sont demandé la même chose: quel est l'avantage d'utiliser --mixed? Pourrait améliorer votre publication afin d'inclure un extrait de la page de manuel.
funroll
@funroll je ne savais pas - très bien mélangé avant d'écrire cette réponse, selon ma propre expérience, le opération mixte tournera spécifier la version que je passe comme argument en tant que version HEAD du référentiel, et rien ne peut être perdu après cette version, afin que nous puissions encore gérer ces changements.
VinceStyling
14

$ git rebase --abort

Exécutez ce code à tout moment si vous souhaitez annuler le rebase git

$ git rebase -i HEAD~2

Pour réappliquer les deux derniers commits. La commande ci-dessus ouvrira un éditeur de code

  • [ Le dernier commit sera en bas ]. Modifiez le dernier commit en squash (s). Puisque le squash fusionnera avec le commit précédent.
  • Appuyez ensuite sur la touche Echap et tapez: wq pour enregistrer et fermer

Après: wq vous serez en mode rebase actif

Remarque : vous obtiendrez un autre éditeur si aucun message d'avertissement / d'erreur, s'il y a une erreur ou un avertissement, un autre éditeur ne s'affichera pas, vous pouvez abandonner en exécutant $ git rebase --abort si vous voyez une erreur ou un avertissement, continuez simplement en exécutant$ git rebase --continue

Vous verrez votre 2 message de validation. Choisissez-en un ou écrivez votre propre message de validation, enregistrez et quittez [: wq]

Remarque 2: vous devrez peut-être forcer l'envoi de vos modifications au référentiel distant si vous exécutez la commande rebase

$ git push -f

$ git push -f origin master

Gnanasekar S
la source
1
Note 2: git push -f origin/masterc'est ce qui manque d'autres réponses. +1
Rishabh Agrahari
2

Depuis que j'utilise git cherry-pick pour à peu près tout, pour moi, il est naturel de le faire même ici.

Étant donné que j'ai branchXvérifié et qu'il y a deux commits à la fin, dont je veux créer un commit combinant leur contenu, je fais ceci:

git checkout HEAD^ // Checkout the privious commit
git cherry-pick --no-commit branchX // Cherry pick the content of the second commit
git commit --amend // Create a new commit with their combined content

Si je veux également mettre à jour branchX(et je suppose que c'est le côté négatif de cette méthode), je dois également:

git checkout branchX
git reset --hard <the_new_commit>
Martin G
la source
1

Si votre branche principale git logressemble à ceci:

commit ac72a4308ba70cc42aace47509a5e
Author: <[email protected]>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <[email protected]>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <[email protected]>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

et vous souhaitez fusionner les deux premiers validations, procédez simplement comme suit:

  1. D'abord pour être en sécurité, l'avant-dernier commit dans une branche distincte. Vous pouvez nommer la branche n'importe quoi.git checkout 77df2a40e53136c7a2d58fd847372 -b merged-commits
  2. Maintenant, choisissez simplement vos modifications du dernier commit dans cette nouvelle branche comme: git cherry-pick -n -x ac72a4308ba70cc42aace47509a5e . (Résolvez les conflits le cas échéant)
  3. Alors maintenant, vos changements dans le dernier commit sont là dans votre avant-dernier commit. Mais vous devez encore vous engager, alors ajoutez d'abord les modifications que vous venez de sélectionner, puis exécutez git commit --amend.

C'est ça. Vous pouvez pousser cette version fusionnée dans la branche "merged-commits" si vous le souhaitez.

De plus, vous pouvez maintenant ignorer les deux validations consécutives dans votre branche principale. Mettez simplement à jour votre branche principale en tant que:

git checkout master
git reset --hard origin/master (CAUTION: This command will remove any local changes to your master branch)
git pull
Usman
la source
0

Si vous souhaitez combiner les deux validations les plus récentes et utiliser simplement le message de la validation la plus ancienne, vous pouvez automatiser le processus à l'aide de expect.

Je suppose:

  • Vous utilisez vi comme éditeur
  • Vos engagements sont d'une ligne chacun

J'ai testé avec git version 2.14.3 (Apple Git-98).


#!/usr/bin/env expect
spawn git rebase -i HEAD~2

# change the second "pick" to "squash"
# down, delete word, insert 's' (for squash), Escape, save and quit
send "jdwis \033:wq\r"

expect "# This is a"

# skip past first commit message (assumed to be one line), delete rest of file
# down 4, delete remaining lines, save and quit
send "4jdG\r:wq\r"

interact
erwaman
la source
On ne sait pas exactement ce que fait votre script.
buhtz
@buhtz J'ai ajouté quelques commentaires supplémentaires. Faites-moi savoir si vous trouvez toujours cela déroutant et si oui, quelle partie.
erwaman
On ne sait toujours pas ce que fait votre scrpit. Est également expectnon décrit.
buhtz
@buhtz quelle partie n'est pas claire? J'ai fourni un lien vers une page avec plus de documentation pour expect.
erwaman
Il manque une description générale du script. On ne sait pas exactement ce qu'il fait. Aucune partie n'est floue. L'intention du script lui-même n'est pas claire.
buhtz