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 -iapproccio ed reset --softè, rebase -imi permette di conservare l'autore del commit, mentre reset --softmi 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 rebaseo 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 -icon 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 -fma per il resto è stato adorabile, grazie.
git push --forceseguito in modo che ci voglia il commit
Puoi usarlo git merge --squashper 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 statussia pulito (poiché git reset --hardeliminerà 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' --squashopzione in modo più dettagliato.
Aggiornamento: l'unico vero vantaggio di questo metodo rispetto al più semplice git reset --soft HEAD~12 && git commitsuggerito 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 mergea fare una delle cose git rebasespecificamente 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 resetquando 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 commitIl messaggio di commit verrà prepopolato in base alla zucca.
gitkper 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~Nil 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 ~/.gitconfigdovrebbe 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 Ncommit, incluso.
Nota: il messaggio di commit risultante è una combinazione di tutti i commit compressi, in ordine. Se non sei soddisfatto, puoi sempre git commit --amendmodificarlo manualmente. (Oppure, modifica l'alias in base ai tuoi gusti.)
git squash -m "New summary."e Ndeterminare automaticamente il numero di commit non compressi.
git commit --amendper 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~3durante 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 pickun comando a tua scelta.
Preferisco usare il comando fixuppoiché 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 squasho in fixupquanto 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 logo gitke 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 picka 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!
picknella riga 1. Se si sceglie squasho fixupper 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 LogCombine to one commitdal 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 pickun commit e squashgli 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
--continuee vim :x.
git addconfigurato correttamente i tuoi file che usi git rebase --continueper 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 --onelinepuò 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 nanoeditor per la fusione. E sembra sotto
....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....
4) Rinomina la parola picka squashcui è 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' nanoeditor. Premere ctrl + oe premere Enterper salvare. E quindi premere ctrl + xper uscire dall'editor.
6) Quindi l' nanoeditor 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 ubuntushell. Se si utilizzano diversi sistemi operativi ( Windowso Mac), i comandi sopra indicati sono gli stessi tranne l'editor. Potresti ottenere un editor diverso.
git add <files>
--fixupopzione e OLDCOMMITdovrebbe 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 rebasecomando 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.
--amendpossibile 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 --amendunisce le nuove modifiche all'ultimo commit cdababcde 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 --forcesu 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 lognon corrisponde a quello che vedi su GitHub.
git checkoutligit reset --softquellogit commitordito 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 -imi 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~5significherebbe "schiacciare gli ultimi 5 commit in uno nuovo";Che dire di una risposta alla domanda relativa a un flusso di lavoro come questo?
merge --squashdopo 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 rebasecorrettamente, più fusioni richiederebbero più risoluzioni di conflitto . NON voglio nemmeno pensarci!
Quindi, questo sembra funzionare per noi.
git pull mastergit checkout -b new-branchgit checkout -b new-branch-tempgit checkout new-branchgit merge --squash new-branch-temp // mette in scena tutti i cambiamentigit commit 'one message to rule them all'git pushTrovo 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 masternuovo 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' -iopzione sed GNU , gli utenti Mac e Linux dovrebbero andare bene con questo.)
git squash masterquando siamo controllati sullo slave. cosa accadrà? nasconderemo il conflitto?
In questione potrebbe essere ambiguo cosa si intende per "ultimo".
ad esempio git log --graphgenera 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.