รวมสองที่เก็บ Git โดยไม่ทำลายประวัติไฟล์


226

ฉันต้องรวมที่เก็บ Git สองอันเข้ากับที่เก็บใหม่และที่สาม ฉันพบคำอธิบายมากมายเกี่ยวกับวิธีการทำเช่นนี้โดยใช้ทรีย่อย (ตัวอย่างเช่นคำตอบของ Jakub Narębskiเกี่ยวกับคุณผสานที่เก็บ Git สองอันได้อย่างไร ) และทำตามคำแนะนำเหล่านั้นส่วนใหญ่ทำงานยกเว้นว่าเมื่อฉันคอมทรีทรีผสานไฟล์ทั้งหมด จากที่เก็บเก่าจะถูกบันทึกเป็นไฟล์ที่เพิ่มใหม่ ฉันสามารถดูประวัติการส่งมอบจากที่เก็บข้อมูลเก่าเมื่อฉันทำgit logแต่ถ้าฉันทำgit log <file>มันจะแสดงการกระทำเพียงครั้งเดียวสำหรับไฟล์นั้น - การรวมทรีย่อย ตัดสินจากความเห็นในคำตอบข้างต้นฉันไม่ได้อยู่คนเดียวในการเห็นปัญหานี้ แต่ฉันไม่พบวิธีแก้ไขปัญหาที่เผยแพร่

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


ฉันไม่ได้ใช้ Git แต่ใน Mercurial ก่อนอื่นฉันต้องทำการแปลงหากจำเป็นต้องแก้ไขพา ธ ไฟล์ของ repos ที่จะรวมและจากนั้นบังคับให้ repo หนึ่งเข้าไปในเป้าหมายเพื่อรับเซ็ตการแก้ไขแล้วทำ ผสานสาขาที่แตกต่างกัน สิ่งนี้ได้รับการทดสอบและใช้งานแล้ว) บางทีนี่อาจช่วยในการหาวิธีแก้ปัญหาสำหรับ Git เช่นกัน ... เปรียบเทียบกับวิธีทรี - ผสานฉันเดาว่าขั้นตอนการแปลงนั้นแตกต่างกันโดยที่ประวัติถูกเขียนใหม่แทนการทำแผนที่เส้นทาง (ถ้าฉันเข้าใจ ได้อย่างถูกต้อง) สิ่งนี้จะทำให้แน่ใจว่าการผสานราบรื่นโดยไม่มีการจัดการเส้นทางไฟล์เป็นพิเศษ
Lucero

ฉันยังพบคำถามนี้ที่เป็นประโยชน์stackoverflow.com/questions/1683531/ …
nacross

ฉันสร้างคำถามติดตามผล อาจน่าสนใจ: รวมสองที่เก็บ Git และเก็บประวัติหลัก: stackoverflow.com/questions/42161910/…
Dimitri Dewaele

โซลูชันอัตโนมัติที่ใช้งานได้ดีสำหรับฉันคือstackoverflow.com/a/30781527/239408
xverges

คำตอบ:


269

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

นี่คือตัวอย่างของสคริปต์ Powershell เพื่อกาวสองคลังข้อมูลเข้าด้วยกัน:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

เห็นได้ชัดว่าคุณสามารถรวม old_b เข้ากับ old_a (ซึ่งจะกลายเป็น repo รวมใหม่) หากคุณต้องการทำเช่นนั้น - แก้ไขสคริปต์ให้เหมาะสม

หากคุณต้องการนำเอาฟีเจอร์ที่กำลังอยู่ในระหว่างดำเนินการให้ใช้สิ่งนี้:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

นั่นเป็นเพียงส่วนหนึ่งที่ไม่ชัดเจนของกระบวนการนั่นไม่ใช่การรวมทรีย่อย แต่เป็นการโต้เถียงกับการเวียนเวียนแบบเวียนที่บอก Git ว่าเราเปลี่ยนชื่อเป้าหมายและช่วยให้ Git ทุกอย่างถูกต้อง

ผมเขียนขึ้นเล็กน้อยคำอธิบายรายละเอียดเพิ่มเติมที่นี่


16
วิธีแก้ปัญหานี้ใช้git mvไม่ได้ผลดีนัก เมื่อคุณใช้git logไฟล์ที่ถูกย้ายในภายหลังคุณจะได้รับการยินยอมจากการย้าย ประวัติก่อนหน้านี้ทั้งหมดจะสูญหายไป นี่เป็นเพราะgit mvจริงๆgit rm; git addแต่ในขั้นตอนเดียว
mholm815

15
เหมือนกับการดำเนินการย้าย / เปลี่ยนชื่ออื่น ๆ ใน Git: จากบรรทัดคำสั่งคุณสามารถรับประวัติทั้งหมดด้วยการทำgit log --followหรือเครื่องมือ GUI ทั้งหมดทำเพื่อคุณโดยอัตโนมัติ ด้วยการรวมทรีย่อยคุณไม่สามารถรับประวัติสำหรับแต่ละไฟล์เท่าที่ฉันรู้ดังนั้นวิธีนี้ดีกว่า
Eric Lee

3
@EricLee เมื่อ repo old_b ถูกรวมเข้าด้วยกันฉันได้รับความขัดแย้งจำนวนมาก คาดหวังหรือไม่? ฉันได้รับความสับสน (เปลี่ยนชื่อ / ลบ)
Jon

9
เมื่อฉันพยายาม "dir -exclude old_a |% {git mv $ _. ชื่อ old_a}" ฉันได้รับ sh.exe ": dir: ไม่พบคำสั่งและ sh.exe": git: ไม่พบคำสั่ง ใช้งานนี้: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
George

5
นี่คือ1(จำนวนหนึ่ง) สำหรับlsและทุน 'ตา' xargsสำหรับ ขอบคุณสำหรับเคล็ดลับนี้!
Dominique Vial

149

นี่คือวิธีที่จะไม่เขียนประวัติใด ๆ ดังนั้นรหัสการยืนยันทั้งหมดจะยังคงใช้ได้ ผลลัพธ์สุดท้ายคือไฟล์ของ repo ที่สองจะสิ้นสุดในไดเรกทอรีย่อย

  1. เพิ่ม repo ตัวที่สองเป็นรีโมต:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. ตรวจสอบให้แน่ใจว่าคุณได้ดาวน์โหลดข้อผูกพันที่สองทั้งหมด:

    git fetch secondrepo
    
  3. สร้างสาขาท้องถิ่นจากสาขาของ repo ที่สอง:

    git branch branchfromsecondrepo secondrepo/master
    
  4. ย้ายไฟล์ทั้งหมดไปไว้ในไดเรกทอรีย่อย:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. รวมสาขาที่สองเข้ากับสาขาหลักของ repo แรก:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

ที่เก็บของคุณจะมีการคอมมิทมากกว่าหนึ่งรูท แต่นั่นไม่น่าจะมีปัญหา


1
ขั้นตอนที่ 2 ใช้งานไม่ได้สำหรับฉัน: ร้ายแรง: ไม่ใช่ชื่อวัตถุที่ถูกต้อง: 'secondrepo / master'
Keith

@Keith: ตรวจสอบให้แน่ใจว่าคุณได้เพิ่ม repo ที่สองเป็นรีโมตชื่อ "secondrepo" และ repo นั้นมีสาขาที่ชื่อว่า "master" (คุณสามารถดูกิ่งบน repo ระยะไกลด้วยคำสั่งgit remote show secondrepo)
Flimm

ฉันต้องทำการดึงเพื่อนำมาลงเช่นกัน ในระหว่างวันที่ 1 ถึง 2 ฉันได้ทำการเรียกครั้งที่สอง
sksamuel

@monkjack: ฉันได้แก้ไขคำตอบของฉันเพื่อรวมขั้นตอนการเรียก git อย่าลังเลที่จะแก้ไขคำตอบของคุณในอนาคต
Flimm

4
@MartijnHeemels สำหรับรุ่นเก่าของ Git --allow-unrelated-historiesเพียงงด ดูประวัติของคำตอบนี้
Flimm

8

ไม่กี่ปีที่ผ่านมาและมีโซลูชันที่ได้รับการโหวตอย่างดี แต่ฉันต้องการแบ่งปันของฉันเพราะมันแตกต่างกันเล็กน้อยเพราะฉันต้องการรวมที่เก็บข้อมูลระยะไกล 2 แห่งเข้าด้วยกันใหม่โดยไม่ต้องลบประวัติจากที่เก็บก่อนหน้านี้

  1. สร้างพื้นที่เก็บข้อมูลใหม่ใน Github

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

  2. ดาวน์โหลด repo ที่เพิ่งสร้างใหม่และเพิ่ม repository ระยะไกลเก่า

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. ดึงไฟล์ทั้งหมดจาก repo เก่าเพื่อสร้างสาขาใหม่

    git fetch OldRepo
    git branch -a
    

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

  4. ในสาขาหลักทำการผสานเพื่อรวม repo เก่ากับที่สร้างขึ้นใหม่

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

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

  5. สร้างโฟลเดอร์ใหม่เพื่อเก็บเนื้อหาที่สร้างขึ้นใหม่ทั้งหมดที่เพิ่มจาก OldRepo และย้ายไฟล์ไปยังโฟลเดอร์ใหม่นี้

  6. สุดท้ายคุณสามารถอัปโหลดไฟล์จาก repos รวมและลบ OldRepo จาก GitHub อย่างปลอดภัย

หวังว่าสิ่งนี้จะเป็นประโยชน์สำหรับทุกคนที่เกี่ยวข้องกับการรวมที่เก็บข้อมูลระยะไกล


1
นี่เป็นทางออกเดียวที่ทำงานสำหรับฉันเพื่อรักษาประวัติคอมไพล์ อย่าลืมที่จะเอาการเชื่อมโยงระยะไกลเพื่อ repo git remote rm OldRepoเก่าด้วย
Harubiyori

7

โปรดดูการใช้งาน

git rebase --root --preserve-merges --onto

เพื่อเชื่อมโยงประวัติศาสตร์สองช่วงแรกในชีวิต

หากคุณมีเส้นทางที่ทับซ้อนกันให้แก้ไขด้วย

git filter-branch --index-filter

เมื่อคุณใช้บันทึกตรวจสอบให้แน่ใจว่าคุณ "ค้นหาสำเนายากขึ้น" ด้วย

git log -CC

ด้วยวิธีนี้คุณจะพบการเคลื่อนไหวของไฟล์ในพา ธ


เอกสาร Git ไม่แนะนำให้รีบูต
Stephen Turner

7

ฉันเปลี่ยนวิธีแก้ปัญหาจาก @Flimm ให้เป็นgit aliasแบบนี้ (เพิ่มลงในของฉัน~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"

12
แค่อยากรู้อยากเห็น: คุณทำเช่นนี้บ่อยพอที่จะต้องมีนามแฝงหรือไม่?
Parker Coates

1
ไม่ฉันไม่จำไม่ได้ แต่จะจำได้ว่าทำอย่างไรจึงมีนามแฝงเป็นวิธีที่ฉันจะจำได้
Fredrik Erlandsson

1
ใช่ .. แต่ลองเปลี่ยนคอมพิวเตอร์และลืมที่จะย้ายนามแฝงของคุณ)
Quetzalcoatl

1
มูลค่าของ$GIT_PREFIXอะไร
neowulf33

github.com/git/git/blob/… 'GIT_PREFIX' ถูกตั้งค่าเป็นคืนโดยการเรียกใช้ 'git rev-parse --show-prefix' จากไดเรกทอรีปัจจุบันดั้งเดิม ดู linkgit: git-rev-parse [1]
Fredrik Erlandsson

3

ฟังก์ชั่นนี้จะลอกแบบ repo ระยะไกลเข้าสู่ repo ท้องถิ่น:

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

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

กำไร!


ฉันใช้ zsh มากกว่าทุบตีและ v2.13.0 ของ git ไม่ว่าฉันจะพยายามทำอะไรฉันก็ไม่สามารถgit filter-branch --index-filterทำงานได้ โดยทั่วไปฉันได้รับข้อความแสดงข้อผิดพลาดว่าไม่มีไฟล์ดัชนีใหม่ เสียงระฆังดังขึ้นไหม?
Patrick Beard

@PatrickBeard ฉันไม่ทราบ zsh คุณสามารถสร้างไฟล์แยกgit-add-repo.shพร้อมฟังก์ชั่นด้านบนในตอนท้ายของไฟล์ใส่บรรทัดgit-add-repo "$@"นี้ หลังจากนั้นคุณสามารถใช้มันจาก zsh like cd current/git/packageและbash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
Andrey Izman

ปัญหาที่ได้รับการกล่าวถึงที่นี่: stackoverflow.com/questions/7798142/... ล้มเหลวในบางครั้งเพื่อให้คุณมีการเพิ่มmv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" if test
Patrick Beard

1
ฉันจะไม่ใช้วิธีนี้! ฉันลองใช้สคริปต์อย่างไร้เดียงสาและใช้คำต่อคำ (ฉันสามารถตำหนิตัวเองในส่วนนั้นได้) และมันขัดขวางการทำธุรกรรมในท้องถิ่นของฉัน ประวัติดูส่วนใหญ่ถูกต้อง แต่การทำ git push กลับไปที่ Github ทำให้เกิด "RPC ที่ล้มเหลวล้มเหลว curl 55 SSL_write () กลับ SYSCALL, errno = 32" ฉันพยายามซ่อมมัน แต่มันก็หักไม่ได้ ฉันสิ้นสุดต้องสร้างสิ่งต่าง ๆ ใน repo ท้องถิ่นใหม่
Mason Freed

@MasonFreed สคริปต์นี้สร้างประวัติคอมไพล์ใหม่ด้วยการผสมผสานของ repos ทั้งสองดังนั้นจึงไม่สามารถผลักไปที่ repo เก่าได้มันต้องสร้างอันใหม่หรือดันด้วยปุ่มบังคับหมายความว่ามันเขียน repo ของคุณบนเซิร์ฟเวอร์
Andrey Izman

2

ทำตามขั้นตอนเพื่อฝัง repo หนึ่งเข้าไปใน repo อื่นโดยมีประวัติ git เดียวโดยรวมทั้งประวัติ git

  1. ลอกเลียนทั้ง repos ที่คุณต้องการผสาน

git clone git@github.com: ผู้ใช้ / parent-repo.git

git clone git@github.com: ผู้ใช้ / child-repo.git

  1. ไปที่ repo ลูก

cd child-repo /

  1. เรียกใช้คำสั่งด้านล่างแทนที่เส้นทางmy/new/subdir(3 เกิดขึ้น) ด้วยโครงสร้างไดเรกทอรีที่คุณต้องการให้ลูก repo

git filter-branch - ว่างเปล่าใหม่ --tree-filter 'ถ้า [! - ฉัน / ใหม่ / ตำบล]; mkdir -p ของฉัน / ใหม่ / subdit git ls-tree - ชื่อเท่านั้น $ GIT_COMMIT | xargs -I ไฟล์ mv ไฟล์ของฉัน / ใหม่ / subdir fi '

  1. ไปที่ repo แม่

cd ../parent-repo/

  1. เพิ่มรีโมตไปยัง repo พาเรนต์ชี้พา ธ ไปยัง repo ลูก

git remote เพิ่ม child-remote ../child-repo/

  1. เรียก repo ลูก

git ดึงข้อมูลเด็ก - ระยะไกล

  1. ผสานประวัติศาสตร์

git merge --allow-unrelated-history child-remote / master

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

บทความด้านล่างช่วยฉันในการฝัง repo หนึ่งเข้าไปใน repo อีกอันหนึ่งโดยมีประวัติ git เดียวโดยรวมทั้งประวัติ git

http://ericlathrop.com/2014/01/combining-git-repositories/

หวังว่านี่จะช่วยได้ Happy Coding!


ขั้นตอนที่ 3 ล้มเหลวสำหรับฉันด้วยข้อผิดพลาดทางไวยากรณ์ Semi-colons หายไป แก้ไขgit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
ยูริแอล

1

สมมติว่าคุณต้องการรวมพื้นที่เก็บข้อมูลaเข้าด้วยกันb(ฉันสมมติว่ามันอยู่ติดกัน):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

ในกรณีที่คุณต้องการใส่aในไดเรกทอรีย่อยให้ทำดังต่อไปนี้ก่อนคำสั่งด้านบน:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

สำหรับนี้คุณจำเป็นต้องgit-filter-repoติดตั้ง ( filter-branchเป็นกำลังใจ )

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

เพิ่มเติมเกี่ยวกับมันนี่

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