repositorio - git pull
¿Cómo fusionar dos repositorios Git? (21)
Aquí hay dos soluciones posibles:
Submódulos
Copie el repositorio A en un directorio separado en el proyecto más grande B, o (quizás mejor) clone el repositorio A en un subdirectorio en el proyecto B. Luego use el submódulo git para hacer de este repositorio un submódulo de un repositorio B.
Esta es una buena solución para repositorios de acoplamiento flexible, donde el desarrollo en el repositorio A continúa, y la mayor parte del desarrollo es un desarrollo independiente separado en A. Consulte también las páginas de SubmoduleSupport y GitSubmoduleTutorial en Git Wiki.
Fusión de subárbol
Puede fusionar el repositorio A en un subdirectorio de un proyecto B utilizando la estrategia de fusión de subárbol . 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 --allow-unrelated-histories
opción --allow-unrelated-histories
para Git> = 2.9.0.)
O puede usar la herramienta de subárbol git ( repositorio en GitHub ) de apenwarr (Avery Pennarun), anunciado por ejemplo en su blog. Una nueva alternativa a los submódulos de Git: subárbol de git .
Creo que en su caso (A es ser parte de un proyecto más grande B) la solución correcta sería utilizar la fusión de subárbol .
Considere el siguiente escenario:
He desarrollado un pequeño proyecto experimental A en su propio repositorio de Git. Ahora ha madurado, y me gustaría que A formara parte del proyecto B más grande, que tiene su propio gran repositorio. Ahora me gustaría agregar A como un subdirectorio de B.
¿Cómo fusiono A en B, sin perder el historial de ningún lado?
El enfoque de submódulo es bueno si desea mantener el proyecto por separado. Sin embargo, si realmente desea combinar ambos proyectos en el mismo repositorio, tiene un poco más de trabajo por hacer.
Lo primero sería usar git filter-branch
para volver a escribir los nombres de todo lo que se encuentra en el segundo repositorio en el subdirectorio donde desea que terminen. Entonces, en lugar de foo.c
, bar.html
, tendrías projb/foo.c
y projb/bar.html
.
Entonces, deberías poder hacer algo como lo siguiente:
git remote add projb [wherever]
git pull projb
El git pull
hará una git fetch
seguida de una git merge
. No debe haber conflictos, si el repositorio al que está projb/
todavía no tiene un directorio projb/
.
La búsqueda adicional indica que se hizo algo similar para fusionar gitk
en git
. Junio C Hamano escribe sobre esto aquí: http://www.mail-archive.com/[email protected]/msg03395.html
En mi caso, tenía un repositorio my-plugin
y un repositorio de main-project
, y quería fingir que my-plugin
siempre se había desarrollado en el subdirectorio de plugins
de main-project
.
Básicamente, reescribí la historia del repositorio my-plugin
para que pareciera que todo el desarrollo tuvo lugar en el subdirectorio plugins/my-plugin
. Luego, agregué el historial de desarrollo de my-plugin
en el historial del main-project
y fusioné los dos árboles. Dado que no había un directorio de plugins/my-plugin
ya presente en el repositorio del main-project
, esta era una fusión trivial sin conflictos. El repositorio resultante contenía toda la historia de los dos proyectos originales y tenía 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
Primero, cree una copia del repositorio my-plugin
, ya que vamos a reescribir el historial de este repositorio.
Ahora, navegue a la raíz del repositorio de my-plugin
, revise su rama principal (probablemente la master
) y ejecute el siguiente comando. Por supuesto, deberías sustituir my-plugin
y plugins
cualesquiera que sean tus nombres reales.
$ 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 una explicación. git filter-branch --tree-filter (...) HEAD
ejecuta el comando (...)
en cada confirmación que sea accesible desde HEAD
. Tenga en cuenta que esto funciona directamente en los datos almacenados para cada confirmación, por lo que no debemos preocuparnos por las nociones de "directorio de trabajo", "índice", "puesta en escena", etc.
Si ejecuta un comando filter-branch
que falla, dejará algunos archivos en el directorio .git
y la próxima vez que intente filter-branch
se quejará de esto, a menos que suministre la opción -f
a filter-branch
.
En cuanto al comando real, no tuve mucha suerte de que bash
hiciera lo que quería, así que en lugar de eso uso zsh -c
para hacer que zsh
ejecute un comando. Primero configuro la opción extended_glob
, que es lo que habilita la sintaxis ^(...)
en el comando mv
, así como la opción glob_dots
, que me permite seleccionar archivos de puntos (como .gitignore
) con un glob ( ^(...)
).
A continuación, uso el comando mkdir -p
para crear plugins
y plugins/my-plugin
al mismo tiempo.
Finalmente, uso la función zsh
"negative glob" ^(.git|plugins)
.git
^(.git|plugins)
para hacer coincidir todos los archivos en el directorio raíz del repositorio, excepto .git
y la carpeta my-plugin
recién creada. (Excluir .git
podría no ser necesario aquí, pero intentar mover un directorio a sí mismo es un error).
En mi repositorio, la confirmación inicial no incluía ningún archivo, por lo que el comando mv
devolvió un error en la confirmación inicial (ya que no había nada disponible para mover). Por lo tanto, he añadido un || true
|| true
para que git filter-branch
no aborte.
La opción --all
le dice a filter-branch
que reescriba el historial de todas las sucursales en el repositorio, y el adicional --
es necesario que le diga a git
que lo interprete como parte de la lista de opciones para que las ramas se reescriban, en lugar de como una opción Para filter-branch
sí mismo.
Ahora, navegue hasta el repositorio de su main-project
y verifique la rama en la que desea fusionarse. Agregue su copia local del repositorio my-plugin
(con su historial modificado) como un control remoto del main-project
con:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Ahora tendrá dos árboles no relacionados en su historial de compromisos, que puede visualizar bien usando:
$ git log --color --graph --decorate --all
Para fusionarlos, utiliza:
$ git merge my-plugin/master --allow-unrelated-histories
Tenga en cuenta que en Git anterior a 2.9.0, la --allow-unrelated-histories
no existe. Si está utilizando una de estas versiones, simplemente omita la opción: el mensaje de error que --allow-unrelated-histories
también se agregó en 2.9.0.
Usted no debe tener ningún conflicto de fusión. Si lo hace, probablemente significa que el comando filter-branch
no funcionó correctamente o que ya existía un directorio plugins/my-plugin
en main-project
.
Asegúrese de ingresar un mensaje de confirmación explicativo para cualquier contribuyente futuro que se pregunte qué tipo de piratería estaba haciendo para crear un repositorio con dos raíces.
Puede visualizar el nuevo gráfico de confirmación, que debe tener dos confirmaciones de raíz, utilizando el comando de git log
anterior. Tenga en cuenta que solo se fusionará la rama master
. Esto significa que si tiene un trabajo importante en otras ramas de my-plugin
que desea combinar en el árbol del main-project
, debe abstenerse de eliminar el control remoto de my-plugin
hasta que haya realizado estas combinaciones. Si no lo hace, entonces las confirmaciones de esas sucursales seguirán estando en el repositorio del main-project
, pero algunas serán inalcanzables y susceptibles de una posible recolección de basura. (Además, tendrá que referirse a ellos por SHA, porque eliminar un control remoto elimina sus sucursales de seguimiento remoto).
Opcionalmente, después de haber fusionado todo lo que desea evitar de my-plugin
, puede quitar el control remoto de my-plugin
utilizando:
$ git remote remove my-plugin
Ahora puede eliminar de forma segura la copia del repositorio de my-plugin
cuyo historial ha cambiado. En mi caso, también agregué un aviso de desaprobación al repositorio de my-plugin
real después de que la fusión se completara y se publicara.
Probado en Mac OS X El Capitan con git --version 2.9.0
y zsh --version 5.2
. Su experiencia puede ser diferente.
Referencias:
- https://git-scm.com/docs/git-filter-branch
- https://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-including-hidden-from-one-directory-to-another
- http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
- La depuración del archivo de Git repo ha fallado, no se puede crear una nueva copia de seguridad
- git, filtro-rama en todas las ramas
He estado tratando de hacer lo mismo durante días, estoy usando git 2.7.2. El subárbol no conserva la historia.
Puede utilizar este método si no va a utilizar el proyecto anterior de nuevo.
Sugeriría que bifurcas B primero y trabajes en la rama.
Aquí están los pasos sin bifurcar:
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 alguno de los archivos en el subdir A, obtendrá el historial completo
git log --follow A/<file>
Este fue el post que me ayudó a hacer esto:
He reunido mucha información aquí en , etc., y he logrado armar un script que resuelve el problema para mí.
La advertencia es que solo toma en cuenta la rama de ''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 quieres.
El script incluso maneja ramas y etiquetas de características, al cambiarles el nombre 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 containing the URLs to the repositories
## which are to be merged on separate lines.
##
## Author: Robert von Burg
## [email protected]
##
## Version: 0.2.0
## Created: 2015-06-17
##
################################################################################
#
# Disallow using undefined variables
shopt -s -o nounset
# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
# Detect proper usage
if [ "$#" -ne "2" ] ; then
echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
exit 1
fi
# Script functions
function failed() {
echo -e "ERROR: Merging of projects failed:"
echo -e "$1"
exit 1
}
function commit_merge() {
current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
CHANGES=$(git status | grep "working directory clean")
MERGING=$(git status | grep "merging")
if [[ "$CHANGES" != "" ]] && [[ "$MERGING" == "" ]] ; then
echo -e "INFO: No commit required."
else
echo -e "INFO: Committing ${sub_project}..."
if ! git commit --quiet -m "[Project] Merged branch ''$1'' of ${sub_project}" ; then
failed "Failed to commit merge of branch ''$1'' of ${sub_project} into ${current_branch}"
fi
fi
}
## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"
# 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: Creating new git repository ${PROJECT_NAME}..."
echo -e "===================================================="
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 th branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "===================================================="
for url in $(cat ${REPO_URL_FILE}) ; do
# Extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"
echo -e "----------------------------------------------------"
# Fetch the project
echo -e "INFO: Fetching ${sub_project}..."
git remote add "${sub_project}" "${url}"
if ! git fetch --no-tags --quiet ${sub_project} 2>/dev/null ; then
failed "Failed to fetch project ${sub_project}"
fi
# Add remote branches
echo -e "INFO: Creating local branches for ${sub_project}..."
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}..."
# Create and checkout new merge branch off of master
git checkout --quiet -b "${sub_project}/${branch_name}" master
git reset --hard --quiet
git clean -d --force --quiet
# Merge the project
echo -e "INFO: Merging ${sub_project}..."
if ! git merge --quiet --no-commit "remotes/${sub_project}/${branch_name}" 2>/dev/null ; 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."
else
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
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" 2>/dev/null ; then
failed "sub_project ${sub_project} is missing master branch!"
fi
# Copy remote tags
echo -e "INFO: Copying tags for ${sub_project}..."
while read tag ; do
tag_ref=$(echo $tag | tr " " "/t" | cut -f 1)
tag_name=$(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%%^*}"
tag_new_name="${sub_project}/${tag_name}"
echo -e "INFO: Copying tag ${tag_name} to ${tag_new_name} for ref ${tag_ref}..."
if ! git tag "${tag_new_name}" "${tag_ref}" 2>/dev/null ; then
echo -e "WARN: Could not copy tag ${tag_name} to ${tag_new_name} for ref ${tag_ref}"
fi
done < <(git ls-remote --tags ${sub_project})
# Remove the remote to the old project
echo -e "INFO: Removing remote ${sub_project}..."
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 "===================================================="
for url in $(cat ${REPO_URL_FILE}) ; do
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."
if ! git merge --quiet --no-commit "${sub_project}/master" 2>/dev/null ; 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
exit 0
También puede obtenerlo en http://paste.ubuntu.com/11732805
Primero cree un archivo con la URL a cada repositorio, por ejemplo:
[email protected]:eitchnet/ch.eitchnet.parent.git
[email protected]:eitchnet/ch.eitchnet.utils.git
[email protected]:eitchnet/ch.eitchnet.privilege.git
Luego llame a la secuencia de comandos dando un nombre al proyecto y la ruta a la secuencia de comandos:
./mergeGitRepositories.sh eitchnet_test eitchnet.lst
El script en sí tiene muchos comentarios que deberían explicar lo que hace.
Sé que pasó mucho tiempo 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
Seguí perdiendo el historial al usar la combinación, por lo que terminé usando rebase ya que en mi caso los dos repositorios son lo suficientemente diferentes como para no fusionarse 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 todas las confirmaciones de projA seguido de las confirmaciones de projB
Si ambos repositorios tienen el mismo tipo de archivos (como dos repositorios de Rails para diferentes proyectos), puede obtener datos del repositorio secundario en su repositorio actual:
git fetch git://repository.url/repo.git master:branch_name
y luego fusionarlo al repositorio actual:
git merge --allow-unrelated-histories branch_name
Si su versión de Git es más pequeña que 2.9, elimine --allow-unrelated-histories
.
Después de esto, pueden ocurrir conflictos. Puedes resolverlos por ejemplo con git mergetool
. kdiff3
se puede usar únicamente con el teclado, por lo que se necesitan 5 archivos de conflicto cuando se lee el código en solo unos minutos.
Recuerda terminar la fusión:
git commit
Si desea colocar los archivos de una rama en el repositorio B en un subárbol del repositorio A y también conservar el historial, siga leyendo. (En el ejemplo a continuación, asumo que queremos que la rama maestra del repositorio B se fusione en la rama maestra del repositorio 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 rama nueva (con un solo compromiso) 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 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
para el comando de pago desprotege los archivos de la rama maestra de A pero no crea ningún compromiso. Podríamos haber seleccionado cualquier confirmación porque a continuación, de todos modos, eliminamos todos los archivos. Luego, sin cometer aún ( -n
), seleccionamos el primer compromiso de la rama maestra de B. (Cherry-pick conserva el mensaje de confirmación original que no parece realizar una verificación directa). Luego creamos el subárbol donde queremos colocar todos los archivos del repositorio B. Luego, debemos mover todos los archivos que se introdujeron en el cereza-pick al subárbol. En el ejemplo anterior, solo hay un archivo README
para mover. Luego, confirmamos nuestra confirmación de raíz de 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
sobre la new_b_root
recién creada. Llamamos a la nueva sucursal b
:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
Ahora, fusionamos nuestra rama b
en A/master
:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m ''Merge repo B into repo A.''
Finalmente, puedes eliminar las ramas B
remotas y temporales:
git remote remove B
git branch -D new_b_root b
El gráfico final tendrá una estructura como esta:
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 fusionar 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 --allow-unrelated-histories
solo existe desde git> = 2.9. Ver Git - git merge Documentation / --allow-unrelated-histories
Actualización : Se --tags
como lo sugiere @jstadler para mantener las etiquetas.
Si está intentando simplemente pegar dos repositorios juntos, los submódulos y las combinaciones de subárboles son la herramienta incorrecta para usar porque no conservan todo el historial de archivos (como las personas han notado en otras respuestas). Vea esta respuesta here para ver la manera simple y correcta de hacer esto.
Una sola rama de otro repositorio se puede colocar fácilmente en un subdirectorio que conserva su historial. Por ejemplo:
git subtree add --prefix=rails git://github.com/rails/rails.git master
Esto aparecerá como un solo compromiso donde todos los archivos de la rama maestra de Rails se agregarán al directorio "rails". Sin embargo, el título del compromiso contiene una referencia al árbol de historial antiguo:
Añadir ''rails /'' desde commit
<rev>
Donde <rev>
es un hash de confirmación SHA-1. Todavía se puede ver la historia, 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 se trata de una antigua rama real intacta. Debería tratar esto como un compromiso de movimiento de archivos 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
Hay soluciones más complejas como hacer esto manualmente o reescribir el historial como se describe en otras respuestas.
El comando git-subtree es una parte de git-contrib oficial, algunos administradores de paquetes lo instalan de forma predeterminada (OS X Homebrew). Pero es posible que tenga que instalarlo usted mismo además de git.
git-subtree
es bueno, pero probablemente no sea el que quieres.
Por ejemplo, si el projectA
es el directorio creado en B, después git subtree
,
git log projectA
listas solo una confirmación: la fusión. Las confirmaciones del proyecto fusionado son para diferentes rutas, por lo que no se muestran.
La respuesta de Greg Hewgill se acerca mucho, aunque en realidad no dice cómo reescribir los caminos.
La solución es sorprendentemente simple.
(1) En A,
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: Esto vuelve a escribir el historial, por lo que si pretende continuar usando este repositorio A, es posible que desee clonar (copiar) una copia desechable primero.
(2) Luego en B, corre
git pull path/to/A
Voila! Tiene un directorio projectA
en B. Si ejecuta git log projectA
, verá todas las confirmaciones de A.
En mi caso, quería dos subdirectorios, projectA
y projectB
. En ese caso, hice el paso (1) a B también.
Combino proyectos de forma ligeramente manual, lo que me permite evitar tener que lidiar con conflictos de combinación.
En primer lugar, copie en los archivos del otro proyecto como desee.
cp -R myotherproject newdirectory
git add newdirectory
siguiente tirón en la historia
git fetch path_or_url_to_other_repo
Dile a Git que se fusione en la historia de la última cosa buscada.
echo ''FETCH_HEAD'' > .git/MERGE_HEAD
ahora cometa como lo harías normalmente
git commit
El comando dado es la mejor solución posible que sugiero.
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
Fusionando 2 repos
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
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 haz todas las operaciones que necesites hacer y confícalas.
C) Luego de vuelta al maestro y una combinación clásica entre las dos ramas:
git checkout master
git merge projectA
Similar a @Smar pero usa 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
Entonces te fusionas manualmente.
(Adaptado del post por Anar Manafov )
Cuando desee fusionar tres o más proyectos en un solo compromiso, siga los pasos descritos en las otras respuestas ( remote add -f
, merge
). Luego, (suave) restablezca el índice a la cabecera anterior (donde no ocurrió la fusión). Agregue todos los archivos ( git add -A
) y confírmelos (mensaje "Combinando los proyectos A, B, C y D en un proyecto). Este es ahora el ID de compromiso del maestro.
Ahora, crea .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, solo agregue tanto head^n..head
como tenga ramas. Para actualizar etiquetas, agregue --tag-name-filter cat
. No siempre agregue eso, porque esto podría causar una reescritura de algunos confirmaciones. Para obtener más información, consulte la página de manual de filter-branch , busque "injertos".
Ahora, tu último compromiso tiene los padres correctos asociados.
Esta función clonará el repositorio remoto en el directorio de repositorio local, después de fusionar todas las confirmaciones se guardarán, git log
se 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 los archivos / dirs del 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"
Avisos Las
rutas se reemplazan a través sed
, así que asegúrese de que se haya movido en las rutas adecuadas después de la fusión.
El --allow-unrelated-histories
parámetro solo existe desde git> = 2.9.
Tuve un desafío similar, pero en mi caso, habíamos desarrollado una versión del código base en el repositorio A, y luego la clonamos en un nuevo repositorio, repo B, para la nueva versión del producto. Después de corregir algunos errores en el repositorio A, tuvimos que FI los cambios en el repositorio B. Terminé haciendo lo siguiente:
- Agregando un control remoto al repositorio B que apunta al repositorio A (agregar git remoto ...)
- Tirando de la rama actual (no estábamos usando el maestro para corregir errores) (git pull remoteForRepoA bugFixBranch)
- Empujando se fusiona con github
Trabajó un regalo :)