많은 하위 디렉토리를 별도의 새로운 Git 저장소로 분리


135

이 질문은 별도의 Git 저장소로 분리 하위 디렉토리를 기반으로 합니다.

하나의 하위 디렉토리를 분리하는 대신 부부를 분리하고 싶습니다. 예를 들어, 현재 디렉토리 트리는 다음과 같습니다.

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ

그리고 대신 이것을 원합니다 :

/apps
  /AAA
/libs
  /XXX

--subdirectory-filter에 인수 git filter-branch가 지정된 디렉토리를 제외하고 처음으로 그것의 실행을 모든 것을 제거를 얻을 수 있기 때문에 작동하지 않습니다. --index-filter모든 원치 않는 파일에 인수를 사용하면 효과가 있지만 (지루하지만) 두 번 이상 실행하려고하면 다음 메시지가 나타납니다.

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

어떤 아이디어? 티아

답변:


155

서브 쉘을 다루고 ext glob (kynan이 제안한대로)을 사용하는 대신이 훨씬 더 간단한 방법을 시도하십시오.

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

에서 언급 한 바와 같이 void.pointer 그 / 그녀의 의견 이 제외하고 모든 것을 제거 apps/AAA하고 libs/XXX현재의 저장소에서.

빈 병합 커밋 제거

빈 병합이 많이 남습니다. 이것들은 raphinesse 가 그의 대답 에서 설명한 것처럼 다른 패스로 제거 할 수 있습니다 .

git filter-branch --prune-empty --parent-filter \
'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

⚠️ 경고 : 위의 GNU 버전을 사용해야 sed하며 xargs그렇지 않으면 모든 커밋이 xargs실패 로 제거됩니다 . brew install gnu-sed findutils다음 사용 gsedgxargs:

git filter-branch --prune-empty --parent-filter \
'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' 

4
또한, --ignore-UNMATCH 플래그가 자식 RM에 전달해야 매우 첫 번째 (저장소가 내 경우에는 자식 svn의 복제로 만들어진), 그렇지 않으면 나를 위해 커밋 실패
Pontomedon

8
믹스에 태그가 있다고 가정하면 --tag-name-filter cat매개 변수를 추가 해야합니다.
Yonatan

16
이 긴 명령이 수행하는 작업을 설명하는 정보를 더 추가 할 수 있습니까?
Burhan Ali

4
나는 이것이 git bash를 사용하여 Windows에서 완벽하게 작동한다는 것을 기쁘게 생각합니다.
Dai

3
@BurhanAli 기록의 모든 커밋마다 보관하려는 파일을 제외한 모든 파일이 삭제됩니다. 모든 것이 끝나면 지정된 트리의 일부와 해당 기록 만 남게됩니다.
void.pointer

39

간단한 자식 명령으로 수동 단계

계획은 개별 디렉토리를 자체 저장소로 분할 한 다음 함께 병합하는 것입니다. 다음 수동 단계는 사용하기 쉬운 스크립트가 아니라 이해하기 쉬운 명령을 사용했으며 추가 N 하위 폴더를 다른 단일 저장소로 병합하는 데 도움이 될 수 있습니다.

나누기

원래 저장소는 다음과 같습니다. original_repo

1-분할 앱 :

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2-분할 라이브러리

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

폴더가 두 개 이상인 경우 계속하십시오. 이제 두 개의 새로운 임시 git 저장소가 생깁니다.

앱과 라이브러리를 병합하여 정복

3-새로운 저장소를 준비하십시오.

mkdir my-desired-repo
cd my-desired-repo
git init

그리고 최소한 한 번의 커밋이 필요합니다. 다음 세 줄을 건너 뛰면 첫 번째 리포지토리가 리포지토리의 루트 바로 아래에 나타납니다.

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

임시 파일이 커밋되면 merge이후 섹션의 명령이 예상대로 중지됩니다.

와 같은 임의의 파일을 추가하는 대신 사용자의 의견 a_file_and_make_a_commit을 받아 .gitignore, README.md등 을 추가하도록 선택할 수 있습니다 .

4-먼저 앱 저장소 병합 :

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

이제 새로운 저장소 안에 apps 디렉토리 가 보일 것 입니다. git log모든 관련 기록 커밋 메시지를 표시해야합니다.

참고 : 크리스는 자식의 최신 버전 (> = 2.9)에 대한 의견, 아래 언급 한 바와 같이, 당신은 지정해야합니다 --allow-unrelated-histories으로git merge

5-같은 방법으로 libs repo를 다음에 병합하십시오.

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

병합 할 저장소가 두 개 이상인 경우 계속하십시오.

참조 : git을 사용하여 다른 저장소의 하위 디렉토리 병합


4
git 2.9부터는 merge 명령에 --allow-unrelated-histories를 사용해야합니다. 그렇지 않으면 이것은 나를 위해 잘 작동하는 것으로 보입니다.
Chris

1
천재! 정말 감사합니다. 초대형 리포지토리에서 트리 필터를 사용하여 살펴본 초기 답변은 git 다시 쓰기를 완료하는 데 26 시간 이상이 걸릴 것으로 예상했습니다. 이 간단하지만 반복 가능한 접근 방식으로 훨씬 더 행복하고 모든 커밋 기록이있는 4 개의 하위 폴더를 새 저장소로 성공적으로 이동했습니다.
shuttsy 2016 년

1
추가 .gitignore하고 README.md파일 을 추가하는 "초기 커밋"에 첫 번째 커밋을 사용할 수 있습니다 .
잭 밀러

2
불행히도이 방법은 git merge .. git read-tree새로 추가 된 파일로 기록하고 모든 내 자식 GUI가 이전 커밋에 연결하지 않기 때문에 단계 에서 추가 된 파일에 대한 추적 기록을 깨뜨리는 것처럼 보입니다 .
Dai

1
@ksadjad, 모르겠다, 정직하다. 수동 병합의 중심은 새 저장소를 형성 할 디렉토리를 선택하고 커밋 내역을 유지하는 것입니다. 커밋 기록 파일을 dirA, dirB, dirDrop에 넣고 dirA 및 dirB 만 새 저장소에 선택하는 상황을 처리하는 방법을 잘 모르겠습니다. 커밋 기록은 원래 파일과 어떻게 관련되어야합니까?
chfw

27

filter-branch두 번 이상 달리기를 원 하십니까? 한 번의 스위프로 모든 작업을 수행 할 수 있으므로 강제로 실행할 필요가 없습니다 ( extglob이 작업을 수행하려면 셸에서 활성화 해야 합니다).

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

이것은 불필요한 서브 디렉토리의 모든 변경 사항을 제거하고 모든 브랜치와 커밋을 유지해야합니다 (이것으로 인해 프 루닝 된 서브 디렉토리의 파일에만 영향을 미치지 않는 한 --prune-empty)-중복 커밋 등에 문제가 없습니다.

이 작업이 끝나면 원하지 않는 디렉토리가에 의해 추적되지 않은 것으로 표시됩니다 git status.

$(ls ...)(가) 필요한 일입니다 extglob용도 쉘 대신 인덱스 필터에 의해 평가 sh내장을 eval(여기서 extglob사용할 수 없습니다). git에서 쉘 옵션을 어떻게 활성화합니까?를 참조하십시오 . 그것에 대한 자세한 내용은.


1
재미있는 생각. 비슷한 문제가 있지만 작동하지 못했습니다. stackoverflow.com/questions/8050687/…
manol

이것은 내 저장소에 파일과 폴더를 모두 뿌리고 있었지만 거의 필요한 것입니다 ... 감사합니다 :)
notlesh

1
흠. extglob을 켜도 괄호 근처에 오류가 발생합니다. 예기치 않은 토큰 근처의 구문 오류`( '내 명령은 다음과 같습니다. -ignore-unmatch src / css / themes /! (some_theme *) "--prune-empty---src / css / themes /! (some_theme *)가있는 모든 ls는 extglob이 나타나는 다른 모든 테마를 반환합니다. 작동 ...
robdodson

2
@MikeGraf 나는 그것이 원하는 결과를 줄 것이라고 생각하지 않는다 : 탈출은 문자 "!"와 일치 할 것이다. 당신의 길에서.
kynan

1
@ david-smiley의 (가장 최근의) 답변은 매우 유사한 접근법을 사용하지만 git명령 에만 의존한다는 이점이 있으므로 ls@Bae가 발견 한 것처럼 운영 체제에서 해석 되는 방식의 차이에 영향을받지 않습니다 .
Jeremy Caney '28

20

많은 시행 착오 끝에 여기에 내 자신의 질문에 대답하십시오.

나는 이것이의 조합을 사용하여 수행하는 관리 git subtreegit-stitch-repo. 이 지침은 다음을 기반으로합니다.

먼저, 별도의 저장소에 보관하고 싶은 디렉토리를 뽑았습니다.

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

그런 다음 빈 저장소를 새로 만들고 마지막 두 저장소를 가져 오거나 스티치했습니다.

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

이 두 가지를 생성 master-A하고 master-B각각 스티치의 repos 중 하나의 내용을 유지. 그것들을 결합하고 정리하려면 :

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

지금은 아주 확실하지 않다 방법 / 이런 때, 그러나 후 첫 번째 checkoutpull마스터 지점에, 코드가 마술 병합 (감사합니다 여기에서 무슨 일에 어떤 통찰력!)

모든 것이 예상대로 내가 통해 보면 것을 제외하고, 일 것으로 보인다 newRepo역사를 커밋 변경 집합이 모두 영향을받는 경우, 중복있다 apps/AAA하고 libs/XXX. 중복을 제거하는 방법이 있다면 완벽 할 것입니다.


여기에서 찾은 깔끔한 도구. "체크 아웃"에 대한 통찰 : "git pull"은 "git fetch && git merge"와 동일합니다. "로컬을 가져 오는 중"이므로 "페치"부분은 무해합니다. 따라서이 체크 아웃 명령은 "git merge master-B"와 동일하며 약간 더 자명합니다.
22.37 at

1
불행히도 git-stitch-repo 도구는 요즘 나쁜 종속성으로 인해 손상되었습니다.
Henrik

@Henrik 정확히 어떤 문제가 발생 했습니까? export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/"Git.pm을 찾을 수 있도록 bash 구성 에 추가 해야 했지만 그것은 나를 위해 작동합니다. 그런 다음 cpan으로 설치했습니다.

git subtree add이 작업을 수행하는 데 사용할 수 있습니다.
laconbass

7

이 문제를 정확히 해결하기 위해 git 필터를 작성했습니다. git_filter라는 환상적인 이름을 가지고 있으며 github에 있습니다.

https://github.com/slobobaby/git_filter

그것은 훌륭한 libgit2를 기반으로합니다.

많은 커밋 (~ 100000)으로 큰 저장소를 분할해야했으며 git filter-branch 기반 솔루션을 실행하는 데 며칠이 걸렸습니다. git_filter는 동일한 작업을 수행하는 데 1 분이 걸립니다.


7

'git splits'git extension 사용

git splitsjkeating의 솔루션을git branch-filter 기반으로 git 확장으로 만든 래퍼 인 bash 스크립트입니다 .

이 상황을 위해 정확하게 만들어졌습니다. 오류가 발생하면 git splits -f옵션을 사용 하여 백업을 강제로 제거하십시오. git splits새 지점에서 작동 하기 때문에 현재 지점을 다시 쓰지 않으므로 백업이 필요하지 않습니다. 자세한 내용은 추가 정보를 참조하고 리포지토리 의 사본 / 복제본에서 사용해야합니다 (만약을 대비하여) .

  1. 설치하십시오 git splits.
  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 apps/AAA libs/ZZZ

  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


분할에 파일을 추가하고 나중에 업데이트 할 수없는 것 같습니다.
Alex

이것은 많은 커밋으로 내 레포에서 느리게 실행되는 것처럼 보입니다.
Shinta Smith

git-split은 --subdirectory-filter에 비해 git --index filter 를 사용하는 것으로 보입니다 . 일부 저장소의 경우 여전히 실행 가능한 옵션이지만 큰 저장소 (여러 기가 바이트, 6 자리 커밋)의 경우 인덱스 필터는 전용 클라우드 하드웨어에서도 효과적으로 실행하는 데 몇 주가 걸립니다.
Jostein Kjønigsen

6
git clone git@example.com:thing.git
cd thing
git fetch
for originBranch in `git branch -r | grep -v master`; do
    branch=${originBranch:7:${#originBranch}}
    git checkout $branch
done
git checkout master

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

git remote set-url origin git@example.com:newthing.git
git push --all

다른 모든 의견을 읽으면 올바른 길을 찾았습니다. 그러나 솔루션은 효과가 있습니다. 모든 브랜치를 가져오고 여러 디렉토리와 함께 작동합니다! 큰!
jschober

1
for다른 유사한 답변을 포함하지 않기 때문에 루프는 인정 가치가있다. 복제본에 각 분기의 로컬 복사본이없는 경우 filter-branch다시 쓰기의 일부로 해당 분기를 설명하지 않으므로 다른 분기에 도입 된 파일은 제외 할 수 있지만 현재 분기와 병합되지는 않을 수 있습니다. ( git fetch이전에도 체크 아웃 한 지점에서 최신 상태를 유지하는 것도 좋습니다.)
Jeremy Caney

5

쉬운 해결책 : git-filter-repo

비슷한 문제가 있었고 여기에 나열된 다양한 접근 방식을 검토 한 후 git-filter-repo를 발견했습니다 . 공식 git documentation here 의 git-filter-branch에 대한 대안으로 권장됩니다 .

기존 저장소의 디렉토리 서브 세트에서 새 저장소를 작성하려면 다음 명령을 사용할 수 있습니다.

git filter-repo --path <file_to_remove>

여러 파일 / 폴더를 연결하여 필터링합니다.

git filter-repo --path keepthisfile --path keepthisfolder/

따라서 원래 질문 인 git-filter-repo를 사용하려면 다음 명령이 필요합니다.

git filter-repo --path apps/AAA/ --path libs/XXX/

이것은 확실히 좋은 대답입니다. 다른 모든 솔루션의 문제는 디렉토리의 모든 분기의 내용을 추출 할 수 없다는 것입니다. 그러나 git filter-repo는 모든 지점에서 폴더를 검색하고 필요하지 않은 모든 것의 전체 트리를 청소하는 것처럼 기록을 완벽하게 다시 작성했습니다.
Teodoro

3

네. -f후속 호출 에서 플래그를 사용하여 filter-branch해당 경고를 무시 하여 백업을 강제로 덮어 씁니다 . :) 그렇지 않으면 해결책이 있다고 생각합니다 (즉, 원하지 않는 디렉토리를 한 번에 삭제 filter-branch).


-4

메시지가 제안하는 것처럼 refs / original의 .git 디렉토리 아래에있는 백업을 삭제하십시오. 디렉토리가 숨겨져 있습니다.

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