2015-01-13 13 views
6

Abbiamo recentemente completato una conversione da Mercurial a Git, tutto è andato liscio, siamo stati persino in grado di ottenere le trasformazioni necessarie per rendere tutto/funzionare relativamente correttamente nel repository. Abbiamo aggiunto uno .gitignore e siamo partiti.Posso riscrivere la cronologia di un intero repository git per includere qualcosa che abbiamo dimenticato?

Tuttavia stiamo riscontrando alcuni rallentamenti estremi non appena mettiamo in atto/lavoriamo con una delle nostre vecchie funzioni. Un po 'di esplorazione e abbiamo scoperto che dal momento che lo .gitignore è stato aggiunto solo al ramo develop quando guardiamo altri commit senza fondersi sviluppiamo in essi git chugg perché sta soffocando cercando di analizzare tutti i nostri artefatti di build (file binari) ecc ... non c'era il file .gitignore per queste vecchie filiali.

Quello che ci piacerebbe fare è inserire in modo efficace un nuovo commit di root con .gitignore in modo che possa essere popolato retroattivamente in tutte le teste/tag. Siamo a nostro agio con una riscrittura della cronologia, il nostro team è relativamente piccolo, quindi tutti si fermano per questa operazione e recuperano i loro repository quando la riscrittura della cronologia viene eseguita non è un problema.

ho trovato informazioni su rebasing padrone su una nuova radice commit e questo funziona per il padrone, il problema è lascia la nostra caratteristica rami staccati sul vecchio albero della storia, si riproduce anche l'intera storia con un nuovo commit appuntamento.

Qualche idea o siamo sfortunati su questo?

risposta

8

Ciò che si desidera fare coinvolgerà due fasi: aggiungere una nuova radice con un opportuno .gitignore e scrub la cronologia per rimuovere i file che non avrebbero dovuto essere aggiunti. Il comando git filter-branch può eseguire entrambe le operazioni.

Impostazione

Considera un rappresentante della tua cronologia.

$ git lola --name-status 
* f1af2bf (HEAD, bar-feature) Add bar 
| A  .gitignore 
| A  bar.c 
| D  main.o 
| D  module.o 
| * 71f711a (master) Add foo 
|/ 
| A foo.c 
| A foo.o 
* 7f1a361 Commit 2 
| A  module.c 
| A  module.o 
* eb21590 Commit 1 
    A  main.c 
    A  main.o 

Per chiarezza, i file *.c rappresentano file sorgente C e *.o sono compilati file oggetti che dovrebbero essere stati ignorati.

Sul ramo della funzione barra, è stato aggiunto un file .gitignore appropriato e file oggetto eliminati che non avrebbero dovuto essere tracciati, ma si desidera che tale criterio venga riflesso ovunque nell'importazione.

Nota che git lola è un non-standard ma alias utile.

git config --global alias.lola \ 
    'log --graph --decorate --pretty=oneline --abbrev-commit --all' 

nuova radice Commit

creare la nuova radice commit come segue.

$ git checkout --orphan new-root 
Switched to a new branch 'new-root' 

La documentazione git checkout nota una possibile stato imprevisto della nuova filiale orfano.

Se si vuole iniziare una storia disconnessa che registra una serie di percorsi che è totalmente diverso da quello di start_point, allora si dovrebbe cancellare l'indice e l'albero a lavorare subito dopo la creazione della filiale orfano eseguendo git rm -rf . dal livello superiore dell'albero di lavoro.Successivamente sarete pronti per preparare i nuovi file, ripopolare l'albero di lavoro, copiandoli da altrove, l'estrazione di un archivio, ecc

Continuando il nostro esempio:

$ git rm -rf . 
rm 'foo.c' 
rm 'foo.o' 
rm 'main.c' 
rm 'main.o' 
rm 'module.c' 
rm 'module.o' 

$ echo '*.o' >.gitignore 

$ git add .gitignore 

$ git commit -m 'Create .gitignore' 
[new-root (root-commit) 00c7780] Create .gitignore 
1 file changed, 1 insertion(+) 
create mode 100644 .gitignore 

Ora la storia si presenta come

$ git lola 
* 00c7780 (HEAD, new-root) Create .gitignore 
* f1af2bf(bar-feature) Add bar 
| * 71f711a (master) Add foo 
|/ 
* 7f1a361 Commit 2 
* eb21590 Commit 1 

che è leggermente fuorviante perché rende nuovo look-root come esso è un discendente di bar-funzione, ma non ha davvero nessun genitore.

$ git rev-parse HEAD^ 
HEAD^ 
fatal: ambiguous argument 'HEAD^': unknown revision or path not in the working tree. 
Use '--' to separate paths from revisions, like this: 
'git <command> [<revision>...] -- [<file>...]' 

Prendere nota dello SHA per l'orfano perché sarà necessario in seguito. In questo esempio, è

$ git rev-parse HEAD 
00c778087723ae890e803043493214fb09706ec7 

riscrivere la storia

Vogliamo git filter-branch per fare tre grandi cambiamenti.

  1. Giuntura nel nuovo commit radice.
  2. Elimina tutti i file temporanei.
  3. Utilizzare il .gitignore dalla nuova radice, a meno che non sia già esistente.

Nella riga di comando, che è incanted come

git filter-branch \ 
    --parent-filter ' 
    test $GIT_COMMIT = eb215900cd15ca2cf9ded74f1a0d9d25f65eb2bf && \ 
       echo "-p 00c778087723ae890e803043493214fb09706ec7" \ 
     || cat' \ 
    --index-filter ' 
    git rm --cached --ignore-unmatch "*.o"; \ 
    git ls-files --cached --error-unmatch .gitignore >/dev/null 2>&1 || 
     git update-index --add --cacheinfo \ 
     100644,$(git rev-parse new-root:.gitignore),.gitignore' \ 
    --tag-name-filter cat \ 
    -- --all 

Spiegazione:

  • I --parent-filter ganci opzione nel tuo nuova radice commettono.
    • eb215... è lo SHA completo del vecchio commit radice, cf.git rev-parse eb215
  • L'opzione --index-filter ha due parti:
    • corso git rm come sopra eliminazioni nulla corrispondenza *.o dall'intero albero perché il modello glob è citato ed interpretato da git piuttosto che il guscio.
    • Verificare la presenza di uno .gitignore esistente con git ls-files e, se non è presente, puntare a quello nella nuova radice.
  • Se si dispone di tag, questi verranno mappati con l'operazione di identità, cat.
  • Il solo -- termina le opzioni e --all è una scorciatoia per tutti gli arbitri.

L'output che si vede somiglierà

Rewrite eb215900cd15ca2cf9ded74f1a0d9d25f65eb2bf (1/5)rm 'main.o' 
Rewrite 7f1a361ee918f7062f686e26b57788dd65bb5fe1 (2/5)rm 'main.o' 
rm 'module.o' 
Rewrite 71f711a15fa1fc60542cc71c9ff4c66b4303e603 (3/5)rm 'foo.o' 
rm 'main.o' 
rm 'module.o' 
Rewrite f1af2bf89ed2236fdaf2a1a75a34c911efbd5982 (5/5) 
Ref 'refs/heads/bar-feature' was rewritten 
Ref 'refs/heads/master' was rewritten 
WARNING: Ref 'refs/heads/new-root' is unchanged 

gli originali sono ancora al sicuro. Il ramo principale ora vive sotto refs/original/refs/heads/master, per esempio. Rivedi le modifiche nei tuoi rami appena riscritti.Quando si è pronti per eliminare il backup, eseguire

git update-ref -d refs/original/refs/heads/master 

si poteva cucinare un comando a coprire tutte le arbitri di backup in un unico comando, ma vi consiglio di attento riesame per ciascuno di essi.

Conclusione

Infine, la nuova storia è

$ git lola --name-status 
* ab8cb1c (bar-feature) Add bar 
| M  .gitignore 
| A  bar.c 
| * 43e5658 (master) Add foo 
|/ 
| A foo.c 
* 6469dab Commit 2 
| A  module.c 
* 47f9f73 Commit 1 
| A  main.c 
* 00c7780 (HEAD, new-root) Create .gitignore 
    A  .gitignore 

Si osservi che tutti i file oggetto sono andati. La modifica a .gitignore in bar-feature è perché ho usato contenuti diversi per assicurarmi che venissero conservati. Per completezza:

$ git diff new-root:.gitignore bar-feature:.gitignore 
diff --git a/new-root:.gitignore b/bar-feature:.gitignore 
index 5761abc..c395c62 100644 
--- a/new-root:.gitignore 
+++ b/bar-feature:.gitignore 
@@ -1 +1,2 @@ 
*.o 
+*.obj 

L'arbitro nuova-root non è più utile, in modo da smaltire con

$ git checkout master 
$ git branch -d new-root 
+2

Sei il mio eroe flippin! – Aren

+0

@Aren Prego! Felice di aiutare. –

-1

responsabilità: questo è teorico (sulla base di documentazione), non ho fatto questo. Clona e prova.

Da quello che ho capito non hai mai commitedfiles che sarebbe stato filtrato dallo .gitignore che vuoi aggiungere alla radice della tua cronologia.

Pertanto, se si rebase il ramo master su un commit newroot contenente solo il segno .gitignore, in realtà non si modificherà il contenuto dei commit, e successivamente si sarà in grado di rebase di tutti gli altri rami che si avere il nuovo commit, e rebase farà il lavoro per te.

Poiché il contenuto dei commit è lo stesso, l'ID della patch deve rimanere lo stesso e rebase applicherà solo ciò che è necessario.

È necessario rebase di ogni ramo uno per uno, ma può essere facilmente copiato.

Altre informazioni possono essere trovate in the git rebase documentation nella sezione: RECUPERARE DA RIEMPIMENTO UPSTREMA alla fine della pagina.

EDIT: Ok non importa, testato e non funziona esattamente in questo modo. Devi dare il punto di rebase per ogni ramo nella nuova cronologia "manualmente", che è un dolore. Potrebbe ancora essere fatto funzionare ma è chiaramente una soluzione peggiore della risposta accettata.

+0

Questo è errato e fuorviante. – Jubobs

+0

Questo è stato l'approccio che ho provato per primo, il problema che ho incontrato è che una volta rebasato il master alla nuova cronologia, le branch non hanno alcun punto di divergenza, quindi non puoi rebase in modo efficace i branch delle feature come dovresti fare scegli le parti giuste della vecchia storia una per una. – Aren

+0

È possibile ribasare qualsiasi ramo sul nuovo commit corrispondente al commit in cui il ramo è stato avviato nella cronologia precedente, FORNITO che tutti i commit nella nuova cronologia hanno lo stesso contenuto (vale a dire che il rebase del branch di themaster era sostanzialmente un no-op oltre ad aggiungere un .gitignore all'inizio) –

Problemi correlati