Come fare in modo che git contrassegni un file eliminato e un nuovo come spostamento di file?


505

Ho spostato un file manualmente e poi l'ho modificato. Secondo Git, è un nuovo file e un file rimosso. C'è un modo per forzare Git a trattarlo come uno spostamento di file?


3
Per un dato file old_file.txt, quindi git mv old_file.txt new_file.txtè equivalente a git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl,

3
Jarl: no non lo è. Se ci sono anche delle modifiche all'interno del file, git mvnon le aggiungeranno alla cache, ma lo git addfaranno. Preferisco spostare nuovamente il file in modo da poterlo utilizzare git mv, quindi git add -privedere il mio set di modifiche.
dhardy,



Git è migliorato negli ultimi 8 anni, se è solo un file, la risposta migliore stackoverflow.com/a/433142/459 non ha fatto nulla ... ma puoi seguire stackoverflow.com/a/1541072/459 per ottenere un rm / aggiungi aggiornato ad uno stato mv / modifica.
dlamblin,

Risposte:


436

Git rileverà automaticamente lo spostamento / rinomina se la modifica non è troppo grave. Solo git addil nuovo file e git rmil vecchio file. git statusmostrerà quindi se ha rilevato la ridenominazione.

inoltre, per spostarsi tra le directory, potrebbe essere necessario:

  1. cd all'inizio di quella struttura di directory.
  2. Correre git add -A .
  3. Esegui git statusper verificare che il "nuovo file" sia ora un file "rinominato"

Se lo stato di git mostra ancora "nuovo file" e non "rinominato", devi seguire i consigli di Hank Gay e fare le mosse e le modifiche in due commit separati.


75
Chiarimento: 'non troppo grave' significa che il nuovo file e il vecchio file sono> simili al 50% in base ad alcuni indici di somiglianza che git usa.
pjz,

151
Qualcosa che mi ha bloccato per alcuni minuti: se i file rinominati e i file eliminati non vengono messi in scena per il commit, verranno visualizzati come eliminazione e come nuovo file. Una volta aggiunti all'indice di gestione temporanea, lo riconoscerà come rinomina.
marczych,

19
Vale la pena ricordare che quando si parla di " Git rileverà automaticamente lo spostamento / rinomina ". Lo fa nel momento in cui lo usi git status, git logo git diff, non nel momento in cui lo fai git add, git mvo git rm. Inoltre parlando di rilevare la ridenominazione, questo ha senso solo per i file in scena. Quindi un git mvseguito da una modifica nel file può sembrare git statuscome se lo considerasse come rename, ma quando si utilizza git stage(uguale a git add) sul file, diventa chiaro che la modifica è troppo grande per essere rilevata come rinomina.
Jarl,

5
La vera chiave sembra essere quella di aggiungere sia la nuova che la vecchia posizione nella stessa git add, che come suggerisce @ jrhorn424, probabilmente dovrebbe essere l'intero repository in una sola volta.
brianary,

5
Questo non è infallibile, soprattutto se il file è cambiato ed è piccolo. Esiste un metodo per dire specificamente a git della mossa?
Rebs,

112

Fai la mossa e la modifica in commit separati.


12
Capisco che Git è in grado di gestire mosse e modifiche allo stesso tempo. Quando si programma in Java e si utilizza un IDE, rinominare una classe è sia una modifica che uno spostamento. Capisco che anche Git dovrebbe essere in grado di capirlo automaticamente quando c'è una mossa (fuori da una rimozione e una creazione).
pupeno,

1
Anche Perl lo richiede e non ho mai avuto Git in grado di rilevare la mossa / rinominare.
jrockway,

10
@jrockway, l'ho avuto. Succede facilmente con file di piccole dimensioni, immagino che "diventino 'troppo diversi' per significare una mossa".
ANeves,

2
C'è un modo per farlo automatizzato? Ho molti file nell'indice, voglio prima fare il commit della mossa e poi la modifica, ma è difficile fare manualmente
ReDetection

5
Una cosa da notare è che se git diffnon riconosce la ridenominazione in un commit, non lo riconoscerà in due commit. Avresti bisogno di usare -Maka --find-renamesper farlo. Quindi, se la motivazione che ti ha portato a questa domanda è vedere la ridenominazione in una richiesta pull (parlando per esperienza), dividerlo in due commit non ti spingerà verso quell'obiettivo.
Mattliu,

44

È tutto una cosa percettiva. Git è generalmente piuttosto bravo a riconoscere le mosse, perché GIT è un tracker di contenuti

Tutto ciò che dipende davvero è come la tua "stat" lo visualizza. L'unica differenza qui è il flag -M.

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log --stat

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git help log

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.

76
Scusate se questo sembra un po 'pedante, ma "Git è generalmente piuttosto bravo a riconoscere le mosse, perché GIT è un tracker di contenuti" mi sembra un non sequitur per me. È un tracker di contenuti, sì, e forse capita di essere bravo a rilevare le mosse, ma l'istruzione una non segue davvero l'altra. Solo perché è un localizzatore di contenuti il ​​rilevamento delle mosse non è necessariamente buono. In effetti, un tracker di contenuti potrebbe non avere alcun rilevamento di spostamento.
Laurence Gonsalves,

1
@WarrenSeine quando si rinomina una directory, gli SHA1 dei file in quella directory non cambiano. Tutto quello che hai è un nuovo oggetto TREE con gli stessi SHA1. Non vi è alcun motivo per cui la ridenominazione di una directory provocherebbe cambiamenti significativi nei dati.
Kent Fredric,

1
@KentFredric Hai mostrato che usando -M con "git log" possiamo verificare se il file è stato rinominato o meno e l'ho provato e funziona bene, ma quando pubblico il mio codice per la revisione e vedo quel file in gerrit ( gerritcodereview.com ) lì mostra che il file è stato appena aggiunto e il precedente è stato eliminato. Quindi esiste un'opzione in "git commit" con la quale faccio commit e gerrit lo mostra correttamente.
Patrick,

1
No. Non l'ho mostrato affatto. Ho mostrato che Git è in grado di far finta che un add + delete sia una ridenominazione perché il contenuto è lo stesso. Non c'è modo per sapere cosa è successo e non gliene importa. Tutto ciò che ricorda git è "aggiunto" ed "eliminato". "Rinominato" non viene mai registrato.
Kent Fredric,

1
Ad esempio, ultimamente ho fatto molto "Copia + Modifica" su un repository. La maggior parte dei modi di vederlo vede solo "nuovo file", ma se lo passi git log -M1 -C1 -B1 -D --find-copies-harder, git può "scoprire" che il nuovo file potrebbe essere stato copiato per primo. A volte lo fa bene, altre volte trova file completamente non correlati che hanno contenuto identico.
Kent Fredric,

36

git diff -Mo git log -Mdovrebbe rilevare automaticamente tali cambiamenti come una ridenominazione con modifiche minori purché effettivamente lo siano. Se le modifiche minori non sono minori, è possibile ridurre la soglia di somiglianza, ad es

$ git log -M20 -p --stat

per ridurlo dal 50% al 20% predefinito.


13
È possibile definire la soglia nella configurazione?
Rielezione

34

Ecco una soluzione rapida e sporca per uno o pochi file rinominati e modificati che non sono stati salvati.

Diciamo che il file è stato chiamato fooe ora si chiama bar:

  1. Rinomina barcon un nome temporaneo:

    mv bar side
    
  2. Cassa foo:

    git checkout HEAD foo
    
  3. Rinomina fooin barcon Git:

    git mv foo bar
    
  4. Ora rinominare il file temporaneo in bar.

    mv side bar
    

Quest'ultimo passaggio è ciò che riporta il contenuto modificato nel file.

Mentre questo può funzionare, se il file spostato ha un contenuto troppo diverso da quello originale, considererà più efficiente decidere che si tratta di un nuovo oggetto. Lasciami dimostrare:

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

    new file:   .gitignore
    renamed:    README -> README.md

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:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

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:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README.md
    modified:   work.js

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

    Rmd

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

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   work.js

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

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.

27
Questo processo non serve a nulla. git non aggiunge metadati per i nomi, git mvè semplicemente una comodità per una git rm/ git addcoppia. Se hai già eseguito "mv bar foo", tutto ciò che devi fare è assicurarti di averlo fatto git add fooe git rm barprima di effettuare il commit. Questo potrebbe essere fatto come un singolo git add -Acomando, o forse una git add foo; git commit -asequenza.
CB Bailey,

17
Tutto quello che so è che prima di farlo, Git non lo riconosceva come una mossa. Dopo averlo fatto, Git l'ha riconosciuto come una mossa.

8
Lo riconosce come una mossa. Ma ha anche il cambiamento come un cambiamento non messo in scena ora. Una volta di addnuovo il file, git spezza lo spostamento / modifica in un file cancellato / aggiunto di nuovo.
Michael Piefel,

4
Questo processo funzionerebbe se tu git commit dopo il passaggio 3, altrimenti non è corretto. Inoltre, @CharlesBailey è corretto, potresti fare altrettanto facilmente mv blah fooal passaggio 3 seguito da un commit e ottenere gli stessi risultati.
DaFlame,

5
"Questo processo non serve a nulla." - Ha uno scopo quando git è stato confuso e pensa che un file sia stato eliminato e che sia stato aggiunto un altro file. Questo cancella la confusione di Git e contrassegna il file come spostato esplicitamente senza dover eseguire commit intermedi.
Danack,

20

Se stai parlando di git statusnon mostrare i nomi, prova git commit --dry-run -ainvece


11

Se stai usando TortoiseGit è importante notare che il rilevamento automatico della ridenominazione di Git avviene durante il commit, ma il fatto che ciò accada non viene sempre visualizzato dal software in anticipo. Avevo spostato due file in una directory diversa ed eseguito alcune lievi modifiche. Uso TortoiseGit come strumento di commit e l'elenco Modifiche apportate mostrava che i file venivano eliminati e aggiunti, non spostati. L'esecuzione dello stato git dalla riga di comando ha mostrato una situazione simile. Tuttavia, dopo aver eseguito il commit dei file, sono stati rinominati nel registro. Quindi la risposta alla tua domanda è, fintanto che non hai fatto nulla di troppo drastico, Git dovrebbe riprendere automaticamente la ridenominazione.

Modifica: apparentemente se aggiungi i nuovi file e poi esegui uno stato git dalla riga di comando, la ridenominazione dovrebbe apparire prima di confermare.

Modifica 2: Inoltre, in TortoiseGit, aggiungi i nuovi file nella finestra di dialogo di commit, ma non impegnarli. Quindi se vai nel comando Show Log e guardi la directory di lavoro, vedrai se Git ha rilevato la ridenominazione prima di confermare.

La stessa domanda è stata sollevata qui: https://tortoisegit.org/issue/1389 ed è stata registrata come un bug da correggere qui: https://tortoisegit.org/issue/1440 Si scopre che è un problema di visualizzazione con il commit di TortoiseGit finestra di dialogo e esiste anche in stato git se non hai aggiunto i nuovi file.


2
Hai ragione, anche se TortoiseGit mostra cancella + aggiungi, anche se lo stato git mostra cancella + aggiungi anche se git commit --dry-run mostra cancella + aggiungi, dopo git commit vedo rinominare e non cancellare + aggiungere.
Tomas Kubes,

1
Sono abbastanza sicuro che il rilevamento automatico della ridenominazione avvenga durante il recupero della cronologia ; nel commit è sempre aggiungi + elimina. Questo spiega anche il comportamento schizofrenico che descrivi. Da qui le opzioni qui: stackoverflow.com/a/434078/155892
Mark Sowul

10

Oppure puoi provare la risposta a questa domanda qui da Amber ! Per citare di nuovo:

Innanzitutto, annulla l'aggiunta graduale per il file spostato manualmente:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Quindi, usa Git per spostare il file:

$ git mv path/to/oldfile path/to/newfile

Ovviamente, se hai già eseguito il trasferimento manuale, potresti voler ripristinare la revisione prima dello spostamento e poi semplicemente uscire da lì.


7

Utilizzare il git mvcomando per spostare i file, anziché i comandi di spostamento del sistema operativo: https://git-scm.com/docs/git-mv

Nota che il git mvcomando esiste solo nelle versioni Git 1.8.5 e successive. Quindi potresti dover aggiornare Git per usare questo comando.


3

Ho avuto questo problema di recente, quando ho spostato (ma non modificato) alcuni file.

Il problema è che Git ha cambiato alcune terminazioni di riga quando ho spostato i file e quindi non sono stato in grado di dire che i file erano gli stessi.

utilizzando git mv risolto il problema, ma funziona solo su singoli file / directory, e ho avuto un sacco di file nella radice del repository da fare.

Un modo per risolvere questo problema sarebbe con un po 'di magia bash / batch.

Un altro modo è il seguente

  • Sposta i file e git commit . Questo aggiorna le terminazioni di riga.
  • Spostare i file nella loro posizione originale, ora che hanno i nuovi finali di riga e git commit --amend
  • Spostare di nuovo i file e git commit --amend. Questa volta non ci sono cambiamenti ai finali di linea, quindi Git è felice

1

Per me ha funzionato per salvare tutte le modifiche prima del commit e saltarle di nuovo. Ciò ha reso git riesaminare i file aggiunti / eliminati e li ha contrassegnati correttamente come spostati.


0

Esiste probabilmente un modo migliore di "riga di comando" per farlo, e so che si tratta di un hack, ma non sono mai stato in grado di trovare una buona soluzione.

Utilizzo di TortoiseGIT: se si dispone di un commit GIT in cui alcune operazioni di spostamento dei file vengono visualizzate come carico di aggiunte / eliminazioni anziché rinominazioni, anche se i file presentano solo piccole modifiche, procedere come segue:

  1. Controlla cosa hai fatto localmente
  2. Effettua il check in una mini modifica di una riga in un secondo commit
  3. Vai al registro GIT in guscio di tartaruga
  4. Seleziona i due commit, fai clic con il pulsante destro del mouse e seleziona "Unisci in un commit"

Il nuovo commit ora mostrerà correttamente i nomi dei file ... che aiuteranno a mantenere la cronologia dei file corretta.


0

Quando modifico, rinomino e sposto un file contemporaneamente, nessuna di queste soluzioni funziona. La soluzione è farlo in due commit (modifica e rinomina / sposta separati) e quindi fixupil secondo commit tramite git rebase -iper averlo in un commit.


0

Il modo in cui capisco questa domanda è "Come far riconoscere a git la cancellazione di un vecchio file e la creazione di un nuovo file come mossa di un file".

Sì nella directory di lavoro dopo aver eliminato un vecchio file e inserito un vecchio file, git statusverrà visualizzato " deleted: old_file" e " Untracked files: ... new_file"

Ma nell'indice / livello di gestione temporanea una volta aggiunto e rimosso il file usando git, verrà riconosciuto come spostamento di file. Per fare ciò, supponendo che tu abbia fatto la cancellazione e la creazione usando il tuo Sistema Operativo, dai i seguenti comandi:

git add new_file
git rm old_file

Se il contenuto del file è simile al 50% o più, il git statuscomando in esecuzione dovrebbe fornire:

renamed: old_file -> new_file

1
git statusdirà " deleted: old_file" e " Untracked files: ... new_file": Era dai tempi di Git 2.18: stackoverflow.com/a/50573107/6309
VonC
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.