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?
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.
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:
cd na parte superior dessa estrutura de diretório.
Corre git add -A .
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.
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.
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.
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;
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:
Renomeie barpara um nome temporário:
mv bar side
Saída foo:
git checkout HEAD foo
Renomeie foopara barcom Git:
git mv foo bar
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.
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
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.
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:
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.
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:
Verifique o que você fez localmente
Faça check-in de uma mini alteração de uma linha em uma segunda confirmação
Vá para o log do GIT no tartaruga git
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.
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.
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:
old_file.txt
, entãogit mv old_file.txt new_file.txt
é equivalente agit rm --cached old_file.txt
,mv old_file.txt new_file.txt
,git add new_file.txt
.git mv
elas não serão adicionadas ao cache, masgit add
sim. Prefiro mover o arquivo de volta para que eu possa usá-logit mv
e depoisgit add -p
revisar meu conjunto de alterações.Respostas:
O Git detectará automaticamente a mudança / renomeação se sua modificação não for muito severa. Apenas
git add
o novo arquivo egit rm
o arquivo antigo.git status
mostrará se detectou a renomeação.Além disso, para movimentar diretórios, pode ser necessário:
git add -A .
git status
para 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.
fonte
git status
,git log
ougit diff
, não no momento em que usagit add
,git mv
ougit rm
. Falando mais sobre a detecção de renomeação, isso só faz sentido para arquivos em etapas. Portanto, umgit mv
seguido por uma alteração no arquivo pode parecergit status
como umrename
, mas quando você usagit stage
(o mesmo quegit add
) no arquivo, fica claro que a alteração é muito grande para ser detectada como uma renomeação.git add
, o que, como sugere @ jrhorn424, provavelmente deve ser apenas o repositório inteiro de uma só vez.Faça a movimentação e a modificação em confirmações separadas.
fonte
git diff
não reconhecer a renomeação em um commit, ele não o reconhecerá em dois commits. Você precisaria usar o-M
aka--find-renames
para 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.É 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
git log --stat
log de ajuda do git
fonte
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.git diff -M
ougit log -M
deve 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;reduzi-lo dos 50% para 20% padrão.
fonte
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
foo
e agora é nomeadobar
:Renomeie
bar
para um nome temporário:Saída
foo
:Renomeie
foo
parabar
com Git:Agora renomeie seu arquivo temporário para
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:
fonte
git mv
é simplesmente uma conveniência para umgit rm
/git add
par. Se você já fez 'mv bar foo', tudo o que você precisa fazer é ter certeza de que já fezgit add foo
egit rm bar
antes de fazer o commit. Isso pode ser feito como um únicogit add -A
comando, ou possivelmente umagit add foo; git commit -a
sequência.add
arquivo novamente, o git divide a mudança / modificação em uma excluída / adicionada novamente.git commit
após a etapa 3, caso contrário, está incorreto. Além disso, @CharlesBailey está correto, você poderia facilmente fazer um normalmv blah foo
na etapa 3, seguido de uma confirmação e obter os mesmos resultados.Se você está falando
git status
não mostrando as renomeações, tentegit commit --dry-run -a
em vezfonte
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.
fonte
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:
Em seguida, use Git para mover o arquivo:
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í.
fonte
Use o
git mv
comando para mover os arquivos, em vez dos comandos de movimentação do SO: https://git-scm.com/docs/git-mvObserve que o
git mv
comando 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.fonte
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 mv
resolvido 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
git commit
. Isso atualiza as terminações de linha.git commit --amend
git commit --amend
. Desta vez, não há alterações nas terminações da linha, portanto o Git está felizfonte
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.
fonte
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:
A nova confirmação agora mostrará corretamente o nome do arquivo ... o que ajudará a manter o histórico adequado do arquivo.
fonte
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,
fixup
a segunda confirmação viagit rebase -i
tê-lo em uma confirmação.fonte
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 status
dirá "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:
Se o conteúdo do arquivo for 50% ou mais semelhante, o
git status
comando running deve fornecer:fonte
git status
dirá "deleted: old_file
" e "Untracked files: ... new_file
": Desde o Git 2.18: stackoverflow.com/a/50573107/6309