Como fazer com que o git marque um arquivo excluído e um novo como um arquivo é movido?

505

Mudei um arquivo manualmente e depois o modifiquei. De acordo com o Git, é um novo arquivo e um arquivo removido. Existe alguma maneira de forçar o Git a tratá-lo como uma movimentação de arquivo?

pupeno
fonte
3
Para um determinado arquivo old_file.txt, então 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: não, não é. Se também houver alterações no arquivo, git mvelas não serão adicionadas ao cache, mas git addsim. Prefiro mover o arquivo de volta para que eu possa usá-lo git mve depois git add -previsar meu conjunto de alterações.
dhardy
verifique meu script github.com/Deathangel908/python-tricks/blob/master/…
deathangel908
O Git melhorou nos últimos 8 anos, se for apenas um arquivo, a resposta principal stackoverflow.com/a/433142/459 não fez nada… mas você pode seguir stackoverflow.com/a/1541072/459 para obter uma atualização / atualização para um status mv / modify.
precisa saber é o seguinte

Respostas:

435

O Git detectará automaticamente a mudança / renomeação se sua modificação não for muito severa. Apenas git addo novo arquivo e git rmo arquivo antigo. git statusmostrará se detectou a renomeação.

Além disso, para movimentar diretórios, pode ser necessário:

  1. cd na parte superior dessa estrutura de diretório.
  2. Corre git add -A .
  3. Execute git statuspara verificar se o "novo arquivo" agora é um arquivo "renomeado"

Se o status do git ainda mostra "novo arquivo" e não "renomeado", você precisa seguir o conselho de Hank Gay, fazer a movimentação e modificar em dois commits separados.

Bombe
fonte
75
Esclarecimento: 'não muito grave' significa que o novo arquivo e o arquivo antigo são> 50% 'similares' com base em alguns índices de similaridade que o git usa.
pjz
151
Algo que me interrompeu por alguns minutos: se os arquivos renomeados e os arquivos excluídos não forem testados para confirmação, eles aparecerão como uma exclusão e um novo arquivo. Depois de adicioná-los ao índice intermediário, ele será reconhecido como uma renomeação.
marczych
19
Vale ressaltar que ao falar em " Git detectará automaticamente o movimento / renomear ". Faz isso no momento em que você usa git status, git logou git diff, não no momento em que usa git add, git mvou git rm. Falando mais sobre a detecção de renomeação, isso só faz sentido para arquivos em etapas. Portanto, um git mvseguido por uma alteração no arquivo pode parecer git statuscomo um rename, mas quando você usa git stage(o mesmo que git add) no arquivo, fica claro que a alteração é muito grande para ser detectada como uma renomeação.
Jarl
5
A chave real parece ser adicionar os locais novos e antigos no mesmo git add, o que, como sugere @ jrhorn424, provavelmente deve ser apenas o repositório inteiro de uma só vez.
Brianary
4
Porém, isso não é infalível, especialmente se o arquivo foi alterado e é pequeno. Existe um método para contar especificamente ao git sobre a mudança?
Rebs
112

Faça a movimentação e a modificação em confirmações separadas.

Hank Gay
fonte
12
Eu entendo que o Git é capaz de lidar com movimentos e modificações ao mesmo tempo. Ao programar em Java e usar um IDE, renomear uma classe é uma modificação e uma mudança. Eu entendo que o Git ainda deve ser capaz de descobrir automaticamente quando houver uma mudança (fora de uma remoção e uma criação).
pupeno
1
O Perl também exige isso, e nunca o Git detectou a mudança / renomeação.
jrockway
10
@jrockway, eu tinha. Acontece facilmente com arquivos pequenos, acho que eles "se tornam 'muito diferentes' para significar uma mudança".
ANeves
2
Existe alguma maneira de fazer isso automatizado? Eu tenho muitos arquivos no índice, querem primeiro cometer o movimento, e então a mudança, mas é difícil de fazer manualmente
Redetecção
5
Uma coisa a observar é que, se git diffnão reconhecer a renomeação em um commit, ele não o reconhecerá em dois commits. Você precisaria usar o -Maka --find-renamespara fazer isso. Portanto, se a motivação que o levou a essa pergunta é ver a renomeação em uma solicitação pull (falando por experiência), dividi-la em duas confirmações não o levará mais a atingir esse objetivo.
mattliu
44

É tudo uma coisa perceptiva. O Git geralmente é bom em reconhecer movimentos, porque o GIT é um rastreador de conteúdo

Tudo o que realmente depende é como o seu "stat" exibe. A única diferença aqui é o sinalizador -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(-)

log de ajuda do git

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.
Kent Fredric
fonte
76
Desculpe se isso parece um pouco pedante, mas "o Git geralmente é bom em reconhecer movimentos, porque o GIT é um rastreador de conteúdo" parece um não-sequitur para mim. É um rastreador de conteúdo, sim, e talvez seja bom em detectar movimentos, mas uma afirmação não se segue da outra. Só porque é um rastreador de conteúdo, a detecção de movimentação não é necessariamente boa. De fato, um rastreador de conteúdo não pode ter nenhuma detecção de movimento.
Laurence Gonsalves
1
@WarrenSeine quando você renomeia um diretório, o SHA1 dos arquivos nesse diretório não muda. Tudo o que você tem é um novo objeto TREE com os mesmos SHA1s. Não há razão para que uma renomeação de diretório cause alterações significativas nos dados.
Kent Fredric
1
@KentFredric Você mostrou que, ao usar -M com "git log", podemos verificar se o arquivo é renomeado ou não e tentei e está funcionando bem, mas quando eu posto meu código para revisão e vejo esse arquivo no gerrit ( gerritcodereview.com ) lá mostra que o arquivo foi adicionado recentemente e o anterior foi excluído. Portanto, existe uma opção no "git commit" usando o que eu confirmo e o gerrit mostra corretamente.
726 Patrick
1
Não. Eu não mostrei nada disso. Eu mostrei que o Git é capaz de fingir que um add + delete é renomeado devido ao conteúdo ser o mesmo. Não há como saber o que aconteceu, e isso não se importa. Todos os lembretes do git são "adicionados" e "excluídos". "Renomeado" nunca é gravado.
Kent Fredric
1
Por exemplo, fiz um monte de "Copiar + Editar" em um repositório recentemente. A maioria das maneiras de visualizá-lo vê apenas "novo arquivo", mas se você passar git log -M1 -C1 -B1 -D --find-copies-harder, o git poderá "descobrir" que o novo arquivo pode ter sido copiado primeiro. Às vezes, ele faz isso direito, outras vezes, encontra arquivos totalmente não relacionados que tinham conteúdo idêntico.
Kent Fredric
36

git diff -Mou git log -Mdeve detectar automaticamente tais alterações, como uma renomeação, com pequenas alterações , desde que realmente sejam. Se suas pequenas alterações não forem menores, você poderá reduzir a semelhança;

$ git log -M20 -p --stat

reduzi-lo dos 50% para 20% padrão.

Simon East
fonte
13
É possível definir o limite na configuração?
ReDetection
34

Aqui está uma solução rápida e suja para um ou alguns arquivos renomeados e modificados que não foram confirmados.

Digamos que o arquivo foi nomeado fooe agora é nomeado bar:

  1. Renomeie barpara um nome temporário:

    mv bar side
    
  2. Saída foo:

    git checkout HEAD foo
    
  3. Renomeie foopara barcom Git:

    git mv foo bar
    
  4. Agora renomeie seu arquivo temporário para bar.

    mv side bar
    

Esta última etapa é o que coloca seu conteúdo alterado de volta no arquivo.

Embora isso possa funcionar, se o arquivo movido tiver um conteúdo muito diferente do git original, será mais eficiente decidir que esse é um novo objeto. Deixe-me demonstrar:

$ 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.
dlamblin
fonte
27
Este processo não serve para nada. O git não adiciona nenhum metadado para renomear, git mvé simplesmente uma conveniência para um git rm/ git addpar. Se você já fez 'mv bar foo', tudo o que você precisa fazer é ter certeza de que já fez git add fooe git rm barantes de fazer o commit. Isso pode ser feito como um único git add -Acomando, ou possivelmente uma git add foo; git commit -asequência.
CB Bailey
17
Tudo o que sei é que, antes de fazê-lo, o Git não o reconheceu como um movimento. Depois que fiz isso, o Git reconheceu isso como um movimento.
8
Ele reconhece isso como um movimento. Mas também tem a mudança como uma mudança sem etapas agora. Uma vez que você addarquivo novamente, o git divide a mudança / modificação em uma excluída / adicionada novamente.
Michael Piefel
4
Este processo funcionaria se você git commitapós a etapa 3, caso contrário, está incorreto. Além disso, @CharlesBailey está correto, você poderia facilmente fazer um normal mv blah foona etapa 3, seguido de uma confirmação e obter os mesmos resultados.
DaFlame
5
"Este processo não serve para nada." - Ele serve para um propósito quando o git fica confuso e pensa que um arquivo foi excluído e outro arquivo foi adicionado. Isso esclarece a confusão do Git e marca o arquivo como movido explicitamente, sem ter que fazer confirmações intermediárias.
Danack 06/06
20

Se você está falando git statusnão mostrando as renomeações, tente git commit --dry-run -aem vez

BoD
fonte
11

Se você estiver usando o TortoiseGit, é importante observar que a detecção automática de renomeação do Git ocorre durante o commit, mas o fato de que isso vai acontecer nem sempre é exibido pelo software de antemão. Mudei dois arquivos para um diretório diferente e realizei algumas edições leves. Eu uso o TortoiseGit como minha ferramenta de confirmação e a lista Alterações feitas mostra os arquivos sendo excluídos e adicionados, não movidos. A execução do status git na linha de comando mostrou uma situação semelhante. No entanto, após confirmar os arquivos, eles apareceram como sendo renomeados no log. Portanto, a resposta para sua pergunta é: desde que você não tenha feito algo muito drástico, o Git deve mudar o nome automaticamente.

Editar: Aparentemente, se você adicionar os novos arquivos e, em seguida, executar um status git na linha de comando, a renomeação deverá aparecer antes de confirmar.

Editar 2: Além disso, no TortoiseGit, adicione os novos arquivos na caixa de diálogo de confirmação, mas não os confirme. Então, se você entrar no comando Show Log e examinar o diretório de trabalho, verá se o Git detectou a renomeação antes de confirmar.

A mesma pergunta foi levantada aqui: https://tortoisegit.org/issue/1389 e foi registrada como um bug para corrigir aqui: https://tortoisegit.org/issue/1440 Acontece que é um problema de exibição no commit do TortoiseGit diálogo e também existe no status git se você não adicionou os novos arquivos.

Giles Roberts
fonte
2
Você está certo, mesmo que o TortoiseGit mostre delete + add, mesmo que o status do git mostre delete + add, mesmo que o git commit --dry-run mostre delete + add, depois que o git commit eu vejo renomear e não delete + add.
Tomas Kubes
1
Tenho certeza de que a detecção automática de renomeação acontece durante a recuperação do histórico ; no commit é sempre adicionar + excluir. Isso também explica o comportamento esquizofrênico que você descreve. Daí as opções aqui: stackoverflow.com/a/434078/155892
Mark Sowul
10

Ou você pode tentar a resposta a esta pergunta aqui de Amber ! Para citar novamente:

Primeiro, cancele sua adição faseada para o arquivo movido manualmente:

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

Em seguida, use Git para mover o arquivo:

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

Obviamente, se você já cometeu a movimentação manual, convém redefinir a revisão antes da mudança e simplesmente git mv a partir daí.

Zingam
fonte
7

Use o git mvcomando para mover os arquivos, em vez dos comandos de movimentação do SO: https://git-scm.com/docs/git-mv

Observe que o git mvcomando existe apenas nas versões 1.8.5 e superior do Git. Então você pode ter que atualizar seu Git para usar este comando.

mostafa.elhoushi
fonte
3

Eu tive esse problema recentemente, ao mover (mas não modificar) alguns arquivos.

O problema é que o Git alterou algumas terminações de linha quando mudei os arquivos e não soube dizer que os arquivos eram os mesmos.

Usando git mvresolvido o problema, mas ele só funciona em arquivos / diretórios únicos, e eu tinha muitos arquivos na raiz do repositório para fazer.

Uma maneira de consertar isso seria com alguma mágica de bash / batch.

Outra maneira é a seguinte

  • Mova os arquivos e git commit. Isso atualiza as terminações de linha.
  • Mova os arquivos de volta para o local original, agora que eles têm as novas terminações de linha e git commit --amend
  • Mova os arquivos novamente e git commit --amend. Desta vez, não há alterações nas terminações da linha, portanto o Git está feliz
cedd
fonte
1

Para mim, trabalhou para esconder todas as alterações antes do commit e exibi-las novamente. Isso fez o git re-analisar os arquivos adicionados / excluídos e os marcou corretamente como movidos.

Alex Ilie
fonte
0

Provavelmente existe uma maneira melhor de "linha de comando" para fazer isso, e eu sei que isso é um hack, mas nunca consegui encontrar uma boa solução.

Usando o TortoiseGIT: Se você possui uma confirmação GIT em que algumas operações de movimentação de arquivos são exibidas como carga de adições / exclusões em vez de renomear, mesmo que os arquivos tenham apenas pequenas alterações, faça o seguinte:

  1. Verifique o que você fez localmente
  2. Faça check-in de uma mini alteração de uma linha em uma segunda confirmação
  3. Vá para o log do GIT no tartaruga git
  4. Selecione as duas confirmações, clique com o botão direito e selecione "mesclar em uma confirmação"

A nova confirmação agora mostrará corretamente o nome do arquivo ... o que ajudará a manter o histórico adequado do arquivo.

Jon Rea
fonte
0

Quando edito, renomeio e movo um arquivo ao mesmo tempo, nenhuma dessas soluções funciona. A solução é fazer isso em duas confirmações (editar e renomear / mover separadamente) e, em seguida, fixupa segunda confirmação via git rebase -itê-lo em uma confirmação.

Hativ
fonte
0

A maneira como entendo essa pergunta é "Como fazer o git reconhecer a exclusão de um arquivo antigo e a criação de um novo arquivo à medida que o arquivo é movido".

Sim no diretório de trabalho, uma vez que você exclua um arquivo antigo e inseriu um arquivo antigo, git statusdirá " deleted: old_file" e " Untracked files: ... new_file"

Porém, no índice / nível de armazenamento temporário, depois de adicionar e remover arquivos usando o git, ele será reconhecido como uma movimentação de arquivos. Para fazer isso, supondo que você tenha excluído e criado usando o Sistema Operacional, forneça os seguintes comandos:

git add new_file
git rm old_file

Se o conteúdo do arquivo for 50% ou mais semelhante, o git statuscomando running deve fornecer:

renamed: old_file -> new_file
harshainfo
fonte
1
git statusdirá " deleted: old_file" e " Untracked files: ... new_file": Desde o Git 2.18: stackoverflow.com/a/50573107/6309
VonC