Qual é o propósito do git-mv?

287

Pelo que entendi, o Git não precisa rastrear operações de renomeação / movimentação / cópia de arquivos , então qual é o real objetivo do git mv ? A página de manual não é especialmente descritiva ...

Isso é obsoleto? É um comando interno, não destinado a ser usado por usuários regulares?

Mauricio Scheffer
fonte

Respostas:

390
git mv oldname newname

é apenas uma abreviação para:

mv oldname newname
git add newname
git rm oldname

isto é, atualiza o índice para caminhos antigos e novos automaticamente.

CB Bailey
fonte
38
Também tem alguns seguranças construído dentro.
Jakub Narębski
6
Obrigado @CharlesBailey - O git considera os arquivos newNameFile e oldNameFile como diferentes? Se sim, o que acontece se queremos mesclá-los? Digamos que ramifiquemos um projeto formiga na ramificação A e criassemos a Ramificação B e depois projetos em B. Os nomes dos arquivos são os mesmos, mas são colocados em caminhos diferentes conforme a estrutura do projeto é alterada. Digamos que os dois galhos cresceram por algum tempo em paralelo. Em algum momento, se quisermos mesclar os projetos, como o git saberá que é o mesmo arquivo que acabou de renomear seu caminho? (se "git mv" == "git add + git rm")
Rose
2
@SergeyOrshanskiy Se a detecção automática der errado mv oldname newname; git add newname; git rm oldname, também irá dar errado git mv oldname newname(veja esta resposta ).
Ajedi32
5
Observe que git mvé um pouco diferente do mv oldname newname; git add newname; git rm oldnameque, se você fez alterações no arquivo antes git mvdele, essas alterações não serão testadas até você criar git addo novo arquivo.
Ajedi32
2
git mv está fazendo algo diferente, como ele lida com mudanças em caso de nome de arquivo (foo.txt para foo.txt), enquanto os comandos individualmente correr não (no OSX)
greg.kindel
66

Do GitFaq oficial :

O Git tem um comando de renomeação git mv, mas isso é apenas uma conveniência. O efeito é indistinguível de remover o arquivo e adicionar outro com nome diferente e o mesmo conteúdo

Adam Nofsinger
fonte
8
Então você perde o histórico de arquivos? Eu estava presume renomeação seria manter o antigo histórico para este diretório ...
Will Hancock
17
Bem, sim e não. Leia o link oficial do GitFaq acima sobre renomeações e, em seguida, leia o e-mail longo de Linus Torvalds sobre por que ele não gosta da noção de arquivos de rastreamento de ferramentas SCM: permalink.gmane.org/gmane.comp.version-control.git/ 217
Adam Nofsinger
3
@WillHancock Eu usei o git um pouco mais agora e posso respondê-lo de forma mais definitiva: dependendo do seu cliente git e de suas opções, você poderá rastrear o arquivo após a renomeação se o arquivo for alterado internamente pouco o suficiente para ser considerado um renomear. Se você alterar muito o arquivo E renomeá-lo, o git não o detectará - em certo sentido, ele está dizendo "não, você pode considerar um arquivo completamente diferente!"
Adam Nofsinger 6/03/2015
7
@AdamNofsinger esse link está morto. Aqui está um espelho: web.archive.org/web/20150209075907/http://…
Carl Walsh
2
Existe uma referência oficial (ou seja, mais digna de empuxo que uma FAQ) que afirma a equivalência entre git mve a abordagem manual? Não é óbvio git help mv.
tvo 14/12
40

O Git está apenas tentando adivinhar o que você está tentando fazer. Está fazendo todos os esforços para preservar a história ininterrupta. Claro, não é perfeito. Assim, git mvvocê pode ser explícito com sua intenção e evitar alguns erros.

Considere este exemplo. Começando com um repositório vazio,

git init
echo "First" >a
echo "Second" >b
git add *
git commit -m "initial commit"
mv a c
mv b a
git status

Resultado:

# On branch master
# 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)
#
#   modified:   a
#   deleted:    b
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   c
no changes added to commit (use "git add" and/or "git commit -a")

A detecção automática falhou :( Ou foi?

$ git add *
$ git commit -m "change"
$ git log c

commit 0c5425be1121c20cc45df04734398dfbac689c39
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:56 2013 -0400

    change

e depois

$ git log --follow c

Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:56 2013 -0400

    change

commit 50c2a4604a27be2a1f4b95399d5e0f96c3dbf70a
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:45 2013 -0400

    initial commit

Agora tente (lembre-se de excluir a .gitpasta ao experimentar):

git init
echo "First" >a
echo "Second" >b
git add *
git commit -m "initial commit"
git mv a c
git status

Por enquanto, tudo bem:

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


git mv b a
git status

Agora, ninguém é perfeito:

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a
#   deleted:    b
#   new file:   c
#

Realmente? Mas é claro...

git add *
git commit -m "change"
git log c
git log --follow c

... e o resultado é o mesmo que acima: --followmostra apenas o histórico completo.


Agora, tenha cuidado ao renomear, pois qualquer uma das opções ainda pode produzir efeitos estranhos . Exemplo:

git init
echo "First" >a
git add a
git commit -m "initial a"
echo "Second" >b
git add b
git commit -m "initial b"

git mv a c
git commit -m "first move"
git mv b a
git commit -m "second move"

git log --follow a

commit 81b80f5690deec1864ebff294f875980216a059d
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:35:58 2013 -0400

    second move

commit f284fba9dc8455295b1abdaae9cc6ee941b66e7f
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:34:54 2013 -0400

    initial b

Compare com:

git init
echo "First" >a
git add a
git commit -m "initial a"
echo "Second" >b
git add b
git commit -m "initial b"

git mv a c
git mv b a
git commit -m "both moves at the same time"

git log --follow a

Resultado:

commit 84bf29b01f32ea6b746857e0d8401654c4413ecd
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:37:13 2013 -0400

    both moves at the same time

commit ec0de3c5358758ffda462913f6e6294731400455
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:36:52 2013 -0400

    initial a

Ups ... Agora, o histórico está voltando para a inicial a em vez da inicial b , o que está errado. Então, quando fizemos dois movimentos por vez, o Git ficou confuso e não acompanhou as alterações corretamente. A propósito, em meus experimentos, o mesmo aconteceu quando eu excluí / criei arquivos em vez de usá-lo git mv. Prossiga com cuidado; voce foi avisado...

osa
fonte
5
+1 para a explicação detalhada. Eu estive procurando por problemas que podem acontecer no histórico de log se os arquivos forem movidos no git, sua resposta foi realmente interessante. Obrigado! Btw, você conhece outras armadilhas que devemos evitar ao mover arquivos no git? (ou qualquer referência que você poderia apontar para .... não googling muita sorte para ele)
pabrantes
1
Bem, meus exemplos são pessimistas. Quando os arquivos estão vazios, é muito mais difícil interpretar corretamente as alterações. Eu imagino que se você apenas confirmar após cada conjunto de renomeações, você estará bem.
osa 23/03
27

Como @Charles diz, git mvé uma abreviação.

A verdadeira questão aqui é "Outros sistemas de controle de versão (por exemplo, Subversion e Perforce) tratam os nomes de arquivos especialmente. Por que o Git não?"

Linus explica em http://permalink.gmane.org/gmane.comp.version-control.git/217 com tato característico:

Por favor, pare com essa porcaria de "arquivos de rastreamento". O Git rastreia exatamente o que importa, ou seja, "coleções de arquivos". Nada mais é relevante, e mesmo pensar que é relevante apenas limita sua visão de mundo. Observe como a noção de "anotação" do CVS sempre inevitavelmente acaba limitando como as pessoas a usam. Eu acho que é uma porcaria totalmente inútil, e eu descrevi algo que eu acho que é um milhão de vezes mais útil, e tudo aconteceu exatamente porque eu não estou limitando meu pensamento ao modelo errado do mundo.

Coronel Panic
fonte
9

Há outro uso que tenho para git mvnão mencionado acima.

Desde a descoberta git add -p(modo de correção do git add; consulte http://git-scm.com/docs/git-add ), gosto de usá-lo para revisar as alterações conforme as adiciono ao índice. Assim, meu fluxo de trabalho passa a (1) trabalhar com código, (2) revisar e adicionar ao índice, (3) confirmar.

Como se git mvencaixa? Se mover um arquivo diretamente e usar git rme git add, todas as alterações serão adicionadas ao índice, e usar git diff para visualizar as alterações será menos fácil (antes de confirmar). Usando git mv, no entanto, acrescenta o novo caminho para o índice, mas não as alterações feitas para o arquivo, permitindo assim git diffe git add -pao trabalho como de costume.

obscuro
fonte
5

Há um caso de nicho em que git mvpermanece muito útil: quando você deseja alterar a caixa de um nome de arquivo em um sistema de arquivos que não diferencia maiúsculas de minúsculas. O APFS (mac) e o NTFS (windows) são, por padrão, sem distinção entre maiúsculas e minúsculas (mas preservando maiúsculas e minúsculas).

greg.kindel menciona isso em um comentário sobre a resposta de CB Bailey.

Suponha que você esteja trabalhando em um mac e tenha um arquivo Mytest.txtgerenciado pelo git. Você deseja alterar o nome do arquivo para MyTest.txt.

Você poderia tentar:

$ mv Mytest.txt MyTest.txt
overwrite MyTest.txt? (y/n [n]) y
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Oh céus. O Git não reconhece que houve alguma alteração no arquivo.

Você pode contornar isso renomeando o arquivo completamente e renomeando-o novamente:

$ mv Mytest.txt temp.txt
$ git rm Mytest.txt
rm 'Mytest.txt'
$ mv temp.txt MyTest.txt
$ git add MyTest.txt 
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    Mytest.txt -> MyTest.txt

Viva!

Ou você pode economizar todo esse incômodo usando git mv:

$ git mv Mytest.txt MyTest.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    Mytest.txt -> MyTest.txt
duncan
fonte