Combinando múltiples repositorios git

207

Digamos que tengo una configuración que se parece a

phd/code/
phd/figures/
phd/thesis/

Por razones históricas, todos estos tienen sus propios repositorios git. Pero me gustaría combinarlos en uno solo para simplificar un poco las cosas. Por ejemplo, en este momento podría hacer dos conjuntos de cambios y tener que hacer algo como

cd phd/code
git commit 
cd ../figures
git commit

Sería (ahora) bueno solo realizar

cd phd
git commit

Parece que hay un par de formas de hacer esto usando submódulos o sacando de mis sub-repositorios, pero eso es un poco más complejo de lo que estoy buscando. Por lo menos, sería feliz con

cd phd
git init
git add [[everything that's already in my other repositories]]

Pero eso no parece una frase. ¿Hay algo en giteso que pueda ayudarme?

Will Robertson
fuente
Considere también este gran enfoque: stackoverflow.com/questions/1425892/…
Johan Sjöberg
Considere también: saintgimp.org/2013/01/22/…
ptim
El script join-git-repos.py hace un buen trabajo si tiene repositorios separados, cada uno con ramas maestras que desea combinar.
Marcar

Respuestas:

149

Aquí hay una solución que di aquí :

  1. Primero haga una copia de seguridad completa de su directorio de doctorado: ¡no quiero ser responsable de sus años perdidos de trabajo duro! ;-)

    $ cp -r phd phd-backup
    
  2. Mueva el contenido de phd/codea phd/code/codey corrija el historial para que parezca que siempre ha estado allí (esto usa el comando filter-branch de git ):

    $ cd phd/code
    $ git filter-branch --index-filter \
        'git ls-files -s | sed "s#\t#&code/#" |
         GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
         git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
    
  3. Lo mismo para el contenido de phd/figuresy phd/thesis(solo reemplace codecon figuresy thesis).

    Ahora su estructura de directorio debería verse así:

    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
    
  4. Luego cree un repositorio git en el directorio raíz, extraiga todo y elimine los repositorios antiguos:

    $ cd phd
    $ git init
    
    $ git pull code
    $ rm -rf code/code
    $ rm -rf code/.git
    
    $ git pull figures --allow-unrelated-histories
    $ rm -rf figures/figures
    $ rm -rf figures/.git
    
    $ git pull thesis --allow-unrelated-histories
    $ rm -rf thesis/thesis
    $ rm -rf thesis/.git
    

    Finalmente, ahora deberías tener lo que querías:

    phd
      |_.git
      |_code
      |    |_(your code...)
      |_figures
      |    |_(your figures...)
      |_thesis
           |_(your thesis...)
    

Un lado bueno de este procedimiento es que dejará en su lugar archivos y directorios no versionados .

Espero que esto ayude.


Sin embargo, solo una palabra de advertencia: si su codedirectorio ya tiene un codesubdirectorio o archivo, las cosas podrían salir muy mal (lo mismo para figuresy thesispor supuesto). Si ese es el caso, simplemente cambie el nombre de ese directorio o archivo antes de realizar todo este procedimiento:

$ cd phd/code
$ git mv code code-repository-migration
$ git commit -m "preparing the code directory for migration"

Y cuando finalice el procedimiento, agregue este paso final:

$ cd phd
$ git mv code/code-repository-migration code/code
$ git commit -m "final step for code directory migration"

Por supuesto, si el codesubdirectorio o el archivo no está versionado, simplemente use en mvlugar de git mvy olvide el git commits.

MiniQuark
fuente
13
Gracias por este fragmento: hizo exactamente lo que necesitaba (una vez que tomé en cuenta que Mac OS X no procesaba "\ t" (tuve que usar ^ V ^ I en su lugar).
Craig Trader
66
No pude lograr que esto funcionara al principio y finalmente encontré la solución al problema en otro viejo tablero de mensajes. En la última línea, tuve que poner comillas alrededor de los nombres de los archivos así: ¡ mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADy luego funcionó muy bien!
Jorin
3
El comando funky filter-branch es de las páginas de manual de filter-branch de git. Debería decir eso como: a) debe atribuirse correctamente b) No ejecutaré dicho comando solo porque alguien, incluso con gran reputación, lo publicó en StackOverflow. Sabiendo que es de las páginas del manual, lo haré.
tymtam
55
¡CUIDADO! MacOS X no utiliza la extensión GNU de sed, por lo que no conoce la secuencia \ t. ¡El resultado es una historia desordenada! Mi solución fue pegar el código en un archivo de script y escribir un verdadero carácter <TAB> en él. Desde la Terminal, se puede ingresar una pestaña presionando ctrl + v y luego escribiendo un <TAB>. No he probado la solución de Craig
Gil Vegliach
55
¡CUIDADO (2)! Observe también que si algunos archivos o directorios contienen guiones ('-'), el comando sed fallará. En ese caso, puede sustituirlo con algo como 's ~ \ t ~ & code / ~'. Aquí, aplicando la misma lógica,
tenga
75

git-stitch-repoprocesará la salida de git-fast-export --all --date-orderen los repositorios git dados en la línea de comandos, y creará una secuencia adecuada para git-fast-importeso creará un nuevo repositorio que contiene todas las confirmaciones en un nuevo árbol de confirmaciones que respeta el historial de todos los repositorios de origen.

Aristóteles Pagaltzis
fuente
33
Uh, es una herramienta de terceros, no parte de git ... :-)
Aristóteles Pagaltzis
1
De hecho, ahora me dices :) Oh, bueno, supongo que tuve que aprender a instalar paquetes de CPAN algún día ...
Will Robertson
1
Gracias por señalar ese comando. Solo lo he estado usando para ayudar a mover algunos repositorios de SVN a Git.
signine
1
¡ADVERTENCIA puede no funcionar si tiene sucursales / fusiones! Desde la página git-stich-repo : "git-stich-repo funciona perfectamente con repositorios que tienen un historial lineal (sin fusiones) ... Las mejoras en el algoritmo de costura agregado en la versión 0.06 deberían ser adecuadas para trabajar con repositorios que tienen ramas y fusiones ".
Bryan P
66
Este es un script externo, la respuesta es demasiado corta y no es realmente útil, este script tiene problemas con los commits de fusión, no muchas personas manejarían Perl o CPAN y esto no se explica bien en la respuesta. Entonces ... -1, lo siento.
Haralan Dobrev
20

Quizás, simplemente (de manera similar a la respuesta anterior, pero usando comandos más simples), haga en cada uno de los repositorios antiguos separados una confirmación que mueva el contenido a un subdirectorio adecuadamente nombrado, por ejemplo:

$ cd phd/code
$ mkdir code
# This won't work literally, because * would also match the new code/ subdir, but you understand what I mean:
$ git mv * code/
$ git commit -m "preparing the code directory for migration"

y luego fusionando los tres repositorios separados en uno nuevo, haciendo algo como:

$ cd ../..
$ mkdir phd.all
$ cd phd.all
$ git init
$ git pull ../phd/code
...

Luego guardará sus historias, pero continuará con un solo repositorio.

imz - Ivan Zakharyaschev
fuente
Esto está bien, pero si está fusionando un repositorio en otro (es decir, phd era un repositorio ya no vacío), entonces si phd tenía carpetas con nombres iguales a las subcarpetas en el directorio de código, encontrará problemas como 'git pull .. / phd / code 'extrae todos los commits con las rutas originales y solo al final aplica el mv commit.
tymtam el
1
@Tymek: pero esto seguirá funcionando en esa situación, sin problemas. Lo que no será bueno es que las rutas en el historial no serán "correctas" (corresponden a las nuevas rutas).
imz - Ivan Zakharyaschev
19

Puede probar la estrategia de combinación de subárbol . Te permitirá combinar el repositorio B en el repositorio A. La ventaja git-filter-branches que no requiere que reescribas tu historial de repositorio A (rompiendo sumas de SHA1).

Leif Gruenwoldt
fuente
El enlace no funciona y esto no preservaría la historia, ¿verdad?
tymtam el
3
@Tymek (lo siento, partes de kernel.org todavía están caídas después de la violación de seguridad). Rompe SHA1 del repo B. entrante Pero A permanece intacto.
Leif Gruenwoldt
2
Aquí hay un espejo de ese documento por ahora ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/howto/…
Leif Gruenwoldt
1
@LeifGruenwoldt El primer enlace está funcionando ahora. Y el enlace espejo se ha ido, debería eliminarlo, supongo.
Vadim Kotov
9

La solución git-filter-branch funciona bien, pero tenga en cuenta que si su repositorio git proviene de una importación SVN, puede fallar con un mensaje como:

Rewrite 422a38a0e9d2c61098b98e6c56213ac83b7bacc2 (1/42)mv: cannot stat `/home/.../wikis/nodows/.git-rewrite/t/../index.new': No such file or directory

En este caso, debe excluir la revisión inicial de la rama de filtro, es decir, cambiarla HEADal final para [SHA of 2nd revision]..HEADver:

http://www.git.code-experiments.com/blog/2010/03/merging-git-repositories.html

Gareth
fuente
2
¡Gracias! ¡Me he estado rascando la cabeza por qué esto no funcionaba! El repositorio de hecho vino de SVN.
Arthur Maltson
1
El mismo error cuando hago eso. Tengo mis esperanzas. Además, el enlace ahora está roto.
Ryan
¿Podría explicar qué quiere decir con "cambiar la cabeza a ...", mi repositorio proviene de una importación de SVN y estoy enfrentando exactamente este problema, agradecería mucho la ayuda!
5

La solución @MiniQuark me ayudó mucho, pero desafortunadamente no tiene en cuenta las etiquetas que están en los repositorios de origen (al menos en mi caso). A continuación se muestra mi mejora en la respuesta @MiniQuark.

  1. Primero cree un directorio que contendrá repositorios compuestos y repositorios combinados, cree un directorio para cada uno combinado.

    $ mkdir new_phd
    $ mkdir new_phd / code
    $ mkdir new_phd / figures
    $ mkdir new_phd / thesis

  2. Haga una extracción de cada repositorio y obtenga todas las etiquetas. (Presentación de instrucciones solo para el codesubdirectorio)

    $ cd new_phd / code
    $ git init
    $ git pull ../../original_phd/code master
    $ git fetch ../../original_phd/code refs / tags / *: refs / tags / *

  3. (Esta es una mejora al punto 2 en respuesta MiniQuark) Mover el contenido de new_phd/codea new_phd/code/codey añadir code_prefeix antes de cada etiqueta

    $ git filter-branch --index-filter 'git ls-files -s | sed "s- \ t \" * - & code / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv $ GIT_INDEX_FILE.new $ GIT_INDEX_FILE '--tag-name-filter' sed" s -. * - código _ & - "'HEAD

  4. Después de hacerlo, habrá el doble de etiquetas que antes de hacer filter-branch. Las etiquetas antiguas permanecen en repositorio y code_se agregan nuevas etiquetas con prefijo.

    $ git tag
    mytag1
    code_mytag1

    Eliminar etiquetas viejas manualmente:

    $ ls .git / refs / tags / * | grep -v "/ code_" | xargs rm

    Repita el punto 2,3,4 para otros subdirectorios.

  5. Ahora tenemos una estructura de directorios como en @MiniQuark y el punto 3.

  6. Haga lo mismo que en el punto 4 de MiniQuark anwser, pero después de hacer un tirón y antes de eliminar el .gitdirectorio, obtenga etiquetas:

    $ git fetch catalog refs / tags / *: refs / tags / *

    Seguir..

Esta es solo otra solución. Espero que ayude a alguien, me ayudó :)

MichK
fuente
5

La respuesta de git-stitch-repo de Aristóteles Pagaltzis solo funciona para repositorios con una historia lineal simple.

La respuesta de MiniQuark funciona para todos los repositorios, pero no maneja etiquetas y ramas.

Creé un programa que funciona de la misma manera que MiniQuark describe, pero utiliza una confirmación de fusión (con N padres) y también recrea todas las etiquetas y ramas para señalar estas confirmaciones de fusión.

Consulte el repositorio git-merge-repos para ver ejemplos de cómo usarlo.

robinst
fuente
3

He creado una herramienta que hace esta tarea. El método utilizado es similar (internamente crea algunas cosas como --filter-branch) pero es más amigable. Es GPL 2.0

http://github.com/geppo12/GitCombineRepo

Giuseppe Monteleone
fuente
3

En realidad, git-stitch-repo ahora admite ramas y etiquetas, incluidas las etiquetas anotadas (descubrí que había un error que informé, y se solucionó). Lo que encontré útil es con etiquetas. Dado que las etiquetas se adjuntan a los commits, y algunas de las soluciones (como el enfoque de Eric Lee) no logran manejar las etiquetas. Intenta crear una rama a partir de una etiqueta importada, y deshacerá cualquier combinación / movimiento de git y lo enviará de vuelta como si el repositorio consolidado fuera casi idéntico al repositorio del que proviene la etiqueta. Además, hay problemas si usa la misma etiqueta en múltiples repositorios que 'fusionó / ​​consolidó'. Por ejemplo, si tiene los repositorios A y B, ambos tienen la etiqueta rel_1.0. Combina el repositorio A y el repositorio B en el repositorio AB. Como las etiquetas rel_1.0 están en dos confirmaciones diferentes (una para A y otra para B), ¿Qué etiqueta será visible en AB? La etiqueta del repositorio A importado o del repositorio B importado, pero no ambos.

git-stitch-repo ayuda a resolver ese problema creando etiquetas rel_1.0-A y rel_1.0-B. Es posible que no pueda pagar la etiqueta rel_1.0 y esperar ambas, pero al menos puede ver ambas, y en teoría, puede fusionarlas en una rama local común y luego crear una etiqueta rel_1.0 en esa rama fusionada (suponiendo que solo fusionar y no cambiar el código fuente). Es mejor trabajar con sucursales, ya que puede combinar ramas similares de cada repositorio en sucursales locales. (dev-a y dev-b se pueden fusionar en una rama de desarrollo local que luego se puede enviar al origen).

usuario3622356
fuente
2

La secuencia que sugeriste

git init
git add *
git commit -a -m "import everything"

funcionará, pero perderá su historial de confirmaciones.

Patrick_O
fuente
Perder el historial no es tan malo, pero dado que el repositorio es para mi propio trabajo (es decir, es privado) hay muchas cosas allí que no quiero versionadas o que aún no están versionadas.
Will Robertson, el
1

Para fusionar un segundo proyecto dentro de un proyecto principal:

A) En el segundo proyecto

git fast-export --all --date-order > /tmp/secondProjectExport

B) En el proyecto principal:

git checkout -b secondProject
git fast-import --force < /tmp/secondProjectExport

En esta rama, realice todas las transformaciones pesadas que necesita hacer y comprométalas.

C) Luego de vuelta al maestro y una fusión clásica entre las dos ramas:

git checkout master
git merge secondProject
usuario123568943685
fuente
Esto fusionaría todos los archivos y carpetas en la raíz de ambos proyectos git en un solo proyecto. Dudo que nadie quiera que esto suceda.
Clintm
0

Lanzaré mi solución aquí también. Básicamente es un envoltorio de script bash bastante simple git filter-branch. Al igual que otras soluciones, solo migra ramas maestras y no migra etiquetas. Sin embargo, los historiales completos de confirmación maestra se migran y es una secuencia de comandos bash corta, por lo que debería ser relativamente fácil de revisar o modificar para los usuarios.

https://github.com/Oakleon/git-join-repos

chrishiestand
fuente
0

Este script bash soluciona el problema de caracteres de tabulación sed (en MacOS, por ejemplo) y el problema de los archivos faltantes.

export SUBREPO="subrepo"; # <= your subrepository name here
export TABULATOR=`printf '\t'`;
FILTER='git ls-files -s | sed "s#${TABULATOR}#&${SUBREPO}/#" |
  GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
  git update-index --index-info &&
  if [ -f "$GIT_INDEX_FILE.new" ]; then mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE; else echo "git filter skipped missing file: $GIT_INXEX_FILE.new"; fi'

git filter-branch --index-filter "$FILTER" HEAD

Esta es una combinación de publicaciones de miniquark , marius-butuc y ryan . Saludos a ellos!

bue
fuente