จะแยกไดเร็กทอรีย่อย git และสร้างโมดูลย่อยจากไดเร็กทอรีได้อย่างไร


120

ฉันเริ่มโครงการเมื่อหลายเดือนก่อนและเก็บทุกอย่างไว้ในไดเรกทอรีหลัก ในไดเร็กทอรีหลักของฉัน "Project" มีไดเร็กทอรีย่อยหลายไดเร็กทอรีที่มีสิ่งที่แตกต่างกัน: Project / paper มีเอกสารที่เขียนใน LaTeX Project / sourcecode / RailsApp มีแอพทางรถไฟของฉัน

"Project" เป็น GITified และมีการคอมมิตทั้งในไดเรกทอรี "paper" และ "RailsApp" ตอนนี้ฉันต้องการใช้ cruisecontrol.rb สำหรับ "RailsApp" ของฉันฉันสงสัยว่ามีวิธีสร้างโมดูลย่อยจาก "RailsApp" โดยไม่เสียประวัติหรือไม่


2
ยังเป็นคำตอบที่ดีมาก: stackoverflow.com/questions/359424/…
Rehno Lindeque

คำตอบ:


123

ปัจจุบันมีวิธีที่ง่ายกว่าการใช้ git filter-branch ด้วยตนเอง: git subtree

การติดตั้ง

git-subtreeตอนนี้NOTEเป็นส่วนหนึ่งของgit(ถ้าคุณติดตั้ง Contrib) ตั้งแต่ 1.7.11 ดังนั้นคุณอาจติดตั้งไว้แล้ว git subtreeคุณสามารถตรวจสอบโดยการดำเนินการ


ในการติดตั้ง git-subtree จากซอร์ส (สำหรับ git เวอร์ชันเก่า):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

หรือถ้าคุณต้องการผู้ชายหน้าและทั้งหมด

make doc
make install

การใช้

แบ่งชิ้นที่ใหญ่กว่าออกเป็นชิ้นเล็ก ๆ :

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add origin git@github.com:my-user/new-project.git
git pull ../ foo-only
git push origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add git@github.com:my-user/new-project.git foo

สำหรับเอกสารรายละเอียด (หน้าคน) git-subtree.txtโปรดอ่าน


10
git subtree หิน!
Simon Woodside

3
แต่จุดของ git-subtree ที่จะหลีกเลี่ยงการใช้โมดูลย่อยไม่ใช่หรือ ฉันหมายถึงคุณเป็นผู้เขียน git-subtree (เว้นแต่จะมีการชนกันของชื่อเล่น) แต่ดูเหมือนว่า git-subtree จะเปลี่ยนไปแม้ว่าคำสั่งที่คุณแสดงจะยังคงใช้ได้ ฉันเข้าใจถูกไหม
Blaisorblade

18
git-subtree ตอนนี้เป็นส่วนหนึ่งของ git แล้ว (ถ้าคุณติดตั้ง Contrib) ตั้งแต่ 1.7.11
Jeremy

8
ดีgit rm -rf ./fooเอาfooจากHEADแต่จะไม่กรองmy-project's ประวัติ จากนั้นgit submodule add git@github.com:my-user/new-project.git fooเพียงทำให้foosubmodule HEADเริ่มต้นจาก ในแง่นั้นการเขียนสคริปต์filter-branchนั้นดีกว่าเนื่องจากอนุญาตให้บรรลุ "ทำราวกับว่า subdir เป็นโมดูลย่อยตั้งแต่เริ่มต้น"
Gregory Pakosz

ขอบคุณสำหรับสิ่งนี้ - git subtree docs เพียงเล็กน้อยทำให้งงงวยและนี่คือ (สำหรับฉัน) สิ่งที่มีประโยชน์มากที่สุดที่ฉันอยากจะทำกับมัน ...
hwjp

38

กร้าคอมไพล์กรองสาขา

Examplesส่วนของการแสดงที่หน้าคนวิธีการสกัดไดเรกทอรีย่อยในโครงการของตัวเองในขณะที่เก็บทั้งหมดของมันของประวัติศาสตร์และประวัติศาสตร์ทิ้งของไฟล์อื่น ๆ / ไดเรกทอรี (เพียงแค่สิ่งที่คุณกำลังมองหา)

ในการเขียนที่เก็บใหม่ให้ดูเหมือนว่าเป็นfoodir/รูทโปรเจ็กต์และทิ้งประวัติอื่น ๆ ทั้งหมด:

   git filter-branch --subdirectory-filter foodir -- --all

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


1
นี้ทำงานได้ดีสำหรับฉัน. ข้อเสียอย่างเดียวที่ฉันสังเกตเห็นคือผลลัพธ์คือสาขาหลักเดียวที่มีการกระทำทั้งหมด
aceofspades

@aceofspades: ทำไมถึงเป็นข้อเสีย?
naught101

2
สำหรับฉันแล้วประเด็นทั้งหมดของการแยกคอมมิตจาก git repo คือฉันต้องการเก็บประวัติไว้
aceofspades

13

วิธีหนึ่งในการทำเช่นนี้คือการผกผัน - ลบทุกอย่างยกเว้นไฟล์ที่คุณต้องการเก็บไว้

โดยพื้นฐานแล้วให้ทำสำเนาของที่เก็บจากนั้นใช้git filter-branchเพื่อลบทุกอย่างยกเว้นไฟล์ / โฟลเดอร์ที่คุณต้องการเก็บไว้

ตัวอย่างเช่นฉันมีโครงการที่ฉันต้องการแตกไฟล์tvnamer.pyไปยังที่เก็บใหม่:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

ที่ใช้git filter-branch --tree-filterเพื่อผ่านแต่ละคอมมิตรันคำสั่งและแนะนำเนื้อหาไดเร็กทอรีผลลัพธ์ นี่เป็นการทำลายล้างอย่างมาก (ดังนั้นคุณควรทำสิ่งนี้กับสำเนาของที่เก็บของคุณเท่านั้น!) และอาจใช้เวลาสักครู่ (ประมาณ 1 นาทีในที่เก็บที่มี 300 คอมมิตและประมาณ 20 ไฟล์)

คำสั่งด้านบนจะเรียกใช้เชลล์สคริปต์ต่อไปนี้ในการแก้ไขแต่ละครั้งซึ่งคุณต้องแก้ไขแน่นอน (เพื่อให้ไม่รวมไดเร็กทอรีย่อยของคุณแทนtvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

ปัญหาที่ชัดเจนที่สุดคือทิ้งข้อความคอมมิตทั้งหมดแม้ว่าจะไม่เกี่ยวข้องกับไฟล์ที่เหลือก็ตาม สคริปต์git-remove-empty-commitsแก้ไขสิ่งนี้ ..

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

คุณต้องใช้-fอาร์กิวเมนต์บังคับเรียกใช้filter-branchอีกครั้งกับอะไรก็ได้refs/original/(ซึ่งโดยพื้นฐานแล้วเป็นข้อมูลสำรอง)

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

อีกครั้งเรียกใช้สิ่งนี้บนสำเนาของที่เก็บของคุณเท่านั้น! - แต่โดยสรุปในการลบไฟล์ทั้งหมดยกเว้น "thisismyfilename.txt":

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

4
git filter-branchมี (ในปัจจุบัน?) --prune-emptyในตัวเลือกที่จะลบกระทำที่ว่างเปล่าคือ คำแนะนำที่ดีกว่าgit filter-branchอยู่ในคำตอบของคำถามนี้: stackoverflow.com/questions/359424/…
Blaisorblade

4

ทั้งสองCoolAJ86และapenwarrคำตอบจะคล้ายกันมาก ฉันกลับไปกลับมาระหว่างทั้งสองพยายามที่จะเข้าใจบิตที่หายไปจากอันใดอันหนึ่ง ด้านล่างนี้เป็นการรวมกันของพวกเขา

ขั้นแรกให้ไปที่ Git Bash ไปที่รูทของ git repo ที่จะแยก ในตัวอย่างของฉันนี่คือ~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin git@github.com:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add git@github.com:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

ด้านล่างนี้คือสำเนาด้านบนโดยแทนที่ชื่อที่ปรับแต่งได้และใช้ https แทน โฟลเดอร์รูทอยู่ในขณะนี้~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package

3

หากคุณต้องการถ่ายโอนไฟล์บางส่วนไปยังที่เก็บใหม่ แต่เก็บประวัติไว้โดยพื้นฐานแล้วคุณจะได้รับประวัติใหม่ทั้งหมด วิธีนี้จะได้ผลโดยทั่วไปมีดังนี้:

  1. สร้างที่เก็บใหม่
  2. สำหรับการแก้ไขที่เก็บเก่าของคุณแต่ละครั้งให้รวมการเปลี่ยนแปลงกับโมดูลของคุณลงในที่เก็บใหม่ สิ่งนี้จะสร้าง "สำเนา" ของประวัติโครงการที่คุณมีอยู่

ควรจะค่อนข้างตรงไปตรงมาที่จะทำให้สิ่งนี้เป็นไปโดยอัตโนมัติหากคุณไม่คิดจะเขียนสคริปต์เล็ก ๆ แต่มีขนดก ตรงไปตรงมาใช่ แต่ก็เจ็บปวดเช่นกัน ผู้คนเคยเขียนประวัติใหม่ใน Git ในอดีตคุณสามารถค้นหาสิ่งนั้นได้

หรืออีกวิธีหนึ่ง:โคลนที่เก็บและลบกระดาษในโคลนลบแอพในต้นฉบับ การดำเนินการนี้จะใช้เวลาหนึ่งนาทีรับประกันว่าจะได้ผลและคุณสามารถกลับไปที่สิ่งที่สำคัญกว่าการพยายามล้างประวัติคอมไพล์ของคุณ และไม่ต้องกังวลเกี่ยวกับพื้นที่ฮาร์ดไดรฟ์ที่สำเนาประวัติซ้ำซ้อน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.