Comment puis-je réduire mes derniers commits X en un seul en utilisant Git?
Utilisez git rebase -i <after-this-commit>
et remplacez "pick" lors de la deuxième validation et des suivantes par "squash" ou "fixup", comme décrit dans le manuel .
Dans cet exemple, <after-this-commit>
est soit le hachage SHA1, soit l'emplacement relatif à partir de HEAD de la branche en cours à partir de laquelle les validations sont analysées pour la commande rebase. Par exemple, si l'utilisateur souhaite visualiser 5 validations à partir du HEAD actuel, la commande est git rebase -i HEAD~5
.
Vous pouvez le faire assez facilement sans git rebase
ni git merge --squash
. Dans cet exemple, nous allons écraser les 3 derniers commits.
Si vous voulez écrire le nouveau message de commit à partir de zéro, cela suffit:
git reset --soft HEAD~3 &&
git commit
Si vous souhaitez commencer à éditer le nouveau message de validation avec une concaténation des messages de validation existants (c'est-à-dire similaire à la liste d'instructions pick/squash/squash /…/squash git rebase -i
), vous devez extraire ces messages et transmettez-les à git commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
Ces deux méthodes réduisent les trois derniers commits en un seul nouveau de la même manière. La réinitialisation logicielle re-pointe simplement HEAD sur le dernier commit que vous ne voulez pas écraser. Ni l’index ni l’arbre de travail ne sont touchés par la réinitialisation logicielle, ce qui laisse l’indice dans l’état souhaité pour votre nouveau commit (c’est-à-dire qu’il contient déjà tous les changements des commit que vous êtes sur le point de «jeter»).
Vous pouvez utiliser git merge --squash
pour cela, qui est légèrement plus élégant que git rebase -i
. Supposons que vous soyez maître et que vous vouliez écraser les 12 derniers commits en un seul.
AVERTISSEMENT: Commencez par vous assurer que votre travail est bien validé. Vérifiez que git status
est propre (puisque git reset --hard
éliminera les modifications planifiées et non planifiées).
Ensuite:
# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12
# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}
# Commit those squashed changes. The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit
La documentation pour git merge
décrit l’option --squash
de manière plus détaillée.
Update: le seul avantage réel de cette méthode par rapport au git reset --soft HEAD~12 && git commit
plus simple suggéré par Chris Johnsen dans sa réponse est que le message de validation est pré-rempli avec chaque message de validation que vous écrasez.
Basé sur la réponse de Chris Johnsen ,
Ajouter un alias "squash" global de bash: (ou Git Bash sous Windows)
git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'
... ou en utilisant l'invite de commande de Windows:
git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Votre ~/.gitconfig
devrait maintenant contenir cet alias:
[alias]
squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Usage:
git squash N
... Ce qui écrase automatiquement les derniers N
commits inclusivement.
Remarque: le message de validation résultant est une combinaison de tous les validations écrasées, dans l'ordre. Si vous n'êtes pas satisfait de cela, vous pouvez toujours git commit --amend
pour le modifier manuellement. (Ou modifiez l'alias pour qu'il corresponde à vos goûts.)
Merci à cet article de blog pratique J'ai découvert que vous pouvez utiliser cette commande pour écraser les 3 derniers commits:
git rebase -i HEAD~3
C'est pratique car cela fonctionne même lorsque vous vous trouvez sur une branche locale sans information de suivi/référentiel à distance.
La commande ouvrira l'éditeur de rebase interactif qui vous permettra ensuite de réorganiser, squash, reformuler, etc. comme d'habitude.
Utilisation de l'éditeur de rebase interactif:
L'éditeur de rebase interactif affiche les trois derniers commits. Cette contrainte a été déterminée par HEAD~3
lors de l'exécution de la commande git rebase -i HEAD~3
.
La validation la plus récente, HEAD
, est affichée en premier sur la ligne 1. Les lignes commençant par #
sont des commentaires/de la documentation.
La documentation affichée est assez claire. Sur une ligne donnée, vous pouvez changer la commande de pick
en une commande de votre choix.
Je préfère utiliser la commande fixup
car ceci "écrase" les modifications du commit dans le commit de la ligne ci-dessus et supprime le message du commit.
Comme le commit de la ligne 1 est HEAD
, dans la plupart des cas, vous le laisseriez comme pick
. Vous ne pouvez pas utiliser squash
ou fixup
car il n’existe aucun autre commit pour écraser le commit.
Si vous utilisez TortoiseGit, vous pouvez utiliser la fonction Combine to one commit
:
Show Log
Combine to one commit
dans le menu contextuelCette fonction exécute automatiquement toutes les étapes nécessaires à un seul git.
Basé sur cet article J'ai trouvé cette méthode plus facile pour mon cas d'utilisation.
Ma branche "dev" devançait "Origin/dev" de 96 commits (ces commits n'étaient donc pas encore poussés vers la télécommande).
Je voulais écraser ces commits en un avant de pousser le changement. Je préfère réinitialiser la branche à l'état 'Origin/dev' (cela laissera toutes les modifications des 96 commits non mises en scène), puis les valider immédiatement
git reset Origin/dev
git add --all
git commit -m 'my commit message'
Pour ce faire, vous pouvez utiliser la commande suivante git.
git rebase -i HEAD~n
n (= 4 ici) est le numéro du dernier commit. Ensuite, vous avez les options suivantes,
pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....
Mettre à jour comme ci-dessous,
p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....
Pour plus de détails, cliquez sur Lien
Bonne chance!!
1) Identifier le hash court de commit
# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....
Ici, même git log --oneline
peut également être utilisé pour obtenir un hachage court.
2) Si vous voulez écraser (fusionner) les deux derniers commit
# git rebase -i deab3412
3) Ceci ouvre un éditeur nano
pour la fusion. Et on dirait ci-dessous
....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....
4) Renommez le mot pick
en squash
qui est présent avant abcd1234
. Après avoir renommé, cela devrait être comme ci-dessous.
....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....
5) Enregistrez et fermez l'éditeur nano
. Appuyez sur ctrl + o
et appuyez sur Enter
pour enregistrer. Et puis appuyez sur ctrl + x
pour quitter l'éditeur.
6) Ensuite, l'éditeur nano
s'ouvre à nouveau pour mettre à jour les commentaires, le cas échéant, mettez-le à jour.
7) Maintenant que sa compression est réussie, vous pouvez le vérifier en consultant les journaux.
# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....
8) Maintenant, appuyez sur repo. Remarque pour ajouter le signe +
avant le nom de la branche. Cela signifie poussée forcée.
# git Push Origin +master
Note: Ceci est basé sur l'utilisation de git sur ubuntu
Shell. Si vous utilisez différents systèmes d'exploitation (Windows
ou Mac
), les commandes ci-dessus sont identiques, à l'exception de editor. Vous pourriez obtenir un éditeur différent.
Anomies answer est bon, mais je ne me sentais pas en sécurité à ce sujet, alors j'ai décidé d'ajouter quelques captures d'écran.
Voyez où vous en êtes avec git log
. Le plus important est de trouver le hash de commit du premier commit que vous ne voulez pas écraser. Donc seulement le:
Exécutez git rebase -i [your hash]
, dans mon cas:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
Dans mon cas, je veux tout écraser sur le commit qui était le premier dans le temps. L'ordre est du premier au dernier, donc exactement comme dans git log
. Dans mon cas, je veux:
Si vous avez sélectionné un seul commit et réduit le reste, vous pouvez ajuster un message de commit:
C'est tout. Une fois que vous avez sauvegardé ceci (:wq
), vous avez terminé. Regardez-le avec git log
.
Si vous êtes sur une branche distante (appelée feature-branch
) clonée à partir d'un référentiel Golden (golden_repo_name
), voici la technique pour réduire vos commits en une:
Commander le golden repo
git checkout golden_repo_name
Créez une nouvelle branche à partir de celle-ci (golden repo) comme suit
git checkout -b dev-branch
Squash fusionner avec votre branche locale que vous avez déjà
git merge --squash feature-branch
Commit tes modifications (ce sera le seul commit qui ira dans dev-branch)
git commit -m "My feature complete"
Poussez la branche dans votre référentiel local
git Push Origin dev-branch
C'est super-duper kludgy, mais d'une manière cool, alors je vais juste le jeter dans le ring:
GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo
Traduction: fournissez un nouvel "éditeur" pour git qui, si le nom du fichier à modifier est git-rebase-todo
(l'invite de rebase interactive), modifie tout sauf le premier "choix" en "squash", et génère par ailleurs vim - invité à éditer le message de validation écrasé, vous obtenez vim. (Et évidemment, je réduisais les cinq derniers commits sur branch foo, mais vous pouvez changer cela à votre guise.)
Je ferais probablement ce que Mark Longair a suggéré , cependant.
Dans la branche sur laquelle vous souhaitez combiner les commits, exécutez:
git rebase -i HEAD~(n number of commits back to review)
Cela ouvrira l’éditeur de texte et vous devrez changer le "pick" devant chaque commit avec "squash" si vous souhaitez que ces commits soient fusionnés. Par exemple, si vous souhaitez fusionner tous les commits en un seul, le "choix" est le premier que vous avez effectué et tous les futurs (placés en dessous du premier) doivent être réglés sur "squash". Si vous utilisez vim, utilisez : x en mode insertion pour enregistrer et quitter l'éditeur.
Puis pour continuer la rebase:
git rebase --continue
Pour plus d'informations et pour savoir comment réécrire votre historique de validation, voir cet article utile
Ce qui peut être vraiment pratique:
Trouvez le hachage de commit que vous voulez écraser, dites d43e15
.
Maintenant utiliser
git reset d43e15
git commit -am 'new commit name'
Si vous voulez écraser tous les commit en un seul commit (par exemple, lors de la publication d'un projet pour la première fois), essayez
git checkout --Orphan <new-branch>
git commit
Je pense que le moyen le plus simple de procéder consiste à créer une nouvelle branche à partir de master et à effectuer une fusion de la branche de fonctionnalité.
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
Ensuite, vous avez toutes les modifications prêtes à être validées.
Pour écraser les 10 derniers commits en 1 seul:
git reset --soft HEAD~10 && git commit -m "squashed commit"
Si vous souhaitez également mettre à jour la branche distante avec la validation compressée:
git Push -f
git rebase -i HEAD^^
où le nombre de ^ est X
(dans ce cas, écrasez les deux derniers commits)
Je trouve une solution plus générique qui ne consiste pas à spécifier les "N" commits, mais plutôt la branche/l'ID de validation sur laquelle vous souhaitez écraser. Cela est moins sujet aux erreurs que de compter les validations jusqu'à une validation spécifique. Spécifiez simplement la balise directement ou, si vous voulez vraiment compter, vous pouvez spécifier HEAD ~ N.
Dans mon flux de travail, je commence une branche, et mon premier commit sur cette branche résume l'objectif (c'est-à-dire que c'est généralement ce que je pousserai comme message "final" pour la fonctionnalité dans le référentiel public.) Je veux faire est git squash master
retour au premier message et puis je suis prêt à pousser.
J'utilise l'alias:
squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
Cela va vider l'historique écrasé avant de le faire - cela vous donne une chance de récupérer en récupérant un ancien ID de commit sur la console si vous voulez revenir. (Les utilisateurs de Solaris notent qu'ils utilisent l'option GNU sed -i
. Les utilisateurs de Mac et de Linux devraient pouvoir s'en charger.)
En question, la signification de "dernier" pourrait être ambiguë.
par exemple, git log --graph
génère les éléments suivants (simplifiés):
* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| |
* | commit H1
| |
* | commit H2
|/
|
Les dernières validations par heure sont H0, fusion, B0. Pour les écraser, vous devrez rebaser votre branche fusionnée sur le commit H1.
Le problème est que H0 contient H1 et H2 (et généralement plus de validations avant la fusion et après le branchement), contrairement à B0. Vous devez donc gérer les modifications de H0, fusion, H1, H2, B0 au moins.
Il est possible d'utiliser rebase mais d'une manière différente que dans d'autres réponses mentionnées:
rebase -i HEAD~2
Cela vous montrera des options de choix (comme mentionné dans d'autres réponses):
pick B1
pick B0
pick H0
Mettez squash au lieu de pick sur H0:
pick B1
pick B0
s H0
Après l'enregistrement et la sortie, rebase appliquera les validations à tour de rôle après H1. Cela signifie qu'il vous demandera de résoudre à nouveau les conflits (où HEAD sera au début H1 puis accumule les commits au fur et à mesure qu'ils sont appliqués).
Une fois l’opération terminée, vous pouvez choisir un message pour H0 et B0 écrasés:
* commit squashed H0 and B0
|
* commit B1
|
* commit H1
|
* commit H2
|
P.S. Si vous ne faites que réinitialiser BO: .__ (par exemple, en utilisant reset --mixed
, expliqué plus en détail ici https://stackoverflow.com/a/18690845/2405850 ):
git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'
alors vous écrasez dans les changements B0 de H0, H1, H2 (perdre complètement les modifications après modification et avant la fusion.
En plus d’autres excellentes réponses, je voudrais ajouter comment git rebase -i
m’embrouille toujours avec l’ordre de livraison - du plus ancien au plus récent ou inversement? Voici donc mon flux de travail:
git rebase -i HEAD~[N]
, où N est le nombre de commits que je souhaite rejoindre, à partir du plus récent. Donc, git rebase -i HEAD~5
signifierait "écraser les 5 derniers commits dans un nouveau";Si vous ne vous souciez pas des messages de validation des validations intermédiaires, vous pouvez utiliser
git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend
(MASTER)
Fleetwood Mac Fritz
║ ║
Add Danny Lindsey Stevie
Kirwan Buckingham Nicks
║ ╚═══╦══════╝
Add Christine ║
Perfect Buckingham
║ Nicks
LA1974══════════╝
║
║
Bill <══════ YOU ARE EDITING HERE
Clinton (CHECKED OUT, CURRENT WORKING DIRECTORY)
Dans cette histoire très abrégée du https://github.com/fleetwood-mac/band-history repository, vous avez ouvert une demande d'extraction visant à fusionner le commit Bill Clinton dans le commit original (MASTER
) Fleetwood Mac .
Vous avez ouvert une demande de tirage et sur GitHub, vous voyez ceci:
Quatre commits:
En pensant que personne ne se soucierait jamais de lire l'historique complet du référentiel. (Il existe actuellement un référentiel, cliquez sur le lien ci-dessus!) Vous décidez d'écraser ces commits. Donc, vous allez courir git reset --soft HEAD~4 && git commit
. Ensuite, vous git Push --force
z-le sur GitHub pour nettoyer votre PR.
Et qu'est-ce qui se passe? Vous venez de faire un engagement unique qui va de Fritz à Bill Clinton. Parce que vous avez oublié qu'hier, vous travailliez sur la version Buckingham Nicks de ce projet. Et git log
ne correspond pas à ce que vous voyez sur GitHub.
git checkout
euxgit reset --soft
quigit commit
qui se déforme directement du du au auQu'en est-il d'une réponse à la question liée à un flux de travail comme celui-ci?
merge --squash
après le PR, mais l'équipe a pensé que cela ralentirait le processus.)Je n'ai pas vu un flux de travail comme celui-ci sur cette page. (C'est peut-être mon cas.) Si je comprends bien rebase
, plusieurs fusions nécessiteraient plusieurs résolutions de conflit . Je ne veux même pas y penser!
Donc, cela semble fonctionner pour nous.
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
git checkout new-branch
git merge --squash new-branch-temp
// met toutes les modifications en scènegit commit 'one message to rule them all'
git Push
Je découvre d’abord le nombre de commits entre ma branche principale et la branche principale actuelle en
git checkout master
git rev-list master.. --count
Ensuite, je crée une autre branche basée sur my-feature, conservez my-feature
branch intacte.
Enfin, je cours
git checkout my-feature
git checkout -b my-rebased-feature
git checkout master
git checkout my-rebased-feature
git rebase master
git rebase head^x -i
// fixup/pick/rewrite
git Push Origin my-rebased-feature -f // force, if my-rebased-feature was ever pushed, otherwise no need for -f flag
// make a PR with clean history, delete both my-feature and my-rebased-feature after merge
J'espère que ça aide, merci.
Une ligne simple qui fonctionne toujours, étant donné que vous êtes actuellement sur la branche que vous souhaitez écraser, maître est la branche dont elle provient, et le dernier commit contient le message de validation et l'auteur que vous souhaitez utiliser:
git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
Si vous utilisez GitUp , sélectionnez le commit que vous souhaitez fusionner avec son parent et appuyez sur S. Vous devez le faire une fois pour chaque commit, mais c'est beaucoup plus simple que de proposer la bonne incantation en ligne de commande. Surtout si c'est quelque chose que vous ne faites que de temps en temps.
si vous voulez par exemple réduire les 3 derniers commits en un seul commit dans une branche (référentiel distant), par exemple: https://bitbucket.org
Ce que j'ai fait c'est
Ajoutez simplement cette fonction bash à votre bash de fichier .zshrc.
# Squash last X commits with a Commit message.
# Usage: squash X 'COMMIT_MSG'
# where X= Number of last commits.
# where COMMIT_MSG= New commit msg.
function squash() {
if [ -z "${1}" -o -z "${2}" ]; then
echo "Usage: \`squash X COMMIT_MSG\`"
echo "X= Number of last commits."
echo "COMMIT_MSG= New commit msg."
return 1
fi
git reset --soft HEAD~"$1"
git add . && git ci -m "$2" # With 100 emoji
git Push --force
}
Alors viens courir
squash X 'New Commit Message'
Et tu as fini.