วิธีการกำจัดคอมไพล์ทั้งหมดมุ่งมั่นที่เป็นหนึ่งเดียว?


480

คุณจะสควอชที่เก็บข้อมูลทั้งหมดของคุณลงไปที่การคอมมิทครั้งแรกได้อย่างไร?

ฉันสามารถลดการกระทำแรกได้ แต่นั่นทำให้ฉันมีข้อผูกพัน 2 ข้อ มีวิธีในการอ้างอิงการกระทำก่อนที่จะมีคนแรก?


8
"การกระทำก่อนคนแรก"?
innaM

31
@innaM - เป็นลำดับกระทำที่ให้กำเนิดคอมไพล์ (ความหวังมีอารมณ์ขันผ่านได้ดีพอผ่าน interweb)
ripper234

11
สำหรับผู้ที่มาในภายหลังสำหรับคำถามนี้โปรดใช้คำตอบที่ทันสมัยกว่านี้
Droogans

1
เกี่ยวข้อง แต่ไม่ซ้ำกัน ( --rootจริง ๆ แล้วไม่ใช่วิธีที่ดีที่สุดในการบีบอัดคอมมิชชันทั้งหมดถ้ามีสควอชจำนวนมาก): รวมสองคอมมิชชันแรกของที่เก็บ Git หรือไม่ .

2
Imo: นี่คือที่ดีที่สุดจาก @MrTux: stackoverflow.com/questions/30236694/...
J0hnG4lt

คำตอบ:


130

บางทีวิธีที่ง่ายที่สุดคือการสร้างที่เก็บใหม่ด้วยสถานะปัจจุบันของสำเนาการทำงาน หากคุณต้องการเก็บข้อความการส่งทั้งหมดที่คุณสามารถทำได้ก่อนgit log > original.logจากนั้นแก้ไขข้อความการส่งข้อความเริ่มต้นในที่เก็บใหม่:

rm -rf .git
git init
git add .
git commit

หรือ

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log

62
แต่คุณกำลังสูญเสียสาขาด้วยวิธีนี้
โอลิเวียร์รีคาโล

150
Git พัฒนาขึ้นตั้งแต่ได้รับคำตอบนี้ git rebase -i --rootไม่มีมีวิธีที่ง่ายและดีกว่า: ดู: stackoverflow.com/a/9254257/109618
David J.

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

3
นี่เป็นวิธีการแก้ปัญหาที่น่ากลัวที่ไม่จำเป็นทำลาย โปรดอย่าใช้มัน
Daniel Kamil Kozar

5
ก็จะทำลาย submodules -1
Krum

694

ในฐานะของคอมไพล์ 1.6.2git rebase --root -iคุณสามารถใช้

สำหรับแต่ละกระทำยกเว้นครั้งแรกที่มีการเปลี่ยนแปลงไปpicksquash


49
โปรดเพิ่มตัวอย่างคำสั่งการทำงานที่สมบูรณ์และตอบคำถามเดิม
Jake

29
ฉันหวังว่าฉันจะอ่านมันก่อนที่จะลบพื้นที่เก็บข้อมูลทั้งหมดของฉันออกไปเหมือนคำตอบที่ยอมรับพูดว่า: /
Mike Chamberlain

38
คำตอบนี้okแต่ถ้าคุณกำลังโต้ตอบ rebasing มากกว่าการพูด, 20 กระทำการ rebase โต้ตอบอาจจะเป็นวิธีที่ช้าเกินไปและเทอะทะ คุณอาจจะมีช่วงเวลาที่ยากลำบากในการพยายามฆ่าคนหลายร้อยหรือหลายพันคน ฉันจะไปกับการตั้งค่าอ่อนหรือผสมเพื่อรากกระทำแล้วแนะนำในกรณีนั้น

20
@Pred ห้ามใช้squashสำหรับการกระทำทั้งหมด แรก ๆ pickหนึ่งจะต้อง
Geert

14
หากคุณมีข้อผูกมัดมากมายมันยากที่จะเปลี่ยน 'เลือก' เป็น 'สควอช' ด้วยตนเอง ใช้:% s / pick / squash / gในบรรทัดคำสั่ง VIM เพื่อทำสิ่งนี้ให้เร็วขึ้น
eilas

314

ปรับปรุง

git squash-allผมได้ทำนามแฝง
ตัวอย่างการใช้ : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Caveat : อย่าลืมแสดงความคิดเห็นมิฉะนั้นจะใช้ข้อความยืนยันเริ่มต้น "เริ่มต้นใหม่"

หรือคุณสามารถสร้างนามแฝงด้วยคำสั่งต่อไปนี้:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

หนึ่งในสายการบิน

git reset $(git commit-tree HEAD^{tree} -m "A new start")

หมายเหตุ : ที่นี่ " A new start" เป็นเพียงตัวอย่างอย่าลังเลที่จะใช้ภาษาของคุณเอง

TL; DR

ไม่จำเป็นต้องสควอชใช้git commit-treeในการสร้างเด็กกำพร้ากระทำและไปกับมัน

อธิบาย

  1. สร้างการส่งคำสั่งเดียวผ่าน git commit-tree

    อะไรgit commit-tree HEAD^{tree} -m "A new start"คือ:

    สร้างวัตถุการยอมรับใหม่ตามวัตถุต้นไม้ที่ให้ไว้และปล่อยวัตถุการยอมรับใหม่ใน stdout ข้อความบันทึกถูกอ่านจากอินพุตมาตรฐานยกเว้นว่าจะได้รับตัวเลือก -m หรือ -F

    การแสดงออกHEAD^{tree}หมายถึงวัตถุต้นไม้ที่สอดคล้องกับHEADคือปลายของสาขาปัจจุบันของคุณ เห็นต้นไม้วัตถุและมอบวัตถุ

  2. รีเซ็ตสาขาปัจจุบันเป็นการกระทำใหม่

    จากนั้นgit resetเพียงรีเซ็ตสาขาปัจจุบันไปยังวัตถุการยอมรับที่สร้างขึ้นใหม่

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

รูปแบบ: Repo ใหม่จากเทมเพลตโครงการ

สิ่งนี้มีประโยชน์ในการสร้าง "การเริ่มต้นยอมรับ" ในโครงการใหม่โดยใช้ที่เก็บอื่นเป็นเทมเพลต / archetype / seed / โครงกระดูก ตัวอย่างเช่น:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

วิธีนี้หลีกเลี่ยงการเพิ่ม repo เทมเพลตเป็นรีโมท ( originหรืออย่างอื่น) และยุบประวัติของ repo เทมเพลตลงในการคอมมิทเริ่มต้นของคุณ


6
ไวยากรณ์การแก้ไข git (HEAD ^ {tree}) มีการอธิบายไว้ที่นี่ในกรณีที่คนอื่นกำลังสงสัย: jk.gs/gitrevisions.html
Colin Bowern

1
สิ่งนี้รีเซ็ตทั้งพื้นที่เก็บข้อมูลโลคัลและรีโมตหรือเพียงหนึ่งในนั้น?
aleclarson

4
@aleclarson นี่รีเซ็ตเฉพาะสาขาปัจจุบันในที่เก็บภายในใช้git push -fสำหรับการเผยแพร่
ryenus

2
git cloneผมพบว่าคำตอบนี้ขณะที่กำลังมองหาวิธีที่จะเริ่มโครงการใหม่จากที่เก็บแม่แบบโครงการที่ไม่เกี่ยวข้องกับ ถ้าคุณเพิ่ม--hardไปgit resetและสลับHEADกับFETCH_HEADในgit commit-treeคุณสามารถสร้างเริ่มต้นกระทำหลังจากเรียก repo แม่แบบ ฉันได้แก้ไขคำตอบพร้อมกับส่วนท้ายที่แสดงสิ่งนี้
แถบเครื่องมือ

4
คุณสามารถกำจัด "Caveat" นั้นได้ แต่ใช้เพียง${1?Please enter a message}
Elliot Cameron

172

หากสิ่งที่คุณต้องการจะทำคือการสควอชคอมมิชชันทั้งหมดลงไปที่รูทการคอมมิชชัน

git rebase --interactive --root

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

ต่อไปนี้เป็นวิธีแก้ปัญหาที่รวดเร็วและมีประสิทธิภาพมากขึ้นเมื่อคุณทำสัญญาจำนวนมาก:

ทางเลือกทางออก # 1: สาขาเด็กกำพร้า

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

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

เอกสารอ้างอิง:

ทางเลือกทางเลือก # 2: ซอฟต์รีเซ็ต

อีกวิธีที่มีประสิทธิภาพก็คือการใช้การตั้งค่าผสมหรืออ่อนไปยังรากกระทำ<root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

เอกสารอ้างอิง:


22
ทางเลือกทางออก # 1: สาขาเด็กกำพร้า - หิน!
โทมัส

8
โซลูชันทางเลือก # 1 FTW git push origin master --forceเพียงเพื่อเพิ่มถ้าคุณต้องการที่จะผลักดันการเปลี่ยนแปลงของคุณจากระยะไกลให้ทำ
Eddy Verbruggen

1
ไม่ควรลืมgit push --force
NecipAllef

อย่าทำสาขาเด็กกำพร้าในรหัส (เช่นอย่าทำทางเลือก # 1 ข้างต้น) หากคุณกำลังจะผลักดันให้เปิดคำขอ Github ดึง !!! Github จะปิดการประชาสัมพันธ์ของคุณเพราะหัวปัจจุบันไม่ได้เป็นลูกหลานของหัวหม่าเก็บไว้
Andrew Mackie

โซลูชันทางเลือก # 1 ยังช่วยหลีกเลี่ยงความขัดแย้งที่อาจเกิดขึ้นได้เมื่อการ
แบนด์วิดธ์

52
echo "message" | git commit-tree HEAD^{tree}

สิ่งนี้จะสร้างการคอมมิชชันกำพร้าด้วยต้นไม้แห่ง HEAD และส่งออกชื่อ (SHA-1) บน stdout จากนั้นเพียงแค่รีเซ็ตสาขาของคุณที่นั่น

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")จะทำให้ง่ายขึ้น
ryenus

4
^ นี่! - ควรเป็นคำตอบ ไม่แน่ใจว่ามันเป็นความตั้งใจของผู้เขียนหรือไม่ แต่เป็นของฉัน (ต้องการ repo ที่เก่าแก่ที่มีความมุ่งมั่นเพียงอย่างเดียวและนั่นทำให้งานสำเร็จ)
chesterbr

@ryenus โซลูชันของคุณทำในสิ่งที่ฉันกำลังมองหาอย่างแน่นอน หากคุณเพิ่มความคิดเห็นของคุณเป็นคำตอบฉันจะยอมรับมัน
tldr

2
เหตุผลที่ฉันไม่แนะนำให้รู้จักกับ subshell-variant ด้วยตัวเองคือมันไม่ทำงานบน cmd.exe ใน Windows
kusma

ในพรอมต์ Windows คุณอาจต้องอ้างอิงพารามิเตอร์ตัวสุดท้าย: echo "message" | git commit-tree "HEAD^{tree}"
Bernard

41

นี่คือวิธีที่ฉันลงเอยด้วยการทำสิ่งนี้ในกรณีที่เหมาะกับคนอื่น:

โปรดจำไว้ว่ามีความเสี่ยงในการทำสิ่งนี้เสมอและไม่ควรสร้างสาขาบันทึกก่อนเริ่มต้น

เริ่มต้นด้วยการเข้าสู่ระบบ

git log --oneline

เลื่อนไปที่คอมมิชชันแรกคัดลอก SHA

git reset --soft <#sha#>

แทนที่<#sha#>w / SHA ที่คัดลอกจากบันทึก

git status

ตรวจสอบให้แน่ใจว่าทุกอย่างเป็นสีเขียว git add -A

git commit --amend

แก้ไขการเปลี่ยนแปลงทั้งหมดในปัจจุบันให้กระทำครั้งแรกในปัจจุบัน

ตอนนี้บังคับให้กดสาขานี้และมันจะเขียนทับสิ่งที่มี


1
ตัวเลือกที่ยอดเยี่ยม! ง่ายจริงๆ
ครั้งใน

1
มันยอดเยี่ยมมากกว่า! ขอบคุณ!
Matt Komarnicki

1
โปรดทราบว่านี่เป็นเรื่องจริงที่จะทิ้งประวัติไว้รอบ ๆ คุณกำพร้า แต่ก็ยังอยู่ที่นั่น
แบรด

คำตอบที่มีประโยชน์มาก ... แต่คุณควรระวังว่าหลังจากคำสั่งแก้ไขคุณจะพบว่าตัวเองในโปรแกรมแก้ไขเป็นกลุ่มที่มีไวยากรณ์พิเศษ ESC, ENTER,: x คือเพื่อนของคุณ
Erich Kuester

ตัวเลือกที่ยอดเยี่ยม!
danivicario

36

ฉันอ่านบางอย่างเกี่ยวกับการใช้กราฟต์ แต่ไม่เคยตรวจสอบมันมากนัก

อย่างไรก็ตามคุณสามารถสควอชเหล่านั้น 2 ครั้งล่าสุดที่กระทำด้วยตนเองด้วยสิ่งนี้:

git reset HEAD~1
git add -A
git commit --amend

4
นี่คือคำตอบที่ฉันกำลังมองหาหวังว่ามันจะเป็นคำตอบที่ยอมรับได้!
Jay

นี่เป็นคำตอบที่น่าอัศจรรย์
ท่านอาจารย์โยดา

36

วิธีที่ง่ายที่สุดคือใช้คำสั่ง 'plumbing' update-refเพื่อลบสาขาปัจจุบัน

คุณไม่สามารถใช้งานได้git branch -Dเนื่องจากมีวาล์วนิรภัยเพื่อหยุดการลบสาขาปัจจุบัน

สิ่งนี้จะทำให้คุณกลับสู่สถานะ 'การส่งครั้งแรก' ซึ่งคุณสามารถเริ่มต้นด้วยการเริ่มต้นครั้งใหม่

git update-ref -d refs/heads/master
git commit -m "New initial commit"

16

git rebase --interactiveครั้งแรกที่สควอชกระทำทั้งหมดของคุณเป็นหนึ่งกระทำโดยใช้ ตอนนี้คุณเหลืออีกสองคนที่จะสควอช เมื่อต้องการทำเช่นนั้นอ่านใด ๆ ของ


15

ในหนึ่งบรรทัดจาก 6 คำ

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills คุณควรอ่านgit help checkoutเกี่ยวกับ--orphan
kyb

คุณสามารถเชื่อมโยงไปยังเอกสารเพื่อให้ทุกคนที่อ่านข้อความนี้ไม่ต้องค้นหาด้วยตนเองหรือไม่
Alexander Mills

1
ง่าย. นี่คือgit help checkout --orphan
kyb

8

สร้างการสำรองข้อมูล

git branch backup

รีเซ็ตเป็นคอมมิชชันที่ระบุ

git reset --soft <#root>

จากนั้นเพิ่มไฟล์ทั้งหมดลงในการจัดเตรียม

git add .

กระทำโดยไม่ต้องปรับปรุงข้อความ

git commit --amend --no-edit

ผลักดันสาขาใหม่ด้วยสควอชมุ่งมั่นที่จะซื้อ

git push -f

สิ่งนี้จะรักษาข้อความการส่งก่อนหน้านี้หรือไม่
not2qubit

1
@ not2qubit ไม่สิ่งนี้จะไม่เก็บรักษาข้อความการส่งก่อนหน้าแทนที่จะส่ง # 1 กระทำ # 2 ส่ง # 3 คุณจะได้รับการเปลี่ยนแปลงทั้งหมดในคอมมิทเหล่านั้นที่บรรจุไว้ในคอมมิชชันเดียว # 1 Commit # 1 จะเป็นคอมมิทที่<root>คุณรีเซ็ตไป git commit --amend --no-editจะยอมรับการเปลี่ยนแปลงทั้งหมดที่กระทำในปัจจุบันซึ่ง<root>ไม่จำเป็นต้องแก้ไขข้อความยืนยัน
David Morton

5

เพื่อสควอชโดยใช้กราฟ

เพิ่มไฟล์.git/info/graftsวางแฮชการคอมมิชชันที่คุณต้องการเป็นรูตของคุณ

git log จะเริ่มจากการกระทำนั้น

เพื่อให้มัน 'ทำงานจริง' git filter-branch


1

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

  • ผู้แต่ง (ชื่อและอีเมล)
  • วันที่ประพันธ์
  • Commiter (ชื่อและอีเมล)
  • วันที่ตกลง
  • ข้อความบันทึก Commmit

แน่นอนว่า commit-SHA ของการคอมมิทใหม่ / ครั้งเดียวจะเปลี่ยนไปเพราะมันหมายถึงประวัติศาสตร์ใหม่ (ไม่ใช่ -) กลายเป็น parentless / root-commit

ซึ่งสามารถทำได้โดยการอ่านและการตั้งค่าตัวแปรบางอย่างสำหรับgit log git commit-treeสมมติว่าคุณต้องการสร้างการส่งคำสั่งเดียวจากmasterในสาขาใหม่one-commitโดยคงการส่งมอบข้อมูลไว้ด้านบน:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')

1

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

git reset your-first-commit-hashtag
git add .
git commit --amend

จากนั้นแก้ไขการคอมมิตแรกหากจำเป็นและบันทึกไฟล์


1

สำหรับฉันมันใช้งานได้เช่นนี้: ฉันมีข้อผูกพันทั้งหมด 4 ข้อและใช้การรีบูตแบบโต้ตอบ:

git rebase -i HEAD~3

ความมุ่งมั่นแรกยังคงอยู่และฉันใช้เวลา 3 ครั้งล่าสุด

ในกรณีที่คุณติดอยู่ในโปรแกรมแก้ไขซึ่งจะปรากฏถัดไปคุณจะเห็น smth like:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

คุณต้องยอมรับก่อนและกำจัดคนอื่น ๆ สิ่งที่คุณควรมีคือ:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

สำหรับการใช้คีย์ INSERT เพื่อเปลี่ยนโหมด 'insert' และ 'แก้ไข'

:wqเพื่อบันทึกและออกจากการใช้งานบรรณาธิการ หากเคอร์เซอร์ของคุณอยู่ระหว่างเส้นที่กระทำหรือที่อื่นให้กด ESC แล้วลองใหม่

เป็นผลให้ฉันมีสองกระทำ: ครั้งแรกซึ่งยังคงอยู่และที่สองกับข้อความ "นี่คือการรวมกันของ 3 กระทำ"

ตรวจสอบรายละเอียดได้ที่นี่: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


0

ฉันมักจะทำสิ่งนี้:

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

  • เรียกใช้git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`เพื่อรีเซ็ตหัวของคุณเป็นการกระทำแรก แต่ปล่อยให้ดัชนีของคุณไม่เปลี่ยนแปลง การเปลี่ยนแปลงทั้งหมดตั้งแต่คอมมิชชันแรกจะปรากฏขึ้นพร้อมที่จะถูกคอมมิต

  • เรียกใช้git commit --amend -m "initial commit"เพื่อแก้ไขคอมมิชชันของคุณเป็นคอมมิทแรกและเปลี่ยนข้อความคอมมิทหรือถ้าคุณต้องการเก็บข้อความคอมมิทที่มีอยู่คุณสามารถรันgit commit --amend --no-edit

  • วิ่งgit push -fเพื่อบังคับผลักดันการเปลี่ยนแปลงของคุณ

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