Introduzione: sono disponibili 5 soluzioni
Il poster originale afferma:
Ho accidentalmente impegnato un file indesiderato ... nel mio repository diversi commit fa ... Voglio eliminare completamente il file dalla cronologia del repository.
È possibile riscrivere la cronologia delle modifiche in modo che filename.orig
non sia mai stato aggiunto al repository?
Esistono molti modi diversi per rimuovere completamente la cronologia di un file da git:
- La modifica si impegna.
- Hard reset (possibilmente più un rebase).
- Reimpostazione non interattiva.
- Rebasi interattivi.
- Filtraggio dei rami.
Nel caso del poster originale, la modifica del commit non è in realtà un'opzione di per sé, poiché in seguito ha effettuato diversi commit aggiuntivi, ma per completezza, spiegherò anche come farlo, per chiunque altro voglia solo per modificare il loro precedente commit.
Si noti che tutte queste soluzioni comportano l' alterazione / riscrittura della cronologia / commit in un modo un altro, quindi chiunque con vecchie copie dei commit dovrà fare un lavoro extra per risincronizzare la propria cronologia con la nuova cronologia.
Soluzione 1: modifica degli impegni
Se hai apportato una modifica accidentale (come l'aggiunta di un file) nel tuo commit precedente e non vuoi più che la cronologia di quel cambiamento esista, allora puoi semplicemente modificare il commit precedente per rimuovere il file da esso:
git rm <file>
git commit --amend --no-edit
Soluzione 2: Hard Reset (eventualmente Plus a Rebase)
Come la soluzione n. 1, se vuoi semplicemente sbarazzarti del tuo precedente commit, hai anche la possibilità di fare semplicemente un hard reset al suo genitore:
git reset --hard HEAD^
Tale comando reimposterà il tuo ramo al precedente commit del primo genitore.
Tuttavia , se, come il poster originale, dopo aver eseguito il commit in cui hai annullato la modifica, hai effettuato diversi commit, puoi comunque utilizzare i ripristini rigidi per modificarlo, ma ciò comporta anche l'utilizzo di un rebase. Ecco i passaggi che è possibile utilizzare per modificare un commit più indietro nella cronologia:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Soluzione 3: Rebase non interattivo
Funzionerà se desideri rimuovere completamente un commit dalla cronologia:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Soluzione 4: basi interattive
Questa soluzione ti consentirà di realizzare le stesse cose delle soluzioni n. 2 e n. 3, ovvero modificare o rimuovere i commit più indietro nella cronologia rispetto al tuo commit immediatamente precedente, quindi quale soluzione scegli di utilizzare dipende da te. I rebase interattivi non sono adatti al rebasing di centinaia di commit, per motivi di prestazioni, quindi utilizzerei rebases non interattivi o la soluzione di diramazione del filtro (vedi sotto) in quel tipo di situazioni.
Per iniziare il rebase interattivo, utilizzare quanto segue:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Ciò farà tornare git indietro alla cronologia del commit al genitore del commit che si desidera modificare o rimuovere. Ti presenterà quindi un elenco dei commit riavvolti in ordine inverso in qualunque editor git sia impostato per l'uso (questo è Vim per impostazione predefinita):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
Il commit che si desidera modificare o rimuovere sarà in cima a questo elenco. Per rimuoverlo, è sufficiente eliminare la sua riga nell'elenco. In caso contrario, sostituire "raccogliere" con "Modifica" nella 1 ° linea, in questo modo:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Quindi, inserisci git rebase --continue
. Se si è scelto di rimuovere completamente il commit, è necessario eseguire tutto ciò che è necessario (tranne la verifica, vedere il passaggio finale per questa soluzione). Se, d'altra parte, si desidera modificare il commit, git riapplicherà il commit e quindi metterà in pausa il rebase.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
A questo punto, è possibile rimuovere il file e modificare il commit, quindi continuare il rebase:
git rm <file>
git commit --amend --no-edit
git rebase --continue
Questo è tutto. Come passaggio finale, sia che tu abbia modificato il commit o sia stato rimosso completamente, è sempre una buona idea verificare che non siano state apportate altre modifiche impreviste al tuo ramo diffondendolo con il suo stato prima del rebase:
git diff master@{1}
Soluzione 5: filtrare i rami
Infine, questa soluzione è la migliore se si desidera cancellare completamente tutte le tracce dell'esistenza di un file dalla cronologia e nessuna delle altre soluzioni è all'altezza del compito.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
Ciò rimuoverà <file>
da tutti i commit, a partire dal commit root. Se invece vuoi solo riscrivere l'intervallo di commit HEAD~5..HEAD
, puoi passarlo come argomento aggiuntivo a filter-branch
, come sottolineato in
questa risposta :
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Ancora una volta, al filter-branch
termine dell'operazione, in genere è consigliabile verificare che non vi siano altre modifiche impreviste diffondendo il ramo con lo stato precedente prima dell'operazione di filtro:
git diff master@{1}
Alternativa filtro-ramo: BFG Repo Cleaner
Ho sentito che lo strumento BFG Repo Cleaner funziona più velocemente di git filter-branch
, quindi potresti voler controllare anche questa opzione. È anche menzionato ufficialmente nella documentazione del ramo filtro come alternativa praticabile:
git-filter-branch ti consente di effettuare riscritture complesse con script shell della tua cronologia di Git, ma probabilmente non avrai bisogno di questa flessibilità se stai semplicemente rimuovendo dati indesiderati come file di grandi dimensioni o password. Per quelle operazioni potresti prendere in considerazione The BFG Repo-Cleaner , un'alternativa basata su JVM al ramo git-filter, in genere almeno 10-50x più veloce per quei casi d'uso e con caratteristiche abbastanza diverse:
Qualsiasi versione particolare di un file viene pulita esattamente una volta . Il BFG, a differenza di git-filter-branch, non ti dà l'opportunità di gestire un file in modo diverso in base a dove o quando è stato eseguito il commit nella tua cronologia. Questo vincolo offre il principale vantaggio in termini di prestazioni di The BFG ed è adatto al compito di pulire i dati errati: non ti interessa dove si trovano i dati errati, vuoi solo che scompaiano .
Per impostazione predefinita, il GGG sfrutta appieno le macchine multi-core, pulendo in parallelo gli alberi dei file di commit. git-filter-branch cleans esegue il commit in modo sequenziale (ovvero in modo a thread singolo), sebbene sia
possibile scrivere filtri che includono il proprio parallelismo, negli script eseguiti su ciascun commit.
Le opzioni di comando sono molto più restrittive di ramo git-filtro, e dedicato proprio ai compiti di rimozione indesiderata dati- ad esempio: --strip-blobs-bigger-than 1M
.
Risorse addizionali
- Pro Git § 6.4 Git Tools - Riscrivere la cronologia .
- Pagina di manuale git-filter-branch (1) .
- Pagina di manuale git-commit (1) .
- Pagina di manuale git-reset (1) .
- Pagina di manuale git-rebase (1) .
- BFG Repo Cleaner (vedi anche questa risposta del creatore stesso ).