¿Cómo importar el repositorio Git existente a otro?

477

Tengo un repositorio de Git en una carpeta llamada XXX , y tengo un segundo repositorio de Git llamado YYY .

Quiero importar el repositorio XXX al repositorio YYY como un subdirectorio llamado ZZZ y agregar todo el historial de cambios de XXX a YYY .

Estructura de carpetas antes:

├── XXX
│   ├── .git
│   └── (project files)
└── YYY
    ├── .git
    └── (project files)

Estructura de carpetas después de:

YYY
├── .git  <-- This now contains the change history from XXX
├──  ZZZ  <-- This was originally XXX
│    └── (project files)
└──  (project files)

¿Se puede hacer esto o debo recurrir al uso de submódulos?

Vijay Patel
fuente
2
En Github ahora es posible hacer esto desde la interfaz web cuando creas un nuevo repositorio
bgcode
Posible duplicado de ¿Cómo fusionar dos repositorios git?
BuZZ-dEE

Respuestas:

430

Probablemente, la forma más simple sería colocar las cosas XXX en una rama en AAA y luego fusionarlas en master:

En YYY :

git remote add other /path/to/XXX
git fetch other
git checkout -b ZZZ other/master
mkdir ZZZ
git mv stuff ZZZ/stuff                      # repeat as necessary for each file/dir
git commit -m "Moved stuff to ZZZ"
git checkout master                
git merge ZZZ --allow-unrelated-histories   # should add ZZZ/ to master
git commit
git remote rm other
git branch -d ZZZ                           # to get rid of the extra branch before pushing
git push                                    # if you have a remote, that is

De hecho, acabo de probar esto con un par de mis repositorios y funciona. A diferencia de la respuesta de Jörg, no le permitirá continuar usando el otro repositorio, pero no creo que haya especificado eso de todos modos.

Nota: Dado que esto se escribió originalmente en 2009, git ha agregado la combinación de subárbol mencionada en la respuesta a continuación. Probablemente usaría ese método hoy, aunque, por supuesto, este método todavía funciona.

ebneter
fuente
1
Gracias. Utilicé una versión ligeramente modificada de tu técnica: creé una rama de 'puesta en escena' en XXX donde creé la carpeta ZZZ y moví las 'cosas' a ella. Luego fusioné XXX con YYY.
Vijay Patel el
1
Esto funciono muy bien para mi. Los únicos cambios que hice fueron: 1) "git branch -d ZZZ" antes del empuje porque no quería que esta rama temporal se quedara. 2) "git push" me estaba dando el error: "No hay referencias en común y ninguna especificada; no hacer nada. Tal vez debería especificar una rama como 'maestro'". (El origen al que estaba presionando era un repositorio vacío y vacío). Pero "git push --all" funcionó como un campeón.
CrazyPyro
1
Quería terminar solo con la carpeta ZZZ más el historial en el repositorio YYY: quería eliminar el repositorio XXX original y la rama ZZZ en el repositorio YYY. Encontré que eliminar la rama ZZZ como @CrazyPyro sugirió que eliminara el historial; para mantenerlo, fusioné la rama ZZZ en maestra antes de eliminarla.
Oli Studholme
44
@SebastianBlask Acabo de perder el tiempo con esto con dos de mis repositorios, y me di cuenta de que falta un paso que nadie parecía notar, a pesar de mis votos positivos sobre esto durante años. :-) Mencioné fusionarlo en maestro, pero en realidad no lo mostré.
Editándolo
2
podría agregar algo como esto, cuando mueva archivos a su subcarpeta: git mv $(ls|grep -v <your foldername>) <your foldername>/ esto copiará todos los archivos y carpetas en su nueva carpeta
serup
367

Si desea conservar el historial de confirmación exacto del segundo repositorio y, por lo tanto, también conserva la capacidad de combinar fácilmente los cambios ascendentes en el futuro, este es el método que desea. Resulta en un historial no modificado del subárbol que se importa a su repositorio más una confirmación de fusión para mover el repositorio fusionado al subdirectorio.

git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit --allow-unrelated-histories XXX_remote/master
git read-tree --prefix=ZZZ/ -u XXX_remote/master
git commit -m "Imported XXX as a subtree."

Puede realizar un seguimiento de los cambios ascendentes de esta manera:

git pull -s subtree XXX_remote master

Git descubre por sí mismo dónde están las raíces antes de hacer la fusión, por lo que no necesita especificar el prefijo en las fusiones posteriores.

La desventaja es que en el historial combinado los archivos no están prefijados (no están en un subdirectorio). Como resultado, git log ZZZ/ale mostrará todos los cambios (si los hay) excepto aquellos en el historial combinado. Tu puedes hacer:

git log --follow -- a

pero eso no mostrará los cambios que no sean en el historial combinado.

En otras palabras, si no cambia ZZZlos archivos en el repositorio XXX, debe especificar --followuna ruta sin prefijar. Si los cambia en ambos repositorios, entonces tiene 2 comandos, ninguno de los cuales muestra todos los cambios.

Versiones de Git anteriores a 2.9 : no es necesario pasar la --allow-unrelated-historiesopción a git merge.

El método en la otra respuesta que usa read-treey omite el merge -s ourspaso no es diferente de copiar los archivos con cp y confirmar el resultado.

La fuente original era del artículo de ayuda "Subtree Merge" de github . Y otro enlace útil .

ColinM
fuente
99
esto no parece haber conservado el historial ... si hago un git logen cualquiera de los archivos que obtuve, solo veo la confirmación de fusión única y nada de su vida anterior en el otro repositorio. Git 1.8.0
Anentropic
8
aha! si uso la ruta anterior del archivo importado, es decir, omito el subdirectorio en el que se importó, entonces git log me dará el historial de confirmación, por ejemplo, en git log -- myfilelugar degit log -- rack/myfile
Anentropic
2
@FrancescoFrassinelli, ¿no es eso deseable? Traer la historia es una característica de este método.
patrickvacek
44
@FrancescoFrassinelli, si no quieres historia, ¿por qué no solo haces una copia normal? Estoy tratando de descubrir qué te atraería a este método si no fuera por el historial: ¡esa es la única razón por la que usé este método!
patrickvacek
77
Desde Git 2.9, necesita la opción --allow-unrelated-historiesal hacer la fusión.
stuXnet
113

git-subtreees un script diseñado exactamente para este caso de uso de fusionar múltiples repositorios en uno y preservar el historial (y / o dividir el historial de subárboles, aunque eso parece ser irrelevante para esta pregunta). Se distribuye como parte del árbol git desde la versión 1.7.11 .

Para fusionar un repositorio <repo>en la revisión <rev>como subdirectorio <prefix>, use git subtree addlo siguiente:

git subtree add -P <prefix> <repo> <rev>

git-subtree implementa la estrategia de fusión de subárbol de una manera más fácil de usar.

Para su caso, dentro del repositorio AAA, debería ejecutar:

git subtree add -P ZZZ /path/to/XXX.git master

La desventaja es que en el historial combinado los archivos no están prefijados (no están en un subdirectorio). Como resultado, git log ZZZ/ale mostrará todos los cambios (si los hay) excepto aquellos en el historial combinado. Tu puedes hacer:

git log --follow -- a

pero eso no mostrará los cambios que no sean en el historial combinado.

En otras palabras, si no cambia ZZZlos archivos en el repositorio XXX, debe especificar --followuna ruta sin prefijar. Si los cambia en ambos repositorios, entonces tiene 2 comandos, ninguno de los cuales muestra todos los cambios.

Más sobre esto aquí .

kynan
fuente
44
Si tiene un directorio para fusionar en lugar de un repositorio simple o remoto,git subtree add -P name-of-desired-prefix ~/location/of/git/repo-without-.git branch-name
Tatsh
2
Experiencia novata: git (versión 2.9.0.windows.1) responde "fatal: argumento ambiguo 'HEAD': revisión desconocida o ruta no en el árbol de trabajo" cuando probé esto en un repositorio local recién inicializado, no desnudo, Pero funcionó bien después de que realmente puse en marcha el nuevo repositorio, es decir, después de agregar un archivo sin formato y confirmar de la manera habitual.
Stein
Funcionó muy bien para mi escenario.
Johnny Utahh
Oh esto es fantástico
dwjohnston
Usé la sugerencia de @Tatsh y funcionó para mí
Carmine Tambascia
49

Hay una instancia bien conocida de esto en el repositorio de Git, que se conoce colectivamente en la comunidad de Git como " la fusión más genial de la historia " (después de la línea de asunto que Linus Torvalds usó en el correo electrónico a la lista de correo de Git que describe esto unir). En este caso, la gitkGUI de Git, que ahora es parte de Git propiamente dicha, solía ser un proyecto separado. Linus logró fusionar ese repositorio en el repositorio de Git de una manera que

  • aparece en el repositorio de Git como si siempre se hubiera desarrollado como parte de Git,
  • toda la historia se mantiene intacta y
  • todavía se puede desarrollar de forma independiente en su antiguo repositorio, con cambios simplemente git pulleditados.

El correo electrónico contiene los pasos necesarios para reproducirse, pero no es para los débiles de corazón: primero, Linus escribió a Git, por lo que probablemente sepa un poco más que tú o yo, y segundo, esto fue hace casi 5 años. y Git ha mejorado considerablemente desde entonces, por lo que tal vez ahora sea mucho más fácil.

En particular, supongo que hoy en día uno usaría un submódulo gitk, en ese caso específico.

Jörg W Mittag
fuente
3
Por cierto. la estrategia utilizada para las fusiones posteriores (si las hay) se llama fusión de subárbol , y hay una git-subtreeherramienta de terceros que puede ayudarlo con esto: github.com/apenwarr/git-subtree
Jakub Narębski
Gracias, me olvidé de eso. La subtreeestrategia de fusión, especialmente en combinación con la git-subtreeherramienta, es una buena alternativa, quizás incluso superior a los submódulos.
Jörg W Mittag el
12

La forma más sencilla de hacerlo es usar git format-patch.

Supongamos que tenemos 2 repositorios git foo y bar .

foo contiene:

  • foo.txt
  • .git

la barra contiene:

  • bar.txt
  • .git

y queremos terminar con foo que contiene el historial de la barra y estos archivos:

  • foo.txt
  • .git
  • foobar / bar.txt

Entonces para hacer eso:

 1. create a temporary directory eg PATH_YOU_WANT/patch-bar
 2. go in bar directory
 3. git format-patch --root HEAD --no-stat -o PATH_YOU_WANT/patch-bar --src-prefix=a/foobar/ --dst-prefix=b/foobar/
 4. go in foo directory
 5. git am PATH_YOU_WANT/patch-bar/*

Y si queremos reescribir todas las confirmaciones de mensajes desde la barra, podemos hacer, por ejemplo, en Linux:

git filter-branch --msg-filter 'sed "1s/^/\[bar\] /"' COMMIT_SHA1_OF_THE_PARENT_OF_THE_FIRST_BAR_COMMIT..HEAD

Esto agregará "[bar]" al comienzo de cada mensaje de confirmación.

Damien R.
fuente
Si el repositorio original contenía ramas y fusiones, git ames probable que falle.
Adam Monsen
1
Menor error: git am elimina algo [ ]del mensaje de confirmación. Por lo tanto, debe usar un marcador diferente que[bar]
HRJ
No funciono para mi. Error "conseguido: foobar / mySubDir / test_host1: no existe en el índice. La copia del parche que falló se encuentra en: /home/myuser/src/proj/.git/rebase-apply/patch Cuando haya resuelto este problema , ejecute "git am --continue". Esto fue después de aplicar 11 parches (de 60).
oligofren
1
Este blog tiene una respuesta similar a una pregunta algo diferente (mover solo archivos seleccionados).
Jesse Glick
Veo una desventaja, todos los commits se agregan al HEAD del repositorio de destino.
CSchulz
8

Esta función clonará el repositorio remoto en el directorio de repositorio local, después de fusionar se guardarán todas las confirmaciones, git logse mostrarán las confirmaciones originales y las rutas adecuadas:

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 utilizar:

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

Si realiza pequeños cambios, incluso puede mover archivos / directorios de repositorio fusionado a diferentes rutas, por ejemplo:

repo="https://github.com/example/example"
path="$(pwd)"

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

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

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

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"

Notices
Paths reemplaza via sed, así que asegúrese de que se movió en las rutas adecuadas después de la fusión.
El --allow-unrelated-historiesparámetro solo existe desde git> = 2.9.

Andrey Izman
fuente
2
Para la gente de OS X, instale gnu-sedpara que la git-add-repofunción funcione. Gracias de nuevo Andrey!
ptaylor
7

Según este artículo , usar subárbol es lo que funcionó para mí y solo se transfirió el historial aplicable. Publicar aquí en caso de que alguien necesite los pasos (asegúrese de reemplazar los marcadores de posición con valores aplicables a usted):

en su repositorio de origen dividir subcarpeta en una nueva rama

git subtree split --prefix=<source-path-to-merge> -b subtree-split-result

en su repositorio de destino fusionarse en la rama de resultados divididos

git remote add merge-source-repo <path-to-your-source-repository>
git fetch merge-source-repo
git merge -s ours --no-commit merge-source-repo/subtree-split-result
git read-tree --prefix=<destination-path-to-merge-into> -u merge-source-repo/subtree-split-result

verificar sus cambios y comprometerse

git status
git commit

No te olvides de

Limpiar eliminando la subtree-split-resultrama

git branch -D subtree-split-result

Elimine el control remoto que agregó para obtener los datos del repositorio de origen

git remote rm merge-source-repo

Alex
fuente
3

Agregando otra respuesta ya que creo que esto es un poco más simple. Se realiza una extracción de repo_dest en repo_to_import y luego se ejecuta una url push --set-upstream: repo_dest master.

Este método me ha funcionado importando varios repositorios más pequeños en uno más grande.

Cómo importar: repo1_to_import to repo_dest

# checkout your repo1_to_import if you don't have it already 
git clone url:repo1_to_import repo1_to_import
cd repo1_to_import

# now. pull all of repo_dest
git pull url:repo_dest
ls 
git status # shows Your branch is ahead of 'origin/master' by xx commits.
# now push to repo_dest
git push --set-upstream url:repo_dest master

# repeat for other repositories you want to import

Cambie el nombre o mueva los archivos y directorios a la posición deseada en el repositorio original antes de realizar la importación. p.ej

cd repo1_to_import
mkdir topDir
git add topDir
git mv this that and the other topDir/
git commit -m"move things into topDir in preparation for exporting into new repo"
# now do the pull and push to import

El método descrito en el siguiente enlace inspiró esta respuesta. Me gustó ya que parecía más simple. ¡Pero cuidado! ¡Hay dragones! https://help.github.com/articles/importing-an-external-git-repository git push --mirror url:repo_dest empuja su historial y estado de repositorio local a remoto (url: repo_dest). PERO elimina la antigua historia y el estado del control remoto. La diversión se produce! :-MI

gaoithe
fuente
1

En mi caso, solo quería importar algunos archivos del otro repositorio (XXX). El subárbol era demasiado complicado para mí y las otras soluciones no funcionaron. Esto es lo que hice:

ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')

Esto le proporciona una lista separada por espacios de todas las confirmaciones que afectan a los archivos que quería importar (ZZZ) en orden inverso (es posible que deba agregar también seguir para capturar los nombres). Luego entré en el repositorio de destino (AAAA), agregué el otro repositorio (XXX) como remoto, hice una búsqueda desde él y finalmente:

git cherry-pick $ALL_COMMITS

que agrega todos los commits a su rama, tendrá todos los archivos con su historial y podrá hacer lo que quiera con ellos como si siempre hubieran estado en este repositorio.

Sebastian Blask
fuente
1

Vea el ejemplo básico en este artículo y considere dicha asignación en repositorios:

  • A<-> YYY,
  • B <-> XXX

Después de toda la actividad descrita en este capítulo (después de la fusión), elimine la rama B-master:

$ git branch -d B-master

Luego, empuje los cambios.

Esto funciona para mi.

VeLKerr
fuente
0

Estaba en una situación en la que estaba buscando -s theirspero, por supuesto, esta estrategia no existe. Mi historia fue que había bifurcado un proyecto en GitHub, y ahora por alguna razón, mi local masterno podía fusionarse, upstream/masteraunque no había realizado cambios locales en esta rama. (Realmente no sé qué pasó allí, supongo que aguas arriba había hecho algunos empujones sucios detrás de escena, ¿tal vez?)

Lo que terminé haciendo fue

# as per https://help.github.com/articles/syncing-a-fork/
git fetch upstream
git checkout master
git merge upstream/master
....
# Lots of conflicts, ended up just abandonging this approach
git reset --hard   # Ditch failed merge
git checkout upstream/master
# Now in detached state
git branch -d master # !
git checkout -b master   # create new master from upstream/master

Entonces ahora my masterestá nuevamente sincronizado con upstream/master(y podría repetir lo anterior para cualquier otra rama que también desee sincronizar de manera similar).

tripleee
fuente
1
A git reset --hard upstream/masteren su mastersucursal local haría el trabajo. De esta manera, no pierde la confluencia de sucursales locales, como el flujo ascendente predeterminado.
tomekwi
0

Puedo sugerir otra solución (alternativa a los submódulos git ) para su problema - herramienta gil (enlaces git)

Permite describir y administrar dependencias complejas de repositorios git.

También proporciona una solución al problema de dependencia de submódulos recursivos git .

Considere que tiene las siguientes dependencias del proyecto: ejemplo de gráfico de dependencia del repositorio git

Luego puede definir el .gitlinksarchivo con la descripción de la relación de repositorios:

# Projects
CppBenchmark CppBenchmark https://github.com/chronoxor/CppBenchmark.git master
CppCommon CppCommon https://github.com/chronoxor/CppCommon.git master
CppLogging CppLogging https://github.com/chronoxor/CppLogging.git master

# Modules
Catch2 modules/Catch2 https://github.com/catchorg/Catch2.git master
cpp-optparse modules/cpp-optparse https://github.com/weisslj/cpp-optparse.git master
fmt modules/fmt https://github.com/fmtlib/fmt.git master
HdrHistogram modules/HdrHistogram https://github.com/HdrHistogram/HdrHistogram_c.git master
zlib modules/zlib https://github.com/madler/zlib.git master

# Scripts
build scripts/build https://github.com/chronoxor/CppBuildScripts.git master
cmake scripts/cmake https://github.com/chronoxor/CppCMakeScripts.git master

Cada línea describe el enlace git en el siguiente formato:

  1. Nombre único del repositorio
  2. Ruta relativa del repositorio (iniciada desde la ruta del archivo .gitlinks)
  3. Repositorio de Git que se utilizará en el comando git clone Repositorio de ramificación para pagar
  4. La línea vacía o la línea que comienza con # no se analiza (se trata como un comentario).

Finalmente, debe actualizar su repositorio de muestras raíz:

# Clone and link all git links dependencies from .gitlinks file
gil clone
gil link

# The same result with a single command
gil update

Como resultado, clonará todos los proyectos necesarios y los vinculará entre sí de la manera adecuada.

Si desea confirmar todos los cambios en algún repositorio con todos los cambios en los repositorios vinculados secundarios, puede hacerlo con un solo comando:

gil commit -a -m "Some big update"

Los comandos Pull, Push funcionan de manera similar:

gil pull
gil push

La herramienta Gil (enlaces git) admite los siguientes comandos:

usage: gil command arguments
Supported commands:
    help - show this help
    context - command will show the current git link context of the current directory
    clone - clone all repositories that are missed in the current context
    link - link all repositories that are missed in the current context
    update - clone and link in a single operation
    pull - pull all repositories in the current directory
    push - push all repositories in the current directory
    commit - commit all repositories in the current directory

Más información sobre el problema de dependencia de submódulos recursivos de git .

cronoxor
fuente
0

Permítanme usar nombres a(en lugar de XXXy ZZZ) y b(en lugar deYYY ), ya que eso hace que la descripción sea un poco más fácil de leer.

Digamos que quiere fusionar repositorio aen b(estoy asumiendo que están situados uno junto al otro):

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Para esto necesita git-filter-repoinstalar ( filter-branchse desaconseja ).

Un ejemplo de fusión de 2 grandes repositorios, colocando uno de ellos en un subdirectorio: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Más sobre esto aquí .

x-yuri
fuente
-1

No sé de una manera fácil de hacer eso. PODRÍAS hacer esto:

  1. Use git filter-branch para agregar un super-directorio ZZZ en el repositorio XXX
  2. Empuje la nueva rama al repositorio AAA
  3. Combina la rama empujada con el tronco de YYY.

Puedo editar con detalles si eso suena atractivo.

Walter Mundt
fuente
-2

Creo que puedes hacer esto usando 'git mv' y 'git pull'.

Soy un git noob justo, así que tenga cuidado con su repositorio principal, pero acabo de probar esto en un directorio temporal y parece funcionar.

Primero: cambie el nombre de la estructura de XXX para que coincida con cómo desea que se vea cuando esté dentro de YYY:

cd XXX
mkdir tmp
git mv ZZZ tmp/ZZZ
git mv tmp ZZZ

Ahora XXX se ve así:

XXX
 |- ZZZ
     |- ZZZ

Ahora use 'git pull' para buscar los cambios en:

cd ../YYY
git pull ../XXX

Ahora YYY se ve así:

YYY
 |- ZZZ
     |- ZZZ
 |- (other folders that already were in YYY)
Aaron
fuente