git merge: ใช้การเปลี่ยนแปลงกับโค้ดที่ย้ายไปยังไฟล์อื่น


132

ตอนนี้ฉันกำลังพยายามผสมคอมไพล์ที่สวยมาก ปัญหาอย่างหนึ่งที่ฉันพบคือฉันได้ทำการเปลี่ยนแปลงรหัสบางอย่างในสาขาของฉัน แต่เพื่อนร่วมงานของฉันย้ายรหัสนั้นไปยังไฟล์ใหม่ในสาขาของเขา เมื่อฉันทำเช่นgit merge my_branch his_branchนั้นคอมไพล์ไม่ได้สังเกตว่าโค้ดในไฟล์ใหม่นั้นเหมือนกับโค้ดเก่าและไม่มีการเปลี่ยนแปลงใด ๆ ของฉันเลย

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

ฉันรู้ว่า git รู้จัก blobs ไม่ใช่ไฟล์ดังนั้นต้องมีวิธีที่จะบอกได้อย่างแน่นอน "ใช้การเปลี่ยนแปลงโค้ดที่แน่นอนจากคอมมิตนี้ยกเว้นไม่ได้อยู่ที่ไหน แต่ตอนนี้อยู่ที่ไหนในไฟล์ใหม่นี้"


2
ไม่เหมือนกันทุกประการ แต่นี่เป็นคำถามที่คล้ายกันกับ anwsers ที่ดีที่อาจนำไปใช้: stackoverflow.com/questions/2701790/…
Mariano Desanze

4
ไม่มีคำตอบใดที่อธิบายว่าเหตุใด git จึงไม่สามารถทำการผสานเช่นนี้โดยอัตโนมัติ ฉันคิดว่ามันควรจะฉลาดพอที่จะตรวจจับการเปลี่ยนชื่อและทำการผสานที่เหมาะสมโดยอัตโนมัติ?
จอห์น

บางทีอาจไม่เป็นความจริงเมื่อถามคำถาม แต่ git เวอร์ชันใหม่ (ฉันใช้ 1.9.5) สามารถผสานการเปลี่ยนแปลงข้ามไฟล์ที่เปลี่ยนชื่อและย้ายได้ มี--rename-thresholdตัวเลือกในการปรับแต่งจำนวนความคล้ายคลึงกันที่จำเป็น
Todd Owen

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

นี่เป็นปัญหาหรือไม่ถ้าคุณทำ rebase ก่อน
hbogert

คำตอบ:


132

ฉันมีปัญหาที่คล้ายกันและฉันแก้ไขได้โดยการปรับเปลี่ยนงานของฉันให้ตรงกับองค์กรของไฟล์เป้าหมาย

บอกได้เลยว่าคุณปรับเปลี่ยนoriginal.txtสาขาคุณ ( localสาขา) แต่ในสาขาต้นแบบที่ได้รับการคัดลอกไปยังอีกคนหนึ่งกล่าวว่าoriginal.txt สำเนานี้ได้รับการดำเนินการในการกระทำที่เรากระทำชื่อcopy.txtCP

คุณต้องการที่จะใช้การเปลี่ยนแปลงของคุณทั้งหมดท้องถิ่นกระทำAและBด้านล่างที่ทำในเพื่อไฟล์ใหม่original.txtcopy.txt

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

สร้างสาขาทิ้งmoveที่จุดเริ่มต้นของการเปลี่ยนแปลงของคุณด้วยgit branch move X. กล่าวคือวางmoveสาขาไว้ที่การคอมมิตXอันหนึ่งก่อนคอมมิตที่คุณต้องการผสาน เป็นไปได้มากว่านี่คือการกระทำที่คุณแยกออกมาเพื่อใช้การเปลี่ยนแปลงของคุณ ในฐานะผู้ใช้DOO @digoryเขียนด้านล่างที่คุณสามารถทำได้เพื่อค้นหาgit merge-base master localX

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

ในสาขานี้ใช้คำสั่งเปลี่ยนชื่อต่อไปนี้:

git mv original.txt copy.txt

สิ่งนี้เปลี่ยนชื่อไฟล์ โปรดทราบว่าcopy.txtในตอนนี้ยังไม่มีต้นไม้ของคุณ
ยอมรับการเปลี่ยนแปลงของคุณ (เราตั้งชื่อการกระทำนี้MV)

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

ตอนนี้คุณสามารถสร้างฐานงานของคุณใหม่ด้านบนของmove:

git rebase move local

วิธีนี้จะใช้งานได้โดยไม่มีปัญหาและการเปลี่ยนแปลงของคุณจะถูกนำไปใช้กับcopy.txtสาขาในพื้นที่ของคุณ

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

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

คุณจะต้องสร้างฐานงานของคุณใหม่อีกครั้งโดยทิ้งการดำเนินการย้ายดังนี้:

git rebase move local --onto CP

... CPการกระทำอยู่ที่ใดซึ่งcopy.txtได้รับการแนะนำในสาขาอื่น นี่เป็นการปรับฐานการเปลี่ยนแปลงทั้งหมดที่copy.txtอยู่ด้านบนของคอมCPมิต ตอนนี้localสาขาของคุณเหมือนกับว่าคุณแก้ไขcopy.txtและไม่แก้ไขเสมอoriginal.txtและคุณสามารถรวมกับคนอื่น ๆ ต่อไปได้

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

มันเป็นสิ่งสำคัญที่การเปลี่ยนแปลงจะถูกนำมาใช้ในCPหรือมิฉะนั้นจะไม่อยู่และการเปลี่ยนแปลงที่จะกลับมาประยุกต์ใช้ในcopy.txtoriginal.txt

หวังว่านี่จะชัดเจน คำตอบนี้มาช้า แต่อาจเป็นประโยชน์กับคนอื่น


2
นั่นเป็นงานมากมาย แต่ฉันคิดว่ามันควรจะทำงานตามหลักการ ฉันคิดว่าคุณจะโชคดีในการผสานมากกว่า rebase
asmeurer

7
โซลูชันนี้เกี่ยวข้องกับคำสั่ง git พื้นฐานเท่านั้นซึ่งแตกต่างจากการแก้ไขแพตช์หรือการใช้งานpatch(ซึ่งอาจเกี่ยวข้องกับงานจำนวนมาก) และนั่นเป็นเหตุผลที่ฉันคิดว่ามันน่าสนใจที่จะแสดง นอกจากนี้โปรดทราบว่าฉันใช้เวลาในการเขียนคำตอบมากกว่าที่จะใช้การเปลี่ยนแปลงจริงในกรณีของฉัน คุณจะแนะนำโดยใช้การผสานในขั้นตอนใด และทำไม? ความแตกต่างเพียงอย่างเดียวที่ฉันเห็นคือโดยการรีแบตใหม่ฉันทำการคอมมิตชั่วคราวซึ่งจะถูกยกเลิกในภายหลัง (คอมมิตMV) ซึ่งเป็นไปไม่ได้ด้วยการผสานเท่านั้น
coredump

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

4
ฉันเพิ่มต้นไม้ ASCII เพื่อชี้แจงแนวทาง การลงทะเบียนการผสานกับ rebase ในกรณีนั้นสิ่งที่ฉันต้องการทำคือใช้การเปลี่ยนแปลงทั้งหมดใน 'original.txt' ในสาขาของฉันและนำไปใช้กับ 'copy.txt' ในสาขาหลักเนื่องจากด้วยเหตุผลบางประการ "original txt 'ถูกคัดลอก (และไม่ย้าย) ไปยัง' copy.txt 'ในบางจุด หลังจากการคัดลอก 'original.txt' อาจมีการพัฒนาในสาขาหลัก หากฉันผสานโดยตรงการเปลี่ยนแปลงในเครื่องของฉันบน original.txt จะถูกนำไปใช้กับ original.txt ที่แก้ไขแล้วในสาขาหลักซึ่งจะรวมได้ยาก ความนับถือ.
coredump

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

31

คุณสามารถใช้git diff(หรือgit format-patch) เพื่อสร้างโปรแกรมแก้ไขได้ตลอดเวลาจากนั้นไปแก้ไขชื่อไฟล์ในโปรแกรมแก้ไขด้วยตนเองและใช้กับgit apply(หรือgit am)

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


1
มันเป็นคำตอบที่ดีที่สุดจนถึงตอนนี้ ฉันคิดไม่ออกว่าจะgit format-patchทำงานอย่างไรให้คุ้มค่า ถ้าฉันทำgit format-patch SHA1มันจะสร้างไฟล์แพตช์จำนวนมากสำหรับประวัติทั้งหมด แต่ฉันเดาว่าgit show SHA1 > diff.patchจะได้ผลเช่นกัน
asmeurer

1
@asmeurer: ใช้-1ตัวเลือก โหมดการทำงานปกติสำหรับการแก้ไขรูปแบบคือช่วงการแก้ไขเช่นเดียวกับorigin/master..masterคุณจึงสามารถเตรียมชุดโปรแกรมแก้ไขได้อย่างง่ายดาย
Cascabel

1
ที่จริงหมายเหตุอื่น git applyและgit amจู้จี้จุกจิกเกินไปเพราะพวกเขาต้องการหมายเลขบรรทัดเดียวกัน แต่ฉันกำลังประสบความสำเร็จกับpatchคำสั่งUNIX
asmeurer

24

นี่คือวิธีแก้ปัญหาการผสานของการเผชิญหน้ากับความขัดแย้งในการผสานด้วยการเปลี่ยนชื่อและแก้ไขและแก้ไขด้วย mergetool ที่จดจำไฟล์ซอร์สการผสาน 3 ไฟล์ที่ถูกต้อง

  • หลังจากการผสานล้มเหลวเนื่องจาก 'ไฟล์ที่ถูกลบ' ที่คุณทราบว่าถูกเปลี่ยนชื่อและแก้ไข:

    1. คุณยกเลิกการรวม
    2. ยอมรับไฟล์ที่เปลี่ยนชื่อในสาขาของคุณ
    3. และรวมอีกครั้ง

เดินผ่าน:

สร้าง file.txt:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

สร้างสาขาที่คุณจะแก้ไขในภายหลัง:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

สร้างการเปลี่ยนชื่อและแก้ไขบนต้นแบบ:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

สลับเป็นสาขาและแก้ไขที่นั่นด้วย:

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

พยายามผสานต้นแบบ:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

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

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

ลองรวมอีกครั้ง:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

ที่ดี! รวมผลลัพธ์ในความขัดแย้ง 'ปกติ' ที่สามารถแก้ไขได้ด้วย mergetool:

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits

น่าสนใจ ฉันจะต้องลองทำในครั้งต่อไปที่ฉันเจอปัญหานี้
asmeurer

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

1
ขอบคุณ. สถานการณ์ของฉันคือฉันย้ายB.txt -> C.txtและA.txt -> B.txtด้วย git mv และ git ไม่สามารถจับคู่ความขัดแย้งในการผสานได้อย่างถูกต้องโดยอัตโนมัติ (ได้รับการผสานความขัดแย้งระหว่างเก่าB.txtและใหม่B.txt) เมื่อใช้วิธีนี้ความขัดแย้งในการผสานจะอยู่ระหว่างไฟล์ที่ถูกต้อง
cib

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