Combinar dos repositorios Git sin romper el historial de archivos


Necesito combinar dos repositorios Git en un nuevo tercer repositorio. He encontrado muchas descripciones de cómo hacer esto usando una fusión de subárbol (por ejemplo La respuesta de Jakub Narębski en ¿Cómo combinar dos repositorios Git?) y seguir esas instrucciones en su mayoría funciona, excepto que cuando confirmo el subárbol fusionar todos los archivos de los repositorios antiguos se registran como nuevos archivos agregados. Puedo ver el historial de confirmaciones de los repositorios antiguos cuando lo hago git log, pero si I do git log <file> muestra solo una confirmación para ese archivo - la fusión de subárbol. A juzgar por los comentarios sobre la respuesta anterior, no estoy solo en ver este problema, pero no he encontrado soluciones publicadas para él.

¿Hay alguna manera de fusionar repositorios y dejar intacto el historial de archivos individuales?

Author: Community, 2012-10-24

5 answers

Resulta que la respuesta es mucho más simple si simplemente intentas unir dos repositorios y hacer que parezca que fue así todo el tiempo en lugar de administrar una dependencia externa. Simplemente necesita agregar controles remotos a sus repositorios antiguos, fusionarlos en su nuevo maestro, mover los archivos y carpetas a un subdirectorio, confirmar el movimiento y repetir para todos los repositorios adicionales. Los submódulos, las fusiones de subárboles y los rebases de fantasía están destinados a resolver un problema ligeramente diferente y no adecuado para lo que estaba tratando de hacer.

Aquí hay un ejemplo de script de Powershell para unir dos repositorios:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
dir > deleteme.txt
git add .
git commit -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Clean up our dummy file because we don't need it any more
git rm .\deleteme.txt
git commit -m "Clean up initial file"

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir –exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Obviamente, podría combinar old_b en old_a (que se convierte en el nuevo repositorio combinado) si prefiere hacerlo: modifique el script para adaptarlo.

Si también desea traer ramas de características en curso, use esto:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

Esa es la única parte no obvia del proceso-que no es una fusión de subárbol, sino más bien un argumento a la combinación recursiva normal que le dice a Git que cambiamos el nombre del destino y que ayuda a Git a alinear todo correctamente.

Escribí una explicación un poco más detallada aquí.

 218
Author: Eric Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-24 02:05:41

Aquí hay una forma que no reescribe ningún historial, por lo que todos los identificadores de confirmación seguirán siendo válidos. El resultado final es que los archivos del segundo repositorio terminarán en un subdirectorio.

  1. Agregue el segundo repositorio como un control remoto:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Asegúrate de que has descargado todos los commits de secondrepo:

    git fetch secondrepo
    
  3. Crea una rama local a partir de la rama del segundo repositorio:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Mueve todos sus archivos a una subdirectorio:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Fusiona la segunda rama en la rama master del primer repositorio:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

Su repositorio tendrá más de una confirmación root, pero eso no debería suponer un problema.

 113
Author: Flimm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-02 02:51:32

Por favor, echa un vistazo a usar

git rebase --root --preserve-merges --onto

Vincular dos historias al principio de sus vidas.

Si tienes rutas que se superponen, arréglalas con

git filter-branch --index-filter

Cuando utilice el registro, asegúrese de" encontrar copias más difíciles " con

git log -CC

De esa manera encontrará cualquier movimiento de archivos en la ruta.

 8
Author: Adam Dymitruk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-24 01:56:22

Convertí la solución de @Flimm esto en un git alias como este (agregado a mi ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"
 5
Author: Fredrik Erlandsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:45

Esta función clonará el repositorio remoto en el directorio de repositorio local:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Cómo usar:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Beneficio!

 3
Author: Andrey Izman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-11 08:24:06