Schiaccia il mio ultimo X insieme con Git


3590

Come posso comprimere i miei ultimi X commit in un commit usando Git?




2
@matt TortoiseGit è il tuo strumento. Fornisce una singola funzione "Combina a un commit" che chiamerà automaticamente tutti i passaggi in background. Purtroppo disponibile solo per Windows. Vedi la mia risposta qui sotto.
Matthias M,

1
Per schiacciamento fino a IL primo commit vedere questo - stackoverflow.com/questions/1657017/...
goelakash

1
postare zucca una necessità di fare forza di spinta stackoverflow.com/questions/10298291/...
vikramvi

Risposte:


2091

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.


260
Questo, penso, risponde a questa domanda un po 'meglio stackoverflow.com/a/5201642/295797
Roy Truelove

92
Cosa si intende per <after-this-commit>?
2540625,

43
<after-this-commit>è il commit X + 1, ovvero il genitore del commit più vecchio che vuoi schiacciare.
joozek,

340
Ho trovato questa risposta troppo concisa per capire senza ambiguità. Un esempio avrebbe aiutato.
Ian Ollmann,

54
La differenza tra questo 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.
zionyx,

3833

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").


163
Ha! Mi piace questo metodo È quello che si chiude allo spirito del problema. È un peccato che richiede così tanto voodoo. Qualcosa del genere dovrebbe essere aggiunto a uno dei comandi di base. Forse git rebase --squash-recent, o addirittura git commit --amend-many.
Adrian Ratnapala,

13
@ABB: se il tuo ramo ha un set “upstream”, allora potresti essere in grado di usare 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.
Chris Johnsen,

104
Questo mi ha richiesto, push -fma per il resto è stato adorabile, grazie.
ore 2

39
@ 2rs2ts git push -f suono pericoloso. Fai attenzione a schiacciare solo gli impegni locali. Non toccare mai commit spinti!
Matthias M,

20
Ho anche bisogno di utilizzare in git push --forceseguito in modo che ci voglia il commit
Zach Saucier

740

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.


16
Dici che è più "elegante" di 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?
Mark Amery,

77
@Mark Amery: ci sono vari motivi per cui ho detto che è più elegante. Ad esempio, non comporta la generazione inutile di un editor, la ricerca e la sostituzione di una stringa nel file "da fare". L'uso 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.
Mark Longair,

12
Un altro vantaggio è che 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!)
Cheezmeister,

2
Sono sempre molto riluttante quando si tratta di reimpostazioni rigide - utilizzerei un tag temporale invece di 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.
Tobias Kienzler,

8
@BT: distrutto il tuo impegno? :( Non sono sicuro di cosa tu voglia dire. Qualunque cosa tu abbia commesso sarai in grado di tornare facilmente dal reflog di git. Se avessi un lavoro non confermato, ma i file fossero messi in scena, dovresti comunque essere in grado di ottenere i loro contenuti, anche se questo sarà più lavoro . Se il tuo lavoro non è stato nemmeno messo in scena, tuttavia, temo che ci sia poco da fare; ecco perché la risposta dice in anticipo: "Prima controlla che lo stato git sia pulito (da git reset --hard getterà via le modifiche in scena e non in scena) ".
Mark Longair,

218

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 ...

  1. Metti i commit da schiacciare su un ramo funzionante (se non lo sono già) - usa gitk per questo
  2. Controlla il ramo di destinazione (ad es. "Master")
  3. git merge --squash (working branch name)
  4. git commit

Il messaggio di commit verrà prepopolato in base alla zucca.


4
Questo è il metodo più sicuro: nessun reset soft / hard (!!) o reflog usato!
TeChn4K,

14
Sarebbe bello se ti espandessi su (1).
Adam,

2
@Adam: Fondamentalmente, questo significa usare l'interfaccia della GUI 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.
nobar,

3
Si noti che questo metodo non contrassegna il ramo di lavoro come completamente unito, quindi rimuoverlo richiede la forzatura della cancellazione. :(
Kyrstellaine,

2
Per (1), ho trovato 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.
eis,

134

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.)


6
Interessante, ma preferirei digitare il messaggio di commit schiacciato da solo, come un riepilogo descrittivo dei miei commit multipli, piuttosto che averlo inserito automaticamente per me. Quindi preferirei specificare git squash -m "New summary."e Ndeterminare automaticamente il numero di commit non compressi.
Acumenus,

1
@ABB, sembra una domanda separata. (Non credo sia esattamente ciò che l'OP stava chiedendo; non ne ho mai sentito il bisogno nel mio flusso di lavoro di git squash.)
EthanB

3
Questo è piuttosto dolce. Personalmente vorrei una versione che utilizza il messaggio di commit dal primo dei commit compressi. Sarebbe buono per cose come le modifiche agli spazi bianchi.
funroll

@funroll Concordato. Lasciare l'ultimo messaggio di commit è un'esigenza super comune per me. Dovremmo essere in grado di escogitare che ...
Steve Clay,

2
@ABB è possibile utilizzare git commit --amendper modificare ulteriormente il messaggio, ma questo alias consente di iniziare bene ciò che dovrebbe essere nel messaggio di commit.
dashesy,

131

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.

editor interattivo di rebase


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!

inserisci qui la descrizione dell'immagine


5
devi scegliere quello in alto e schiacciare il resto? Dovresti modificare la tua risposta per spiegare come usare l'editor di rebase interattivo in modo più dettagliato
Kolob Canyon

2
Sì, lasciare 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'.".
Br3nt,

56

Se usi TortoiseGit, puoi la funzione Combine to one commit:

  1. Apri il menu contestuale di TortoiseGit
  2. Selezionare Show Log
  3. Contrassegna i commit rilevanti nella vista registro
  4. Seleziona Combine to one commitdal menu contestuale

Combina commit

Questa funzione esegue automaticamente tutti i passaggi singoli necessari. Purtroppo disponibile solo per Windows.


Per quanto ne so, questo non funzionerà per i commit di merge.
Thorkil Holm-Jacobsen,

1
Sebbene non sia commentato da nessun altro, questo funziona anche per i commit che non sono in testa. Ad esempio, il mio bisogno era di eliminare alcuni commit WIP che ho fatto con una descrizione più sana prima di spingere. Ha funzionato magnificamente. Certo, spero ancora di poter imparare a farlo con i comandi.
Charles Roberto Canato,

55

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


48

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'

1
Proprio quello di cui avevo bisogno. Annullamento degli commit dal mio ramo delle funzioni e quindi seleziono Cherry che si impegna nel mio master.
David Victor,

1
Questo non schiaccia i precedenti commit!
IgorGanapolsky,

potresti approfondire ulteriormente @igorGanapolsky?
trudolf,

3
@trudolf Questo non è davvero schiacciante (la selezione di singoli impegni per schiacciare). Si tratta più di eseguire tutte le modifiche contemporaneamente.
IgorGanapolsky il

15
sì, quindi comprime tutti i tuoi commit in uno solo. Congratulazioni!
trudolf,

45

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:

p, pick = usa commit

s, squash = usa il commit, ma si fonde nel commit precedente

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


2
Per favore, spiega anche cosa fa --continuee vim :x.
not2qubit

Il rebase avverrà in blocchi mentre attraversa i commit sul tuo ramo, dopo che hai 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
aabiro

32

La risposta di Anomies è buona, ma mi sono sentito insicuro al riguardo, quindi ho deciso di aggiungere un paio di schermate.

Passaggio 0: registro git

Vedi dove sei git log. Ancora più importante, trova l'hash di commit del primo commit che non vuoi schiacciare. Quindi solo il:

inserisci qui la descrizione dell'immagine

Passaggio 1: git rebase

Eseguire git rebase -i [your hash], nel mio caso:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Step 2: scegli / schiaccia quello che vuoi

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:

inserisci qui la descrizione dell'immagine

Passaggio 3: regola i messaggi

Se hai selezionato solo un commit e hai eliminato il resto, puoi modificare un messaggio di commit:

inserisci qui la descrizione dell'immagine

Questo è tutto. Una volta salvato questo ( :wq), il gioco è fatto. Dai un'occhiata con git log.


2
sarebbe bello vedere il risultato finale, ad esgit log
Timothy LJ Stewart il

2
No grazie. Questo è esattamente il modo in cui Iost mi impegna.
Axalix

@Axalix Hai rimosso tutte le tue linee? Ecco come perdi i tuoi impegni.
a3y3,

31

Procedura 1

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.

Procedura 2

  1. Per prima cosa aggiungi i file richiesti per il commit
git add <files>
  1. Quindi commetti usando l' --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>.

  1. Quindi eseguire il comando seguente per unire (schiacciare) il nuovo commit in 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.

Procedura 3

  1. Se è necessario aggiungere nuove modifiche all'ultimo commit, è --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

Conclusione

  • Il vantaggio della prima procedura è quello di eliminare più commit e riordinare. Ma questa procedura sarà difficile se avremo bisogno di unire una correzione a commit molto vecchio.
  • Quindi la seconda procedura aiuta a unire facilmente il commit a commit molto vecchio.
  • E la terza procedura è utile in un caso per schiacciare una nuova modifica per l'ultimo commit.

Aggiorna solo gli ultimi due commit anche se ho ripristinato un ID commit al sesto ultimo commit, non so perché
Carlos Liu,

Anche tu puoi riorganizzare l'ordine di commit. Funziona bene
rashok,

29

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

--force è pericoloso quando più persone lavorano su un ramo condiviso poiché si aggiorna ciecamente in remoto con la propria copia locale. --force-with-lease avrebbe potuto essere migliore in quanto si assicura che il telecomando non abbia commessi da altri dall'ultima volta che l'hai preso.
bharath,

27

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:

  1. Dai un'occhiata al repo dorato

    git checkout golden_repo_name
    
  2. Crea un nuovo ramo da esso (repository dorato) come segue

    git checkout -b dev-branch
    
  3. Squash si fonde con il tuo ramo locale che hai già

    git merge --squash feature-branch
    
  4. Commetti le tue modifiche (questo sarà l'unico commit che va nel ramo dev)

    git commit -m "My feature complete"
    
  5. Invia il ramo al tuo repository locale

    git push origin dev-branch
    

Dal momento che stavo solo schiacciando ~ 100 commit (per sincronizzare i rami svn tramite git-svn), questo è molto più veloce rispetto al rebasing interattivo!
salvia,

1
Leggendo giù, vedo @ il commento di Chris, che è quello che ho usato per fare (rebase --soft ...) - peccato che StackOverflow non è più sta mettendo la risposta con centinaia di upvotes in alto ...
salvia

1
sono d'accordo con te @sage, speriamo che possano farlo in futuro
Sandesh Kumar,

Questo è il modo giusto L'approccio di rebase è buono, ma dovrebbe essere usato solo per lo squash come soluzione di ultima istanza.
Axalix,

20

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'

2
Questo. Perché non lo usano più persone? È molto più veloce della reimpostazione e della compressione dei singoli commit.
a3y3,

17

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 .


7
+1: è divertente e istruttivo, in quanto per me non è affatto ovvio che puoi mettere qualcosa di più complesso del nome di un programma nella variabile d'ambiente GIT_EDITOR.
Mark Longair,

16

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

15

2020 Soluzione semplice senza rebase:

git reset --soft HEAD~2

git commit -m "new commit message"

git push --force

2 significa che gli ultimi due commit verranno schiacciati. Puoi sostituirlo con qualsiasi numero


14

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.


12

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}

4
Sono stato assolutamente livido per la frustrazione per lo schiacciamento dei commit e per quanto sia stupidamente complicato - basta usare l'ultimo messaggio e schiacciarli tutti per un commit! Perché è così difficile ???? Questa fodera lo fa per me. Grazie dal profondo del mio cuore arrabbiato.
Locane,

12

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 è

  1. git reset --soft Head ~ 3 &&
  2. git commit
  3. git push origin (branch_name) --force

3
Fai solo attenzione, poiché se usi la forza non c'è modo di recuperare i precedenti commit da quando l'hai rimosso
Albert Ruelan,

12

⚠️ ATTENZIONE: "I miei ultimi X commit" potrebbero essere ambigui.

  (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:

  • Aggiungi Danny Kirwan
  • Aggiungi Christine Perfect
  • LA1974
  • Bill Clinton

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.

🐻 MORALE DELLA STORIA

  1. Trovare i file esatto che si desidera ottenere a , e git checkoutli
  2. Trova l'esatto impegno precedente che desideri mantenere nella storia e git reset --softquello
  3. Crea un git commitordito che si deforma direttamente dal al al

1
Questo è al 100% il modo più semplice per farlo. Se il tuo HEAD attuale è lo stato corretto che desideri, puoi saltare il primo.
Stan,

Questo è l'unico modo che conosco che consente di riscrivere la prima cronologia di commit.
Vadorequest,

9

Se non ti interessano i messaggi di commit degli commit intermedi, puoi usare

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend

7

Se stai lavorando con GitLab, puoi semplicemente fare clic sull'opzione Squash nella richiesta di unione come mostrato di seguito. Il messaggio di commit sarà il titolo della richiesta di unione.

inserisci qui la descrizione dell'immagine


6
git rebase -i HEAD^^

dove il numero di ^ è s

(in questo caso, schiaccia gli ultimi due commit)


6

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:

  1. 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";
  2. viene visualizzato l'editor che mostra l'elenco di commit che voglio unire. Ora vengono visualizzati in ordine inverso : il commit precedente è in primo piano. Segna come "squash" o "s" tutti i commit lì dentro tranne il primo / più vecchio : sarà usato come punto di partenza. Salva e chiudi l'editor;
  3. l'editor viene nuovamente visualizzato con un messaggio predefinito per il nuovo commit: modificalo in base alle tue esigenze, salva e chiudi. Squash completato!

Fonti e letture aggiuntive: # 1 , # 2 .


6

Che dire di una risposta alla domanda relativa a un flusso di lavoro come questo?

  1. molti commit locali, mescolati con più fusioni DA master ,
  2. finalmente una spinta al telecomando,
  3. PR e unisci a master per revisore . (Sì, sarebbe più facile per lo sviluppatore 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.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. modificare e impegnare molto localmente, unire il master regolarmente
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // mette in scena tutti i cambiamenti
  7. git commit 'one message to rule them all'
  8. git push
  9. Il revisore esegue le PR e si unisce al master.

Da molte opinioni mi piace il tuo approccio. È molto comodo e veloce
Artem Solovev,

5

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 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.)


Ho provato l'alias ma non sono sicuro che i sostituti di sed abbiano alcun effetto. Cosa dovrebbero fare?
raine,

Il primo sed scarica semplicemente la cronologia sulla console. Il secondo sed sostituisce tutto 'pick' con 'f' (correzione) e riscrive il file dell'editor sul posto (l'opzione -i). Quindi il secondo fa tutto il lavoro.
Ethan,

Hai ragione, contare il numero N di commit specifici è molto soggetto a errori. Mi ha rovinato diverse volte e ho perso ore a cercare di annullare il rebase.
IgorGanapolsky,

Ciao Ethan, vorrei sapere se questo flusso di lavoro nasconderà possibili conflitti sulla fusione. Quindi, per favore, considera se hai due rami master e slave. Se lo slave ha un conflitto con il master e usiamo git squash masterquando siamo controllati sullo slave. cosa accadrà? nasconderemo il conflitto?
Sergio Bilello,

@Sergio Questo è un caso di riscrittura della cronologia, quindi probabilmente avrai conflitti se commetti squash che sono già stati spinti, e quindi provi a fondere / rebase la versione schiacciata. (Alcuni casi banali potrebbero cavarsela.)
Ethan,

5

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.


4

1) git reset --soft HEAD ~ n

n - Numero del commit, necessità di schiacciare

2) git commit -m "nuovo messaggio di commit"

3) git push origin nome_directory --force

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.