Como você mescla dois repositórios Git?


1622

Considere o seguinte cenário:

Eu desenvolvi um pequeno projeto experimental A em seu próprio repositório Git. Agora ele amadureceu e eu gostaria que A fizesse parte de um projeto maior B, que possui seu próprio repositório. Gostaria agora de adicionar A como um subdiretório de B.

Como mesclar A em B, sem perder o histórico de nenhum lado?


8
Se você está apenas tentando combinar dois repositórios em um, sem precisar manter os dois, dê uma olhada nesta pergunta: stackoverflow.com/questions/13040958/…
Flimm

Para integrar repo git no dir personalizado com economia de todos os comits usar stackoverflow.com/a/43340714/1772410
Andrey Izman

Respostas:


437

Uma única ramificação de outro repositório pode ser facilmente colocada em um subdiretório, mantendo seu histórico. Por exemplo:

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

Isso aparecerá como uma única confirmação, na qual todos os arquivos da ramificação mestre do Rails são adicionados ao diretório "rails". No entanto, o título do commit contém uma referência à antiga árvore de histórico:

Adicione 'rails /' do commit <rev>

Onde <rev>está um hash de confirmação do SHA-1. Você ainda pode ver a história, culpar algumas mudanças.

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

Observe que você não pode ver o prefixo do diretório a partir daqui, pois esse é um ramo antigo real deixado intacto. Você deve tratar isso como um commit normal de movimentação de arquivos: você precisará de um salto extra ao alcançá-lo.

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

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

Existem soluções mais complexas, como fazer isso manualmente ou reescrever o histórico, conforme descrito em outras respostas.

O comando git-subtree faz parte do git-contrib oficial; alguns gerenciadores de pacotes o instalam por padrão (OS X Homebrew). Mas você pode ter que instalá-lo sozinho, além do git.


2
Aqui estão as instruções sobre como instalar o Git SubTree (em junho de 2013): stackoverflow.com/a/11613541/694469 (e substituí git co v1.7.11.3 por ... v1.8.3).
precisa saber é o seguinte

1
Obrigado pelo aviso sobre a resposta abaixo. Como de git 1.8.4 'sub' ainda não está incluído (pelo menos não no Ubuntu 12,04 git ppa (ppa: git-núcleo / ppa))
Matt Klein

1
Posso confirmar que, depois disso, git log rails/somefileo histórico de confirmações desse arquivo não será exibido, exceto a confirmação de mesclagem. Como sugeriu @artfulrobot, verifique a resposta de Greg Hewgill . E você pode precisar usar git filter-brancho repositório que deseja incluir.
Jifeng Zhang

6
Ou ler "a fusão de duas Git Repositórios em Um repositório sem Perder Arquivo Histórico" de Eric Lee saintgimp.org/2013/01/22/...
Jifeng Zhang

4
Como outros já disseram, git subtreepode não fazer o que você pensa! Veja aqui uma solução mais completa.
Paul Draper

1908

Se você deseja mesclar project-aem 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

Retirado de: git mesclar repositórios diferentes?

Esse método funcionou muito bem para mim, é mais curto e, na minha opinião, muito mais limpo.

Caso queira colocar project-aem um subdiretório, você pode usar git-filter-repo( filter-branché desencorajado ). Execute os seguintes comandos antes dos comandos acima:

cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a

Um exemplo de mesclagem de 2 grandes repositórios, colocando um deles em um subdiretório: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Nota: O --allow-unrelated-historiesparâmetro existe apenas desde git> = 2.9. Veja a documentação do Git - git merge / --allow-unrelated-historories

Atualização : adicionada --tagsconforme sugerido por @jstadler para manter as tags.


8
Isso fez o negócio para mim. Funcionou como um encanto pela primeira vez, com apenas um conflito no arquivo .gitignore! Preservou perfeitamente o histórico de confirmação. A grande vantagem em relação a outras abordagens - além da simplicidade - é que, com isso, não é necessário que haja uma referência contínua ao repositório mesclado. Porém, uma coisa a ser observada - se você é um desenvolvedor iOS como eu - é muito cuidadoso ao inserir o arquivo de projeto do repositório de destino na área de trabalho.
Max MacLeod

30
Obrigado. Trabalhou para mim. I necessário para mover o diretório mescladas em uma sub-pasta para depois de seguir os passos acima eu simplesmente useigit mv source-dir/ dest/new-source-dir
Sid

13
A git mergeetapa falha aqui com fatal: refusing to merge unrelated histories; --allow-unrelated-historiescorrige isso conforme explicado nos documentos .
ssc

19
--allow-unrelated-historiesfoi introduzido no git 2.9 . Nas versões anteriores, era o comportamento padrão.
Douglas Royds

11
Shorter: git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD.
jthill

614

Aqui estão duas soluções possíveis:

Submodules

Copie o repositório A em um diretório separado no projeto maior B, ou (talvez melhor) clone o repositório A em um subdiretório no projeto B. Em seguida, use o submodule git para tornar esse repositório um submódulo de um repositório B.

Isto é uma boa solução para repositórios fracamente acopladas, onde o desenvolvimento de um repositório continua, e a maior parte do desenvolvimento é um desenvolvimento independente separado em A. Ver também SubmoduleSupport e GitSubmoduleTutorial páginas GIT Wiki.

Mesclar subárvore

Você pode mesclar o repositório A em um subdiretório de um projeto B usando a estratégia de mesclagem de subárvore . Isso é descrito em Subárvore Mesclando e Você 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

(A opção --allow-unrelated-historiesé necessária para Git> = 2.9.0.)

Ou você pode usar a ferramenta de subárvore git ( repositório no GitHub ) por apenwarr (Avery Pennarun), anunciada, por exemplo, em sua postagem no blog. Uma nova alternativa aos submódulos do Git: subárvore git .


Eu acho que no seu caso (A deve fazer parte de um projeto maior B), a solução correta seria usar a mesclagem de subárvore .


1
Isso funciona e parece preservar o histórico, mas não é possível que você possa usá-lo para diferenciar arquivos ou dividir a divisão. Estou perdendo um passo?
Jato

56
isso está incompleto . Sim, você recebe um monte de confirmações, mas elas não se referem mais aos caminhos corretos. git log dir-B/somefilenão mostrará nada, exceto a mesclagem. Veja a resposta de Greg Hewgill faz referência a esta importante questão.
Artfulrobot

2
IMPORTANTE: mestre do projeto de subárvore da subárvore git pull --no-rebase -s Se você não fizer isso e tiver definido o pull para rebase automaticamente, você terminará com "Não foi possível analisar o objeto". Veja osdir.com/ml/git/2009-07/msg01576.html
Eric Bowman - abstracto -

4
Essa resposta pode ser confusa porque tem B como a subárvore mesclada quando na pergunta era A. Resultado de uma cópia e colagem?
vfclists

11
Se você estiver tentando simplesmente colar dois repositórios, os submódulos e as mesclagens de subárvores são a ferramenta errada a ser usada, porque eles não preservam todo o histórico do arquivo (como outros comentadores observaram). Consulte stackoverflow.com/questions/13040958/… .
Eric Lee

194

A abordagem do submódulo é boa se você deseja manter o projeto separadamente. No entanto, se você realmente deseja mesclar os dois projetos no mesmo repositório, terá um pouco mais de trabalho a fazer.

A primeira coisa seria usar git filter-branchpara reescrever os nomes de tudo no segundo repositório para estar no subdiretório onde você gostaria que eles terminassem. Então, em vez de foo.c, bar.htmlvocê teria projb/foo.ce projb/bar.html.

Então, você poderá fazer algo como o seguinte:

git remote add projb [wherever]
git pull projb

O git pullfará um git fetchseguido de um git merge. Não deve haver conflitos, se o repositório para o qual você está puxando ainda não tiver um projb/diretório.

Além disso a pesquisa indica que algo semelhante foi feito para fundir gitkem git. Junio ​​C Hamano escreve sobre isso aqui: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


4
sub fusão seria melhor solução, e não necessitam de reescrever a história do projeto incluiu
Jakub Narębski

8
Eu gostaria de saber como usar git filter-branchpara conseguir isso. Na página de manual, diz o contrário: fazer subdir / tornar-se a raiz, mas não o contrário.
Artfulrobot

31
esta resposta seria ótimo se ele explicou como usar filter-branch para alcançar o resultado desejado
Anentropic

14
Eu encontrei como usar filter-branch aqui: stackoverflow.com/questions/4042816/...
David Minor

3
Veja esta resposta para a implementação do esboço de Greg.
Paul Draper

75

git-subtree é legal, mas provavelmente não é o que você deseja.

Por exemplo, se projectAé o diretório criado no B, depois git subtree,

git log projectA

lista apenas uma confirmação: a mesclagem. As confirmações do projeto mesclado são para caminhos diferentes, para que não apareçam.

A resposta de Greg Hewgill chega mais perto, embora na verdade não diga como reescrever os caminhos.


A solução é surpreendentemente simples.

(1) Em 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: Isso reescreve o histórico; portanto, se você pretende continuar usando esse repo A, convém clonar (copiar) uma cópia descartável primeiro.

Nota: É necessário modificar o script substituto dentro do comando sed no caso de você usar caracteres não-ascii (ou caracteres em branco) nos nomes ou caminho dos arquivos. Nesse caso, o local do arquivo em um registro produzido por "ls-files -s" começa com aspas.

(2) Então em B, corra

git pull path/to/A

Voila! Você tem um projectAdiretório em B. Se você executar git log projectA, verá todos os commits de A.


No meu caso, eu queria dois subdiretórios, projectAe projectB. Nesse caso, eu também dei o passo (1) para B.


1
Parece que você copiou sua resposta de stackoverflow.com/a/618113/586086 ?
Andrew Mao

1
@ AndrewMao, acho que sim ... Na verdade, não consigo me lembrar. Eu usei esse script um pouco.
Paul Draper

6
Eu gostaria de acrescentar que \ t não funciona no OS X e você tem que entrar <tab>
Muneeb Ali

2
"$GIT_INDEX_FILE"deve ser citado (duas vezes), caso contrário, seu método falhará se, por exemplo, o caminho contiver espaços.
Rob W

4
Se você está se perguntando, para inserir um <tab> no osx, você precisa:Ctrl-V <tab>
casey

48

Se os dois repositórios tiverem o mesmo tipo de arquivo (como dois repositórios Rails para projetos diferentes), você poderá buscar dados do repositório secundário no seu repositório atual:

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

e mescle-o ao repositório atual:

git merge --allow-unrelated-histories branch_name

Se sua versão do Git for menor que 2.9, remova --allow-unrelated-histories.

Depois disso, podem ocorrer conflitos. Você pode resolvê-los, por exemplo, com git mergetool. kdiff3pode ser usado apenas com o teclado; portanto, um arquivo de conflito leva 5 minutos ao ler o código em apenas alguns minutos.

Lembre-se de terminar a mesclagem:

git commit

25

Eu continuei perdendo o histórico ao usar a mesclagem, então acabei usando a rebase, pois no meu caso os dois repositórios são diferentes o suficiente para não acabar mesclando a cada 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

=> resolva conflitos e continue quantas vezes for necessário ...

git rebase --continue

Isso leva a um projeto com todas as confirmações do projA seguidas pelas confirmações do projB


25

No meu caso, eu tinha um my-pluginrepositório e um main-projectrepositório e queria fingir que my-pluginsempre havia sido desenvolvido no pluginssubdiretório de main-project.

Basicamente, reescrevi o histórico do my-pluginrepositório para que parecesse que todo o desenvolvimento ocorreu no plugins/my-pluginsubdiretório. Em seguida, adicionei o histórico de desenvolvimento my-pluginao main-projecthistórico e mesclei as duas árvores. Como não havia plugins/my-plugindiretório já presente no main-projectrepositório, essa foi uma mesclagem trivial sem conflitos. O repositório resultante continha todo o histórico dos dois projetos originais e tinha duas raízes.

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

Versão longa

Primeiro, crie uma cópia do my-pluginrepositório, porque vamos reescrever o histórico desse repositório.

Agora, navegue até a raiz do my-pluginrepositório, verifique sua ramificação principal (provavelmente master) e execute o seguinte comando. Obviamente, você deve substituir my-plugine pluginsquaisquer que sejam seus nomes reais.

$ 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

Agora, para uma explicação. git filter-branch --tree-filter (...) HEADexecuta o (...)comando em todas as confirmações acessíveis HEAD. Observe que isso opera diretamente nos dados armazenados para cada confirmação, portanto, não precisamos nos preocupar com noções de "diretório de trabalho", "índice", "preparo" e assim por diante.

Se você executar um filter-branchcomando que falhar, ele deixará para trás alguns arquivos no .gitdiretório e, na próxima vez em que tentar filter-branch, reclamará disso, a menos que você forneça a -fopção filter-branch.

Quanto ao comando real, não tive muita sorte bashem fazer o que queria, então, em vez disso, uso zsh -cpara zshexecutar um comando. Primeiro, defino a extended_globopção, que é o que habilita a ^(...)sintaxe no mvcomando, bem como a glob_dotsopção, que permite selecionar arquivos de ponto (como .gitignore) com um globo ( ^(...)).

Em seguida, uso o mkdir -pcomando para criar os dois pluginse plugins/my-pluginao mesmo tempo.

Por fim, uso o zshrecurso "glob negativo" ^(.git|plugins)para corresponder a todos os arquivos no diretório raiz do repositório, exceto .gita my-pluginpasta recém-criada . (A exclusão .gitpode não ser necessária aqui, mas tentar mover um diretório para si mesmo é um erro.)

No meu repositório, o commit inicial não incluía nenhum arquivo; portanto, o mvcomando retornou um erro no commit inicial (já que nada estava disponível para mover). Portanto, adicionei um || truepara que git filter-branchnão abortasse.

A --allopção diz filter-branchpara reescrever o histórico de todas as ramificações no repositório, e o extra --é necessário dizer gitpara interpretá-la como parte da lista de opções para reescrever ramificações, em vez de ser uma opção para filter-branchsi mesma.

Agora, navegue até o seu main-projectrepositório e verifique em qual filial você deseja mesclar. Adicione sua cópia local do my-pluginrepositório (com seu histórico modificado) como um controle remoto de main-projectcom:

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

Agora você terá duas árvores não relacionadas em seu histórico de consolidação, que podem ser visualizadas com bom uso:

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

Para mesclá-los, use:

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

Observe que no Git anterior à 2.9.0, a --allow-unrelated-historiesopção não existe. Se você estiver usando uma dessas versões, apenas omita a opção: a mensagem de erro que --allow-unrelated-historiesimpede também foi adicionada na 2.9.0.

Você não deve ter nenhum conflito de mesclagem. Se o fizer, provavelmente significa que o filter-branchcomando não funcionou corretamente ou já havia um plugins/my-plugindiretório main-project.

Certifique-se de inserir uma mensagem de confirmação explicativa para qualquer colaborador futuro, imaginando que hackery estava acontecendo para criar um repositório com duas raízes.

Você pode visualizar o novo gráfico de confirmação, que deve ter duas confirmações raiz, usando o git logcomando acima . Observe que apenas a masterramificação será mesclada . Isso significa que, se você tiver um trabalho importante em outros my-pluginramos que deseja mesclar na main-projectárvore, evite excluir o my-plugincontrole remoto até fazer essas mesclagens. Caso contrário, as confirmações dessas ramificações ainda estarão no main-projectrepositório, mas algumas estarão inacessíveis e suscetíveis a eventual coleta de lixo. (Além disso, você precisará consultá-los pelo SHA, porque a exclusão de um controle remoto remove suas ramificações de rastreamento remoto.)

Opcionalmente, depois de mesclar tudo o que você deseja impedir my-plugin, você pode remover o my-plugincontrole remoto usando:

$ git remote remove my-plugin

Agora você pode excluir com segurança a cópia do my-pluginrepositório cujo histórico foi alterado. No meu caso, também adicionei um aviso de descontinuação ao my-pluginrepositório real depois que a mesclagem foi concluída e enviada por push.


Testado no Mac OS X El Capitan com git --version 2.9.0e zsh --version 5.2. Sua milhagem pode variar.

Referências:


1
De onde --allow-unrelated-historiesvem?
Xpto

3
@MarceloFilho Check man git-merge. Por padrão, o comando git merge se recusa a mesclar históricos que não compartilham um ancestral comum. Essa opção pode ser usada para substituir essa segurança ao mesclar históricos de dois projetos que iniciaram suas vidas independentemente. Como essa é uma ocasião muito rara, não existe variável de configuração para habilitar isso por padrão e não será adicionada.
Radon Rosborough

Deve estar disponível em git version 2.7.2.windows.1?
Xpto

2
@ MarceloFilho Isso foi adicionado na versão 2.9.0, mas nas versões mais antigas você não deveria ter que passar na opção (ela funcionará). github.com/git/git/blob/…
Radon Rosborough

Isso funcionou bem. E pude usar a ramificação do filtro para reescrever os nomes dos arquivos para onde eu queria na árvore antes da mesclagem. Suponho que haja mais trabalho envolvido se você precisar mover a história além do ramo mestre.
codeDr

9

Estou tentando fazer a mesma coisa há dias, estou usando o git 2.7.2. A subárvore não preserva o histórico.

Você pode usar esse método se não usar o projeto antigo novamente.

Eu sugiro que você ramifique B primeiro e trabalhe no ramo.

Aqui estão as etapas sem ramificação:

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 você agora registrar qualquer um dos arquivos no subdiretório A, obterá o histórico completo

git log --follow A/<file>

Este foi o post que me ajudou a fazer isso:

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


8

Se você deseja colocar os arquivos de uma ramificação no repositório B em uma subárvore do repositório A e também preservar o histórico, continue lendo. (No exemplo abaixo, estou assumindo que queremos que a ramificação mestre do repositório B seja mesclada no ramo mestre do repositório A.)

No repositório A, primeiro faça o seguinte para disponibilizar o repositório B:

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

Agora, criamos um novo ramo (com apenas um commit) no repositório A que chamamos new_b_root. A confirmação resultante terá os arquivos que foram confirmados na primeira confirmação da ramificação principal do repositório B, mas colocados em um subdiretório chamado 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))"

Explicação: A --orphanopção para o comando checkout faz check-out dos arquivos da ramificação principal de A, mas não cria nenhuma confirmação. Poderíamos ter selecionado qualquer confirmação, porque a seguir, limpamos todos os arquivos de qualquer maneira. Então, sem confirmar ainda ( -n), escolhemos o primeiro commit no ramo principal de B. (A seleção de cereja preserva a mensagem de confirmação original que um checkout direto não parece fazer.) Em seguida, criamos a subárvore onde queremos colocar todos os arquivos do repositório B. Em seguida, precisamos mover todos os arquivos que foram introduzidos no escolha cereja para a subárvore. No exemplo acima, há apenas um READMEarquivo para mover. Em seguida, confirmamos nosso commit raiz B-repo e, ao mesmo tempo, também preservamos o registro de data e hora do commit original.

Agora, criaremos uma nova B/masterramificação em cima da recém-criada new_b_root. Chamamos o novo ramo b:

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

Agora, mesclamos nossa bramificação em A/master:

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

Por fim, você pode remover as Bramificações remotas e temporárias:

git remote remove B
git branch -D new_b_root b

O gráfico final terá uma estrutura como esta:

insira a descrição da imagem aqui


Ótima resposta, obrigado! Eu realmente perdi nas outras respostas com "subárvore git" ou "mesclar - histórias não permitidas" de Andresch Serj que o subdiretório não tinha o log.
Ilendir

8

Reuni muitas informações aqui no Stack OverFlow, etc., e consegui montar um script que resolve o problema para mim.

A ressalva é que ele leva em consideração apenas a ramificação 'develop' de cada repositório e a mescla em um diretório separado em um repositório completamente novo.

Tags e outras ramificações são ignoradas - pode não ser o que você deseja.

O script ainda lida com ramificações e tags de recursos - renomeando-os no novo projeto para que você saiba de onde eles vieram.

#!/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

Você também pode obtê-lo em http://paste.ubuntu.com/11732805

Primeiro, crie um arquivo com a URL para cada repositório, por exemplo:

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

Em seguida, chame o script fornecendo um nome para o projeto e o caminho para o script:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

O script em si tem muitos comentários que devem explicar o que ele faz.


Em vez de direcionar os leitores para uma resposta, poste a resposta aqui (ou seja, edite o que você disse nesse comentário nesta resposta).
josliber

1
Claro, apenas achou melhor não me repetir ... =)
eitch

Se você acha que essa pergunta é idêntica à outra, é possível sinalizá-la como duplicada usando o link "sinalizador" sob a própria pergunta e indicando a outra pergunta. Se não for uma pergunta duplicada, mas você acha que a mesma resposta exata pode ser usada para resolver os dois problemas, basta postar a mesma resposta nos dois problemas (como você fez agora). Obrigado por contribuir!
josliber

Surpreendente! Não funcionou no prompt do Windows bash, mas funcionou perfeitamente de uma caixa do Vagrant executando o ubuntu. Que economia de tempo!
xverges

Feliz por estar de serviço =)
eitch

7

Eu sei que é muito tempo depois do fato, mas não fiquei feliz com as outras respostas que encontrei aqui, então escrevi o seguinte:

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

2
Era exatamente isso que eu estava procurando. Obrigado! No entanto, tive que mudar a linha 22 para:if [[ $dirname =~ ^.*\.git$ ]]; then
heyman

2
^. * blarg $ é um desperdício ganancioso RE. Melhor dizer .blarg $ e pular a âncora da frente.
jettero 27/01

7

Se você estiver tentando simplesmente colar dois repositórios, os submódulos e as mesclagens de subárvores são a ferramenta errada a ser usada, porque eles não preservam todo o histórico do arquivo (como as pessoas observaram em outras respostas). Veja esta resposta aqui para a maneira simples e correta de fazer isso.


1
Sua solução funciona bem apenas para o novo repositório, mas que tal mesclar repositório em outro com conflitos de arquivo?
precisa saber é o seguinte

6

Tive um desafio semelhante, mas, no meu caso, desenvolvemos uma versão da base de código no repositório A e clonamos isso em um novo repositório, o repositório B, para a nova versão do produto. Depois de corrigir alguns bugs no repositório A, precisávamos FI as alterações no repositório B. Acabamos fazendo o seguinte:

  1. Adicionando um controle remoto ao repositório B que aponta para o repositório A (git remote add ...)
  2. Puxando a ramificação atual (não estávamos usando o mestre para correções de bugs) (git pull remoteForRepoA bugFixBranch)
  3. O envio de mesclagens para o github

Trabalhou um deleite :)


5

Semelhante ao @Smar, mas usa caminhos do sistema de arquivos, definidos em PRIMARY e SECONDARY:

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

Então você mescla manualmente.

(adaptado do post de Anar Manafov )


5

Mesclando 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

4

Quando você deseja mesclar três ou mais projetos em uma única confirmação, execute as etapas conforme descrito nas outras respostas ( remote add -f, merge). Em seguida, (suave) redefina o índice para o cabeçalho antigo (onde nenhuma mesclagem aconteceu). Adicione todos os arquivos ( git add -A) e confirme-os (mensagem "Mesclando os projetos A, B, C e D em um projeto). Agora é o ID de confirmação do mestre.

Agora, crie .git/info/graftscom o seguinte conteúdo:

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

Corra git filter-branch -- head^..head head^2..head head^3..head. Se você tiver mais de três ramificações, adicione o máximo head^n..headque tiver. Para atualizar tags, acrescente --tag-name-filter cat. Nem sempre adicione isso, pois isso pode causar uma reescrita de algumas confirmações. Para detalhes, consulte a página de manual do filtro-branch , procure por "enxertos".

Agora, seu último commit tem os pais certos associados.


1
Aguarde, por que você deseja mesclar três projetos em uma única confirmação?
21313 Steve Bennett

Comecei com repositório, repositório-cliente e modelador como projetos git separados. Isso foi difícil para os colegas de trabalho, então me juntei a eles em um único projeto git. Para poder que a "raiz" do novo projeto se origine de outros três projetos, eu queria ter uma confirmação de mesclagem única .
koppor

4

Para mesclar um A em B:

1) No projeto A

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

2) No projeto B

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

Nesta ramificação, execute todas as operações necessárias e as confirme.

C) Depois, de volta ao mestre e uma fusão clássica entre os dois ramos:

git checkout master
git merge projectA

2

Essa função clonará o repo remoto no dir repo local, após a mesclagem de todas as confirmações serem salvas, git logserão mostradas as confirmações originais e os caminhos adequados:

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"
}

Como usar:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Se fizer algumas alterações, você pode até mover arquivos / diretórios de repositório mesclado para caminhos diferentes, por exemplo:

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
Caminhos substitui via sed, portanto, verifique se ele se moveu nos caminhos adequados após a mesclagem.
O --allow-unrelated-historiesparâmetro existe apenas desde git> = 2.9.


1

O comando dado é a melhor solução possível, eu sugiro.

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

1

Mesclo projetos levemente manualmente, o que me permite evitar a necessidade de lidar com conflitos de mesclagem.

primeiro, copie os arquivos do outro projeto da maneira que desejar.

cp -R myotherproject newdirectory
git add newdirectory

próxima atração na história

git fetch path_or_url_to_other_repo

diga ao git para se fundir na história da última coisa buscada

echo 'FETCH_HEAD' > .git/MERGE_HEAD

agora comprometa, no entanto, você normalmente comprometeria

git commit

0

Eu queria mover um projeto pequeno para um subdiretório de um projeto maior. Como meu pequeno projeto não tinha muitos commits, usei git format-patch --output-directory /path/to/patch-dir. Então, no projeto maior, eu usei git am --directory=dir/in/project /path/to/patch-dir/*.

Isso parece muito menos assustador e muito mais limpo que um ramo de filtro. Concedido, pode não ser aplicável a todos os casos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.