web-dev-qa-db-fra.com

Squash les deux premiers commits à Git?

Avec git rebase --interactive <commit> vous pouvez écraser un nombre quelconque de commits en un seul.

Tout cela est formidable, sauf si vous souhaitez écraser les commits dans le commit initial. Cela semble impossible à faire.

Y a-t-il des moyens de le réaliser?


Modérément lié:

Dans une question connexe, j’ai réussi à proposer une approche différente en ce qui concerne la nécessité d’écraser le premier commit, c’est-à-dire en faire le deuxième.

Si cela vous intéresse: git: comment insérer un commit en premier, en décalant tous les autres?

517
kch

Mise à jour de juillet 2012 ( git 1.7.12 + )

Vous pouvez maintenant rediffuser tous les commits jusqu'à la racine et sélectionner le deuxième commit Y à écraser avec le premier X.

git rebase -i --root master

pick sha1 X
squash sha1 Y
pick sha1 Z
git rebase [-i] --root $tip

Cette commande peut maintenant être utilisée pour réécrire tout l'historique menant de "$tip" à la validation racine.

Voir commit df5df20c1308f936ea542c86df1e9c6974168472 sur GitHub de Chris Webb (arachsys) .


Réponse originale (février 2009)

Je pense que vous trouverez différentes recettes à ce sujet dans la SO question "Comment associer les deux premiers commits d'un dépôt git? "

Charles Bailey a fourni le plus réponse détaillée , nous rappelant qu'un commit est un arbre complet (pas seulement des différences d'un état précédent).
Et ici, l'ancien commit (le "commit initial") et le nouveau commit (résultat de l'écrasement) n'auront aucun ancêtre commun.
Cela signifie que vous ne pouvez pas "commit --amend" la validation initiale dans une nouvelle, puis rebasonner sur la nouvelle validation initiale l'historique de la précédente validation initiale (beaucoup de conflits)

(Cette dernière phrase n'est plus vraie avec git rebase -i --root <aBranch>)

Plutôt (avec A la "validation initiale" d'origine et B une validation ultérieure devait être écrasée dans la validation initiale):

  1. Retournez au dernier commit que nous souhaitons former comme premier commit (détachez HEAD):

    git checkout <sha1_for_B>
    
  2. Réinitialisez le pointeur de branche sur le commit initial, mais en laissant l'index et l'arbre de travail intacts:

    git reset --soft <sha1_for_A>
    
  3. Modifiez l’arbre initial en utilisant l’arbre de 'B':

    git commit --amend
    
  4. Marquez temporairement cette nouvelle validation initiale (ou vous pouvez vous souvenir manuellement de la nouvelle validation sha1):

    git tag tmp
    
  5. Retournez à la branche d'origine (supposons maître pour cet exemple):

    git checkout master
    
  6. Rejouer tous les commits après B sur le nouveau commit initial:

    git rebase --onto tmp <sha1_for_B>
    
  7. Supprimer la balise temporaire:

    git tag -d tmp
    

De cette façon, le "rebase --onto" n'introduit pas de conflits lors de la fusion, car il rebase l'historique créé après le dernier commit (B) à écraser. dans la version initiale (qui était A) à tmp (représentant le nouveau commit initial écrasé): les fusions rapides simples sont effectuées uniquement.

Cela fonctionne pour "A-B", mais aussi "A-...-...-...-B" (un nombre quelconque de commits peut être écrasé dans le premier de cette façon)

683
VonC

J'ai retravaillé le script de VonC pour tout faire automatiquement et ne rien me demander. Vous lui donnez deux SHA1 de commit et il écrase tout entre eux dans un commit nommé "histoire écrasée":

#!/bin/sh
# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout $2

# reset the branch pointer to the initial commit (= $1),
# but leaving the index and working tree intact.
git reset --soft $1

# amend the initial tree using the tree from $2
git commit --amend -m "squashed history"

# remember the new commit sha1
TARGET=`git rev-list HEAD --max-count=1`

# go back to the original branch (assume master for this example)
git checkout master

# Replay all the commits after $2 onto the new initial commit
git rebase --onto $TARGET $2
30
fonsinchen

Pour ce que ça vaut, j'évite ce problème en créant toujours un premier commit "no-op", dans lequel la seule chose dans le référentiel est un .gitignore vide:

https://github.com/DarwinAwardWinner/git-custom-commands/blob/master/bin/git-myinit

De cette façon, il n'y a jamais aucune raison de jouer avec le premier commit.

23
Ryan Thompson

Si vous voulez simplement réduire tous les commits en un seul, commencez par réinitialiser le référentiel et modifiez le premier commit:

git reset hash-of-first-commit
git add -A
git commit --amend

La réinitialisation de Git laissera l’arbre de travail intact, donc tout est toujours là. Il suffit donc d’ajouter les fichiers à l’aide des commandes git add et de modifier le premier commit avec ces modifications. Par rapport à rebase -i, vous perdrez cependant la possibilité de fusionner les commentaires git.

18
Mike Looijmans

Cela écrasera le deuxième engagement dans le premier:

A-B-C-... -> AB-C-...

git filter-branch --commit-filter '
    if [ "$GIT_COMMIT" = <sha1ofA> ];
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi
' HEAD

Le message de validation pour AB sera pris de B (bien que je préfère de A).

A le même effet que la réponse de Uwe Kleine-König, mais fonctionne également pour A non initial.

5
Antony Hatchkins

Réduire le premier et le deuxième commit entraînerait la réécriture du premier commit. Si vous avez plusieurs branches basées sur le premier commit, vous devez couper cette branche.

Prenons l'exemple suivant:

a---b---HEAD
 \
  \
   '---d

Réduire a et b dans un nouveau commit "ab" donnerait deux arbres distincts, ce qui dans la plupart des cas n'est pas souhaitable car git-merge et git-rebase ne fonctionnera plus à travers les deux branches.

ab---HEAD

a---d

Si vous le souhaitez vraiment, cela peut être fait. Jetez un œil à git-filter-branch pour un outil puissant (et dangereux) de réécriture de l'historique.

3
hillu

Vous pouvez utiliser git filter-branch pour cela. par exemple.

git filter-branch --parent-filter \
'if test $GIT_COMMIT != <sha1ofB>; then cat; fi'

Cela a pour résultat que AB-C jette le journal de commit de A.

3
Uwe Kleine-König

Vous pouvez utiliser rebase interactive pour modifier les deux derniers commits avant qu'ils ne soient poussés vers une télécommande.

git rebase HEAD^^ -i
1
todd