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?

È stato utile?

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:

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:

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

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:

 inserisci qui la descrizione dell'immagine

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:

  1. Aggiunta di un telecomando al repository B che puntava al repository A (git remote add ...)
  2. Estrazione del ramo corrente (non usavamo master per la correzione di bug) (git pull remoteForRepoA bugFixBranch)
  3. 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top