Je soumets habituellement une liste de commits pour révision. Si j'ai les commits suivants:
HEAD
Commit3
Commit2
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
?
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
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:
git commit --amend
pour apporter des modifications, ougit 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.
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
.
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
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
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
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
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
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>
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
docsGIT_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 .Je me suis souvent trouvé en train de réparer un passé, et j'en ai écrit un script.
Voici le flux de travail:
git commit-edit <commit-hash>
Cela vous laissera tomber au commit que vous voulez éditer.
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)
Refaites le commit avec --amend
, par exemple:
git commit --amend
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
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.
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.
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 p
pick
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 .....
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"
)
Usage:
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.
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.
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:
~/Project-old
.~/Project
).cp -r
les fichiers du dossier ~/Project-old
vers ~/Project
.mv
ed et qu'ils sont inclus correctement dans .gitignore
..git
du ~/Project
récemment cloné par l'ancien. C'est là que résident les journaux de l'histoire problématique!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!