วิธีการย้ายไฟล์จาก repit git หนึ่งไปยังอีก (ไม่ใช่โคลน), การเก็บประวัติ


483

ที่เก็บ Git ของเราเริ่มต้นจากส่วนหนึ่งของที่เก็บมอนสเตอร์ SVN เดียวที่แต่ละโครงการมีต้นไม้เป็นของตัวเองเช่น:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

svn mvเห็นได้ชัดว่ามันเป็นเรื่องง่ายสวยที่จะย้ายไฟล์จากที่หนึ่งไปยังอีกด้วย แต่ใน Git แต่ละโครงการอยู่ในพื้นที่เก็บข้อมูลของตัวเองและวันนี้ผมก็ถามว่าจะย้ายไดเรกทอรีย่อยจากไปproject2 project1ฉันทำอะไรเช่นนี้

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

แต่ดูเหมือนว่าจะค่อนข้างซับซ้อน มีวิธีที่ดีกว่าในการทำสิ่งนี้โดยทั่วไป? หรือว่าฉันใช้แนวทางที่ถูกต้อง?

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


1
นั่นเป็นวิธีที่สมเหตุสมผลสำหรับฉัน ฉันไม่สามารถคิดวิธีที่ชัดเจนในการปรับปรุงวิธีการของคุณอย่างมีนัยสำคัญ เป็นเรื่องดีที่ Git ทำให้มันง่ายจริง ๆ (ฉันไม่ต้องการลองย้ายไดเรกทอรีของไฟล์ระหว่างแหล่งเก็บข้อมูลต่าง ๆใน Subversion)
Greg Hewgill

1
@ebneter - ฉันเคยทำสิ่งนี้ (ย้ายประวัติจาก repo svn หนึ่งไปยังอีก) ด้วยตนเองโดยใช้เชลล์สคริปต์ โดยทั่วไปฉันเล่นซ้ำประวัติ (แตกต่างกระทำข้อความบันทึก) จากไฟล์เฉพาะ / dirs ลงในพื้นที่เก็บข้อมูลที่สอง
Adam Monsen

1
ผมสงสัยว่าทำไมคุณไม่ทำgit fetch p2 && git merge p2แทนgit fetch p2 && git branch .. && git merge p2? แก้ไข: เอาล่ะดูเหมือนว่าคุณต้องการรับการเปลี่ยนแปลงในสาขาใหม่ชื่อ p2 ไม่ใช่สาขาปัจจุบัน
Lekensteyn

1
มีวิธีการป้องกัน - สาขากรองจากการทำลายโครงสร้างไดเรกทอรีหรือไม่ ขั้นตอน "git mv" นั้นส่งผลให้เกิดการกระทำที่เต็มไปด้วยการลบไฟล์และการสร้างไฟล์
Edward Falk

1
โปรดทราบว่าเมื่อใช้คอมไพล์ 2.9 การรวมประวัติที่ไม่เกี่ยวข้องจะไม่ได้รับอนุญาตตามค่าเริ่มต้น หากต้องการทำให้มันใช้งานได้ให้เพิ่ม--allow-unrelated-historiesไปที่ล่าสุดgit mergeเพื่อให้ทำงานได้
Scott Berrevoets

คำตอบ:


55

อ๋อกดปุ่มบน--subdirectory-filterของfilter-branchเป็นกุญแจสำคัญ ความจริงที่ว่าคุณใช้มันเป็นหลักพิสูจน์ได้ว่าไม่มีวิธีที่ง่ายกว่า - คุณไม่มีทางเลือกนอกจากการเขียนประวัติใหม่เนื่องจากคุณต้องการที่จะจบด้วยชุดย่อย (เปลี่ยนชื่อ) ของไฟล์และนี่เป็นการเปลี่ยนแฮช เนื่องจากไม่มีคำสั่งมาตรฐาน (เช่นpull) เขียนประวัติใหม่จึงไม่มีวิธีที่คุณสามารถใช้มันเพื่อทำสิ่งนี้ให้สำเร็จ

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


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

1
@rogerdpack: ไม่นี่จะไม่ติดตามไฟล์ผ่านการเปลี่ยนชื่อ ฉันเชื่อว่ามันจะถูกสร้างขึ้น ณ จุดที่มันถูกย้ายไปยังไดเรกทอรีย่อยที่เลือก หากคุณต้องการเลือกเพียงไฟล์เดียวให้ดู--index-filterในfilter-branchmanpage
Cascabel

8
มีสูตรใดที่ฉันสามารถติดตามการเปลี่ยนชื่อได้บ้าง
Night Warrier

ฉันคิดว่าการบำรุงรักษาและการดูแลประวัติเป็นหนึ่งในประเด็นหลักของคอมไพล์
artburkart

287

หากประวัติของคุณมีสติคุณสามารถลบคอมมิทออกเป็นแพทช์และนำไปใช้ในที่เก็บใหม่:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

หรือในหนึ่งบรรทัด

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(นำมาจากเอกสารของ Exherbo )


21
สำหรับไฟล์สามหรือสี่ไฟล์ฉันจำเป็นต้องย้ายสิ่งนี้เป็นทางออกที่ง่ายกว่าคำตอบที่ยอมรับ ในที่สุดฉันก็ตัดทอนเส้นทางออกในไฟล์แพทช์ด้วย find-replace เพื่อให้มันพอดีกับโครงสร้างไดเรกทอรีของ repo ใหม่
Rian Sanderson

8
ฉันได้เพิ่มตัวเลือกเพื่อให้ไฟล์ไบนารี (เช่นภาพ) git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patchนอกจากนี้ยังมีการย้ายข้อมูลได้อย่างถูกต้อง: ทำงานได้โดยไม่มีปัญหา AFAICT
Emmanuel Touzery

35
ในขั้นตอนการใช้งานฉันใช้--committer-date-is-author-dateตัวเลือกเพื่อเก็บรักษาวันที่ส่งมอบดั้งเดิมแทนวันที่ย้ายไฟล์
darrenmc

6
ผสานกระทำในประวัติศาสตร์ทำลายคำสั่ง "am" คุณสามารถเพิ่ม "-m --first-parent" ลงในคำสั่ง git log ด้านบนจากนั้นก็ใช้งานได้สำหรับฉัน
GáborLipták

6
@Daniel Golden ฉันจัดการเพื่อแก้ไขปัญหาเกี่ยวกับไฟล์ที่ถูกย้าย (ซึ่งเป็นผลมาจากข้อผิดพลาดในgit logเพื่อที่จะไม่ทำงานกับทั้งสอง--followและ--reverseถูกต้อง) ผมใช้คำตอบนี้และที่นี่เป็นสคริปต์ที่สมบูรณ์แบบที่ผมใช้ตอนนี้เพื่อย้ายไฟล์
tsayen

75

หลังจากลองใช้วิธีการต่าง ๆ ในการย้ายไฟล์หรือโฟลเดอร์จากที่เก็บ Git หนึ่งไปยังอีกที่หนึ่งซึ่งดูเหมือนว่าจะทำงานได้อย่างน่าเชื่อถือแสดงไว้ด้านล่าง

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

ขั้นตอนที่หนึ่ง

  1. ทำสำเนาของที่เก็บ A ตามขั้นตอนต่อไปนี้ทำการเปลี่ยนแปลงที่สำคัญในสำเนานี้ซึ่งคุณไม่ควรผลักดัน!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. ซีดีลงไป

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. ลบลิงก์ไปยังที่เก็บต้นฉบับเพื่อหลีกเลี่ยงการเปลี่ยนแปลงระยะไกลโดยไม่ตั้งใจ (เช่นการกด)

    git remote rm origin
    
  4. ดูประวัติและไฟล์ของคุณโดยลบสิ่งที่ไม่ได้อยู่ในไดเรกทอรี 1 ผลลัพธ์คือเนื้อหาของไดเรกทอรี 1 ที่กระจายออกไปสู่ฐานของพื้นที่เก็บข้อมูล A

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. สำหรับการย้ายไฟล์เดียวเท่านั้น: ผ่านสิ่งที่เหลือและลบทุกอย่างยกเว้นไฟล์ที่ต้องการ (คุณอาจต้องลบไฟล์ที่คุณไม่ต้องการด้วยชื่อและคอมมิทเดียวกัน)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

ขั้นตอนที่สอง

  1. ขั้นตอนการล้างข้อมูล

    git reset --hard
    
  2. ขั้นตอนการล้างข้อมูล

    git gc --aggressive
    
  3. ขั้นตอนการล้างข้อมูล

    git prune
    

คุณอาจต้องการนำเข้าไฟล์เหล่านี้ไปยังแหล่งเก็บข้อมูล B ภายในไดเรกทอรีไม่ใช่ราก:

  1. ทำไดเรกทอรีนั้น

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. ย้ายไฟล์ไปยังไดเรกทอรีนั้น

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. เพิ่มไฟล์ไปยังไดเรกทอรีนั้น

    git add .
    
  4. ยืนยันการเปลี่ยนแปลงของคุณและเราพร้อมที่จะรวมไฟล์เหล่านี้เข้ากับที่เก็บใหม่

    git commit
    

ด่านที่สาม

  1. ทำสำเนาของที่เก็บ B หากคุณยังไม่มี

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (สมมติว่า FOLDER_TO_KEEP เป็นชื่อของที่เก็บใหม่ที่คุณกำลังคัดลอกไป)

  2. ซีดีลงไป

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. สร้างการเชื่อมต่อระยะไกลไปยังที่เก็บ A เป็นสาขาในพื้นที่เก็บข้อมูล B

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. ดึงจากสาขานี้ (มีเฉพาะไดเรกทอรีที่คุณต้องการย้าย) ไปยังพื้นที่เก็บข้อมูล B

    git pull repo-A-branch master --allow-unrelated-histories
    

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

  5. สุดท้ายคุณอาจต้องการล้างข้อมูลด้วยการลบการเชื่อมต่อระยะไกลไปยังที่เก็บ A

    git remote rm repo-A-branch
    
  6. ดันและคุณพร้อมแล้ว

    git push
    

ฉันได้ทำตามขั้นตอนส่วนใหญ่ที่ระบุไว้ที่นี่ แต่ดูเหมือนว่าจะคัดลอกเฉพาะประวัติกระทำของไฟล์หรือ dir จากต้นแบบ (และไม่ได้มาจากสาขาอื่น ๆ ) นั่นถูกต้องใช่ไหม?
Bao-Long Nguyen-Trong

ฉันคิดว่าถูกต้องและคุณต้องทำตามขั้นตอนที่คล้ายกันสำหรับสาขาใด ๆ ที่คุณต้องการย้ายไฟล์หรือโฟลเดอร์เช่น เปลี่ยนเป็นสาขาเช่น MyBranch ใน repository A, filter-branch เป็นต้นจากนั้นคุณจะต้อง "git pull repo-A-branch MyBranch" ใน repository B
mcarans

ขอบคุณสำหรับการตอบกลับ. คุณรู้หรือไม่ว่าการย้ายแท็กในกิ่งจะถูกย้ายด้วยหรือไม่
Bao-Long Nguyen-Trong

ฉันกลัวฉันไม่รู้ แต่จะเดาได้เลยว่าพวกเขาจะเป็น
mcarans

1
@ carcar แต่น่าเสียดายที่นี่ไม่ใช่วิธีที่เชื่อถือได้แม้ว่ามันจะดูเหมือน มันทนทุกข์ทรมานจากปัญหาเดียวกันกับการแก้ปัญหาอื่น ๆ ทั้งหมด - มันไม่ได้เก็บประวัติการเปลี่ยนชื่อในอดีต ในกรณีของฉันกระทำก่อนอื่นคือเมื่อฉันเปลี่ยนชื่อไดเรกทอรี / ไฟล์ ทุกสิ่งที่เหนือกว่าจะสูญหายไป
xZero

20

ฉันพบนี้มีประโยชน์มาก มันเป็นวิธีที่ง่ายมากที่คุณสร้างแพทช์ที่ใช้กับ repo ใหม่ ดูหน้าเชื่อมโยงสำหรับรายละเอียดเพิ่มเติม

มีเพียงสามขั้นตอน (คัดลอกมาจากบล็อก):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

ปัญหาเดียวที่ฉันมีคือฉันไม่สามารถใช้โปรแกรมแก้ไขทั้งหมดพร้อมกันได้

git am --3way <patch-directory>/*.patch

ใน Windows ฉันได้รับข้อผิดพลาดของ InvalidArgument ดังนั้นฉันจึงต้องใช้แผ่นแปะทั้งหมดทีละแผ่น


ไม่ได้ผลสำหรับฉันเนื่องจากในบางจุด sha-hash หายไป สิ่งนี้ช่วยฉันได้: stackoverflow.com/questions/17371150/…
dr0i

ต่างจากวิธี "บันทึก git" ตัวเลือกนี้ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน! ขอบคุณ!
AlejandroVD

1
พยายามใช้แนวทางที่แตกต่างสำหรับการย้ายโครงการไปยัง repo ใหม่ นี่เป็นคนเดียวที่ทำงานให้ฉัน ไม่สามารถเชื่อได้ว่างานทั่วไปดังกล่าวจะต้องมีความซับซ้อน
Chris_D_Turk

ขอบคุณสำหรับการแบ่งปันบล็อกรอสส์เฮ็นดของ วิธีนี้ใช้ได้ผลสำหรับฉัน
Kaushik Acharya

1
นี่เป็นวิธีการแก้ปัญหาที่หรูหรามาก แต่อีกครั้งมันได้รับความทุกข์ทรมานจากปัญหาเดียวกันกับการแก้ปัญหาอื่น ๆ ทั้งหมด - มันจะไม่เก็บประวัติการเปลี่ยนชื่อที่ผ่านมา
xZero

6

รักษาชื่อไดเรคทอรี่

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

โซลูชันของฉันคือการใช้ tree-filter และเพียงแค่ลบไฟล์และไดเรกทอรีที่ไม่ต้องการออกจากโคลนชั่วคราวของแหล่งเก็บข้อมูลจากนั้นดึงจากโคลนนั้นลงในพื้นที่เก็บข้อมูลเป้าหมายของฉันใน 5 ขั้นตอนง่ายๆ

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

สคริปต์นี้จะไม่ทำการแก้ไขใด ๆ กับ repo เดิมของคุณ หากไม่มี repo ปลายทางที่ระบุในไฟล์แผนที่สคริปต์นี้จะพยายามสร้าง
Chetabahana

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

6

หนึ่งฉันมักจะใช้ที่นี่เป็นhttp://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ ง่ายและรวดเร็ว

เพื่อให้สอดคล้องกับมาตรฐาน stackoverflow นี่คือขั้นตอน:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

5

คำตอบนี้ให้คำสั่งที่น่าสนใจตามgit amและนำเสนอโดยใช้ตัวอย่างทีละขั้นตอน

วัตถุประสงค์

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

ขั้นตอน

  1. แยกประวัติในรูปแบบอีเมลโดยใช้
    git log --pretty=email -p --reverse --full-index --binary
  2. จัดโครงสร้างไฟล์ต้นไม้ใหม่และอัปเดตการเปลี่ยนแปลงชื่อไฟล์ในประวัติ [ตัวเลือก]
  3. ใช้ประวัติใหม่โดยใช้ git am

1. แยกประวัติในรูปแบบอีเมล

ตัวอย่าง: สารสกัดจากประวัติศาสตร์file3, file4และfile5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

ทำความสะอาดปลายทางไดเรกทอรีชั่วคราว

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

ทำความสะอาดแหล่งที่มา repo ของคุณ

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

แยกประวัติของแต่ละไฟล์ในรูปแบบอีเมล

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

แต่น่าเสียดายที่ตัวเลือก--followหรือไม่สามารถใช้ร่วมกับ--find-copies-harder --reverseนี่คือเหตุผลที่ประวัติถูกตัดเมื่อเปลี่ยนชื่อไฟล์ (หรือเมื่อเปลี่ยนชื่อไดเรกทอรีแม่)

หลัง: ประวัติชั่วคราวในรูปแบบอีเมล

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2. จัดระเบียบไฟล์ต้นไม้ใหม่และอัปเดตการเปลี่ยนแปลงชื่อไฟล์ในประวัติ [ตัวเลือก]

สมมติว่าคุณต้องการย้ายไฟล์ทั้งสามนี้ใน repo อื่น (อาจเป็น repo เดียวกัน)

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

ดังนั้นจัดระเบียบไฟล์ของคุณใหม่:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

ประวัติชั่วคราวของคุณคือตอนนี้:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

เปลี่ยนชื่อไฟล์ในประวัติด้วย:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

หมายเหตุ:สิ่งนี้จะเขียนประวัติเพื่อสะท้อนการเปลี่ยนแปลงของพา ธ และชื่อไฟล์
      (เช่นการเปลี่ยนตำแหน่ง / ชื่อใหม่ภายใน repo ใหม่)


3. ใช้ประวัติใหม่

ธุรกรรมซื้อคืนอื่น ๆ ของคุณคือ:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

ใช้การคอมมิตจากไฟล์ประวัติชั่วคราว:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

repo อื่นของคุณคือตอนนี้:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

ใช้git statusเพื่อดูจำนวนการกระทำที่พร้อมจะผลักดัน :-)

หมายเหตุ:เมื่อประวัติถูกเขียนใหม่เพื่อสะท้อนถึงการเปลี่ยนเส้นทางและชื่อไฟล์:
      (เช่นเมื่อเปรียบเทียบกับตำแหน่ง / ชื่อภายใน repo หน้าที่แล้ว)

  • ไม่จำเป็นต้องgit mvเปลี่ยนตำแหน่ง / ชื่อไฟล์
  • ไม่จำเป็นต้องgit log --followเข้าถึงประวัติเต็ม

เคล็ดลับพิเศษ: ตรวจจับไฟล์ที่ถูกเปลี่ยนชื่อ / ย้ายภายใน repo ของคุณ

หากต้องการแสดงรายการไฟล์ที่ถูกเปลี่ยนชื่อ:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

การปรับแต่งเพิ่มเติมได้ที่: คุณสามารถดำเนินการคำสั่งgit logโดยใช้ตัวเลือกหรือ--find-copies-harder --reverseนอกจากนี้คุณยังสามารถลบสองคอลัมน์แรกโดยใช้cut -f3-และ grepping pattern ที่สมบูรณ์ '{. * =>. *}'

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

มีคันที่คล้ายกันเพื่อเกา (altough เฉพาะสำหรับบางไฟล์ของพื้นที่เก็บข้อมูลที่กำหนด) สคริปต์นี้พิสูจน์แล้วว่าเป็นประโยชน์จริง ๆ : git-import

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

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

ซึ่งนำไปใช้กับที่เก็บใหม่:

cd new_repo
git am "$temp"/*.patch 

สำหรับรายละเอียดโปรดค้นหา:


2

ลองสิ่งนี้

cd repo1

การดำเนินการนี้จะลบไดเรกทอรีทั้งหมดยกเว้นไดเรกทอรีที่กล่าวถึงโดยเก็บรักษาประวัติสำหรับไดเรกทอรีเหล่านี้เท่านั้น

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

ตอนนี้คุณสามารถเพิ่ม repo ใหม่ของคุณใน git remote และผลักไปที่นั้น

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

เพิ่ม-fเพื่อเขียนทับ


คำเตือน: git-filter-branch มีจำนวนมากเกินไปของ gotchas สร้างประวัติ mangled เขียนใหม่ กดปุ่ม Ctrl-C ก่อนดำเนินการยกเลิกจากนั้นใช้เครื่องมือตัวกรองทางเลือกเช่น 'git filter-repo' ( github.com/newren/git-filter-repo ) แทน ดูหน้าคู่มือสาขากรองสำหรับรายละเอียดเพิ่มเติม; หากต้องการเตือนคำเตือนนี้ให้ตั้งค่า FILTER_BRANCH_SQUELCH_WARNING = 1
โคลิน

1

ใช้แรงบันดาลใจจากhttp://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ฉันสร้างฟังก์ชัน Powershell สำหรับทำสิ่งเดียวกันซึ่งมี ทำงานได้ดีสำหรับฉันจนถึงตอนนี้:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

การใช้งานสำหรับตัวอย่างนี้:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

หลังจากที่คุณทำเช่นนี้คุณสามารถจัดระเบียบไฟล์ในmigrate-from-project2สาขาอีกครั้งก่อนที่จะรวม


1

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

มันสามารถย้ายโฟลเดอร์โดยพลการ/path/to/fooจากrepo1ไป/some/other/folder/barยังrepo2(เส้นทางของโฟลเดอร์อาจเหมือนหรือแตกต่างกันระยะทางจากโฟลเดอร์รูทอาจแตกต่างกัน)

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

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

หากไม่มีการขัดแย้งชื่อไฟล์คุณเพียงแค่ต้องgit commitสิ้นสุดเพื่อทำการผสานให้เสร็จสิ้น

ข้อเสียคือมันจะไม่ติดตามการเปลี่ยนชื่อไฟล์ (นอกREWRITE_FROMโฟลเดอร์) ในคำขอ repo - ดึงแหล่งที่มายินดีต้อนรับบน GitHub เพื่อรองรับ

ลิงค์ GitHub: git-move-folder-between-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

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

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

ในสองขั้นตอนนั้นฉันสามารถทำให้สาขาของ repo อื่น ๆ ปรากฏใน repo เดียวกัน

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

git br --set-upstream-to=origin/mainline

ตอนนี้มันทำงานราวกับว่ามันเป็นเพียงอีกสาขาหนึ่งที่ฉันผลักดันกับ repo เดียวกัน


0

หากเส้นทางสำหรับไฟล์ในคำถามที่จะเหมือนกันในสอง Repos และคุณต้องการที่จะนำกว่าเพียงหนึ่งไฟล์หรือชุดเล็ก ๆ git cherry-pickของไฟล์ที่เกี่ยวข้องวิธีหนึ่งที่ง่ายต่อการทำเช่นนี้คือการใช้งาน

ขั้นตอนแรกคือการกระทำนำมาจาก repo อื่น ๆ ลงใน repo git fetch <remote-url>ท้องถิ่นของคุณเองโดยใช้ สิ่งนี้จะปล่อยให้FETCH_HEADชี้ไปที่หัวกระทำจาก repo อื่น ๆ ; ถ้าคุณต้องการที่จะรักษาอ้างอิงถึงที่กระทำหลังจากที่คุณได้กระทำการดึงข้อมูลอื่น ๆ git tag other-head FETCH_HEADที่คุณอาจต้องการที่จะติดแท็กด้วย

จากนั้นคุณจะต้องสร้างคอมมิทเริ่มต้นสำหรับไฟล์นั้น (หากไม่มี) หรือคอมมิชชันที่จะนำไฟล์ไปสู่สถานะที่สามารถแก้ไขได้ด้วยคอมมิชชันแรกจาก repo อื่น ๆ ที่คุณต้องการ สามารถทำสิ่งนี้ได้ด้วย a git cherry-pick <commit-0>ถ้าcommit-0แนะนำไฟล์ที่คุณต้องการหรือคุณอาจต้องสร้างคอมมิทด้วยมือ เพิ่ม-nไปที่ตัวเลือก cherry-pick หากคุณต้องการแก้ไขการคอมมิทเริ่มต้นเช่นปล่อยไฟล์จากการคอมมิทที่คุณไม่ต้องการนำเข้า

หลังจากนั้นคุณสามารถดำเนินการgit cherry-pickต่อได้ตามปกติอีกครั้งโดยใช้-nเมื่อจำเป็น ในกรณีที่ง่ายที่สุด (กระทำทั้งหมดเป็นสิ่งที่คุณต้องการและนำไปใช้อย่างหมดจด) git cherry-pick <commit-1> <commit-2> <commit-3> ...คุณสามารถให้รายการเต็มรูปแบบของการกระทำในบรรทัดคำสั่งเชอร์รี่รับ:


0

สิ่งนี้จะง่ายขึ้นโดยใช้ git-filter-repo

เพื่อที่จะย้ายproject2/sub/dirไปที่project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

ในการติดตั้งเครื่องมือเพียง: pip3 install git-filter-repo ( รายละเอียดเพิ่มเติมและตัวเลือกใน README )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

วิธีการด้านล่างเพื่อย้าย GIT Stash ของฉันไปที่ GitLab โดยรักษาทุกสาขาและรักษาประวัติ

โคลนที่เก็บเก่าเป็นโลคัล

git clone --bare <STASH-URL>

สร้างที่เก็บว่างใน GitLab

git push --mirror <GitLab-URL>

ด้านบนที่ฉันทำเมื่อเราย้ายรหัสของเราจากที่ซ่อนไปยัง GitLab และมันทำงานได้ดีมาก

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