하위 디렉토리에 git 저장소 병합


85

내 작업 git 저장소의 원격 git 저장소를 하위 디렉토리로 병합하고 싶습니다. 결과 저장소에 두 저장소의 병합 된 기록이 포함되고 병합 된 저장소의 각 파일이 원격 저장소에있는 것처럼 기록이 유지되기를 원합니다. How to use the subtree merge strategy 에서 언급 한대로 하위 트리 전략을 사용해 보았지만 해당 절차를 수행 한 후 결과 저장소에는 실제로 두 저장소의 병합 된 기록이 포함되어 있지만 원격 저장소에서 오는 개별 파일은 해당 기록을 유지하지 않습니다. ( 'git log'는 "Merged branch ..."라는 메시지 만 표시합니다.)

또한 두 개의 결합 된 git 저장소가 더 이상 분리되는 것을 원하지 않기 때문에 하위 모듈을 사용하고 싶지 않습니다.

원격 저장소에서 가져온 개별 파일이 기록을 유지하는 하위 디렉토리로 다른 저장소의 원격 git 저장소를 병합 할 수 있습니까?

도움을 주셔서 대단히 감사합니다.

편집 : 현재 git filter-branch를 사용하여 병합 된 저장소 기록을 다시 쓰는 솔루션을 시도하고 있습니다. 작동하는 것 같지만 좀 더 테스트해야합니다. 나는 내 결과를보고하기 위해 돌아갈 것이다.

편집 2 : 원격 저장소의 파일 기록이 명백하게 손실되는 결과로 git의 하위 트리 전략과 함께 사용한 정확한 명령을 더 명확하게 제공하기를 바랍니다. A를 현재 작업중인 git repo로하고 B를 A에 하위 디렉토리로 통합하려는 git repo를 지정합니다. 다음을 수행했습니다.

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

이 명령을 실행 한 후 subdir / Iwant / to / put / B / in 디렉터리로 이동하면 B의 모든 파일이 표시되지만 git log그중 하나에 "Merge B as subdir in subdir / Iwant / to / put /큰 상자." B에있는 파일 기록은 손실됩니다.

어떤 (나는 자식에 초보자이야 이후 내가 잘못 될 수있다) 작업으로 다음과 같다 :

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

filter-branch에 대한 위의 명령은에서 가져 왔습니다 git help filter-branch. 여기서 저는 subdir 경로 만 변경했습니다.


무엇 않는 gitk역사에 대해 말할? 나는 과거에 git subtree merge를 성공적으로 사용했습니다. 정확한 명령을 공개 할 수 있을까요? git-filter-branch가 올바른 접근 방식인지 잘 모르겠습니다. 새로운 기록을 합성하기 위해 git-fast-export 및 git-fast-import를 시도하는 것이 좋습니다.
Seth Robertson

하위 트리 절차를 수행 gitk하면 두 개의 저장소가 팁에 병합되고 초기 커밋에서 관련이없는 것을 보여줍니다. (gitk의 히스토리 뷰 스크린 샷을 게시하면 도움이 될까요? 할 수 있습니까?) 불행히도 원격 저장소의 개별 파일은 터미널에서 수행하면 히스토리를 유지하지 않습니다 git log <file-from-remote-repo>. 나는 조사 git-fast-export하고 git-fast-import; 나는 git을 처음 접했습니다. git 하위 트리와 함께 사용한 명령을 정확히 보여주기 위해 질문을 편집 할 것입니다. 답장을 보내 주셔서 대단히 감사합니다.
christosc

@christosc : 두 번째 방법은 아름답고 매우 간단하게 작동했습니다. 감사합니다! 나는 단지 subdir / Iwant / to / put / B / in /을 변경하고 그것을 oneliner로 만들어야했습니다 (Windows의 msysgit는 명령에서 줄 반환을 지원하지 않는 것처럼 보이기 때문에) : git filter-branch --index-filter 'git ls- 파일 -s | sed "s- \ t \"*-& subdir / Iwant / to / put / B / in /- "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv"$ GIT_INDEX_FILE.new ""$ GIT_INDEX_FILE " 'HEAD
gaborous 2013 년

@ user1121352 도움이되어 기쁩니다.
christosc 2013 년

나는 보통이 대답을 따르 stackoverflow.com/a/1684694/207791을
빅터 Sergienko

답변:


40

무슨 일이 일어나고 있는지에 대한 완전한 설명을 얻은 후, 나는 그것을 이해한다고 생각하며 어떤 경우에도 해결 방법이 있습니다. 특히, 나는 이름 변경 감지가 --prefix와의 하위 트리 병합에 속고 있다고 생각합니다. 내 테스트 케이스는 다음과 같습니다.

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

우리는 각각 여러 커밋으로 git 디렉토리 a와 b를 만듭니다. 하위 트리 병합을 수행 한 다음 새 하위 트리에서 최종 커밋을 수행합니다.

달리기 gitk(z / a)는 히스토리가 나타나는 것을 보여줍니다. 우리는 그것을 볼 수 있습니다. 달리기 git log는 역사가 나타나는 것을 보여줍니다. 그러나 특정 파일을 보면 문제가 있습니다. git log bdir/B

음, 우리가 할 수있는 트릭이 있습니다. --follow를 사용하여 특정 파일의 이름 바꾸기 이전 기록을 볼 수 있습니다. git log --follow -- B. 이것은 좋지만 사전 병합의 기록과 병합 후의 기록을 연결하지 못하기 때문에 좋지 않습니다.

-M과 -C를 가지고 놀았지만 특정 파일을 따라갈 수 없었습니다.

그래서 해결책은 git에게 하위 트리 병합의 일부로 발생하는 이름 변경에 대해 알려주는 것입니다. 불행히도 git-read-tree는 하위 트리 병합에 대해 매우 까다롭기 때문에 임시 디렉토리를 통해 작업해야하지만 커밋하기 전에 사라질 수 있습니다. 그 후 우리는 전체 역사를 볼 수 있습니다.

먼저 "A"저장소를 만들고 몇 가지 커밋을 만듭니다.

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

둘째, "B"저장소를 만들고 몇 가지 커밋을 만듭니다.

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

그리고이 작업을 수행하는 비결은 Git이 하위 디렉토리를 만들고 내용을 이동하여 이름을 인식하도록 강제합니다.

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

저장소 "A"로 돌아가서 "B"의 내용을 가져와 병합합니다.

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

이제 병합되었음을 표시하려면 :

cd bdir
echo BBB>>B
git commit -a -m BBB

전체 역사가 연결된 체인에 보존되어 있음을 증명하려면 :

git log --follow B

이 작업을 수행 한 후 기록을 얻지 만 문제는 실제로 이전 "b"저장소를 유지하고 때때로 병합하는 경우 (실제로는 별도로 유지 관리되는 타사 인 경우) 해당 타사 이후 문제가 발생한다는 것입니다. 이름을 변경하지 않았습니다. 이름을 변경하여 b 버전에 새로운 변경 사항을 병합해야하며 원활하게 진행되지 않을까 두렵습니다. 그러나 b가 사라지면 당신이 이깁니다.


실제로 @Seth 작동합니다! 그리고 필터 브랜치처럼 역사 재 작성에 의지 할 필요가 없었습니다. 이것은 다소기만적인 역사를 만듭니다 (예 :를 보는 동안 git log --stat). 또한 나는 --followgit log의 문서에서 스위치를 알아 차리지 못했다 . 이름 변경에 매우 편리해 보입니다. 그렇게 상세하고 유익한 답변을 해주셔서 대단히 감사합니다!
christosc

2
이 응답은 예제 코드가 단일 세미콜론으로 구분 된 한 줄 대신 읽기 쉬운 줄로 나뉘어 진 경우 훨씬 더 유용 할 것입니다. ;)
jwadsack 2012-07-26

전체 기록을 유지하면서 "b"를 "a"로 병합하고 싶습니다. 어떻게 할 수 있습니까?
emeraldhieu


3
@AlexBrown이 언급했듯이이 새 버전에서는 git생성 fatal: refusing to merge unrelated histories되므로 git merge -s ours --allow-unrelated-histories --no-commit B/master대신 실행해야합니다 .
pjvandehaar

61

git-subtree는 기록을 보존하면서 여러 저장소를 하나로 병합하는이 사용 사례를 위해 설계된 스크립트입니다 (및 / 또는이 질문과 관련이없는 것처럼 보이지만 하위 트리의 기록을 분할). 릴리스 1.7.11 이후 git 트리의 일부로 배포됩니다 .

<repo>리비전 의 저장소 를 <rev>하위 디렉토리로 병합하려면 다음과 같이 <prefix>사용하십시오 git subtree add.

git subtree add -P <prefix> <repo> <rev>

git-subtree 는보다 사용자 친화적 인 방식으로 하위 트리 병합 전략 을 구현합니다 .

단점은 병합의 역사에 파일이 (가 아닌 하위 디렉토리에) 접두어가 있다는 것입니다. 저장소 ab. 결과적으로 git log a/f1병합 된 기록의 변경 사항을 제외한 모든 변경 사항 (있는 경우)이 표시됩니다. 넌 할 수있어:

git log --follow -- f1

그러나 병합 된 기록의 다른 변경 사항은 표시되지 않습니다.

즉, arepository에서의 파일을 변경하지 않으면 및 접두사가없는 경로 b를 지정해야 --follow합니다. 두 저장소 모두에서 변경하면 두 개의 명령이 있으며 어느 것도 모든 변경 사항을 표시하지 않습니다.

여기에서 더 자세히 알아 보십시오 .


좋은! 이것은 정확히 한 줄에 필요한 것입니다. 감사합니다, 미래!
iameli

이것은 다른 저장소를 하위 방향으로 내 저장소에 병합하는 완벽한 솔루션입니다.
eitch

1
의 기존 하위 디렉토리에서는 작동하지 않습니다 <prefix>. 자신의 저장소 somewhen로 수동으로 이동 된 하위 디렉토리를 병합하기 위해 예를 들어, 당신은 다시 그것을 병합 할.
리처드 키퍼

8

내가 원한

  1. 명시 적 병합없이 선형 히스토리 유지
  2. 이 병합 된 저장소의 파일처럼 보이게 항상 하위 디렉토리에 존재하고, 부작용 메이크로했다 git log -- file없이 작업 --follow.

1 단계 : 모든 파일이 항상 하위 디렉터리 아래에 존재하는 것처럼 보이도록 소스 저장소의 기록을 다시 작성합니다.

다시 작성된 기록에 대한 임시 분기를 만듭니다.

git checkout -b tmp_subdir

그런 다음 이미 이동 한 파일을 제외한 모든 파일이 하위 디렉터리에 있도록 기록을 다시 쓸 수git filter-branch 있는 방법에 설명 된대로 사용 합니다. :

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

2 단계 : 대상 저장소로 전환합니다. 소스 저장소를 대상 저장소에 원격으로 추가하고 해당 컨텐츠를 가져옵니다.

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

3 단계 : merge --onto대상 리포지토리 위에 재 작성된 소스 리포지토리의 커밋을 추가하는 데 사용 합니다.

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

로그를 확인하여 이것이 실제로 원하는 것을 얻었는지 확인할 수 있습니다.

git log --stat

4 단계 : 리베이스 후에는 "분리 된 HEAD"상태에 있습니다. 마스터를 새 헤드로 빨리 감을 수 있습니다.

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

5 단계 : 마지막으로 몇 가지 정리 : 임시 리모컨을 제거합니다.

git remote rm sourcerepo

git rebase지정된 옵션을 함께 허용하지 않는 것 같습니다. "오류 : 대화 형 옵션을 결합 할 수 없습니다 (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root +- -onto) with am 옵션 (--committer-date-is-author-date) "
Sam

흥미 롭군요! 드롭을 시도하십시오 --committer-date-is-author-date. 호환되지 않는 옵션에 대한 검사는 최근 git v2.19.0 ( github.com/git/git/commit/… ) 에 추가되었습니다 . 설명에서 그것은 --committer-date-is-author-date어쨌든 전에 조용히 무시 된 것처럼 들립니다 .
hfs

이전 filter-branch명령을 사용하는 대신를 사용하면 git filter-repo --to-subdirectory-filter <dir>훨씬 빠르고 쉽습니다.
Willem

5

정말로 함께 꿰매고 싶다면 접목을 찾으십시오. 또한 git rebase --preserve-merges --onto. 커미터 정보의 작성자 날짜를 유지하는 옵션도 있습니다.


@adymitruk 답장 해 주셔서 감사합니다. 저는 git을 처음 접했기 때문에 제안한 솔루션을 살펴볼 것입니다. 나는 시도 git filter-branch했고 작동하는 것처럼 보이지만 아마도 당신의 것이 더 낫습니다. 시도해 볼게요.
christosc 2011-06-22

@adymitruk 브랜치로 상호 관련되지 않은 두 개의 리포지토리에 리베이스를 사용할 수 있습니까? 난 ... 병합 할 두 개의 저장소가없는 일반적인 초기 커밋을 의미
christosc

감사합니다 @adymitruk. 두 개의 관련되지 않은 저장소로 리베이스를 수행 할 수 있는지 확실하지 않았습니다. 확실히 유용 할 것입니다…
christosc 2011-06-22

그러나 필터 브랜치를 두려워하지 마십시오. 그것은 우리를 여러 번 구했습니다. 다른 지점을 먼저 만들면 언제든지 돌아갈 수 있습니다. 또는 reflog를 사용하십시오.
Adam Dymitruk 2011-06-22

나는… 어쨌든 나는이 git 개념과 명령에 대한 문서를 읽는 것이 더 낫다. VCS, 즉 svn에 대한 경험이 거의 없기 때문에 git에 압도 당합니다. 그 힘은 그만한 가치가있는 것 같습니다.
christosc

4

다음 솔루션이 나에게 적합하다는 것을 알았습니다. 먼저 프로젝트 B로 이동하여 이미 모든 파일이 새 하위 디렉터리로 이동되는 새 분기를 만듭니다. 그런 다음이 새 분기를 원점으로 푸시합니다. 다음으로 프로젝트 A로 이동하여 B의 리모컨을 추가하고 가져온 다음 이동 된 분기를 체크 아웃하고 마스터로 돌아가 병합합니다.

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

하위 디렉토리로 이동하면 subdir사용할 수 git log --follow있으며 여전히 기록이 있습니다.

나는 자식 전문가가 아니기 때문에 이것이 특히 좋은 해결책인지 또는 경고가 있는지에 대해서는 언급 할 수 없지만 지금까지는 괜찮아 보입니다.


사람들은 여기에이 방법을 upvoting 것 같다 : stackoverflow.com/questions/1683531/...
nacross

3

여분의 저장소를 git 하위 모듈로 추가해 보셨습니까? 히스토리를 포함하는 저장소와 병합하지 않고 실제로 독립적 인 저장소가됩니다.

당신이하지 않았기 때문에 나는 그것을 언급합니다.


1
답변 Abizern에 감사드립니다. 사실 저는 두 개의 저장소 히스토리가 하나로 병합되기를 원합니다. 더 이상 분리되는 것을 원하지 않기 때문에 하위 모듈을 언급하지 않았습니다.
christosc 18:41에

1

저장소 a를 다음 과 같이 병합하고 싶다고 b가정 해 보겠습니다. (서로 나란히 있다고 가정합니다) :

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

이를 위해서는 git-filter-repo설치 filter-branch가 필요합니다 ( 권장되지 않음 ).

2 개의 큰 저장소를 병합하고 그중 하나를 하위 디렉토리에 넣는 예 : https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

여기에서 더 자세히 알아 보십시오 .


0

내가 원했던 hfs의 대답과 유사

  • 명시적인 병합없이 선형 히스토리를 유지하고
  • 이 병합 된 저장소의 파일처럼 보이게 항상 하위 디렉토리에 존재하고, 부작용 메이크로했다 git log -- file없이 작업 --follow.

그러나 나는 더 현대적인 것을 선택했습니다 filter-repo( new저장소가 존재하고 체크 아웃 되었다고 가정 ).

git clone git@host/repo/old.git
cd old
git checkout -b tmp_subdir
git filter-repo --to-subdirectory-filter old

cd ../new
git remote add old ../old
git fetch old
git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-date-is-author-date

충돌을 수정 (수동으로)하거나 버전으로 --merge -s recursive -X theirs해결하려는 경우 포함하도록 rebase 명령을 변경해야 할 수 있습니다 theirs.

git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-
date-is-author-date --merge -s recursive -X theirs

분리 된 HEAD에 도달하게되므로 새 브랜치를 생성하고이를 주 메모에 병합합니다. 현대 저장소는 "마스터"브랜치가 아니라 "기본"브랜치를 사용해야합니다.

branch for a more inclusive language.
git checkout -b old_merge
git checkout main
git merge old_merge

대청소

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