ver repositorio modificados crear cambiar archivos actualizar git repository

modificados - Cómo mover archivos de un repositorio de git a otro(no un clon), conservando el historial



git push (12)

Nuestros repositorios de Git comenzaron como partes de un único repositorio SVN de monstruos donde los proyectos individuales tenían cada uno 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 mueva un subdirectorio de project2 a 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 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. ¿Hay 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 de parte de otro ( como en una pregunta anterior ).


Con la inspiración de http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ , creé esta función Powershell para hacer lo mismo, que tiene funcionó muy bien para mí hasta ahora:

# Migrates the git history of a file or directory from one Git repo to another. # Start in the root directory of the source repo. # Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to. # Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ function Migrate-GitHistory { # The file or directory within the current Git repo to migrate. param([string] $fileOrDir) # Path to the destination repo param([string] $destRepoDir) # A temp directory to use for storing the patch file (optional) param([string] $tempDir = "/temp/migrateGit") mkdir $tempDir # git log $fileOrDir -- to list commits that will be migrated Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan git format-patch -o $tempDir --root -- $fileOrDir cd $destRepoDir Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan ls $tempDir -Filter *.patch ` | foreach { git am $_.FullName } }

Uso para este ejemplo:

git clone project2 git clone project1 cd project1 # Create a new branch to migrate to git checkout -b migrate-from-project2 cd ../project2 Migrate-GitHistory "deeply/buried/java/source/directory/A" "../project1"

Después de hacer esto, puede reorganizar los archivos en la rama migrate-from-project2 antes de fusionarlos.


Después de haber probado varios métodos 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, volver a escribir el historial de Git, clonar el repositorio de destino y extraer el archivo o la carpeta con el historial directamente en este repositorio de destino.

La etapa uno

  1. Haga una copia del repositorio A, ya que los siguientes pasos hacen 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

    (asumiendo que myprojects es el repositorio desde el que desea copiar)

  2. cd en él

    cd <git repository A directory> eg. cd /c/Working/GIT/myprojects

  3. Elimine el enlace al repositorio original para evitar realizar cambios remotos accidentalmente (por ejemplo, presionando)

    git remote rm origin

  4. Ir a través de 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

  5. Solo para mover un solo archivo: revise lo que queda y elimine todo excepto el archivo deseado. (Es posible que deba eliminar los archivos que no desee con el mismo nombre y confirmación).

    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

    p.ej. FILE_TO_KEEP = pom.xml para mantener solo el archivo pom.xml de FOLDER_TO_KEEP

Etapa dos

  1. Paso de limpieza

    git reset --hard

  2. Paso de limpieza

    git gc --aggressive

  3. 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:

  1. Hacer ese directorio

    mkdir <base directory> eg. mkdir FOLDER_TO_KEEP

  2. Mover archivos a ese directorio

    git mv * <base directory> eg. git mv * FOLDER_TO_KEEP

  3. Agregar archivos a ese directorio

    git add .

  4. Confirme sus cambios y estamos listos para combinar estos archivos en el nuevo repositorio

    git commit

Etapa tres

  1. 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 en el que está copiando)

  2. cd en él

    cd <git repository B directory> eg. cd /c/Working/GIT/FOLDER_TO_KEEP

  3. Cree una conexión remota al repositorio A como una rama en el repositorio B

    git remote add repo-A-branch <git repository A directory>

    (repo-A-branch puede ser cualquier cosa, es solo un nombre arbitrario)

    eg. git remote add repo-A-branch /c/Working/GIT/myprojects

  4. Tire de esta rama (que contiene solo el directorio que desea mover) al repositorio B.

    git pull repo-A-branch master --allow-unrelated-histories

    El pull copia tanto los archivos como el historial. Nota: puede usar una combinación en lugar de una extracción, pero la extracción funciona mejor.

  5. Finalmente, es probable que desee limpiar un poco eliminando la conexión remota al repositorio A

    git remote rm repo-A-branch

  6. Empuje y ya está todo listo.

    git push



En mi caso, no necesitaba preservar el repositorio desde el que estaba migrando ni conservar ningún historial anterior. Tenía un parche de la misma rama, desde un control remoto diferente

#Source directory git remote rm origin #Target directory git remote add branch-name-from-old-repo ../source_directory

En esos dos pasos, pude hacer que la rama del otro repo apareciera en el mismo repositorio.

Finalmente, configuré esta rama (que importé del otro repositorio) para seguir la línea principal del repositorio de destino (para poder diferenciarla con precisión)

git br --set-upstream-to=origin/mainline

Ahora se comportó como si fuera solo otra rama que había empujado contra el mismo repositorio.


Encontré this muy útil. Es un enfoque muy simple en el que crea parches que se aplican al nuevo repositorio. Vea la página vinculada para más detalles.

Solo contiene tres pasos (copiados del blog):

# Setup a directory to hold the patches mkdir <patch-directory> # Create the patches git format-patch -o <patch-directory> --root /path/to/copy # Apply the patches in the new repo using a 3 way merge in case of conflicts # (merges from the other repo are not turned into patches). # The 3way can be omitted. git am --3way <patch-directory>/*.patch

El único problema que tuve fue que no podía aplicar todos los parches a la vez usando

git am --3way <patch-directory>/*.patch

Bajo Windows recibí un error InvalidArgument. Así que tuve que aplicar todos los parches uno tras otro.


Esta respuesta proporciona comandos interesantes basados ​​en git am y se presenta utilizando ejemplos, paso a paso.

Objetivo

  • Desea mover algunos o todos los archivos de un repositorio a otro.
  • Quieres mantener su historia.
  • Pero no te importa mantener etiquetas y ramas.
  • Acepta un historial limitado para archivos renombrados (y archivos en directorios renombrados).

Procedimiento

  1. Extraer 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 el cambio de nombre de archivo en el historial [opcional]
  3. Aplicar nueva historia usando git am

1. Extraer el historial en formato de correo electrónico.

Ejemplo: extraer el historial de file4 , file4 y file5

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

Limpie el directorio de destino temporal

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

Limpia tu fuente de repo

git commit ... # Commit your working files rm .gitignore # Disable gitignore git clean -n # Simulate removal git clean -f # Remove untracked file git checkout .gitignore # Restore gitignore

Extraer 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"'' {} '';''

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

Después: Historial temporal en formato de correo electrónico.

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

2. Reorganizar el árbol de archivos y actualizar el cambio de nombre de archivo en el historial [opcional]

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 # was subdir │ │ ├── file33 # was file3 │ │ └── file44 # was file4 │ └── dirB2 # new dir │ └── file5 # = file5 └── dirH └── file77

Por eso reorganiza tus archivos:

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

Tu historia temporal es ahora:

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

Cambie también los nombres de archivos 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"'' {} '';''

Nota: Esto vuelve a escribir el historial para reflejar el cambio de ruta y nombre de archivo.
(es decir, el cambio de la nueva ubicación / nombre dentro del nuevo repositorio)

3. Aplicar nueva historia.

Tu otro repo es:

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

Aplicar confirmaciones de archivos de historial temporal:

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

Tu otro repo es ahora:

my_other_repo ├── dirF │ ├── file55 │ └── file56 ├── dirB ^ │ ├── dirB1 | New files │ │ ├── file33 | with │ │ └── file44 | history │ └── dirB2 | kept │ └── file5 v └── dirH └── file77

Use el git status para ver la cantidad de confirmaciones listas para ser empujadas :-)

Nota: como el historial se ha reescrito para reflejar la ruta y el cambio de nombre de archivo:
(es decir, comparado con la ubicación / nombre dentro del repositorio anterior)

  • No es necesario git mv para cambiar la ubicación / nombre de archivo.
  • No es necesario git log --follow para acceder al historial completo.

Truco adicional: detecte archivos renombrados / movidos dentro de su repositorio

Para listar 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 las opciones --find-copies-harder o --reverse . También puede eliminar las dos primeras columnas usando cut -f3- y grepping el patrón completo ''{. * =>. *}''.

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


GUARDANDO EL NOMBRE DEL DIRECTORIO

El filtro de subdirectorio (o el subárbol git de comando más corto) funciona bien pero no funcionó para mí, ya que eliminan el nombre del directorio de la información de confirmación. En mi escenario, solo quiero combinar partes de un repositorio en otro y conservar el historial con el nombre completo de la ruta.

Mi solución fue usar el filtro de árbol y simplemente eliminar los archivos y directorios no deseados de un clon temporal del repositorio de origen, luego extraer de ese clon a mi repositorio de destino en 5 pasos simples.

# 1. clone the source git clone ssh://<user>@<source-repo url> cd <source-repo> # 2. remove the stuff we want to exclude git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD # 3. move to target repo and create a merge branch (for safety) cd <path to target-repo> git checkout -b <merge branch> # 4. Add the source-repo as remote git remote add source-repo <path to source-repo> # 5. fetch it git pull source-repo master # 6. check that you got it right (better safe than sorry, right?) gitk


Habiendo tenido una picazón similar al rasguño (aunque solo para algunos archivos de un repositorio determinado), esta secuencia de comandos demostró ser realmente útil: git-import

La versión corta es que crea archivos de parche del archivo o directorio dado ( $object ) desde el repositorio existente:

cd old_repo git format-patch --thread -o "$temp" --root -- "$object"

que luego se aplican a un nuevo repositorio:

cd new_repo git am "$temp"/*.patch

Para más detalles, consulte:


Quería algo robusto y reutilizable (función one-command-and-go + undo), así que escribí el siguiente script de bash. Me funcionó en varias ocasiones, así que pensé en compartirlo aquí.

Es capaz de mover una carpeta /path/to/foo arbitraria /path/to/foo desde repo1 a /some/other/folder/bar repo2 (las rutas de la carpeta pueden ser iguales o diferentes, la distancia desde la carpeta raíz puede ser diferente).

Dado que solo pasa por las confirmaciones que tocan los archivos en la carpeta de entrada (no sobre todas las confirmaciones del repositorio de origen), debería ser bastante rápido incluso en grandes repositorios de origen, si simplemente extrae una subcarpeta profundamente anidada que no se tocó en cada cometer.

Ya que lo que esto hace es crear una rama huérfana con todo el historial del antiguo repo y luego fusionarla con la HEAD, incluso funcionará en caso de conflictos de nombres de archivos (entonces tendrá que resolver una fusión al final del curso) .

Si no hay conflictos de nombre de archivo, solo necesita git commit al final para finalizar la fusión.

El inconveniente es que probablemente no seguirá los REWRITE_FROM de archivos (fuera de la carpeta REWRITE_FROM ) en el repositorio de origen; las solicitudes de extracción en GitHub son bienvenidas para dar cabida a eso.

Enlace de GitHub: git-move-folder-between-repos-keep-history

#!/bin/bash # Copy a folder from one git repo to another git repo, # preserving full history of the folder. SRC_GIT_REPO=''/d/git-experimental/your-old-webapp'' DST_GIT_REPO=''/d/git-experimental/your-new-webapp'' SRC_BRANCH_NAME=''master'' DST_BRANCH_NAME=''import-stuff-from-old-webapp'' # Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash! REWRITE_FROM=''app/src/main/static/'' REWRITE_TO=''app/src/main/static/'' verifyPreconditions() { #echo ''Checking if SRC_GIT_REPO is a git repo...'' && { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } && #echo ''Checking if DST_GIT_REPO is a git repo...'' && { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } && #echo ''Checking if REWRITE_FROM is not empty...'' && { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } && #echo ''Checking if REWRITE_TO is not empty...'' && { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } && #echo ''Checking if REWRITE_FROM folder exists in SRC_GIT_REPO'' && { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } && #echo ''Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME'' && { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } && #echo ''Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME'' && { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } && echo ''[OK] All preconditions met'' } # Import folder from one git repo to another git repo, including full history. # # Internally, it rewrites the history of the src repo (by creating # a temporary orphaned branch; isolating all the files from REWRITE_FROM path # to the root of the repo, commit by commit; and rewriting them again # to the original path). # # Then it creates another temporary branch in the dest repo, # fetches the commits from the rewritten src repo, and does a merge. # # Before any work is done, all the preconditions are verified: all folders # and branches must exist (except REWRITE_TO folder in dest repo, which # can exist, but does not have to). # # The code should work reasonably on repos with reasonable git history. # I did not test pathological cases, like folder being created, deleted, # created again etc. but probably it will work fine in that case too. # # In case you realize something went wrong, you should be able to reverse # the changes by calling `undoImportFolderFromAnotherGitRepo` function. # However, to be safe, please back up your repos just in case, before running # the script. `git filter-branch` is a powerful but dangerous command. importFolderFromAnotherGitRepo(){ SED_COMMAND=''s-/t/"*-/t''${REWRITE_TO}''-'' verifyPreconditions && cd "${SRC_GIT_REPO}" && echo "Current working directory: ${SRC_GIT_REPO}" && git checkout "${SRC_BRANCH_NAME}" && echo ''Backing up current branch as FILTER_BRANCH_BACKUP'' && git branch -f FILTER_BRANCH_BACKUP && SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" && echo "Creating temporary branch ''${SRC_BRANCH_NAME_EXPORTED}''..." && git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" && echo ''Rewriting history, step 1/2...'' && git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} && echo ''Rewriting history, step 2/2...'' && git filter-branch -f --index-filter / "git ls-files -s | sed /"$SED_COMMAND/" | GIT_INDEX_FILE=/$GIT_INDEX_FILE.new git update-index --index-info && mv /$GIT_INDEX_FILE.new /$GIT_INDEX_FILE" HEAD && cd - && cd "${DST_GIT_REPO}" && echo "Current working directory: ${DST_GIT_REPO}" && echo "Adding git remote pointing to SRC_GIT_REPO..." && git remote add old-repo ${SRC_GIT_REPO} && echo "Fetching from SRC_GIT_REPO..." && git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" && echo "Checking out DST_BRANCH_NAME..." && git checkout "${DST_BRANCH_NAME}" && echo "Merging SRC_GIT_REPO/" && git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit && cd - } # If something didn''t work as you''d expect, you can undo, tune the params, and try again undoImportFolderFromAnotherGitRepo(){ cd "${SRC_GIT_REPO}" && SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" && git checkout "${SRC_BRANCH_NAME}" && git branch -D "${SRC_BRANCH_NAME_EXPORTED}" && cd - && cd "${DST_GIT_REPO}" && git remote rm old-repo && git merge --abort cd - } importFolderFromAnotherGitRepo #undoImportFolderFromAnotherGitRepo


Sí, pulsar en el --subdirectory-filter de filter-branch fue clave. El hecho de que lo usaste esencialmente demuestra que no hay una manera más fácil: no tenías más remedio que volver a escribir el historial, ya que querías 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 (p. Ej., pull ) reescribe el historial, no hay forma de que puedas usarlos para lograr esto.

Por supuesto, podría refinar los detalles, ya que parte de su clonación y ramificación no fue 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.


Si las rutas para los archivos en cuestión son las mismas en los dos repositorios y desea traer solo un archivo o un pequeño conjunto de archivos relacionados, una forma fácil de hacerlo es usar git cherry-pick .

El primer paso es llevar las confirmaciones del otro repositorio a su propio repositorio local usando git fetch <remote-url> . Esto dejará a FETCH_HEAD apuntando a la confirmación de cabecera del otro repositorio; Si desea conservar una referencia a ese compromiso después de haber realizado otras búsquedas, puede etiquetarlo con la git tag other-head FETCH_HEAD .

A continuación, deberá crear una confirmación inicial para ese archivo (si no existe) o una confirmación para llevar el archivo a un estado que pueda parcharse con la primera confirmación del otro repositorio que desee traer. Puede podrá hacer esto con un git cherry-pick <commit-0> si commit-0 introdujo los archivos que desea, o si necesita construir el commit ''a mano''. Agregue -n a las opciones de selección directa si necesita modificar la confirmación inicial para, por ejemplo, eliminar los archivos de esa confirmación que no desea incluir.

Después de eso, puede continuar con git cherry-pick subsiguientes confirmaciones, nuevamente usando -n donde sea necesario. En el caso más simple (todas las confirmaciones son exactamente lo que usted quiere y se aplican de manera limpia) puede proporcionar la lista completa de confirmaciones en la línea de comando cherry-pick: git cherry-pick <commit-1> <commit-2> <commit-3> ...


Si su historial es sensato, puede quitar las confirmaciones como parche y aplicarlas en el nuevo repositorio:

cd repository git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch cd ../another_repository git am < ../repository/patch

O en una linea

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am)

(Tomado de los documentos de Exherbo )