Pregunta

Considere el siguiente escenario:

He desarrollado un pequeño proyecto experimental Una en su propio repositorio Git.Ahora ha madurado, y me gustaría ser parte de un proyecto más amplio B, que tiene su propio repositorio grande.Me gustaría añadir Un como un subdirectorio de B.

¿Cómo puedo combinar Un en B, sin perder la historia en cualquier lado?

¿Fue útil?

Solución

Una rama única de otro repositorio se puede colocar fácilmente bajo un subdirectorio que conserva su historial. Por ejemplo:

git subtree add --prefix=rails git://github.com/rails/rails.git master

Esto aparecerá como una confirmación única donde todos los archivos de la rama maestra Rails se agregan a " rails " directorio. Sin embargo, el título del commit contiene una referencia al viejo árbol de historia:

  

Agregue 'rails /' desde commit <rev>

Donde <=> es un hash de confirmación SHA-1. Todavía puedes ver el historial, culpar a algunos cambios.

git log <rev>
git blame <rev> -- README.md

Tenga en cuenta que no puede ver el prefijo de directorio desde aquí, ya que esta es una rama antigua real que quedó intacta. Debe tratar esto como una confirmación de movimiento de archivo habitual: necesitará un salto adicional al alcanzarlo.

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

Existen soluciones más complejas como hacer esto manualmente o reescribir el historial como se describe en otras respuestas.

El comando git-subtree es parte de git-contrib oficial, algunos administradores de paquetes lo instalan por defecto (OS X Homebrew). Pero es posible que deba instalarlo usted mismo además de git.

Otros consejos

Si desea fusionar project-a en project-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

Tomado de: git merge diferentes repositorios?

Este método funcionó bastante bien para mí, es más corto y, en mi opinión, mucho más limpio.

Nota: el parámetro --allow-unrelated-histories solo existe desde git > = 2.9. Consulte Git - git merge Documentation / --allow- historias no relacionadas

Actualización : se agregó --tags según lo sugerido por @jstadler para mantener las etiquetas.

Aquí hay dos posibles soluciones:

Submódulos

Copie el repositorio A en un directorio separado en el proyecto B más grande, o (quizás mejor) clone el repositorio A en un subdirectorio en el proyecto B. Luego use git submodule para hacer de este repositorio un < strong> submódulo de un repositorio B.

Esta es una buena solución para repositorios poco acoplados, donde el desarrollo en el repositorio A continúa, y la mayor parte del desarrollo es un desarrollo independiente en A. Ver también SubmoduleSupport y GitSubmoduleTutorial páginas en Git Wiki.

Combinación de subárbol

Puede fusionar el repositorio A en un subdirectorio de un proyecto B utilizando la estrategia subtree merge . Esto se describe en Subtree Merging and You por Markus Prinz.

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

(Se necesita la opción --allow-unrelated-histories para Git > = 2.9.0.)

O puede usar la herramienta git subtree ( repositorio en GitHub ) por apenwarr (Avery Pennarun), anunciado por ejemplo en su publicación de blog Una nueva alternativa a los submódulos de Git: git subtree .


Creo que en su caso (A es parte de un proyecto más grande B) la solución correcta sería usar subtree merge .

El submódulo de aproximación es buena si se quiere mantener el proyecto por separado.Sin embargo, si usted realmente desea fusionar ambos proyectos en el mismo repositorio, entonces usted tiene un poco más de trabajo.

El primero sería el uso de git filter-branch volver a escribir los nombres de todo en el segundo repositorio de estar en el subdirectorio donde le gustaría tenerlos.Así que en lugar de foo.c, bar.html, usted tendría projb/foo.c y projb/bar.html.

Entonces, usted debería ser capaz de hacer algo como lo siguiente:

git remote add projb [wherever]
git pull projb

El git pull va a hacer un git fetch seguido por un git merge.No debe haber conflictos, si el repositorio está tirando para no dispone aún de un projb/ directorio.

Buscar más indica que algo similar se hizo para combinar gitk en git.Junio C Hamano escribe sobre ello aquí: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

git-subtree es bonito, pero probablemente no es el que usted desea.

Por ejemplo, si projectA es el directorio creado en el B, después de git subtree,

git log projectA

las listas de sólo uno commit:la combinación.La cometa de la fusión del proyecto son por caminos diferentes, por lo que no se muestran.

Greg Hewgill la respuesta que más se acerca, aunque no dicen cómo reescribir los caminos.


La solución es sorprendentemente simple.

(1) En Una,

PREFIX=projectA #adjust this

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

Nota:Este reescribe la historia, así que si usted tiene la intención de continuar utilizando este repo, usted quizás desee clonar (copiar) una de usar y tirar copia de la primera.

(2) Luego en B, ejecutar

git pull path/to/A

Voila!Usted tiene un projectA directorio en B.Si ejecuta git log projectA, verá todas las confirmaciones de A.


En mi caso, yo quería dos subdirectorios, projectA y projectB.En ese caso, yo hice el paso (1) a B así.

Si ambos repositorios tienen el mismo tipo de archivos (como dos repositorios Rails para diferentes proyectos), puede buscar datos del repositorio secundario en su repositorio actual:

git fetch git://repository.url/repo.git master:branch_name

y luego fusionarlo con el repositorio actual:

git merge --allow-unrelated-histories branch_name

Si su versión de Git es menor que 2.9, elimine --allow-unrelated-histories.

Después de esto, pueden ocurrir conflictos. Puede resolverlos, por ejemplo, con git mergetool. kdiff3 se puede usar únicamente con el teclado, por lo que se necesitan 5 archivos de conflicto al leer el código solo unos minutos.

Recuerde finalizar la fusión:

git commit

Seguí perdiendo el historial al usar la combinación, así que terminé usando rebase ya que en mi caso los dos repositorios son lo suficientemente diferentes como para no terminar fusionándose en cada confirmación:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

= > resolver conflictos, luego continuar, tantas veces como sea necesario ...

git rebase --continue

Hacer esto lleva a un proyecto que tiene todos los commits de projA seguidos de commits de projB

En mi caso, tuve un my-plugin repositorio y un main-project repositorio, y quise fingir que my-plugin siempre había sido desarrollado en el plugins subdirectorio de main-project.

Básicamente, he reescrito la historia de la my-plugin repositorio, de modo que parecía que todo el desarrollo se produjo en la plugins/my-plugin subdirectorio.Entonces, he añadido el desarrollo de la historia de my-plugin en el main-project la historia, y la fusión de los dos árboles juntos.Ya que no había plugins/my-plugin directorio, ya presentes en el main-project repositorio, este fue un trivial no-conflictos de mezcla.El resultado repositorio de contenidos de toda la historia de ambos proyectos originales, y tiene dos raíces.

TL;DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

Versión larga

En primer lugar, cree una copia de la my-plugin repositorio, porque vamos a ser la reescritura de la historia de este repositorio.

Ahora, vaya a la raíz de la my-plugin repositorio, echa un vistazo a su rama principal (probablemente master), y ejecutar el siguiente comando.Por supuesto, se debe sustituir my-plugin y plugins sea cual sea sus nombres reales son.

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all

Ahora para obtener una explicación. git filter-branch --tree-filter (...) HEAD ejecuta el (...) comando en cada confirmación de que es accesible desde HEAD.Tenga en cuenta que este opera directamente sobre los datos almacenados para cada commit, por lo que no tiene que preocuparse acerca de las nociones de "directorio de trabajo", "índice", "puesta en escena", y así sucesivamente.

Si ejecuta un filter-branch comando que no, va a salir de detrás de algunos de los archivos en el .git directorio y la próxima vez que intente filter-branch se quejan de esto, a menos que el suministro de la -f opción a filter-branch.

Como para el comando real, yo no tenía mucha suerte de llegar a bash para hacer lo que yo quería, así que en lugar de usar yo zsh -c para hacer zsh ejecutar un comando.Primero me puse la extended_glob opción, que es lo que permite que el ^(...) la sintaxis en la mv comando, así como el glob_dots la opción que me permite seleccionar dotfiles (tales como .gitignore) con un pegote (^(...)).

A continuación, utilizar la mkdir -p comando para crear tanto plugins y plugins/my-plugin al mismo tiempo.

Por último, yo uso el zsh "negativo glob" característica ^(.git|plugins) para que coincida con todos los archivos en el directorio raíz del repositorio, excepto para .git y el recién creado my-plugin carpeta.(Excluyendo .git podría no ser necesario aquí, pero tratando de mover un directorio en sí mismo es un error.)

En mi repositorio, la inicial de cometer no incluyen los archivos, por lo que el mv comando devuelve un error en la inicial commit (ya que no hay nada disponible para mover).Por lo tanto, he añadido un || true así que git filter-branch no abortar.

El --all opción indica filter-branch reescribir la historia para todos sucursales en el repositorio, y el extra -- es necesario decir git para interpretarlo como una parte de la lista de opciones para las sucursales de reescritura, en lugar de como una opción para filter-branch sí.

Ahora, vaya a su main-project depósito y retirar cualquier rama que desea combinar en.Agregar la copia local de la my-plugin repositorio (con su historia modificado) como mando a distancia de main-project con:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Ahora tendrá dos independientes árboles en su confirmación de la historia, que se puede visualizar bien el uso de:

$ git log --color --graph --decorate --all

Para combinarlos, utilice:

$ git merge my-plugin/master --allow-unrelated-histories

Tenga en cuenta que en la pre-2.9.0 Git, el --allow-unrelated-histories opción no existe.Si usted está utilizando una de estas versiones, solo omite la opción:el mensaje de error que --allow-unrelated-histories evita que se también añadido en 2.9.0.

Usted no debe tener cualquier combinación de los conflictos.Si lo hace, probablemente significa que el filter-branch el comando no funciona correctamente o no ya era un plugins/my-plugin directorio en main-project.

Asegúrese de ingresar una exposición del mensaje de confirmación para los futuros contribuyentes preguntándose qué trucos iba a hacer un repositorio con dos raíces.

Se puede visualizar el nuevo commit gráfico, que debe tener dos de la raíz se compromete, mediante la anterior git log comando.Tenga en cuenta que sólo el master rama se fusionarán.Esto significa que si usted tiene un trabajo importante en otros my-plugin las ramas que desea combinar en la main-project árbol, usted debería abstenerse de suprimir la my-plugin control remoto hasta que han hecho estas combinaciones.Si no, entonces el cometa de esas ramas todavía estará en el main-project repositorio, pero algunos serán accesibles y susceptibles a la eventual recolección de basura.(También, usted tendrá que hacer referencia a ellos por SHA, porque la eliminación de un remoto quita su remota de seguimiento de las ramas.)

Opcionalmente, después de haber fusionado todo lo que usted desea guardar my-plugin, usted puede quitar la my-plugin remoto mediante:

$ git remote remove my-plugin

Ahora usted puede eliminar de forma segura la copia de la my-plugin repositorio cuya historia cambió.En mi caso, también he añadido una desatención aviso a la real my-plugin repositorio después de la combinación fue completa y la empujó.


Probado en Mac OS X con El Capitan git --version 2.9.0 y zsh --version 5.2.Su kilometraje puede variar.

Referencias:

He estado tratando de hacer lo mismo durante días, estoy usando git 2.7.2. Subtree no conserva la historia.

Puede usar este método si no volverá a usar el proyecto anterior.

Sugeriría que ramifique B primero y trabaje en la rama.

Estos son los pasos sin ramificación:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Si ahora registra cualquiera de los archivos en el subdirectorio A, obtendrá el historial completo

git log --follow A/<file>

Esta fue la publicación que me ayudó a hacer esto:

http: // saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

Si desea colocar los archivos de una rama en el repositorio B en un subtree del repositorio A y también preservar la historia, seguir leyendo. (En el siguiente ejemplo, supongo que queremos fusionar la rama maestra del repo B en la rama maestra del repo A).

En el repositorio A, primero haga lo siguiente para que el repositorio B esté disponible:

git remote add B ../B # Add repo B as a new remote.
git fetch B

Ahora creamos una nueva sucursal (con solo una confirmación) en el repositorio A que llamamos new_b_root. La confirmación resultante tendrá los archivos que se confirmaron en la primera confirmación de la rama maestra del repositorio B pero que se colocaron en un subdirectorio llamado path/to/b-files/.

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

Explicación: La opción --orphan del comando de pago extrae los archivos de la rama maestra de A pero no crea ninguna confirmación. Podríamos haber seleccionado cualquier confirmación porque de todos modos borramos todos los archivos. Luego, sin comprometernos aún (-n), seleccionamos la primera confirmación de la rama maestra de B. (La selección de cereza conserva el mensaje de confirmación original que no parece hacer un pago directo). Luego creamos el subárbol donde queremos colocar todos los archivos del repositorio B. Luego tenemos que mover todos los archivos que se introdujeron en el selección de cereza al subárbol. En el ejemplo anterior, solo hay un archivo README para mover. Luego confirmamos nuestra confirmación de raíz B-repo y, al mismo tiempo, también conservamos la marca de tiempo de la confirmación original.

Ahora, crearemos una nueva rama B/master encima de la recién creada b. Llamamos a la nueva sucursal A/master:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

Ahora, fusionamos nuestra B rama en <=>:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

Finalmente, puede eliminar las <=> ramas remotas y temporales:

git remote remove B
git branch -D new_b_root b

El gráfico final tendrá una estructura como esta:

 ingrese la descripción de la imagen aquí

Sé que es mucho después del hecho, pero no estaba contento con las otras respuestas que encontré aquí, así que escribí esto:

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done

He reunido mucha información aquí sobre Stack & nbsp; OverFlow, etc., y he logrado armar un script que me resuelve el problema.

La advertencia es que solo tiene en cuenta la rama 'desarrollo' de cada repositorio y la fusiona en un directorio separado en un repositorio completamente nuevo.

Las etiquetas y otras ramas se ignoran; esto podría no ser lo que desea.

El script incluso maneja ramas y etiquetas de características, renombrándolas en el nuevo proyecto para que sepa de dónde provienen.

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi


# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi


# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo


# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})


  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done


# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

También puede obtenerlo de http://paste.ubuntu.com/11732805

Primero cree un archivo con la URL de cada repositorio, por ejemplo:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

Luego llame al script dando un nombre del proyecto y la ruta al script:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

El script en sí tiene muchos comentarios que deberían explicar lo que hace.

Si está intentando simplemente pegar dos repositorios juntos, los submódulos y las fusiones de subárbol son la herramienta incorrecta para usar porque no conservan todo el historial de archivos (como la gente ha notado en otras respuestas). Vea esta respuesta aquí para conocer la forma simple y correcta de hacerlo.

Similar a @Smar pero utiliza rutas del sistema de archivos, establecidas en PRIMARIO y SECUNDARIO:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

Luego se fusiona manualmente.

(adaptado de publicación de Anar Manafov )

Tuve un desafío similar, pero en mi caso, habíamos desarrollado una versión de la base de código en el repositorio A, luego la cloné en un nuevo repositorio, repositorio B, para la nueva versión del producto. Después de corregir algunos errores en el repositorio A, necesitábamos FI los cambios en el repositorio B. Terminamos haciendo lo siguiente:

  1. Agregar un control remoto al repositorio B que apuntaba al repositorio A (git remote add ...)
  2. Extrayendo la rama actual (no estábamos usando el maestro para la corrección de errores) (git pull remoteForRepoA bugFixBranch)
  3. Empujar fusiones a github

Trabajó un regalo :)

Cuando desee fusionar tres o más proyectos en un single commit, siga los pasos que se describen en las otras respuestas (remote add -f, merge). Luego, (suave) restablece el índice a la cabeza anterior (donde no ocurrió fusión). Agregue todos los archivos (git add -A) y confírmelos (mensaje & Quot; Fusionar proyectos A, B, C y D en un proyecto). Este es ahora el commit-id de master.

Ahora, cree .git/info/grafts con el siguiente contenido:

<commit-id of master> <list of commit ids of all parents>

Ejecutar git filter-branch -- head^..head head^2..head head^3..head. Si tiene más de tres ramas, simplemente agregue tantas head^n..head como tenga ramas. Para actualizar las etiquetas, agregue --tag-name-filter cat. No siempre agregue eso, porque esto podría causar una reescritura de algunas confirmaciones. Para obtener más información, consulte página de manual de filter-branch , busque " injertos " ;.

Ahora, su último compromiso tiene los padres correctos asociados.

Para fusionar una A dentro de B:

1) En el proyecto A

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

2) En el proyecto B

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

En esta rama, haga todas las operaciones 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 projectA

Fusionando 2 repositorios

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master

Esta función clonará el repositorio remoto en el directorio de repositorio local, después de combinar todas las confirmaciones se guardará, git log mostrará 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 usar:

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 repositorios combinados 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"

Avisos
Las rutas reemplazan a través de sed, así que asegúrese de que se movió en las rutas adecuadas después de la fusión.
El parámetro --allow-unrelated-histories solo existe desde git & Gt; = 2.9.

Fusiono proyectos ligeramente de forma manual, lo que me permite evitar tener que lidiar con conflictos de fusión.

primero, copie los archivos del otro proyecto como quiera.

cp -R myotherproject newdirectory
git add newdirectory

siguiente extracción en el historial

git fetch path_or_url_to_other_repo

dígale a git que se fusione en la historia de la última cosa recuperada

echo 'FETCH_HEAD' > .git/MERGE_HEAD

ahora confirme como normalmente lo haría

git commit

El comando dado es la mejor solución posible, sugiero.

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

Quería mover un proyecto pequeño a un subdirectorio de uno más grande. Como mi pequeño proyecto no tenía muchas confirmaciones, usé git format-patch --output-directory /path/to/patch-dir. Luego, en el proyecto más grande, usé git am --directory=dir/in/project /path/to/patch-dir/*.

Esto se siente manera menos aterrador y mucho más limpio que una rama de filtro. Por supuesto, puede no ser aplicable a todos los casos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top