Come schiacciare tutto git si impegna in uno?


481

Come comprimere l'intero repository fino al primo commit?

Posso tornare al primo commit, ma questo mi lascerebbe con 2 commit. C'è un modo per fare riferimento al commit prima del primo?


8
"il commit prima del primo"?
innaM

31
@innaM - È l' impegno primordiale che ha generato l'intuizione . (Spera che l'umorismo passi abbastanza bene attraverso l'interweb).
ripper234

11
Per coloro che verranno dopo a questa domanda, assicurati di utilizzare la risposta più moderna .
Droogans,

1
Correlato, ma non un duplicato (in --rootrealtà non è la soluzione migliore per eliminare tutti i commit se ce ne sono molti da eliminare): Combina i primi due commit di un repository Git? .

2
IMO: questa è la migliore da @MrTux: stackoverflow.com/questions/30236694/... .
J0hnG4lt

Risposte:


129

Forse il modo più semplice è semplicemente creare un nuovo repository con lo stato corrente della copia di lavoro. Se vuoi conservare tutti i messaggi di commit che potresti fare prima git log > original.loge poi modificarli per il tuo messaggio di commit iniziale nel nuovo repository:

rm -rf .git
git init
git add .
git commit

o

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log

62
ma stai perdendo rami con questo metodo
Olivier Refalo

150
Git si è evoluto da quando è stata data questa risposta. No, non c'è un modo più semplice e migliore: git rebase -i --root. Vedi: stackoverflow.com/a/9254257/109618
David J.

5
Questo potrebbe funzionare in alcuni casi, ma essenzialmente non è la risposta alla domanda. Con questa ricetta, perdi tutte le tue configurazioni e anche tutti gli altri rami.
iwein

3
Questa è una soluzione orribile che è inutilmente distruttiva. Per favore, non usarlo.
Daniel Kamil Kozar,

5
inoltre romperà i sottomoduli. -1
Krum,

694

A partire da git 1.6.2 , puoi usare git rebase --root -i.

Per ogni commit tranne il primo, passare picka squash.


49
Aggiungi un esempio di comando completo e funzionante che risponda alla domanda originale.
Jake,

29
Vorrei averlo letto prima di spazzare via tutto il mio repository come dice la risposta accettata: /
Mike Chamberlain,

38
Questa risposta è ok , ma se stai interagendo in modo interattivo più di, diciamo, 20 commit, il rebase interattivo sarà probabilmente troppo lento e ingombrante. Probabilmente avrai difficoltà a cercare di schiacciare centinaia o migliaia di commit. Vorrei andare con un ripristino soft o misto al commit di root, quindi ricominciare, in quel caso.

20
@Pred Non utilizzare squashper tutti i commit. Il primo deve essere pick.
Geert,

14
Se hai molti commit è difficile cambiare manualmente 'pick' in 'squash'. Usa :% s / pick / squash / g nella riga di comando VIM per farlo più velocemente.
eilas,

315

Aggiornare

Ho fatto un alias git squash-all.
Esempio di utilizzo : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Avvertimento : ricordarsi di fornire un commento, altrimenti verrebbe utilizzato il messaggio di commit predefinito "Un nuovo inizio".

Oppure puoi creare l'alias con il seguente comando:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

Una fodera

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Nota : qui "A new start " è solo un esempio, sentiti libero di usare la tua lingua.

TL; DR

Non c'è bisogno di schiacciare, usare git commit-tree per creare un commit orfano e seguirlo.

Spiegare

  1. creare un unico commit tramite git commit-tree

    Quello che git commit-tree HEAD^{tree} -m "A new start"fa è:

    Crea un nuovo oggetto commit basato sull'oggetto albero fornito ed emette il nuovo ID oggetto commit su stdout. Il messaggio di registro viene letto dall'input standard, a meno che non vengano fornite le opzioni -m o -F.

    L'espressione HEAD^{tree}indica l'oggetto albero corrispondente HEAD, ovvero la punta del ramo corrente. vedi Tree-Objects e Commit-Objects .

  2. reimpostare il ramo corrente sul nuovo commit

    Quindi git resetreimpostare semplicemente il ramo corrente sull'oggetto commit appena creato.

In questo modo, non viene toccato nulla nell'area di lavoro, né è necessario rebase / squash, il che lo rende davvero veloce. E il tempo necessario è irrilevante per le dimensioni del repository o la profondità della cronologia.

Variazione: nuovo repo da un modello di progetto

Ciò è utile per creare il "commit iniziale" in un nuovo progetto usando un altro repository come template / archetype / seed / skeleton. Per esempio:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Ciò evita di aggiungere il repository di modelli come remoto ( origino in altro modo) e comprime la cronologia del repository di modelli nel commit iniziale.


6
La sintassi di revisione git (HEAD ^ {tree}) è spiegata qui nel caso qualcuno si stesse chiedendo: jk.gs/gitrevisions.html
Colin Bowern,

1
Questo reimposta il repository locale e remoto o solo uno di essi?
aleclarson,

4
@aleclarson, reimposta solo il ramo corrente nel repository locale, da utilizzare git push -fper la propagazione.
reno il

2
Ho trovato questa risposta mentre cercavo un modo per iniziare un nuovo progetto da un repository di modelli di progetto che non coinvolgesse git clone. Se si aggiunge --harda git resete interruttore HEADcon FETCH_HEADin git commit-treeè possibile creare un primo commit dopo il recupero repo modello. Ho modificato la risposta con una sezione alla fine che lo dimostra.
toolbear,

4
Potresti sbarazzarti di quel "Caveat" ma semplicemente usando${1?Please enter a message}
Elliot Cameron il

172

Se tutto ciò che vuoi fare è schiacciare tutti i tuoi commit fino al commit root, allora while

git rebase --interactive --root

può funzionare, non è pratico per un gran numero di commit (ad esempio, centinaia di commit), poiché l'operazione di rebase probabilmente verrà eseguita molto lentamente per generare l'elenco di commit dell'editor di rebase interattivo, nonché eseguire lo stesso rebase.

Ecco due soluzioni più rapide ed efficienti quando si eliminano un gran numero di commit:

Soluzione alternativa n. 1: rami orfani

Puoi semplicemente creare un nuovo ramo orfano sulla punta (ovvero il commit più recente) del tuo ramo attuale. Questo ramo orfano costituisce il commit radice iniziale di un albero della cronologia del commit completamente nuovo e separato, che è effettivamente equivalente a eliminare tutti i tuoi commit:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Documentazione:

Soluzione alternativa n. 2: soft reset

Un'altra soluzione efficiente è semplicemente utilizzare un reset misto o soft per il commit root <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Documentazione:


22
Soluzione alternativa n. 1: rami orfani - rocce!
Thomas,

8
Soluzione alternativa n. 1 FTW. Solo per aggiungere, se si desidera inviare le modifiche al telecomando, fare git push origin master --force.
Eddy Verbruggen,

1
non dovrebbe dimenticaregit push --force
NecipAllef,

Non fare una diramazione orfana sul codice (ovvero non fare la soluzione alternativa n. 1 sopra) se stai per inviare una richiesta pull di Github aperta !!! Github chiuderà il tuo PR perché la testa attuale non è un discendente della testa memorizzata sha .
Andrew Mackie,

La soluzione alternativa n. 1 evita anche i conflitti di unione che possono verificarsi durante il commit della
compressione

52
echo "message" | git commit-tree HEAD^{tree}

Questo creerà un commit orfano con l'albero di HEAD e ne produrrà il nome (SHA-1) su stdout. Quindi resetta il tuo ramo lì.

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")renderebbe più facile.
reno il

4
^ QUESTO! - dovrebbe essere una risposta. Non sono del tutto sicuro se fosse l'intenzione dell'autore, ma era mia (aveva bisogno di un repository incontaminato con un unico commit, e questo ha portato a termine il lavoro).
Chester

@ryenus, la tua soluzione ha fatto esattamente quello che stavo cercando. Se aggiungi il tuo commento come risposta, lo accetterò.
tldr,

2
Il motivo per cui non ho suggerito la variante subshell da solo, è che non funzionerà su cmd.exe in Windows.
kusma,

Nei prompt di Windows, potresti dover citare l'ultimo parametro: echo "message" | git commit-tree "HEAD^{tree}"
Bernard,

41

Ecco come ho finito per farlo, nel caso in cui funzioni per qualcun altro:

Ricorda che c'è sempre il rischio di fare cose del genere, e non è mai una cattiva idea creare un ramo di salvataggio prima di iniziare.

Inizia registrando

git log --oneline

Scorri fino al primo commit, copia SHA

git reset --soft <#sha#>

Sostituisci <#sha#>con SHA copiato dal registro

git status

Assicurati che tutto sia verde, altrimenti corri git add -A

git commit --amend

Modifica tutte le modifiche correnti al primo commit corrente

Adesso forza spingi questo ramo e sovrascriverà quello che c'è.


1
Opzione eccellente! Davvero semplice.
volte il

1
Questo è più che fantastico! Grazie!
Matt Komarnicki,

1
Si noti che questo sembra effettivamente lasciare la storia in giro. L'hai lasciato orfano, ma è ancora lì.
Brad

Risposta molto utile ... ma dovresti essere consapevole che dopo il comando di modifica ti troverai nell'editor vim con la sua sintassi speciale. ESC, ENTER,: x è tuo amico.
Erich Kuester,

Opzione eccellente!
danivicario,

36

Ho letto qualcosa sull'uso degli innesti, ma non ho mai studiato molto.

Ad ogni modo, puoi eliminare manualmente gli ultimi 2 commit con qualcosa del genere:

git reset HEAD~1
git add -A
git commit --amend

4
Questa è in realtà la risposta che stavo cercando, vorrei che fosse quella accettata!
Jay,

Questa è una risposta così sorprendente
Maestro Yoda del

36

Il modo più semplice è usare il comando 'idraulico' update-ref per eliminare il ramo corrente.

Non puoi usare git branch -D in quanto ha una valvola di sicurezza per impedirti di eliminare il ramo corrente.

Questo ti riporta allo stato di "commit iniziale" dove puoi iniziare con un nuovo commit iniziale.

git update-ref -d refs/heads/master
git commit -m "New initial commit"


15

In una riga di 6 parole

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills, si dovrebbe leggere git help checkoutsu--orphan
KYB

puoi collegarti ai documenti per questo in modo che chiunque legga questo non debba cercarlo manualmente?
Alexander Mills,

1
facile. eccolo quigit help checkout --orphan
kyb

8

creare un backup

git branch backup

ripristina al commit specificato

git reset --soft <#root>

quindi aggiungere tutti i file alla stadiazione

git add .

commit senza aggiornare il messaggio

git commit --amend --no-edit

spingere il nuovo ramo con schiacciato si impegna a repo

git push -f

Ciò manterrà i precedenti messaggi di commit?
not2qubit

1
@ not2qubit no questo non manterrà i precedenti messaggi di commit, invece di commit n. 1, commit n. 2, commit n. 3, otterrai tutte le modifiche in quei commit impacchettati in un singolo commit n. 1. Il <root>commit n. 1 sarà il commit a cui è stato reimpostato. git commit --amend --no-editeseguirà il commit di tutte le modifiche al commit corrente che non è <root>necessario modificare il messaggio di commit.
David Morton,

5

Per schiacciare usando innesti

Aggiungi un file .git/info/grafts, inserisci lì l'hash di commit che vuoi diventare il tuo root

git log inizierà ora da quel commit

Per renderlo "reale" git filter-branch


1

Questa risposta migliora su una coppia sopra (per favore votali), supponendo che oltre a creare un commit (no-genitori no-history), tu desideri anche conservare tutti i dati di commit di quel commit:

  • Autore (nome ed e-mail)
  • Data di creazione
  • Commiter (nome ed e-mail)
  • Data impegnata
  • Conferma messaggio di registro

Naturalmente il commit-SHA del nuovo / singolo commit cambierà, perché rappresenta una nuova (non) storia, diventando un parentless / root-commit.

Questo può essere fatto leggendo git loge impostando alcune variabili per git commit-tree. Supponendo che si desidera creare un singolo commit da masterin un nuovo ramo one-commit, mantenendo i dati di commit sopra:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')

1

Per fare ciò, puoi reimpostare il tuo repository git locale sul primo hashtag di commit, quindi tutte le modifiche dopo quel commit non saranno messe in scena, quindi puoi eseguire il commit con l'opzione --amend.

git reset your-first-commit-hashtag
git add .
git commit --amend

Quindi, se necessario, modifica il primo nome di commit e salva il file.


1

Per me ha funzionato così: avevo 4 commit in totale e ho usato il rebase interattivo:

git rebase -i HEAD~3

Il primo commit rimane e ho preso 3 ultimi commit.

Nel caso in cui sei bloccato nell'editor che appare dopo, vedi smth come:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Devi prendere il primo impegno e schiacciarne altri. Quello che dovresti avere è:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

Per questo usa il tasto INSERT per cambiare la modalità 'insert' e 'edit'.

Per salvare ed uscire dall'editor utilizzare :wq. Se il cursore si trova tra quelle righe di commit o altrove, premere ESC e riprovare.

Di conseguenza ho avuto due commit: il primo rimasto e il secondo con il messaggio "Questa è una combinazione di 3 commit".

Controlla i dettagli qui: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


0

Di solito lo faccio in questo modo:

  • Assicurarsi che tutto sia impegnato e annotare l'id di commit più recente nel caso in cui qualcosa vada storto o creare un ramo separato come backup

  • Corri git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`per ripristinare la testa al primo commit, ma lascia invariato l'indice. Tutte le modifiche dal primo commit ora appariranno pronte per essere impegnate.

  • Esegui git commit --amend -m "initial commit"per modificare il commit al primo commit e modificare il messaggio di commit oppure, se si desidera mantenere il messaggio di commit esistente, è possibile eseguiregit commit --amend --no-edit

  • Corri git push -fper forzare a spingere le tue modifiche

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.