Come unire due repository Git?
-
07-07-2019 - |
Domanda
Considera il seguente scenario:
Ho sviluppato un piccolo progetto sperimentale A nel proprio repository Git.Ora è maturato e mi piacerebbe che A facesse parte di un progetto B più ampio, che ha il suo grande repository.Ora vorrei aggiungere A come sottodirectory di B.
Come posso unire A in B, senza perdere la cronologia da nessuna parte?
Soluzione
Un singolo ramo di un altro repository può essere facilmente inserito in una sottodirectory conservandone la cronologia. Ad esempio:
git subtree add --prefix=rails git://github.com/rails/rails.git master
Questo apparirà come un unico commit in cui tutti i file del ramo master di Rails vengono aggiunti in " rails " directory. Tuttavia, il titolo del commit contiene un riferimento all'albero della cronologia precedente:
Aggiungi 'rails /' da commit
<rev>
Dove <=> è un hash di commit SHA-1. Puoi ancora vedere la cronologia, incolpare alcune modifiche.
git log <rev>
git blame <rev> -- README.md
Nota che non puoi vedere il prefisso della directory da qui poiché questo è un ramo vecchio effettivo lasciato intatto. Dovresti trattarlo come un normale commit di spostamento dei file: avrai bisogno di un salto in più quando lo raggiungi.
# finishes with all files added at once commit
git log rails/README.md
# then continue from original tree
git log <rev> -- README.md
Esistono soluzioni più complesse come farlo manualmente o riscrivere la cronologia come descritto in altre risposte.
Il comando git-subtree fa parte di git-contrib ufficiale, alcuni gestori di pacchetti lo installano di default (OS X Homebrew). Ma potresti doverlo installare da solo oltre a git.
Altri suggerimenti
Se vuoi unire project-a
in 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
Tratto da: git unire repository diversi?
Questo metodo ha funzionato abbastanza bene per me, è più breve e secondo me molto più pulito.
Nota: il parametro --allow-unrelated-histories
esiste solo da git > = 2.9. Vedi Git - git merge Documentation / --allow- imparentati-histories
Aggiorna : aggiunto --tags
come suggerito da @jstadler per mantenere i tag.
Ecco due possibili soluzioni:
moduli
Copia il repository A in una directory separata nel progetto B più grande o (forse meglio) clona il repository A in una sottodirectory del progetto B. Quindi usa git submodule per rendere questo repository un < strong> sottomodulo di un repository B.
Questa è una buona soluzione per i repository accoppiati liberamente, in cui lo sviluppo nel repository A continua e la maggior parte dello sviluppo è uno sviluppo autonomo separato in A. Vedi anche SubmoduleSupport e GitSubmoduleTutorial
Unione sottotree
È possibile unire il repository A in una sottodirectory di un progetto B utilizzando la strategia unione sottotree . Questo è descritto in Sottotree Merging and You di 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
(L'opzione --allow-unrelated-histories
è necessaria per Git > = 2.9.0.)
Oppure puoi usare lo strumento git subtree ( repository su GitHub ) di apenwarr (Avery Pennarun), annunciato ad esempio nel suo post sul blog Una nuova alternativa ai sottomoduli Git: git subtree .
Penso che nel tuo caso (A debba far parte di un progetto più grande B) la soluzione corretta sarebbe quella di utilizzare unione di sottostrutture .
L'approccio del sottomodulo è utile se si desidera mantenere il progetto separatamente.Tuttavia, se vuoi davvero unire entrambi i progetti nello stesso repository, allora hai ancora un po' di lavoro da fare.
La prima cosa sarebbe usare git filter-branch
per riscrivere i nomi di tutto nel secondo repository in modo che siano nella sottodirectory in cui vorresti che finissero.Quindi invece di foo.c
, bar.html
, avresti projb/foo.c
E projb/bar.html
.
Quindi, dovresti essere in grado di fare qualcosa di simile al seguente:
git remote add projb [wherever]
git pull projb
IL git pull
farà un git fetch
seguito da a git merge
.Non dovrebbero esserci conflitti, se il repository a cui stai eseguendo il pull non ha ancora un file projb/
directory.
Ulteriori ricerche indicano che è stato fatto qualcosa di simile per l'unione gitk
in git
.Junio C Hamano ne scrive qui: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html
git-subtree
è carino, ma probabilmente non è quello che desideri.
Ad esempio, se projectA
è la directory creata in B, dopo git subtree
,
git log projectA
elenca solo un commit: l'unione. I commit del progetto unito sono per percorsi diversi, quindi non vengono visualizzati.
La risposta di Greg Hewgill si avvicina, anche se in realtà non dice come riscrivere i percorsi.
La soluzione è sorprendentemente semplice.
(1) In 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: questo riscrive la cronologia, quindi se si intende continuare a utilizzare questo repository A, è consigliabile clonare (copiare) prima una copia usa e getta.
(2) Quindi in B, esegui
git pull path/to/A
Voila! Hai una git log projectA
directory in B. Se esegui projectB
, vedrai tutti i commit da A.
Nel mio caso, volevo due sottodirectory, <=> e <=>. In quel caso, ho fatto anche il passaggio da (1) a B.
Se entrambi i repository hanno lo stesso tipo di file (come due repository Rails per progetti diversi), è possibile recuperare i dati del repository secondario nel repository corrente:
git fetch git://repository.url/repo.git master:branch_name
e quindi uniscilo al repository corrente:
git merge --allow-unrelated-histories branch_name
Se la tua versione di Git è inferiore a 2.9, rimuovi --allow-unrelated-histories
.
Successivamente, potrebbero verificarsi conflitti. Puoi risolverli ad esempio con git mergetool
. kdiff3
può essere utilizzato esclusivamente con la tastiera, pertanto sono necessari 5 file di conflitto durante la lettura del codice in pochi minuti.
Ricorda di completare l'unione:
git commit
Ho continuato a perdere la cronologia durante l'utilizzo di Merge, quindi ho finito per usare rebase poiché nel mio caso i due repository sono abbastanza diversi da non finire per unire ad ogni commit:
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
& = gt; risolvere i conflitti, quindi continuare, tutte le volte che è necessario ...
git rebase --continue
In questo modo, un progetto ha tutti i commit di projA seguiti da commit di projB
Nel mio caso, avevo un my-plugin
repository e un main-project
repository e volevo fingere che plugins
fosse sempre stato sviluppato nella sottodirectory plugins/my-plugin
di master
.
Fondamentalmente, ho riscritto la cronologia del repository git filter-branch --tree-filter (...) HEAD
in modo che sia apparso tutto lo sviluppo nella sottodirectory (...)
. Quindi, ho aggiunto la cronologia di sviluppo di HEAD
nella filter-branch
storia e ho unito i due alberi insieme. Poiché non era già presente una directory .git
nel repository -f
, questa era una banale fusione senza conflitti. Il repository risultante conteneva tutta la storia di entrambi i progetti originali e aveva due radici.
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
Versione lunga
Innanzitutto, crea una copia del bash
repository, perché riscriveremo la cronologia di questo repository.
Ora, vai alla radice del repository zsh -c
, controlla il tuo ramo principale (probabilmente zsh
) ed esegui il comando seguente. Ovviamente, dovresti sostituire extended_glob
e ^(...)
qualunque sia il tuo vero nome.
$ 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
Ora per una spiegazione. mv
esegue il comando glob_dots
su ogni commit raggiungibile da .gitignore
. Nota che questo funziona direttamente sui dati memorizzati per ogni commit, quindi non dobbiamo preoccuparci delle nozioni di & Quot; directory di lavoro & Quot ;, & Quot; indice & Quot ;, < !> quot; messa in scena " ;, e così via.
Se si esegue un comando mkdir -p
che ha esito negativo, lascerà alcuni file nella directory ^(.git|plugins)
e la prossima volta che si prova || true
si lamenterà di ciò, a meno che non si fornisca l'opzione git filter-branch
a --all
.
Per quanto riguarda il comando effettivo, non ho avuto molta fortuna a fare --
fare quello che volevo, quindi invece uso git
per fare --allow-unrelated-histories
eseguire un comando. Per prima cosa ho impostato l'opzione git log
, che è ciò che abilita la sintassi git --version 2.9.0
nel comando zsh --version 5.2
, così come l'opzione <=>, che mi permette di selezionare dotfile (come <=>) con un glob (<=>).
Successivamente, utilizzo il comando <=> per creare contemporaneamente <=> e <=>.
Infine, uso il <=> " negativo glob " funzione <=> per abbinare tutti i file nella directory principale del repository tranne <=> e la cartella <=> appena creata. (Escludere <=> potrebbe non essere necessario qui, ma provare a spostare una directory in se stesso è un errore.)
Nel mio repository, il commit iniziale non includeva alcun file, quindi il comando <=> ha restituito un errore sul commit iniziale (poiché non era disponibile nulla da spostare). Pertanto, ho aggiunto un <=> in modo che <=> non si interrompesse.
L'opzione <=> dice a <=> di riscrivere la cronologia per tutti i rami nel repository, e il <=> extra è necessario per dire a <=> di interpretarlo come parte di l'elenco di opzioni per i rami da riscrivere, anziché come opzione per <=> stesso.
Ora, vai al tuo <=> repository e controlla in quale ramo vuoi unirti. Aggiungi la tua copia locale del <=> repository (con la sua cronologia modificata) come telecomando di <=> con:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Ora avrai due alberi non correlati nella tua cronologia di commit, che puoi visualizzare bene usando:
$ git log --color --graph --decorate --all
Per unirli, utilizzare:
$ git merge my-plugin/master --allow-unrelated-histories
Nota che in Git precedente alla 2.9.0, l'opzione <=> non esiste. Se stai usando una di queste versioni, ometti l'opzione: il messaggio di errore che <=> impedisce era anche aggiunto in 2.9.0.
Non dovresti avere conflitti di unione. Se lo fai, probabilmente significa che il comando <=> non ha funzionato correttamente o che c'era già una <=> directory in <=>.
Assicurati di inserire un messaggio di commit esplicativo per tutti i futuri contributori che si chiedono cosa stia succedendo un hacker per creare un repository con due radici.
È possibile visualizzare il nuovo grafico di commit, che dovrebbe avere due commit root, usando il comando <=> sopra. Tieni presente che verrà unito solo il <=> ramo . Ciò significa che se si ha un lavoro importante su altri <=> rami che si desidera unire nell'albero <=>, è necessario astenersi dall'eliminare il <=> telecomando fino a quando non si sono eseguite queste fusioni. Se yo no, quindi i commit da quei rami saranno ancora nel repository <=>, ma alcuni saranno irraggiungibili e suscettibili all'eventuale raccolta dei rifiuti. (Inoltre, dovrai fare riferimento a loro da SHA, poiché l'eliminazione di un telecomando rimuove i suoi rami di tracciamento remoto.)
Facoltativamente, dopo aver unito tutto ciò che si desidera mantenere da <=>, è possibile rimuovere il <=> telecomando utilizzando:
$ git remote remove my-plugin
Ora puoi eliminare in sicurezza la copia del repository <=> di cui hai cambiato la cronologia. Nel mio caso, ho anche aggiunto un avviso di deprecazione al vero <=> repository dopo che l'unione è stata completata e inviata.
Testato su Mac OS X El Capitan con <=> e <=>. Il chilometraggio può variare.
References:
- 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/
- Impossibile eliminare il file dal repository Git, impossibile creare un nuovo backup
- git, filtro-ramo su tutti i rami
Ho provato a fare la stessa cosa per giorni, sto usando git 2.7.2. Sottostruttura non conserva la storia.
Puoi utilizzare questo metodo se non utilizzerai più il vecchio progetto.
Vorrei suggerire di ramificare prima B e lavorare nel ramo.
Ecco i passaggi senza diramazione:
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
Se ora registri uno dei file nel sottodirectory A otterrai la cronologia completa
git log --follow A/<file>
Questo è stato il post che mi ha aiutato a fare questo:
Se si desidera inserire i file da un ramo nel repository B in un sububreeree//em> del repository A e preserva anche la storia, continua a leggere. (Nell'esempio che segue, presumo che vogliamo che il ramo master del repository B sia fuso nel ramo master del repository A.)
Nel repository A, eseguire innanzitutto le seguenti operazioni per rendere disponibile il repository B:
git remote add B ../B # Add repo B as a new remote.
git fetch B
Ora creiamo una nuova filiale (con un solo commit) nel repository A che chiamiamo new_b_root
. Il commit risultante avrà i file che sono stati impegnati nel primo commit del ramo master del repository B ma inseriti in una sottodirectory denominata 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))"
Spiegazione: L'opzione --orphan
al comando di checkout estrae i file dal ramo master di A ma non crea alcun commit. Avremmo potuto selezionare qualsiasi commit perché in seguito cancelleremo comunque tutti i file. Quindi, senza ancora eseguire il commit (-n
), selezioniamo il primo commit dal ramo principale di B. (Cherry-pick conserva il messaggio di commit originale che un checkout diretto non sembra fare.) Quindi creiamo la sottostruttura in cui vogliamo mettere tutti i file dal repository B. Dobbiamo quindi spostare tutti i file che sono stati introdotti nel scegli la ciliegia per la sottostruttura. Nell'esempio sopra, c'è solo un file README
da spostare. Quindi eseguiamo il commit del nostro root B-repo commit e, allo stesso tempo, preserviamo anche il timestamp del commit originale.
Ora creeremo un nuovo ramo B/master
sopra il b
appena creato. Chiamiamo la nuova filiale A/master
:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
Ora uniamo il nostro ramo B
in <=>:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
Infine, puoi rimuovere i <=> rami remoti e temporanei:
git remote remove B
git branch -D new_b_root b
Il grafico finale avrà una struttura come questa:
So che è passato molto tempo, ma non ero contento delle altre risposte che ho trovato qui, quindi ho scritto questo:
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
Ho raccolto molte informazioni qui su Stack & nbsp; OverFlow, ecc., e sono riuscito a mettere insieme uno script che risolve il problema per me.
L'avvertenza è che prende in considerazione solo il ramo "sviluppo" di ciascun repository e lo unisce in una directory separata in un repository completamente nuovo.
I tag e gli altri rami vengono ignorati: potrebbe non essere quello che desideri.
Lo script gestisce persino i rami e i tag delle funzionalità, rinominandoli nel nuovo progetto in modo da sapere da dove provengono.
#!/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
Puoi anche ottenerlo da http://paste.ubuntu.com/11732805
Innanzitutto crea un file con l'URL per ciascun repository, ad esempio:
git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git
Quindi chiama lo script dando un nome al progetto e il percorso dello script:
./mergeGitRepositories.sh eitchnet_test eitchnet.lst
Lo stesso script ha molti commenti che dovrebbero spiegare cosa fa.
Se stai cercando di incollare semplicemente due repository insieme, i sottomoduli e le fusioni di sottostruttura sono lo strumento sbagliato da usare perché non conservano tutta la cronologia dei file (come hanno notato le persone su altre risposte). Vedi questa risposta qui per il modo semplice e corretto di farlo.
Simile a @Smar ma utilizza i percorsi del file system, impostati in PRIMARY e SECONDARY:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
Quindi unisci manualmente.
(adattato da pubblicato da Anar Manafov )
Avevo una sfida simile, ma nel mio caso avevamo sviluppato una versione della base di codice nel repository A, quindi clonata in un nuovo repository, repository B, per la nuova versione del prodotto. Dopo aver corretto alcuni bug nel repository A, dovevamo inserire le modifiche nel repository B. Alla fine abbiamo fatto quanto segue:
- Aggiunta di un telecomando al repository B che puntava al repository A (git remote add ...)
- Estrazione del ramo corrente (non usavamo master per la correzione di bug) (git pull remoteForRepoA bugFixBranch)
- Il pushing si unisce a github
Ha funzionato a meraviglia :)
Quando desideri unire tre o più progetti in un singolo , esegui i passaggi come descritto nelle altre risposte (remote add -f
, merge
). Quindi, (soft) ripristina l'indice sulla vecchia testa (dove non è avvenuta alcuna unione). Aggiungi tutti i file (git add -A
) e esegui il commit (messaggio & Quot; Unione dei progetti A, B, C e D in un progetto). Questo è ora il commit-id del master.
Ora crea .git/info/grafts
con il seguente contenuto:
<commit-id of master> <list of commit ids of all parents>
Esegui git filter-branch -- head^..head head^2..head head^3..head
. Se hai più di tre rami, aggiungi solo head^n..head
quanti rami hai. Per aggiornare i tag, aggiungi --tag-name-filter cat
. Non aggiungerlo sempre, perché ciò potrebbe causare una riscrittura di alcuni commit. Per i dettagli vedere pagina man del filtro-ramo , cerca " innesti " ;.
Ora il tuo ultimo commit ha i genitori giusti associati.
Per unire una A in B:
1) Nel progetto A
git fast-export --all --date-order > /tmp/ProjectAExport
2) Nel progetto B
git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport
In questo ramo fai tutte le operazioni che devi fare e commettile.
C) Quindi di nuovo al master e una fusione classica tra i due rami:
git checkout master
git merge projectA
Unione di 2 repository
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
Questa funzione clonerà il repository remoto nella directory di repository locale, dopo aver salvato tutti i commit, git log
mostrerà i commit originali e i percorsi corretti:
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"
}
Come usare:
cd current/package
git-add-repo https://github.com/example/example dir/to/save
Se apporti alcune piccole modifiche puoi persino spostare file / directory di repository uniti in percorsi diversi, ad esempio:
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"
Annunci
I percorsi vengono sostituiti tramite sed
, quindi assicurati che si siano spostati nei percorsi corretti dopo l'unione.
Il parametro --allow-unrelated-histories
esiste solo da git & Gt; = 2.9.
Unisco i progetti leggermente manualmente, il che mi consente di evitare la necessità di affrontare i conflitti di unione.
per prima cosa, copia i file dall'altro progetto come preferisci.
cp -R myotherproject newdirectory
git add newdirectory
prossimo pull nella storia
git fetch path_or_url_to_other_repo
dì a git di fondersi nella storia dell'ultima cosa recuperata
echo 'FETCH_HEAD' > .git/MERGE_HEAD
ora commetti comunque come faresti normalmente
git commit
Il comando dato è la migliore soluzione possibile che suggerisco.
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
Volevo spostare un piccolo progetto in una sottodirectory di uno più grande. Dato che il mio piccolo progetto non aveva molti commit, ho usato git format-patch --output-directory /path/to/patch-dir
. Quindi, nel progetto più grande, ho usato git am --directory=dir/in/project /path/to/patch-dir/*
.
Questo sembra molto meno spaventoso e molto più pulito di un ramo di filtro. Concesso, potrebbe non essere applicabile a tutti i casi.