ยกเลิกการรีเซ็ตคอมไพล์ - ฮาร์ดด้วยไฟล์ที่ไม่ได้กำหนดไว้ในพื้นที่การจัดเตรียม


110

ฉันกำลังพยายามกู้คืนงานของฉัน ฉันโง่ได้git reset --hardแต่ก่อนที่ผมเคยทำเท่านั้นและไม่ได้ทำget add . git commitกรุณาช่วย! นี่คือบันทึกของฉัน:

MacBookPro:api user$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)

#   modified:   .gitignore
...


MacBookPro:api user$ git reset --hard
HEAD is now at ff546fa added new strucuture for api

เป็นไปได้ไหมที่จะเลิกทำgit reset --hardในสถานการณ์นี้


@MarkLongair คนน่ากลัว! คุณเพิ่งได้งานของฉันคืน! ฉันเขียนสคริปต์ Python เพื่อสร้างไฟล์ของผลลัพธ์ทั้งหมด! ฉันจะเพิ่มสคริปต์เป็นคำตอบ
บอย

4
ไม่ใช่ 'โง่' .. แต่ 'ไร้เดียงสา' ... เพราะฉันทำเหมือนกัน!
Rosdi Kasim

อาจจะยังโง่อยู่ ;-)
Duncan McGregor

นี่เป็นบทความที่ยอดเยี่ยมเกี่ยวกับวิธีย้อนกลับบางส่วนนี้ จะต้องใช้การทำงานด้วยตนเอง
ม.ค. Swart

@MarkLongair `` ค้นหา. git / objects / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | เรียงลำดับ `` ทำงานให้ฉัน วันที่ยังปรากฏขึ้นเริ่มการตรวจสอบ blobs จากจุดสิ้นสุด
ZAky

คำตอบ:


175

คุณควรจะสามารถกู้คืนไฟล์ใด ๆ ที่คุณเพิ่มลงในดัชนี (เช่นในสถานการณ์ของคุณด้วยgit add .) แม้ว่ามันอาจจะใช้งานได้เล็กน้อย ในการเพิ่มไฟล์ลงในดัชนี git จะเพิ่มไฟล์ลงในฐานข้อมูลออบเจ็กต์ซึ่งหมายความว่าสามารถกู้คืนได้ตราบเท่าที่การรวบรวมขยะยังไม่เกิดขึ้น มีตัวอย่างวิธีการทำในคำตอบของ Jakub Narębskiที่นี่:

อย่างไรก็ตามฉันลองใช้ที่เก็บทดสอบแล้วและมีปัญหาสองสามอย่าง - --cachedควรจะเป็น--cacheและฉันพบว่ามันไม่ได้สร้าง.git/lost-foundไดเร็กทอรีจริง อย่างไรก็ตามขั้นตอนต่อไปนี้ใช้ได้ผลสำหรับฉัน:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)")

สิ่งนี้ควรส่งออกอ็อบเจ็กต์ทั้งหมดในฐานข้อมูลอ็อบเจ็กต์ที่ไม่สามารถเข้าถึงได้โดย ref ใด ๆ ในดัชนีหรือผ่าน reflog ผลลัพธ์จะมีลักษณะดังนี้:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... และสำหรับแต่ละกลุ่มคุณสามารถทำได้:

git show 907b308

เพื่อส่งออกเนื้อหาของไฟล์


ผลผลิตมากเกินไป?

อัปเดตเพื่อตอบสนองต่อความคิดเห็นของseheด้านล่าง:

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

ขั้นแรกให้บันทึกผลลัพธ์ของคำสั่งด้วย:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > all

ตอนนี้ชื่อวัตถุของการกระทำที่ไม่สามารถเข้าถึงได้เหล่านั้นสามารถพบได้ด้วย:

egrep commit all | cut -d ' ' -f 3

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

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") \
  $(egrep commit all | cut -d ' ' -f 3)

สิ่งนี้ช่วยลดจำนวนวัตถุที่คุณต้องพิจารณาลงอย่างมาก


ปรับปรุง: ฟิลิป Oakleyด้านล่างแสดงให้เห็นวิธีการตัดลงจำนวนของวัตถุที่จะต้องพิจารณาซึ่งเป็นเพียงการพิจารณาไฟล์ส่วนใหญ่เมื่อเร็ว ๆ .git/objectsนี้ภายใต้การปรับเปลี่ยนอีก คุณสามารถค้นหาสิ่งเหล่านี้ได้ด้วย:

find .git/objects/ -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort

(ฉันพบfindคำเรียกร้องนั้นที่นี่ ) ส่วนท้ายของรายการนั้นอาจมีลักษณะดังนี้:

2011-08-22 11:43:43.0234896770 .git/objects/b2/1700b09c0bc0fc848f67dd751a9e4ea5b4133b
2011-09-13 07:36:37.5868133260 .git/objects/de/629830603289ef159268f443da79968360913a

ในกรณีนี้คุณสามารถดูวัตถุเหล่านั้นด้วย:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(โปรดทราบว่าคุณต้องลบ/ในตอนท้ายของเส้นทางเพื่อรับชื่อวัตถุ)


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

3
เป็นไปไม่ได้ที่จะดูการประทับเวลาออบเจ็กต์สำหรับออบเจ็กต์ล่าสุดที่ช้ากว่าคอมมิตล่าสุดของคุณหรือไม่? หรือฉันขาดอะไรไป
Philip Oakley

1
@Philip Oakley: ขอบคุณสำหรับคำแนะนำนั้นฉันได้เพิ่มคำแนะนำเพื่อค้นหาวัตถุที่แก้ไขล่าสุดในฐานข้อมูลวัตถุ
Mark Longair

2
เพื่อนมันสุดยอดมาก! ตอนนี้บันทึก @ $$ ของฉันไว้ และฉันได้เรียนรู้วิทยาศาสตร์คอมไพล์ ดูสิ่งที่คุณเพิ่มในดัชนีดีกว่า ...
bowsersenior

2
อย่าขโมยแสงมะนาวที่นี่ แต่เป็นเพียงบันทึกย่อเนื่องจากฉันพบปัญหาเดียวกันและคิดว่าฉันสูญเสียงานทั้งหมด สำหรับผู้ที่ใช้ IDE IDEมักจะมีโซลูชันการกู้คืนด้วยคลิกเดียว ในกรณีของ PHPstorm ฉันต้องคลิกขวาที่ไดเร็กทอรีและเลือกแสดงประวัติท้องถิ่นจากนั้นเปลี่ยนกลับเป็นสถานะสุดท้ายที่ถูกต้อง
Shalom Sam

58

ฉันเพิ่งทำgit reset --hardและสูญเสียหนึ่งการกระทำ แต่ฉันรู้แฮชคอมมิตดังนั้นฉันจึงสามารถgit cherry-pick COMMIT_HASHกู้คืนได้

ฉันทำสิ่งนี้ภายในไม่กี่นาทีหลังจากสูญเสียคอมมิตดังนั้นอาจได้ผลสำหรับคุณบางคน


5
ขอบคุณขอบคุณขอบคุณคุณ Managed เพื่อกู้คืนวันทำงานด้วยคำตอบนี้
Dace

22
อาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงคุณสามารถดูแฮชเหล่านั้นโดยใช้git reflogเช่นgit reset --hard-> git reflog(ดูที่แฮช HEAD @ {1}) และสุดท้ายgit cherry-pick COMMIT_HASH
Javier López

2
คนที่อ่านสิ่งนี้โปรดระวังว่าสิ่งนี้ใช้ได้กับแฮชคอมมิตเดียวเท่านั้นหากมีมากกว่าหนึ่งคอมมิตจะไม่สามารถแก้ปัญหาได้ในครั้งเดียว!
Ain Tohvri

4
คุณสามารถรีเซ็ต "ไปข้างหน้า" ได้จริง ดังนั้นหากคุณรีเซ็ตผ่านการคอมมิตหลายครั้งการทำ 'git reset --hard <commit>' อีกครั้งควรคืนค่าคอมมิตเชนทั้งหมดจนถึงจุดนั้น (เนื่องจากยังไม่ได้รับ GC)
akimsko

6
ซับเดียวสำหรับการกู้คืนการกระทำที่หายไปgit reset --hard(สมมติว่าคุณไม่ได้ทำอย่างอื่นหลังจากคำสั่งนั้น) คือgit reset --hard @{1}
Ajedi32

13

ขอบคุณ Mark Longair ฉันได้ของคืนแล้ว!

ก่อนอื่นฉันบันทึกแฮชทั้งหมดลงในไฟล์:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > allhashes

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

commits = ["c2520e04839c05505ef17f985a49ffd42809f",
    "41901be74651829d97f29934f190055ae4e93",
    "50f078c937f07b508a1a73d3566a822927a57",
    "51077d43a3ed6333c8a3616412c9b3b0fb6d4",
    "56e290dc0aaa20e64702357b340d397213cb",
    "5b731d988cfb24500842ec5df84d3e1950c87",
    "9c438e09cf759bf84e109a2f0c18520",
    ...
    ]

from subprocess import call
filename = "file"
i = 1
for c in commits:
    f = open(filename + str(i),"wb")
    call(["git", "show", c],stdout=f)
    i+=1

1
ใช่ นี่คือสิ่งที่ฉันต้องการ ฉันชอบสคริปต์ Python สำหรับการสร้างไฟล์ใหม่ด้วย คำตอบอื่น ๆ ทำให้ฉันกังวลเกี่ยวกับการสูญเสียข้อมูลด้วยการรวบรวมขยะดังนั้นการทิ้งไฟล์จึงเป็นชัยชนะสำหรับฉัน :)
Eric Olson

1
ฉันเขียนสคริปต์นี้ตามคำตอบนี้ ทำงานนอกกรอบ: github.com/pendashteh/git-recover-index
Alexar

1
mkdir lost; git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") | grep -Po '\s\S{40}$' | xargs -i echo "git show {} > lost/{}.blob" | shฉันคิดว่ามันง่ายต่อการทำงานโดยอัตโนมัติเช่นนี้ ไฟล์จะสิ้นสุดในlost/*.blob
Matija Nalis

6

วิธีแก้ปัญหาของ @ Ajedi32 ในความคิดเห็นใช้ได้ผลกับฉันในสถานการณ์นี้

git reset --hard @{1}

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


ฉันมีไฟล์ที่ไม่ได้รับคำสั่งจำนวนมาก จากนั้นฉันก็คอมไพล์ 1 ไฟล์และทำไฟล์git reset --hardซึ่งทำให้ repo ของฉันไม่เป็นที่รู้จัก @ ดันแคน @ {1} ทำอะไร และคุณอ้างถึงความคิดเห็นใด กำลังรีเซ็ต a git reset?
alpha_989

ฉันคิดว่า @ {1} เป็นการอ้างอิงสัมพัทธ์กับการกระทำครั้งสุดท้าย แต่ฉันไม่ใช่ผู้เชี่ยวชาญเพียงแค่รายงานสิ่งที่ได้ผลสำหรับฉัน
Duncan McGregor

3

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

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

ด้วยความสิ้นหวังฉันพยายามกด CTRL-Z ในตัวแก้ไขของฉัน (LightTable) หนึ่งครั้งในทุกแท็บที่เปิดอยู่ - นี่เป็นโชคดีที่กู้คืนไฟล์ในแท็บนั้นไปสู่สถานะล่าสุดก่อนไฟล์git reset --hard. HTH.


1
สถานการณ์เดียวกันเป๊ะพลาดที่จะเพิ่มดัชนีและทำการฮาร์ดรีเซ็ตและไม่มีวิธีแก้ปัญหาใดที่ใช้ได้ผลข้างต้นทั้งหมด อันนี้เป็นการเคลื่อนไหวที่ชาญฉลาดขอบคุณที่ฉันทำ Ctrl + Z และช่วยชีวิตฉันไว้ได้ ตัวแก้ไขของฉัน: SublimeText หนึ่งขึ้นจากด้านข้างของฉัน!
วิเวก

1

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

ฉันจัดเตรียมไฟล์บางไฟล์และทำแบบgit reset --hardประหลาดเล็กน้อยจากนั้นสังเกตว่าสถานะของฉันแสดงให้เห็นว่าไฟล์ทั้งหมดของฉันยังคงอยู่ในขั้นตอนและการลบทั้งหมดที่ไม่ได้จัดเตรียมไว้

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

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


1

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

  1. ดังที่กล่าวไว้โดย chilicuil ให้เรียกใช้git reflogเพื่อระบุแฮชคอมมิตที่คุณต้องการกลับไปที่นั่น

  2. ดังที่กล่าวไว้โดย akimsko คุณอาจไม่ต้องการเลือกเชอร์รี่เว้นแต่คุณจะสูญเสียการกระทำเพียงครั้งเดียวดังนั้นคุณควรวิ่ง git reset --hard <hash-commit-you-want>

หมายเหตุสำหรับผู้ใช้ egit Eclipse: ฉันไม่พบวิธีทำขั้นตอนเหล่านี้ภายใน Eclipse ด้วย egit การปิด Eclipse การรันคำสั่งด้านบนจากหน้าต่างเทอร์มินัลจากนั้นการเปิด Eclipse อีกครั้งก็ใช้ได้ดีสำหรับฉัน


0

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

ขั้นตอนมีดังนี้

  1. ในการกดเป็นกลุ่มและพิมพ์คำสั่ง: set undodirถ้าคุณได้persistent undoเปิดอยู่ในของคุณก็จะแสดงผลคล้ายกับ.vimrc undodir=~/.vim_runtime/temp_dirs/undodir

  2. ใน repo ของคุณใช้git logเพื่อค้นหาวันที่ / เวลาสุดท้ายที่คุณทำการคอมมิตครั้งล่าสุด

  3. ในการสำรวจเปลือกของคุณที่คุณใช้undodir cd ~/.vim_runtime/temp_dirs/undodir

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

    find . -newermt "2018-03-20 11:24:44" \! -newermt "2018-03-23" \( -type f -regextype posix-extended -regex '.*' \) \-not -path "*/env/*" -not -path "*/.git/*"

    ที่นี่ "2018-03-20 11:24:44" คือวันที่และเวลาของการคอมมิตครั้งสุดท้าย หากวันที่คุณทำgit reset --hardคือ "2018-03-22" ให้ใช้ "2018-03-22" จากนั้นใช้ "2018-03-23" นี่เป็นเพราะมุมมองของการค้นหาโดยที่ขอบเขตล่างรวมอยู่ด้วยและขอบเขตบนเป็นเอกสิทธิ์ https://unix.stackexchange.com/a/70404/242983

  5. จากนั้นไปที่แต่ละไฟล์เปิดขึ้นเป็นกลุ่มและทำ "ก่อนหน้านี้ 20m" คุณสามารถดูรายละเอียดเพิ่มเติมเกี่ยวกับ "ก่อนหน้า" ได้โดยใช้ "h ก่อนหน้า" ในที่นี้ earlier 20mหมายถึงกลับไปที่สถานะของไฟล์ 20 นาทีย้อนกลับไปโดยสมมติว่าคุณทำgit hard --resetย้อนหลัง 20 นาที ทำซ้ำกับไฟล์ทั้งหมดที่คายออกมาจากfindคำสั่ง ฉันแน่ใจว่าใครบางคนสามารถเขียนสคริปต์ที่จะรวมสิ่งเหล่านี้เข้าด้วยกัน


0

ฉันใช้ IntelliJ และสามารถผ่านแต่ละไฟล์และทำ:

Edit -> reload from disk

โชคดีที่ฉันเพิ่งทำgit statusถูกต้องก่อนที่จะล้างการเปลี่ยนแปลงในการทำงานดังนั้นฉันจึงรู้ว่าต้องโหลดอะไรใหม่


0

การจดจำลำดับชั้นไฟล์ของคุณและใช้เทคนิคของ Mark Longair ด้วยการดัดแปลง Phil Oakley ให้ผลลัพธ์ที่น่าทึ่ง

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

เฮ้!


0

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

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