¿Cuál es el propósito de git-mv?

287

Por lo que entiendo, Git realmente no necesita rastrear las operaciones de cambio de nombre / movimiento / copia de archivos , entonces, ¿cuál es el verdadero propósito de git mv ? La página del manual no es especialmente descriptiva ...

¿Es obsoleto? ¿Es un comando interno que no debe ser utilizado por usuarios habituales?

Mauricio Scheffer
fuente

Respuestas:

390
git mv oldname newname

es solo una abreviatura de:

mv oldname newname
git add newname
git rm oldname

es decir, actualiza el índice de las rutas antiguas y nuevas automáticamente.

CB Bailey
fuente
38
También tiene algunas seguridades integradas.
Jakub Narębski
66
Gracias @CharlesBailey: ¿Git considera los archivos newNameFile y oldNameFile como diferentes? En caso afirmativo, ¿qué sucede si queremos fusionarlos? Digamos que ramificamos un proyecto de hormigas en la rama A y creamos la Rama B y luego mavenizemos proyectos en B. Los nombres de los archivos son los mismos pero se colocan en caminos diferentes a medida que cambia la estructura del proyecto. Digamos que ambas ramas crecieron durante algún tiempo en paralelo. En algún momento, si queremos fusionar los proyectos, ¿cómo sabremos que es el mismo archivo que acaba de cambiar su nombre? (si "git mv" == "git add + git rm")
Rose
2
@SergeyOrshanskiy Si la detección automática sale mal mv oldname newname; git add newname; git rm oldname, también saldrá mal git mv oldname newname(ver esta respuesta ).
Ajedi32
55
Tenga en cuenta que git mves ligeramente diferente del mv oldname newname; git add newname; git rm oldname, en que si realizó cambios en el archivo antes de git mvcrearlo, esos cambios no se organizarán hasta que obtenga git addel nuevo archivo.
Ajedi32
2
git mv está haciendo algo diferente, ya que maneja los cambios en el caso del nombre de archivo (foo.txt a Foo.txt) mientras que esos comandos se ejecutan individualmente no (en OSX)
greg.kindel
66

Del GitFaq oficial :

Git tiene un comando para cambiar el nombre git mv, pero eso es solo una conveniencia. El efecto es indistinguible de eliminar el archivo y agregar otro con un nombre diferente y el mismo contenido

Adam Nofsinger
fuente
8
Entonces, ¿pierdes el historial de archivos? Se suponía que cambiar el nombre mantendría la antigua historia de ese directorio ...
Will Hancock
17
Pues sí y no. Lea el enlace oficial de GitFaq arriba sobre los cambios de nombre, y luego lea el extenso correo electrónico de Linus Torvalds sobre por qué no le gusta la noción de una herramienta de SCM que rastrea archivos: permalink.gmane.org/gmane.comp.version-control.git/ 217
Adam Nofsinger
3
@WillHancock He usado git un poco más ahora, y puedo responderle de manera más definitiva: dependiendo de su cliente git y sus opciones, podrá rastrear el archivo más allá del cambio de nombre si el archivo cambió internamente lo suficientemente poco como para que lo considere un rebautizar. Si cambia demasiado el archivo Y lo renombra, git no lo detectará, en cierto sentido dice "no, ¡también podría considerar que es un archivo completamente diferente!"
Adam Nofsinger
77
@ AdamNofsinger ese enlace está muerto. Aquí hay un espejo: web.archive.org/web/20150209075907/http://…
Carl Walsh
2
¿Existe una referencia oficial (es decir, más digna de empuje que una pregunta frecuente) que indique la equivalencia git mvy el enfoque manual? No es obvio de git help mv.
tvo
40

Git solo está tratando de adivinar lo que estás tratando de hacer. Está haciendo todo lo posible para preservar la historia ininterrumpida. Por supuesto, no es perfecto. Entonces le git mvpermite ser explícito con su intención y evitar algunos errores.

Considera este ejemplo. Comenzando con un repositorio vacío,

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")

La autodetección falló :( ¿O lo hizo?

$ 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

y entonces

$ 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

Ahora intente en su lugar (recuerde eliminar la .gitcarpeta cuando experimente):

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

Hasta aquí todo bien:

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


git mv b a
git status

Ahora, nadie es perfecto:

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

De Verdad? Pero por supuesto...

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

... y el resultado es el mismo que el anterior: solo --followmuestra el historial completo.


Ahora, tenga cuidado con el cambio de nombre, ya que cualquiera de las opciones puede producir efectos extraños . Ejemplo:

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

Contraste con:

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 ... Ahora el historial vuelve a la inicial a en lugar de a la inicial b , lo cual está mal. Entonces, cuando hicimos dos movimientos a la vez, Git se confundió y no siguió los cambios correctamente. Por cierto, en mis experimentos sucedió lo mismo cuando eliminé / creé archivos en lugar de usarlos git mv. Proceder con cuidado; has sido advertido...

osa
fuente
55
+1 para la explicación detallada. He estado buscando problemas que podrían ocurrir en el historial de registro si los archivos se mueven en git, su respuesta fue realmente interesante. ¡Gracias! Por cierto, ¿conoces alguna otra trampa que debamos evitar al mover archivos en git? (o cualquier referencia que pueda señalar ... no es muy afortunado buscando en Google)
pabrantes
1
Bueno, mis ejemplos son pesimistas. Cuando los archivos están vacíos, es mucho más difícil interpretar adecuadamente los cambios. Me imagino que si solo se compromete después de cada conjunto de cambios de nombre, debería estar bien.
osa
27

Como dice @Charles, git mves una taquigrafía.

La verdadera pregunta aquí es "Otros sistemas de control de versiones (por ejemplo, Subversion y Perforce) tratan los cambios de nombre de archivos especialmente. ¿Por qué no Git?"

Linus explica en http://permalink.gmane.org/gmane.comp.version-control.git/217 con un tacto característico:

Detenga esta basura de "archivos de seguimiento". Git rastrea exactamente lo que importa, a saber, "colecciones de archivos". Nada más es relevante, e incluso pensar que es relevante solo limita su visión del mundo. Observe cómo la noción de CVS "anotar" siempre termina inevitablemente limitando la forma en que las personas lo usan. Creo que es una mierda totalmente inútil, y he descrito algo que creo que es un millón de veces más útil, y todo se cayó exactamente porque no estoy limitando mi pensamiento al modelo equivocado del mundo.

Coronel Panic
fuente
9

Hay otro uso que tengo para git mvno mencionado anteriormente.

Desde que descubrí git add -p(modo parche de git add; consulte http://git-scm.com/docs/git-add ), me gusta usarlo para revisar los cambios a medida que los agrego al índice. Por lo tanto, mi flujo de trabajo se convierte en (1) trabajar en el código, (2) revisar y agregar al índice, (3) confirmar.

¿Cómo git mvencaja? Si mueve un archivo directamente y luego usa git rmy git add, todos los cambios se agregan al índice, y usar git diff para ver los cambios es menos fácil (antes de confirmar). El uso git mv, sin embargo, añade la nueva vía para el índice, pero no los cambios realizados en el archivo, permitiendo de este modo git diffy git add -pal trabajo como de costumbre.

dhardy
fuente
5

Hay un caso de nicho donde git mvsigue siendo muy útil: cuando desea cambiar la carcasa de un nombre de archivo en un sistema de archivos que no distingue entre mayúsculas y minúsculas. Tanto APFS (mac) como NTFS (windows) son, por defecto, insensibles a mayúsculas y minúsculas (pero conservan mayúsculas y minúsculas).

greg.kindel menciona esto en un comentario sobre la respuesta de CB Bailey.

Supongamos que está trabajando en una Mac y tiene un archivo Mytest.txtadministrado por git. Desea cambiar el nombre del archivo a MyTest.txt.

Tu podrías intentar:

$ 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 querido. Git no reconoce que ha habido ningún cambio en el archivo.

Usted podría evitar esto con el cambio de nombre del archivo completo a continuación, cambiar el nombre de nuevo:

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

O podrías ahorrarte toda esa molestia 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
fuente