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

484

Nuestros repositorios Git comenzaron como partes de un único repositorio SVN monstruoso donde los proyectos individuales tenían cada uno su propio árbol de la siguiente manera:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Obviamente, era bastante fácil mover archivos de uno a otro svn mv. Pero en Git, cada proyecto está en su propio repositorio, y hoy me pidieron que moviera un subdirectorio de project2a 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. ¿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 ).

ebneter
fuente
1
Eso suena como un enfoque razonable para mí; No se me ocurre ninguna forma obvia de mejorar significativamente su método. Es bueno que Git realmente lo haga fácil (no me gustaría intentar mover un directorio de archivos entre diferentes repositorios en Subversion, por ejemplo).
Greg Hewgill
1
@ebneter: he hecho esto (moví el historial de un repositorio svn a otro) manualmente, usando scripts de shell. Básicamente, reproduje el historial (diffs, commit logs messages) de archivos / directorios particulares en un segundo repositorio.
Adam Monsen
1
Me pregunto por qué no haces en git fetch p2 && git merge p2lugar de git fetch p2 && git branch .. && git merge p2? Editar: bien, parece que desea obtener los cambios en una nueva rama llamada p2, no en la rama actual.
Lekensteyn
1
¿No hay forma de evitar que --filter-branch destruya la estructura del directorio? Ese paso "git mv" da como resultado una confirmación masiva llena de eliminaciones de archivos y creaciones de archivos.
Edward Falk
1
Tenga en cuenta que, a partir de git 2.9, la fusión de historias no relacionadas no está permitida de forma predeterminada. Para que funcione, agregue --allow-unrelated-historiesal último git mergepara que funcione.
Scott Berrevoets

Respuestas:

55

Sí, golpear el --subdirectory-filterde filter-branchera la 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 hash. Dado que ninguno de los comandos estándar (p pull. Ej. ) Reescribe el historial, no hay forma de que pueda usarlos para lograr esto.

Podría refinar los detalles, por supuesto, parte de su 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 no es facilitar la reescritura de la historia.

Cascabel
fuente
1
¿Qué pasa si su archivo se ha movido a través de varios directorios y ahora reside en uno? ¿Funcionará el filtro de subdirectorio? (es decir, supongo que si solo quiero mover un archivo, ¿puedo moverlo a su propio subdirectorio y esto funcionará?)
rogerdpack
1
@rogerdpack: No, esto no seguirá el archivo a través de cambios de nombre. Creo que parecerá haber sido creado en el punto en que se movió al subdirectorio seleccionado. Si desea seleccionar solo un archivo, eche un vistazo a --index-filterla página de filter-branchmanual.
Cascabel
8
¿Hay alguna receta sobre cómo puedo seguir los cambios de nombre?
Night Warrier
Creo que mantener y curar la historia es uno de los puntos principales de git.
artburkart
288

Si su historial es correcto, puede tomar los commits como parche y aplicarlos 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 --committer-date-is-author-date < ../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 --committer-date-is-author-date)

(Tomado de los documentos de Exherbo )

Smar
fuente
21
Para los tres o 4 archivos que necesitaba mover, esta era una solución mucho más simple que la respuesta aceptada. Terminé recortando las rutas en el archivo de parche con find-replace para que se ajuste a la estructura de directorios de mi nuevo repositorio.
Rian Sanderson
8
He añadido opciones para que los archivos binarios (como imágenes) también se migran correctamente: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Funciona sin problemas AFAICT.
Emmanuel Touzery
35
En el paso de aplicar, utilicé la --committer-date-is-author-dateopción para preservar la fecha de confirmación original en lugar de la fecha en que se movieron los archivos.
darrenmc
66
merge commits en el historial rompe el comando "am". Puede agregar "-m --first-parent" al comando git log anterior, luego funcionó para mí.
Gábor Lipták
66
@Daniel Golden Me las arreglé para solucionar el problema con los archivos que se han movido (que es consecuencia de un error git log, por lo que no funciona con ambos --followy --reversecorrectamente). Utilicé esta respuesta , y aquí hay un script completo que uso ahora para mover archivos
tsayen
75

Después de probar varios enfoques para mover un archivo o carpeta de un repositorio de Git a otro, a continuación se describe el único que parece funcionar de manera confiable.

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 en este repositorio de destino.

La etapa uno

  1. ¡Haga una copia del repositorio A ya que los siguientes pasos hacen cambios importantes a 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)
    
  2. cd en ella

    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. 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
    
  5. Solo para mover un solo archivo: revise lo que queda y elimine todo excepto el archivo deseado. (Es posible que deba eliminar archivos que no desea 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
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from 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 en el repositorio B dentro de un directorio, no la raíz:

  1. Haz 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 fusionar estos archivos en el nuevo repositorio

    git commit
    

Etapa tres

  1. Haga una copia del repositorio B si aún no tiene uno

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (suponiendo que FOLDER_TO_KEEP es el nombre del nuevo repositorio al que está copiando)

  2. cd en ella

    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 can be anything - it's just an arbitrary name)
    
    # 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
    

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

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

    git remote rm repo-A-branch
    
  6. Empuja y estás listo.

    git push
    
mcarans
fuente
He seguido la mayoría de los pasos descritos aquí, sin embargo, parece que solo copia el historial de confirmación del archivo o directorio del maestro (y no de ninguna otra rama). ¿Está bien?
Bao-Long Nguyen-Trong
Creo que es correcto y que tendrías que seguir pasos similares para cualquier rama desde la que quieras mover archivos o carpetas, es decir. cambiar a la rama ej. MyBranch en el repositorio A, rama de filtro, etc. Entonces "git pull repo-A-branch MyBranch" en el repositorio B.
mcarans
Gracias por la respuesta. ¿Sabes si las etiquetas en las ramas también se migrarán?
Bao-Long Nguyen-Trong
Me temo que no lo sé, pero supongo que lo serían.
mcarans
1
@mcarans Desafortunadamente, esta NO es una forma confiable, aunque parece serlo. Sufre el mismo problema que todas las demás soluciones: no retiene el historial del cambio de nombre. En mi caso, la primera confirmación es cuando cambié el nombre del directorio / archivo. Todo más allá de eso está perdido.
xZero
20

He encontrado esto 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

En Windows recibí un error de argumento no válido. Así que tuve que aplicar todos los parches uno tras otro.

anhoppe
fuente
No funcionó para mí, ya que en algún momento faltaban los sombreados. Esto me ayudó: stackoverflow.com/questions/17371150/…
dr0i
A diferencia del enfoque "git log", ¡esta opción funcionó perfectamente para mí! ¡Gracias!
AlejandroVD
1
Intenté diferentes enfoques para mover proyectos a un nuevo repositorio. Este es el único que funcionó para mí. No puedo creer que una tarea tan común deba ser tan complicada.
Chris_D_Turk
Gracias por compartir el blog de Ross Hendrickson . Este enfoque funcionó para mí.
Kaushik Acharya
1
Esta es una solución muy elegante, sin embargo, una vez más, sufre el mismo problema que todas las demás soluciones: NO conservará el historial pasado el cambio de nombre.
xZero
6

MANTENER EL NOMBRE DEL DIRECTORIO

El subdirectorio-filtro (o el comando más corto git subtree) funciona bien pero no funcionó para mí ya que eliminan el nombre del directorio de la información de confirmación. En mi caso, solo quiero fusionar 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 al repositorio de destino en 5 simples pasos.

# 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
Joachim Nilsson
fuente
Este script no hará modificaciones a su repositorio original. Si el repositorio de destino especificado en el archivo de mapa no existe, entonces este script intentará crearlo.
Chetabahana
1
También creo que mantener los nombres de directorio intactos es tremendamente importante. De lo contrario, obtendrá confirmaciones de cambio de nombre adicionales en el repositorio de destino.
ipuustin
5

Esta respuesta proporciona comandos interesantes basados git amy presentados usando 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. Extraiga el historial en formato de correo electrónico utilizando
    git log --pretty=email -p --reverse --full-index --binary
  2. Reorganice el árbol de archivos y actualice el cambio de nombre de archivo en el historial [opcional]
  3. Aplicar nuevo historial usando git am

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

Ejemplo: historia Extracto de file3, file4yfile5

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

Limpie el destino del directorio temporal

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

Limpia tu fuente de repositorio

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

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

Lamentablemente opción --followo --find-copies-harderno se puede combinar con --reverse. Es por eso que 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. Reorganice el árbol de archivos y actualice el cambio de nombre de archivo en el historial [opcional]

Suponga 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 lo tanto, reorganice sus 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 historial temporal es ahora:

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

Cambie también los nombres de archivo dentro del historial:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

Nota: Esto reescribe 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 nuevo historial

Su otro repositorio es:

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

Aplicar confirmaciones de archivos de historial temporales:

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

Su otro repositorio es ahora:

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

Use git statuspara 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, en comparación con la ubicación / nombre dentro del repositorio anterior)

  • No es necesario git mvcambiar la ubicación / nombre de archivo.
  • No es necesario git log --followacceder al historial completo.

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

Para enumerar los archivos que han cambiado de nombre:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Más personalizaciones: puede completar el comando git logcon las opciones --find-copies-hardero --reverse. También puede eliminar las dos primeras columnas utilizando cut -f3-y agrupando el patrón completo '{. * =>. *}'.

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

Después de haber tenido un picor similar desde cero (aunque solo para algunos archivos de un repositorio dado), este script 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:

ViToni
fuente
2

Prueba esto

cd repo1

Esto eliminará todos los directorios excepto los mencionados, conservando el historial solo para estos directorios

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

Ahora puede agregar su nuevo repositorio en su control remoto git y empujarlo a eso

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

agregar -fpara sobrescribir

Chetan Basutkar
fuente
ADVERTENCIA: git-filter-branch tiene un exceso de problemas que generan reescrituras de historial destrozadas. Presione Ctrl-C antes de proceder a abortar, luego use una herramienta de filtrado alternativa como 'git filter-repo' ( github.com/newren/git-filter-repo ) en su lugar. Consulte la página de manual de rama de filtro para más detalles; para silenciar esta advertencia, establezca FILTER_BRANCH_SQUELCH_WARNING = 1.
Colin
1

Usando 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 migrate-from-project2rama antes de fusionarlos.

crimbo
fuente
1

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

Es capaz de mover una carpeta arbitraria /path/to/foode repo1dentro /some/other/folder/bara 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 repositorios de gran fuente, si solo extrae una subcarpeta profundamente anidada que no se tocó en cada cometer.

Como lo que esto hace es crear una rama huérfana con toda la historia del repositorio anterior y luego fusionarla con el HEAD, incluso funcionará en caso de conflictos de nombres de archivo (entonces tendría que resolver una fusión al final del curso) .

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

La desventaja es que probablemente no seguirá los cambios de nombre de los archivos (fuera de la REWRITE_FROMcarpeta) en las solicitudes de repositorio de origen bienvenidas en GitHub para acomodar 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
jakub.g
fuente
0

En mi caso, no necesitaba preservar el repositorio del que estaba migrando ni preservar 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 repositorio apareciera en el mismo repositorio.

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

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

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

Jason D
fuente
0

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 usarlo git cherry-pick.

El primer paso es llevar los commits del otro repositorio a su propio repositorio local usando git fetch <remote-url>. Esto dejará de FETCH_HEADapuntar a la confirmación del jefe del otro repositorio; si desea conservar una referencia a esa confirmación después de haber realizado otras recuperaciones, puede etiquetarla git tag other-head FETCH_HEAD.

Luego, 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 ser parcheado con la primera confirmación del otro repositorio que desea incorporar. Puede ser capaz de hacer esto con un archivo introducido git cherry-pick <commit-0>si commit-0lo desea, o puede que necesite construir el commit 'a mano'. Agregue -na las opciones de selección de cereza si necesita modificar la confirmación inicial para, por ejemplo, descartar archivos de esa confirmación que no desea incorporar.

Después de eso, puede continuar con git cherry-picklas confirmaciones posteriores, nuevamente utilizando -ncuando sea necesario. En el caso más simple (todos los envíos son exactamente lo que quiere y aplicar limpiamente) se puede dar la lista completa de confirmaciones en la línea de comandos de cereza-Pick: git cherry-pick <commit-1> <commit-2> <commit-3> ....

cjs
fuente
0

Esto se vuelve más simple usando git-filter-repo.

Para pasar project2/sub/dira project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

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
Tapuzi
fuente
-2

El siguiente método para migrar mi GIT Stash a GitLab manteniendo todas las ramas y preservando el historial.

Clonar el antiguo repositorio a local.

git clone --bare <STASH-URL>

Crea un repositorio vacío en GitLab.

git push --mirror <GitLab-URL>

Lo anterior lo realicé cuando migramos nuestro código de alijo a GitLab y funcionó muy bien.

Roopkumar Akubathini
fuente