คุณจะรวมที่เก็บ Git สองแห่งได้อย่างไร


1620

พิจารณาสถานการณ์สมมติต่อไปนี้:

ฉันได้พัฒนาโครงการทดลองขนาดเล็ก A ใน Git repo ของตัวเอง ตอนนี้ครบกำหนดแล้วและฉันต้องการให้ A เป็นส่วนหนึ่งของโครงการ B ขนาดใหญ่ซึ่งมีพื้นที่เก็บข้อมูลขนาดใหญ่ของตัวเอง ตอนนี้ฉันต้องการเพิ่ม A เป็นไดเรกทอรีย่อยของ B

ฉันจะรวม A เข้ากับ B โดยไม่สูญเสียประวัติด้านใดได้อย่างไร


8
หากคุณเพียงแค่พยายามรวมสองที่เก็บข้อมูลไว้ในที่เดียวโดยไม่จำเป็นต้องเก็บที่เก็บทั้งสองไว้ดูที่คำถามนี้: stackoverflow.com/questions/13040958/…
Flimm

สำหรับการรวม repit git ใน dir ที่กำหนดเองกับการบันทึก comits ทั้งหมดใช้stackoverflow.com/a/43340714/1772410
Andrey Izman

คำตอบ:


436

สาขาเดียวของที่เก็บอื่นสามารถวางได้อย่างง่ายดายภายใต้ไดเรกทอรีย่อยที่เก็บประวัติของมัน ตัวอย่างเช่น:

git subtree add --prefix=rails git://github.com/rails/rails.git master

สิ่งนี้จะปรากฏเป็นคอมมิชชันเดียวที่ไฟล์ทั้งหมดของสาขาหลักของ Rails ถูกเพิ่มเข้าไปในไดเรกทอรี "Rails" อย่างไรก็ตามชื่อของคอมมิชชันนั้นมีการอ้างอิงถึงแผนผังประวัติเก่า:

เพิ่ม 'ทางรถไฟ /' จากการกระทำ <rev>

ที่ไหน<rev>เป็น SHA-1 กระทำกัญชา คุณยังสามารถดูประวัติตำหนิการเปลี่ยนแปลงบางอย่าง

git log <rev>
git blame <rev> -- README.md

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

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

มีวิธีแก้ปัญหาที่ซับซ้อนมากขึ้นเช่นการทำเช่นนี้ด้วยตนเองหรือเขียนประวัติศาสตร์ตามที่อธิบายไว้ในคำตอบอื่น ๆ

คำสั่ง git-subtree เป็นส่วนหนึ่งของ git-contrib อย่างเป็นทางการผู้จัดการแพ็คเก็ตบางตัวติดตั้งเป็นค่าเริ่มต้น (OS X Homebrew) แต่คุณอาจต้องติดตั้งด้วยตัวเองนอกเหนือจากคอมไพล์


2
นี่คือคำแนะนำเกี่ยวกับวิธีการติดตั้ง Git SubTree (จนถึงมิถุนายน 2013): stackoverflow.com/a/11613541/694469 (และฉันแทนที่git co v1.7.11.3 ด้วย... v1.8.3)
KajMagnus

1
ขอบคุณสำหรับหัวขึ้นเกี่ยวกับคำตอบด้านล่าง ในฐานะของ git 1.8.4 'subtree' ยังไม่รวมอยู่ (อย่างน้อยไม่ได้อยู่ใน Ubuntu 12.04 git ppa (ppa: git-core / ppa))
Matt Klein

1
ฉันสามารถยืนยันได้ว่าหลังจากนี้git log rails/somefileจะไม่แสดงประวัติการคอมมิทของไฟล์ยกเว้นการคอมมิทคอมมิชชัน ในฐานะที่เป็น @artfulrobot แนะนำให้ตรวจสอบคำตอบของเกร็ก Hewgill และคุณอาจจำเป็นต้องใช้git filter-branchใน repo ที่คุณต้องการรวม
Jifeng Zhang

6
หรืออ่าน "การรวมสอง Git ของ Eric Lee เข้ากับที่เก็บหนึ่งเดียวโดยไม่สูญเสียประวัติไฟล์" saintgimp.org/2013/01/22/…
Jifeng Zhang

4
อย่างที่คนอื่นพูดgit subtreeไม่อาจทำในสิ่งที่คุณคิด! ดูที่นี่สำหรับโซลูชันที่สมบูรณ์ยิ่งขึ้น
พอลเดรเปอร์

1906

หากคุณต้องการที่จะผสานproject-aเข้าproject-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

นำมาจาก: คอมไพล์ผสานที่เก็บที่แตกต่างกันใช่ไหม

วิธีนี้ใช้งานได้ดีสำหรับฉันมันสั้นกว่าและในความคิดของฉันก็สะอาดกว่ามาก

ในกรณีที่คุณต้องการที่จะนำproject-aเข้ามาในไดเรกทอรีย่อยคุณสามารถใช้git-filter-repo( filter-branchเป็นกำลังใจ ) เรียกใช้คำสั่งต่อไปนี้ก่อนคำสั่งด้านบน:

cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a

ตัวอย่างของการรวมสองที่เก็บขนาดใหญ่โดยวางหนึ่งในนั้นไว้ในไดเรกทอรีย่อย: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

หมายเหตุ:--allow-unrelated-historiesพารามิเตอร์มีอยู่เพียงตั้งแต่ Git> = 2.9 ดูGit - git merge Documentation / --allow-unrelated-history

อัปเดต : เพิ่ม--tagsตามที่แนะนำโดย @jstadler เพื่อเก็บแท็ก


8
นี่เป็นธุรกิจสำหรับฉัน ทำงานเหมือนเครื่องรางครั้งแรกที่มีความขัดแย้งเพียงไฟล์เดียวในไฟล์. gitignore! มันรักษาประวัติศาสตร์การกระทำอย่างสมบูรณ์แบบ ข้อดีที่เหนือกว่าวิธีอื่น ๆ - นอกเหนือจากความเรียบง่าย - คือสิ่งนี้ไม่จำเป็นต้องมีการอ้างอิงอย่างต่อเนื่องกับธุรกรรมซื้อคืนที่ผสาน อย่างไรก็ตามสิ่งหนึ่งที่ต้องระวังหากคุณเป็นนักพัฒนา iOS อย่างฉัน - ต้องระวังอย่างมากที่จะวางไฟล์โครงการ repo ของเป้าหมายลงในพื้นที่ทำงาน
Max MacLeod

30
ขอบคุณ ทำงานให้ฉัน ฉันต้องการย้ายไดเรกทอรีที่ผสานไปยังโฟลเดอร์ย่อยดังนั้นหลังจากทำตามขั้นตอนข้างต้นที่ฉันใช้git mv source-dir/ dest/new-source-dir
Sid

13
git mergeขั้นตอนล้มเหลวที่นี่กับfatal: refusing to merge unrelated histories; --allow-unrelated-historiesการแก้ไขที่ตามที่อธิบายไว้ในเอกสาร
ssc

19
--allow-unrelated-historiesเป็นที่รู้จักในคอมไพล์ 2.9 ในรุ่นก่อนหน้านี้มันเป็นพฤติกรรมเริ่มต้น
Douglas Royds

11
git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEADสั้น:
jthill

614

ต่อไปนี้เป็นวิธีแก้ไขปัญหาสองวิธี:

submodules

คัดลอกที่เก็บ A ลงในไดเร็กทอรีแยกต่างหากในโปรเจ็กต์ B ที่มีขนาดใหญ่กว่าหรือ (อาจจะดีกว่า) โคลนที่เก็บ A ในไดเร็กทอรีย่อยในโปรเจ็กต์ B จากนั้นใช้git submoduleเพื่อทำให้ที่เก็บนี้เป็นsubmoduleของที่เก็บ B

นี่คือทางออกที่ดีสำหรับเก็บหลวมคู่ที่พัฒนาในพื้นที่เก็บข้อมูลอย่างต่อเนื่องและส่วนที่สำคัญของการพัฒนาคือการพัฒนาแบบสแตนด์อะโลนที่แยกต่างหากในเอดูเพิ่มเติมSubmoduleSupportและGitSubmoduleTutorialหน้าใน Git วิกิพีเดีย

รวมทรีย่อย

คุณสามารถรวมที่เก็บ A เข้าในไดเรกทอรีย่อยของโครงการ B โดยใช้กลยุทธ์การรวมทรีย่อย นี่คือคำอธิบายในSubtree Merging และ Youโดย Markus Prinz

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

( --allow-unrelated-historiesจำเป็นต้องมีตัวเลือกสำหรับ Git> = 2.9.0)

หรือคุณสามารถใช้คอมไพล์ทรีย่อยเครื่องมือ ( พื้นที่เก็บข้อมูลบน GitHub ) โดย apenwarr (เอเวอรี่ Pennarun) ประกาศยกตัวอย่างเช่นในโพสต์บล็อกของเขาทางเลือกใหม่ในการ submodules Git: ทรีย่อยคอมไพล์


ผมคิดว่าในกรณีของคุณ (A คือการเป็นส่วนหนึ่งของโครงการขนาดใหญ่ B) วิธีที่ถูกต้องจะใช้การผสานทรีย่อย


1
วิธีนี้ใช้งานได้และดูเหมือนว่าจะเก็บประวัติไว้ แต่ไม่สามารถใช้เพื่อแยกไฟล์หรือแบ่งเป็นสองส่วนผ่านการรวม ฉันไม่มีขั้นตอนหรือไม่
jettero

55
นี้ไม่สมบูรณ์ ใช่คุณได้รับภาระผูกพัน แต่พวกเขาไม่ได้อ้างถึงเส้นทางที่ถูกต้องอีกต่อไป git log dir-B/somefileจะไม่แสดงอะไรเลยนอกจากการผสานเดียว ดูคำตอบของ Greg Hewgillอ้างอิงปัญหาสำคัญนี้
artfulrobot

2
สำคัญ: git pull - no-rebase -s ทรีย่อย Bproject master หากคุณไม่ทำเช่นนั้นและคุณได้ตั้งค่า pull เป็น rebase โดยอัตโนมัติคุณจะพบกับ "ไม่สามารถแยกวิเคราะห์วัตถุ" ดูosdir.com/ml/git/2009-07/msg01576.html
Eric Bowman - abstracto -

4
คำตอบนี้อาจสับสนเนื่องจากมี B เป็นทรีย่อยที่ผสานเมื่อมีคำถามว่า A ผลลัพธ์ของการคัดลอกและวาง
vfclists

11
หากคุณพยายามที่จะทำการรวมสองที่เก็บไว้ด้วยกันการรวม submodules และ subtree เป็นเครื่องมือที่ไม่ถูกต้องที่จะใช้เพราะมันไม่ได้เก็บประวัติไฟล์ทั้งหมดไว้ ดูstackoverflow.com/questions/13040958/...
Eric Lee

194

วิธี submodule ดีถ้าคุณต้องการรักษาโครงการแยกต่างหาก อย่างไรก็ตามหากคุณต้องการรวมทั้งสองโครงการเข้ากับที่เก็บเดียวกันคุณจะต้องทำงานเพิ่มขึ้นอีกเล็กน้อย

สิ่งแรกคือการใช้git filter-branchเพื่อเขียนชื่อของทุกสิ่งในที่เก็บข้อมูลที่สองให้อยู่ในไดเรกทอรีย่อยที่คุณต้องการให้พวกเขาสิ้นสุด ดังนั้นแทนที่จะfoo.c, bar.htmlคุณจะต้องและprojb/foo.cprojb/bar.html

จากนั้นคุณควรทำสิ่งต่อไปนี้:

git remote add projb [wherever]
git pull projb

git pullจะทำตามด้วยgit fetch git mergeไม่ควรมีข้อขัดแย้งหากพื้นที่เก็บข้อมูลที่คุณกำลังดึงยังไม่มีprojb/ไดเรกทอรี

การค้นหาเพิ่มเติมแสดงให้เห็นว่าสิ่งที่คล้ายกันคือทำเพื่อผสานเข้าgitk gitJunio ​​C Hamano เขียนเกี่ยวกับที่นี่: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


4
ทรีย่อยผสานจะเป็นทางออกที่ดีกว่าและไม่จำเป็นต้องมีการเขียนประวัติศาสตร์ของโครงการรวม
ยาคุบบNarębski

8
ฉันต้องการทราบวิธีใช้git filter-branchเพื่อให้บรรลุสิ่งนี้ ในหน้า man มันบอกเกี่ยวกับทางตรงข้าม: ทำให้ subdir / กลายเป็น root แต่ไม่ใช่วิธีอื่น ๆ
artfulrobot

31
คำตอบนี้จะดีถ้ามันอธิบายวิธีการใช้ตัวกรองสาขาเพื่อให้ได้ผลลัพธ์ที่ต้องการ
Anentropic

14
ฉันพบวิธีใช้ตัวกรองสาขาที่นี่: stackoverflow.com/questions/4042816/…
David Minor

3
ดูคำตอบนี้สำหรับการนำโครงร่างของ Greg ไปใช้
พอลเดรเปอร์

75

git-subtree ดี แต่อาจไม่ใช่สิ่งที่คุณต้องการ

ตัวอย่างเช่นถ้าprojectAเป็นไดเรกทอรีที่สร้างขึ้นใน B หลังจากgit subtree,

git log projectA

แสดงการกระทำเพียงรายการเดียว : การรวม คอมมิทจากโครงการที่รวมเป็นเส้นทางที่แตกต่างกันดังนั้นพวกเขาจะไม่ปรากฏขึ้น

คำตอบของ Greg Hewgill มาใกล้ที่สุดแม้ว่าจะไม่ได้พูดถึงวิธีการเขียนเส้นทางใหม่


การแก้ปัญหานั้นง่ายอย่างน่าประหลาดใจ

(1) ใน A

PREFIX=projectA #adjust this

git filter-branch --index-filter '
    git ls-files -s |
    sed "s,\t,&'"$PREFIX"'/," |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD

หมายเหตุ: นี่จะเขียนประวัติใหม่ดังนั้นหากคุณตั้งใจจะใช้ repo A ต่อไปคุณอาจต้องการโคลน (คัดลอก) สำเนาใบปลิวของมันก่อน

หมายเหตุเบเน่: คุณต้องแก้ไขสคริปต์สำรองภายในคำสั่ง sed ในกรณีที่คุณใช้อักขระที่ไม่ใช่ ASCII (หรืออักขระสีขาว) ในชื่อไฟล์หรือพา ธ ในกรณีนั้นตำแหน่งไฟล์ภายในระเบียนที่สร้างโดย "ls-files -s" เริ่มต้นด้วยเครื่องหมายคำพูด

(2) จากนั้นใน B ให้เรียกใช้

git pull path/to/A

Voila! คุณมีprojectAไดเรกทอรีเป็นB ถ้าคุณเรียกใช้git log projectAคุณจะเห็นการกระทำทั้งหมดจาก A


ในกรณีของผมผมอยากสองไดเรกทอรีย่อยและprojectA projectBในกรณีนั้นฉันทำขั้นตอน (1) ถึง B เช่นกัน


1
ดูเหมือนว่าคุณคัดลอกคำตอบของคุณจากstackoverflow.com/a/618113/586086 ?
Andrew Mao

1
@ AndrewMao ฉันคิดอย่างนั้น ... ฉันจำไม่ได้จริงๆ ฉันใช้สคริปต์นี้มาไม่นาน
พอลเดรเปอร์

6
ฉันจะเพิ่มว่า \ t ไม่ทำงานบน OS X และคุณต้องป้อน <tab>
Muneeb Ali

2
"$GIT_INDEX_FILE"จะต้องยกมา (สองครั้ง) มิฉะนั้นวิธีการของคุณจะล้มเหลวถ้าเช่นเส้นทางที่มีช่องว่าง
Rob W

4
หากคุณสงสัยว่าการแทรกแท็บ <tab> ใน osx คุณจำเป็นต้องCtrl-V <tab>
casey

48

หากที่เก็บข้อมูลทั้งสองมีไฟล์ประเภทเดียวกัน (เช่นที่เก็บรางสองแห่งสำหรับโครงการต่างกัน) คุณสามารถดึงข้อมูลของที่เก็บข้อมูลสำรองไปยังที่เก็บปัจจุบันของคุณ:

git fetch git://repository.url/repo.git master:branch_name

จากนั้นรวมเข้ากับที่เก็บปัจจุบัน:

git merge --allow-unrelated-histories branch_name

ถ้ารุ่น Git ของคุณมีขนาดเล็กกว่า 2.9 --allow-unrelated-historiesลบ

หลังจากนี้อาจเกิดความขัดแย้ง git mergetoolคุณสามารถแก้ไขปัญหาเหล่านั้นเช่นกับ kdiff3สามารถใช้กับแป้นพิมพ์เพียงอย่างเดียวดังนั้นไฟล์ความขัดแย้ง 5 ไฟล์ใช้เวลาอ่านโค้ดเพียงไม่กี่นาที

อย่าลืมผสานให้เสร็จ:

git commit

25

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

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> แก้ไขข้อขัดแย้งจากนั้นดำเนินการต่อบ่อยเท่าที่ต้องการ ...

git rebase --continue

การทำเช่นนี้นำไปสู่โครงการหนึ่งที่มีการกระทำทั้งหมดจาก projA ตามด้วยการกระทำจาก projB


25

ในกรณีของฉันฉันมีmy-pluginพื้นที่เก็บข้อมูลและmain-projectพื้นที่เก็บข้อมูลและฉันอยากจะแกล้งทำเป็นว่าmy-pluginเคยพัฒนาในไดเรกทอรีย่อยของpluginsmain-project

โดยทั่วไปฉันจะเขียนประวัติของที่my-pluginเก็บเพื่อให้การพัฒนาทั้งหมดเกิดขึ้นในplugins/my-pluginไดเรกทอรีย่อย จากนั้นฉันเพิ่มประวัติการพัฒนาmy-pluginลงในmain-projectประวัติศาสตร์และรวมต้นไม้สองต้นเข้าด้วยกัน เนื่องจากไม่มีplugins/my-pluginไดเรกทอรีอยู่ในที่main-projectเก็บจึงเป็นการรวมที่ไม่ขัดแย้งกันเล็กน้อย ที่เก็บผลลัพธ์มีประวัติทั้งหมดจากโครงการดั้งเดิมและมีสองราก

TL; DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

รุ่นยาว

ก่อนอื่นให้สร้างสำเนาของที่my-pluginเก็บเพราะเรากำลังจะเขียนประวัติของที่เก็บนี้อีกครั้ง

ตอนนี้ไปที่รูทของที่my-pluginเก็บเช็คสาขาหลักของคุณ (อาจmaster) และรันคำสั่งต่อไปนี้ แน่นอนคุณควรแทนที่my-pluginและpluginsชื่อจริงของคุณคืออะไร

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all

ตอนนี้สำหรับคำอธิบาย git filter-branch --tree-filter (...) HEADรันคำสั่งในทุกการกระทำที่สามารถเข้าถึงได้จาก(...) HEADโปรดทราบว่าสิ่งนี้ทำงานโดยตรงกับข้อมูลที่เก็บไว้สำหรับการกระทำแต่ละครั้งดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับแนวคิดของ "ไดเรกทอรีการทำงาน", "ดัชนี", "การจัดเตรียม" และอื่น ๆ

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

สำหรับคำสั่งจริงฉันไม่มีโชคมากที่bashจะทำสิ่งที่ฉันต้องการดังนั้นแทนที่จะใช้zsh -cเพื่อzshเรียกใช้งานคำสั่ง ครั้งแรกที่ฉันตั้งค่าextended_globตัวเลือกซึ่งเป็นสิ่งที่ช่วยให้^(...)ไวยากรณ์ในmvคำสั่งเช่นเดียวกับglob_dotsตัวเลือกซึ่งช่วยให้ฉันเลือก dotfiles (เช่น.gitignore) ด้วย glob ( ^(...))

ต่อไปฉันใช้mkdir -pคำสั่งเพื่อสร้างทั้งสองpluginsและplugins/my-pluginในเวลาเดียวกัน

ในที่สุดฉันใช้คุณสมบัติzsh"ลบ glob" ^(.git|plugins)เพื่อจับคู่ไฟล์ทั้งหมดในไดเรกทอรีรากของที่เก็บยกเว้น.gitและmy-pluginโฟลเดอร์ที่สร้างขึ้นใหม่ (การยกเว้น.gitอาจไม่จำเป็นที่นี่ แต่การลองย้ายไดเรกทอรีไปยังตัวเองถือเป็นข้อผิดพลาด)

ในที่เก็บของฉันการคอมมิชชันเริ่มต้นไม่ได้รวมไฟล์ใด ๆ ดังนั้นmvคำสั่งส่งคืนข้อผิดพลาดในการคอมมิทเริ่มต้น ดังนั้นฉันจึงเพิ่มรายการ|| trueที่git filter-branchจะไม่ยกเลิก

--allตัวเลือกที่บอกfilter-branchจะเขียนประวัติศาสตร์สำหรับทุกสาขาในพื้นที่เก็บข้อมูลและเพิ่มเติม--เป็นสิ่งจำเป็นที่จะบอกgitจะตีความว่ามันเป็นส่วนหนึ่งของรายการตัวเลือกสำหรับสาขาที่จะเขียนแทนการเป็นตัวเลือกให้กับfilter-branchตัวเอง

ตอนนี้ไปที่ที่main-projectเก็บของคุณแล้วดูสาขาที่คุณต้องการรวม เพิ่มสำเนาที่my-pluginเก็บโลคัลของคุณ (ที่แก้ไขประวัติ) เป็นรีโมตmain-projectด้วย:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

ตอนนี้คุณจะมีต้นไม้ที่ไม่เกี่ยวข้องสองต้นในประวัติการกระทำของคุณซึ่งคุณสามารถเห็นภาพได้อย่างชัดเจนโดยใช้:

$ git log --color --graph --decorate --all

เพื่อรวมพวกเขาใช้:

$ git merge my-plugin/master --allow-unrelated-histories

โปรดทราบว่าใน Git pre-2.9.0 --allow-unrelated-historiesไม่มีตัวเลือกอยู่ หากคุณกำลังใช้รุ่นใดรุ่นหนึ่งเหล่านี้เพียงละเว้นตัวเลือก: ข้อความแสดงข้อผิดพลาดที่--allow-unrelated-historiesป้องกันไม่ให้ถูกยังเพิ่มใน 2.9.0

คุณไม่ควรมีข้อขัดแย้งในการผสาน หากคุณทำเช่นนั้นอาจหมายถึงว่าfilter-branchคำสั่งทำงานไม่ถูกต้องหรือมีplugins/my-pluginไดเรกทอรีอยู่main-projectแล้ว

ตรวจสอบให้แน่ใจว่าได้ป้อนข้อความการมอบหมายที่อธิบายได้สำหรับผู้มีส่วนร่วมในอนาคตสงสัยว่าแฮกเกอร์กำลังทำอะไรเพื่อสร้างที่เก็บที่มีสองราก

คุณสามารถเห็นกราฟการคอมมิตใหม่ซึ่งควรมีคอมมิททรูทสองอันโดยใช้git logคำสั่งด้านบน โปรดทราบว่าเฉพาะmasterสาขาที่จะถูกรวม ซึ่งหมายความว่าหากคุณมีงานสำคัญในmy-pluginสาขาอื่นที่คุณต้องการรวมเข้ากับmain-projectต้นไม้คุณควรละเว้นการลบmy-pluginรีโมตจนกว่าคุณจะทำการผสานเหล่านี้ หากคุณไม่ทำเช่นนั้นการผูกมัดจากสาขาเหล่านั้นจะยังคงอยู่ในที่main-projectเก็บ แต่บางอันจะไม่สามารถเข้าถึงได้และไวต่อการรวบรวมขยะในที่สุด (นอกจากนี้คุณจะต้องอ้างถึงพวกเขาโดย SHA เนื่องจากการลบรีโมตจะลบสาขาการติดตามระยะไกล)

เป็นทางเลือกหลังจากคุณรวมทุกอย่างที่คุณต้องการเก็บไว้my-pluginแล้วคุณสามารถลบmy-pluginรีโมตโดยใช้:

$ git remote remove my-plugin

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


ผ่านการทดสอบบน Mac OS X El Capitan ด้วยและgit --version 2.9.0 zsh --version 5.2ไมล์สะสมของคุณอาจแตกต่างกันไป

อ้างอิง:


1
ไหน--allow-unrelated-historiesจะมาจากไหน?
xpto

3
@MarceloFilho man git-mergeตรวจสอบ โดยค่าเริ่มต้นคำสั่ง git merge ปฏิเสธที่จะรวมประวัติที่ไม่แบ่งปันบรรพบุรุษร่วมกัน ตัวเลือกนี้สามารถใช้เพื่อแทนที่ความปลอดภัยนี้เมื่อรวมประวัติของสองโครงการที่เริ่มชีวิตของพวกเขาด้วยตนเอง เนื่องจากเป็นโอกาสที่หายากมากจึงไม่มีตัวแปรการกำหนดค่าให้เปิดใช้งานสิ่งนี้ตามค่าเริ่มต้นและจะไม่ถูกเพิ่ม
Radon Rosborough

ควรจะใช้ได้git version 2.7.2.windows.1หรือไม่
xpto

2
@MarceloFilho สิ่งนี้ถูกเพิ่มเข้ามาใน 2.9.0 แต่ในเวอร์ชั่นที่เก่ากว่าคุณไม่จำเป็นต้องผ่านตัวเลือก github.com/git/git/blob/…
Radon Rosborough

มันใช้งานได้ดี และฉันสามารถใช้สาขาตัวกรองเพื่อเขียนชื่อไฟล์ไปยังตำแหน่งที่ฉันต้องการในทรีก่อนการผสาน ฉันคิดว่ามีงานที่เกี่ยวข้องมากกว่านี้ถ้าคุณต้องการย้ายประวัตินอกเหนือจากสาขาหลัก
codeDr

9

ฉันพยายามทำสิ่งเดียวกันมาหลายวันแล้วฉันใช้ git 2.7.2 ทรีย่อยไม่เก็บประวัติ

คุณสามารถใช้วิธีนี้หากคุณจะไม่ได้ใช้โครงการเก่าอีกครั้ง

ฉันขอแนะนำให้คุณสาขา B ก่อนและทำงานในสาขา

นี่คือขั้นตอนโดยไม่แยก:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

หากคุณเข้าสู่ระบบไฟล์ใด ๆ ในตำบลย่อยคุณจะได้รับประวัติเต็ม

git log --follow A/<file>

นี่คือโพสต์ที่ช่วยฉันทำสิ่งนี้:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/


8

หากคุณต้องการวางไฟล์จากสาขาใน repo B ในทรีย่อยของ repo A และเก็บประวัติไว้อ่านต่อไป (ในตัวอย่างด้านล่างฉันสมมติว่าเราต้องการให้สาขาหลักของ repo B รวมเข้ากับสาขาหลักของ repo A)

ใน repo A ก่อนอื่นให้ทำ repo B ต่อไปนี้:

git remote add B ../B # Add repo B as a new remote.
git fetch B

ตอนนี้เราสร้างแบรนด์สาขาใหม่ (ที่มีเพียงหนึ่งกระทำ) ใน repo new_b_rootที่เราเรียกว่า ผลจากการกระทำจะมีไฟล์ที่มีความมุ่งมั่นในครั้งแรกของการกระทำ repo B สาขาต้นแบบ path/to/b-files/แต่ใส่ในไดเรกทอรีย่อยที่เรียกว่า

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

คำอธิบาย: --orphanตัวเลือกสำหรับคำสั่งเช็คเอาท์จะตรวจสอบไฟล์จากสาขาหลักของ A แต่ไม่ได้สร้างการผูกมัดใด ๆ เราสามารถเลือกกระทำได้เพราะต่อไปเราจะล้างไฟล์ทั้งหมดออกไป จากนั้นโดยไม่กระทำ ( -n) เราเลือกเชอร์รี่ข้อผูกพันแรกจากสาขาหลักของ B (เชอร์รี่เลือกเก็บรักษาข้อความการกระทำดั้งเดิมซึ่งเช็คเอาท์แบบตรงไม่ทำ) จากนั้นเราสร้างทรีย่อยที่เราต้องการวางไฟล์ทั้งหมดจาก repo B จากนั้นเราต้องย้ายไฟล์ทั้งหมดที่ถูกนำมาใช้ใน เชอร์รี่เลือกที่จะทรีย่อย ในตัวอย่างด้านบนมีเพียงREADMEไฟล์ที่จะย้าย จากนั้นเราจะส่งมอบราก B-repo ของเราและในเวลาเดียวกันเราก็ยังคงบันทึกเวลาของการกระทำดั้งเดิม

ตอนนี้เราจะสร้างใหม่สาขาที่ด้านบนของที่สร้างขึ้นใหม่B/master new_b_rootเราเรียกสาขาใหม่b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

ตอนนี้เรารวมbสาขาของเราเป็นA/master:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

ในที่สุดคุณสามารถลบBสาขาระยะไกลและชั่วคราว:

git remote remove B
git branch -D new_b_root b

กราฟสุดท้ายจะมีโครงสร้างดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่


คำตอบที่ดีขอบคุณ! ฉันพลาดคำตอบอื่น ๆ ด้วย "ทรีย่อย git" หรือ "ผสาน - ไม่เกี่ยวข้องกับประวัติศาสตร์" จาก Andresch Serj ว่าไดเรกทอรีย่อยไม่มีบันทึก
Ilendir

8

ฉันได้รวบรวมข้อมูลจำนวนมากไว้ที่นี่ใน Stack OverFlow ฯลฯ และมีการจัดการที่จะรวมสคริปต์เข้าด้วยกันเพื่อแก้ปัญหาให้ฉัน

ข้อแม้คือมันจะคำนึงถึงสาขา 'พัฒนา' ของที่เก็บแต่ละแห่งเท่านั้นและรวมเข้าไปในไดเรกทอรีที่แยกต่างหากในที่เก็บใหม่ที่สมบูรณ์

แท็กและสาขาอื่น ๆ จะถูกละเว้น - นี่อาจไม่ใช่สิ่งที่คุณต้องการ

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

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi


# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi


# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo


# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})


  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done


# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

คุณสามารถหาได้จากhttp://paste.ubuntu.com/11732805

ขั้นแรกสร้างไฟล์ที่มี URL ไปยังที่เก็บแต่ละแห่งเช่น:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

จากนั้นเรียกสคริปต์ที่ให้ชื่อโครงการและเส้นทางไปยังสคริปต์:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

สคริปต์มีความคิดเห็นจำนวนมากซึ่งควรอธิบายว่ามันทำอะไร


แทนที่จะส่งผู้อ่านไปยังคำตอบโปรดโพสต์คำตอบที่นี่ (หรือแก้ไขสิ่งที่คุณพูดในความคิดเห็นนั้นในคำตอบนี้)
josliber

1
แน่นอนแค่คิดว่ามันจะดีกว่าที่จะไม่ทำซ้ำตัวเอง ... =)
eitch

หากคุณคิดว่าคำถามนี้เหมือนกันกับคำถามอื่นคุณสามารถทำเครื่องหมายเป็นซ้ำโดยใช้ลิงก์ "ตั้งค่าสถานะ" ใต้คำถามนั้นและระบุคำถามอื่น ๆ หากไม่ใช่คำถามที่ซ้ำกัน แต่คุณคิดว่าคำตอบเดียวกันสามารถใช้แก้ปัญหาทั้งสองได้ดังนั้นให้โพสต์คำตอบเดียวกันกับปัญหาทั้งสอง (ตามที่คุณทำไปแล้ว) ขอบคุณสำหรับการมีส่วนร่วม!
josliber

! ที่น่าตื่นตาตื่นใจ ไม่สามารถใช้งานได้กับ Windows bash prompt แต่มันทำงานได้อย่างไม่มีที่ติในกล่อง Vagrant ที่ใช้อูบุนตู ช่างเป็นอะไรที่ประหยัดเวลา!
xverges

มีความสุขที่ได้เป็นผู้ให้บริการ =)
eitch

7

ฉันรู้ว่ามันนานหลังจากความจริง แต่ฉันไม่พอใจกับคำตอบอื่น ๆ ที่ฉันพบที่นี่ดังนั้นฉันเขียนสิ่งนี้:

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done

2
นี่คือสิ่งที่ฉันกำลังมองหา ขอบคุณ! อย่างไรก็ตามฉันต้องเปลี่ยนบรรทัดที่ 22 เป็น:if [[ $dirname =~ ^.*\.git$ ]]; then
heyman

2
^. * blarg $ เป็นโลภอย่างสิ้นเปลือง ดีกว่าที่จะพูด. blarg $ และข้ามสมอด้านหน้า
jettero

7

หากคุณกำลังพยายามที่จะรวมสองที่เก็บไว้ด้วยกันอย่างง่าย ๆ การรวม submodules และ subtree เป็นเครื่องมือที่ไม่ถูกต้องที่จะใช้เพราะมันไม่ได้เก็บประวัติไฟล์ทั้งหมดไว้ ดูคำตอบที่นี่สำหรับวิธีที่ง่ายและถูกต้องในการทำเช่นนี้


1
โซลูชันของคุณทำงานได้ดีกับที่เก็บใหม่เท่านั้น แต่จะรวม repo เข้ากับที่เก็บไฟล์ขัดแย้งกันได้อย่างไร
Andrey Izman

6

ฉันมีความท้าทายที่คล้ายกัน แต่ในกรณีของฉันเราได้พัฒนา codebase หนึ่งเวอร์ชันใน repo A จากนั้นโคลนที่เป็น repo ใหม่ repo B สำหรับเวอร์ชันใหม่ของผลิตภัณฑ์ หลังจากแก้ไขข้อผิดพลาดบางอย่างใน repo A เราจำเป็นต้อง FI การเปลี่ยนแปลงเป็น repo B. จบลงด้วยการทำสิ่งต่อไปนี้:

  1. การเพิ่มรีโมตไปยัง repo B ที่ชี้ไปยัง repo A (git remote add ... )
  2. ดึงสาขาปัจจุบัน (เราไม่ได้ใช้มาสเตอร์เพื่อแก้ไขข้อบกพร่อง) (git pull remoteForRepoA bugFixBranch)
  3. ผลักดันการรวมกันเป็น GitHub

ทำงานรักษา :)


5

คล้ายกับ @Smar แต่ใช้เส้นทางของระบบไฟล์ตั้งค่าเป็น Primary และ SECONDARY:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

จากนั้นคุณผสานเอง

(ดัดแปลงมาจากโพสต์โดย Anar Manafov )


5

รวม 2 repos

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master

4

เมื่อคุณต้องการรวมสามโครงการหรือมากกว่าในการกระทำเดียวให้ทำตามขั้นตอนที่อธิบายไว้ในคำตอบอื่น ๆ ( remote add -f, merge) จากนั้น (อ่อน) รีเซ็ตดัชนีเป็นหัวเก่า (ที่ไม่มีการผสานเกิดขึ้น) เพิ่มไฟล์ทั้งหมด ( git add -A) และคอมมิชชันพวกเขา (ข้อความ "การผสานโครงการ A, B, C, และ D เข้าในโครงการเดียว) ตอนนี้เป็นตอนนี้ยอมรับรหัสของต้นแบบ

ตอนนี้สร้าง.git/info/graftsด้วยเนื้อหาดังต่อไปนี้:

<commit-id of master> <list of commit ids of all parents>

git filter-branch -- head^..head head^2..head head^3..headวิ่ง หากคุณมีสาขามากกว่าสามสาขาให้เพิ่มสาขาตามhead^n..headที่คุณมี --tag-name-filter catแท็กปรับปรุงผนวก อย่าเพิ่มเข้าไปเสมอเพราะอาจทำให้เกิดการเขียนผิดพลาด ดูรายละเอียดเพิ่มเติมได้ที่man page ของ filter-branchค้นหา "grafts"

ตอนนี้ความมุ่งมั่นครั้งล่าสุดของคุณมีผู้ปกครองที่เกี่ยวข้องเชื่อมโยงกัน


1
รอทำไมคุณต้องการรวมสามโครงการเข้าด้วยกันในการคอมมิชชันเดียว?
Steve Bennett

ฉันเริ่มด้วย repository, repository-client และ modeler เป็นโครงการ git แยกกัน นี่เป็นเรื่องยากสำหรับเพื่อนร่วมงานดังนั้นฉันจึงเข้าร่วมในโครงการคอมไพล์เดียว เพื่อให้สามารถ "รูท" ของโปรเจ็กต์ใหม่นั้นมีต้นกำเนิดจากโปรเจ็กต์อื่น ๆ สามโครงการฉันต้องการให้คอมมิชชันเดียวผสาน
koppor

4

หากต้องการรวม A ภายใน B:

1) ในโครงการ A

git fast-export --all --date-order > /tmp/ProjectAExport

2) ในโครงการ B

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

ในสาขานี้ดำเนินการทั้งหมดที่คุณต้องทำและกระทำ

C) จากนั้นกลับไปที่ต้นแบบและการผสานแบบคลาสสิกระหว่างสองสาขา:

git checkout master
git merge projectA

2

ฟังก์ชั่นนี้จะโคลน repo ระยะไกลเข้าไปใน repo dir ท้องถิ่นหลังจากรวมการกระทำทั้งหมดจะถูกบันทึกไว้git logจะแสดงการกระทำเดิมและเส้นทางที่เหมาะสม:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

วิธีใช้:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

หากทำการเปลี่ยนแปลงเพียงเล็กน้อยคุณสามารถย้ายไฟล์ / dirs ของ repo ที่ผสานไปยังพา ธ อื่นเช่น:

repo="https://github.com/example/example"
path="$(pwd)"

tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g' | sed 's/\./_/g')"

git clone "$repo" "$tmp"
cd "$tmp"

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"

ประกาศ
พา ธ แทนที่ด้วยsedดังนั้นโปรดตรวจสอบให้แน่ใจว่าได้ย้ายในเส้นทางที่เหมาะสมหลังจากรวม พารามิเตอร์มีอยู่เพียงตั้งแต่ Git> = 2.9
--allow-unrelated-histories


1

คำสั่งที่กำหนดเป็นทางออกที่ดีที่สุดที่ฉันแนะนำ

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

1

ฉันรวมโครงการด้วยตนเองเล็กน้อยซึ่งทำให้ฉันไม่ต้องจัดการกับข้อขัดแย้งในการรวม

ก่อนอื่นให้คัดลอกไฟล์จากโครงการอื่นตามที่คุณต้องการ

cp -R myotherproject newdirectory
git add newdirectory

ดึงครั้งต่อไปในประวัติศาสตร์

git fetch path_or_url_to_other_repo

บอกคอมไพล์เพื่อรวมในประวัติศาสตร์ของสิ่งที่ดึงล่าสุด

echo 'FETCH_HEAD' > .git/MERGE_HEAD

ตอนนี้ทำตามปกติ แต่คุณจะกระทำ

git commit

0

ฉันต้องการย้ายโครงการขนาดเล็กไปยังไดเรกทอรีย่อยของโครงการที่ใหญ่กว่า ตั้งแต่โครงการขนาดเล็กของฉันไม่ได้มีการกระทำหลาย ๆ git format-patch --output-directory /path/to/patch-dirคนที่ผมใช้ git am --directory=dir/in/project /path/to/patch-dir/*จากนั้นในโครงการขนาดใหญ่ที่ผมใช้

สิ่งนี้ให้ความรู้สึกที่น่ากลัวน้อยกว่าและทำความสะอาดได้ดีกว่าตัวกรองสาขา จริงอยู่ที่มันอาจไม่สามารถใช้ได้กับทุกกรณี

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