Comment injecter un commit entre deux commits arbitraires dans le passé?

127

Supposons que j'ai l'historique de validation suivant sur ma branche locale uniquement:

A -- B -- C

Comment insérer un nouveau commit entre Aet B?

BartoszKP
la source
3
commit appelé Ą ? :)
Antek
J'ai une question très similaire sur la façon d'insérer une nouvelle version dans le passé au lieu d'un commit.
Pavel P du

Réponses:

178

C'est encore plus facile que dans la réponse d'OP.

  1. git rebase -i <any earlier commit>. Cela affiche une liste de commits dans votre éditeur de texte configuré.
  2. Trouvez le commit que vous voulez insérer après (supposons que c'est a1b2c3d). Dans votre éditeur, pour cette ligne, changez picken edit.
  3. Commencez le rebase en fermant votre éditeur de texte (enregistrez vos modifications). Cela vous laisse à une invite de commande avec la validation que vous avez choisie précédemment ( a1b2c3d) comme si elle venait d'être validée .
  4. Apportez vos modifications et git commit( PAS de modification, contrairement à la plupart des edits). Cela crée un nouveau commit après celui que vous avez choisi.
  5. git rebase --continue. Cela relit les commits successifs, laissant votre nouveau commit inséré au bon endroit.

Sachez que cela réécrira l'histoire et brisera quiconque essaiera de tirer.

SLaks
la source
1
Cela a ajouté le nouveau commit après le commit, qui est après celui sur lequel je rebasais (également le dernier commit), au lieu de juste après celui sur lequel je rebasais. Le résultat était le même que si j'avais simplement fait un nouveau commit à la fin avec les changements que je voulais insérer. Mon histoire est devenue A -- B -- C -- Dau lieu de désirée A -- D -- B -- C.
XedinUnknown
2
@XedinUnknown: Alors vous n'avez pas utilisé Rebase correctement.
SLaks
3
Maintenant Dpourrait être un commit n'importe où. Supposons que nous ayons A - B - Cet que nous ayons un commit Dqui n'est même pas dans cette branche. Nous connaissons son SHA cependant, nous pouvons le faire git rebase -i HEAD~3. Maintenant, entre les lignes Aet B pick, nous insérons une nouvelle pick ligne qui dit pick SHA, en donnant le hachage du désiré D. Il n'est pas nécessaire que ce soit le hachage complet, mais simplement le hachage raccourci. git rebase -icerise choisit simplement les validations listées par picklignes dans le tampon; il n'est pas nécessaire qu'ils soient les originaux répertoriés pour vous.
Kaz
1
@Kaz Cela ressemble à une réponse différente et valide.
BartoszKP
3
Encore plus simple, vous pouvez utiliser le breakmot - clé dans l'éditeur sur sa propre ligne entre deux commits (ou sur la première ligne, pour insérer un commit avant votre commit spécifié).
SimonT
30

S'avère être assez simple, la réponse trouvée ici . Supposons que vous soyez sur une branche branch. Suivez ces étapes:

  • créez une branche temporaire à partir du commit après avoir voulu insérer le nouveau commit (dans ce cas commit A):

    git checkout -b temp A
    
  • effectuez les changements et validez-les, en créant un commit, appelons-le N:

    git commit -a -m "Message"
    

    (ou git addsuivi de git commit)

  • rebase les commits que vous voulez avoir après le nouveau commit (dans ce cas commits Bet C) sur le nouveau commit:

    git rebase temp branch
    

(vous devez peut-être utiliser -ppour conserver les fusions, s'il y en avait - grâce à un commentaire qui n'existe plus de ciekawy )

  • supprimer la branche temporaire:

    git branch -d temp
    

Après cela, l'historique se présente comme suit:

A -- N -- B -- C

Il est bien sûr possible que certains conflits apparaissent lors du rebasage.

Dans le cas où votre branche n'est pas uniquement locale, cela introduira un historique de réécriture, ce qui pourrait entraîner de graves problèmes.

BartoszKP
la source
2
Je n'ai pas pu suivre la réponse acceptée par SLaks, mais cela a fonctionné pour moi. Après avoir obtenu l'historique de validation que je voulais, j'ai dû git push --forcechanger le dépôt distant.
escapecharacter
1
Lorsque vous utilisez rebase, l'option -Xtheirs résout automatiquement les conflits correctement git rebase temp branch -Xtheirs. Réponse utile pour l'injection dans un script!
David C
Pour les noobs comme moi, j'aimerais ajouter cela après git rebase temp branch, mais avant git branch -d temp, tout ce que vous avez à faire est de résoudre et de mettre en scène les conflits et les problèmes de fusion git rebase --continue, c'est-à-dire pas besoin de commettre quoi que ce soit, etc.
Pugsley
19

Solution encore plus simple:

  1. Créez votre nouveau commit à la fin, D. Maintenant vous avez:

    A -- B -- C -- D
    
  2. Puis exécutez:

    $ git rebase -i hash-of-A
    
  3. Git ouvrira votre éditeur et il ressemblera à ceci:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Déplacez simplement D vers le haut comme ceci, puis enregistrez et quittez

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Maintenant, vous aurez:

    A -- D -- B -- C
    
Matthieu
la source
6
Bonne idée, mais il peut être difficile d'introduire D sur C, lorsque vous prévoyez que ces changements soient faits. à A.
BartoszKP
J'ai une situation où j'ai 3 commits que je veux rebaser ensemble et un commit au milieu qui n'est pas lié. C'est super agréable de pouvoir simplement déplacer ce commit plus tôt ou plus tard dans la ligne des commits.
unflores
13

En supposant que l'historique de validation est preA -- A -- B -- C, si vous souhaitez insérer une validation entre Aet B, les étapes sont les suivantes:

  1. git rebase -i hash-of-preA

  2. Git ouvrira votre éditeur. Le contenu peut aimer ceci:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Remplacez le premier pickpar edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Sauvegarder et quitter.

  3. Modifiez votre code puis git add . && git commit -m "I"

  4. git rebase --continue

Maintenant, votre historique de commit Git est preA -- A -- I -- B -- C


Si vous rencontrez un conflit, Git s'arrêtera à ce commit. Vous pouvez utiliser git diffpour localiser les marqueurs de conflit et les résoudre. Après avoir résolu tous les conflits, vous devez utiliser git add <filename>pour indiquer à Git que le conflit a été résolu, puis réexécutergit rebase --continue .

Si vous souhaitez annuler le rebase, utilisez git rebase --abort.

haolee
la source
11

Voici une stratégie qui évite de faire un "edit hack" lors du rebase vu dans les autres réponses que j'ai lues.

En utilisant, git rebase -ivous obtenez une liste des validations depuis cette validation. Ajoutez simplement un "break" en haut du fichier, cela provoquera la rupture du rebase à ce stade.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Une fois lancé, git rebaseva maintenant s'arrêter au point de la "pause". Vous pouvez maintenant éditer vos fichiers et créer votre commit normalement. Vous pouvez ensuite continuer le rebase avec git rebase --continue. Cela peut provoquer des conflits que vous devrez résoudre. Si vous vous perdez, n'oubliez pas que vous pouvez toujours annuler l'utilisation de git rebase --abort.

Cette stratégie peut être généralisée pour insérer un commit n'importe où, il suffit de mettre le "break" à l'endroit où vous voulez insérer un commit.

Après avoir réécrit l'histoire, n'oubliez pas de le faire git push -f. Les avertissements habituels concernant d'autres personnes qui récupèrent votre branche s'appliquent.

axérologie
la source
Désolé, mais j'ai du mal à comprendre comment est ce "éviter le rebase". Vous êtes en cours d' exécution rebaseici. Ce n'est pas beaucoup de différence que vous créiez le commit pendant le rebase ou avant.
BartoszKP
Woops, je voulais dire éviter le "hack d'édition" pendant le rebase, je suppose que je l'ai mal formulé.
axerologementy
Droite. Ma réponse n'utilise pas non plus la fonction «modifier» du rebase. Pourtant, c'est encore une autre approche valable - merci! :-)
BartoszKP
C'est de loin la meilleure solution!
Theodore R. Smith il y a
6

Beaucoup de bonnes réponses ici déjà. Je voulais juste ajouter une solution "no rebase", en 4 étapes faciles.


Résumé

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Explication

(Remarque: l'un des avantages de cette solution est que vous ne touchez pas votre branche avant l'étape finale, lorsque vous êtes sûr à 100% que vous êtes d'accord avec le résultat final, vous avez donc une étape de «pré-confirmation» très pratique permettant des tests AB .)


État initial (j'ai supposé masterle nom de votre succursale)

A -- B -- C <<< master <<< HEAD

1) Commencez par pointer HEAD au bon endroit

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Optionnellement ici, plutôt que de détacher HEAD, nous aurions pu créer une branche temporaire avec git checkout -b temp A, que nous aurions besoin de supprimer à la fin du processus. Les deux variantes fonctionnent, faites comme vous préférez car tout le reste reste le même)


2) Créez le nouveau commit D à insérer

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Ensuite, apportez des copies des derniers commits manquants B et C (serait la même ligne s'il y avait plus de commits)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(test AB confortable ici si nécessaire)

Le moment est venu d'inspecter votre code, de tester tout ce qui doit être testé, et vous pouvez également comparer / comparer / inspecter ce que vous aviez et ce que vous obtiendriez après les opérations.


4) En fonction de vos tests entre Cet C', soit c'est OK, soit c'est KO.

(SOIT) 4-OK) Enfin, déplacez la référence demaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(OU) 4-KO) Laissez simplement masterinchangé

Si vous avez créé une branche temporaire, supprimez-la simplement avec git branch -d <name>, mais si vous avez opté pour la route HEAD détachée, aucune action n'est nécessaire à ce stade, les nouveaux commits seront éligibles pour le ramasse-miettes juste après que vous les reconnectiez HEADavec ungit checkout master

Dans ces deux cas (OK ou KO), à ce stade, il suffit de vérifier à masternouveau pour le rattacher HEAD.

RomainValeri
la source