ทำไม git stash pop ถึงบอกว่าไม่สามารถกู้คืนไฟล์ที่ไม่ได้ติดตามจากรายการที่ซ่อนได้


94

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

ดังนั้นฉันจึงจัดเตรียมการเปลี่ยนแปลงของฉันโดยใช้:

$ git stash push -a

(ในการมองย้อนกลับไปฉันอาจจะใช้--include-untrackedแทนได้--all)

จากนั้นเมื่อฉันไปเปิดที่ซ่อนฉันได้รับข้อผิดพลาดมากมายตามบรรทัดของ:

$ git stash pop
foo.txt already exists, no checkout
bar.txt already exists, no checkout
...
Could not restore untracked files from stash entry

ดูเหมือนจะไม่มีการเปลี่ยนแปลงใด ๆ ที่เรียกคืนจากที่ซ่อน

ฉันลองแล้ว$ git stash branch tempแต่นั่นก็แสดงข้อผิดพลาดเดียวกัน

ฉันหาวิธีแก้ปัญหานี้ซึ่งจะใช้:

$ git stash show -p | git apply

ภัยพิบัติถูกหลีกเลี่ยงในตอนนี้ แต่สิ่งนี้ทำให้เกิดคำถามขึ้น

เหตุใดข้อผิดพลาดนี้จึงเกิดขึ้นตั้งแต่แรกและฉันจะหลีกเลี่ยงได้อย่างไรในครั้งต่อไป


29
ฉันต้องใช้:git stash show -p | git apply --3
xmedeko

2
สิ่งเดียวที่ใช้ได้ผลสำหรับฉันคือความคิดเห็นข้างต้น !!
Mehraj Malik

4
ขอบคุณ @xmedeko ใครช่วยบอกความแตกต่างระหว่าง git stash show -p | ใช้ git และ git stash show -p | ใช้ git --3?
Deepak Mohandas

3
หากคุณตกใจเพียงรายการไฟล์ stashed ของคุณด้วยและกว่าไฟล์กู้ภัยหนึ่งโดยหนึ่ง:git stash show $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txtสิ่งนี้จะได้ไฟล์จากที่ซ่อนและบันทึกภายใต้ชื่ออื่น ตอนนี้คุณปลอดภัยแล้วที่จะทดลองด้วยวิธีการช่วยเหลือที่เหมาะสม (ดูคำตอบด้านล่าง) หากเกิดความผิดพลาดคุณจะมีไฟล์ที่ได้รับการช่วยเหลือเป็นทรัพยากรสุดท้ายเสมอ
Danijel

ว้าวขอบคุณ @xmedeko! อีกครั้งความคิดเห็นของคุณเป็นสิ่งเดียวที่ใช้ได้ผลและมันก็ง่ายมาก +1!
pcdev

คำตอบ:


99

ในฐานะที่เป็นคำอธิบายเพิ่มเติมโปรดทราบว่าการคอมgit stashมิตสองครั้งหรือสามคอมมิต ค่าเริ่มต้นคือสอง คุณจะได้สามตัวถ้าคุณใช้การสะกดของตัวเลือก--allหรือ--include-untracked

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

สิ่งที่อยู่ในที่ซ่อน

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

...--o--o--o   <-- branch (HEAD)
           |
           i

หากคุณกำลังสร้างที่ซ่อนตามปกติgit stashโค้ดจะสร้างขึ้นในwตอนนี้โดยการคัดลอกไฟล์แผนผังงานที่ติดตามทั้งหมดของคุณ (ลงในดัชนีเสริมชั่วคราว) Git ชุดแรกของผู้ปกครองนี้wมุ่งมั่นที่จะชี้ไปกระทำและผู้ปกครองที่สองไปยังจุดที่จะกระทำHEAD iสุดท้ายตั้งค่าstashให้ชี้ไปที่การwกระทำนี้:

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash

ถ้าคุณเพิ่ม--include-untrackedหรือ--all, Git ทำให้เป็นพิเศษกระทำuในระหว่างการทำและi wเนื้อหาสแน็ปช็อตสำหรับuคือไฟล์ที่ไม่ถูกติดตาม แต่ไม่ถูกเพิกเฉย ( --include-untracked) หรือไฟล์ที่ไม่ถูกติดตามแม้ว่าจะถูกละเว้น ( --all) พิเศษนี้uกระทำมีไม่มีพ่อแม่และจากนั้นเมื่อgit stashรถwจะกำหนดw's สามพ่อแม่นี้uกระทำเพื่อให้คุณได้รับ:

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash
            /
           u

ในจุดนี้ Git จะลบไฟล์ work-tree ที่รวมเข้าด้วยuกัน (ใช้git cleanเพื่อทำเช่นนั้น)

การกู้คืนที่ซ่อน

เมื่อคุณกู้คืนที่ซ่อนคุณมีตัวเลือกในการใช้--indexหรือไม่ใช้ นี้จะบอกgit stash apply(หรือคำสั่งใด ๆ ที่ใช้ภายในapplyเช่นpop) ว่ามันควรจะใช้iกระทำเพื่อพยายามที่จะปรับเปลี่ยนดัชนีปัจจุบันของคุณ การปรับเปลี่ยนนี้ทำได้ด้วย:

git diff <hash-of-i> <hash-of-i's-parent> | git apply --index

(ไม่มากก็น้อยมีรายละเอียดสำคัญมากมายที่เข้ามาขัดขวางแนวคิดพื้นฐานที่นี่)

หากคุณละเว้น--indexให้git stash applyละเว้นการiกระทำโดยสิ้นเชิง

หากที่เก็บมีเพียงสองคอมมิตgit stash applyตอนนี้สามารถใช้คอมwมิตได้ มันทำได้โดยการเรียกgit merge2 (โดยไม่อนุญาตให้คอมมิตหรือถือว่าผลลัพธ์เป็นการผสานปกติ) โดยใช้คอมมิตดั้งเดิมที่สร้างที่เก็บ ( iของพาเรนต์และพาเรนต์wแรก) เป็นฐานการผสานwเช่นเดียวกับ--theirsกระทำและปัจจุบันของคุณ (HEAD) กระทำเป็นเป้าหมายของการผสาน ถ้าการรวมสำเร็จทุกอย่างก็ดี - อย่างน้อยGitก็คิดอย่างนั้น - และgit stash applyตัวเองก็ทำสำเร็จ หากคุณเคยgit stash popใช้ที่ซ่อนโค้ดจะลดการซ่อน 3 หากการผสานล้มเหลว Git จะประกาศการใช้งานที่ล้มเหลว ถ้าคุณใช้git stash popgit stash applyรหัสยังคงซ่อนและให้ความล้มเหลวในสถานะเดียวกับ

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

ในการทำให้เกิดขึ้นคุณสามารถใช้git cleanตัวเองได้ แต่อย่าลืมว่าไฟล์ที่ไม่ได้ติดตาม (ละเว้นหรือไม่) ไม่มีตัวอื่นอยู่ในที่เก็บ Git ดังนั้นโปรดแน่ใจว่าไฟล์เหล่านี้สามารถถูกทำลายได้ทั้งหมด! หรือคุณสามารถทำให้ไดเรกทอรีชั่วคราวและย้ายไฟล์ที่มีเพื่อความปลอดภัยหรือแม้กระทั่งทำอีกgit stash save -uหรือgit stash save -aตั้งแต่ผู้ที่จะทำงานgit cleanให้คุณ แต่นั่นทำให้คุณมีที่uเก็บสไตล์อื่นเพื่อจัดการในภายหลัง


1refs/stashนี่คือในความเป็นจริง สิ่งนี้มีความสำคัญหากคุณตั้งชื่อสาขาstash: ชื่อเต็มของสาขาคือrefs/heads/stashดังนั้นสิ่งเหล่านี้จะไม่ขัดแย้งกัน แต่อย่าทำอย่างนั้น: Gitจะไม่รังเกียจ แต่คุณจะสับสนในตัวเอง :-)

2git stashรหัสจริงใช้git merge-recursiveโดยตรงที่นี่ สิ่งนี้จำเป็นด้วยเหตุผลหลายประการและยังมีผลข้างเคียงในการทำให้แน่ใจว่า Git ไม่ถือว่าเป็นการผสานเมื่อคุณแก้ไขข้อขัดแย้งและกระทำ

3นี่คือเหตุผลที่ผมขอแนะนำให้หลีกเลี่ยงในความโปรดปรานของgit stash pop git stash applyคุณมีโอกาสตรวจสอบสิ่งที่นำไปใช้และตัดสินใจว่าจะนำไปใช้จริงหรือไม่ ถ้าไม่คุณยังมีที่ซ่อนอยู่ซึ่งหมายความว่าคุณสามารถใช้git stash branchกู้คืนทุกอย่างได้อย่างสมบูรณ์ สมมติว่าไม่มีการuกระทำที่น่ารำคาญนั้น

4ควรมีจริงๆ: git stash apply --skip-untrackedหรือบางอย่าง นอกจากนี้ยังควรมีตัวแปรที่หมายถึงการทิ้งuไฟล์คอมมิตทั้งหมดลงในไดเร็กทอรีใหม่เช่นgit stash apply --untracked-into <dir>บางที


8
คำตอบที่มีรายละเอียดน่าอัศจรรย์ ขอขอบคุณที่สละเวลาเขียนสิ่งนี้ ฉันได้เรียนรู้มากมาย!
steinybot

คำอธิบายนี้สมควรได้รับตราฮีโร่ ขอบคุณที่ใส่รายละเอียดที่ยอดเยี่ยมเช่นนี้!
Lucas Fowler

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

3
ฉันไม่เข้าใจวิธีแก้ปัญหา เป็นเพียงการเพิ่ม--index: git stash apply --index?
Danijel

1
ขอบคุณ @torek ฉันทำครั้งแรกgit stash save --allจากนั้นฉันก็ทำทันทีgit stash applyแต่บางไฟล์หายไปเพราะฉันเปลี่ยนชื่อแล้วสร้างอีกครั้ง (ก่อนที่จะซ่อน) สิ่งที่ช่วยได้: git checkout stash@{0} -- .ฉันจะไม่รำคาญด้วยซ้ำgit checkout stash^3 -- .เพราะตอนนี้ทุกอย่างดูเหมือนจะเรียบร้อยแล้ว เป็นเรื่องเล็กน้อยที่ฉันไม่มีเวลาทำความเข้าใจว่าเกิดอะไรขึ้น ขอบคุณ.
Danijel

100

ฉันจัดการปัญหาของคุณใหม่แล้ว ดูเหมือนว่าถ้าคุณซ่อนไฟล์ที่ไม่ได้ติดตามและจากนั้นคุณสร้างไฟล์เหล่านั้น (ในตัวอย่างของคุณ, foo.txtและbar.txt) git stash popแล้วคุณมีการเปลี่ยนแปลงในท้องถิ่นไปยังไฟล์ที่ไม่ได้ติดตามว่าจะเขียนทับเมื่อคุณใช้

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

git checkout stash -- .

นี่คือบางส่วนข้อมูลเพิ่มเติมที่ฉันพบในคำสั่งก่อนหน้า


แผนผังการทำงานของฉันสะอาดหมดจดแม้ว่าอาจมีการเปลี่ยนแปลงไฟล์ที่ถูกเพิกเฉย
steinybot

1
ดูเหมือนว่าการใช้--all/ -a จะรวมไฟล์ที่ถูกละเว้นดังนั้นอาจเกี่ยวข้อง
Daniel Smith

1
ฉันจะสมมติว่าตรรกะเดียวกันนี้ใช้กับไฟล์ที่ถูกละเว้นและทำเครื่องหมายว่านี่เป็นคำตอบ (แม้ว่าฉันคิดว่าgit merge --squash --strategy-option=theirs stashวิธีนี้ดีกว่าในกรณีนี้)
steinybot

เห็นด้วยฉันชอบแนวทางที่สอง! ขอให้โชคดีกับงานของคุณ!
Daniel Smith

สิ่งนี้มีประโยชน์ แต่ไม่ได้กู้คืนไฟล์ที่ไม่ได้ติดตาม - หากคุณมีปัญหาเดียวกัน ( already exists, no checkout) ตรวจสอบคำตอบของฉันด้านล่าง
Erik Koopmans

25

หากต้องการขยายคำตอบของ Daniel Smith : โค้ดนั้นจะกู้คืนไฟล์ที่ติดตามเท่านั้นแม้ว่าคุณจะใช้--include-untracked(หรือ-u) เมื่อสร้างที่เก็บ รหัสเต็มที่ต้องการคือ:

git checkout stash -- .
git checkout stash^3 -- .
git stash drop

# Optional to unstage the changes (auto-staged by default).
git reset

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

  • ระวัง - สิ่งนี้จะเขียนทับทุกสิ่งที่มีเนื้อหาที่ซ่อนของคุณ!
  • การกู้คืนไฟล์โดยgit checkoutทำให้ไฟล์ทั้งหมดกลายเป็นฉากโดยอัตโนมัติดังนั้นฉันจึงเพิ่มgit resetทุกอย่างเพื่อยกเลิกขั้นตอน
  • ทรัพยากรบางอย่างใช้stash@{0}และstash@{0}^3ในการทดสอบของฉันมันใช้งานได้เหมือนกันโดยมีหรือไม่มี@{0}

แหล่งที่มา:


1
ทำไมคุณถึงลบที่ซ่อนทำไมไม่ทิ้งไว้ที่นั่นสักพักเพื่อเหตุผลด้านความปลอดภัย
Danijel

@ Danijel แน่ใจว่าคุณสามารถเก็บซ่อนได้ - ขึ้นอยู่กับกรณีการใช้งานของคุณฉันเดา สำหรับจุดประสงค์ของฉันฉันได้ทำการซ่อนเมื่อได้รับการกู้คืนแล้ว
Erik Koopmans

3

นอกเหนือจากคำตอบอื่น ๆ แล้วฉันยังใช้เคล็ดลับเล็กน้อย

  • ลบไฟล์ใหม่ทั้งหมด ( ไฟล์ที่มีอยู่แล้วเช่น foo.txt และ bar.txt ในคำถาม)
  • git stash apply (สามารถใช้คำสั่งใดก็ได้เช่นใช้ป๊อปเป็นต้น)

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