역사를 보존하면서 하나의 git repo에서 다른 복제본으로 복제하는 방법


483

Git 리포지토리는 개별 프로젝트마다 각각 자체 트리가있는 단일 몬스터 SVN 리포지토리의 일부로 시작되었습니다.

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

분명히을 사용하여 파일을 한 파일에서 다른 파일로 쉽게 이동할 수있었습니다 svn mv. 그러나 Git에서 각 프로젝트는 자체 저장소에 있으며 오늘은 하위 디렉토리를에서 project2로 이동하라는 요청을 받았습니다 project1. 나는 이런 식으로했다 :

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

그러나 그것은 꽤 복잡해 보입니다. 이런 종류의 일을 일반적으로 수행하는 더 좋은 방법이 있습니까? 아니면 올바른 접근 방식을 채택 했습니까?

여기에는 이전 질문 에서처럼 다른 저장소의 일부에서 새 독립형 저장소를 작성하는 대신 히스토리를 기존 저장소에 병합하는 것이 포함됩니다 .


1
그것은 나에게 합리적인 접근처럼 들린다; 나는 당신의 방법을 크게 향상시키는 확실한 방법을 생각할 수 없습니다. Git이 실제로 이것을 쉽게 만들어주는 것이 좋습니다 ( 예를 들어 Subversion의 다른 저장소 간에 파일 디렉토리를 옮기고 싶지는 않습니다 ).
Greg Hewgill

1
@ebneter-쉘 스크립트를 사용하여 수동 으로이 작업을 수행했습니다 (한 svn 저장소에서 다른 저장소로 이동). 기본적으로 특정 파일 / 디렉토리의 기록 (diffs, commit logs 메시지)을 두 번째 저장소로 재생했습니다.
Adam Monsen

1
왜 당신이 git fetch p2 && git merge p2대신 하지 않는가 git fetch p2 && git branch .. && git merge p2? 편집 : 좋아, 현재 분기가 아닌 p2라는 새 분기에서 변경 사항을 가져 오는 것처럼 보입니다.
Lekensteyn

1
--filter-branch가 디렉토리 구조를 파괴하는 것을 막을 방법이 없습니까? "git mv"단계는 파일 삭제 및 파일 작성으로 가득 찬 대량 커밋을 발생시킵니다.
Edward Falk

1
git 2.9에서 관련되지 않은 이력 병합은 기본적으로 허용되지 않습니다. 작동하게하려면 --allow-unrelated-histories마지막 git merge에 추가 하여 작동 시키십시오 .
Scott Berrevoets 2016 년

답변:


55

네의에 타격 --subdirectory-filter의 것이 filter-branch핵심이었다. 당신이 그것을 사용했다는 사실은 본질적으로 더 쉬운 방법이 없음을 증명합니다-당신은 파일의 (이름이 바뀐) 서브셋으로 끝나기를 원했기 때문에 기록을 다시 쓰는 것 외에는 선택의 여지가 없었으며, 이것은 정의에 의해 해시를 변경합니다. 표준 명령 (예 pull:)은 기록을 다시 쓰지 않으므로이를 수행하는 데 사용할 수있는 방법은 없습니다.

물론 세부 사항을 세분화 할 수 있습니다-일부 복제 및 분기는 엄격하게 필요하지는 않았지만 전반적인 접근 방식은 좋습니다! 복잡하다는 것은 부끄러운 일이지만 물론 자식의 요점은 역사를 쉽게 다시 쓰지 못하게하는 것이 아닙니다.


1
파일이 여러 디렉토리를 통해 이동하여 하나의 디렉토리에있는 경우 하위 디렉토리 필터가 계속 작동합니까? (즉, 하나의 파일 만 이동하려는 경우 파일을 자체 서브 디렉토리로 옮길 수 있으며 이것이 작동 할 것이라고 가정합니다.)
rogerdpack

1
@ rogerdpack : 아니오, 이것은 이름을 변경해도 파일을 따르지 않습니다. 선택한 하위 디렉토리로 이동 한 시점에 작성된 것으로 보입니다. 하나의 파일 만 선택 --index-filter하려면 filter-branch맨 페이지를 살펴보십시오 .
Cascabel

8
이름 변경을 따르는 방법에 대한 레시피가 있습니까?
Night Warrier

역사를 유지하고 관리하는 것이 git의 주요 포인트 중 하나라고 생각합니다.
artburkart

287

히스토리가 정상이면 커밋을 패치로 가져 와서 새 저장소에 적용 할 수 있습니다.

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

또는 한 줄로

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

( Exherbo의 문서 에서 가져온 )


21
세 개 또는 네 개의 파일을 이동하는 데 필요한 대답보다 훨씬 간단한 해결책이었습니다. 패치 파일의 경로를 find-replace로 트리밍하여 새 리포지토리의 디렉토리 구조에 맞췄습니다.
리안 샌더슨

8
이미지와 같은 이진 파일도 올바르게 마이그레이션되도록 옵션을 추가했습니다 git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. AFAICT 문제없이 작동합니다.
Emmanuel Touzery

35
적용 단계 --committer-date-is-author-date에서 파일을 이동 한 날짜 대신 원래 커밋 날짜를 유지 하는 옵션을 사용했습니다 .
darrenmc

6
히스토리에서 병합 커밋은 "am"명령을 중단합니다. 위의 git log 명령에 "-m --first-parent"를 추가하면 효과가 있습니다.
Gábor Lipták 8

6
@Daniel 골든 나는 (버그의 결과입니다 이동 된 파일의 문제를 해결하기 위해 관리했습니다 git log가 모두 일을하지 않도록하는 것이, --follow그리고 --reverse제대로). 나는 이 답변을 사용 했고 여기에 파일을 옮기기 위해 사용
tsayen

75

하나의 Git 리포지토리에서 다른 Git 리포지토리로 파일 또는 폴더를 이동하기 위해 다양한 접근 방식을 시도했지만 안정적으로 작동하는 유일한 방법은 다음과 같습니다.

여기에는 파일 또는 폴더를 이동하려는 저장소 복제, 파일 또는 폴더를 루트로 이동, Git 히스토리 재 작성, 대상 저장소 복제 및 히스토리가있는 파일 또는 폴더를이 대상 저장소로 직접 가져 오기가 포함됩니다.

1 단계

  1. 다음 단계에 따라 저장소 A의 사본을 작성하여이 사본을 크게 변경하지 마십시오.

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. 그것에 CD

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. 실수로 원격 변경을하지 않도록 원래 저장소에 대한 링크를 삭제하십시오 (예 : 푸시)

    git remote rm origin
    
  4. 디렉토리 1에없는 것을 제거하여 히스토리와 파일을 살펴보십시오. 결과는 디렉토리 1의 컨텐츠가 저장소 A의 기본으로 분출됩니다.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. 단일 파일 이동의 경우 : 남은 작업을 수행하고 원하는 파일을 제외한 모든 항목을 제거하십시오. (같은 이름으로 원하지 않는 파일을 삭제하고 커밋해야 할 수도 있습니다.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

2 단계

  1. 정리 단계

    git reset --hard
    
  2. 정리 단계

    git gc --aggressive
    
  3. 정리 단계

    git prune
    

루트가 아닌 디렉토리 내에서이 파일을 저장소 B로 가져올 수 있습니다.

  1. 그 디렉토리를 만드십시오

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. 해당 디렉토리로 파일 이동

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. 해당 디렉토리에 파일 추가

    git add .
    
  4. 변경 사항을 커밋하면 이러한 파일을 새 리포지토리에 병합 할 준비가되었습니다.

    git commit
    

3 단계

  1. 저장소 B가 없으면 사본 B를 작성하십시오.

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (FOLDER_TO_KEEP가 복사중인 새 저장소의 이름이라고 가정)

  2. 그것에 CD

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. 저장소 A의 저장소로 저장소 A에 대한 원격 연결 작성

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. 이 분기 (이동하려는 디렉토리 만 포함)에서 저장소 B로 가져 오십시오.

    git pull repo-A-branch master --allow-unrelated-histories
    

    풀은 파일과 기록을 모두 복사합니다. 참고 : 당기기 대신 병합을 사용할 수 있지만 당기기가 더 좋습니다.

  5. 마지막으로 저장소 A에 대한 원격 연결을 제거하여 비트를 정리하고 싶을 것입니다.

    git remote rm repo-A-branch
    
  6. 푸시하고 모두 설정되었습니다.

    git push
    

여기에 설명 된 대부분의 단계를 거쳤지만 파일 또는 디렉토리의 커밋 기록 만 마스터 (다른 지점이 아닌)에서 복사 한 것 같습니다. 맞습니까?
Bao-Long Nguyen-Trong

나는 그것이 맞고 파일이나 폴더를 이동하려는 모든 분기에 대해 비슷한 단계를 거쳐야한다고 생각합니다. 지점으로 전환 예. 저장소 A의 MyBranch, 필터 분기 등. 그런 다음 저장소 B의 "Repo-A-branch MyBranch를 git pull repo-A-branch"합니다.
mcarans

답장을 보내 주셔서 감사합니다. 지점의 태그도 마이그레이션되는지 알고 있습니까?
바오 롱 응 우옌-트롱

잘 모르겠습니다.하지만 그럴 것이라고 생각합니다.
mcarans

1
@mcarans 불행히도, 이것은 신뢰할 수있는 방법은 아니지만 그렇게 보입니다. 다른 모든 솔루션과 동일한 문제가 발생합니다. 이름을 바꾼 과거 기록은 유지하지 않습니다. 필자의 경우 첫 번째 커밋은 디렉토리 / 파일의 이름을 바꿀 때입니다. 그 이상의 모든 것이 사라집니다.
xZero

20

나는 이것이 매우 유용하다는 것을 알았다 . 새 리포지토리에 적용되는 패치를 만드는 매우 간단한 방법입니다. 자세한 내용은 링크 된 페이지를 참조하십시오.

블로그에서 복사 한 세 단계 만 포함합니다.

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

내가 가진 유일한 문제는 한 번에 모든 패치를 적용 할 수 없다는 것입니다.

git am --3way <patch-directory>/*.patch

Windows에서 InvalidArgument 오류가 발생했습니다. 그래서 모든 패치를 하나씩 적용해야했습니다.


어느 시점에서 sha-hash가 없어서 나를 위해 작동하지 않았습니다. 이것은 나를 도왔다 : stackoverflow.com/questions/17371150/…
dr0i

"git log"접근 방식과 달리이 옵션은 완벽하게 작동했습니다! 감사!
AlejandroVD

1
프로젝트를 새로운 리포지토리로 옮기기위한 다양한 접근법을 시도했습니다. 이것은 나를 위해 일한 유일한 것입니다. 이러한 일반적인 작업이 그렇게 복잡해야한다고 믿을 수 없습니다.
Chris_D_Turk

Ross Hendrickson의 블로그 를 공유해 주셔서 감사합니다 . 이 접근법은 나를 위해 일했습니다.
Kaushik Acharya

1
그러나 이것은 매우 우아한 솔루션이지만 다른 모든 솔루션과 동일한 문제로 인해 어려움을 겪습니다. 이름을 바꾼 과거 기록은 유지되지 않습니다.
xZero

6

디렉토리 이름 유지

하위 디렉토리 필터 (또는 더 짧은 명령 git 하위 트리)는 잘 작동하지만 커밋 정보에서 디렉토리 이름을 제거하므로 나에게 적합하지 않습니다. 내 시나리오에서는 한 저장소의 일부를 다른 저장소로 병합하고 전체 경로 이름으로 기록을 유지하려고합니다.

내 솔루션은 트리 필터를 사용하고 소스 리포지토리의 임시 복제본에서 원하지 않는 파일과 디렉토리를 간단히 제거한 다음 5 단계만으로 해당 복제본을 대상 리포지토리로 가져 오는 것입니다.

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

스크립트 는 원래 리포지토리를 수정하지 않습니다. 맵 파일에 지정된 대상 리포지토리가 존재하지 않으면이 스크립트는이를 생성하려고 시도합니다.
Chetabahana

1
또한 디렉토리 이름을 그대로 유지하는 것이 매우 중요하다고 생각합니다. 그렇지 않으면 대상 리포지토리에 대한 추가 이름 변경 커밋이 발생합니다.
ipuustin

6

내가 항상 사용하는 것은 여기 http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ 입니다. 간단하고 빠릅니다.

스택 오버 플로우 표준을 준수하기위한 절차는 다음과 같습니다.

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

5

이 답변은 git am예제를 기반으로 한 단계별로 흥미로운 명령을 제공합니다 .

객관적인

  • 한 저장소에서 다른 저장소로 일부 또는 모든 파일을 이동하려고합니다.
  • 당신은 그들의 역사를 유지하고 싶습니다.
  • 그러나 태그와 분기를 유지하는 것은 중요하지 않습니다.
  • 이름이 바뀐 파일 (및 이름이 바뀐 디렉토리의 파일)에 대한 제한된 기록을 허용합니다.

순서

  1. 를 사용하여 이메일 형식으로 내역 추출
    git log --pretty=email -p --reverse --full-index --binary
  2. 파일 트리 재구성 및 기록에서 파일 이름 변경 업데이트 [선택 사항]
  3. 다음을 사용하여 새 기록 적용 git am

1. 이메일 형식으로 기록을 추출

예 : 추출물의 역사 file3, file4그리고file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

임시 디렉토리 대상을 정리하십시오.

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

당신 되찾기 청소 소스를

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

이메일 형식으로 각 파일의 히스토리 추출

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

불행하게도 옵션 --follow또는 --find-copies-harder함께 사용할 수 없습니다 --reverse. 파일 이름이 변경 될 때 (또는 상위 디렉토리 이름이 변경 될 때) 히스토리가 잘리는 이유입니다.

이후 : 이메일 형식의 임시 히스토리

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2. 파일 트리를 재구성하고 기록에서 파일 이름 변경을 업데이트합니다 [선택 사항]

이 다른 리포지토리에서이 세 파일을 이동한다고 가정합니다 (동일한 리포지토리 일 수 있음).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

따라서 파일을 재구성하십시오.

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

귀하의 임시 이력은 다음과 같습니다.

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

기록 내에서 파일 이름도 변경하십시오.

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

참고 : 경로 및 파일 이름 변경을 반영하여 기록을 다시 작성합니다.
      (즉, 새로운 저장소 내에서 새로운 위치 / 이름 변경)


3. 새로운 역사를 적용

다른 저장소는 다음과 같습니다.

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

임시 히스토리 파일에서 커밋을 적용하십시오.

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

다른 레포는 지금 :

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

git status푸시 될 준비가 된 커밋의 양을 보는 데 사용 하십시오 :-)

참고 : 경로 및 파일 이름 변경을 반영하기 위해 기록이 다시 작성되었으므로
      (즉, 이전 리포지토리 내의 위치 / 이름과 비교)

  • 필요가 없습니다 git mv위치 / 파일 이름을 변경할 .
  • git log --follow전체 기록에 액세스 할 필요가 없습니다 .

추가 트릭 : repo 내에서 이름이 바뀌거나 이동 한 파일 감지

이름이 바뀐 파일을 나열하려면 다음을 수행하십시오.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

추가 사용자 정의 : git log옵션 --find-copies-harder또는을 사용 하여 명령 을 완료 할 수 있습니다 --reverse. cut -f3-완전한 패턴 '{. * =>. *}'를 사용 하고 grepping 하여 처음 두 열을 제거 할 수도 있습니다 .

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

비슷한 저장소가 긁 히기 시작 했으므로 (주어진 저장소의 일부 파일에만 해당)이 스크립트는 실제로 도움이되는 것으로 판명되었습니다 : git-import

짧은 버전은 $object기존 저장소에서 지정된 파일 또는 디렉토리 ( ) 의 패치 파일을 작성한다는 것 입니다.

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

그런 다음 새 저장소에 적용됩니다.

cd new_repo
git am "$temp"/*.patch 

자세한 내용은 다음을 참조하십시오.


2

이 시도

cd repo1

이렇게하면 언급 된 디렉토리를 제외한 모든 디렉토리가 제거되고 해당 디렉토리에 대해서만 히스토리가 보존됩니다.

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

이제 git remote에 새 저장소를 추가하고 밀어 넣을 수 있습니다

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

-f덮어 쓰기에 추가


경고 : git-filter-branch에는 엉망 기록을 다시 작성하는 많은 문제가 있습니다. 중단하기 전에 Ctrl-C를 누른 다음 대신 'git filter-repo'( github.com/newren/git-filter-repo ) 와 같은 대체 필터링 도구를 사용하십시오. 자세한 내용은 필터 브랜치 매뉴얼 페이지를 참조하십시오. 이 경고를 없애려면 FILTER_BRANCH_SQUELCH_WARNING = 1을 설정하십시오.
콜린

1

http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ 에서 영감을 얻어 동일한 작업을 수행하기 위해이 Powershell 기능을 만들었습니다. 지금까지 나를 위해 훌륭하게 일했습니다.

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

이 예제의 사용법 :

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

이 작업을 수행 한 후에는 migrate-from-project2병합하기 전에 지점 에서 파일을 재구성 할 수 있습니다 .


1

나는 강력하고 재사용 가능한 무언가를 원했기 때문에 (하나의 명령 및 이동 + 실행 취소 기능) 다음 bash 스크립트를 작성했습니다. 여러 번 나를 위해 일한 적이 있으므로 여기서 공유한다고 생각했습니다.

임의의 폴더를 이동 할 수 있습니다 /path/to/foo에서 repo1/some/other/folder/barrepo2 (루트 폴더로부터의 거리가 상이해도 동일하거나 상이 할 수있는 폴더 경로).

입력 폴더의 파일을 터치하는 커밋 (소스 리포지토리의 모든 커밋이 아닌) 만 수행하므로 모든 소스에서 접하지 않은 깊게 중첩 된 하위 폴더를 추출하면 큰 소스 리포지토리에서도 상당히 빨라야합니다 범하다.

이것이하는 것은 모든 이전 리포지토리와 함께 고아 분기를 만든 다음 HEAD에 병합하는 것이므로 파일 이름이 충돌하는 경우에도 작동합니다 (그러면 끝에서 병합을 해결해야합니다) .

파일 이름이 충돌하지 않으면 git commit 마지막에 병합을 완료 됩니다.

단점은 파일 이름 바꾸기를 따르지 않을 것입니다. REWRITE_FROM 소스 리포지토리에서 폴더 풀 요청은 GitHub에서 환영합니다.

GitHub 링크 : git-move-folder-between-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

제 경우에는 이전 기록을 마이그레이션하거나 이전 기록을 보존 할 필요가 없었습니다. 다른 리모콘에서 같은 지점의 패치를 사용했습니다.

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

이 두 단계에서 다른 리포지토리를 동일한 리포지토리에 표시 할 수있었습니다.

마지막 으로이 지점 (다른 리포지토리에서 가져온 지점)을 대상 리포지토리의 메인 라인을 따르도록 설정했습니다 (따라서 정확하게 비교할 수 있습니다)

git br --set-upstream-to=origin/mainline

이제는 동일한 리포지토리에 대해 푸시 한 다른 지점 인 것처럼 행동했습니다.


0

문제의 파일 경로가 두 repos에서 동일하고 하나의 파일 또는 작은 관련 파일 세트를 가져 오려는 경우이를 수행하는 쉬운 방법은을 사용하는 것 git cherry-pick입니다.

첫 번째 단계는 다른 리포지토리에서 커밋을 로컬 리포지토리로 가져 오는 것입니다. git fetch <remote-url> 입니다. 이것은 FETCH_HEAD다른 리포지토리의 헤드 커밋을 가리 킵니다. 다른 가져 오기를 수행 한 후에 해당 커밋에 대한 참조를 유지하려면 태그를 태그로 지정할 수 있습니다 git tag other-head FETCH_HEAD.

그런 다음 해당 파일에 대한 초기 커밋 (없는 경우) 또는 파일을 가져 오려는 다른 리포지토리의 첫 번째 커밋으로 패치 할 수있는 상태로 가져 오기위한 커밋을 만들어야합니다. git cherry-pick <commit-0>if로 이것을 할 수있다commit-0 원하는 파일이 소개 할 수 있거나 커밋을 직접 작성해야 할 수도 있습니다. -n초기 커밋을 수정해야하는 경우 Cherry-pick 옵션에 추가하십시오 ( 예 : 가져 오지 않으려는 커밋에서 파일 삭제).

그런 git cherry-pick다음 -n필요한 경우 다시 사용하여 후속 커밋을 계속할 수 있습니다 . 가장 간단한 경우 (모든 커밋은 정확히 원하는대로 깨끗하게 적용됩니다) cherry-pick 명령 줄에서 전체 커밋 목록을 제공 할 수 있습니다 git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

git-filter-repo를 사용하면 간단 해집니다.

로 이동 project2/sub/dir하려면 project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

도구를 간단하게 설치하려면 : pip3 install git-filter-repo ( README의 추가 세부 사항 및 옵션 )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

모든 지점을 유지하고 기록을 보존하여 GIT Stash를 GitLab으로 마이그레이션하는 아래 방법.

이전 저장소를 로컬로 복제하십시오.

git clone --bare <STASH-URL>

GitLab에 빈 저장소를 만듭니다.

git push --mirror <GitLab-URL>

위의 코드를 숨김에서 GitLab으로 마이그레이션했을 때 수행 한 결과 매우 효과적이었습니다.

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