파일 히스토리를 깨지 않고 두 개의 Git 리포지토리 병합


226

두 개의 Git 리포지토리를 새로운 세 번째 리포지토리로 병합해야합니다. 하위 트리 병합 (예 : 두 개의 Git 리포지토리를 병합하는 방법에 대한 Jakub Narębski의 답변) 을 사용 하여이 작업을 수행하는 방법에 대한 많은 설명을 찾았으며 하위 트리를 커밋 할 때 모든 파일을 병합한다는 점을 제외하고는 대부분 지침을 따릅니다. 이전 리포지토리에서 새 추가 파일로 기록됩니다. 내가 할 때 이전 리포지토리에서 커밋 기록을 볼 수 있지만 그렇게 하면 해당 파일에 대한 커밋이 하나만 표시됩니다-하위 트리 병합. 위의 답변에 대한 의견에서 판단 할 때, 나는이 문제를 보는 데 혼자가 아니지만 게시 된 해결책을 찾지 못했습니다.git loggit log <file>

리포지토리를 병합하고 개별 파일 기록을 그대로 두는 방법이 있습니까?


Git을 사용하지는 않지만 Mercurial에서는 먼저 병합 할 저장소의 파일 경로를 수정하기 위해 필요한 경우 변환을 수행 한 다음 변경 사항을 가져 오기 위해 하나의 저장소를 대상으로 강제 당기십시오. 다른 지점의 병합. 이것은 테스트되고 작동합니다.) 아마도 이것은 Git에 대한 솔루션을 찾는 데 도움이 될 것입니다 ... 하위 트리 병합 접근법과 비교할 때 경로를 매핑하는 대신 히스토리가 다시 작성된 위치에서 변환 단계가 다르다고 생각합니다 바르게). 그러면 파일 경로를 특수하게 처리하지 않고도 원활하게 병합 할 수 있습니다.
Lucero

나는 또한이 질문이 도움이
되었다는

후속 질문을 작성했습니다. 흥미로운 일이 될 수 있습니다 두 Git 저장소를 병합 마스터 이력 유지 : stackoverflow.com/questions/42161910/...
디미트리 Dewaele

나를 위해 일한 자동화 된 솔루션은 stackoverflow.com/a/30781527/239408
xverges '

답변:


269

두 리포지토리를 함께 붙이고 외부 종속성을 관리하는 것이 아니라 모든 방식으로 보이도록하면 대답이 훨씬 간단합니다. 원격 저장소를 기존 저장소에 추가하고 새 저장소에 병합하고 파일 및 폴더를 하위 디렉토리로 이동 한 다음 이동을 커밋하고 모든 추가 저장소에 대해 반복하면됩니다. 하위 모듈, 하위 트리 병합 및 고급 rebase는 약간 다른 문제를 해결하기 위해 만들어졌으며 내가하려는 일에 적합하지 않습니다.

다음은 두 개의 저장소를 서로 연결하는 Powershell 스크립트의 예입니다.

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

그 대신에 old_b를 old_a (새로운 결합 저장소가 됨)로 병합하는 대신 스크립트를 수정하십시오.

진행중인 기능 분기를 가져 오려면 다음을 사용하십시오.

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

그것은 프로세스의 유일한 명백한 부분입니다-그것은 하위 트리 병합이 아니라, 일반적인 재귀 병합에 대한 인수로, Git에게 대상의 이름을 변경했으며 Git이 모든 것을 올바르게 정렬하도록 도와줍니다.

여기에 좀 더 자세한 설명을 썼습니다 .


16
이 솔루션 git mv은 잘 작동하지 않습니다. 나중에 git log이동 된 파일 중 하나에서를 사용하면 이동에서 커밋 만받습니다. 모든 이전 기록이 손실됩니다. 때문입니다 git mv정말 git rm; git add하지만, 한 번에 .
mholm815

15
Git의 다른 이동 / 이름 바꾸기 작업과 동일합니다. 명령 줄에서을 수행하여 모든 기록을 얻을 수 git log --follow있거나 모든 GUI 도구가 자동으로 수행합니다. 하위 트리 병합을 사용하면 내가 아는 한 개별 파일에 대한 기록을 얻을 없으므로이 방법이 더 좋습니다.
Eric Lee

3
@EricLee old_b 저장소가 병합되면 병합 충돌이 많이 발생합니다. 기대 되나요? 충돌이 발생합니다 (이름 변경 / 삭제)
Jon

9
"dir -exclude old_a | % {git mv $ _. Name old_a}"를 시도하면 sh.exe ": dir : command not found 및 sh.exe": git : command not found를 얻습니다. 이 작품을 사용하여 : ls -I old_a | xargs -I '{}'git mv '{}'old_a /
George

5
이것은 1(1 번) ls이고 자본은 '눈'입니다 xargs. 이 팁에 감사드립니다!
Dominique Vial

149

기록을 다시 쓰지 않는 방법이 있으므로 모든 커밋 ID가 계속 유효합니다. 결과적으로 두 번째 리포지토리의 파일은 하위 디렉토리에있게됩니다.

  1. 두 번째 저장소를 원격으로 추가하십시오.

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. 두 번째 repo의 커밋을 모두 다운로드했는지 확인하십시오.

    git fetch secondrepo
    
  3. 두 번째 리포지토리에서 로컬 브랜치를 만듭니다.

    git branch branchfromsecondrepo secondrepo/master
    
  4. 모든 파일을 서브 디렉토리로 이동하십시오.

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. 두 번째 브랜치를 첫 번째 repo의 마스터 브랜치로 병합하십시오.

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

저장소에 루트 커밋이 두 개 이상 있지만 문제가되지는 않습니다.


1
2 단계는 나를 위해 작동하지 않습니다 : 치명적 : 유효한 개체 이름이 아닙니다 : 'secondrepo / master'.
Keith

@Keith : 두 번째 리포지토리를 "secondrepo"라는 원격으로 추가하고 해당 git remote show secondrepo
리포지토리에

가져 오기 위해 가져와야했습니다. 1과 2 사이에서 git fetch
secondrepo

@ monkjack : git fetch 단계를 포함하도록 답변을 편집했습니다. 나중에 답을 직접 편집하십시오.
Flimm

4
@MartijnHeemels 이전 버전의 Git에서는 생략하십시오 --allow-unrelated-histories. 이 답변 게시물의 내역을 참조하십시오.
Flimm

8

몇 년이 지났고 잘 기반을 둔 투표 솔루션이 있지만 이전 저장소의 기록을 삭제하지 않고 2 개의 원격 저장소를 새 저장소로 병합하고 싶기 때문에 조금 다릅니다 .

  1. Github에서 새 저장소를 만듭니다.

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

  2. 새로 작성된 저장소를 다운로드하고 이전 원격 저장소를 추가하십시오.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. 이전 리포지토리에서 모든 파일을 가져 와서 새 분기를 만듭니다.

    git fetch OldRepo
    git branch -a
    

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

  4. 마스터 브랜치에서 병합을 수행하여 이전 리포지토리를 새로 생성 된 리포지토리와 결합하십시오.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

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

  5. OldRepo에서 추가 된 모든 새로 작성된 컨텐츠를 저장하고 파일을이 새 폴더로 이동하려면 새 폴더를 작성하십시오.

  6. 마지막으로 결합 된 저장소에서 파일을 업로드하고 GitHub에서 OldRepo를 안전하게 삭제할 수 있습니다.

이것이 원격 저장소 병합을 다루는 모든 사람에게 유용 할 수 있기를 바랍니다.


1
이것은 git history를 보존하기 위해 일한 유일한 솔루션입니다. 으로 이전 리포지토리에 대한 원격 링크를 제거하는 것을 잊지 마십시오 git remote rm OldRepo.
Harubiyori

7

사용을 살펴보십시오

git rebase --root --preserve-merges --onto

삶의 초기에 두 역사를 연결합니다.

경로가 겹치는 경우 다음과 같이 수정하십시오.

git filter-branch --index-filter

로그를 사용할 때 다음을 사용하여 "복사본 찾기"를 확인하십시오.

git log -CC

그렇게하면 경로에서 파일의 움직임을 찾을 수 있습니다.


Git 문서는 리베이스를 권장하지 않습니다 ... git-scm.com/book/en/v2/Git-Branching-Rebasing#_rebase_peril
Stephen Turner

7

@Flimm 의 솔루션 을 다음 git alias과 같이 (내 추가 ~/.gitconfig)했습니다.

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"

12
궁금한 점이 있습니다. 별명이 필요할 정도로 자주이 작업을 수행합니까?
Parker Coates

1
아니요. 어떻게해야하는지 기억하지 못하므로 별명은이를 기억하는 방법입니다.
Fredrik Erlandsson

1
그래 ..하지만 컴퓨터를 바꾸고 별명을 옮기는 것을
잊어라

1
의 가치는 $GIT_PREFIX무엇입니까?
neowulf33

github.com/git/git/blob/… 'GIT_PREFIX'는 원래 현재 디렉토리에서 'git rev-parse --show-prefix'를 실행하여 반환 된 것으로 설정됩니다. linkgit : git-rev-parse [1]를 참조하십시오.
Fredrik Erlandsson

3

이 기능은 원격 저장소를 로컬 저장소로 복제합니다.

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

주의. 이 스크립트는 커밋을 다시 작성할 수 있지만 모든 작성자와 날짜를 저장합니다. 즉, 새로운 커밋에 다른 해시가 있으며, 변경 사항을 원격 서버로 푸시하려고하면 강제 키로 만 수행 할 수 있으며 서버에서 커밋도 다시 작성됩니다. 따라서 시작하기 전에 백업을 만드십시오.

이익!


bash 대신 zsh를 사용하고 git v2.13.0을 사용하고 있습니다. 내가 시도한 것이 무엇이든, 나는 git filter-branch --index-filter일을 할 수 없었습니다 . 일반적으로 .new 색인 파일이 존재하지 않는다는 오류 메시지가 나타납니다. 그 종소리가 울리나요?
패트릭 비어드

@PatrickBeard 나는 zsh를 모른다, 당신은 파일의 끝에이 git-add-repo.sh줄을 넣어 위의 기능으로 분리 된 파일 을 만들 수 있습니다 git-add-repo "$@". 당신이 zsh을 등으로 사용할 수있는 후 cd current/git/packagebash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
안드레이 Izman

문제는 여기에서 논의되었습니다 : stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" 때때로 실패하기 때문에을 추가해야합니다 if test.
패트릭 비어드

1
나는이 방법을 사용하지 않을 것이다! 나는 순진하고 간결하게 스크립트를 시도했으며 (나는 그 부분에 대해서만 나 자신을 비난 할 수있다), 그것은 내 로컬 git repo를 방해했다. 역사는 대부분 옳아 보이지만 Github에 git push를 되돌려 놓으면 "RPC 실패; curl 55 SSL_write ()에서 SYSCALL, errno = 32"오류가 발생했습니다. 수리하려고했지만 돌이킬 수 없었습니다. 나는 새로운 지역 저장소에서 물건을 재구성해야했습니다.
Mason 석방

그것은 기존의 repo에 푸시 할 수 있도록이 스크립트는 모두의 repos의 혼합으로 새로운 자식 역사를 만들어 @MasonFreed, 그것은 서버에 REPO를 다시 작성 수단 힘 키를 새 또는 푸시를 만들 필요
안드레이 Izman

2

두 git 기록을 병합하여 하나의 git history를 갖는 한 repo를 다른 repo에 포함시키는 단계를 따르십시오.

  1. 병합하려는 리포지토리를 모두 복제하십시오.

자식 클론 git@github.com : user / parent-repo.git

자식 클론 git@github.com : user / child-repo.git

  1. 아동 저장소로 이동

CD 아동 레포 /

  1. 아래 명령을 실행하고 경로 my/new/subdir(3 발생)를 하위 저장소를 원하는 디렉토리 구조로 바꾸십시오 .

git filter-branch --prune-empty --tree-filter 'if [! -e my / new / subdir]; mkdir -p my / new / subdir git ls-tree-이름 만 $ GIT_COMMIT | xargs -I 파일 mv 파일 my / new / subdir fi '

  1. 부모 저장소로 이동

cd ../parent-repo/

  1. 하위 리포지토리에 대한 경로를 가리키는 상위 리포지토리에 원격 추가

자식 원격 추가 자식 원격 ../child-repo/

  1. 아이 레포를 가져옵니다

자식 원격 가져 오기 자식

  1. 역사를 병합

git merge-관련이없는 아이의 원격 / 마스터를 허용

부모 리포지토리에서 git 로그를 확인하면 자식 리포지토리가 병합되어야합니다. 커밋 소스에서 나타내는 태그를 볼 수도 있습니다.

아래 기사는 두 리포지토리를 병합하여 하나의 단일 리포지토리를 갖는 한 리포지를 다른 리포에 포함시키는 데 도움이되었습니다.

http://ericlathrop.com/2014/01/combining-git-repositories/

도움이 되었기를 바랍니다. 행복한 코딩!


구문 오류로 3 단계가 실패했습니다. 세미콜론이 없습니다. 수정git filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L

1

당신이 저장소를 병합 할 말 ab(나는 그들이 서로 옆에 위치하고있어 있으리라 믿고있어) :

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

a서브 디렉토리 에 넣으려면 위 명령 전에 다음을 수행하십시오.

cd a
git filter-repo --to-subdirectory-filter a
cd ..

당신이 필요이를 위해 git-filter-repo설치 ( filter-branch되어 낙담 ).

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

여기 에 더 있습니다 .

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