web-dev-qa-db-fra.com

Comment modifier un commit spécifique?

Je soumets habituellement une liste de commits pour révision. Si j'ai les commits suivants:

  1. HEAD
  2. Commit3
  3. Commit2
  4. Commit1

... Je sais que je peux modifier le commit principal avec git commit --amend. Mais comment puis-je modifier Commit1, étant donné que ce n'est pas le commit HEAD?

2002
Sam Liao

Vous pouvez utiliser git rebase . Par exemple, si vous souhaitez modifier commit bbc643cd, exécutez

$ git rebase --interactive 'bbc643cd^'

Veuillez noter le caret ^ à la fin de la commande, car vous devez en réalité vous baser sur le commit avant celui que vous souhaitez modifier .

Dans l'éditeur par défaut, modifiez pick en edit dans la ligne en mentionnant 'bbc643cd'.

Enregistrez le fichier et quittez: git interprétera et exécutera automatiquement les commandes du fichier. Vous vous retrouverez dans la situation précédente dans laquelle vous venez de créer commit bbc643cd.

À ce stade, bbc643cd est votre dernier commit et vous pouvez le modifier facilement : apportez vos modifications puis validez-les avec la commande:

$ git commit --all --amend --no-edit

Après cela, tapez:

$ git rebase --continue

pour revenir à la précédente HEAD commit.

WARNING: Notez que cela va changer le SHA-1 de ce commit ainsi que tous les enfants - En d'autres termes, cela réécrit l'histoire à partir de ce moment. Vous pouvez interrompre cette opération si vous appuyez sur la commande git Push --force

2609
ZelluX

Utilisez la formidable base interactive:

git rebase -i @~9   # Show the last 9 commits in a text editor

Recherchez le commit souhaité, remplacez pick par e (edit), puis enregistrez et fermez le fichier. Git reviendra sur ce commit, vous permettant soit de:

  • utilisez git commit --amend pour apporter des modifications, ou
  • utilisez git reset @~ pour annuler le dernier commit, mais pas les modifications apportées aux fichiers (c’est-à-dire vous rendre au point où vous en étiez lorsque vous avez édité les fichiers, mais n’avait pas encore été validé).

Ce dernier est utile pour effectuer des tâches plus complexes telles que la scission en plusieurs commits.

Ensuite, exécutez git rebase --continue et Git relit les modifications ultérieures par-dessus votre validation modifiée. Vous pouvez être invité à résoudre certains conflits de fusion.

Remarque: @ est un raccourci pour HEAD et ~ est la validation avant la validation spécifiée.

En savoir plus sur historique de la réécriture dans la documentation Git.


N'ayez pas peur de vous rebaser

ProTip ™: N'ayez pas peur d'expérimenter avec des commandes "dangereuses" qui réécrivent l'historique * - Git ne supprime pas vos validations pendant 90 jours par défaut; vous pouvez les trouver dans le reflog:

$ git reset @~3   # go back 3 commits
$ git reflog
c4f708b HEAD@{0}: reset: moving to @~3
2c52489 HEAD@{1}: commit: more changes
4a5246d HEAD@{2}: commit: make important changes
e8571e4 HEAD@{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started

* Méfiez-vous des options telles que --hard et --force bien qu'elles puissent supprimer des données.
* De même, ne réécrivez pas l'historique des branches sur lesquelles vous collaborez.



Sur de nombreux systèmes, git rebase -i ouvrira Vim par défaut. Vim ne fonctionne pas comme la plupart des éditeurs de texte modernes, alors jetez un oeil à comment rebaser à l'aide de Vim . Si vous préférez utiliser un autre éditeur, changez-le avec git config --global core.editor your-favorite-text-editor.

401
Zaz

Interactive rebase avec --autosquash est quelque chose que j'utilise fréquemment lorsque je dois corriger des commits antérieurs plus profonds dans l'historique. Cela accélère essentiellement le processus décrit par la réponse de ZelluX, et est particulièrement utile lorsque vous avez plus d'un commit à éditer.

De la documentation:

--autosquash

Lorsque le message de journal de validation commence par "squash! ..." (ou "fixup! ..."), et qu'il existe un commit dont le titre commence par le même ..., modifiez automatiquement la liste de tâches de rebase -i pour que le commit marqué pour écraser vient juste après le commit à modifier

Supposons que vous avez une histoire qui ressemble à ceci:

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

et vous avez des modifications que vous souhaitez modifier dans Commit2, puis validez vos modifications à l'aide de

$ git commit -m "fixup! Commit2"

sinon, vous pouvez utiliser commit-sha au lieu du message de validation, donc "fixup! e8adec4 ou même simplement un préfixe du message de validation.

Puis initiez une rebase interactive sur le commit avant

$ git rebase e8adec4^ -i --autosquash

votre éditeur s'ouvrira avec les commits déjà correctement commandés

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

tout ce que vous avez à faire est de sauvegarder et de quitter

68
thrau

Courir:

$ git rebase --interactive commit_hash^

chaque ^ indique le nombre de commits que vous souhaitez éditer, s'il ne s'agit que d'un (le hachage de validation que vous avez spécifié), vous n'ajoutez donc qu'un ^.

En utilisant Vim, vous changez les mots pick en reword pour les commits que vous souhaitez modifier, enregistrer et quitter (:wq). Ensuite, git vous invitera à chaque commit que vous avez marqué comme reformulation afin que vous puissiez modifier le message de commit.

Chaque message de validation que vous devez enregistrer et quitter (:wq) pour passer au message de validation suivant

Si vous souhaitez quitter sans appliquer les modifications, appuyez sur :q!

EDIT: pour naviguer dans vim vous utilisez j pour monter, k pour descendre, h pour aller à gauche et l pour aller à droite (tout cela en mode NORMAL, appuyez sur ESC pour aller en mode NORMAL.). Pour éditer un texte, appuyez sur i pour entrer dans le mode INSERT, où vous insérez du texte. Appuyez sur ESC pour revenir au mode NORMAL :)

UPDATE: Voici un excellent lien depuis la liste de github Comment annuler (presque) quoi que ce soit avec git

38
betoharres

Si, pour une raison quelconque, vous n'aimez pas les éditeurs interactifs, vous pouvez utiliser git rebase --onto.

Supposons que vous souhaitiez modifier Commit1. Tout d’abord, branche de avantCommit1:

git checkout -b amending [commit before Commit1]

Deuxièmement, prenez Commit1 avec cherry-pick:

git cherry-pick Commit1

Maintenant, modifiez vos modifications en créant Commit1':

git add ...
git commit --amend -m "new message for Commit1"

Et enfin, après avoir stocké tous les autres changements, greffez le reste de vos commits jusqu’à master par-dessus votre nouveau commet:

git rebase --onto amending Commit1 master

Lire: "rebase, sur la branche amending, tout est validé entre Commit1 (non inclus) et master (inclusif)". C'est-à-dire, Commit2 et Commit3, supprimant entièrement l'ancien Commit1. Vous pouvez simplement les sélectionner, mais cette méthode est plus simple.

N'oubliez pas de nettoyer vos branches!

git branch -d amending
16
FeepingCreature

Basé sur Documentation

Modification du message de messages de validation plus anciens ou multiples

git rebase -i HEAD~3 

Ce qui précède affiche une liste des 3 derniers commits sur la branche en cours, remplacez 3 par autre chose si vous voulez plus. La liste ressemblera à ce qui suit:

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but Push the same commit.

Remplacez , sélectionnez par le reformulation avant chaque message de validation que vous souhaitez modifier. Supposons que vous changiez le deuxième commit de la liste, votre fichier ressemblera à ceci:

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but Push the same commit.

Enregistrez et fermez le fichier de liste de validation. Un nouvel éditeur apparaît pour vous permettre de modifier votre message de validation, de le modifier et de le sauvegarder.

Enfin, forcez sur les modifications apportées.

git Push --force
11
justMe

Commande complètement non interactive(1)

Je pensais juste que je partagerais un alias que j'utilise pour cela. Il est basé sur non interactif rebase interactif. Pour l'ajouter à votre git, lancez cette commande (explication donnée ci-dessous):

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'

Le plus gros avantage de cette commande est le fait que c'est no-vim .


(1)étant donné qu'il n'y a pas de conflits pendant rebase, bien sûr

Usage

git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111

Le nom amend-to semble approprié à mon humble avis. Comparez le flux avec --amend:

git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>

Explication

  • git config --global alias.<NAME> '!<COMMAND>' - crée un alias de git global nommé <NAME> qui exécutera la commande non-git <COMMAND>
  • f() { <BODY> }; f - une fonction bash "anonyme".
  • SHA=`git rev-parse "$1"`; - convertit l'argument en révision git et affecte le résultat à la variable SHA
  • git commit --fixup "$SHA" - fixup-commit pour SHA. Voir git-commit docs
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^" cette partie a été couverte par d'autres réponses.
    • --autosquash est ce qui est utilisé conjointement avec git commit --fixup, voir git-rebase docs pour plus d'informations.
    • GIT_SEQUENCE_EDITOR=true est ce qui rend le tout non-interactif. Ce hack, j'ai appris de ce post de blog .
11
Dethariel

Modification automatique de la base interactive suivie de la validation du retour prêt pour une reprise

Je me suis souvent trouvé en train de réparer un passé, et j'en ai écrit un script.

Voici le flux de travail:

  1. git commit-edit <commit-hash>
    

    Cela vous laissera tomber au commit que vous voulez éditer.

  2. Fixez et organisez le commit comme vous le souhaitiez.

    (Vous pouvez utiliser git stash save pour conserver tous les fichiers que vous ne validez pas)

  3. Refaites le commit avec --amend, par exemple:

    git commit --amend
    
  4. Terminez la rebase:

    git rebase --continue
    

Pour que ce qui précède fonctionne, placez le script ci-dessous dans un fichier exécutable appelé git-commit-edit quelque part dans votre $PATH:

#!/bin/bash

set -euo pipefail

script_name=${0##*/}

warn () { printf '%s: %s\n' "$script_name" "$*" >&2; }
die () { warn "$@"; exit 1; }

[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"

# Default to editing the parent of the most recent commit
# The most recent commit can be edited with `git commit --amend`
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")

if [[ $OSTYPE =~ ^darwin ]]; then
  sed_inplace=(sed -Ei "")
else
  sed_inplace=(sed -Ei)
fi

export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \\1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)"  # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty  #  Commit an empty commit so that that cache diffs are un-reversed

echo
echo "Editing commit: $message" >&2
echo
7
Tom Hale

Venu à cette approche (et c'est probablement exactement la même chose que d'utiliser rebase interactive) mais pour moi c'est un peu simple.

Remarque: je présente cette approche pour illustrer ce que vous pouvez faire plutôt que comme une alternative de tous les jours. Puisqu'il a plusieurs étapes (et éventuellement quelques mises en garde.)

Supposons que vous souhaitiez modifier la validation 0 et que vous êtes actuellement sur feature-branch.

some-commit---0---1---2---(feature-branch)HEAD

Commander à cette validation et créer un quick-branch. Vous pouvez également cloner votre branche d'objet en tant que point de récupération (avant de commencer).

?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch

Vous allez maintenant avoir quelque chose comme ça:

0(quick-branch)HEAD---1---2---(feature-branch)

Les changements de scène, cachent tout le reste.

git add ./example.txt
git stash

Validez les modifications et revenez à feature-branch

git commit --amend
git checkout feature-branch

Vous allez maintenant avoir quelque chose comme ça:

some-commit---0---1---2---(feature-branch)HEAD
           \
             ---0'(quick-branch)

Rebasez feature-branch sur quick-branch (résolvez les conflits éventuels). Appliquez le stash et retirez quick-branch.

git rebase quick-branch
git stash pop
git branch -D quick-branch

Et vous vous retrouvez avec:

some-commit---0'---1'---2'---HEAD(feature-branch)

Git ne dupliquera pas (bien que je ne puisse pas vraiment dire dans quelle mesure) le commit 0 lors du changement de base.

Remarque: tous les hachages de validation sont modifiés à partir de la validation que nous avions initialement l'intention de modifier.

7
Olga

Pour obtenir une commande non interactive, placez un script avec ce contenu dans votre PATH:

#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"

Utilisez-le en enregistrant vos modifications (avec git add), puis exécutez git fixup <commit-to-modify>. Bien sûr, ce sera toujours interactif si vous avez des conflits.

6
Pelle Nilsson

J'ai résolu ça,

1) en créant un nouveau commit avec les changements que je veux ..

r8gs4r commit 0

2) Je sais quel commit j'ai besoin de fusionner avec elle. qui est commit 3.

donc, git rebase -i HEAD~4 # 4 représente la récente mise à jour 4 (ici la mise à jour 3 est à la 4ème place)

3) dans le rebase interactif, le commit récent sera situé en bas. ça va se ressembler,

pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0

4) ici, nous devons réorganiser le commit si vous voulez fusionner avec un spécifique. ça devrait être comme,

parent
|_child

pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1

après la réorganisation, vous devez remplacer ppick par f (correction fusionnera sans message de validation) ou s (squash la fusion avec un message de validation peut changer en temps d'exécution)

puis enregistrez votre arbre.

maintenant fusionner avec le commit existant.

Remarque: sa méthode n'est pas préférable, sauf si vous maintenez vous-même. Si vous avez une grande taille d’équipe, sa méthode de réécriture de git tree n’est pas acceptable. Si vous voulez maintenir votre arbre propre avec moins de modifications, vous pouvez essayer ceci et si sa petite équipe n'est pas préférable .....

4

git stash + rebase automation

Car quand j'ai besoin de modifier un vieux commit souvent pour les critiques de Gerrit, je le fais:

git-amend-old() (
  # Stash, apply to past commit, and rebase the current branch on to of the result.
  current_branch="$(git rev-parse --abbrev-ref HEAD)"
  apply_to="$1"
  git stash
  git checkout "$apply_to"
  git stash apply
  git add -u
  git commit --amend --no-edit
  new_sha="$(git log --format="%H" -n 1)"
  git checkout "$current_branch"
  git rebase --onto "$new_sha" "$apply_to"
)

GitHub en amont .

Usage:

  • modifier le fichier
  • git-amend-old $old_sha

J'aime ceci sur --autosquash car il n'écrase pas d'autres corrections.

Pour moi, c'était pour supprimer certaines informations d'identification d'un dépôt. J'ai essayé de changer de base et j'ai rencontré une tonne de conflits apparemment sans lien au cours de mon parcours lorsque j'essayais de me baser - de continuer. N'essayez pas de vous rebaser, utilisez l'outil appelé BFG (brew install bfg) sur mac.

2
Pellet

Si vous n'avez pas encore poussé les commits, vous pouvez revenir à une validation précédente en utilisant git reset HEAD^[1,2,3,4...]

Par exemple

git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"

Oups, j'ai oublié d'ajouter file2 au premier commit ...

git reset HEAD^1 // because I only need to go back 1 commit

git add <file2>

Cela ajoutera file2 au premier commit.

0
rharvey

Eh bien, cette solution peut sembler très stupide, mais peut vous sauver dans certaines conditions.

Un de mes amis vient de tomber sur commettant accidentellement de très gros fichiers (quatre fichiers générés automatiquement, entre 3 Go et 5 Go chacun), puis a créé du code supplémentaire. s’engage en plus de cela avant de réaliser le problème que git Push ne fonctionnait plus!

Les fichiers avaient été répertoriés dans .gitignore, mais après avoir renommé le dossier du conteneur, ils ont été exposés et validés! Et maintenant, il y avait quelques modifications supplémentaires du code en plus de cela, mais Push fonctionnait indéfiniment (essayer de télécharger des Go de données!) Et échouerait finalement à cause de taille limite du fichier de Github =.

Le problème avec rebase interactive ou quelque chose de similaire était qu'ils s'occuperaient de fouiller autour de ces énormes fichiers et prendraient une éternité pour faire quoi que ce soit. Néanmoins, après avoir passé près d'une heure dans l'interface de ligne de commande, nous ne savions pas si les fichiers (et les deltas) étaient réellement supprimés de l'historique ou tout simplement non inclus dans les commits actuels. La Push ne fonctionnait pas non plus et mon ami était vraiment coincé.

Donc, la solution que j'ai trouvée était:

  1. Renommez le dossier git actuel en ~/Project-old.
  2. Clone à nouveau le dossier git depuis github (dans ~/Project).
  3. Passer à la même succursale.
  4. Manuellement cp -r les fichiers du dossier ~/Project-old vers ~/Project.
  5. Assurez-vous que les fichiers volumineux qu'il n'est pas nécessaire d'archiver sont mved et qu'ils sont inclus correctement dans .gitignore.
  6. Assurez-vous également de ne pas écraser le dossier .git du ~/Project récemment cloné par l'ancien. C'est là que résident les journaux de l'histoire problématique!
  7. Maintenant, examinez les modifications. Ce devrait être l'union de tous les commits récents, à l'exclusion des fichiers problématiques.
  8. Enfin, validez les modifications et il est bon d’être Push 'ed.

Le principal problème de cette solution est qu’il s’agit de copier manuellement certains fichiers et de fusionner tous les commits récents en un seul (évidemment avec un nouveau commit-hash.) B

Les gros avantages sont que, à chaque étape, il est très clair ( que cela fonctionne très bien pour les gros fichiers (ainsi que les fichiers sensibles) , et cela ne fonctionne pas. Ne laissez aucune trace dans l’histoire!

0
Aidin