แยกที่เก็บ Git ขนาดใหญ่ออกเป็นที่เก็บข้อมูลขนาดเล็กจำนวนมาก


86

หลังจากแปลงที่เก็บ SVN เป็น Git เรียบร้อยแล้วตอนนี้ฉันมีที่เก็บ Git ขนาดใหญ่มากที่ฉันต้องการแยกย่อยออกเป็นที่เก็บขนาดเล็กหลาย ๆ ที่เก็บและรักษาประวัติไว้

ดังนั้นใครสักคนสามารถช่วยแบ่ง repo ที่อาจมีลักษณะดังนี้:

MyHugeRepo/
   .git/
   DIR_A/
   DIR_B/
   DIR_1/
   DIR_2/

ออกเป็นสองที่เก็บที่มีลักษณะดังนี้:

MyABRepo/
   .git
   DIR_A/
   DIR_B/

My12Repo/
   .git
   DIR_1/
   DIR_2/

ฉันได้พยายามเส้นทางต่อไปนี้ในคำถามก่อนหน้านี้ แต่ก็ไม่ได้จริงๆพอดีเมื่อพยายามที่จะนำไดเรกทอรีหลายเป็น repo แยกต่างหาก ( แยก (ย้าย) ไดเรกทอรีย่อยลงพื้นที่เก็บข้อมูล Git แยกต่างหาก )


11
เมื่อคุณพอใจกับคำตอบโปรดทำเครื่องหมายว่ายอมรับ
Ben Fowler

1
สำหรับใครก็ตามที่ต้องการแยกไดเร็กทอรี (ซ้อนกัน) ออกเป็น repo ใหม่ (แทนที่จะต้องการลบหลายไดเร็กทอรีซึ่งอาจยากกว่าในบางโปรเจ็กต์) คำตอบนี้มีประโยชน์สำหรับฉัน: stackoverflow.com/a/19957874/164439
thaddeusmt

คำตอบ:


80

สิ่งนี้จะตั้งค่า MyABRepo; แน่นอนคุณสามารถทำ My12Repo ได้ในทำนองเดียวกัน

git clone MyHugeRepo/ MyABRepo.tmp/
cd MyABRepo.tmp
git filter-branch --prune-empty --index-filter 'git rm --cached --ignore-unmatch DIR_1/* DIR_2/*' HEAD 

การอ้างอิงถึง. git / refs / original / refs / head / master ยังคงอยู่ คุณสามารถลบสิ่งนั้นได้ด้วย:

cd ..
git clone MyABRepo.tmp MyABRepo

หากทุกอย่างเป็นไปด้วยดีคุณสามารถลบ MyABRepo.tmp


หากคุณได้รับข้อผิดพลาดเกี่ยวกับ. git-rewrite ด้วยเหตุผลบางประการคุณสามารถลองสิ่งนี้:

git clone MyHugeRepo/ MyABRepo.tmp/
cd MyABRepo.tmp
git filter-branch -d /tmp/git-rewrite.tmp --prune-empty --index-filter 'git rm --cached --ignore-unmatch DIR_1/* DIR_2/*' HEAD 
cd ..
git clone MyABRepo.tmp MyABRepo

นี้จะสร้างและใช้ /tmp/git-rewrite.tmp .git-rewriteเป็นไดเรกทอรีชั่วคราวแทน โดยปกติคุณสามารถแทนที่เส้นทางใด ๆ ที่คุณต้องการแทนได้/tmp/git-rewrite.tmpตราบเท่าที่คุณได้รับอนุญาตให้เขียนและไม่มีไดเร็กทอรีอยู่แล้ว


manpage 'git filter-branch' แนะนำให้สร้างโคลนใหม่ของพื้นที่เก็บข้อมูลที่เขียนซ้ำแทนขั้นตอนสุดท้ายที่กล่าวถึงข้างต้น
Jakub Narębski

ฉันลองสิ่งนี้และได้รับข้อผิดพลาดเมื่อพยายามลบโฟลเดอร์. git-rewrite ในตอนท้าย
MikeM

-d <path-on-another-physical-disk> ใช้ได้ผลสำหรับฉันและกำจัดความล้มเหลวของ 'mv' ที่ stange ภายใน --tree-filter
Vertigo

คุณมีความคิดที่จะรับคอมมิตครั้งแรกอย่างไรหากเกี่ยวข้องกับเส้นทางที่ยกเว้น (เช่นDIR_Aเป็นต้น)
bitmask

1
ฉันไม่ได้ตระหนักถึงการแตกแขนงทั้งหมดของfilter-branch. สำหรับผู้ที่ไม่ทราบระบบจะเขียนประวัติอีกครั้งดังนั้นหากคุณวางแผนที่จะผลักดัน repo หลังจากที่คุณทำเสร็จแล้วแฮชคอมมิตจะแตกต่างออกไปในตอนนี้และจะไม่ทำงาน
thaddeusmt

10

คุณสามารถใช้git filter-branch --index-filterกับgit rm --cachedการลบไดเรกทอรีที่ไม่พึงประสงค์จากโคลน / สำเนาของพื้นที่เก็บข้อมูลเดิมของคุณ

ตัวอย่างเช่น:

trim_repo() { : trim_repo src dst dir-to-trim-out...
  : uses printf %q: needs bash, zsh, or maybe ksh
  git clone "$1" "$2" &&
  (
    cd "$2" &&
    shift 2 &&

    : mirror original branches &&
    git checkout HEAD~0 2>/dev/null &&
    d=$(printf ' %q' "$@") &&
    git for-each-ref --shell --format='
      o=%(refname:short) b=${o#origin/} &&
      if test -n "$b" && test "$b" != HEAD; then 
        git branch --force --no-track "$b" "$o"
      fi
    ' refs/remotes/origin/ | sh -e &&
    git checkout - &&
    git remote rm origin &&

    : do the filtering &&
    git filter-branch \
      --index-filter 'git rm --ignore-unmatch --cached -r -- '"$d" \
      --tag-name-filter cat \
      --prune-empty \
      -- --all
  )
}
trim_repo MyHugeRepo MyABRepo DIR_1 DIR_2
trim_repo MyHugeRepo My12Repo DIR_A DIR_B

คุณจะต้องลบสาขาหรือแท็กที่ไม่จำเป็นของที่เก็บแต่ละรายการด้วยตนเอง (เช่นถ้าคุณมีfeature-x-for-AB branch คุณอาจต้องการลบออกจากที่เก็บ "12")


1
:ไม่ใช่ตัวแสดงความคิดเห็นใน bash คุณควรใช้#แทน
Daenyth

4
@Daenyth :เป็นคำสั่งในตัวแบบดั้งเดิม ( ระบุไว้ใน POSIX ) มันรวมอยู่ในbashแต่ไม่ใช่ความคิดเห็น ฉันใช้มันโดยเฉพาะตามความต้องการ#เพราะไม่ใช่ทุกเชลล์ที่ใช้#เป็นตัวแนะนำความคิดเห็นในทุกบริบท (เช่นzshแบบโต้ตอบโดยไม่เปิดใช้งานตัวเลือก INTERACTIVE_COMMENTS) การใช้:ทำให้ข้อความทั้งหมดเหมาะสำหรับการวางลงในเชลล์แบบโต้ตอบรวมทั้งบันทึกในไฟล์สคริปต์
Chris Johnsen

1
ยอดเยี่ยม! ทางออกเดียวที่ฉันพบว่าทำให้กิ่งก้านทั้งหมดยังคงสภาพสมบูรณ์
pheelicks

แปลกสำหรับฉันมันหยุดด้วยgit remote rm originซึ่งดูเหมือนจะกลับมา 1 เสมอดังนั้นฉันจึงแทนที่&&ด้วย;สำหรับบรรทัดนี้
kynan

ดี $ @ ใช้งานได้มากกว่าสอง dirs เมื่อจำเป็น พอเสร็จก็โทรgit remote add origin $TARGET; git push origin master.
Walter A

7

โครงการ git_split เป็นสคริปต์ง่ายๆที่ทำสิ่งที่คุณกำลังมองหา https://github.com/vangorra/git_split

เปลี่ยนไดเร็กทอรี git ให้เป็นที่เก็บของตนเองในที่ตั้งของตนเอง ไม่มีธุรกิจตลกย่อย สคริปต์นี้จะใช้ไดเร็กทอรีที่มีอยู่ในที่เก็บ 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

ขอบคุณสำหรับคำตอบของคุณ แต่สุดท้ายฉันก็แค่คัดลอกที่เก็บสองครั้งจากนั้นลบไฟล์ที่ฉันไม่ต้องการออกจากแต่ละไฟล์ ฉันจะใช้ filter-branch ในภายหลังเพื่อตัดคอมมิตทั้งหมดสำหรับไฟล์ที่ถูกลบเนื่องจากเวอร์ชันนั้นถูกควบคุมไว้แล้วที่อื่น

cp -R MyHugeRepo MyABRepo
cp -R MyHugeRepo My12Repo

cd MyABRepo/
rm -Rf DIR_1/ DIR_2/
git add -A
git commit -a

สิ่งนี้ใช้ได้ผลกับสิ่งที่ฉันต้องการ

แก้ไข: แน่นอนสิ่งเดียวกันนี้เกิดขึ้นใน My12Repo กับไดเร็กทอรี A และ B สิ่งนี้ทำให้ฉันสอง repos ที่มีประวัติเหมือนกันจนถึงจุดที่ฉันลบไดเร็กทอรีที่ไม่ต้องการ


1
สิ่งนี้ไม่ได้เก็บรักษาประวัติการกระทำ
Daenyth

ยังไง? ฉันยังมีประวัติทั้งหมดแม้กระทั่งไฟล์ที่ถูกลบ
MikeM

1
เนื่องจากข้อกำหนดของคุณไม่ใช่ว่า repo A ต้องแสร้งทำเป็นว่า repo B ไม่เคยมีอยู่จริงฉันคิดว่านี่ (การทิ้งบันทึกการกระทำที่ส่งผลกระทบเฉพาะ B) เป็นวิธีแก้ปัญหาที่เหมาะสม ดีกว่าที่จะทำซ้ำประวัติศาสตร์เล็กน้อยกว่าการทำลายมัน
Steve Clay
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.