Gestire i nomi dei file in git


440

Ho letto che quando si rinomina un file in git , è necessario eseguire il commit di eventuali modifiche, eseguire la ridenominazione e quindi mettere in scena il file rinominato. Git riconoscerà il file dal contenuto, piuttosto che vederlo come un nuovo file non tracciato e manterrà la cronologia delle modifiche.

Tuttavia, facendo proprio questo stasera ho finito per tornare a git mv.

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

Rinomina il mio foglio di stile nel Finder da iphone.cssamobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   css/mobile.css

Quindi git ora pensa di aver eliminato un file CSS e di averne aggiunto uno nuovo. Non è quello che voglio, annulla la ridenominazione e lascia che git faccia il lavoro.

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

Torna da dove ho iniziato.

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

Usiamo git mvinvece.

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   index.html
#

Sembra che stiamo bene. Quindi perché Git non ha riconosciuto la ridenominazione la prima volta che ho usato Finder?


29
Git tiene traccia del contenuto, non dei file, quindi non importa come porti l'indice nello stato corretto - add+rmo mv- produce lo stesso risultato. Git quindi utilizza il suo rilevamento di rinomina / copia per farti sapere che era una ridenominazione. Anche la fonte che hai citato è imprecisa. Non importa se modifichi + rinomina nello stesso commit o meno. Quando fai una differenza tra sia la modifica che la ridenominazione, il rilevamento della ridenominazione lo vedrà come una ridenominazione + modifica, o se la modifica è una riscrittura totale, verrà mostrata come aggiunta ed eliminata - comunque non importa come hai eseguito esso.
Cascabel,

6
Se questo è vero, perché non lo ha rilevato con la mia ridenominazione utilizzando Finder?
Greg K,

26
git mv old newaggiorna automaticamente l'indice. Quando rinominerai al di fuori di Git, dovrai fare git add newe git rm oldmettere in scena le modifiche all'indice. Una volta fatto git status, funzionerà come previsto.
Chris Johnsen,

4
Ho appena spostato un mucchio di file in una directory public_html, che sono tracciati in git. Dopo aver eseguito git add .e git commit, ha ancora mostrato un sacco di file "cancellati" in git status. Ho eseguito un git commit -ae le eliminazioni sono state confermate, ma ora non ho alcuna cronologia sui file che vivono public_htmlora. Questo flusso di lavoro non è fluido come vorrei.
Greg K,

Risposte:


352

Per git mvla pagina del manuale dice

L'indice viene aggiornato dopo il completamento con esito positivo, […]

Quindi, all'inizio, devi aggiornare l'indice da solo (usando git add mobile.css). Tuttavia
git status mostrerà comunque due file diversi

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

È possibile ottenere un output diverso eseguendo git commit --dry-run -a, il che si traduce in ciò che ci si aspetta:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

Non posso dirti esattamente perché vediamo queste differenze tra git statuse
git commit --dry-run -a, ma ecco un suggerimento di Linus :

git in realtà non si preoccupa nemmeno dell'intera "rilevazione della ridenominazione" internamente, e qualsiasi commit che hai fatto con le ridenominazioni è totalmente indipendente dall'euristica che usiamo per mostrare le rinominazioni.

A dry-runutilizza i meccanismi di rinomina reale, mentre git statusprobabilmente non lo fa.


1
Non hai menzionato il passaggio in cui l'hai fatto git add mobile.css. Senza di essa git status -aavrebbe solo "visto" la rimozione del iphone.cssfile precedentemente tracciato ma non avrebbe toccato il nuovo mobile.cssfile non tracciato . Inoltre, git status -anon è valido con Git 1.7.0 e versioni successive. "" Git status "non è più" git commit --dry-run "." in kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . Utilizzare git commit --dry-run -ase si desidera questa funzionalità. Come altri hanno già detto, basta aggiornare l'indice e git statusfunzionerà come previsto dal PO.
Chris Johnsen,

3
se lo fai normalmente git commit, il file rinominato non verrà eseguito e l'albero di lavoro sarà sempre lo stesso. git commit -asconfigge praticamente ogni aspetto del flusso di lavoro / modello di pensiero di git: ogni cambiamento è impegnato. cosa succede se si desidera solo rinominare il file, ma eseguire il commit delle modifiche index.htmlin un altro commit?
Knittl

@Chris: sì, ovviamente ho aggiunto mobile.cssche avrei dovuto menzionare. Ma questo è il punto della mia risposta: la pagina man lo dice the index is updatedquando lo usi git-mv. Grazie per il status -achiarimento, ho usato git 1.6.4
tanascius il

4
Bella risposta! Stavo sbattendo la testa contro il muro cercando di capire perché git statusnon stavo rilevando la ridenominazione. In esecuzione git commit -a --dry-rundopo aver aggiunto i miei "nuovi" file ha mostrato i nomi e alla fine mi ha dato la sicurezza di impegnarmi!
stephen.hanson,

1
In git 1.9.1 git statusora si comporta come git commit.
Jacques René Mesrine,

77

Devi aggiungere i due file modificati all'indice prima che git lo riconosca come mossa.

L'unica differenza tra mv old newe git mv old newè che git mv aggiunge anche i file all'indice.

mv old newallora git add -Aavrebbe funzionato anche lui.

Nota che non puoi semplicemente usarlo git add .perché non aggiunge rimozioni all'indice.

Vedi Differenza tra "git add -A" e "git add".


3
Grazie per il git add -Alink, molto utile, poiché stavo cercando tale scorciatoia!
PhiLho,

5
Si noti che con git 2, git add . non aggiungere traslochi all'indice.
Nick McCurdy,

19

La cosa migliore è provarlo per te.

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

Ora git status e git commit --dry-run -a mostra due diversi risultati in cui lo stato git mostra bbb.txt come un nuovo file / aaa.txt viene eliminato e i comandi --dry-run mostrano il vero nome.

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# 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:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

Ora vai avanti e fai il check-in.

git commit -a -m "Rename"

Ora puoi vedere che il file è stato rinominato, e ciò che è mostrato nello stato git è sbagliato.

Morale della storia: se non sei sicuro che il tuo file sia stato rinominato, emetti un "git commit --dry-run -a". Se mostra che il file è stato rinominato, sei a posto.


3
Per quello che conta per Git, entrambi sono corretti . Quest'ultimo è però più vicino a come lo vedi tu, come committer. La vera differenza tra rinomina ed elimina + crea è solo a livello di sistema operativo / file system (ad es. Stesso inode # vs. nuovo inode #), di cui a Git non importa molto.
Alois Mahdal

17

Per git 1.7.x i seguenti comandi hanno funzionato per me:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

Non è stato necessario aggiungere git, poiché il file originale (ad esempio css / mobile.css) era già nei file di commit in precedenza.


6
Questo. Tutte le altre risposte sono ridicolmente e inutilmente complesse. Ciò mantiene la cronologia dei file tra i commit in modo che le fusioni prima / dopo la ridenominazione del file non vengano interrotte.
Splendido

10

devi git add css/mobile.cssil nuovo file e git rm css/iphone.css, quindi git lo sa. quindi mostrerà lo stesso output ingit status

puoi vederlo chiaramente nell'output di stato (il nuovo nome del file):

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

e (il vecchio nome):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

penso che dietro le quinte git mvnon sia altro che uno script wrapper che fa esattamente questo: elimina il file dall'indice e aggiungilo con un nome diverso


Non pensavo di doverlo fare git rm css/iphone.cssperché pensavo che questo avrebbe rimosso la storia esistente. Forse sto fraintendendo il flusso di lavoro in git.
Greg K,

4
@Greg K: git rmnon rimuoverà la cronologia. Rimuove solo una voce dall'indice in modo che il prossimo commit non abbia la voce. Tuttavia, esisterà ancora nei commit ancestrali. Ciò di cui potresti essere confuso è che (ad esempio) git log -- newsi fermerà nel punto in cui ti sei impegnato git mv old new. Se vuoi seguire i nomi, usa git log --follow -- new.
Chris Johnsen,

9

Pensiamo ai tuoi file dal punto di vista git.

Tieni presente che git non tiene traccia dei metadati sui tuoi file

Il tuo repository ha (tra gli altri)

$ cd repo
$ ls
...
iphone.css
...

ed è sotto controllo git:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

Prova questo con:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

Quando lo fai

$ mv iphone.css mobile.css

Dal punto di vista git,

  • non c'è iphone.css (viene cancellato -git lo avverte-).
  • c'è un nuovo file mobile.css .
  • Quei file sono totalmente indipendenti.

Quindi, git avvisa dei file che già conosce ( iphone.css ) e dei nuovi file che rileva ( mobile.css ) ma solo quando i file sono in indice o HEAD git inizia a controllarne il contenuto.

In questo momento, né "cancellazione iphone.css" né mobile.css sono nell'indice.

Aggiungi la cancellazione di iphone.css all'indice

$ git rm iphone.css

git ti dice esattamente cosa è successo: ( iphone.css viene eliminato. Non è successo altro)

quindi aggiungere il nuovo file mobile.css

$ git add mobile.css

Questa volta sia la cancellazione che il nuovo file sono nell'indice. Ora git rileva che il contesto è lo stesso ed esponendolo come rinominazione. In effetti, se i file sono simili al 50%, questo verrà rilevato come una ridenominazione, che consente di modificare mobile.css un po ' mantenendo l'operazione come una ridenominazione.

Vedi questo è riproducibile su git diff. Ora che i tuoi file sono nell'indice devi usare --cached. Modifica un po ' mobile.css , aggiungilo per indicizzare e vedi la differenza tra:

$ git diff --cached 

e

$ git diff --cached -M

-Mè l'opzione "rileva nomi" per git diff. -Msta per -M50%(il 50% o più di somiglianza farà sì che git lo esprima come una ridenominazione) ma è possibile ridurlo a -M20%(20%) se si modifica molto mobile.css.


8

Step1: rinomina il file da oldfile a newfile

git mv #oldfile #newfile

Step2: git commit e aggiungi commenti

git commit -m "rename oldfile to newfile"

Passaggio 3: invia questa modifica al server remoto

git push origin #localbranch:#remotebranch

1
Si prega di aggiungere un commento in modo che sia utile per OP
Devrath

Il passaggio 2 non è necessario. Dopo git mv, il nuovo file è già nell'indice.
Alois Mahdal,

7

Git riconoscerà il file dal contenuto, piuttosto che vederlo come un nuovo file non tracciato

Ecco dove hai sbagliato.

È solo dopo aver aggiunto il file, che git lo riconoscerà dal contenuto.


Esattamente. Quando git in scena mostrerà la ridenominazione correttamente.
Max MacLeod,

3

Non hai messo in scena i risultati della tua mossa di ricerca. Credo che se avessi fatto il passaggio tramite Finder e poi lo avesse fatto git add css/mobile.css ; git rm css/iphone.css, git calcolerebbe l'hash del nuovo file e solo allora realizzerebbe che gli hash dei file corrispondono (e quindi è una ridenominazione).


2

Nei casi in cui è necessario rinominare i file manualmente, ad es. usare uno script per rinominare in batch un mucchio di file, quindi usare ha git add -A .funzionato per me.


2

Per gli utenti di Xcode: se si rinomina il file in Xcode, l'icona del badge cambia in append. Se esegui un commit utilizzando XCode, creerai effettivamente un nuovo file e perderai la cronologia.

Una soluzione alternativa è semplice ma devi farlo prima di iniziare a utilizzare Xcode:

  1. Esegui uno stato git sulla tua cartella. Dovresti vedere che le modifiche organizzate sono corrette:

rinominato: Project / OldName.h -> Project / NewName.h rinominato: Project / OldName.m -> Project / NewName.m

  1. esegui il commit -m 'cambio nome'

Quindi torna a XCode e vedrai il badge modificato da A a M ed è ora possibile salvare ulteriori modifiche usando xcode.

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.