Come posso comprimere i miei ultimi X commit in un commit usando Git?
Come posso comprimere i miei ultimi X commit in un commit usando Git?
Risposte:
Utilizzare git rebase -i <after-this-commit>
e sostituire "pick" sul secondo e successivo commit con "squash" o "fixup", come descritto nel manuale .
In questo esempio, <after-this-commit>
è l'hash SHA1 o la posizione relativa dall'HEAD del ramo corrente da cui vengono analizzati i commit per il comando rebase. Ad esempio, se l'utente desidera visualizzare 5 commit dall'attuale HEAD in passato, il comando è git rebase -i HEAD~5
.
<after-this-commit>
?
<after-this-commit>
è il commit X + 1, ovvero il genitore del commit più vecchio che vuoi schiacciare.
rebase -i
approccio ed reset --soft
è, rebase -i
mi permette di conservare l'autore del commit, mentre reset --soft
mi consente di ricominciare. A volte ho bisogno di schiacciare i commit delle richieste pull mantenendo le informazioni sull'autore. A volte ho bisogno di reimpostare soft per i miei commit. Valorizza comunque entrambe le risposte.
Puoi farlo abbastanza facilmente senza git rebase
o git merge --squash
. In questo esempio, elimineremo gli ultimi 3 commit.
Se vuoi scrivere il nuovo messaggio di commit da zero, questo è sufficiente:
git reset --soft HEAD~3 &&
git commit
Se vuoi iniziare a modificare il nuovo messaggio di commit con una concatenazione dei messaggi di commit esistenti (cioè simile a quello git rebase -i
con cui inizierebbe un elenco di istruzioni pick / squash / squash /… / squash ), allora devi estrarre quei messaggi e passare loro a git commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
Entrambi questi metodi comprimono gli ultimi tre commit in un singolo nuovo commit allo stesso modo. Il soft reset reindirizza HEAD all'ultimo commit che non si desidera eliminare. Né l'indice né l'albero di lavoro vengono toccati dal soft reset, lasciando l'indice nello stato desiderato per il nuovo commit (ovvero ha già tutte le modifiche rispetto ai commit che si sta per "buttare via").
git rebase --squash-recent
, o addirittura git commit --amend-many
.
branch@{upstream}
(o solo @{upstream}
per il ramo corrente; in entrambi i casi, l'ultima parte può essere abbreviata in @{u}
; vedi gitrevisions ). Questo può differire dal tuo "ultimo commit push" (ad esempio se qualcun altro ha spinto qualcosa che ha costruito sopra la tua push più recente e poi l'hai recuperata), ma sembra che potrebbe essere vicino a quello che vuoi.
push -f
ma per il resto è stato adorabile, grazie.
git push --force
seguito in modo che ci voglia il commit
Puoi usarlo git merge --squash
per questo, che è leggermente più elegante di git rebase -i
. Supponiamo che tu sia il padrone e che tu voglia schiacciare gli ultimi 12 commit in uno.
ATTENZIONE: assicurati innanzitutto di impegnare il tuo lavoro, controlla che git status
sia pulito (poiché git reset --hard
eliminerà le modifiche graduali e non programmate)
Poi:
# 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 documentazione pergit merge
descrive l' --squash
opzione in modo più dettagliato.
Aggiornamento: l'unico vero vantaggio di questo metodo rispetto al più semplice git reset --soft HEAD~12 && git commit
suggerito da Chris Johnsen nella sua risposta è che ricevi il messaggio di commit prepopolato con ogni messaggio di commit che stai schiacciando.
git rebase -i
, ma non dai un motivo. Tentativo -1 di questo perché mi sembra che in realtà sia vero il contrario e questo è un trucco; non stai eseguendo più comandi del necessario solo per forzare git merge
a fare una delle cose git rebase
specificamente progettate per?
git merge --squash
è anche più facile da usare in uno script. In sostanza, il ragionamento era che per questo non è necessario "l'interattività" git rebase -i
.
git merge --squash
è meno probabile che produca conflitti di unione di fronte a mosse / eliminazioni / rinominazioni rispetto al rebasing, specialmente se si sta unendo da una filiale locale. (disclaimer: basato su una sola esperienza, correggimi se questo non è vero nel caso generale!)
HEAD@{1}
essere solo sul lato sicuro, ad esempio quando il flusso di lavoro viene interrotto per un'ora da un'interruzione di corrente, ecc.
Raccomando di evitare git reset
quando possibile, specialmente per i principianti Git. A meno che non sia davvero necessario automatizzare un processo basato su una serie di commit, esiste un modo meno esotico ...
git merge --squash (working branch name)
git commit
Il messaggio di commit verrà prepopolato in base alla zucca.
gitk
per etichettare la riga di codice che stai schiacciando e anche etichettare la base su cui schiacciare. Nel caso normale, entrambe queste etichette già esistono, quindi il passaggio (1) può essere ignorato.
git branch your-feature && git reset --hard HEAD~N
il modo più conveniente. Tuttavia, comporta nuovamente il reset di git, che questa risposta ha cercato di evitare.
Sulla base della risposta di Chris Johnsen ,
Aggiungi un alias globale "squash" da bash: (o Git Bash su 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'
... o usando il prompt dei comandi di 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"
Il tuo ~/.gitconfig
dovrebbe ora contenere questo alias:
[alias]
squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Uso:
git squash N
... Che comprime automaticamente gli ultimi N
commit, incluso.
Nota: il messaggio di commit risultante è una combinazione di tutti i commit compressi, in ordine. Se non sei soddisfatto, puoi sempre git commit --amend
modificarlo manualmente. (Oppure, modifica l'alias in base ai tuoi gusti.)
git squash -m "New summary."
e N
determinare automaticamente il numero di commit non compressi.
git commit --amend
per modificare ulteriormente il messaggio, ma questo alias consente di iniziare bene ciò che dovrebbe essere nel messaggio di commit.
Grazie a questo utile post sul blog ho scoperto che è possibile utilizzare questo comando per annullare gli ultimi 3 commit:
git rebase -i HEAD~3
Questo è utile in quanto funziona anche quando ci si trova in una filiale locale senza informazioni di tracciamento / repository remoto.
Il comando aprirà l'editor di rebase interattivo che ti permetterà quindi di riordinare, schiacciare, riformulare, ecc. Come al solito.
Utilizzando l'editor di rebase interattivo:
L'editor rebase interattivo mostra gli ultimi tre commit. Questo vincolo è stato determinato HEAD~3
durante l'esecuzione del comando git rebase -i HEAD~3
.
Il commit più recente HEAD
, viene visualizzato per primo sulla riga 1. Le righe che iniziano con a #
sono commenti / documentazione.
La documentazione visualizzata è abbastanza chiara. Su una data riga puoi cambiare il comando da pick
un comando a tua scelta.
Preferisco usare il comando fixup
poiché questo "schiaccia" le modifiche del commit nel commit sulla riga sopra e scarta il messaggio del commit.
Come è il commit sulla linea 1 HEAD
, nella maggior parte dei casi lo lasceresti come pick
. Non è possibile utilizzare squash
o in fixup
quanto non vi sono altri commit in cui annullare il commit.
È inoltre possibile modificare l'ordine dei commit. Ciò consente di annullare o eseguire commit di commit non cronologicamente adiacenti.
Un esempio pratico quotidiano
Ho recentemente impegnato una nuova funzionalità. Da allora, ho commesso due correzioni di bug. Ma ora ho scoperto un bug (o forse solo un errore di ortografia) nella nuova funzione che ho commesso. Che noioso! Non voglio un nuovo commit che inquina la mia cronologia dei commit!
La prima cosa che faccio è correggere l'errore e fare un nuovo commit con il commento squash this into my new feature!
.
Quindi eseguo git log
o gitk
e ottengo il commit SHA della nuova funzionalità (in questo caso 1ff9460
).
Successivamente, visualizzo l'editor di rebase interattivo con git rebase -i 1ff9460~
. Il ~
dopo il commit SHA dice l'editor di includere che commettono nell'editor.
Successivamente, sposto il commit contenente la fix ( fe7f1e0
) sotto il commit della funzione e cambio pick
a fixup
.
Quando si chiude l'editor, la correzione verrà schiacciata nella funzione di commit e la mia cronologia di commit sembrerà bella e pulita!
Funziona bene quando tutti i commit sono locali, ma se provi a cambiare i commit già inviati al telecomando puoi davvero causare problemi ad altri sviluppatori che hanno verificato lo stesso ramo!
pick
nella riga 1. Se si sceglie squash
o fixup
per il commit nella riga 1, git mostrerà un messaggio che dice "errore: impossibile 'riparare' senza un commit precedente". Quindi ti darà la possibilità di risolverlo: "Puoi risolvere questo problema con 'git rebase --edit-todo' e quindi eseguire 'git rebase --continue'." oppure puoi semplicemente interrompere e ricominciare da capo: "O puoi interrompere il rebase con 'git rebase --abort'.".
Se usi TortoiseGit, puoi la funzione Combine to one commit
:
Show Log
Combine to one commit
dal menu contestualeQuesta funzione esegue automaticamente tutti i passaggi singoli necessari. Purtroppo disponibile solo per Windows.
Per fare ciò puoi usare il seguente comando git.
git rebase -i HEAD~n
n (= 4 qui) è il numero dell'ultimo commit. Quindi hai le seguenti opzioni,
pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....
Aggiorna come sotto pick
un commit e squash
gli altri nel più recente,
p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....
Per i dettagli fare clic sul collegamento
Sulla base di questo articolo ho trovato questo metodo più semplice per il mio caso d'uso.
Il mio ramo "dev" era in anticipo rispetto a "origin / dev" di 96 commit (quindi questi commit non erano ancora stati inviati al telecomando).
Volevo schiacciare questi impegni in uno solo prima di spingere il cambiamento. Preferisco ripristinare il ramo allo stato di 'origine / sviluppo' (questo lascerà tutte le modifiche dai 96 commit non messi in scena) e quindi eseguirò il commit delle modifiche contemporaneamente:
git reset origin/dev
git add --all
git commit -m 'my commit message'
Nel ramo che desideri combinare i commit, esegui:
git rebase -i HEAD~(n number of commits back to review)
esempio:
git rebase -i HEAD~1
Questo aprirà l'editor di testo e devi cambiare 'pick' davanti a ogni commit con 'squash' se desideri che questi commit vengano uniti. Dalla documentazione:
Ad esempio, se stai cercando di unire tutti i commit in uno, il 'pick' è il primo commit che hai fatto e tutti quelli futuri (posizionati sotto il primo) dovrebbero essere impostati su 'squash'. Se si utilizza vim, utilizzare : x in modalità inserimento per salvare ed uscire dall'editor.
Quindi per continuare il rebase:
git rebase --continue
Per ulteriori informazioni su questo e altri modi per riscrivere la cronologia dei commit, consulta questo utile post
--continue
e vim :x
.
git add
configurato correttamente i tuoi file che usi git rebase --continue
per passare al commit successivo e iniziare a unire. :x
è un comando che salverà le modifiche del file quando si utilizza vim, vedere questo
La risposta di Anomies è buona, ma mi sono sentito insicuro al riguardo, quindi ho deciso di aggiungere un paio di schermate.
Vedi dove sei git log
. Ancora più importante, trova l'hash di commit del primo commit che non vuoi schiacciare. Quindi solo il:
Eseguire git rebase -i [your hash]
, nel mio caso:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
Nel mio caso, voglio schiacciare tutto sul commit che era il primo in tempo. L'ordinamento è dal primo all'ultimo, quindi esattamente nell'altro modo git log
. Nel mio caso, voglio:
Se hai selezionato solo un commit e hai eliminato il resto, puoi modificare un messaggio di commit:
Questo è tutto. Una volta salvato questo ( :wq
), il gioco è fatto. Dai un'occhiata con git log
.
git log
1) Identifica l'hash breve di commit
# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....
Anche qui git log --oneline
può essere utilizzato anche per ottenere hash brevi.
2) Se si desidera schiacciare (unire) gli ultimi due commit
# git rebase -i deab3412
3) Questo apre un nano
editor per la fusione. E sembra sotto
....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....
4) Rinomina la parola pick
a squash
cui è presente prima abcd1234
. Dopo la ridenominazione dovrebbe essere come di seguito.
....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....
5) Ora salva e chiudi l' nano
editor. Premere ctrl + o
e premere Enter
per salvare. E quindi premere ctrl + x
per uscire dall'editor.
6) Quindi l' nano
editor si apre nuovamente per l'aggiornamento dei commenti, se necessario aggiornarlo.
7) Ora viene schiacciato correttamente, è possibile verificarlo controllando i registri.
# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....
8) Ora spingere in repo. Nota per aggiungere il +
segno prima del nome del ramo. Questo significa spinta forzata.
# git push origin +master
Nota: si basa sull'uso di git on ubuntu
shell. Se si utilizzano diversi sistemi operativi ( Windows
o Mac
), i comandi sopra indicati sono gli stessi tranne l'editor. Potresti ottenere un editor diverso.
git add <files>
--fixup
opzione e OLDCOMMIT
dovrebbe essere su cui dobbiamo unire (schiacciare) questo commit.git commit --fixup=OLDCOMMIT
Ora questo crea un nuovo commit su HEAD con fixup1 <OLDCOMMIT_MSG>
.
OLDCOMMIT
.git rebase --interactive --autosquash OLDCOMMIT^
Qui ^
significa che il precedente è stato commesso OLDCOMMIT
. Questo rebase
comando apre una finestra interattiva su un editor (vim o nano) sul quale non è necessario fare nulla solo per salvare ed uscire è sufficiente. Poiché l'opzione passata a questo sposta automaticamente l'ultimo commit accanto al vecchio commit e cambia l'operazione in fixup
(equivalente a squash). Quindi rebase continua e finisce.
--amend
possibile utilizzare i mezzi git-commit
. # git log --pretty=oneline --abbrev-commit
cdababcd Fix issue B
deab3412 Fix issue A
....
# git add <files> # New changes
# git commit --amend
# git log --pretty=oneline --abbrev-commit
1d4ab2e1 Fix issue B
deab3412 Fix issue A
....
Qui --amend
unisce le nuove modifiche all'ultimo commit cdababcd
e genera un nuovo ID commit1d4ab2e1
Per schiacciare gli ultimi 10 commit in 1 singolo commit:
git reset --soft HEAD~10 && git commit -m "squashed commit"
Se vuoi anche aggiornare il ramo remoto con il commit schiacciato:
git push -f
Se sei su un ramo remoto (chiamato feature-branch
) clonato da un Golden Repository ( golden_repo_name
), allora ecco la tecnica per comprimere i tuoi commit in uno:
Dai un'occhiata al repo dorato
git checkout golden_repo_name
Crea un nuovo ramo da esso (repository dorato) come segue
git checkout -b dev-branch
Squash si fonde con il tuo ramo locale che hai già
git merge --squash feature-branch
Commetti le tue modifiche (questo sarà l'unico commit che va nel ramo dev)
git commit -m "My feature complete"
Invia il ramo al tuo repository locale
git push origin dev-branch
Cosa può essere davvero conveniente:
trova l'hash di commit che vuoi schiacciare sopra, diciamo d43e15
.
Adesso usa
git reset d43e15
git commit -am 'new commit name'
Questo è kludgy super-duper, ma in un modo un po 'cool, quindi lo getterò sul 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
Traduzione: fornisce un nuovo "editor" per git che, se il nome del file da modificare è git-rebase-todo
(il prompt di rebase interattivo) cambia tutto tranne il primo "pick" in "squash", e altrimenti genera vim - in modo che quando richiesto per modificare il messaggio di commit schiacciato, ottieni vim. (E ovviamente stavo schiacciando gli ultimi cinque commit sul ramo foo, ma potresti cambiarlo come preferisci.)
Probabilmente farei comunque quello che Mark Longair ha suggerito .
Se si desidera ridurre ogni commit in un singolo commit (ad esempio quando si rilascia un progetto pubblicamente per la prima volta), provare:
git checkout --orphan <new-branch>
git commit
Penso che il modo più semplice per farlo sia quello di creare un nuovo ramo fuori dal master e fare una fusione - squarcio del ramo delle caratteristiche.
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
Quindi hai tutte le modifiche pronte per il commit.
Semplice one-liner che funziona sempre, dato che sei attualmente sul ramo da cui vuoi schiacciare, master è il ramo da cui ha origine e l'ultimo commit contiene il messaggio di commit e l'autore che desideri utilizzare:
git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
se per esempio vuoi schiacciare gli ultimi 3 commit in un singolo commit in un ramo (repository remoto) ad esempio: https://bitbucket.org
Quello che ho fatto è
(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)
In questa storia molto abbreviata del repository https://github.com/fleetwood-mac/band-history hai aperto una richiesta pull per unire il commit di Bill Clinton nel commit originale ( MASTER
) di Fleetwood per Mac.
Hai aperto una richiesta pull e su GitHub vedi questo:
Quattro commit:
Pensando che a nessuno sarebbe mai importato leggere la storia completa del repository. (In realtà esiste un repository, fai clic sul link sopra!) Decidi di eliminare questi commit. Quindi vai e corri git reset --soft HEAD~4 && git commit
. Quindi lo fai git push --force
su GitHub per ripulire il tuo PR.
E cosa succede? Hai appena fatto un singolo commit che va da Fritz a Bill Clinton. Perché hai dimenticato che ieri stavi lavorando alla versione di Buckingham Nicks di questo progetto. E git log
non corrisponde a quello che vedi su GitHub.
git checkout
ligit reset --soft
quellogit commit
ordito che si deforma direttamente dal al algit rebase -i HEAD^^
dove il numero di ^ è s
(in questo caso, schiaccia gli ultimi due commit)
Oltre ad altre eccellenti risposte, vorrei aggiungere come git rebase -i
mi confonde sempre con l'ordine di commit, dal più vecchio al più recente o viceversa? Quindi questo è il mio flusso di lavoro:
git rebase -i HEAD~[N]
, dove N è il numero di commit a cui voglio partecipare, a partire da quello più recente . Quindi git rebase -i HEAD~5
significherebbe "schiacciare gli ultimi 5 commit in uno nuovo";Che dire di una risposta alla domanda relativa a un flusso di lavoro come questo?
merge --squash
dopo il PR, ma il team ha pensato che avrebbe rallentato il processo.)Non ho visto un flusso di lavoro simile in questa pagina. (Questi potrebbero essere i miei occhi.) Se capisco rebase
correttamente, più fusioni richiederebbero più risoluzioni di conflitto . NON voglio nemmeno pensarci!
Quindi, questo sembra funzionare per noi.
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
git checkout new-branch
git merge --squash new-branch-temp
// mette in scena tutti i cambiamentigit commit 'one message to rule them all'
git push
Trovo che una soluzione più generica non sia quella di specificare 'N' commit, ma piuttosto il branch / commit-id su cui vuoi schiacciare. Questo è meno soggetto ad errori rispetto al conteggio dei commit fino a un commit specifico: basta specificare direttamente il tag o, se si desidera davvero contare, è possibile specificare HEAD ~ N.
Nel mio flusso di lavoro, avvio una succursale e il mio primo commit su quella succursale sintetizza l'obiettivo (cioè di solito è quello che spingerò come messaggio 'finale' per la funzione al repository pubblico.) Quindi quando ho finito, tutto Voglio fare è di git squash master
nuovo al primo messaggio e poi sono pronto a spingere.
Uso l'alias:
squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
Questo eliminerà la cronologia che viene compressa prima di farlo — questo ti dà la possibilità di recuperare prendendo un vecchio ID commit dalla console se vuoi ripristinare. (Gli utenti di Solaris notano che utilizza l' -i
opzione sed GNU , gli utenti Mac e Linux dovrebbero andare bene con questo.)
git squash master
quando siamo controllati sullo slave. cosa accadrà? nasconderemo il conflitto?
In questione potrebbe essere ambiguo cosa si intende per "ultimo".
ad esempio git log --graph
genera quanto segue (semplificato):
* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| |
* | commit H1
| |
* | commit H2
|/
|
Quindi gli ultimi commit per ora sono H0, unione, B0. Per schiacciarli dovrai rifare il ramo unito su commit H1.
Il problema è che H0 contiene H1 e H2 (e generalmente più commit prima dell'unione e dopo la ramificazione) mentre B0 no. Quindi devi gestire le modifiche da H0, unire, H1, H2, B0 almeno.
È possibile utilizzare rebase ma in modo diverso rispetto alle risposte citate da altri:
rebase -i HEAD~2
Questo ti mostrerà le opzioni di scelta (come menzionato in altre risposte):
pick B1
pick B0
pick H0
Metti la zucca invece di scegliere su H0:
pick B1
pick B0
s H0
Dopo il salvataggio e l'uscita rebase verranno applicati i commit a turno dopo H1. Ciò significa che ti chiederà di risolvere nuovamente i conflitti (dove HEAD sarà inizialmente H1 e poi accumulerà i commit man mano che vengono applicati).
Al termine del rebase puoi scegliere il messaggio per H0 e B0 schiacciati:
* commit squashed H0 and B0
|
* commit B1
|
* commit H1
|
* commit H2
|
PS Se esegui solo un ripristino su BO: (ad esempio, l'utilizzo reset --mixed
è spiegato in modo più dettagliato qui https://stackoverflow.com/a/18690845/2405850 ):
git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'
quindi si schiacciano in B0 le modifiche di H0, H1, H2 (perdendo si impegna completamente per le modifiche dopo la ramificazione e prima dell'unione.