ค้นหาจุดสาขาด้วย Git หรือไม่


455

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

พื้นที่เก็บข้อมูลของฉันโดยทั่วไปมีลักษณะเช่นนี้:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

ฉันกำลังมองหาการแก้ไข A ซึ่งไม่ใช่สิ่งที่git merge-base (--all)พบ


คำตอบ:


499

ฉันกำลังมองหาสิ่งเดียวกันและฉันพบคำถามนี้ ขอบคุณสำหรับการถาม!

อย่างไรก็ตามฉันพบว่าคำตอบที่ฉันเห็นที่นี่ดูเหมือนจะไม่มากให้คำตอบที่คุณถาม (หรือว่าฉันกำลังมองหา) - พวกเขาดูเหมือนจะให้GกระทำการแทนการAกระทำ

ดังนั้นฉันได้สร้างต้นไม้ต่อไปนี้ (ตัวอักษรที่ได้รับมอบหมายตามลำดับเวลา) ดังนั้นฉันจึงสามารถทดสอบสิ่งต่างๆได้:

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

สิ่งนี้ดูแตกต่างจากของคุณเล็กน้อยเพราะฉันต้องการตรวจสอบให้แน่ใจว่าฉันได้รับ (หมายถึงกราฟนี้ไม่ใช่ของคุณ) B แต่ไม่ใช่ A (ไม่ใช่ D หรือ E) นี่คือตัวอักษรที่แนบมากับคำนำหน้า SHA และส่งข้อความ (repo ของฉันสามารถโคลนได้จากที่นี่หากใคร ๆ ก็สนใจ)

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

ดังนั้นเป้าหมาย: ค้นหา BB ต่อไปนี้เป็นสามวิธีที่ฉันพบหลังจากซ่อมแซมเล็กน้อย:


1. สายตาด้วย gitk:

คุณควรเห็นต้นไม้แบบนี้ (มองจากต้นแบบ):

gitk จับภาพหน้าจอจากต้นแบบ

หรือที่นี่ (ดูจากหัวข้อ):

gitk จับภาพหน้าจอจากหัวข้อ

ในทั้งสองกรณีฉันได้เลือกการกระทำที่อยู่Bในกราฟของฉัน เมื่อคุณคลิกที่ปุ่ม SHA แบบเต็มจะปรากฏในช่องป้อนข้อความใต้กราฟ


2. สายตา แต่จาก terminal:

git log --graph --oneline --all

(แก้ไข / บันทึกด้านข้าง: การเพิ่ม --decorateยังน่าสนใจโดยเพิ่มการระบุชื่อสาขาแท็ก ฯลฯ ไม่เพิ่มสิ่งนี้ในบรรทัดคำสั่งด้านบนเนื่องจากผลลัพธ์ด้านล่างไม่ได้สะท้อนถึงการใช้งาน)

ซึ่งแสดงให้เห็น (สมมติว่าgit config --global color.ui auto):

ผลลัพธ์ของบันทึก git - กราฟ - ออนไลน์ - ทั้งหมด

หรือในข้อความแบบตรง:

* a9546a2 ผสานจากหัวข้อกลับไปเป็นหลัก
| \  
| * 648ca35 การรวมต้นแบบเข้ากับหัวข้อ
| | \  
| * | 132ee2a กระทำครั้งแรกในสาขาหัวข้อ
* | | e7c863d ส่งมอบข้อมูลให้กับมาสเตอร์หลังจากที่มาสเตอร์ถูกรวมเข้ากับหัวข้อ
| | /  
| / |   
* | 37ad159 การโพสต์สาขากระทำกับต้นแบบ
| /  
* 6aafd7f วินาทีคอมมิทบนมาสเตอร์ก่อนที่จะแตกแขนง
* 4112403 การส่งข้อมูลเริ่มต้นกับมาสเตอร์

ไม่ว่าในกรณีใดเราเห็นว่า 6aafd7f กระทำการเป็นจุดร่วมที่ต่ำที่สุดเช่นBในกราฟของฉันหรือAในของคุณ


3. ด้วยเวทมนตร์คาถา:

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

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

ซึ่งคุณยังสามารถใส่ลงไปในของคุณ ~ / .gitconfig เป็น(หมายเหตุ: ตามรอยเส้นประเป็นสิ่งสำคัญ; ขอบคุณไบรอันสำหรับการนำความสนใจไปที่) :

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

ซึ่งสามารถทำได้ผ่านบรรทัดคำสั่ง (convoluted with quoting) ดังต่อไปนี้:

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

หมายเหตุ: zshสามารถได้อย่างง่ายดายเพียงได้รับbashแต่shจะไม่ทำงาน - The ไวยากรณ์ไม่อยู่ในวานิลลา<() sh(ขอบคุณอีกครั้ง @conny ที่ทำให้ฉันตระหนักถึงมันในความคิดเห็นเกี่ยวกับคำตอบอื่นในหน้านี้!)

หมายเหตุ: รุ่นสำรองข้างต้น:

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

เป็นบรรทัด. git-config:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

จากเปลือก:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

ดังนั้นในต้นไม้ทดสอบของฉัน (ซึ่งไม่สามารถใช้ได้ในขณะนี้ขออภัยมันกลับมาแล้ว) ซึ่งตอนนี้ใช้ได้กับทั้งต้นแบบและหัวข้อ (ให้คอมมิท G และ B ตามลำดับ) ขอขอบคุณอีกครั้ง liori สำหรับแบบฟอร์มสำรอง


นั่นคือสิ่งที่ฉัน [และ liori] ขึ้นมาด้วย ดูเหมือนว่าจะทำงานให้ฉัน นอกจากนี้ยังอนุญาตนามแฝงเพิ่มเติมอีกสองรายการที่อาจพิสูจน์ได้ว่ามีประโยชน์:

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

มีความสุข git-ing!


5
ขอบคุณ lindes ตัวเลือกของเชลล์เหมาะอย่างยิ่งสำหรับสถานการณ์ที่คุณต้องการค้นหาจุดสาขาของสาขาการบำรุงรักษาที่ใช้เวลานาน เมื่อคุณกำลังมองหาการแก้ไขที่อาจจะเป็นการผูกมัดพันครั้งในอดีตตัวเลือกที่มองเห็นจะไม่ตัดเลย * 8 ')
ทำเครื่องหมายบูธ

4
ในวิธีที่สามของคุณขึ้นอยู่กับว่าบริบทจะแสดงบรรทัดแรกที่ไม่เปลี่ยนแปลง สิ่งนี้จะไม่เกิดขึ้นในบางกรณีหรือหากคุณมีข้อกำหนดที่แตกต่างกันเล็กน้อย (เช่นฉันต้องการเพียงหนึ่งในประวัติศาสตร์เท่านั้น - ผู้ปกครองคนแรกและฉันใช้วิธีนี้ในสคริปต์ที่บางครั้งอาจใช้แบบเดียวกัน สาขาทั้งสองด้าน) ฉันพบว่ามันปลอดภัยกว่าที่จะใช้diffโหมด if-then-else และลบบรรทัดที่ถูกเปลี่ยน / ลบออกจากเอาต์พุตแทนที่จะนับว่ามีบริบทที่ใหญ่พอโดย: diff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1.
liori

3
git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~ฉันจะทำงานได้ดีเช่นกัน
Tobias Schulte

4
@ JakubNarębski: คุณมีวิธีที่git merge-base --fork-point ...จะมอบ B (กระทำ6aafd7f) สำหรับต้นไม้ต้นนี้ไหม? ฉันกำลังยุ่งกับสิ่งอื่น ๆ เมื่อคุณโพสต์นั้นและฟังดูดี แต่ในที่สุดฉันก็ลองแล้วและฉันจะไม่ทำให้มันทำงาน ... ฉันอาจจะได้อะไรที่ใหม่กว่านี้หรือแค่ความล้มเหลวเงียบ (ไม่มีข้อผิดพลาด) ข้อความ แต่สถานะทางออก 1) พยายามข้อโต้แย้งเช่นgit co master; git merge-base --fork-point topic, git co topic; git merge-base --fork-point master, git merge-base --fork-point topic master(สำหรับเช็คเอาท์อย่างใดอย่างหนึ่ง) ฯลฯ เป็นสิ่งที่มีฉันทำผิดหรือขาดหายไป?
lindes

25
@ JakubNarębski @lindes --fork-pointอ้างอิงจาก reflog ดังนั้นมันจะทำงานได้ก็ต่อเมื่อคุณทำการเปลี่ยนแปลงในเครื่อง แม้แล้วรายการ reflog อาจหมดอายุ มันมีประโยชน์ แต่ไม่น่าเชื่อถือเลย
Daniel Lubarov

131

คุณอาจกำลังมองหาgit merge-base:

git merge-baseค้นหาบรรพบุรุษที่ดีที่สุดระหว่างสองคอมมิทที่จะใช้ในการผสานสามทาง บรรพบุรุษร่วมกันคนหนึ่งดีกว่าบรรพชนทั่วไปคนอื่น ๆ ถ้าหากภายหลังเป็นบรรพบุรุษของอดีต บรรพบุรุษเป็นคนธรรมดาที่ไม่ได้มีบรรพบุรุษร่วมกันดีกว่าเป็นดีที่สุดบรรพบุรุษร่วมกันกล่าวคือฐานการผสาน โปรดทราบว่าอาจมีฐานผสานมากกว่าหนึ่งฐานสำหรับคู่การกระทำ


10
หมายเหตุ--allตัวเลือกสำหรับ " git merge-base"
Jakub Narębski

10
นี้ไม่ตอบคำถามเดิม แต่คนส่วนใหญ่ถามคำถามที่ง่ายมากที่นี้คือคำตอบ :)
FelipeC

6
เขาบอกว่าเขาไม่ได้ไม่ได้ผลจากฐานการคอมไพล์
Tom Tanner

9
@TomTanner: ฉันเพิ่งดูประวัติคำถามและคำถามเดิมถูกแก้ไขเพื่อรวมบันทึกนั้นประมาณgit merge-baseห้าชั่วโมงหลังจากคำตอบของฉันถูกโพสต์ (อาจตอบสนองต่อคำตอบของฉัน) อย่างไรก็ตามฉันจะทิ้งคำตอบนี้ไว้เหมือนเดิมเพราะอาจเป็นประโยชน์กับคนอื่นที่ค้นหาคำถามนี้ผ่านการค้นหา
Greg Hewgill

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

42

ฉันใช้git rev-listสิ่งนี้ ตัวอย่างเช่น (สังเกตจุด3จุด)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

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

ฉันได้เพิ่มคำสั่งนั้นในนามแฝงของฉันใน~/.gitconfig:

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

ดังนั้นฉันสามารถเรียกมันว่า:

$ git diverges branch-a master

หมายเหตุ: นี่ดูเหมือนจะให้ความมุ่งมั่นแรกในสาขามากกว่าบรรพบุรุษร่วม (นั่นคือให้GแทนAต่อกราฟในคำถามเดิม) ฉันมีคำตอบที่ได้รับAว่าฉันจะโพสต์ในปัจจุบัน
lindes

@ lindes: มันให้บรรพบุรุษร่วมกันในทุกกรณีที่ฉันได้ลอง คุณมีตัวอย่างที่มันไม่ได้?
mipadi

ใช่. ในคำตอบของฉัน (ซึ่งมีการเชื่อมโยงไป repo คุณสามารถโคลนนั้นgit checkout topicและเรียกใช้แล้วนี้มีtopicในสถานที่ของbranch-a) รายการมัน648ca357b946939654da12aaf2dc072763f3caeeและ37ad15952db5c065679d7fc31838369712f0b338- ทั้ง37ad159และ648ca35อยู่ใน ancestory ของสาขาในปัจจุบัน (หลังเป็นหัวปัจจุบันของtopic) แต่ไม่เป็นประเด็นก่อนเกิดการแตกแขนง คุณได้รับสิ่งที่แตกต่างกันอย่างไร
lindes

@lindes: ฉันไม่สามารถคัดลอก repo ของคุณ (อาจเป็นปัญหาสิทธิ์)
mipadi

โอ๊ะขอโทษด้วย! ขอบคุณที่ทำให้ฉันรู้. ฉันลืมรัน git update-server-info มันควรจะดีตอนนี้ :)
lindes

31

ถ้าคุณชอบคำสั่งสั้น ๆ

git rev-list $ (git rev-list - First-parent ^ branch_name master | tail -n1) ^^! 

นี่คือคำอธิบาย

คำสั่งต่อไปนี้ให้รายการของการคอมมิททั้งหมดในมาสเตอร์ที่เกิดขึ้นหลังจาก branch_name ถูกสร้างขึ้น

git rev-list - First-parent ^ branch_name ต้นแบบ 

เนื่องจากคุณสนใจเฉพาะสิ่งแรกสุดที่คุณต้องการให้บรรทัดสุดท้ายของผลลัพธ์:

git rev-list ^ branch_name - ผู้ปกครองคนแรก | tail -n1

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

คำสั่ง

คอมไพล์รายชื่อคอมมิท ^^!

เป็นเพียงวิธีที่จะแสดงการอ้างอิงกระทำหลัก คุณสามารถใช้

บันทึกคอมไพล์ -1 กระทำ ^

หรืออะไรก็ตาม

PS: ฉันไม่เห็นด้วยกับการโต้แย้งว่าคำสั่งบรรพบุรุษไม่เกี่ยวข้อง ขึ้นอยู่กับสิ่งที่คุณต้องการ ตัวอย่างเช่นในกรณีนี้

_C1___C2_______ ต้นแบบ
  \ \ _XXXXX_ สาขา A (Xs แสดงถึงการข้ามส่วนโดยพลระหว่างต้นแบบและ A)
   \ _____ / สาขา B

มันทำให้รู้สึกที่สมบูรณ์แบบในการส่งออก C2 เป็น "การแยก" กระทำ นี่คือเมื่อนักพัฒนาแยกออกจาก "ต้นแบบ" เมื่อเขาแยกสาขา "B" ไม่ได้รวมอยู่ในสาขาของเขา! นี่คือสิ่งที่วิธีแก้ปัญหาในโพสต์นี้มอบให้

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


3
นี่คือคำตอบที่สะอาดที่สุดเรามาลงคะแนนให้ดีที่สุด การแก้ไขที่แนะนำ: git rev-list commit^^!สามารถทำให้ง่ายขึ้นเป็นgit rev-parse commit^
Russell Davis

นี่ควรเป็นคำตอบ!
trinth

2
คำตอบนี้เป็นสิ่งที่ดีฉันเพิ่งแทนที่git rev-list --first-parent ^branch_name masterด้วยgit rev-list --first-parent branch_name ^masterเพราะถ้าสาขาหลักเป็น 0 กระทำก่อนหน้าสาขาอื่น (ส่งต่อได้อย่างรวดเร็ว) มันจะไม่มีการสร้างผลลัพธ์ ด้วยโซลูชันของฉันจะไม่มีการสร้างเอาต์พุตหากต้นแบบอยู่ข้างหน้าอย่างเคร่งครัด (เช่นสาขาได้รับการผสานอย่างสมบูรณ์) ซึ่งเป็นสิ่งที่ฉันต้องการ
Michael Schmeißer

3
สิ่งนี้จะไม่ทำงานเว้นแต่ฉันจะพลาดอะไรบางอย่างไป มีการผสานทั้งสองทิศทางในตัวอย่างสาขา ดูเหมือนคุณจะนำมาพิจารณา แต่ฉันเชื่อว่าสิ่งนี้จะทำให้คำตอบของคุณล้มเหลว git rev-list --first-parent ^topic masterจะนำคุณกลับไปที่การคอมมิทครั้งแรกหลังจากการรวมครั้งล่าสุดจากmasterเป็นtopic(ถ้ามีอยู่)
jerry

1
@jerry คุณถูกต้องคำตอบนี้เป็นขยะ ตัวอย่างเช่นในกรณีที่ backmerge เพิ่งเกิดขึ้น (รวมต้นแบบเข้ากับหัวข้อ) และ master ไม่มีการคอมมิตใหม่หลังจากนั้นคำสั่ง git rev-list แรก - คำสั่ง -first-parent ไม่มีผลลัพธ์ใด ๆ เลย
TamaMcGlinn

19

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

บันทึก

การสร้างพื้นที่เก็บข้อมูลที่มีโครงสร้างที่กำหนดเราจะได้รับบันทึกการคอมไพล์ของ:

$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

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

วิธีแก้ปัญหาที่ใช้งานได้

วิธีแก้ปัญหาเดียวที่ใช้งานได้คือคำตอบที่lindesส่งคืนอย่างถูกต้องA:

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

ดังที่ชาร์ลส์เบลีย์ชี้ให้เห็นว่าการแก้ปัญหานี้ค่อนข้างบอบบาง

หากคุณbranch_Aเข้ามาmasterแล้วรวมmasterเข้าด้วยกันbranch_Aโดยไม่ต้องทำคอมมิชชันการแก้ปัญหาของ lindes จะให้คุณเท่านั้นแรกล่าสุดเท่านั้น

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

นี้จริงๆ boils ทั้งหมดลงไปgits ขาดของสิ่งที่hgเรียกชื่อสาขา JHW- สนาม blogger เรียกร้องเหล่านี้lineagesกับครอบครัวในบทความของเขาเหตุผลที่ผมชอบ Mercurial มากกว่า Gitและบทความติดตามของเขาดูเพิ่มเติม Mercurial กับ Git (กับกราฟ!) ฉันจะแนะนำคนอ่านพวกเขาเพื่อดูว่าทำไมบางแปลงปรอทพลาดไม่ได้มีชื่อสาขาgitใน

โซลูชันที่ไม่ทำงาน

วิธีการแก้ปัญหาที่จัดทำโดยmipadiส่งคืนสองคำตอบIและC:

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

วิธีการแก้ปัญหาโดยGreg HewgillกลับมาI

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

วิธีแก้ปัญหาที่จัดทำโดยKarlส่งคืนX:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

สคริปต์

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

ฉันสงสัยว่าเวอร์ชัน git สร้างความแตกต่างได้มาก แต่:

$ git --version
git version 1.7.1

ขอบคุณCharles Bailey ที่แสดงให้ฉันเห็นถึงวิธีที่กะทัดรัดมากขึ้นในการสคริปต์ที่เก็บตัวอย่าง


2
diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1การแก้ปัญหาโดยคาร์ลเป็นเรื่องง่ายในการแก้ไข: ขอขอบคุณที่ให้คำแนะนำในการสร้าง repo :)
FelipeC

ฉันคิดว่าคุณหมายถึง "ความหลากหลายของโซลูชันที่คาร์ลส่งคืน X" ปรับทำงานเดิมมันก็น่าเกลียดเพียง :-)
คาร์ล

ไม่ต้นฉบับของคุณใช้งานไม่ได้ ได้รับรูปแบบการทำงานที่เลวร้ายที่สุด แต่การเพิ่มตัวเลือก --topo-order ทำให้เวอร์ชันของคุณใช้งานได้ดี :)
FelipeC

@felipec - ดูความคิดเห็นสุดท้ายของฉันในคำตอบโดยชาร์ลส์เบลีย์ อนิจจาการแชทของเรา(และความคิดเห็นเก่าทั้งหมด) ได้ถูกลบไปแล้ว ฉันจะพยายามอัปเดตคำตอบเมื่อมีเวลา
Mark Booth

น่าสนใจ ฉันต้องการสันนิษฐานว่าทอพอโลยีเป็นค่าเริ่มต้น Silly me :-)
Karl

10

โดยทั่วไปสิ่งนี้เป็นไปไม่ได้ ในประวัติสาขาแยกสาขาและรวมก่อนสาขาที่ตั้งชื่อถูกแยกออกและสาขากลางของสองสาขาที่มีชื่อมีลักษณะเหมือนกัน

ใน git สาขาเป็นเพียงชื่อปัจจุบันของเคล็ดลับของส่วนของประวัติศาสตร์ พวกเขาไม่มีตัวตนที่แข็งแกร่งจริงๆ

นี่ไม่ใช่เรื่องใหญ่นักเนื่องจากการผสานฐาน (ดูคำตอบของ Greg Hewgill) ของการกระทำสองอย่างนั้นมักจะมีประโยชน์มากกว่าการมอบความมุ่งมั่นล่าสุดที่ทั้งสองสาขาแบ่งปันกัน

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

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

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

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"


3
ขอบคุณชาร์ลส์คุณเชื่อฉันถ้าฉันต้องการทราบจุดที่สาขาเดิมแยกฉันจะต้องติดแท็ก ผมหวังว่าgitมีความเทียบเท่ากับhg's ชื่อสาขาก็จะทำให้การจัดการชีวิตอยู่นานสาขาการบำรุงรักษาเพื่อให้ง่ายมาก
Mark Booth

1
"ในระบบคอมไพล์สาขาเป็นเพียงชื่อปัจจุบันของเคล็ดลับในส่วนของประวัติศาสตร์พวกเขาไม่มีตัวตนที่แข็งแกร่งจริง ๆ " นั่นเป็นสิ่งที่น่ากลัวที่จะพูดและทำให้ฉันเชื่อว่าฉันต้องเข้าใจสาขา Git ดีขึ้น - ขอบคุณ (+ 1)
dumbledad

ในประวัติสาขาแยกสาขาและรวมก่อนสาขาที่ตั้งชื่อถูกแยกออกและสาขากลางของสองสาขาที่มีชื่อมีลักษณะเหมือนกัน อ๋อ +1
user1071847

5

เกี่ยวกับสิ่งที่ชอบ

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^

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

1
นามแฝง Git เทียบเท่า: diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'(ไม่มีไฟล์ชั่วคราว)
Conny

@conny: โอ้ว้าว - ฉันไม่เคยเห็นไวยากรณ์ <(foo) ... มันมีประโยชน์อย่างเหลือเชื่อขอบคุณ! (ทำงานใน zsh ด้วยเช่นกัน)
lindes

นี่ดูเหมือนจะมอบความผูกพันแรกให้กับฉันมากกว่าจะเป็นบรรพบุรุษร่วมกัน (นั่นคือให้GแทนAต่อกราฟในคำถามเดิม) ฉันคิดว่าฉันได้พบคำตอบซึ่งฉันจะโพสต์ในปัจจุบัน
lindes

แทนที่จะเป็น 'git log --pretty = oneline' คุณสามารถทำ 'git rev-list' จากนั้นคุณสามารถข้ามการตัดได้เช่นกันยิ่งไปกว่านี้สิ่งนี้จะช่วยให้ผู้ปกครองทำหน้าที่จุดแตกต่างดังนั้นเพียงแค่หาง -2 | หัว 1. ดังนั้น:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
FelipeC

5

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

แต่ฉันได้ทำตามวิธีการที่แตกต่างกันตามความจริงที่ว่าทั้งสองสาขามีประวัติศาสตร์ร่วมกันประวัติศาสตร์ทั้งหมดก่อนที่จะแยกเป็น 100% เหมือนกันดังนั้นแทนที่จะกลับไปข้อเสนอของฉันกำลังจะไปข้างหน้า (จากที่ 1 กระทำ), ค้นหาความแตกต่างที่ 1 ในทั้งสองสาขา จุดสาขาจะเป็นเพียงผู้ปกครองของความแตกต่างแรกที่พบ

ในทางปฏิบัติ:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

และมันก็แก้ปัญหากรณีปกติของฉันทั้งหมด แน่ใจว่ามีเส้นขอบไม่ครอบคลุม แต่ ... ciao :-)


diff <(git rev-list "$ {1: -master}" --first-parent) <(git rev-list "$ {2: -HEAD}" --first-parent) -U1 | หาง -1
Alexander Bird

ฉันพบนี้จะเร็วขึ้น (2-100x):comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1
Andrei Neculau

4

ฉันเพิ่งต้องแก้ปัญหานี้เช่นกันและลงเอยด้วยการเขียนสคริปต์ Ruby สำหรับสิ่งนี้: https://github.com/vaneyckt/git-find-branching-point


มันไม่ทำงานกรานล้มเหลวใน "unpack_object_header_gently" และไม่ได้รับการดูแล
puchu

4

หลังจากการวิจัยและการอภิปรายเป็นจำนวนมากเป็นที่ชัดเจนว่าไม่มีกระสุนวิเศษที่สามารถใช้ได้ในทุกสถานการณ์อย่างน้อยก็ไม่ได้อยู่ใน Git รุ่นปัจจุบัน

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

ในการค้นหาจุดสาขาของสาขา Devel สิ่งที่คุณต้องทำคือใช้งานdevel@{tail}เท่านั้น

https://github.com/felipec/git/commits/fc/tail


1
อาจเป็นทางออกเดียวที่มีเสถียรภาพ คุณเห็นหรือไม่ว่าสิ่งนี้สามารถเข้าสู่คอมไพล์ได้หรือไม่? ฉันไม่เห็นคำขอดึง
Alexander Klimetschek

@AlexanderKlimetschek ฉันไม่ได้ส่งแพทช์และฉันไม่คิดว่าจะได้รับการยอมรับ อย่างไรก็ตามฉันลองใช้วิธีอื่น: ตะขอ "update-branch" ซึ่งทำสิ่งที่คล้ายกันมาก วิธีนี้โดยค่าเริ่มต้น Git จะไม่ทำอะไรเลย แต่คุณสามารถเปิดใช้ hook เพื่ออัพเดทสาขาหาง คุณจะไม่มี devel @ {tail} แต่จะไม่เลวร้ายนักหากใช้ tails / devel แทน
FelipeC

3

ต่อไปนี้เป็นรุ่นปรับปรุงของคำตอบของฉันก่อนหน้าคำตอบก่อนหน้า มันขึ้นอยู่กับความมุ่งมั่นข้อความจากการผสานเพื่อค้นหาที่สาขาถูกสร้างขึ้นครั้งแรก

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

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}

3

คำสั่งต่อไปนี้จะเปิดเผย SHA1 ของ Commit A

git merge-base --fork-point A


สิ่งนี้จะไม่เกิดขึ้นถ้าสาขาหลักและสาขาย่อยมีการรวมกันระหว่างกัน
nawfal

2

ฉันดูเหมือนจะได้รับความสุขด้วย

git rev-list branch...master

บรรทัดสุดท้ายที่คุณได้รับคือการกระทำครั้งแรกในสาขาดังนั้นมันเป็นเรื่องของการรับผู้ปกครองของที่ ดังนั้น

git rev-list -1 `git rev-list branch...master | tail -1`^

ดูเหมือนว่าจะทำงานให้ฉันและไม่ต้องการ diffs และอื่น ๆ (ซึ่งเป็นประโยชน์ในขณะที่เราไม่ได้รุ่น diff)

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


2

เพื่อค้นหาการกระทำจากจุดแยกคุณสามารถใช้สิ่งนี้

git log --ancestry-path master..topicbranch

คำสั่งนี้ใช้ไม่ได้กับฉันใน exmple ที่กำหนด โปรดให้อะไรที่คุณให้เป็นพารามิเตอร์สำหรับช่วงการยอมรับ
Jesper Rønn-Jensen

2

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

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

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

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

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

นี่คือสิ่งที่มนุษย์คาดหวังตามแนวคิด:

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

นี่คือสิ่งที่คุณได้รับ:

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

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

Either:
      / - C2 (B1)
    -- C1
      \ - C3 (B2)
Or:
      / - C3 (B1)
    -- C1
      \ - C2 (B2)

คุณรู้อยู่เสมอว่า C1 มาก่อน C2 และ C3 แต่คุณไม่เคยรู้อย่างน่าเชื่อถือว่า C2 มาก่อน C3 หรือ C3 มาก่อน C2 (เพราะคุณสามารถตั้งเวลาบนเวิร์กสเตชันของคุณเป็นอะไรก็ได้) B1 และ B2 ก็สร้างความเข้าใจผิดด้วยเพราะคุณไม่รู้ว่าสาขาไหนมาก่อน คุณสามารถคาดเดาได้ดีมากและแม่นยำในหลายกรณี มันเป็นเหมือนสนามแข่ง โดยทั่วไปทุกอย่างเท่าเทียมกันกับรถยนต์คุณสามารถสมมติได้ว่ารถยนต์ที่มาในรอบหลังเริ่มตักข้างหลัง นอกจากนี้เรายังมีการประชุมที่มีความน่าเชื่อถือมากตัวอย่างเช่นท่านอาจารย์จะเป็นตัวแทนของกิ่งไม้ที่มีอายุยืนยาวที่สุดแม้จะเศร้าที่ฉันเห็นกรณีที่แม้จะไม่เป็นเช่นนั้นก็ตาม

ตัวอย่างที่ให้ไว้ที่นี่เป็นตัวอย่างการรักษาประวัติ:

Human:
    - X - A - B - C - D - F (B1)
           \     / \     /
            G - H ----- I - J (B2)
Real:
            B ----- C - D - F (B1)
           /       / \     /
    - X - A       /   \   /
           \     /     \ /
            G - H ----- I - J (B2)

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

Paths:
    F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
    J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))

นี่ไม่ใช่การแสดงที่มีประสิทธิภาพมากนัก แต่เป็นการแสดงออกของเส้นทางทั้งหมดที่สามารถนำมาจากการอ้างอิงแต่ละครั้ง (B1 และ B2)

ที่เก็บข้อมูลภายในของ Git มีลักษณะดังนี้ (ไม่ใช่ว่า A ในฐานะผู้ปกครองปรากฏสองครั้ง):

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

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

Paths simplified:
    F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
    F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
    J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
    F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
    F->D->C->B->A | J->I->->G->A | A->X
Topological:
    - X - A - B - C - D - F (B1)
           \
            G - H - I - J (B2)

เมื่อทั้งคู่โดนโซ่ของพวกเขาจะเหมือนกันก่อนหน้านั้นโซ่ของพวกเขาจะแตกต่างกันอย่างสิ้นเชิง คนแรกที่กระทำร่วมกันอีกสองคนคือการกระทำร่วมกันคือบรรพบุรุษและจากที่พวกเขาแยกออกจากกัน อาจมีความสับสนระหว่างคำที่กระทำสาขาและผู้อ้างอิง ในความเป็นจริงคุณสามารถรวมการกระทำ นี่คือสิ่งที่ผสานกันจริงๆ การอ้างอิงเพียงชี้ไปที่การคอมมิชชันและสาขาจะไม่มีอะไรมากไปกว่าการอ้างอิงในโฟลเดอร์. git / refs / heads ตำแหน่งของโฟลเดอร์คือสิ่งที่กำหนดว่าการอ้างอิงนั้นเป็นสาขามากกว่าสิ่งอื่นเช่นแท็ก

ที่ที่คุณสูญเสียประวัติคือการผสานจะทำหนึ่งในสองสิ่งนี้ขึ้นอยู่กับสถานการณ์

พิจารณา:

      / - B (B1)
    - A
      \ - C (B2)

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

      / - B - D (B1)
    - A      /
      \ --- C (B2)

ณ จุดนี้ D (B1) ตอนนี้มีการเปลี่ยนแปลงทั้งสองชุดจากทั้งสองสาขา (ตัวเองและ B2) อย่างไรก็ตามสาขาที่สองไม่มีการเปลี่ยนแปลงจาก B1 หากคุณรวมการเปลี่ยนแปลงจาก B1 เป็น B2 เพื่อให้ซิงค์แล้วคุณอาจคาดหวังสิ่งที่มีลักษณะเช่นนี้ (คุณสามารถบังคับให้รวม git ทำเช่นนี้ได้อย่างไรก็ตามด้วย --no-ff):

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

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

From:
      / - B - D - E (B1)
    - A      /
      \ --- C (B2)
To:
      / - B - D - E (B1) (B2)
    - A      /
      \ --- C

หากคุณหยุดทำงานใน B1 สิ่งต่าง ๆ เป็นเรื่องปกติสำหรับการรักษาประวัติศาสตร์ในระยะยาว โดยทั่วไปแล้วจะมีเพียง B1 (ซึ่งอาจเป็นผู้เชี่ยวชาญ) ดังนั้นตำแหน่งของ B2 ในประวัติของ B2 จะแสดงถึงจุดที่ถูกรวมเข้ากับ B1 ได้สำเร็จ นี่คือสิ่งที่ git คาดหวังให้คุณทำกับสาขา B จาก A จากนั้นคุณสามารถรวม A เป็น B ได้มากเท่าที่คุณชอบเมื่อมีการเปลี่ยนแปลงสะสม แต่เมื่อรวม B กลับเข้า A คุณไม่คาดหวังว่าคุณจะทำงานกับ B และต่อไป . หากคุณยังคงทำงานในสาขาของคุณหลังจากการส่งต่ออย่างรวดเร็วไปยังสาขาที่คุณกำลังดำเนินการอยู่ดังนั้นประวัติของคุณที่ลบ B ในแต่ละครั้ง คุณกำลังสร้างสาขาใหม่ในแต่ละครั้งหลังจากส่งต่ออย่างรวดเร็วไปยังแหล่งที่มาแล้วส่งไปยังสาขา

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

1 ถึง 3 และ 5 ถึง 8 เป็นกิ่งที่มีโครงสร้างที่ปรากฏขึ้นหากคุณตามประวัติศาสตร์ทั้ง 4 หรือ 9 ไม่มีทางใดที่จะรู้ได้ว่าสาขาโครงสร้างที่ไม่มีชื่อและไม่อ้างอิงนั้นเป็นของสาขาที่มีชื่อและการอ้างอิงเป็น ในตอนท้ายของโครงสร้าง คุณอาจสมมุติจากภาพวาดนี้ว่า 0 ถึง 4 เป็นของ B1 และ 4 ถึง 9 เป็นของ B2 แต่นอกเหนือจาก 4 และ 9 ไม่สามารถรู้ได้ว่าสาขาไหนเป็นสาขาใดฉันก็วาดมันในแบบที่ให้ ภาพลวงตาของสิ่งนั้น 0 อาจเป็นของ B2 และ 5 อาจเป็นของ B1 มีกรณีที่แตกต่างกัน 16 กรณีในกรณีนี้ซึ่งชื่อสาขาแต่ละสาขาโครงสร้างอาจเป็นของ

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


"Git ไม่รักษาประวัติการอ้างอิง" มันทำ แต่ไม่ใช่โดยค่าเริ่มต้นและไม่ใช่เวลาที่ครอบคลุม ดูman git-reflogและส่วนที่เกี่ยวกับวันที่: "master@{one.week.ago} หมายถึง" โดยที่มาสเตอร์เคยชี้ไปที่หนึ่งสัปดาห์ก่อนในที่เก็บในเครื่องนี้ "" หรือการสนทนาบนใน<refname>@{<date>} man gitrevisionsและในcore.reflogExpire man git-config
Patrick Mevzek

2

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

ในเวลาเดียวกันฉันสร้างสาขาฉันยังสร้างแท็กที่มีชื่อเดียวกัน แต่ด้วย-initคำต่อท้ายตัวอย่างเช่นfeature-branchและfeature-branch-initและ

(มันแปลกมากที่นี่เป็นคำถามที่ตอบยากมาก!)


1
เมื่อพิจารณาถึงความโง่เขลาที่เหลือเชื่อของการออกแบบแนวคิดของ "สาขา" โดยไม่มีความคิดใด ๆ ว่ามันถูกสร้างขึ้นเมื่อใดและที่ไหน ... บวกกับความยุ่งยากอันยิ่งใหญ่ของโซลูชั่นที่แนะนำอื่น ๆ - โดยผู้ที่พยายามจะฉลาด สิ่งที่ฉันคิดว่าฉันต้องการทางออกของคุณ มีเพียงภาระเดียวที่ต้องจำไว้ให้ทำเช่นนี้ทุกครั้งที่คุณสร้างสาขา - สิ่งที่ผู้ใช้คอมไพล์ทำบ่อยมาก นอกจากนี้ - ฉันอ่านบางแห่งที่ 'แท็กมีโทษว่า' หนัก ' ถึงกระนั้นฉันคิดว่านั่นคือสิ่งที่ฉันคิดว่าฉันจะทำ
Motti Shneor

1

วิธีง่ายๆในการเพียงแค่ทำให้มันง่ายที่จะเห็นจุดแตกแขนงในคือการใช้ตัวเลือกgit log --graph--first-parent

ตัวอย่างเช่นรับrepoจากคำตอบที่ยอมรับได้ :

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

ตอนนี้เพิ่ม--first-parent:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

นั่นทำให้ง่ายขึ้น!

หมายเหตุถ้า repo มีสาขามากมายคุณจะต้องการระบุ 2 สาขาที่คุณกำลังเปรียบเทียบแทนการใช้--all:

$ git log --decorate --oneline --graph --first-parent master origin/topic

0

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

ในความคิดนี้มันไม่ง่ายเลยที่จะคำนวณด้วยคำสั่งเชลล์ git ปกติเนื่องจากgit rev-list- เครื่องมือที่ทรงพลังที่สุดของเรา - ไม่อนุญาตให้เรา จำกัดเส้นทางที่มีการส่งคำสั่ง ที่ใกล้เคียงที่สุดที่เรามีคือgit rev-list --boundaryซึ่งสามารถให้ชุดของความมุ่งมั่นทั้งหมดที่ (หมายเหตุ: git rev-list --ancestry-pathน่าสนใจ แต่ฉันไม่มีวิธีทำให้มีประโยชน์ที่นี่)

นี่คือสคริปต์: https://gist.github.com/abortz/d464c88923c520b79e3d https://gist.github.com/abortz/d464c88923c520b79e3dมันค่อนข้างง่าย แต่เนื่องจากการวนซ้ำมันซับซ้อนพอที่จะรับประกันส่วนสำคัญ

โปรดทราบว่าโซลูชันอื่น ๆ ส่วนใหญ่ที่เสนอที่นี่ไม่สามารถทำงานได้ในทุกสถานการณ์ด้วยเหตุผลง่าย ๆ : git rev-list --first-parentไม่น่าเชื่อถือในประวัติเชิงเส้นเนื่องจากสามารถรวมเข้ากับการสั่งซื้อ

git rev-list --topo-orderในทางกลับกันเป็นอย่างมากประโยชน์ - สำหรับการเดินที่กระทำตามลำดับภูมิประเทศ - แต่การทำ diffs เปราะ: มีการเรียงลำดับภูมิประเทศที่เป็นไปได้หลายอย่างสำหรับกราฟที่กำหนดดังนั้นคุณจึงขึ้นอยู่กับความเสถียรของการเรียงลำดับ ที่กล่าวว่าวิธีการแก้ปัญหาของ strongk7 อาจทำงานได้ดีเกือบตลอดเวลา อย่างไรก็ตามมันช้ากว่าของฉันซึ่งเป็นผลมาจากการเดินประวัติทั้งหมดของ repo ... สองครั้ง :-)


0

การดำเนินการต่อไปนี้เทียบเท่า git svn log --stop-on-copyและยังสามารถใช้ค้นหาต้นกำเนิดสาขา

เข้าใกล้

  1. รับหัวทุกสาขา
  2. รวบรวม mergeBase สำหรับสาขาเป้าหมายซึ่งกันและกันสาขา
  3. git.log และทำซ้ำ
  4. หยุดการคอมมิตครั้งแรกที่ปรากฏในรายการ mergeBase

เหมือนแม่น้ำทุกสายวิ่งไปที่ทะเลกิ่งไม้ทุกกิ่งวิ่งไปหาเจ้านายดังนั้นเราจึงพบว่าการผสานระหว่างกิ่งก้านที่ดูเหมือนไม่เกี่ยวข้อง เมื่อเราเดินกลับจากหัวหน้าสาขาผ่านบรรพบุรุษเราสามารถหยุดที่ฐานการผสานที่มีศักยภาพแรกได้เนื่องจากในทางทฤษฎีมันควรเป็นจุดกำเนิดของสาขานี้

หมายเหตุ

  • ฉันไม่ได้ลองวิธีนี้ซึ่งพี่น้องและลูกพี่ลูกน้องรวมตัวกัน
  • ฉันรู้ว่าต้องมีทางออกที่ดีกว่า

รายละเอียด: https://stackoverflow.com/a/35353202/9950


-1

คุณสามารถใช้คำสั่งต่อไปนี้เพื่อส่งคืนการคอมมิชชันที่เก่าที่สุดใน branch_a ซึ่งไม่สามารถเข้าถึงได้จากมาสเตอร์:

git rev-list branch_a ^master | tail -1

บางทีด้วยการเพิ่มสติตรวจสอบว่าผู้ปกครองของการกระทำนั้นสามารถเข้าถึงได้จากต้นแบบ ...


1
มันใช้งานไม่ได้ ถ้า branch_a ได้รับการรวมกันเป็นหนึ่งครั้งแล้วดำเนินการต่อการรวมนั้นจะถือเป็นส่วนหนึ่งของต้นแบบดังนั้นพวกเขาจะไม่ปรากฏใน ^ master
FelipeC

-2

คุณสามารถตรวจสอบ reflog ของสาขา A เพื่อค้นหาสิ่งที่มันถูกสร้างขึ้นรวมทั้งประวัติเต็มของการกระทำที่ชี้ไปที่สาขานั้น Reflogs .git/logsอยู่ใน


2
ฉันไม่คิดว่ามันจะใช้งานได้ทั่วไปเพราะ reflog สามารถถูกเล็มได้ และฉันไม่คิดว่า (?) reflogs ได้รับการผลักดันอย่างใดอย่างหนึ่งดังนั้นนี้จะทำงานเฉพาะในสถานการณ์ repo เดียว
GaryO

-2

ฉันเชื่อว่าฉันได้พบวิธีที่เกี่ยวข้องกับทุกกรณีที่กล่าวถึงที่นี่:

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

ชาร์ลส์เบลีย์ค่อนข้างถูกต้องว่าการแก้ปัญหาตามคำสั่งของบรรพบุรุษมีค่า จำกัด เท่านั้น ในตอนท้ายของวันที่คุณต้องการบันทึกบางส่วนของ "การกระทำนี้มาจากสาขา X" แต่บันทึกดังกล่าวมีอยู่แล้ว; โดยค่าเริ่มต้น 'git merge' จะใช้ข้อความกระทำเช่น "รวมสาขา 'branch_A' เป็นหลัก" สิ่งนี้จะบอกคุณว่าการกระทำทั้งหมดจากผู้ปกครองคนที่สอง (การกระทำ ^ 2) มาจาก 'branch_A' และถูกรวมเข้าด้วยกันเป็นครั้งแรก parent (คอมมิชชัน ^ 1) ซึ่งคือ 'master'

อาวุธที่มีข้อมูลนี้คุณสามารถค้นหาการผสานแรกของ 'branch_A' (ซึ่งเป็นเมื่อ 'branch_A' เข้ามามีชีวิตจริง ๆ ) และค้นหาผสานฐานซึ่งจะเป็นจุดสาขา :)

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

สำหรับประโยชน์:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

จากนั้นคุณสามารถทำ ' git branch-point branch_A'

สนุก ;)


4
การใช้ข้อความผสานนั้นมีความเปราะบางมากกว่าการตั้งสมมติฐานเกี่ยวกับคำสั่งของผู้ปกครอง มันไม่ใช่แค่สถานการณ์สมมุติเช่นกัน ฉันมักจะใช้git merge -mในการพูดในสิ่งที่ฉันได้รวมไว้ในมากกว่าชื่อของสาขาชั่วคราว potentionally (เช่น "ผสานการเปลี่ยนแปลงการฉีดเป็นคุณสมบัติ xyz refactor") สมมติว่าฉันเป็นประโยชน์กับ-mตัวอย่างของฉันน้อยลง? ปัญหาไม่สามารถละลายได้โดยทั่วไปเพราะฉันสามารถสร้างประวัติศาสตร์เดียวกันกับหนึ่งหรือสองสาขาชั่วคราวและไม่มีทางที่จะบอกความแตกต่าง
CB Bailey

1
@CharlesBailey นั่นเป็นปัญหาของคุณแล้ว คุณไม่ควรลบบรรทัดเหล่านั้นออกจากข้อความยืนยันคุณควรเพิ่มข้อความที่เหลือด้านล่างบรรทัดเดิม ทุกวันนี้ 'git merge' จะเปิดตัวแก้ไขให้คุณโดยอัตโนมัติเพื่อเพิ่มสิ่งที่คุณต้องการและสำหรับ git รุ่นเก่าที่คุณสามารถทำได้คือ 'git merge --edit' ไม่ว่าจะด้วยวิธีใดคุณสามารถใช้ commit hook เพื่อเพิ่ม "Commited on branch 'foo'" ในแต่ละการกระทำหากนั่นคือสิ่งที่คุณต้องการจริงๆ อย่างไรก็ตามวิธีนี้ทำงานสำหรับคนส่วนใหญ่
FelipeC

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