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

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

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

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



답변

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

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


답변

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

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의 문서 에서 가져온 )


답변

하나의 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
    

답변

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

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

# 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 오류가 발생했습니다. 그래서 모든 패치를 하나씩 적용해야했습니다.


답변

디렉토리 이름 유지

하위 디렉토리 필터 (또는 더 짧은 명령 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


답변

내가 항상 사용하는 것은 여기 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


답변

이 답변은 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 '{.* => .*}'