เป็นไปได้ไหมที่จะย้าย / เปลี่ยนชื่อไฟล์ใน Git และรักษาประวัติไว้?


667

ฉันต้องการเปลี่ยนชื่อ / ย้ายแผนผังย่อยของโครงการใน Git ย้ายจาก

/project/xyz

ถึง

/components/xyz

ถ้าฉันใช้ที่ราบgit mv project componentsจากนั้นประวัติศาสตร์ทั้งหมดที่กระทำเพื่อให้xyz projectหลงทาง มีวิธีในการเคลื่อนย้ายสิ่งนี้เช่นที่รักษาประวัติไว้หรือไม่?



2
ฉันแค่ต้องการที่จะทราบว่าฉันเพิ่งทดสอบการย้ายไฟล์ผ่านระบบไฟล์และหลังจากกระทำ (ผ่านทาง Intellij) ฉันสามารถดูประวัติทั้งหมด (รวมถึงประวัติเมื่อมันอยู่ในตำแหน่งอื่น) เมื่อดูประวัติ (อีกครั้งใน intellij) ฉันคิดว่า intellij ไม่ได้ทำอะไรเป็นพิเศษโดยเฉพาะอย่างยิ่งในการทำเช่นนั้นดังนั้นจึงเป็นเรื่องดีที่ได้รู้ว่าอย่างน้อยที่สุดประวัติศาสตร์ก็สามารถสืบย้อนกลับไปได้
BT

สำหรับกฎที่ตามด้วย Git เมื่อตรวจจับการเปลี่ยนชื่อไดเรกทอรีดูคำตอบของฉันด้านล่าง
VonC

ฉันเขียนคำตอบที่นี่ ฉันหวังว่ามันจะได้ผล stackoverflow.com/questions/10828267/…
Mahmut EFE

คำตอบ:


651

Git ตรวจจับการเปลี่ยนชื่อแทนที่จะใช้การกระทำนั้นต่อเนื่องดังนั้นไม่ว่าคุณจะใช้git mvหรือmvไม่ก็ตาม

logคำสั่งใช้--followอาร์กิวเมนต์ที่ยังคงประวัติศาสตร์ก่อนที่จะดำเนินการเปลี่ยนชื่อคือจะค้นหาเนื้อหาคล้ายกันโดยใช้การวิเคราะห์พฤติกรรมนี้:

http://git-scm.com/docs/git-log

หากต้องการค้นหาประวัติแบบเต็มให้ใช้คำสั่งต่อไปนี้:

git log --follow ./path/to/file

63
ฉันสงสัยว่านี่เป็นการพิจารณาประสิทธิภาพ หากคุณไม่ต้องการประวัติเต็มก็จะใช้เวลาสแกนเนื้อหาอีกต่อไป วิธีที่ง่ายที่สุดคือการติดตั้งนามแฝงและเพียงแค่เขียนgit config alias.logf "log --follow" git logf ./path/to/file
Troels Thomsen

13
@TroelsThomsen อีเมลนี้โดย Linus Torvalds ซึ่งเชื่อมโยงจากคำตอบนี้บ่งบอกว่ามันเป็นตัวเลือกการออกแบบโดยเจตนาของ Git เนื่องจากมันถูกกล่าวหาว่ามีประสิทธิภาพมากกว่าการติดตามการเปลี่ยนชื่อเป็นต้น
Emil Lundberg

127
คำตอบนี้ทำให้เข้าใจผิดเล็กน้อย Git ทำการ "ตรวจจับการเปลี่ยนชื่อ" แต่ช้ามากในเกม คำถามคือถามว่าคุณมั่นใจได้อย่างไรว่า Git เปลี่ยนชื่อแทร็กและคนที่อ่านข้อความนี้สามารถอนุมานได้ว่า Git ตรวจพบพวกเขาโดยอัตโนมัติเพื่อคุณและจดบันทึกไว้ มันไม่ใช่. Git ไม่มีการจัดการกับการเปลี่ยนชื่อจริงและมีเครื่องมือผสาน / บันทึกที่พยายามคิดว่าเกิดอะไรขึ้น - และไม่ค่อยทำให้ถูกต้อง ไลนัสมีข้อผิดพลาดที่รุนแรง แต่การโต้เถียงกันอย่างรุนแรงว่าทำไมคอมไพล์ไม่ควรทำอย่างถูกต้องและติดตามการเปลี่ยนชื่ออย่างชัดเจน ดังนั้นเราติดอยู่ที่นี่
Chris Moschini

29
สำคัญ: หากคุณเปลี่ยนชื่อไดเรกทอรีเช่นในระหว่างการเปลี่ยนชื่อแพคเกจ Java ตรวจสอบให้แน่ใจว่าได้ดำเนินการสองคอมมิตก่อนอื่นสำหรับคำสั่ง 'git mv {old} {new}' ที่สองสำหรับการอัปเดตไฟล์ Java ทั้งหมดที่อ้างอิง ไดเรกทอรีแพคเกจการเปลี่ยนแปลง มิฉะนั้น git ไม่สามารถติดตามแต่ละไฟล์ได้แม้จะมีพารามิเตอร์ --follow
nn4l

44
แม้ว่า Linus อาจทำผิดพลาดน้อยมาก แต่สิ่งนี้ดูเหมือนจะเป็นข้อผิดพลาด เพียงเปลี่ยนชื่อโฟลเดอร์จะทำให้เดลต้าขนาดใหญ่อัปโหลดไปยัง GitHub ซึ่งทำให้ฉันระมัดระวังเกี่ยวกับการเปลี่ยนชื่อโฟลเดอร์ของฉัน ... แต่นั่นเป็นแจ็คเก็ตตรงขนาดใหญ่สำหรับโปรแกรมเมอร์ ฉันต้องนิยามความหมายของบางสิ่งใหม่หรือเปลี่ยนแปลงวิธีการจัดหมวดหมู่ Linus: "กล่าวอีกนัยหนึ่งฉันพูดถูกฉันมักจะพูดถูก แต่บางครั้งฉันก็พูดถูกกว่าเวลาอื่นและก็น่าเสียดายเมื่อฉันพูดว่า 'ไฟล์ไม่สำคัญ' ฉันก็พูดถูกจริงๆ ( TM)." ... ฉันมีข้อสงสัยเกี่ยวกับสิ่งนั้น
Gabe Halsmer

94

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

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

ตอนนี้เนื่องจากคุณยังอยู่กับฉันคุณอาจเป็นนักพัฒนาเดี่ยวที่จะเปลี่ยนชื่อไฟล์ที่แยกได้อย่างสมบูรณ์ ลองย้ายไฟล์โดยใช้filter-tree!

สมมติว่าคุณกำลังจะย้ายไฟล์oldไปยังโฟลเดอร์dirและตั้งชื่อnew

สิ่งนี้สามารถทำได้ด้วยgit mv old dir/new && git add -u dir/newแต่นั่นทำลายประวัติศาสตร์

แทน:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

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

เมื่อเสร็จแล้วไฟล์จะถูกย้ายและเข้าสู่ระบบเหมือนเดิม คุณรู้สึกเหมือนโจรสลัดนินจา

นอกจากนี้ยัง; mkdir dir จำเป็นเฉพาะเมื่อคุณย้ายไฟล์ไปยังโฟลเดอร์ใหม่เท่านั้น ถ้าจะหลีกเลี่ยงการสร้างโฟลเดอร์นี้ก่อนหน้านี้ในประวัติศาสตร์กว่าไฟล์ของคุณที่มีอยู่


57
ในฐานะที่เป็นคนคลั่งไคล้ - log-git ที่คลั่งไคล้ฉันจะไม่ทำสิ่งนี้ ไฟล์ไม่ได้ถูกตั้งชื่อว่า ณ เวลาเหล่านั้นประวัติจึงสะท้อนให้เห็นถึงสถานการณ์ที่ไม่มีอยู่จริง ใครจะรู้ว่าการทดสอบอะไรบ้างที่อาจทำลายในอดีต! ความเสี่ยงในการแตกรุ่นก่อนหน้านั้นค่อนข้างมากทุกกรณีไม่คุ้มค่า
Vincent

7
@ วินเซ็นต์คุณพูดถูกและฉันก็พยายามจะชัดเจนที่สุดเท่าที่จะทำได้เกี่ยวกับความไม่ชอบมาพากลของวิธีแก้ปัญหานี้ว่าเหมาะสม ฉันยังคิดว่าเรากำลังพูดถึงความหมายสองคำของ "ประวัติศาสตร์" ในกรณีนี้ฉันขอขอบคุณทั้งสอง
Øystein Steimler

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

3
@ user2291758 นั่นเป็นกรณีการใช้งานของฉัน คำสั่ง git ที่ทรงพลังกว่านี้เป็นสิ่งที่อันตราย แต่นั่นไม่ได้หมายความว่าพวกมันไม่มีกรณีการใช้ที่น่าสนใจหากคุณรู้ว่าคุณกำลังทำอะไรอยู่!
philix

1
หากเป็นไปได้การใช้การ--index-filterเปลี่ยนชื่อจะเร็วขึ้นมากเนื่องจากไม่จำเป็นต้องตรวจสอบต้นไม้และกลับมาใช้ใหม่ทุกครั้ง --index-filterทำหน้าที่โดยตรงกับแต่ละการกระทำดัชนี
โทมัส Guyot-Sionnest

87

เลขที่

คำตอบสั้น ๆ คือไม่มี ไม่สามารถเปลี่ยนชื่อไฟล์ใน Git และจดจำประวัติได้ และมันก็เป็นความเจ็บปวด

มีข่าวลือว่ามันใช้git log --follow--find-copies-harderงานได้ แต่มันไม่ได้ผลสำหรับฉันแม้ว่าจะไม่มีการเปลี่ยนแปลงเนื้อหาไฟล์และการเคลื่อนไหวได้ทำไปgit mvแล้ว

(ตอนแรกฉันใช้ Eclipse เพื่อเปลี่ยนชื่อและอัปเดตแพ็กเกจในการดำเนินการหนึ่งซึ่งอาจทำให้ Git สับสน แต่นั่นเป็นสิ่งที่ทำกันทั่วไปมาก--followดูเหมือนว่าจะทำงานได้ถ้ามีเพียง a เท่านั้นที่mvดำเนินการแล้ว a commitและa mvอยู่ไม่ไกลเกินไป)

Linus บอกว่าคุณควรเข้าใจเนื้อหาทั้งหมดของโครงการซอฟต์แวร์แบบองค์รวมโดยไม่จำเป็นต้องติดตามแต่ละไฟล์ น่าเศร้าที่สมองเล็ก ๆ ของฉันทำไม่ได้

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

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

แต่น่าเสียดายที่แบ่ง Eclipse --followซึ่งดูเหมือนว่าจะใช้ git log --followบางครั้งไม่แสดงประวัติเต็มของไฟล์ที่มีประวัติการเปลี่ยนชื่อที่ซับซ้อนแม้ว่าgit logจะมี (ฉันไม่รู้ว่าทำไม.)

(มีแฮ็กที่ฉลาดเกินไปที่กลับไปและแนะนำงานเก่า แต่พวกเขาค่อนข้างน่ากลัวดู GitHub-Gist: emiller / git-mv-with-history )


2
ฉันเชื่อว่าคุณถูกต้อง ฉันแค่พยายามใช้ php-cs-fixer เพื่อทำการฟอร์แมตแหล่งที่มาสำหรับโครงการ Laravel 5 ของฉัน แต่มันยืนยันที่จะเปลี่ยนการใช้อักษรตัวพิมพ์ใหญ่ของส่วนคำสั่งเพื่อให้ตรงกับค่าตัวพิมพ์เล็กของโฟลเดอร์แอป แต่เนมสเปซ (หรือผู้แต่งอัตโนมัติ) ทำงานเฉพาะกับ CamelCase เท่านั้น ฉันต้องเปลี่ยนการใช้อักษรตัวพิมพ์ใหญ่ของโฟลเดอร์เป็นแอป แต่สิ่งนี้ทำให้การเปลี่ยนแปลงของฉันสูญหาย นี่เป็นตัวอย่างที่น่าสนใจมากที่สุด แต่แสดงให้เห็นว่าฮิวริสติก git ไม่สามารถติดตามการเปลี่ยนแปลงชื่อที่ง่ายที่สุดได้อย่างไร (- ผู้ติดตามและ - ค้นหา - สำเนา - ยากควรเป็นกฎไม่ใช่ข้อยกเว้น)
Zack Morris

6
git -1, การโค่นล้ม +1
Cosmin

สิ่งนี้ยังคงเป็นจริงหรือไม่? นั่นเป็นเหตุผลที่ฉันจะอยู่กับ tfs ได้มากกว่าตอนนี้การเก็บประวัติของไฟล์ที่ถูกย้าย / เปลี่ยนชื่อเป็นสิ่งที่ต้องทำในโครงการขนาดใหญ่
Cesar

@ Cesar ถ้าโดย "จดจำประวัติ" เขาหมายถึง "ติดตามการเปลี่ยนชื่อเมื่อดูบันทึก" (ซึ่งเป็นสิ่งที่มีประโยชน์เพียงอย่างเดียวที่เราควรใส่ใจ) นั่นก็ไม่เป็นความจริงเลย! Git ไม่ได้ "บันทึก" การเปลี่ยนชื่อ แต่เครื่องมือสามารถตรวจจับได้อย่างง่ายดายและนำเสนอเราด้วยการเปลี่ยนชื่อและย้าย หาก "ไม่ทำงาน" สำหรับบางคนเขา / เธอควรเปลี่ยนเครื่องมือที่ใช้งานอยู่ มีGUI Git ที่ยอดเยี่ยมมากมายที่มีความสามารถนี้
Mohammad Dehghan

คำตอบสั้น ๆ คือใช่ Git รุ่นปัจจุบันรองรับ "บันทึก git - ติดตาม" เช่นกัน และฉันเห็นด้วยกับ @MammammDehghan
insung

42
git log --follow [file]

จะแสดงประวัติผ่านการเปลี่ยนชื่อ


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

22
@yoyo: นั่นเป็นเพราะคอมไพล์ไม่ได้ติดตามการเปลี่ยนชื่อมันตรวจพบพวกเขา โดยทั่วไปไม่ได้git mv git rm && git addมีตัวเลือกเช่น-M90/ --find-renames=90พิจารณาไฟล์ที่จะเปลี่ยนชื่อเมื่อมันเหมือนกัน 90%
vdboor

22

ฉันทำ:

git mv {old} {new}
git add -u {new}

3
ดูเหมือนว่าคุณจะไม่ทำอะไรเลยสำหรับฉันมันควรจะอัปเดตประวัติหรือไม่
jeremy

1
บางทีคุณต้องการพฤติกรรมของ-Aแทนหรือไม่ อีกครั้งดูที่นี่: git-scm.com/docs/git-add
James M. Greene

1
มันเพิ่มไฟล์อย่างไรก็ตามมันไม่ได้อัปเดตประวัติเพื่อให้ 'ชื่อไฟล์บันทึก git' แสดงประวัติแบบเต็ม มันจะแสดงประวัติเต็มถ้าคุณใช้ตัวเลือก --follow ยัง
jeremy

3
ฉันทำ refactor ที่ซับซ้อนซึ่งย้ายไดเรกทอรีรวม (ใช้ mv ไม่ใช่ git mv) แล้วเปลี่ยนเส้นทาง #include จำนวนมากภายในไฟล์ที่ถูกเปลี่ยนชื่อ คอมไพล์ไม่พบความคล้ายคลึงกันมากพอที่จะติดตามประวัติ แต่คอมไพล์เพิ่ม -u เป็นสิ่งที่ฉันต้องการ ตอนนี้สถานะ git จะระบุว่า "เปลี่ยนชื่อ" ก่อนที่จะแสดงว่า "ถูกลบ" และ "ไฟล์ใหม่"
AndyJost

1
มีคำถามมากมายเกี่ยวกับ SO git add -uอยู่ว่าวัตถุประสงค์ของการมี เอกสาร Git มีแนวโน้มที่จะไม่ช่วยเหลือและเป็นสถานที่สุดท้ายที่ฉันต้องการดู นี่คือหนึ่งในการโพสต์แสดงgit add -uในการกระทำ: stackoverflow.com/a/2117202
nobar

17

ฉันต้องการเปลี่ยนชื่อ / ย้ายแผนผังย่อยของโครงการใน Git ย้ายจาก

/project/xyz

ถึง

/ อุปกรณ์ / xyz

ถ้าฉันใช้ที่ราบgit mv project componentsจากนั้นประวัติการกระทำทั้งหมดสำหรับxyzโครงการจะหายไป

ไม่ (8 ปีต่อมา Git 2.19, Q3 2018), เพราะ Git จะตรวจจับการเปลี่ยนชื่อไดเรกทอรีและตอนนี้เป็นเอกสารที่ดีขึ้น

ดูกระทำ b00bf1c , กระทำ 1634688 , กระทำ 0661e49 , กระทำ 4d34dff , กระทำ 983f464 , กระทำ c840e1a , กระทำ 9929430 (27 มิถุนายน 2018) และกระทำ d4e8062 , กระทำ 5dacd4a (25 มิถุนายน 2018) โดยเอลียาห์ Newren (newren )
(รวมโดยJunio C Hamano - gitster-ในการกระทำ 0ce5a69 , 24 กรกฎาคม 2018)

ที่อธิบายไว้ในตอนนี้Documentation/technical/directory-rename-detection.txt:

ตัวอย่าง:

เมื่อทั้งหมดของx/a, x/bและx/cได้ย้ายไปz/a, z/bและz/cก็มีแนวโน้มที่x/dเพิ่มขึ้นในขณะเดียวกันยังต้องการที่จะย้ายไปz/dโดยการใช้คำใบ้ว่าไดเรกทอรีทั้งหมด ' x' ย้ายไป ' z'

แต่ก็มีหลายกรณีเช่น:

ประวัติด้านหนึ่งของการเปลี่ยนชื่อx -> zและอื่น ๆ เปลี่ยนชื่อไฟล์บางอย่าง x/eทำให้ต้องผสานเพื่อเปลี่ยนชื่อสกรรมกริยา

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

กฎพื้นฐานสองข้อ จำกัด เมื่อตรวจจับการเปลี่ยนชื่อไดเรกทอรีใช้:

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

คุณสามารถเห็นการทดสอบจำนวนมากt/t6043-merge-rename-directories.shซึ่งยังชี้ให้เห็นว่า:

  • a) หากการเปลี่ยนชื่อแบ่งไดเรกทอรีเป็นสองคนหรือมากกว่านั้นไดเรกทอรีที่มีการเปลี่ยนชื่อมากที่สุดคือ "wins"
  • b) หลีกเลี่ยงการเปลี่ยนชื่อไดเรกทอรีตรวจจับสำหรับเส้นทางถ้าเส้นทางนั้นเป็นแหล่งที่มาของการเปลี่ยนชื่อที่ด้านใดด้านหนึ่งของการผสาน
  • c) ใช้เฉพาะการเปลี่ยนชื่อไดเรกทอรีโดยนัยกับไดเรกทอรีหากอีกด้านหนึ่งของประวัติศาสตร์เป็นชื่อที่ทำการเปลี่ยนชื่อ

15

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

  • ใช้(แรงบันดาลใจจากSmarยืมมาจากExherbo )git am
  • เพิ่มประวัติการส่งไฟล์ที่คัดลอกหรือย้าย
  • จากไดเรกทอรีหนึ่งไปยังอีก
  • หรือจากที่เก็บหนึ่งไปอีกที่หนึ่ง

การ จำกัด

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

สรุป

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

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 the folder

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

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

Dan Bonacheaแนะนำให้ย้อนกลับลูปของคำสั่งการสร้างบันทึก git ในขั้นตอนแรกนี้: แทนที่จะเรียกใช้บันทึก git หนึ่งครั้งต่อไฟล์ให้เรียกใช้หนึ่งครั้งด้วยรายการไฟล์ในบรรทัดคำสั่งและสร้างบันทึกรวมเดียว วิธีนี้กระทำที่แก้ไขหลายไฟล์ยังคงกระทำเดียวในผลและกระทำใหม่ทั้งหมดรักษาลำดับเดิมของพวกเขา โปรดทราบว่าสิ่งนี้ยังต้องมีการเปลี่ยนแปลงในขั้นตอนที่สองด้านล่างเมื่อเขียนชื่อไฟล์ใหม่ในบันทึก (รวมตอนนี้)


2. จัดระเบียบไฟล์ต้นไม้ใหม่และอัปเดตชื่อไฟล์

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

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

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

cd /tmp/mail/dir
mkdir -p dirB/dirB1
mv subdir/file3 dirB/dirB1/file33
mv subdir/file4 dirB/dirB1/file44
mkdir -p 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"' {} ';'

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

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

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

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

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

--committer-date-is-author-dateรักษาตราประทับเวลาเดิม ( ความคิดเห็นของDan Bonachea )

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

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

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


เคล็ดลับพิเศษ: ตรวจสอบไฟล์ที่ถูกเปลี่ยนชื่อ / ย้ายภายใน 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 '{.* => .*}'

4
ระวัง: เทคนิคนี้แยกการกระทำที่เปลี่ยน 2 ไฟล์ขึ้นไปเป็นการแยกแฟรกเมนต์ - แยกออกไปและทำการเรียงลำดับของชื่อไฟล์ด้วยการเรียงลำดับชื่อไฟล์ ประวัติผลลัพธ์จึงเป็น "แก้ไข" เฉพาะบนพื้นฐานแบบไฟล์ต่อไฟล์ หากคุณกำลังย้ายมากกว่าหนึ่งไฟล์ดังนั้น NONE ของการคอมมิทใหม่ในประวัติผลลัพธ์จะแสดงสแน็ปช็อตที่สอดคล้องกันของไฟล์ที่ถูกย้ายซึ่งมีอยู่ในประวัติของ repo ดั้งเดิม
Dan Bonachea

2
สวัสดี @DanBonachea ขอบคุณสำหรับคำติชมที่น่าสนใจ ฉันได้ทำการย้ายข้อมูล repos บางอย่างที่มีหลายไฟล์เรียบร้อยแล้วโดยใช้เทคนิคนี้ (แม้จะเปลี่ยนชื่อไฟล์และย้ายไฟล์ข้ามไดเรกทอรี) คุณแนะนำให้เปลี่ยนแปลงอะไรในคำตอบนี้ คุณคิดว่าเราควรเพิ่มแบนเนอร์คำเตือนที่ด้านบนของคำตอบนี้เพื่ออธิบายข้อ จำกัด ของเทคนิคนี้หรือไม่? ไชโย
โอลิเบร

2
ฉันปรับเทคนิคนี้เพื่อหลีกเลี่ยงปัญหาโดยการย้อนกลับลูปของคำสั่งการสร้างบันทึก git ในขั้นตอนที่ 1 คือ แทนที่จะเรียกใช้บันทึก git หนึ่งครั้งต่อไฟล์ให้เรียกใช้หนึ่งครั้งด้วยรายการไฟล์ในบรรทัดคำสั่งและสร้างบันทึกรวมเดียว วิธีนี้กระทำที่แก้ไขไฟล์ 2 ไฟล์หรือมากกว่านั้นยังคงเป็นการกระทำเดียวในผลลัพธ์และคอมมิชชันใหม่ทั้งหมดยังคงรักษาความสัมพันธ์ดั้งเดิมไว้ หมายเหตุสิ่งนี้ยังต้องมีการเปลี่ยนแปลงในขั้นตอนที่ 2 เมื่อเขียนชื่อไฟล์ใหม่ในบันทึก (รวมตอนนี้) ฉันยังใช้ git am - คำสั่ง-date-is-author-date เพื่อรักษา timestamps การส่งต้นฉบับ
Dan Bonachea

1
ขอบคุณสำหรับการทดลองและการแบ่งปัน ฉันได้อัปเดตคำตอบสำหรับผู้อ่านคนอื่นแล้ว อย่างไรก็ตามฉันใช้เวลาในการทดสอบการประมวลผลของคุณ โปรดแก้ไขคำตอบนี้หากคุณต้องการแสดงตัวอย่างของบรรทัดคำสั่ง Cheers;)
olibre

4

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

ขั้นตอนที่ 0: สร้าง 'ประวัติ' สาขาจาก 'หลัก' เพื่อความปลอดภัย

ขั้นตอนที่ 1: ใช้เครื่องมือgit-filter-repoเพื่อเขียนประวัติใหม่ คำสั่งนี้ด้านล่างย้ายโฟลเดอร์ 'FolderwithContentOfInterest' ขึ้นไปหนึ่งระดับและแก้ไขประวัติการกระทำที่เกี่ยวข้อง

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force

ขั้นตอนที่ 2: ณ เวลานี้ที่เก็บ GitHub สูญเสียเส้นทางที่เก็บข้อมูลระยะไกล เพิ่มการอ้างอิงระยะไกล

git remote add origin git@github.com:MyCompany/MyRepo.git

ขั้นตอนที่ 3: ดึงข้อมูลในที่เก็บ

git pull

ขั้นตอนที่ 4: เชื่อมต่อสาขาที่สูญหายในท้องถิ่นกับสาขาต้นทาง

git branch --set-upstream-to=origin/history history

ขั้นตอนที่ 5: ที่อยู่ผสานความขัดแย้งสำหรับโครงสร้างโฟลเดอร์หากได้รับแจ้ง

ขั้นตอนที่ 6: กด !!

git push

หมายเหตุ: ประวัติที่ปรับเปลี่ยนและย้ายโฟลเดอร์จะปรากฏขึ้นเพื่อมุ่งมั่นแล้ว enter code here

เสร็จสิ้น โค้ดย้ายไปที่พาเรนต์ / ไดเร็กทอรีที่ต้องการซึ่งเก็บประวัติไว้เหมือนเดิม!


2

ในขณะที่แกนกลางของ Git การประปาของ Git ไม่ได้ติดตามการเปลี่ยนชื่อประวัติที่คุณแสดงด้วย Git log "Porcelain" สามารถตรวจจับได้หากคุณต้องการ

สำหรับการgit logใช้งานที่กำหนด-M ตัวเลือก:

git log -p -M

ด้วย Git รุ่นปัจจุบัน

มันใช้งานได้กับคำสั่งอื่นเช่นgit diffกัน

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

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

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

git config diff.renames 1

การเคลื่อนย้ายไฟล์จากที่หนึ่งไปยังอีกไดเรกทอรีถูกตรวจพบ นี่คือตัวอย่าง:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:20:19 2017 -0500

    test rename again

diff --git a/yyy/power.py b/zzz/power.py
similarity index 100%
rename from yyy/power.py
rename to zzz/power.py

commit ae181377154eca800832087500c258a20c95d1c3
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:19:17 2017 -0500

    rename test

diff --git a/power.py b/yyy/power.py
similarity index 100%
rename from power.py
rename to yyy/power.py

โปรดทราบว่าการทำงานนี้เมื่อใดก็ตามที่คุณกำลังใช้ diff git logไม่เพียงกับ ตัวอย่างเช่น:

$ git diff HEAD c3ee8df
diff --git a/power.py b/zzz/power.py
similarity index 100%
rename from power.py
rename to zzz/power.py

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

 $ git merge -v master
 Auto-merging single
 Merge made by the 'recursive' strategy.
  one => single | 4 ++++
  1 file changed, 4 insertions(+)
  rename one => single (67%)

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

นี่เป็นคำตอบสำหรับคำถามเก่าดังนั้นคำตอบอื่น ๆ อาจถูกต้องสำหรับเวอร์ชัน Git ในเวลานั้น


1

ก่อนอื่นให้สร้างคอมมิชชันสแตนด์อะโลนด้วยการเปลี่ยนชื่อ

จากนั้นการเปลี่ยนแปลงใด ๆ ในท้ายที่สุดกับเนื้อหาไฟล์ที่อยู่ในการกระทำที่แยกต่างหาก


1

หากต้องการเปลี่ยนชื่อไดเรกทอรีหรือไฟล์ (ฉันไม่ค่อยรู้เรื่องตัวพิมพ์ใหญ่และซับซ้อนดังนั้นอาจมีข้อ จำกัด บางประการ):

git filter-repo --path-rename OLD_NAME:NEW_NAME

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

git filter-repo --replace-text expressions.txt

expressions.txtเป็นไฟล์ที่เต็มไปด้วยบรรทัดเช่นliteral:OLD_NAME==>NEW_NAME(เป็นไปได้ที่จะใช้ Python RE with regex:หรือ glob ด้วยglob:)

ในการเปลี่ยนชื่อไดเรกทอรีในข้อความที่กระทำ:

git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'

นิพจน์ทั่วไปของไพ ธ อนนั้นก็รองรับเช่นกัน แต่จะต้องเขียนด้วยภาษาไพ ธ อนด้วยตนเอง

หากที่เก็บเป็นต้นฉบับโดยไม่มีรีโมทคุณจะต้องเพิ่ม--forceเพื่อบังคับให้เขียนซ้ำ (คุณอาจต้องการสร้างข้อมูลสำรองของที่เก็บข้อมูลของคุณก่อนทำสิ่งนี้)

หากคุณไม่ต้องการที่จะรักษา refs (พวกเขาจะปรากฏในประวัติศาสตร์สาขาของ Git GUI) --replace-refs delete-no-addคุณจะต้องเพิ่ม


0

เพียงย้ายไฟล์และสเตจด้วย:

git add .

ก่อนส่งคุณสามารถตรวจสอบสถานะ:

git status

ที่จะแสดง:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    old-folder/file.txt -> new-folder/file.txt

ฉันทดสอบกับ Git เวอร์ชั่น 2.26.1

สกัดจากGitHub หน้าช่วยเหลือ


-3

ฉันย้ายไฟล์แล้วทำ

git add -A

ซึ่งใส่ในพื้นที่ sataging ไฟล์ที่ถูกลบ / ใหม่ทั้งหมด ที่นี่ git ตระหนักว่าไฟล์ถูกย้าย

git commit -m "my message"
git push

ฉันไม่รู้ว่าทำไม แต่ใช้งานได้สำหรับฉัน


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