¿Cómo hacer que git marque un archivo eliminado y uno nuevo como un movimiento de archivo?

505

Moví un archivo manualmente y luego lo modifiqué. Según Git, es un archivo nuevo y un archivo eliminado. ¿Hay alguna forma de obligar a Git a tratarlo como un movimiento de archivo?

pupeno
fuente
3
Para un archivo dado old_file.txt, entonces git mv old_file.txt new_file.txtes equivalente a git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl
3
Jarl: no, no lo es. Si también hay cambios dentro del archivo, git mvno los agregará al caché, pero lo git addhará. Prefiero mover el archivo hacia atrás para poder usarlo git mvy luego git add -previsar mi conjunto de cambios.
dhardy
compruebe mi script github.com/Deathangel908/python-tricks/blob/master/…
deathangel908
Git ha mejorado en los últimos 8 años, si es solo un archivo, la respuesta principal stackoverflow.com/a/433142/459 no hizo nada ... pero puede seguir stackoverflow.com/a/1541072/459 para obtener un rm / add actualizado a un estado mv / modificar.
dlamblin

Respuestas:

436

Git detectará automáticamente el movimiento / cambio de nombre si su modificación no es demasiado severa. Solo git addel archivo nuevo y git rmel archivo viejo. git statusluego mostrará si ha detectado el cambio de nombre.

Además, para moverse por los directorios, es posible que deba:

  1. cd a la parte superior de esa estructura de directorios.
  2. correr git add -A .
  3. Ejecute git statuspara verificar que el "nuevo archivo" ahora es un archivo "renombrado"

Si el estado de git aún muestra "nuevo archivo" y no "renombrado", debe seguir el consejo de Hank Gay y hacer el movimiento y la modificación en dos confirmaciones separadas.

Bombe
fuente
75
Aclaración: 'no demasiado severo' significa que el archivo nuevo y el archivo antiguo son> 50% 'similares' según algunos índices de similitud que usa git.
pjz
151
Algo que me colgó durante unos minutos: si los archivos renombrados y los archivos eliminados no se preparan para comprometerse, aparecerán como un archivo eliminado y nuevo. Una vez que los agregue al índice provisional, lo reconocerá como un cambio de nombre.
marczych
19
Vale la pena mencionar que cuando se habla de " Git detectará automáticamente el movimiento / renombrar ". Lo hace en el momento en que lo usa git status, git logo git diff, no en el momento en que lo hace git add, git mvo git rm. Hablando además de detectar el cambio de nombre, eso solo tiene sentido para los archivos almacenados. Entonces, un git mvcambio en el archivo puede verse git statuscomo si lo considerara como un rename, pero cuando usa git stage(igual que git add) en el archivo, queda claro que el cambio es demasiado grande para ser detectado como un cambio de nombre.
Jarl
55
La clave real parece ser agregar las ubicaciones nuevas y antiguas en el mismo git add, que como sugiere @ jrhorn424, probablemente debería ser todo el repositorio a la vez.
brianary
55
Sin embargo, esto no es infalible, especialmente si el archivo cambió y es pequeño. ¿Hay algún método para decirle específicamente a git sobre el movimiento?
Rebs
112

Realice el movimiento y la modificación en confirmaciones separadas.

Hank Gay
fuente
12
Entiendo que Git puede manejar movimientos y modificaciones al mismo tiempo. Al programar en Java y usar un IDE, renombrar una clase es tanto una modificación como un movimiento. Entiendo que Git incluso debería poder resolverlo automáticamente cuando hay un movimiento (fuera de una eliminación y una creación).
pupeno
1
Perl también requiere esto, y nunca he hecho que Git detecte el movimiento / cambio de nombre.
jrockway
10
@jrockway, lo hice. Sucede fácilmente con archivos pequeños, supongo que "se vuelven 'demasiado diferentes' para significar un movimiento".
ANeves
2
¿Hay alguna manera de hacer esto automatizado? Tengo muchos archivos en el índice, primero quiero confirmar el movimiento y luego el cambio, pero es difícil hacerlo manualmente
Redetección
55
Una cosa a tener en cuenta es que si git diffno reconoce el cambio de nombre en una confirmación, no lo reconocerá en dos confirmaciones. Necesitaría usar -Maka --find-renamespara hacer eso. Entonces, si la motivación que lo llevó a esta pregunta es ver el cambio de nombre en una solicitud de extracción (hablando por experiencia), dividirlo en dos confirmaciones no lo impulsará hacia ese objetivo.
mattliu
44

Todo es una cosa perceptiva. Git es generalmente bastante bueno para reconocer movimientos, porque GIT es un rastreador de contenido

Todo lo que realmente depende es cómo lo muestre su "estadística". La única diferencia aquí es la bandera -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(-)

registro de ayuda de git

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.
Kent Fredric
fuente
76
Lo siento si esto parece un poco pedante, pero "Git generalmente es bastante bueno para reconocer movimientos, porque GIT es un rastreador de contenido" me parece un poco inseguro. Es un rastreador de contenido, sí, y tal vez sea bueno para detectar movimientos, pero una declaración realmente no se sigue de la otra. Solo porque es un rastreador de contenido, la detección de movimiento no es necesariamente buena. De hecho, un rastreador de contenido no podría tener detección de movimiento en absoluto.
Laurence Gonsalves
1
@WarrenSeine cuando cambia el nombre de un directorio, los SHA1 de los archivos en ese directorio no cambian. Todo lo que tiene es un nuevo objeto TREE con los mismos SHA1. No hay razón para que un cambio de nombre de directorio cause cambios significativos en los datos.
Kent Fredric
1
@KentFredric Usted demostró que usando -M con "git log" podemos verificar si el archivo se renombró o no y lo probé y funciona bien, pero cuando publico mi código para su revisión y veo ese archivo en gerrit ( gerritcodereview.com ) allí muestra que el archivo se agregó recientemente y se eliminó el anterior. Entonces, ¿hay una opción en "git commit" con la que sí me comprometo y gerrit lo muestra correctamente.
Patrick
1
No. No mostré eso en absoluto. Mostré que Git es capaz de pretender que agregar + eliminar es un cambio de nombre debido a que el contenido es el mismo. No hay forma de que sepa qué sucedió, y no le importa. Todo lo que recuerda git es "agregado" y "eliminado". "Renombrado" nunca se graba.
Kent Fredric
1
Por ejemplo, hice mucho "Copiar + Editar" en un repositorio últimamente. La mayoría de las formas de verlo solo ven "nuevo archivo", pero si pasa git log -M1 -C1 -B1 -D --find-copies-harder, git puede "descubrir" que el nuevo archivo podría haberse copiado primero. A veces lo hace bien, otras veces, encuentra archivos completamente no relacionados que tienen contenido idéntico.
Kent Fredric
36

git diff -Mo git log -Mdebería detectar automáticamente tales cambios como un cambio de nombre con cambios menores , siempre que realmente lo sean. Si sus cambios menores no son menores, puede reducir la similitud que existe, p. Ej.

$ git log -M20 -p --stat

para reducirlo del 50% predeterminado al 20%.

Simon East
fuente
13
¿Es posible definir el umbral en la configuración?
Redetección
34

Aquí hay una solución rápida y sucia para uno o unos pocos archivos renombrados y modificados que no están comprometidos.

Digamos que el archivo se llamó fooy ahora se llama bar:

  1. Cambiar el nombre bara un nombre temporal:

    mv bar side
    
  2. Pago foo:

    git checkout HEAD foo
    
  3. Renombrar fooa barcon Git:

    git mv foo bar
    
  4. Ahora cambie el nombre de su archivo temporal a bar.

    mv side bar
    

Este último paso es lo que recupera el contenido modificado en el archivo.

Si bien esto puede funcionar, si el archivo movido es demasiado diferente en contenido del git original, se considerará más eficiente decidir que este es un objeto nuevo. Déjame demostrarte:

$ 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
fuente
27
Este proceso no sirve para nada. git no agrega ningún metadato para cambiar el nombre, git mves simplemente una conveniencia para un git rm/ git addpar. Si ya has hecho 'mv bar foo', entonces todo lo que tienes que hacer es asegurarte de que lo has hecho git add fooy git rm barantes de realizar el compromiso. Esto podría hacerse como un solo git add -Acomando, o posiblemente una git add foo; git commit -asecuencia.
CB Bailey
17
Todo lo que sé es que antes de hacerlo, Git no lo reconoció como un movimiento. Después de hacer esto, Git lo reconoció como un movimiento.
8
Lo reconoce como un movimiento. Pero también tiene el cambio como un cambio no organizado ahora. Una vez que addvuelva a usar el archivo, git rompe el movimiento / modificación en un borrado / agregado nuevamente.
Michael Piefel
44
Este proceso funcionaría si git commitdespués del paso 3, de lo contrario, es incorrecto. Además, @CharlesBailey es correcto, podría hacer lo normal con facilidad mv blah fooen el paso 3 seguido de una confirmación y obtener los mismos resultados.
DaFlame
55
"Este proceso no sirve para nada". - Tiene un propósito cuando git se ha confundido y cree que se ha eliminado un archivo y se ha agregado otro archivo. Esto aclara la confusión de Git y marca el archivo como movido explícitamente sin tener que hacer confirmaciones intermedias.
Danack
20

Si está hablando de git statusno mostrar los cambios de nombre, intente en su git commit --dry-run -alugar

BoD
fuente
11

Si está utilizando TortoiseGit, es importante tener en cuenta que la detección automática de cambio de nombre de Git ocurre durante la confirmación, pero el hecho de que esto suceda no siempre se muestra por adelantado por el software. Moví dos archivos a un directorio diferente y realicé algunas pequeñas modificaciones. Uso TortoiseGit como mi herramienta de confirmación y la lista Cambios realizados mostró que los archivos se eliminaron y agregaron, no se movieron. Ejecutar el estado de git desde la línea de comando mostró una situación similar. Sin embargo, después de confirmar los archivos, aparecieron como renombrados en el registro. Entonces, la respuesta a su pregunta es, siempre que no haya hecho nada demasiado drástico, Git debería elegir el cambio de nombre automáticamente.

Editar: Aparentemente, si agrega los nuevos archivos y luego realiza un estado git desde la línea de comando, el cambio de nombre debería aparecer antes de comprometerse.

Edición 2: Además, en TortoiseGit, agregue los nuevos archivos en el diálogo de confirmación, pero no los confirme. Luego, si ingresa al comando Mostrar registro y mira el directorio de trabajo, verá si Git ha detectado el cambio de nombre antes de comprometerse.

Aquí se planteó la misma pregunta: https://tortoisegit.org/issue/1389 y se ha registrado como un error para solucionarlo aquí: https://tortoisegit.org/issue/1440 Resulta que es un problema de visualización con el compromiso de TortoiseGit diálogo y también existe en estado git si no ha agregado los nuevos archivos.

Giles Roberts
fuente
2
Tienes razón, incluso si TortoiseGit muestra eliminar + agregar, incluso si el estado de git muestra eliminar + agregar, incluso si git commit --dry-run muestra eliminar + agregar, después de git commit veo renombrar y no eliminar + agregar.
Tomas Kubes
1
Estoy bastante seguro de que la detección automática de cambio de nombre ocurre durante la recuperación del historial ; en el commit siempre es agregar + eliminar. Eso también explica el comportamiento esquizofrénico que usted describe. De ahí las opciones aquí: stackoverflow.com/a/434078/155892
Mark Sowul el
10

¡O podría intentar la respuesta a esta pregunta aquí por Amber ! Para citarlo de nuevo:

Primero, cancele su complemento por etapas para el archivo movido manualmente:

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

Luego, use Git para mover el archivo:

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

Por supuesto, si ya cometió el movimiento manual, es posible que desee restablecer la revisión antes del movimiento, y luego simplemente git mv desde allí.

Zingam
fuente
7

Use el git mvcomando para mover los archivos, en lugar de los comandos de movimiento del sistema operativo: https://git-scm.com/docs/git-mv

Tenga en cuenta que el git mvcomando solo existe en las versiones de Git 1.8.5 y posteriores. Por lo tanto, es posible que deba actualizar su Git para usar este comando.

mostafa.elhoushi
fuente
3

Recientemente tuve este problema al mover (pero no modificar) algunos archivos.

El problema es que Git cambió algunas terminaciones de línea cuando moví los archivos, y luego no pude decir que los archivos eran iguales.

El uso git mvsolucionó el problema, pero solo funciona en archivos / directorios individuales, y tenía muchos archivos en la raíz del repositorio para hacer.

Una forma de arreglar esto sería con algo de magia bash / batch.

Otra forma es la siguiente

  • Mueve los archivos y git commit. Esto actualiza los finales de línea.
  • Vuelva a mover los archivos a su ubicación original, ahora que tienen las nuevas terminaciones de línea, y git commit --amend
  • Mueva los archivos nuevamente y git commit --amend. Esta vez no hay cambios en los finales de línea, así que Git está feliz.
cedd
fuente
1

Para mí funcionó guardar todos los cambios antes de la confirmación y sacarlos nuevamente. Esto hizo que git volviera a analizar los archivos agregados / eliminados y los marcó correctamente como movidos.

Alex Ilie
fuente
0

Probablemente hay una mejor forma de "línea de comando" para hacer esto, y sé que esto es un truco, pero nunca he podido encontrar una buena solución.

Uso de TortoiseGIT: si tiene una confirmación GIT donde algunas operaciones de movimiento de archivos se muestran como carga de adiciones / eliminaciones en lugar de cambios de nombre, a pesar de que los archivos solo tienen pequeños cambios, haga esto:

  1. Comprueba lo que has hecho localmente
  2. Registre un mini cambio de una línea en una segunda confirmación
  3. Ir a GIT iniciar sesión tortuga git
  4. Seleccione las dos confirmaciones, haga clic derecho y seleccione "fusionar en una confirmación"

La nueva confirmación ahora mostrará correctamente el cambio de nombre de los archivos ... lo que ayudará a mantener el historial de archivos adecuado.

Jon Rea
fuente
0

Cuando edito, renombre y muevo un archivo al mismo tiempo, ninguna de estas soluciones funciona. La solución es hacerlo en dos confirmaciones (editar y renombrar / mover separadamente) y luego fixupla segunda confirmación git rebase -ipara tenerlo en una confirmación.

Hativ
fuente
0

La forma en que entiendo esta pregunta es "Cómo hacer que git reconozca la eliminación de un archivo antiguo y la creación de un nuevo archivo como movimiento de archivo".

Sí, en el directorio de trabajo, una vez que elimine un archivo antiguo e inserte un archivo antiguo, git statusdirá " deleted: old_file" y " Untracked files: ... new_file"

Pero en el índice / nivel provisional una vez que agregue y elimine el archivo usando git, se reconocerá como un movimiento de archivo. Para hacerlo, suponiendo que haya realizado la eliminación y creación utilizando su Sistema Operativo, dé los siguientes comandos:

git add new_file
git rm old_file

Si el contenido del archivo es similar al 50% o más, ejecutar el git statuscomando debería proporcionarle:

renamed: old_file -> new_file
harshainfo
fuente
1
git statusdirá " deleted: old_file" y " Untracked files: ... new_file": no desde Git 2.18: stackoverflow.com/a/50573107/6309
VonC