다음 시나리오를 고려하십시오.
자체 Git 저장소에서 작은 실험 프로젝트 A를 개발했습니다. 이제 성숙 해졌고 A는 자체 큰 저장소가있는 더 큰 프로젝트 B의 일부가되기를 바랍니다. 이제 A를 B의 하위 디렉토리로 추가하고 싶습니다.
어느 쪽이든 히스토리를 잃지 않고 A를 B로 병합하는 방법은 무엇입니까?
다음 시나리오를 고려하십시오.
자체 Git 저장소에서 작은 실험 프로젝트 A를 개발했습니다. 이제 성숙 해졌고 A는 자체 큰 저장소가있는 더 큰 프로젝트 B의 일부가되기를 바랍니다. 이제 A를 B의 하위 디렉토리로 추가하고 싶습니다.
어느 쪽이든 히스토리를 잃지 않고 A를 B로 병합하는 방법은 무엇입니까?
답변:
다른 저장소의 단일 브랜치는 히스토리를 유지하는 서브 디렉토리 아래에 쉽게 배치 할 수 있습니다. 예를 들면 다음과 같습니다.
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 외에도 직접 설치해야 할 수도 있습니다.
git co v1.7.11.3
와 함께 ... v1.8.3
).
git log rails/somefile
는 병합 커밋을 제외한 해당 파일의 커밋 기록이 표시되지 않음을 확인할 수 있습니다 . @artfulrobot이 제안했듯이 Greg Hewgill의 답변을 확인하십시오 . git filter-branch
포함하려는 리포지토리 에 사용해야 할 수도 있습니다.
병합 할 경우 project-a
에 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
:에서 촬영 자식 병합 다른 저장소?
이 방법은 나를 위해 잘 작동했습니다. 더 짧고 제 생각에는 훨씬 깨끗합니다.
경우 당신이 데려 가고 싶다는 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가 제안한대로 추가 되었습니다.
git mv source-dir/ dest/new-source-dir
--allow-unrelated-histories
git 2.9 에서 소개되었습니다 . 이전 버전에서는 기본 동작이었습니다.
git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD
.
가능한 두 가지 해결책은 다음과 같습니다.
리포지토리 A를 더 큰 프로젝트 B의 별도 디렉터리에 복사하거나 리포지토리 A를 프로젝트 B의 하위 디렉터리에 복제하십시오. 그런 다음 git submodule 을 사용하여이 리포지토리를 리포지토리 B 의 하위 모듈 로 만드십시오 .
이 방법은 리포지토리 A의 개발이 계속되고 느슨하게 연결된 리포지토리에 적합한 솔루션이며, 개발의 주요 부분은 A의 독립형 독립 개발입니다. Git Wiki의 SubmoduleSupport 및 GitSubmoduleTutorial 페이지 도 참조하십시오 .
서브 트리 병합 전략을 사용하여 저장소 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의 일부입니다) 올바른 솔루션은 하위 트리 병합 을 사용하는 것 입니다.
git log dir-B/somefile
하나의 병합을 제외하고는 아무것도 표시하지 않습니다. 참조 그렉 Hewgill의 답변 참고 문헌이 중요한 문제.
하위 모듈 접근 방식은 프로젝트를 별도로 유지하려는 경우에 좋습니다. 그러나 두 프로젝트를 동일한 저장소에 실제로 병합하려면 약간 더 많은 작업이 필요합니다.
첫 번째 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
git filter-branch
달성 하는 데 사용하는 방법을 알고 싶습니다 . 매뉴얼 페이지에서 반대 방향에 대해 말합니다 : subdir /을 루트로 만들지 만 다른 방법은 아닙니다.
git-subtree
좋았지 만 아마도 당신이 원하는 것이 아닐 것입니다.
예를 들어 projectA
B에서 작성된 디렉토리 인 경우 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 단계까지 수행했습니다.
"$GIT_INDEX_FILE"
따옴표로 묶어야합니다 (두 번). 그렇지 않으면 경로에 공백이 포함 된 경우 메서드가 실패합니다.
Ctrl-V <tab>
두 리포지토리에 동일한 종류의 파일 (예 : 다른 프로젝트에 대한 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
병합을 사용할 때 기록을 계속 잃어 버렸으므로 리베이스를 사용하여 끝났습니다. 내 경우에는 두 저장소가 커밋 할 때마다 병합되지 않을 정도로 다릅니다.
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의 커밋을 갖습니다.
제 경우에는 my-plugin
저장소와 main-project
저장소가 my-plugin
있었고의 plugins
하위 디렉토리 에서 항상 개발 된 것처럼 가장하고 싶었습니다 main-project
.
기본적으로, 나는 my-plugin
모든 개발이 plugins/my-plugin
서브 디렉토리 에서 일어난 것처럼 보이도록 저장소 의 히스토리를 다시 썼다 . 그럼, 개발 이력을 추가 my-plugin
로 main-project
역사와 함께 두 그루의 나무를 합병했다. 에 plugins/my-plugin
디렉토리 가 없기 때문에main-project
저장소 사소한 충돌이 없었습니다. 결과 리포지토리에는 두 원본 프로젝트의 모든 기록이 포함되었으며 두 가지 루트가있었습니다.
$ 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 -c
을 zsh
실행하는 데 사용 합니다 . 먼저 명령 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
. 귀하의 마일리지가 다를 수 있습니다.
참고 문헌 :
--allow-unrelated-histories
에서 오나요?
man git-merge
. 기본적으로 git merge 명령은 공통 조상을 공유하지 않는 기록을 병합하지 않습니다. 이 옵션은 독립적으로 생활을 시작한 두 프로젝트의 기록을 병합 할 때이 안전을 무시하는 데 사용할 수 있습니다. 매우 드문 경우이므로 기본적으로이 기능을 활성화하는 구성 변수가 없으며 추가되지 않습니다.
git version 2.7.2.windows.1
?
며칠 동안 같은 일을하려고했지만 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>
이것은 내가 이것을하는 데 도움이되는 게시물이었습니다.
당신이에 환매 특약 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))"
설명 : --orphan
checkout 명령 옵션은 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
최종 그래프는 다음과 같은 구조를 갖습니다.
여기에서 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
스크립트 자체에는 스크립트의 기능을 설명하는 많은 주석이 있습니다.
나는 그것이 사실 이후 오래되었다는 것을 알고 있지만 여기에서 찾은 다른 답변에 만족하지 않아서 이것을 썼습니다.
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
if [[ $dirname =~ ^.*\.git$ ]]; then
단순히 두 개의 리포지토리를 서로 연결하려는 경우 하위 모듈과 하위 트리 병합은 사람들이 다른 답변에서 언급했듯이 모든 파일 기록을 유지하지 않기 때문에 사용하기에 잘못된 도구입니다. 이 답변을 참조하십시오 여기에 간단하고이 작업을 수행하는 올바른 방법을.
@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의 게시물 에서 수정 )
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
단일 커밋 에서 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"를 검색하십시오.
이제 마지막 커밋에 올바른 부모가 연결되었습니다.
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
이 함수는 원격 저장소를 로컬 저장소 디렉토리로 복제하고, 모든 커밋을 병합 한 후 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 이후에만 존재합니다.