두 개의 Git 리포지토리를 어떻게 병합합니까?


1620

다음 시나리오를 고려하십시오.

자체 Git 저장소에서 작은 실험 프로젝트 A를 개발했습니다. 이제 성숙 해졌고 A는 자체 큰 저장소가있는 더 큰 프로젝트 B의 일부가되기를 바랍니다. 이제 A를 B의 하위 디렉토리로 추가하고 싶습니다.

어느 쪽이든 히스토리를 잃지 않고 A를 B로 병합하는 방법은 무엇입니까?


8
두 저장소를 모두 유지할 필요없이 두 저장소를 하나로 결합하려는 경우이 질문을 살펴보십시오. stackoverflow.com/questions/13040958/…
Flimm

모든 커밋을 저장하고 사용자 정의 디렉토리에 git repo를 병합하려면 stackoverflow.com/a/43340714/1772410
Andrey Izman

답변:


436

다른 저장소의 단일 브랜치는 히스토리를 유지하는 서브 디렉토리 아래에 쉽게 배치 할 수 있습니다. 예를 들면 다음과 같습니다.

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

이것은 Rails 마스터 브랜치의 모든 파일이 "rails"디렉토리에 추가되는 단일 커밋으로 나타납니다. 그러나 커밋의 제목에는 이전 히스토리 트리에 대한 참조가 포함됩니다.

커밋에서 'rails /'추가 <rev>

<rev>SHA-1 커밋 해시는 어디에 있습니까 ? 당신은 여전히 ​​역사를 볼 수 있으며 일부 변화를 비난합니다.

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

디렉토리 접두사는 여기에서 실제로 볼 수있는 오래된 오래된 브랜치이므로 여기에서 볼 수 없습니다. 이것을 일반적인 파일 이동 커밋처럼 취급해야합니다. 도달 할 때 추가 점프가 필요합니다.

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

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

다른 답변에서 설명한 것처럼 수동으로 수행하거나 기록을 다시 쓰는 것과 같은 더 복잡한 솔루션이 있습니다.

git-subtree 명령은 공식 git-contrib의 일부이며 일부 패킷 관리자는 기본적으로이를 설치합니다 (OS X Homebrew). 그러나 git 외에도 직접 설치해야 할 수도 있습니다.


2
여기에 (6 월 2013) 망할 놈의 서브 트리를 설치하는 방법에 대한 지침은 다음과 같습니다 stackoverflow.com/a/11613541/694469은 (내가 대체 git co v1.7.11.3 와 함께 ... v1.8.3).
KajMagnus

1
아래 답변에 대한 의견을 보내 주셔서 감사합니다. 현재로 자식 1.8.4 '하위 트리'아직 (적어도하지 자식이 PPA (PPA 우분투 12.04에 : 자식 코어 / PPA)) 포함되어 있지 않습니다
매트 클라인

1
이 후에 git log rails/somefile는 병합 커밋을 제외한 해당 파일의 커밋 기록이 표시되지 않음을 확인할 수 있습니다 . @artfulrobot이 제안했듯이 Greg Hewgill의 답변을 확인하십시오 . git filter-branch포함하려는 리포지토리 에 사용해야 할 수도 있습니다.
Jifeng Zhang

6
또는 Eric Lee의 "파일 히스토리를 잃지
Jifeng Zhang

4
다른 사람들이 말했듯이, git subtree당신이 생각하는 것을하지 않을 수 있습니다! 보다 완벽한 솔루션 은 여기 를 참조 하십시오 .
Paul Draper

1906

병합 할 경우 project-aproject-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

:에서 촬영 자식 병합 다른 저장소?

이 방법은 나를 위해 잘 작동했습니다. 더 짧고 제 생각에는 훨씬 깨끗합니다.

경우 당신이 데려 가고 싶다는 project-a하위 디렉토리, 당신이 사용할 수에 git-filter-repo( filter-branch있다 낙담 ). 위의 명령 전에 다음 명령을 실행하십시오.

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

두 개의 큰 리포지토리를 병합하여 그 중 하나를 하위 디렉터리에 넣는 예 : https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

참고 :--allow-unrelated-histories매개 변수는 git> = 2.9 이후에만 존재합니다. Git-git merge Documentation / --allow-unrelated-histories를 참조하십시오.

업데이트 : --tags태그를 유지하기 위해 @jstadler가 제안한대로 추가 되었습니다.


8
이것은 나를 위해 사업을했다. gitignore 파일에서 충돌이 하나만 발생하여 처음으로 매력처럼 작동했습니다! 커밋 히스토리를 완벽하게 보존했습니다. 단순성과 더불어 다른 접근 방식에 비해 큰 장점은 병합 된 리포지토리에 대한 지속적인 참조가 필요하지 않다는 것입니다. 그러나 당신이 나와 같은 iOS 개발자라면 조심해야 할 것은 대상 리포지토리의 프로젝트 파일을 작업 공간에 놓을 때 매우 조심하는 것입니다.
Max MacLeod

30
감사. 나를 위해 일했다. 병합 된 디렉토리를 하위 폴더로 이동해야하므로 위의 단계를 수행 한 후 간단히 사용했습니다git mv source-dir/ dest/new-source-dir
Sid

13
git merge단계는 여기서 실패합니다 fatal: refusing to merge unrelated histories. 문서에--allow-unrelated-histories 설명 된대로 수정합니다 .
ssc

19
--allow-unrelated-historiesgit 2.9 에서 소개되었습니다 . 이전 버전에서는 기본 동작이었습니다.
Douglas Royds

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

614

가능한 두 가지 해결책은 다음과 같습니다.

서브 모듈

리포지토리 A를 더 큰 프로젝트 B의 별도 디렉터리에 복사하거나 리포지토리 A를 프로젝트 B의 하위 디렉터리에 복제하십시오. 그런 다음 git submodule 을 사용하여이 리포지토리를 리포지토리 B 의 하위 모듈 로 만드십시오 .

이 방법은 리포지토리 A의 개발이 계속되고 느슨하게 연결된 리포지토리에 적합한 솔루션이며, 개발의 주요 부분은 A의 독립형 독립 개발입니다. Git Wiki의 SubmoduleSupportGitSubmoduleTutorial 페이지 도 참조하십시오 .

하위 트리 병합

서브 트리 병합 전략을 사용하여 저장소 A를 프로젝트 B의 서브 디렉토리로 병합 할 수 있습니다 . 이는 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

(옵션 --allow-unrelated-histories은 Git> = 2.9.0에 필요합니다.)

또는 apenwarr (Avery Pennarun)의 git subtree 도구 ( GitHub의 저장소)를 사용할 수 있습니다 (예 : 블로그 게시물 Git 하위 모듈의 새로운 대안 : git subtree) .


귀하의 경우 (A는 더 큰 프로젝트 B의 일부입니다) 올바른 솔루션은 하위 트리 병합 을 사용하는 것 입니다.


1
이것은 작동하고 기록을 보존하는 것처럼 보이지만 병합을 통해 파일을 분할하거나 이등분하는 데 사용할 수는 없습니다. 단계가 빠졌습니까?
jettero

55
이것은 불완전 합니다. 예, 커밋이 많이 발생하지만 더 이상 올바른 경로를 참조하지 않습니다. git log dir-B/somefile하나의 병합을 제외하고는 아무것도 표시하지 않습니다. 참조 그렉 Hewgill의 답변 참고 문헌이 중요한 문제.
artfulrobot 2018 년

2
중요 : git pull --no-rebase -s 하위 트리 Bproject 마스터 그렇게하지 않으면 pull을 자동으로 rebase로 설정하면 "객체를 구문 분석 할 수 없습니다"가 표시됩니다. 14:35의 osdir.com/ml/git/2009-07/msg01576.html
Eric Bowman-abstracto-

4
질문에 A 일 때 B를 병합 된 하위 트리로 사용하기 때문에이 대답은 혼란 스러울 수 있습니다. 복사하여 붙여 넣기 결과?
vfclists

11
단순히 두 개의 리포지토리를 서로 연결하려는 경우 하위 주석과 하위 트리 병합은 모든 파일 기록을 유지하지 않기 때문에 사용하기에 잘못된 도구입니다 (다른 주석 작성자가 언급했듯이). stackoverflow.com/questions/13040958/…를 참조하십시오 .
Eric Lee

194

하위 모듈 접근 방식은 프로젝트를 별도로 유지하려는 경우에 좋습니다. 그러나 두 프로젝트를 동일한 저장소에 실제로 병합하려면 약간 더 많은 작업이 필요합니다.

첫 번째 git filter-branch는 두 번째 저장소의 모든 이름을 다시 작성하여 하위 디렉토리에있게하는 것입니다. 그래서 대신foo.c , bar.html당신은 것 projb/foo.c하고 projb/bar.html.

그런 다음 다음과 같은 작업을 수행 할 수 있어야합니다.

git remote add projb [wherever]
git pull projb

git pull을 할 것입니다git fetch 뒤에 옵니다 git merge. 가져 오는 리포지토리에 아직 projb/디렉토리 가없는 경우 충돌이 없어야합니다 .

또한 검색은 비슷한 병합 완료되었음을 나타냅니다 gitk으로 git. Junio ​​C Hamano는 이에 대해 다음과 같이 썼습니다 : http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


4
하위 트리 병합은 더 나은 솔루션이 될 것이며, 포함 된 프로젝트의 재 작성 역사를 필요로하지 않는다
야쿱 Narębski

8
이것을 git filter-branch달성 하는 데 사용하는 방법을 알고 싶습니다 . 매뉴얼 페이지에서 반대 방향에 대해 말합니다 : subdir /을 루트로 만들지 만 다른 방법은 아닙니다.
artfulrobot 2016 년

31
이 결과는 필터 브랜치를 사용하여 원하는 결과를 얻는 방법을 설명하면 좋을 것입니다.
Anentropic

14
filter-branch를 사용하는 방법을 여기에서 찾았습니다 : stackoverflow.com/questions/4042816/…
David Minor

3
Greg의 개요를 구현하려면 이 답변 을 참조하십시오 .
Paul Draper

75

git-subtree 좋았지 만 아마도 당신이 원하는 것이 아닐 것입니다.

예를 들어 projectAB에서 작성된 디렉토리 인 경우 git subtree,

git log projectA

목록 하나는 커밋 : 병합. 병합 된 프로젝트의 커밋은 다른 경로에 대한 것이므로 표시되지 않습니다.

Greg Hewgill의 답변은 실제로 경로를 다시 작성하는 방법을 말하지는 않지만 가장 가깝습니다.


해결책은 놀랍도록 간단합니다.

(1) 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

참고 : 기록이 다시 작성되므로이 리포지토리 A를 계속 사용하려는 경우 먼저이 복제본의 복제본을 복제 (복사) 할 수 있습니다.

참고 Bene : 파일 이름이나 경로에 ASCII가 아닌 문자 (또는 흰색 문자)를 사용하는 경우 sed 명령 내에서 대체 스크립트를 수정해야합니다. 이 경우 "ls-files -s"에 의해 생성 된 레코드 내의 파일 위치는 따옴표로 시작합니다.

(2) 그런 다음 B에서

git pull path/to/A

짜잔! 당신은이 projectA실행할 경우 B의 디렉토리를 git log projectA당신 A. 모든 커밋을 볼 수,


내 경우, 나는 두 개의 하위 디렉토리를 원 projectA하고 projectB. 이 경우에는 (1) 단계에서 B 단계까지 수행했습니다.


1
stackoverflow.com/a/618113/586086 에서 답변을 복사 한 것 같습니다 .
Andrew Mao

1
@AndrewMao, 그렇게 생각합니다 ... 실제로는 기억할 수 없습니다. 이 스크립트를 꽤 많이 사용했습니다.
Paul Draper

6
OS X에서 \ t가 작동하지 않는다는 것을 추가하고 <tab>을 입력해야합니다.
Muneeb Ali

2
"$GIT_INDEX_FILE"따옴표로 묶어야합니다 (두 번). 그렇지 않으면 경로에 공백이 포함 된 경우 메서드가 실패합니다.
Rob W

4
궁금하다면, osx에 <tab>을 삽입하려면Ctrl-V <tab>
Casey

48

두 리포지토리에 동일한 종류의 파일 (예 : 다른 프로젝트에 대한 2 개의 Rails 리포지토리)이있는 경우 보조 리포지토리의 데이터를 현재 리포지토리로 가져올 수 있습니다.

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

그런 다음 현재 저장소에 병합하십시오.

git merge --allow-unrelated-histories branch_name

Git 버전이 2.9보다 작은 경우를 제거하십시오 --allow-unrelated-histories.

이 후 충돌이 발생할 수 있습니다. 예를 들어으로 해결할 수 있습니다 git mergetool. kdiff3키보드로만 사용할 수 있으므로 몇 분만 코드를 읽을 때 5 개의 충돌 파일이 사용됩니다.

병합을 완료해야합니다.

git commit

25

병합을 사용할 때 기록을 계속 잃어 버렸으므로 리베이스를 사용하여 끝났습니다. 내 경우에는 두 저장소가 커밋 할 때마다 병합되지 않을 정도로 다릅니다.

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

=> 충돌을 해결 한 다음 필요한만큼 계속하십시오 ...

git rebase --continue

이를 수행하면 하나의 프로젝트가 projA의 모든 커밋과 projB의 커밋을 갖습니다.


25

제 경우에는 my-plugin저장소와 main-project저장소가 my-plugin있었고의 plugins하위 디렉토리 에서 항상 개발 된 것처럼 가장하고 싶었습니다 main-project.

기본적으로, 나는 my-plugin모든 개발이 plugins/my-plugin서브 디렉토리 에서 일어난 것처럼 보이도록 저장소 의 히스토리를 다시 썼다 . 그럼, 개발 이력을 추가 my-pluginmain-project역사와 함께 두 그루의 나무를 합병했다. 에 plugins/my-plugin디렉토리 가 없기 때문에main-project저장소 사소한 충돌이 없었습니다. 결과 리포지토리에는 두 원본 프로젝트의 모든 기록이 포함되었으며 두 가지 루트가있었습니다.

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

긴 버전

먼저이 my-plugin저장소의 히스토리를 다시 작성하므로 저장소 의 사본을 작성하십시오.

이제 my-plugin저장소 의 루트로 이동 하여 기본 분기 (아마도 master)를 확인한 후 다음 명령을 실행하십시오. 물론, 당신은 대체해야 my-plugin하고 plugins실제 이름은 무엇입니다.

$ 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

이제 설명을하겠습니다. 에서 도달 할 수있는 모든 커밋마다 명령을 git filter-branch --tree-filter (...) HEAD실행합니다 . 이것은 각 커밋에 대해 저장된 데이터에서 직접 작동하므로 "작업 디렉토리", "인덱스", "스테이징"등의 개념에 대해 걱정할 필요가 없습니다.(...)HEAD

filter-branch실패한 명령을 실행 하면 .git디렉토리에 일부 파일이 남게되며 다음에 시도 filter-branch할 때 -f옵션을 제공하지 않으면 이에 대해 불평 합니다 filter-branch.

실제 명령에 관해서 bash는, 내가 원하는 것을 수행하는 데 많은 운이 없었 으므로 대신 명령 zsh -czsh실행하는 데 사용 합니다 . 먼저 명령 extended_glob에서 ^(...)구문을 활성화하는 옵션 과 glob ( ) 로 점 파일 (예 :)을 선택할 수 mv있는 glob_dots옵션을 설정했습니다 ..gitignore^(...)

다음으로, 내가 사용 mkdir -p모두를 만드는 명령을 plugins하고 plugins/my-plugin같은 시간에.

마지막으로, zsh"negative glob"기능 ^(.git|plugins)을 사용하여 저장소의 루트 디렉토리 .git와 새로 작성된 my-plugin폴더를 제외한 모든 파일을 일치시킵니다 . (여기서 제외 .git할 필요는 없지만 디렉토리 자체로 이동하려고하면 오류가 발생합니다.)

내 저장소에서 초기 커밋에는 파일이 포함되어 있지 않으므로 mv명령은 초기 커밋에서 오류를 반환했습니다 (이동할 수있는 것이 없기 때문에). 따라서 중단되지 || true않도록 추가했습니다 git filter-branch.

--all옵션은 이야기 filter-branch에 대한 역사를 다시 작성하는 모든 저장소에 지사를, 그리고 여분은 --말할 필요가 git대신에 옵션으로 다시 쓰기로 분기의 옵션 목록의 일부로 해석 filter-branch자체.

이제 main-project저장소로 이동 하여 병합하려는 분기를 확인하십시오. 다음을 사용하여 my-plugin리포지토리 의 로컬 복사본 (이력이 수정 된 상태)을 원격으로 추가하십시오 main-project.

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

커밋 히스토리에 두 개의 관련없는 트리가 생겼습니다.

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

그것들을 병합하려면 다음을 사용하십시오.

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

2.9.0 이전 Git에는 --allow-unrelated-histories옵션이 존재하지 않습니다. 이 버전 중 하나를 사용하는 경우 옵션을 생략하십시오 --allow-unrelated-histories. 2.9.0에서 방지 하는 오류 메시지추가되었습니다.

병합 충돌이 없어야합니다. 그럴 경우, filter-branch명령이 제대로 작동하지 않았거나 이미 plugins/my-plugin디렉토리가 있음을 의미 main-project합니다.

해커가 두 개의 루트를 가진 저장소를 만들기 위해 무슨 일이 있었는지 궁금해하는 미래의 기고자에게 설명 커밋 메시지를 입력하십시오.

위의 git log명령을 사용하여 새로운 커밋 그래프를 시각화 할 수 있습니다. 여기에는 두 개의 루트 커밋이 있어야합니다 . 주의 에만 master지점이 병합됩니다 . 즉 my-plugin, main-project트리 에 병합하려는 다른 분기 에 대한 중요한 작업이있는 경우 my-plugin이러한 병합을 완료 할 때까지 원격 삭제를 삼가야 합니다. 그렇지 않으면 해당 브랜치의 커밋은 여전히 main-project리포지토리에 있지만 일부는 도달 할 수없고 최종 가비지 수집에 취약합니다. 또한 원격을 삭제하면 원격 추적 분기가 제거되므로 SHA로 참조해야합니다.

선택적으로에서 유지하려는 모든 것을 병합 한 후 다음을 사용 my-plugin하여 my-plugin리모컨을 제거 할 수 있습니다 .

$ git remote remove my-plugin

my-plugin히스토리가 변경된 저장소 의 사본을 안전하게 삭제할 수 있습니다 . 필자의 경우 my-plugin병합이 완료되고 푸시 된 후에 실제 저장소에 폐기 알림을 추가했습니다 .


맥 OS X 엘과 캐피에서 테스트 git --version 2.9.0하고 zsh --version 5.2. 귀하의 마일리지가 다를 수 있습니다.

참고 문헌 :


1
어디 --allow-unrelated-histories에서 오나요?
xpto

3
@MarceloFilho 확인 man git-merge. 기본적으로 git merge 명령은 공통 조상을 공유하지 않는 기록을 병합하지 않습니다. 이 옵션은 독립적으로 생활을 시작한 두 프로젝트의 기록을 병합 할 때이 안전을 무시하는 데 사용할 수 있습니다. 매우 드문 경우이므로 기본적으로이 기능을 활성화하는 구성 변수가 없으며 추가되지 않습니다.
라돈 로스 버러

에 사용할 수 있어야합니까 git version 2.7.2.windows.1?
xpto

2
@MarceloFilho이 버전은 2.9.0에서 추가되었지만 이전 버전에서는 옵션을 전달할 필요가 없습니다 (작동합니다). github.com/git/git/blob/…
라돈

이것은 잘 작동했습니다. 그리고 필터 분기를 사용하여 병합 전에 트리에서 원하는 위치에 파일 이름을 다시 쓸 수있었습니다. 마스터 브랜치 외에도 히스토리를 이동 해야하는 경우 더 많은 작업이 필요하다고 생각합니다.
codeDr

9

며칠 동안 같은 일을하려고했지만 git 2.7.2를 사용하고 있습니다. 하위 트리는 기록을 유지하지 않습니다.

이전 프로젝트를 다시 사용하지 않을 경우이 방법을 사용할 수 있습니다.

B 지점을 먼저 시작하고 지점에서 작업하는 것이 좋습니다.

분기가없는 단계는 다음과 같습니다.

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

하위 디렉토리 A에 파일을 기록하면 전체 히스토리가 표시됩니다.

git log --follow A/<file>

이것은 내가 이것을하는 데 도움이되는 게시물이었습니다.

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


8

당신이에 환매 특약 B에 지점에서 파일을 넣을 경우 하위 트리 의 repo A의 도 역사를 보존, 계속 읽어. (아래 예에서, repo B의 마스터 브랜치를 repo A의 마스터 브랜치로 병합하고자한다고 가정합니다.)

리포지토리 A에서 먼저 리포지토리 B를 사용 가능하게하려면 다음을 수행하십시오.

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

이제 우리는 repo A에 새로운 지점 (커밋이 하나만 있음)을 호출 new_b_root합니다. 결과 커밋은 repo B의 마스터 브랜치의 첫 번째 커밋에서 커밋되었지만이라는 하위 디렉토리에 파일이 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))"

설명 : --orphancheckout 명령 옵션은 A의 마스터 브랜치에서 파일을 체크 아웃하지만 커밋을 작성하지는 않습니다. 다음에는 모든 파일을 지우므로 커밋을 선택할 수 있습니다. 그런 다음 아직 커밋하지 않고 ( -n) B의 마스터 브랜치에서 첫 커밋을 선택합니다. (cherry-pick은 똑바로 체크 아웃하지 않는 원래 커밋 메시지를 유지합니다.) 그런 다음 repo B에서 모든 파일을 넣을 하위 트리를 만듭니다. 그런 다음 도입 된 모든 파일을 이동해야합니다 하위 트리를 선택합니다. 위의 예에서는 README이동할 파일 만 있습니다. 그런 다음 B-repo 루트 커밋을 커밋하고 동시에 원래 커밋의 타임 스탬프도 보존합니다.

이제 B/master새로 생성 된 위에 새 분기를 만듭니다 new_b_root. 우리는 새로운 지점을 부릅니다 b:

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

이제 b지점을 A/master다음 으로 병합합니다 .

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

마지막으로 B원격 및 임시 분기를 제거 할 수 있습니다 .

git remote remove B
git branch -D new_b_root b

최종 그래프는 다음과 같은 구조를 갖습니다.

여기에 이미지 설명을 입력하십시오


좋은 답변, 감사합니다! Andresch Serj의 "git subtree"또는 "merge --allow-unrelated-histories"라는 다른 답변에서 하위 디렉토리에 로그가 없다는 사실을 정말로 놓쳤습니다.
Ilendir

8

여기에서 Stack OverFlow 등에 대한 많은 정보를 수집했으며 문제를 해결하는 스크립트를 함께 만들었습니다.

주의 사항은 각 저장소의 '개발'분기 만 고려하여 완전히 새로운 저장소의 별도 디렉토리에 병합한다는 것입니다.

태그와 다른 브랜치는 무시됩니다-이것은 당신이 원하는 것이 아닐 수도 있습니다.

이 스크립트는 기능 브랜치 및 태그를 처리하여 새 프로젝트에서 이름을 바꾸어 원래 위치를 알 수 있도록합니다.

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

http://paste.ubuntu.com/11732805 에서 얻을 수도 있습니다 .

먼저 각 저장소에 대한 URL을 가진 파일을 작성하십시오 (예 :

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

그런 다음 프로젝트 이름과 스크립트 경로를 제공하는 스크립트를 호출하십시오.

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

스크립트 자체에는 스크립트의 기능을 설명하는 많은 주석이 있습니다.


독자를 답변으로 안내하는 대신 여기에 답변을 게시하십시오 (일명 해당 의견에서 말한 내용을이 답변으로 편집).
josliber

1
물론, 나 자신을 반복하지 않는 것이 좋다고 생각했다 ... =)
eitch

이 질문이 다른 질문과 동일하다고 생각되면 질문 자체의 "플래그"링크를 사용하여 다른 질문을 나타내는 중복 질문을 표시 할 수 있습니다. 중복 질문이 아니지만 두 가지 문제를 모두 해결하기 위해 똑같은 대답을 사용할 수 있다고 생각되면 두 가지 문제에 대해 동일한 대답을 게시하십시오 (이전처럼). 기여해 주셔서 감사합니다!
josliber

놀랄 만한! Windows bash 프롬프트에서 작동하지 않았지만 우분투를 실행하는 Vagrant 상자를 완벽하게 형성했습니다. 시간을 절약하세요!
xverges

서비스에
행복하게

7

나는 그것이 사실 이후 오래되었다는 것을 알고 있지만 여기에서 찾은 다른 답변에 만족하지 않아서 이것을 썼습니다.

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
이것은 내가 찾던 것입니다. 감사! 그러나 22 행을 다음과 같이 변경해야했습니다.if [[ $dirname =~ ^.*\.git$ ]]; then
heyman

2
^. * blarg $는 욕심 많은 RE입니다. .blarg $를 말하고 앞 앵커를 건너 뛰는 것이 좋습니다.
jettero

7

단순히 두 개의 리포지토리를 서로 연결하려는 경우 하위 모듈과 하위 트리 병합은 사람들이 다른 답변에서 언급했듯이 모든 파일 기록을 유지하지 않기 때문에 사용하기에 잘못된 도구입니다. 이 답변을 참조하십시오 여기에 간단하고이 작업을 수행하는 올바른 방법을.


1
솔루션은 새 리포지토리에서만 잘 작동하지만 파일 충돌로 다른 리포지토리에 리포지를 병합하는 방법은 무엇입니까?
Andrey Izman

6

비슷한 과제가 있었지만 제 경우에는 리포지토리 A에서 한 가지 버전의 코드베이스를 개발 한 다음 새 버전의 리포지토리 인 리포지토리 B로 복제했습니다. 리포지토리 A에서 일부 버그를 수정 한 후 리포지토리 B에 변경 사항을 적용해야했습니다.

  1. repo A를 가리키는 repo B에 원격 추가 (git remote add ...)
  2. 현재 브랜치 풀기 (버그 수정에는 master를 사용하지 않았습니다) (git pull remoteForRepoA bugFixBranch)
  3. 병합을 github에 푸시

치료를했다 :)


5

@Smar와 유사하지만 PRIMARY 및 SECONDARY에 설정된 파일 시스템 경로를 사용합니다.

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

그런 다음 수동으로 병합합니다.

( Anar Manafov의 게시물 에서 수정 )


5

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

단일 커밋 에서 3 개 이상의 프로젝트를 병합 하려면 다른 답변 ( remote add -f, merge)에 설명 된 단계를 수행하십시오 . 그런 다음 (소프트) 인덱스를 이전 헤드로 다시 설정하십시오 (병합이 발생하지 않은 곳). 모든 파일 추가 (git add -A )을 하고 커밋합니다 ( "프로젝트 A, B, C 및 D 프로젝트를 하나의 프로젝트에 병합"). 이제 마스터의 커밋 ID입니다.

이제 .git/info/grafts다음 내용으로 작성하십시오 .

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

를 실행하십시오 git filter-branch -- head^..head head^2..head head^3..head. 분기가 3 개 이상인 경우 분기가있는만큼 추가하십시오 head^n..head. 태그를 업데이트하려면을 추가하십시오 --tag-name-filter cat. 커밋이 다시 작성 될 수 있으므로 항상 추가하지 마십시오. 자세한 내용은 필터 브랜치 매뉴얼 페이지 를 참조하십시오. 에서 "grafts"를 검색하십시오.

이제 마지막 커밋에 올바른 부모가 연결되었습니다.


1
잠깐, 왜 하나의 커밋으로 세 개의 프로젝트를 병합하겠습니까?
Steve Bennett

리포지토리, 리포지토리 클라이언트 및 모델러를 별도의 git 프로젝트로 시작했습니다. 동료에게는 어려웠으므로 단일 git 프로젝트에 참여했습니다. 새 프로젝트의 "루트"가 3 개의 다른 프로젝트에서 시작될 수 있도록 단일 병합 커밋을 원했습니다 .
koppor

4

B 내에서 A를 병합하려면

1) 프로젝트 A

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

2) 프로젝트 B

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

이 지점에서는 수행해야 할 모든 작업을 수행하고 커밋합니다.

C) 그런 다음 마스터로 돌아가서 두 지점 사이의 고전적인 병합 :

git checkout master
git merge projectA

2

이 함수는 원격 저장소를 로컬 저장소 디렉토리로 복제하고, 모든 커밋을 병합 한 후 git log원래 커밋과 적절한 경로를 보여줍니다.

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

사용하는 방법:

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

약간 변경하면 병합 된 저장소의 파일 / 디렉토리를 다른 경로로 이동할 수도 있습니다. 예를 들면 다음과 같습니다.

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"

공지 사항
경로는 via를 대체 sed하므로 병합 후 경로가 올바른 경로로 이동했는지 확인하십시오.
--allow-unrelated-histories매개 변수는 git> = 2.9 이후에만 존재합니다.


1

주어진 명령은 내가 제안하는 최선의 해결책입니다.

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

1

프로젝트를 약간 수동으로 병합하므로 병합 충돌을 처리 할 필요가 없습니다.

먼저 다른 프로젝트의 파일을 원하는대로 복사하십시오.

cp -R myotherproject newdirectory
git add newdirectory

다음으로 역사를 끌어들이다

git fetch path_or_url_to_other_repo

마지막으로 가져온 물건의 역사에서 병합하도록 git에게 알리십시오.

echo 'FETCH_HEAD' > .git/MERGE_HEAD

이제 커밋하지만 일반적으로 커밋합니다.

git commit

0

작은 프로젝트를 더 큰 하위 디렉토리로 옮기고 싶었습니다. 내 작은 프로젝트에는 많은 커밋이 없었기 때문에을 사용했습니다 git format-patch --output-directory /path/to/patch-dir. 그런 다음 더 큰 프로젝트에서을 사용했습니다 git am --directory=dir/in/project /path/to/patch-dir/*.

이 느낌 방식으로 필터 분기보다 무서운 방법이 더 깨끗합니다. 물론 모든 경우에 해당되는 것은 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.