Manipulando Renomeação de Arquivos no Git

440

Eu li isso ao renomear arquivos no git , você deve confirmar quaisquer alterações, executar sua renomeação e depois preparar seu arquivo renomeado. O Git reconhecerá o arquivo a partir do conteúdo, em vez de vê-lo como um novo arquivo não rastreado, e manterá o histórico de alterações.

No entanto, fazendo exatamente isso esta noite, acabei revertendo para git mv.

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

Renomeie minha folha de estilo no Finder de iphone.cssparamobile.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

Então o git agora acha que excluí um arquivo CSS e adicionei um novo. Não é o que eu quero, vamos desfazer a renomeação e deixar o git fazer o trabalho.

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

Voltar para onde eu comecei.

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

Vamos usar em seu git mvlugar.

> $ 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
#

Parece que estamos bem. Então, por que o git não reconheceu o nome da primeira vez que usei o Finder?

Greg K
fonte
29
O Git rastreia o conteúdo, não os arquivos, por isso não importa como você coloca seu índice no estado adequado - add+rmou mv- produz o mesmo resultado. O Git usa sua detecção de renomeação / cópia para que você saiba que foi uma renomeação. A fonte que você citou também é imprecisa. Realmente não importa se você modifica + renomear no mesmo commit ou não. Quando você faz uma diferença entre modificar e renomear, a detecção de renomeação será vista como uma renomeação + modificação, ou se a modificação for uma reescrita total, será exibida como adicionada e excluída - ainda não importa como você executou isto.
quer
6
Se isso for verdade, por que não o detectou com minha renomeação usando o Finder?
Greg K
26
git mv old newatualiza automaticamente o índice. Ao renomear fora do Git, você precisará fazer git add newe git rm oldrealizar as alterações no índice. Depois de ter feito isso git statusfuncionará como esperado.
precisa
4
Acabei de mover um monte de arquivos para um public_htmldiretório, que é rastreado no git. Tendo realizado git add .e git commit, ainda mostrou um monte de arquivos 'excluídos' no git status. Eu executei um git commit -ae as exclusões foram confirmadas, mas agora não tenho histórico nos arquivos que vivem public_htmlagora. Esse fluxo de trabalho não é tão bom quanto eu gostaria.
Greg K

Respostas:

352

Para git mva página do manual diz

O índice é atualizado após a conclusão bem-sucedida, […]

Portanto, primeiro você deve atualizar o índice por conta própria (usando git add mobile.css). No entanto
git status , ainda mostrará dois arquivos diferentes

$ 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
#

Você pode obter uma saída diferente executando git commit --dry-run -a, o que resulta no que você espera:

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
#

Não sei dizer exatamente por que vemos essas diferenças entre git statuse
git commit --dry-run -a, mas aqui está uma dica de Linus :

O git realmente nem se importa com toda a "detecção de renomeação" internamente, e quaisquer confirmações feitas com renomeação são totalmente independentes das heurísticas que usamos para mostrar as renomeações.

A dry-runusa os mecanismos de renomeação reais, enquanto git statusprovavelmente não.

tanascius
fonte
1
Você não mencionou a etapa em que fez git add mobile.css. Sem ele git status -a, teria "visto" apenas a remoção do iphone.cssarquivo rastreado anteriormente, mas não teria tocado no novo mobile.cssarquivo não rastreado . Além disso, git status -aé inválido no Git 1.7.0 e posterior. "" Status git "não é mais" git commit --dry-run ".” em kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . Use git commit --dry-run -ase você quiser essa funcionalidade. Como já foi dito, basta atualizar o índice e git statusfuncionará como o OP espera.
quer
3
se você fizer um normal, git commitele não confirmará o arquivo renomeado e a árvore de trabalho ainda será a mesma. git commit -aderrota praticamente todos os aspectos do modelo de fluxo de trabalho / pensamento do git - toda mudança é comprometida. e se você quiser renomear o arquivo apenas, mas confirmar as alterações index.htmlem outro commit?
knittl
@ Chris: sim, é claro que eu adicionei o mobile.cssque eu deveria ter mencionado. Mas esse é o ponto da minha resposta: a página de manual diz isso the index is updatedquando você usa git-mv. Obrigado pelo status -aesclarecimento, eu usei git 1.6.4
tanascius
4
Ótima resposta! Eu estava batendo minha cabeça contra a parede tentando descobrir por que git statusnão estava detectando a renomeação. A execução git commit -a --dry-runapós adicionar meus "novos" arquivos mostrou os renomeados e finalmente me deu confiança para confirmar!
stephen.hanson
1
git statusAgora no git 1.9.1 se comporta como git commit.
Jacques René Mesrine
77

Você precisa adicionar os dois arquivos modificados ao índice antes que o git o reconheça como um movimento.

A única diferença entre mv old newe git mv old newé que o git mv também adiciona os arquivos ao índice.

mv old newentão git add -Ateria funcionado também.

Note que você não pode simplesmente usar git add . porque isso não adiciona remoções ao índice.

Consulte Diferença entre "git add -A" e "git add".

não retangular
fonte
3
Obrigado pelo git add -Alink, muito útil, pois eu estava procurando por esse atalho!
PhiLho
5
Note-se que com git 2, git add . não adicionar remoções ao índice.
Nick McCurdy
19

O melhor é tentar você mesmo.

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

Agora, o git status e o git commit --dry-run -a mostram dois resultados diferentes, nos quais o status git mostra bbb.txt quando um novo arquivo / aaa.txt é excluído, e os comandos --dry-run mostram a renomeação real.

~/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
#

Agora vá em frente e faça o check-in.

git commit -a -m "Rename"

Agora você pode ver que o arquivo é renomeado e o que é mostrado no status git está errado.

Moral da história: se você não tiver certeza se seu arquivo foi renomeado, emita um "git commit --dry-run -a". Se estiver mostrando que o arquivo foi renomeado, você estará pronto.

dimuthu
fonte
3
Para o que importa para o Git, ambos estão corretos . O último está mais perto de como você, como comissário, provavelmente o vê. A diferença real entre renomear e excluir + criar é apenas no nível do sistema operacional / sistema de arquivos (por exemplo, o mesmo inode # vs. new inode #), com o qual o Git realmente não se importa.
Alois Mahdal
15

Para o git 1.7.x, os seguintes comandos funcionaram para mim:

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

Não havia necessidade do git add, pois o arquivo original (por exemplo, css / mobile.css) já estava nos arquivos confirmados anteriormente.

GrigorisG
fonte
5
Este. Todas as outras respostas são ridiculamente e desnecessariamente complexas. Isso mantém o histórico de arquivos entre confirmações, para que as mesclagens antes / depois da renomeação do arquivo não sejam quebradas.
Phlucious
10

você precisa git add css/mobile.cssdo novo arquivo e git rm css/iphone.css, portanto, o git sabe disso. então mostrará a mesma saída emgit status

você pode vê-lo claramente na saída de status (o novo nome do arquivo):

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

e (o nome antigo):

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

Eu acho que nos bastidores git mvnada mais é do que um script wrapper que faz exatamente isso: exclua o arquivo do índice e adicione-o com um nome diferente

knittl
fonte
Não achei que fosse necessário, git rm css/iphone.cssporque achei que isso removeria a história existente. Talvez eu esteja entendendo mal o fluxo de trabalho no git.
Greg K
4
@ Greg K: git rmnão removerá o histórico. Ele remove apenas uma entrada do índice para que o próximo commit não tenha a entrada. No entanto, ele ainda existirá nos commits ancestrais. O que você pode estar confuso é que (por exemplo) git log -- newirá parar no ponto em que você se comprometeu git mv old new. Se você deseja seguir os nomes renomeados, use git log --follow -- new.
quer
9

Vamos pensar nos seus arquivos da perspectiva do git.

Lembre-se de que o git não rastreia nenhum metadado sobre seus arquivos

Seu repositório possui (entre outros)

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

e está sob controle git:

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

Teste isso com:

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

Quando você faz

$ mv iphone.css mobile.css

Da perspectiva do git,

  • não há iphone.css (ele é excluído -git adverte sobre isso-).
  • existe um novo arquivo mobile.css .
  • Esses arquivos são totalmente independentes.

Portanto, o git aconselha sobre arquivos que ele já conhece ( iphone.css ) e novos arquivos que ele detecta ( mobile.css ), mas somente quando os arquivos estão no índice ou no HEAD, o git começa a verificar seu conteúdo.

No momento, nem a "exclusão do iphone.css" nem o mobile.css estão no índice.

Adicione a exclusão do iphone.css ao índice

$ git rm iphone.css

O git informa exatamente o que aconteceu: ( iphone.css é excluído. Nada mais aconteceu)

adicione o novo arquivo mobile.css

$ git add mobile.css

Desta vez, a exclusão e o novo arquivo estão no índice. Agora, o git detecta o contexto e o expõe como um renomeado. De fato, se os arquivos forem 50% semelhantes, ele detectará isso como uma renomeação, permitindo alterar mobile.css um pouco, mantendo a operação como uma renomeação.

Veja que isso é reproduzível em git diff. Agora que seus arquivos estão no índice, você deve usar --cached. Edite mobile.css um pouco, adicione-o ao índice e veja a diferença entre:

$ git diff --cached 

e

$ git diff --cached -M

-Mé a opção "detectar renomeação" para git diff. -Msignifica -M50%(50% ou mais de semelhança fará o git expressá-lo como renomeado), mas você pode reduzi-lo para -M20%(20%) se editar muito o mobile.css.

albfan
fonte
8

Etapa 1: renomear o arquivo de oldfile para newfile

git mv #oldfile #newfile

Etapa 2: git confirmar e adicionar comentários

git commit -m "rename oldfile to newfile"

Etapa 3: envie essa alteração para servidor remoto

git push origin #localbranch:#remotebranch
Haimei
fonte
1
Por favor, adicione algum comentário para que seja útil para OP
Devrath
O passo 2 é desnecessário. Depois git mv, o novo arquivo já está no índice.
Alois Mahdal
7

O Git reconhecerá o arquivo a partir do conteúdo, em vez de vê-lo como um novo arquivo não rastreado

Foi aí que você errou.

Somente após você adicionar o arquivo, o git o reconhecerá no conteúdo.

hasen
fonte
Exatamente. Quando testado, o git mostrará a renomeação corretamente.
Max MacLeod
3

Você não encenou os resultados do seu movimento de busca. Acredito que se você fizesse a mudança via Finder e o fizesse git add css/mobile.css ; git rm css/iphone.css, o git calcularia o hash do novo arquivo e só então perceberia que os hashes dos arquivos correspondem (e, portanto, é uma renomeação).

Mike Seplowitz
fonte
2

Nos casos em que você realmente precisa renomear os arquivos manualmente, por exemplo. usando um script para renomear em lote um monte de arquivos e, em seguida, usando git add -A .funcionou para mim.

pckben
fonte
2

Para usuários do Xcode: Se você renomear seu arquivo no Xcode, verá o ícone do emblema mudar para acrescentar. Se você fizer uma confirmação usando o XCode, criará um novo arquivo e perderá o histórico.

Uma solução alternativa é fácil, mas você deve fazer isso antes de confirmar o uso do Xcode:

  1. Faça um status git na sua pasta. Você deve ver que as alterações faseadas estão corretas:

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

  1. faça commit -m 'mudança de nome'

Em seguida, volte ao XCode e você verá o distintivo alterado de A para M, e é possível salvar alterações adicionais ao usar o xcode agora.

doozMen
fonte