Pregunta ¿Es posible mover / cambiar el nombre de los archivos en Git y mantener su historial?


Me gustaría cambiar el nombre / mover un subárbol de proyecto en Git moviéndolo de

/project/xyz

a

/components/xyz

Si uso un simple git mv project components, luego todo el historial de compromisos para el xyz project se pierde. ¿Hay alguna manera de mover esto para que la historia se mantenga?


550
2018-02-22 22:11


origen


Respuestas:


Git detecta el cambio de nombre en lugar de persistir en la operación con la confirmación, así que ya sea que use git mv o mv no importa

El comando log toma una --follow argumento que continúa el historial antes de una operación de cambio de nombre, es decir, busca contenido similar utilizando la heurística:

http://git-scm.com/docs/git-log

Para buscar el historial completo, use el siguiente comando:

git log --follow ./path/to/file

560
2018-02-22 22:26



Es posible para cambiar el nombre de un archivo y mantener el historial intacto, aunque hace que el archivo cambie de nombre a lo largo de todo el historial del repositorio. Esto es probablemente solo para los obsesivos amantes del registro de git, y tiene algunas implicaciones serias, que incluyen estas:

  • Podría estar reescribiendo un historial compartido, que es lo más importante NO durante el uso de Git. Si alguien más ha clonado el repositorio, lo romperás haciendo esto. Tendrán que volver a clonarse para evitar dolores de cabeza. Esto podría estar bien si el cambio de nombre es lo suficientemente importante, pero tendrá que considerarlo cuidadosamente: ¡podría terminar trastornando a toda una comunidad de código abierto!
  • Si ha hecho referencia al archivo usando su nombre antiguo anteriormente en el historial del repositorio, está efectivamente rompiendo versiones anteriores. Para remediar esto, tendrás que saltar un poco más al aro. No es imposible, simplemente tedioso y posiblemente no valga la pena.

Ahora, como todavía estás conmigo, probablemente seas un desarrollador solista que cambia el nombre de un archivo completamente aislado. Vamos a mover un archivo usando filter-tree!

Supongamos que va a mover un archivo old en una carpeta dir y darle el nombre new

Esto podría hacerse con git mv old dir/new && git add -u dir/new, pero eso rompe la historia.

En lugar:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

será rehacer cada compromiso en la rama, ejecutando el comando en los ticks para cada iteración. Muchas cosas pueden salir mal cuando haces esto. Normalmente pruebo para ver si el archivo está presente (de lo contrario, todavía no me he movido) y luego realizo los pasos necesarios para calzar el árbol a mi gusto. Aquí puede buscar archivos para alterar las referencias al archivo, etc. Knock a ti mismo! :)

Cuando se completa, el archivo se mueve y el registro está intacto. Te sientes como un pirata ninja.

También; El directorio mkdir solo es necesario si mueve el archivo a una nueva carpeta, por supuesto. los Si evitará la creación de esta carpeta antes en la historia de lo que su archivo existe.


75
2018-02-28 11:59



No.

La respuesta corta es NO, no es posible renombrar un archivo en Git y recordar el historial. Y es un dolor.

Corre el rumor de que git log --follow--find-copies-harder funcionará, pero no funciona para mí, incluso si no hay cambios en el contenido del archivo, y los movimientos se han realizado con git mv.

(Inicialmente usé Eclipse para renombrar y actualizar paquetes en una operación, lo que puede haber confundido a git. Pero eso es algo muy común de hacer. --follow parece funcionar si solo un mv se realiza y luego una commit y el mv no es demasiado lejos.)

Linus dice que se supone que debes comprender el contenido completo de un proyecto de software de manera integral, sin necesidad de rastrear archivos individuales. Bueno, lamentablemente, mi pequeño cerebro no puede hacer eso.

Es realmente molesto que tanta gente repitió sin pensar la afirmación de que git sigue automáticamente los movimientos. Han perdido mi tiempo. Git no hace tal cosa. Por diseño (!) Git no rastrea movimientos en absoluto.

Mi solución es cambiar el nombre de los archivos a sus ubicaciones originales. Cambie el software para que se ajuste al control de origen. Con Git, parece que necesitas hacerlo bien la primera vez.

Desafortunadamente, eso rompe Eclipse, que parece usar --follow.
git log --follow A veces no muestra el historial completo de archivos con historias de cambio de nombre complicadas aunque    git log hace. (No se por que.)

(Hay algunos hacks demasiado inteligentes que se remontan y vuelven a comprometer el trabajo anterior, pero son bastante aterradores. Ver GitHub-Gist: emiller / git-mv-con-historia.)


59
2017-09-28 05:46



git log --follow [file]

le mostrará la historia a través de cambiar el nombre.


37
2018-02-22 22:25



Hago:

git mv {old} {new}
git add -u {new}

16
2017-11-24 19:07



Objetivo

  • Utilizar git am  (Inspirado de Smar, tomado de Exherbo)
  • Agregar historial de confirmaciones de archivos copiados / movidos
  • De un directorio a otro
  • O de un repositorio a otro

Limitación

  • Etiquetas y ramas no se mantienen
  • El historial se corta en el cambio de nombre de ruta de acceso (cambio de nombre de directorio)

Resumen

  1. Extrae el historial en formato de correo electrónico usando
    git log --pretty=email -p --reverse --full-index --binary
  2. Reorganizar el árbol de archivos y actualizar los nombres de archivos
  3. Agregar nuevo historial usando
    cat extracted-history | git am --committer-date-is-author-date

1. Extracto del historial en formato de correo electrónico

Ejemplo: Extraer el historial de file3, file4 y file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Establecer / limpiar el destino

export historydir=/tmp/mail/dir       # Absolute path
rm -rf "$historydir"    # Caution when cleaning the folder

Extrae el historial de cada archivo en formato de correo electrónico

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Lamentablemente --follow o --find-copies-harder no se puede combinar con --reverse. Esta es la razón por la que el historial se corta cuando se cambia el nombre del archivo (o cuando se cambia el nombre de un directorio principal).

Historial temporal en formato de correo electrónico:

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

Dan Bonachea sugiere invertir los bucles del comando de generación de registros git en este primer paso: en lugar de ejecutar git log una vez por archivo, ejecútelo exactamente una vez con una lista de archivos en la línea de comandos y genere un único registro unificado. De esta manera se compromete a que modificar archivos múltiples permanezca como una única confirmación en el resultado, y todos los nuevos commits mantengan su orden relativa original. Tenga en cuenta que esto también requiere cambios en el segundo paso a continuación al volver a escribir nombres de archivo en el registro (ahora unificado).


2. Reorganizar el árbol de archivos y actualizar los nombres de archivos

Supongamos que desea mover estos tres archivos en este otro repositorio (puede ser el mismo repositorio).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # from subdir
│   │   ├── file33    # from file3
│   │   └── file44    # from file4
│   └── dirB2         # new dir
│        └── file5    # from file5
└── dirH
    └── file77

Por lo tanto, reorganice sus archivos:

cd /tmp/mail/dir
mkdir -p dirB/dirB1
mv subdir/file3 dirB/dirB1/file33
mv subdir/file4 dirB/dirB1/file44
mkdir -p dirB/dirB2
mv file5 dirB/dirB2

Tu historial temporal es ahora:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Cambiar también los nombres de archivo dentro de la historia:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

3. Aplicar nueva historia

Su otro repositorio es:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Aplique confirmaciones de archivos de historial temporal:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date

--committer-date-is-author-date conserva las marcas de tiempo de confirmación originales (Dan Bonacheaun comentario de)

Su otro repositorio es ahora:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

Utilizar git status para ver la cantidad de commits listos para ser empujados :-)


Truco adicional: compruebe los archivos renombrados / movidos dentro de su repositorio

Para enumerar los archivos que han sido renombrados:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Más personalizaciones: puede completar el comando git log usando opciones --find-copies-harder o --reverse. También puede eliminar las primeras dos columnas usando cut -f3- y grepping patrón completo '{. * =>. *}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

12
2017-11-24 18:36



Mientras que el núcleo de git, la plomería git no hace un seguimiento de los cambios de nombre, la historia que muestra con el registro de git "porcelana" puede detectarlos si lo desea.

Para una dada git log usa la opción -M

git log -p -M

Con una versión actual de git.

Esto funciona para otros comandos como git diff también.

Hay opciones para hacer las comparaciones más o menos rigurosas. Si cambia el nombre de un archivo sin realizar cambios significativos en el archivo al mismo tiempo, es más fácil para el registro de git y sus amigos detectar el cambio de nombre. Por esta razón, algunas personas renombran archivos en una confirmación y los cambian en otra.

Hay un costo en el uso de la CPU cada vez que le pides a git que encuentre dónde se han cambiado los nombres de los archivos, por lo tanto, si lo usas o no, y cuándo, depende de ti.

Si desea que siempre se informe sobre su historial con la detección de cambio de nombre en un repositorio en particular, puede usar:

git config diff.renames 1

Archivos que se mueven de un directorio a otro es detectado Aquí hay un ejemplo:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:20:19 2017 -0500

    test rename again

diff --git a/yyy/power.py b/zzz/power.py
similarity index 100%
rename from yyy/power.py
rename to zzz/power.py

commit ae181377154eca800832087500c258a20c95d1c3
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:19:17 2017 -0500

    rename test

diff --git a/power.py b/yyy/power.py
similarity index 100%
rename from power.py
rename to yyy/power.py

Tenga en cuenta que esto funciona siempre que use diff, no solo con git log. Por ejemplo:

$ git diff HEAD c3ee8df
diff --git a/power.py b/zzz/power.py
similarity index 100%
rename from power.py
rename to zzz/power.py

Como prueba hice un pequeño cambio en un archivo en una rama de características y lo comprometí y luego en la rama principal renombré el archivo, me comprometí y luego hice un pequeño cambio en otra parte del archivo y me comprometí. Cuando fui a feature branch y fusioné desde master, la fusión cambió el nombre del archivo y fusionó los cambios. Aquí está el resultado de la fusión:

 $ git merge -v master
 Auto-merging single
 Merge made by the 'recursive' strategy.
  one => single | 4 ++++
  1 file changed, 4 insertions(+)
  rename one => single (67%)

El resultado fue un directorio de trabajo con el archivo renombrado y ambos cambios de texto realizados. Entonces, es posible que git haga lo correcto a pesar de que no rastrea explícitamente los nombres.

Esta es una respuesta tardía a una pregunta anterior, por lo que las otras respuestas pueden haber sido correctas para la versión de git en ese momento.


2
2018-02-23 04:54