2015-04-17 13 views
10

Ho un file che ho rinominato e quindi modificato. Vorrei dire a Git di mettere in scena il cambio di nome, ma non le modifiche del contenuto. Cioè, vorrei mettere in scena la cancellazione del vecchio nome del file e l'aggiunta del vecchio contenuto del file con il nuovo nome del file.Come inscenare una rinomina senza modifiche successive in git?

Così ho questo:

Changes not staged for commit: 

     deleted: old-name.txt 

Untracked files: 

     new-name.txt 

ma vogliono o questo:

Changes to be committed: 

     new file: new-name.txt 
     deleted: old-name.txt 

Changes not staged for commit: 

     modified: new-name.txt 

o questo:

Changes to be committed: 

     renamed: old-name.txt -> new-name.txt 

Changes not staged for commit: 

     modified: new-name.txt 

(dove la misura di similarità dovrebbe essere al 100%).

Non riesco a pensare a un modo semplice per farlo.

C'è sintassi per ottenere il contenuto di una revisione specifica di un file specifico e aggiungerlo all'area di gestione temporanea di git in un percorso specificato?

La parte di eliminazione, con git rm, va bene:

$ git rm old-name.txt 

E 'la parte aggiunta del rename sto lottando con. (Ho potuto salvare i nuovi contenuti, checkout una nuova copia (per i vecchi contenuti), mv nel guscio, git add, e poi recuperare i nuovi contenuti, ma che sembra un modo molto lungo intorno!)

Grazie!

+4

Provato 'git mv'? – vaultah

+1

'git mv' da solo non funzionerà se il file originale è già stato eliminato, o il percorso di destinazione esiste. Devi fare il processo di salvataggio/ripristino che hai delineato ... durante il quale, puoi usare 'git mv' invece di' mv + git add'. Dato che git non stava eseguendo il monitoraggio di 'new-name.txt' quando hai apportato delle modifiche ad esso, così com'è-non può aiutare a separare queste modifiche. – hinerm

risposta

12

Git in realtà non rinomina. Sono tutti calcolati in modo "after the fact": git confronta un commit con un altro e, in corrispondenza del tempo di confronto, decide se è stato rinominato. Ciò significa che se git considera qualcosa "un rinominare" cambia dinamicamente. So che mi stai chiedendo di un impegno che non hai ancora fatto, ma abbi pazienza con me, questo in realtà è tutto in pareggio (ma la risposta sarà lunga).


quando si chiede git (via git show o git log -p o git diff HEAD^ HEAD) "ciò che è accaduto nel corso dell'ultimo commit", viene eseguito un diff del precedente commit (HEAD^ o HEAD~1 o l'attuale grezzo SHA-1 per il precedente commit-any di questi farà per identificarlo) e il commit corrente (HEAD). Nel fare questo diff può scoprire che c'era un old.txt e non c'è più; e non c'era lo new.txt ma ora c'è.

Questi nomi di file, i file che prima erano lì, ma non lo sono, e i file che sono lì ora che non sono stati messi nella pila contrassegnati come "candidati per la rinomina". Quindi, per ogni nome nella pila, git confronta "vecchi contenuti" e "nuovi contenuti". Il confronto per la corrispondenza esatta è semplicissimo a causa del modo in cui git riduce i contenuti in SHA-1; se la corrispondenza esatta fallisce, git passa a un diff alternativo "sono i contenuti almeno simili" per verificare la presenza di rinomina. Con git diff questo passaggio facoltativo è controllato dal flag -M. Con altri comandi è impostato dai valori git config o codificato nel comando.

Ora, torna all'area di staging e git status: ciò che git memorizza nell'indice/area di staging è fondamentalmente "un prototipo per il prossimo commit". Quando si esegue il comando git add, git memorizza il contenuto del file proprio in quel punto, calcolando l'SHA-1 nel processo e quindi memorizzando l'SHA-1 nell'indice. Quando fai qualcosa su git rm, git memorizza una nota nell'indice dicendo "questo nome di percorso viene deliberatamente rimosso al prossimo commit".

Il comando git status, quindi, fa semplicemente un diff o realmente due differ: HEAD vs indice, per ciò che sta per essere commesso; e indice vs albero di lavoro, per quale cosa potrebbe essere essere (ma non è ancora) sarà impegnato.

In quella prima diff, git utilizza lo stesso meccanismo di sempre per rilevare i nomi. Se c'è un percorso nel commit HEAD inserito nell'indice e un percorso nell'indice nuovo e non nel commit HEAD, è un candidato per il rilevamento dei nomi. Il comando git status consente di rinominare il rilevamento su "on" (e il limite di conteggio dei file su 200; con un solo candidato per il rilevamento dei nomi questo limite è sufficiente).


Cosa significa tutto questo per il tuo caso? Bene, hai rinominato un file (senza utilizzare git mv, ma non importa perché git status trova il nome o non riesce a trovarlo, al tempo git status) e ora ha una versione nuova e diversa del nuovo file.

Se si è git add la nuova versione, quella versione più recente va nel repository, e il suo SHA-1 è nell'indice, e quando git status fa un confronto confronterà il nuovo e il vecchio. Se sono almeno "simili al 50%" (il valore cablato per git status), git ti dirà che il file è stato rinominato.

Naturalmente, git add -ing i modificati contenuto non è del tutto quello che hai chiesto: si voleva fare commettere un intermedio in cui il file è solo rinominato, vale a dire, un commit con un albero con il nuovo nome , ma i vecchi contenuti.

Non è possibile eseguire a causa di tutti i suddetti rilevamenti di ridenominazione dinamici. Se si vuole farlo (per qualsiasi motivo) ... beh, git non rende tutto così facile.

Il modo più diretto è proprio come lei suggerisce: spostare i contenuti modificati da qualche parte fuori del modo, utilizzare git checkout -- old-name.txt, quindi git mv old-name.txt new-name.txt, quindi eseguire il commit. Lo git mv rinominerà entrambi il file nell'indice/area di staging e rinominerà la versione dell'albero di lavoro.

Se git mv avuto un'opzione --cached come git rm fa, si può solo git mv --cached old-name.txt new-name.txt e poi git commit. Il primo passo rinominerebbe il file nell'indice, senza toccare l'albero del lavoro. Ma non è così: insiste a sovrascrivere la versione dell'albero di lavoro, e insiste sul fatto che il vecchio nome deve esistere nell'albero di lavoro per iniziare.

Il metodo a passo singolo per eseguire questa operazione senza toccare l'albero di lavoro è utilizzare git update-index --index-info, ma anche questo è un po 'disordinato (lo mostrerò comunque tra un momento). Fortunatamente, c'è un'ultima cosa che possiamo fare.Ho installato la stessa situazione si ha, rinominando il vecchio nome a quello nuovo e la modifica del file:

$ git status 
On branch master 
Changes not staged for commit: 
    (use "git add/rm <file>..." to update what will be committed) 
    (use "git checkout -- <file>..." to discard changes in working directory) 

    deleted: old-name.txt 

Untracked files: 
    (use "git add <file>..." to include in what will be committed) 

    new-name.txt 

Quello che facciamo ora è, primo luogo, mettere manualmente il file di nuovo sotto il suo vecchio nome , quindi utilizzare git mv per passare di nuovo per il nuovo nome:

$ mv new-name.txt old-name.txt 
$ git mv old-name.txt new-name.txt 

Questa volta git mv aggiorna il nome nell'indice, ma mantiene i contenuti originali come l'indice di SHA-1, ma si muove il lavoro- versione albero (nuovi contenuti) in posizione nel lavoro-albero:

$ git status 
On branch master 
Changes to be committed: 
    (use "git reset HEAD <file>..." to unstage) 

    renamed: old-name.txt -> new-name.txt 

Changes not staged for commit: 
    (use "git add <file>..." to update what will be committed) 
    (use "git checkout -- <file>..." to discard changes in working directory) 

    modified: new-name.txt 

Ora basta git commit per fare un commit con la ridenominazione in atto, ma non i nuovi contenuti.

(Si noti che questo dipende non ci sia un nuovo file con il vecchio nome!)


Cosa succede ad usare git update-index? Beh, prima cerchiamo di riportare le cose alla "cambiato nel lavoro-albero, indice corrisponde TESTA commettere" stato:

$ git reset --mixed HEAD # set index=HEAD, leave work-tree alone 

Ora vediamo cosa c'è nella indice per old-name.txt:

$ git ls-files --stage -- old-name.txt 
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0 old-name.txt 

Allora, che cosa abbiamo bisogno git update-index --index-info fare è quello di spazzare via la voce per old-name.txt ma fare una voce altrimenti identico per new-name.txt:

$ (git ls-files --stage -- old-name.txt; 
    git ls-files --stage -- old-name.txt) | 
    sed -e \ 
'1s/^[0-9]* [0-9a-f]*/000000 0000000000000000000000000000000000000000/' \ 
     -e '2s/old-name.txt$/new-name.txt/' | 
    git update-index --index-info 

(nota: ho rotto t lui sopra per scopi di pubblicazione, era tutto una riga quando l'ho digitato; in sh/bash, dovrebbe funzionare interrotto in questo modo, dati i backslash che ho aggiunto per continuare il comando "sed").

Ci sono altri modi per farlo, ma semplicemente estraendo la voce di indice due volte e modificando la prima in una rimozione e la seconda con il nuovo nome sembrava la più semplice qui, da qui il comando sed. La prima sostituzione cambia la modalità file (100644 ma qualsiasi modalità dovrebbe essere convertita in zeri) e SHA-1 (corrisponde a qualsiasi SHA-1, sostituisce con gli speciali zeri tutti di zit SHA-1), e la seconda lascia la modalità e SHA-1 da solo durante la sostituzione del nome.

Al termine dell'indice di aggiornamento, l'indice ha registrato la rimozione del vecchio percorso e l'aggiunta del nuovo percorso (con la stessa modalità e SHA-1 presenti nel vecchio percorso).

Si noti che questo potrebbe non funzionare correttamente se l'indice contiene voci non raggruppate per old-name.txt poiché potrebbero esserci altre fasi (da 1 a 3) per il file.

+1

La mia opzione preferita elencata è rinominare vecchio, quindi usare 'git mv'. –

+1

Inoltre, può valere la pena notare che si potrebbe fare 'git stash' per tornare allo stato originale, quindi eseguire' git mv' per rinominare. Dopo aver eseguito il commit, puoi usare 'git stash --pop', con un possibile conflitto di unione, per tornare al punto in cui ti trovavi. –

+1

@BrianJ: sì, quello (mv, quindi git mv) è sicuramente il più semplice qui. Può essere più difficile se hai creato un nuovo file con il vecchio nome, dal momento che non c'è "spazio libero" per spostare i 15 pezzi del puzzle in quel caso :-) – torek

4

@torek ha dato una risposta molto chiara e completa. Ci sono molti dettagli molto utili lì; vale la pena leggere bene.

Ma, per il bene di coloro in fretta, il punto cruciale della soluzione molto semplice dato è stato questo:

Quello che ora è, in primo luogo, mettere manualmente il file di nuovo sotto il suo vecchio nome , quindi utilizzare git mv per passare di nuovo per il nuovo nome:

$ mv new-name.txt old-name.txt 
$ git mv old-name.txt new-name.txt 

(. E 'stato solo l'mv schiena che mi mancava, per rendere possibile l'git mv)

Please upvote @ torek's risposta se lo trovate utile.

Problemi correlati