Cómo mover algunos archivos de un repositorio de git a otro (no un clon), preservando el historial
Nuestros repositorios Git comenzaron como partes de un único repositorio SVN monstruoso donde cada proyecto individual tenía su propio árbol, así:
project1/branches
/tags
/trunk
project2/branches
/tags
/trunk
Obviamente, fue bastante fácil mover archivos de uno a otro con svn mv
. Pero en Git, cada proyecto está en su propio repositorio y hoy me pidieron que moviera un subdirectorio project2
de project1
. Hice algo como esto:
$ git clone project2
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do
> git mv $f deeply/buried/different/java/source/directory/B
> done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push
Pero eso parece bastante complicado. ¿Existe una mejor manera de hacer este tipo de cosas en general? ¿O he adoptado el enfoque correcto?
Tenga en cuenta que esto implica fusionar el historial en un repositorio existente, en lugar de simplemente crear un nuevo repositorio independiente a partir de parte de otro ( como en una pregunta anterior ).
Si su historial es correcto, puede eliminar las confirmaciones como parche y aplicarlas en el nuevo repositorio:
cd repository
git log \
--pretty=email \
--patch-with-stat \
--reverse \
--full-index \
--binary \
-m \
--first-parent \
-- path/to/file_or_folder \
> patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch
O en una línea
git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)
Sugerencia: si las confirmaciones en el subdirectorio del proyecto fuente deben extraerse a un nuevo directorio raíz del repositorio, git am
se le puede dar un argumento como -p2
eliminar directorios adicionales del parche.
(Tomado de los documentos de Exherbo )
Después de haber probado varios enfoques para mover un archivo o carpeta de un repositorio Git a otro, el único que parece funcionar de manera confiable se describe a continuación.
Implica clonar el repositorio desde el que desea mover el archivo o carpeta, mover ese archivo o carpeta a la raíz, reescribir el historial de Git, clonar el repositorio de destino y extraer el archivo o carpeta con el historial directamente a este repositorio de destino.
La etapa uno
Haga una copia del repositorio A ya que los siguientes pasos realizan cambios importantes en esta copia que no debe presionar.
git clone --branch <branch> --origin origin --progress \ -v <git repository A url> # eg. git clone --branch master --origin origin --progress \ # -v https://username@giturl/scm/projects/myprojects.git # (assuming myprojects is the repository you want to copy from)
cd en él
cd <git repository A directory> # eg. cd /c/Working/GIT/myprojects
Elimine el enlace al repositorio original para evitar realizar cambios remotos accidentalmente (por ejemplo, presionando)
git remote rm origin
Revise su historial y archivos, eliminando todo lo que no esté en el directorio 1. El resultado es el contenido del directorio 1 arrojado a la base del repositorio A.
git filter-branch --subdirectory-filter <directory> -- --all # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
Para mover solo un archivo: revise lo que queda y elimine todo excepto el archivo deseado. (Es posible que deba eliminar los archivos que no desea con el mismo nombre y confirmar).
git filter-branch -f --index-filter \ 'git ls-files -s | grep $'\t'FILE_TO_KEEP$ | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info && \ mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
Etapa dos
Paso de limpieza
git reset --hard
Paso de limpieza
git gc --aggressive
Paso de limpieza
git prune
Es posible que desee importar estos archivos al repositorio B dentro de un directorio que no sea la raíz:
Haz ese directorio
mkdir <base directory> eg. mkdir FOLDER_TO_KEEP
Mover archivos a ese directorio
git mv * <base directory> eg. git mv * FOLDER_TO_KEEP
Agregar archivos a ese directorio
git add .
Confirme sus cambios y estaremos listos para fusionar estos archivos en el nuevo repositorio.
git commit
Etapa tres
Haga una copia del repositorio B si aún no tiene una
git clone <git repository B url> # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
(asumiendo que FOLDER_TO_KEEP es el nombre del nuevo repositorio al que está copiando)
cd en él
cd <git repository B directory> # eg. cd /c/Working/GIT/FOLDER_TO_KEEP
Cree una conexión remota al repositorio A como una sucursal en el repositorio B
git remote add repo-A-branch <git repository A directory> # (repo-A-branch can be anything - it's just an arbitrary name) # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
Extraiga de esta rama (que contiene solo el directorio que desea mover) al repositorio B.
git pull repo-A-branch master --allow-unrelated-histories
La extracción copia tanto los archivos como el historial. Nota: Puede utilizar una combinación en lugar de una extracción, pero la extracción funciona mejor.
Finalmente, probablemente quieras limpiar un poco eliminando la conexión remota al repositorio A.
git remote rm repo-A-branch
Empuja y ya está todo listo.
git push
Sí, presionar el --subdirectory-filter
botón de filter-branch
fue clave. El hecho de que lo haya usado esencialmente demuestra que no hay una manera más fácil: no tuvo más remedio que reescribir el historial, ya que quería terminar con solo un subconjunto (renombrado) de los archivos, y esto, por definición, cambia los hashes. Dado que ninguno de los comandos estándar (por ejemplo pull
, ) reescribe el historial, no hay forma de usarlos para lograrlo.
Por supuesto, podría refinar los detalles (parte de la clonación y ramificación no era estrictamente necesaria), ¡pero el enfoque general es bueno! Es una pena que sea complicado, pero, por supuesto, el objetivo de git no es facilitar la reescritura de la historia.
Esto se vuelve más sencillo usando git-filter-repo.
Para desplazarse project2/sub/dir
a project1/sub/dir
:
# Create a new repo containing only the subdirectory:
git clone project2 project2_clone --no-local
cd project2_clone
git filter-repo --path sub/dir
# Merge the new repo:
cd ../project1
git remote add tmp ../project2_clone/
git fetch tmp master
git merge remotes/tmp/master --allow-unrelated-histories
git remote remove tmp
Para instalar la herramienta simplemente: pip3 install git-filter-repo
( más detalles y opciones en README )
# Before: (root)
.
|-- project1
| `-- 3
`-- project2
|-- 1
`-- sub
`-- dir
`-- 2
# After: (project1)
.
├── 3
└── sub
└── dir
└── 2