하위 디렉토리를 별도의 Git 저장소로 분리 (이동)


1758

여러 하위 디렉토리가 포함 된 Git 저장소가 있습니다. 이제 하위 디렉토리 중 하나가 다른 디렉토리와 관련이 없으며 별도의 저장소로 분리해야한다는 것을 알았습니다.

서브 디렉토리 내에 파일 히스토리를 유지하면서 어떻게해야합니까?

복제본을 만들고 각 복제본의 원치 않는 부분을 제거 할 수 있다고 생각하지만 이전 버전 등을 확인할 때 완전한 트리를 제공한다고 가정합니다. 두 저장소에는 공유 기록이 없습니다.

명확히하기 위해 다음과 같은 구조가 있습니다.

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

그러나 나는 이것을 대신하고 싶습니다 :

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

7
이것은 지금 사소한 일이며 git filter-branch아래 답변 을 참조하십시오.
jeremyjjbrown

8
@jeremyjjbrown이 맞습니다. 이 작업은 더 이상 어렵지 않지만 이전의 모든 답변이 결과를 지배하므로 Google에서 정답을 찾기가 어렵습니다.
Agnel Kurian

답변:


1228

업데이트 :이 프로세스는 너무 일반적이므로 자식 팀이 새로운 도구로 훨씬 간단 해졌습니다 git subtree. 하위 디렉토리를 별도의 Git 저장소로 분리 (이동) 하십시오.


리포지토리를 복제 한 다음 새 리포지토리 git filter-branch에서 원하는 하위 디렉토리를 제외한 모든 것을 가비지 수집하도록 표시하려고합니다.

  1. 로컬 저장소를 복제하려면 다음을 수행하십시오.

    git clone /XYZ /ABC
    

    (참고 : 리포지토리는 하드 링크를 사용하여 복제되지만 하드 링크 된 파일은 자체적으로 수정되지 않으므로 새로운 파일이 생성되므로 문제가되지 않습니다.)

  2. 이제 다시 작성하고 싶은 흥미로운 브랜치를 보존 한 다음, 원점을 제거하여 원점에서 이전 커밋을 참조하지 않도록 원점을 제거하십시오.

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    또는 모든 원격 지점 :

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. 이제 하위 프로젝트와 관련이없는 태그를 제거 할 수도 있습니다. 나중에 다시 할 수도 있지만 리포지를 다시 정리해야 할 수도 있습니다. 나는 그렇게하지 않았고 WARNING: Ref 'refs/tags/v0.1' is unchanged모든 태그에 대해 (하위 프로젝트와 관련이 없기 때문에) 태그를 얻었습니다 . 또한 이러한 태그를 제거한 후 더 많은 공간이 회수됩니다. 분명히 git filter-branch다른 태그를 다시 쓸 수 있어야하지만 이것을 확인할 수는 없습니다. 모든 태그를 제거하려면을 사용하십시오 git tag -l | xargs git tag -d.

  4. 그런 다음 filter-branch를 사용하고 다른 파일을 제외하도록 재설정하여 정리할 수 있도록하십시오. --tag-name-filter cat --prune-empty빈 커밋을 제거하고 태그를 다시 작성하기 위해 추가해 보겠습니다 (서명을 제거해야 함).

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    또는 HEAD 분기 만 다시 작성하고 태그 및 기타 분기를 무시하려면 :

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. 그런 다음 백업 참조 로그를 삭제하여 공간을 실제로 회수 할 수 있습니다 (현재 작업이 파괴적 임)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    이제 모든 히스토리가 보존 된 ABC 서브 디렉토리의 로컬 자식 저장소가 있습니다.

참고 : 대부분의 git filter-branch경우 실제로 추가 된 매개 변수가 있어야합니다 -- --all. 그렇습니다 --space-- all. 이것은 명령의 마지막 매개 변수 여야합니다. Matli가 발견 한 것처럼 프로젝트 리포지토리 및 태그는 새 리포지토리에 포함됩니다.

편집 : 아래 주석의 다양한 제안은 예를 들어 저장소가 실제로 축소되어 있는지 확인하기 위해 통합되었습니다 (항상 이전의 경우는 아님).


29
아주 좋은 대답입니다. 감사! 그리고 내가 원하는 것을 정확히 얻기 ​​위해 filter-branch 명령에 "---all"을 추가했습니다.
matli December

12
왜 필요한 --no-hardlinks가요? 하나의 하드 링크를 제거해도 다른 파일에는 영향을 미치지 않습니다. 힘내 개체도 변경할 수 없습니다. 소유자 / 파일 권한을 변경 한 경우에만 필요합니다 --no-hardlinks.
vdboor 2019

67
내가 권장하는 추가 단계는 "git remote rm origin"입니다. 내가 실수하지 않으면 이것은 원래 저장소로 돌아가는 것을 막을 것입니다.
Tom

13
추가 할 또 다른 명령 filter-branch--prune-empty현재 비어있는 커밋을 제거하는 것입니다.
세스 존슨

8
Paul과 마찬가지로 새 저장소에 프로젝트 태그를 원하지 않았으므로을 사용하지 않았습니다 -- --all. 또한 실행 git remote rm origin하고, git tag -l | xargs git tag -d전과 git filter-branch명령. 이것은 내 .git디렉토리를 60M에서 ~ 300K로 줄였습니다. 크기를 줄이려면이 두 명령을 모두 실행해야합니다.
saltycrane

1321

쉬운 길 ™

이것은 Git의 대군 주가 실제로 쉽게 만들었던 일반적이고 유용한 관행이라는 것이 밝혀졌지만 Git의 최신 버전이 있어야합니다 (> = 1.7.11 May 2012). 최신 Git을 설치하는 방법 은 부록 을 참조하십시오 . 또한 아래 연습 에서 실제 예가 있습니다.

  1. 오래된 레포를 준비하십시오

    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    

    참고 : <name-of-folder> 선행 또는 후행 문자를 포함하지 않아야합니다. 예를 들어, 이름이 지정된 폴더는 NOT subproject으로 전달되어야합니다.subproject./subproject/

    Windows 사용자를위한 참고 사항 : 폴더 깊이가 1보다 <name-of-folder>크면 * nix 스타일 폴더 구분 기호 (/)가 있어야합니다. 예를 들어, 이름이 지정된 폴더 path1\path2\subproject는 다음과 같이 전달되어야합니다.path1/path2/subproject

  2. 새로운 저장소를 만듭니다

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. 새 저장소를 GitHub 또는 어디에서나 연결

    git remote add origin <git@github.com:user/new-repo.git>
    git push -u origin master
    
  4. 정리 내측 <big-repo>, 하면 원하는

    git rm -rf <name-of-folder>
    

    참고 : 이렇게하면 모든 기록 참조가 리포지토리에 남습니다. 실제로 암호를 커밋 한 것이 우려되거나 폴더 의 파일 크기를 줄여야하는 경우 아래 부록을 참조하십시오 .git.

...

연습

이것들은 위와 같은 단계 이지만, 저장소 대신에 나의 정확한 단계를 따르는 것입니다 <meta-named-things>.

다음은 노드에서 JavaScript 브라우저 모듈을 구현하기 위해 가지고있는 프로젝트입니다.

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

단일 폴더를 btoa별도의 Git 리포지토리로 분할하고 싶습니다.

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

이제 btoa-only커밋 만 있고 btoa새 리포지토리를 만들고 싶은 새 분기 가 있습니다.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

다음으로 GitHub 또는 Bitbucket 또는 새 항목에 새 저장소를 만들고 origin

git remote add origin git@github.com:node-browser-compat/btoa.git
git push -u origin master

행복한 날!

참고 : 당신이있는 REPO을 만든 경우 README.md, .gitignore그리고 LICENSE먼저 당겨야합니다 :

git pull origin master
git push origin master

마지막으로 더 큰 저장소에서 폴더를 제거하고 싶습니다.

git rm -rf btoa

...

부록

macOS의 최신 Git

Homebrew를 사용하여 최신 버전의 Git을 얻으려면 :

brew install git

우분투의 최신 힘내

sudo apt-get update
sudo apt-get install git
git --version

그래도 작동하지 않으면 (오래된 버전의 우분투가 있음)

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

그래도 작동하지 않으면 시도하십시오

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

의견에서 rui.araujo에게 감사합니다.

역사 지우기

기본적으로 Git에서 파일을 제거해도 실제로 제거되지는 않으며 더 이상 존재하지 않는다고 커밋합니다. 기록 참조를 실제로 제거하려면 (예 : 비밀번호를 커밋 한 경우) 다음을 수행해야합니다.

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

그 후 파일이나 폴더가 더 이상 Git 히스토리에 나타나지 않는지 확인할 수 있습니다

git log -- <name-of-folder> # should show nothing

그러나 GitHub 등에 삭제를 "밀어 넣을"수는 없습니다 . 당신이 시도하면 오류가 발생하고 당신이 git pull할 수 있기 전에해야 할 것입니다 git push-그리고 당신은 다시 역사에 모든 것을 가지고 돌아갑니다.

따라서 "origin"에서 히스토리를 삭제하려면 GitHub, Bitbucket 등에서 히스토리를 삭제해야합니다. 리포지토리를 삭제하고 정리 된 리포지토리를 다시 푸시해야합니다. 그러나 기다리십시오- 더 있습니다 ! -비밀번호를 제거하는 데 관심이있는 경우 백업을 정리해야합니다 (아래 참조).

.git작게 만들기

위에서 언급 한 delete history 명령은 여전히 ​​많은 백업 파일을 남겨두고 있습니다 .Git은 실수로 저장소를 망치지 않도록 도와주는 데 너무 친절하기 때문입니다. 결국 며칠과 몇 달 동안 고아 파일을 삭제하지만 실수로 원하지 않는 것을 삭제했다는 사실을 알기 위해 잠시 동안 그대로 둡니다.

따라서 리포지토리 의 복제 크기줄이기 위해 휴지통비우 려면 즉시이 이상한 작업을 모두 수행해야합니다.

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

즉, 잘못된 하위 디렉토리를 정리 한 경우를 대비하여이 단계를 수행하지 않는 것이 좋습니다. 리포지토리를 푸시 할 때 백업 파일이 복제되어서는 안되며 로컬 복사본에만 있습니다.

신용


16
git subtree여전히 'contrib'폴더의 일부이며 모든 배포판에 기본적으로 설치되지 않습니다. github.com/git/git/blob/master/contrib/subtree
onionjake 14

11
@krlmlr sudo chmod + x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Ubuntu 13.04에서 활성화하려면
rui.araujo

41
공개 저장소로 비밀번호를 푸시 한 경우 공개 리포지토리에서 비밀번호를 제거하지 말고 아무도 보지 않기를 바랍니다.
Miles Rout

8
이 솔루션은 기록을 유지하지 않습니다.
Cœur

18
popdpushd명령 메이크업이 오히려 암시하고 어렵게 할 예정 무엇 grok 수에 ...
jones77

133

Paul의 답변 은 / ABC를 포함하는 새 저장소를 작성하지만 / XYZ에서 / ABC를 제거하지는 않습니다. 다음 명령은 / XYZ 내에서 / ABC를 제거합니다.

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

물론 'clone --no-hardlinks'리포지토리에서 먼저 테스트 한 다음 Paul이 나열한 reset, gc 및 prune 명령을 따릅니다.


53
그을 git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEAD그리고있을 것입니다 훨씬 더 빨리. 인덱스 필터는 인덱스에서 작동하지만 트리 필터는 모든 커밋마다 모든 것을 체크 아웃하고 스테이징 해야 합니다.
fmarc

51
어떤 경우에는 저장소 XYZ의 역사를 망쳐 놓는 것은 과잉입니다 ... 단순한 "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC into its repo '"는 대부분의 사람들에게 더 잘 작동합니다.
Evgeny

2
예를 들어, 두 디렉토리가 분리 된 후 제거하기 위해이 명령을 두 번 이상 수행하는 경우이 명령에 -f (force)를 사용하려고합니다. 그렇지 않으면 "새 백업을 만들 수 없습니다."라는 메시지가 나타납니다.
Brian Carlton

4
--index-filter메소드를 수행하는 git rm -q -r -f경우을 호출하여 각 호출이 삭제하는 각 파일에 대한 행을 인쇄하지 않도록 할 수도 있습니다 .
Eric Naeseth

1
Paul의 답변이 너무 철저하기 때문에 Paul의 답변을 편집하는 것이 좋습니다.
Erik Aronesty

96

새 리포지토리에서 이전 기록을 올바르게 삭제하려면 filter-branch단계 후에 약간 더 많은 작업을 수행해야한다는 것을 알았습니다 .

  1. 클론과 필터를 수행하십시오.

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. 이전 기록에 대한 모든 참조를 제거하십시오. "원산지"는 클론을 추적하고 있으며 "원본"은 필터 브랜치가 오래된 것들을 저장하는 곳입니다.

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. 지금도, 당신의 역사는 fsck가 건드리지 않을 팩 파일에 갇혀있을 수 있습니다. 파쇄로 찢어서 새 팩 파일을 작성하고 사용하지 않는 객체를 삭제하십시오.

    git repack -ad
    

이에 대한 설명 에서 필터 지점에 대한 설명서 .


3
어딘가 생각 git gc --aggressive --prune=now이 여전히 빠져 있다고 생각 하지 않습니까?
Albert

1
@Albert repack 명령은 그것을 처리하고 느슨한 객체는 없습니다.
Josh Lee

네, git gc --aggressive --prune=now새로운 레포를 많이 줄였습니다
Tomek Wyderka

간단하고 우아합니다. 감사!
Marco Pelegrini

40

편집 : Bash 스크립트가 추가되었습니다.

여기에 주어진 대답은 나에게 부분적으로 효과적이었습니다. 많은 큰 파일이 캐시에 남아있었습니다. 마지막으로 작동 한 것 (프리 노드에서 #git에서 몇 시간 후) :

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

이전 솔루션에서 리포지토리 크기는 약 100MB였습니다. 이것은 1.7MB로 줄었습니다. 어쩌면 누군가를 도울 수 있습니다 :)


다음 bash 스크립트는 작업을 자동화합니다.

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

26

이것은 더 이상 복잡하지 않으므로 repo 복제본 에서 git filter-branch 명령을 사용하여 원하지 않는 하위 디렉토리를 컬링 한 다음 새 리모콘으로 푸시하면됩니다.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

3
이것은 매력처럼 작동했습니다. 위의 예에서 YOUR_SUBDIR은 유지하려는 하위 디렉토리입니다. 다른 모든 항목은 제거됩니다.
JT Taylor

1
댓글을 기반으로 업데이트합니다.
jeremyjjbrown

2
이것은 질문에 대답하지 않습니다. 문서에서 말하고 The result will contain that directory (and only that) as its project root.실제로 이것이 얻을 것입니다. 즉 원래 프로젝트 구조가 유지되지 않습니다.
NicBright

2
@NicBright 질문에서와 같이 XYZ 및 ABC의 문제를 설명하여 무엇이 잘못되었는지 보여줄 수 있습니까?
Adam

@jeremyjjbrown는 복제 된 REPO를 재사용하고 새로운 REPO 여기, 즉 내 질문을 사용하지 할 수 stackoverflow.com/questions/49269602/...
Qiulang

19

업데이트 : git-subtree 모듈은 너무 유용하여 git 팀이 코어로 가져 와서 만들었습니다 git subtree. 하위 디렉토리를 별도의 Git 저장소로 분리 (이동) 하십시오.

git-subtree가 이것에 유용 할 수 있습니다

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (더 이상 사용되지 않음)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/


1
git-subtree는 이제 conitb 트리에 있지만 Git의 일부이므로 항상 기본적으로 설치되는 것은 아닙니다. Homebrew git 공식에 의해 설치되었지만 man 페이지는 설치되어 있지 않습니다. 따라서 apenwarr는 그의 버전을 쓸모없는 것으로 부릅니다.
echristopherson

19

다음은 여러 하위 폴더 (말하자 및 )를 새로운 자식 저장소로 분할하기 위해 CoolAJ86"The Easy Way ™"답변 을 약간 수정 한 것 입니다.sub1sub2

Easy Way ™ (여러 하위 폴더)

  1. 오래된 레포를 준비하십시오

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    참고 : <name-of-folder> 선행 또는 후행 문자를 포함하지 않아야합니다. 예를 들어, 이름이 지정된 폴더는 NOT subproject으로 전달되어야합니다.subproject./subproject/

    Windows 사용자의 경우 : 폴더 깊이가 1보다 <name-of-folder>크면 * nix 스타일 폴더 구분 기호 (/)가 있어야합니다. 예를 들어, 이름이 지정된 폴더는 path1\path2\subproject로 전달되어야합니다 path1/path2/subproject. 또한 mvcommand를 사용하지 말고을 (를) 사용하십시오 move.

    최종 메모 : 기본 답변과의 독특하고 큰 차이점은 스크립트 " git filter-branch..." 의 두 번째 줄입니다

  2. 새로운 저장소를 만듭니다

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. 새 저장소를 Github 또는 어디에서나 연결

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. 원하는 경우 정리

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    참고 : 이것은 모든 기록 참조를 저장소에 남겨 둡니다. 실제로 암호를 커밋하거나 폴더 의 파일 크기를 줄여야하는 경우 원래 답변 의 부록 을 참조 하십시오 .git.


1
이것은 약간의 수정으로 나를 위해 일했습니다. 내 sub1sub2폴더가 초기 버전에 존재하지 않았기 때문에 --tree-filter다음과 같이 스크립트 를 수정해야했습니다 "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". 두 번째 filter-branch명령 에서는 <sub1>을 <sub2>로 바꾸고 <폴더 이름> 생성을 생략 했으며 기존 백업의 경고를 무시한 -f후에 포함했습니다 filter-branch.
pglezen

git의 히스토리 중에 하위 디렉토리가 변경된 경우에는 작동하지 않습니다. 이 문제를 어떻게 해결할 수 있습니까?
nietras

@nietras는 rogerdpack의 답변을 참조하십시오. 이 다른 답변의 모든 정보를 읽고 흡수 한 후 그것을 찾기 위해 잠시 시간을 보냈습니다.
Adam

12

원래 질문은 XYZ / ABC / (* files)가 ABC / ABC / (* files)가되기를 원합니다. 내 코드에 허용되는 답변을 구현 한 후에 실제로 XYZ / ABC / (* files)가 ABC / (* files)로 변경되는 것을 알았습니다. 필터 브랜치 매뉴얼 페이지에도

결과는 프로젝트 루트로 해당 디렉토리 (및 그 디렉토리 만 포함)를 포함합니다 . "

즉, 최상위 폴더를 "업"한 수준으로 승격시킵니다. 예를 들어 내 역사에서 최상위 폴더의 이름을 바꿨 기 때문에 중요한 차이점입니다. 폴더를 한 수준으로 "위로"올리면 git은 이름을 바꾼 커밋에서 연속성을 잃습니다.

필터 브랜치 후 연속성을 잃었습니다.

질문에 대한 나의 대답은 저장소의 사본을 2 개 만들고 각각에 보관하려는 폴더를 수동으로 삭제하는 것입니다. 매뉴얼 페이지는 다음과 같이 백업합니다.

[...] 간단한 단일 커밋으로 문제를 해결하기에 충분하다면 [이 명령]을 사용하지 마십시오.


1
나는 그 그래프의 스타일을 좋아한다. 어떤 도구를 사용하고 있는지 물어봐도 될까요?
Slipp D. Thompson

3
Mac 용 타워. 난 정말 좋아. Mac 자체로 전환하는 것이 거의 가치가 있습니다.
MM.

2
그러나 필자의 경우 하위 폴더의 이름 이 어느 시점에서 바뀌어 하루 targetdir만에 이름바뀌기git filter-branch 전에 이름을 바꾸기 전에 이루어진 모든 커밋이 삭제되었습니다! Git이 이러한 것들을 추적하고 개별 콘텐츠 청크의 마이그레이션을 얼마나 잘 수행하고 있는지 고려하면 충격적입니다!
Jay Allen

1
또한, 누군가가 같은 보트에서 자신을 발견하면 여기에 내가 사용한 명령이 있습니다. git rm여러 인수 가 필요 하다는 것을 잊지 마십시오 . 따라서 각 파일 / 폴더에 대해 실행할 이유가 없습니다. BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen

7

Paul의 답변에 추가하기 위해 궁극적으로 공간을 복구하려면 HEAD를 깨끗한 저장소로 푸시해야하며 .git / objects / pack 디렉토리의 크기가 줄어 듭니다.

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init-베어

gc 정리 후 다음을 수행하십시오.

$ git push ... ABC.git HEAD

그럼 넌 할 수있어

$ git clone ... ABC.git

ABC / .git의 크기가 줄어 듭니다.

실제로, 시간이 많이 걸리는 단계 (예 : git gc)는 리포지토리를 정리할 때 필요하지 않습니다.

$ git clone-하드 링크 없음 / XYZ / ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD

6

이제 올바른 방법은 다음과 같습니다.

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub는 이제 그러한 경우에 관한 작은 기사 도 가지고 있습니다.

그러나 원본 repo를 복제하여 디렉토리를 먼저 분리하십시오 (모든 파일과 다른 디렉토리를 삭제하고 작업해야 할 가능성이 있기 때문에).

따라서 알고리즘은 다음과 같아야합니다.

  1. 원격 저장소를 다른 디렉토리에 복제
  2. git filter-branch일부 서브 디렉토리에서 왼쪽 파일 만 사용하여 새 원격으로 푸시
  3. 원래 원격 저장소에서이 서브 디렉토리를 제거하기위한 확약 작성

6

여기에있는 대부분의 대답은 어떤 형태 git filter-branch --subdirectory-filter와 그 어음 에 의존하는 것으로 보입니다 . 예를 들어 폴더 이름을 바꾼 경우와 같이 일부 경우에는 "대부분"작동 할 수 있습니다. 예 :

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

일반적인 git 필터 스타일을 사용하여 "move_me_renamed"를 추출하면 처음 move_this_dir ( ref ) 일 때 발생했던 파일 변경 기록이 손실됩니다 .

따라서 실제로 모든 변경 내역을 유지하는 유일한 방법 은 본질적으로 저장소를 복사하고 (새 저장소를 작성하고 원본으로 설정) 다른 모든 것을 압축하는 것입니다. 다음과 같이 하위 디렉토리의 이름을 상위 디렉토리로 바꿉니다.

  1. 다중 모듈 프로젝트를 로컬로 복제
  2. 지점-무엇이 있는지 확인하십시오. git branch -a
  3. 워크 스테이션에서 로컬 사본을 가져 오려면 분할에 포함 할 각 분기에 체크 아웃하십시오. git checkout --track origin/branchABC
  4. 새 디렉토리에 사본을 작성하십시오. cp -r oldmultimod simple
  5. 새 프로젝트 사본으로 이동하십시오. cd simple
  6. 이 프로젝트에 필요하지 않은 다른 모듈을 제거하십시오.
  7. git rm otherModule1 other2 other3
  8. 이제 대상 모듈의 하위 디렉토리 만 남아 있습니다.
  9. 모듈 루트가 새 프로젝트 루트가되도록 모듈 하위 디렉토리를 제거하십시오.
  10. git mv moduleSubdir1/* .
  11. 유물 하위 디렉토리를 삭제하십시오. rmdir moduleSubdir1
  12. 언제든지 변경 사항을 확인하십시오. git status
  13. 새로운 자식 저장소를 만들고 URL을 복사 하여이 프로젝트를 가리 키십시오.
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. 이것이 좋은지 확인하십시오. git remote -v
  16. 변경 사항을 원격 저장소로 푸시하십시오. git push
  17. 원격 저장소로 가서 모든 것이 있는지 확인하십시오.
  18. 필요한 다른 지점에 대해서도 반복하십시오. git checkout branch2

이것은 github 문서 "서브 폴더를 새로운 저장소로 분할" 6-11 단계에 따라 모듈을 새로운 저장소 로 푸시합니다.

이렇게하면 .git 폴더의 공간이 절약되지 않지만 이름을 바꿔도 해당 파일의 모든 변경 기록이 유지됩니다. 그리고 "많은"기록이 잃어버린 것 등이 없다면 이것은 가치가 없을 수 있습니다. 그러나 적어도 당신은 오래된 커밋을 잃지 않도록 보장됩니다!


1
자식 건초 더미에서 바늘을 찾았습니다! 이제 커밋 기록을 모두 유지할 수 있습니다 .
Adam

5

하위 폴더를 새로운 리포지토리로 분할하는 GitHub의 가이드를 권장 합니다 . 단계는 Paul의 답변 과 비슷 하지만 지침을 이해하기가 더 쉽다는 것을 알았습니다.

GitHub에서 호스팅되는 것이 아니라 로컬 리포지토리에 적용되도록 지침을 수정했습니다.


하위 폴더를 새 저장소로 분할

  1. Git Bash를 엽니 다.

  2. 현재 작업 디렉토리를 새 저장소를 작성하려는 위치로 변경하십시오.

  3. 하위 폴더가 포함 된 저장소를 복제하십시오.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. 현재 작업 디렉토리를 복제 된 저장소로 변경하십시오.

cd REPOSITORY-NAME
  1. 저장소의 나머지 파일에서 서브 폴더를 필터링하려면 git filter-branch다음 정보를 제공하여를 실행 하십시오.
    • FOLDER-NAME: 프로젝트 내에서 별도의 리포지토리를 만들려는 폴더입니다.
      • 팁 : Windows 사용자는 /폴더를 구분 하는 데 사용해야 합니다.
    • BRANCH-NAME: 현재 프로젝트의 기본 분기 (예 : master또는) gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten

멋진 게시물이지만 링크 된 문서의 첫 번째 단락은 If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.여기에있는 모든 답변에 대한 의견에 따라 언급 filter-branch했지만 subtree스크립트는 하위 디렉토리의 이름이 바뀔 때마다 기록이 손실됩니다. 이 문제를 해결하기 위해 수행 할 수있는 작업이 있습니까?
Adam

이전 디렉토리 이름 바꾸기 / 이동을 포함하여 모든 커밋을 보존하는 솔루션을 찾았습니다. 바로이 질문에 대한 rogerdpack의 답변입니다.
Adam

유일한 문제는 복제 된 저장소를 더 이상 사용할 수 없다는 것입니다.
Qiulang

5

( 아마?) git filter-branch최신 버전을 사용하여 실행할 때이 새로운 도구 git-filter-repo 를 사용한다고 말합니다 . 이 도구는 확실히 나를 위해 단순화했습니다.git2.22+

filter-repo로 필터링

XYZ원래 질문에서 repo 를 작성하는 명령 :

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

가정 : * 원격 XYZ 리포지토리는 푸시 이전에 새롭고 비어있었습니다.

필터링 및 이동

필자의 경우에는 좀 더 일관된 구조를 위해 몇 개의 디렉토리를 옮기고 싶었습니다. 처음에는 간단한 filter-repo명령 다음에을 실행 git mv dir-to-rename했지만 --path-rename옵션을 사용하면 약간 더 나은 기록을 얻을 수 있습니다. 5 hours ago새 저장소에서 이동 된 파일에 대한 마지막 수정을 보는 대신 이제 last year원래 저장소의 수정 된 시간과 일치하는 GitHub UI에 표시됩니다.

대신에...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

나는 결국 달렸다 ...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
노트:
  • 나는 Git Rev News 블로그 포스트 가 또 다른 repo-filtering 툴을 만드는 이유를 잘 설명 했다고 생각했다 .
  • 처음에는 원래 리포지토리에서 대상 리포지토리 이름과 일치하는 하위 디렉토리를 만든 다음 필터링 (사용 git filter-repo --subdirectory-filter dir-matching-new-repo-name) 경로를 시도했습니다 . 이 명령은 해당 서브 디렉토리를 복사 된 로컬 저장소의 루트로 올바르게 변환했지만 서브 디렉토리 작성에 소요 된 세 가지 커미트의 히스토리도 생성했습니다. (나는 그것을 --path여러 번 지정할 수 있다는 것을 몰랐기 때문에 소스 리포지토리에 서브 디렉토리를 생성 할 필요성을 배제했다.) 역사, 나는 방금 명령 git reset commit-before-subdir-move --hard후 사용 하고 약간 수정 된 로컬 클론에서 작동 하도록 명령에 clone추가 --force했습니다 filter-repo.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • 로 확장 패턴을 알지 못했기 때문에 설치가 중단 git되었지만 궁극적으로 git-filter-repo를 복제 하고 심볼릭 링크했습니다 $(git --exec-path).
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

1
새로운 추천에 대한 Upvoted filter-repo(I가 지난 달 발표하는 도구를 stackoverflow.com/a/58251653/6309 )
VonC

git-filter-repo이 시점에서 사용 이 확실히 선호되는 방법이어야합니다. 보다 훨씬 빠르고 안전하며 git-filter-branchgit history를 다시 작성할 때 발생할 수있는 많은 문제로부터 보호합니다. 이 답변이 해결되어야 할 것이기 때문에이 답변이 더 많은 관심을 끌기를 바랍니다 git-filter-repo.
Jeremy Caney

4

나는 정확히이 문제가 있었지만 git filter-branch를 기반으로 한 모든 표준 솔루션은 매우 느 렸습니다. 작은 저장소가 있다면 이것이 문제가되지 않을 수 있습니다. libgit2를 기반으로 한 다른 git 필터링 프로그램을 작성했습니다.이 단계는 첫 번째 단계로 기본 리포지토리의 각 필터링에 대한 분기를 만들고 다음 단계로 리포지토리를 정리합니다. 내 저장소 (500Mb 100000 커밋)에서 표준 git filter-branch 메소드는 며칠이 걸렸습니다. 내 프로그램은 동일한 필터링을 수행하는 데 몇 분이 걸립니다.

git_filter라는 멋진 이름이 있으며 여기에 있습니다.

https://github.com/slobobaby/git_filter

GitHub에서.

누군가에게 도움이되기를 바랍니다.


4

이 필터 명령을 사용하여 태그와 분기를 유지하면서 하위 디렉토리를 제거하십시오.

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all

여기 고양이가 뭐야?
rogerdpack

4

가치있는 것을 위해, 여기 Windows 시스템에서 GitHub를 사용하는 방법이 있습니다. 에 거주하는 복제 된 저장소가 있다고 가정 해 봅시다 C:\dir1. 디렉토리 구조는 다음과 같습니다 C:\dir1\dir2\dir3. dir3디렉토리는 내가 새로운 별도의 repo로 지정할 하나입니다.

깃 허브 :

  1. 새 저장소를 작성하십시오. MyTeam/mynewrepo

배쉬 프롬프트 :

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    리턴 : Ref 'refs/heads/master' was rewritten(fyi : dir2 / dir3은 대소 문자를 구분합니다.)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc. 작동하지 않고 " remote origin already exists"을 (를 ) 반환했습니다.

  4. $ git push --progress some_name master


3

내가 따라 위에서 언급 한 , 나는 (내 만지는 모든 커밋을 삭제 반대 솔루션을 사용했다 dir/subdir/targetdir(필요에 따라) 커밋의 95 %에 대해 제거 꽤 잘 작동하는 것 같았다). 그러나 두 가지 작은 문제가 남아 있습니다.

FIRST는 , filter-branch소개하거나 수정 코드를하지만 분명히, 커밋 제거 작업까지 강타했다 병합 커밋은 Gitiverse에서의 역 아래됩니다.

이것은 아마도 함께 살 수있는 미용 문제입니다 (눈을 피하면서 천천히 뒤로 물러납니다) .

두 번째로 남아있는 커밋은 거의 모두 복제되었습니다! 프로젝트의 전체 역사에 걸친 두 번째 중복 타임 라인을 얻은 것 같습니다. 흥미로운 것은 (아래 그림에서 볼 수 있듯이) 내 세 개의 로컬 브랜치가 모두 동일한 타임 라인에 있지 않다는 것입니다.

내가 상상할 수있는 유일한 것은 삭제 된 커밋 중 하나가 filter-branch 실제로 delete했던 단일 병합 커밋 일 것입니다. 각 병합되지 않은 스트랜드가 자체 커밋 사본을 가져 가면서 병렬 타임 라인을 만들었습니다. ( 어깨를 으쓱는 어디에 타 디스입니까?) 나는 거라고하지만 확실히 내가이 문제를 해결할 수 있습니다 꽤있어 정말 그것이 어떻게 일어 났는지 이해하기 좋아합니다.

미친 병합 페스티벌 -O-RAMA의 경우, 커밋 기록에 확고하게 자리 잡았 기 때문에 (내가 올 때마다 나를 괴롭힘) 실제로 그 원인이 아닌 것 같습니다. 화장품이 아닌 문제는 Tower.app에서 꽤 예쁘기 때문에.


3

더 쉬운 방법

  1. 설치하십시오 git splits. jkeating의 솔루션을 기반으로 git 확장으로 만들었습니다 .
  2. 디렉토리를 로컬 브랜치로 분할 #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2

  3. 어딘가에 빈 저장소를 만듭니다. xyz경로가있는 GitHub에 빈 저장소를 만들었다 고 가정 합니다.git@github.com:simpliwp/xyz.git

  4. 새 저장소로 푸시하십시오. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. 새로 작성된 원격 저장소를 새 로컬 디렉토리에 복제하십시오.
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


"쉬운 방법"과 비교하여이 방법의 장점은 원격이 이미 새로운 저장소에 대해 설정되어 있으므로 하위 트리 추가를 즉시 수행 할 수 있다는 것입니다. 사실이 방법은 나에게 더 쉬운 것 같습니다 (없는 경우에도 git splits)
MM

이 솔루션을 게시 한 AndrewD에게 제안합니다. 나는 OSX (작업하게 자신의 repo를 포크 한 github.com/ricardoespsanto/git-splits 즉 다른 사람에게 유용 경우)
ricardoespsanto

2

가비지 수집 전에 실제로 파일을 정리하려면 "git reflog expire --expire = now --all"과 같은 것이 필요할 수 있습니다. git filter-branch는 히스토리에서 참조를 제거하지만 데이터를 보유하는 reflog 항목은 제거하지 않습니다. 물론 이것을 먼저 테스트하십시오.

초기 조건이 약간 다르지만 디스크 사용량이 크게 줄었습니다. 아마도 --subdirectory-filter는 이러한 요구를 무효화하지만 의심합니다.


2

https://github.com/vangorra/git_split 에서 git_split 프로젝트를 확인 하십시오.

자식 디렉토리를 자신의 위치에있는 자체 저장소로 바꿉니다. 하위 트리 재미있는 사업이 없습니다. 이 스크립트는 git 저장소의 기존 디렉토리를 가져 와서 해당 디렉토리를 자체의 독립 저장소로 바꿉니다. 그 과정에서 제공 한 디렉토리에 대한 전체 변경 내역을 복사합니다.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.

1

이것을 gitconfig에 넣으십시오.

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

1

git 하위 트리가 모두 훌륭하고 훌륭하다고 확신하지만 이동하려는 git 관리 코드의 하위 디렉토리는 모두 일식입니다. 따라서 egit을 사용하는 경우 고통 스럽습니다. 이동하려는 프로젝트를 가져 와서 팀 연결을 끊은 다음 팀-> 새 위치로 공유하십시오. 기본적으로 이전 리포지토리 위치를 사용하려고하지만 사용중인 기존 선택을 선택 취소하고 새 위치를 선택하여 이동할 수 있습니다. 모든 우박 egit.


3
서브 트리의 "정확하고 훌륭한"부분은 서브 디렉토리의 히스토리가 따라 오는 것입니다. 역사가 필요하지 않으면 고통스럽고 쉬운 방법이 있습니다.
pglezen

0

https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/를 쉽게 시도 할 수 있습니다

이것은 나를 위해 일했습니다. 위에서 주어진 단계에서 직면 한 문제는 다음과 같습니다.

  1. 이 명령에 git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAMEBRANCH-NAME입니다 마스터

  2. 보호 문제로 인해 커밋 할 때 마지막 단계가 실패하면 https://docs.gitlab.com/ee/user/project/protected_branches.html을 따르십시오.


0

나는 매우 간단한 해결책을 찾았습니다. 아이디어는 저장소를 복사 한 다음 불필요한 부분을 제거하는 것입니다. 이것이 작동하는 방식입니다.

1) 분할하려는 저장소를 복제하십시오.

git clone git@git.thehost.io:testrepo/test.git

2) 자식 폴더로 이동

cd test/

2) 불필요한 폴더를 제거하고 커밋하십시오.

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) BFG로 불필요한 폴더 양식 기록 제거

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

폴더를 곱하면 쉼표를 사용할 수 있습니다

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) 기록에 방금 삭제 한 파일 / 폴더가 포함되어 있지 않은지 확인하십시오.

git log --diff-filter=D --summary | grep delete

5) 이제 ABC가없는 깨끗한 저장소가 있으므로 새로운 출처로 밀어 넣으십시오.

remote add origin git@github.com:username/new_repo
git push -u origin master

그게 다야. 다른 저장소를 얻기 위해 단계를 반복 할 수 있습니다.

XY1, XY2를 제거하고 3 단계에서 XYZ-> ABC로 이름을 바꿉니다.


거의 완벽하지만 ... 현재 비어있는 모든 오래된 커밋을 제거하기 위해 "git filter-branch --prune-empty"를 잊어 버렸습니다. 원점 마스터로 푸시하기 전에해야 할 일!
ZettaCircl

오래된 빈 커밋을 제거한 후에 실수를하고 "리 푸쉬"하고 싶다면 "git push -u origin master --force-with-lease"
ZettaCircl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.