Mengapa git stash pop mengatakan bahwa ia tidak dapat memulihkan file yang tidak terlacak dari entri simpanan?

94

Saya memiliki banyak perubahan bertahap dan tidak bertahap dan saya ingin segera beralih ke cabang lain dan kemudian beralih kembali.

Jadi saya melakukan perubahan saya menggunakan:

$ git stash push -a

(Kalau dipikir-pikir, saya mungkin bisa menggunakan --include-untrackedalih-alih --all)

Kemudian ketika saya pergi untuk membuka simpanan saya mendapatkan banyak kesalahan di sepanjang baris:

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

Sepertinya tidak ada perubahan apa pun yang dipulihkan dari simpanan.

Saya juga mencoba $ git stash branch temptetapi itu menunjukkan kesalahan yang sama.

Saya memang menemukan cara untuk mengatasinya yang akan digunakan:

$ git stash show -p | git apply

Bencana dapat dihindari untuk saat ini, tetapi ini menimbulkan beberapa pertanyaan.

Mengapa kesalahan ini terjadi sejak awal dan bagaimana cara menghindarinya di lain waktu?

steinybot.dll
sumber
29
Saya harus menggunakan:git stash show -p | git apply --3
xmedeko
2
Satu-satunya hal yang berhasil bagi saya adalah DI ATAS KOMENTAR !!
Mehraj Malik
4
Terima kasih @xmedeko, adakah yang bisa membedakan antara git stash show -p | git apply dan git stash show -p | git apply --3?
Deepak Mohandas
3
Jika Anda panik, hanya daftar file Anda tersimpan dengan git stash showdan dari file penyelamatan satu per satu: $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt. Ini akan mengambil file dari simpanan dan menyimpannya dengan nama yang berbeda. Sekarang Anda aman untuk bereksperimen dengan metode penyelamatan yang tepat (lihat jawaban di bawah). Jika ada yang tidak beres, Anda selalu memiliki file yang diselamatkan sebagai sumber daya terakhir.
Danijel
Wow, terima kasih @xmedeko! Sekali lagi komentar Anda adalah satu-satunya hal yang berhasil, dan sangat sederhana. +1!
pcdev

Jawaban:

99

Sebagai sedikit penjelasan tambahan, catatan yang git stashmembuat dua komit, atau tiga komit. Defaultnya adalah dua; Anda mendapatkan tiga jika Anda menggunakan ejaan --allatau --include-untrackedopsi.

Dua, atau tiga, komit ini istimewa dalam satu hal penting: tidak ada cabang. Git menempatkannya melalui nama khusus stash. 1 Namun, hal terpenting adalah apa yang Git izinkan untuk Anda — dan buat Anda — lakukan dengan dua atau tiga komitmen ini. Untuk memahami hal ini kita perlu melihat apa yang ada di dalam commit tersebut.

Apa yang ada di dalam simpanan

Setiap komit bisa mendaftar satu atau lebih komit orang tua . Ini membentuk grafik, di mana kemudian komitmen menunjuk kembali ke yang sebelumnya. Stash biasanya menampung dua komit, yang ingin saya panggil iuntuk konten index / staging-area, dan wuntuk konten work-tree. Ingat juga bahwa setiap komit menyimpan sebuah snapshot. Dalam komit normal, snapshot ini dibuat dari konten index / staging-area. Jadi ikomit sebenarnya adalah komit normal sempurna! Hanya saja tidak di cabang mana pun:

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

Jika Anda membuat simpanan normal, git stashkodenya wsekarang dengan menyalin semua file pohon kerja terlacak Anda (ke dalam indeks tambahan sementara). Git menetapkan orang tua pertama dari wkomit ini untuk menunjuk ke HEADkomit, dan orang tua kedua untuk menunjuk ke komit i. Terakhir, set stashuntuk menunjuk ke wkomit ini :

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

Jika Anda menambahkan --include-untrackedatau --all, Git membuat komit ekstra u,, di antara pembuatan idan w. Konten snapshot untuk uadalah file yang tidak terlacak tetapi tidak diabaikan ( --include-untracked), atau file yang tidak terlacak meskipun diabaikan ( --all). Ekstra ini uberkomitmen memiliki tidak ada orang tua, dan kemudian ketika git stashmerek w, ia menetapkan w's ketiga orang tua untuk ini ukomit, sehingga Anda mendapatkan:

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

Git juga, pada titik ini, menghapus semua file pohon kerja yang berakhir di ukomit (menggunakan git cleanuntuk melakukan itu).

Mengembalikan simpanan

Saat Anda pergi untuk memulihkan simpanan, Anda memiliki opsi untuk menggunakan --index, atau tidak menggunakannya. Ini memberitahu git stash apply(atau salah satu perintah yang menggunakan internal apply, seperti pop) yang seharusnya menggunakan yang iberkomitmen untuk mencoba mengubah indeks Anda saat ini. Modifikasi ini dilakukan dengan:

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

(kurang lebih; ​​ada banyak detail kecil yang menghalangi ide dasar di sini).

Jika Anda menghilangkan --index, git stash applymengabaikan ikomit sepenuhnya .

Jika simpanan hanya memiliki dua komit, git stash applysekarang dapat menerapkan wkomit. Ini dilakukan dengan memanggil git merge2 (tanpa mengizinkannya untuk mengkomit atau memperlakukan hasil sebagai gabungan normal), menggunakan komit asli tempat simpanan dibuat ( iinduk pertama, dan winduk pertama) sebagai basis gabungan, wsebagai --theirskomit, dan komit (HEAD) Anda saat ini sebagai target penggabungan. Jika penggabungan berhasil, semua akan baik-baik saja — yah, setidaknya menurut Git — dan git stash applysukses itu sendiri. Jika Anda dulu git stash popmenggunakan simpanan, kode sekarang menjatuhkan simpanan. 3 Jika penggabungan gagal, Git menyatakan bahwa penerapan gagal. Jika Anda dulugit stash pop, kode mempertahankan simpanan dan memberikan status kegagalan yang sama seperti untuk git stash apply.

Tetapi jika Anda memiliki komit ketiga itu — jika ada ukomit di simpanan yang Anda terapkan — maka segalanya berubah! Tidak ada pilihan untuk berpura-pura bahwa ukomit tidak ada. 4 Git bersikeras mengekstrak semua file dari yang ukomit, ke dalam arus kerja-pohon. Ini berarti file harus tidak ada sama sekali, atau memiliki konten yang sama seperti di ukomit.

Untuk mewujudkannya, Anda dapat menggunakan git cleandiri Anda sendiri — tetapi ingat bahwa file yang tidak terlacak (diabaikan atau tidak) tidak memiliki keberadaan lain di dalam repositori Git, jadi pastikan semua file ini dapat dihancurkan! Atau, Anda dapat membuat direktori sementara, dan memindahkan file ke sana untuk diamankan — atau bahkan melakukan yang lain git stash save -uatau git stash save -a, karena itu akan berjalan git cleanuntuk Anda. Tapi itu hanya membuat Anda memiliki usimpanan gaya lain untuk ditangani nanti.


1 Ini sebenarnya refs/stash. Ini penting jika Anda membuat cabang dengan nama stash: nama lengkap cabang adalah refs/heads/stash, jadi tidak ada konflik. Tapi jangan lakukan itu: Git tidak keberatan, tapi Anda akan membingungkan diri sendiri. :-)

2 The git stashkode benar-benar menggunakan git merge-recursivelangsung di sini. Ini diperlukan karena berbagai alasan, dan juga memiliki efek samping untuk memastikan Git tidak memperlakukannya sebagai gabungan saat Anda menyelesaikan konflik dan melakukan.

3 Inilah mengapa saya merekomendasikan menghindari git stash pop, demi git stash apply. Anda mendapat kesempatan untuk meninjau apa yang telah diterapkan, dan memutuskan apakah itu benar - benar diterapkan dengan benar. Jika tidak, Anda masih memiliki simpanan yang berarti dapat Anda gunakan git stash branchuntuk memulihkan semuanya dengan sempurna. Nah, dengan asumsi kurangnya ukomitmen sial itu .

4 Benar-benar harus ada: git stash apply --skip-untrackedatau sesuatu. Juga harus ada varian yang berarti letakkan semua ufile komit itu ke direktori baru , misalnya git stash apply --untracked-into <dir>, mungkin.

torek
sumber
8
Jawaban yang sangat mendetail. Terima kasih telah meluangkan waktu untuk menulis ini. Saya belajar banyak!
steinybot
Penjelasan ini layak mendapatkan lencana pahlawan. Terima kasih telah memberikan detail yang luar biasa!
Lucas Fowler
1
@seelts Sayangnya, Git terus berkembang, sehingga buku cepat ketinggalan zaman. Tapi Git harus dipahami sebagai seperangkat alat — perintah yang memanipulasi komit-penuh file, atau memanipulasi grafik komit, atau apa pun — yang Anda kumpulkan menjadi apa pun yang berguna bagi Anda , dan tidak terlalu banyak buku yang tampaknya mendekatinya sehingga cara baik.
Torek
3
Saya gagal memahami solusi untuk masalah tersebut. Apakah hanya menambahkan --index: git stash apply --index?
Danijel
1
Terima kasih @torek. Saya pertama kali melakukannya git stash save --all, lalu saya langsung melakukannya git stash apply, tetapi beberapa file hilang karena saya mengganti namanya dan kemudian membuat lagi (sebelum disimpan). Yang membantu adalah: git checkout stash@{0} -- .Saya bahkan tidak akan repot-repot git checkout stash^3 -- .karena semua tampaknya baik-baik saja sekarang. Sayang sekali saya tidak punya waktu untuk benar-benar memahami apa yang sedang terjadi. Terima kasih.
Danijel
100

Saya berhasil membuat ulang masalah Anda. Tampaknya jika Anda menyimpan file yang tidak terlacak lalu Anda membuat file tersebut (dalam contoh Anda, foo.txtdan bar.txt), maka Anda memiliki perubahan lokal pada file yang tidak terlacak yang akan ditimpa saat Anda melamar git stash pop.

Untuk mengatasi masalah ini, Anda dapat menggunakan perintah berikut. Ini akan menimpa semua perubahan lokal yang belum disimpan jadi hati-hati.

git checkout stash -- .

Berikut adalah beberapa informasi lebih lanjut yang saya temukan pada perintah sebelumnya .

Daniel Smith
sumber
Pohon kerja saya pasti bersih meskipun mungkin ada perubahan pada file yang diabaikan.
steinybot
1
Sepertinya menggunakan --all/ -a akan menyertakan file yang diabaikan , jadi mungkin relevan.
Daniel Smith
1
Saya akan berasumsi bahwa logika yang sama berlaku untuk file yang diabaikan dan menandai ini sebagai jawabannya (meskipun saya pikir git merge --squash --strategy-option=theirs stashpendekatannya lebih baik dalam kasus ini).
steinybot
Setuju, saya suka pendekatan kedua itu! Semoga berhasil dengan pekerjaan Anda!
Daniel Smith
Ini membantu tetapi tidak mengembalikan file yang tidak terlacak - jika Anda memiliki masalah yang sama ( already exists, no checkout), periksa jawaban saya di bawah.
Erik Koopmans
25

Untuk memperluas jawaban Daniel Smith : kode itu hanya memulihkan file yang dilacak , meskipun Anda menggunakan --include-untracked(atau -u) saat membuat simpanan. Kode lengkap yang dibutuhkan adalah:

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

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

Ini sepenuhnya memulihkan konten yang dilacak (dalam stash) dan konten yang tidak terlacak (dalam stash^3), lalu menghapus simpanan. Beberapa catatan:

  • Hati-hati - ini akan menimpa semuanya dengan konten simpanan Anda!
  • Mengembalikan file dengan git checkoutmenyebabkan semuanya menjadi bertahap secara otomatis, jadi saya telah menambahkan git resetke semua unstage.
  • Beberapa sumber daya menggunakan stash@{0}dan stash@{0}^3, dalam pengujian saya, cara kerjanya sama dengan atau tanpa@{0}

Sumber:

Erik Koopmans
sumber
1
Mengapa Anda menghapus simpanan, mengapa tidak membiarkannya di sana sebentar demi alasan keamanan?
Danijel
@Danijel Tentu Anda bisa menyimpan simpanan - tergantung pada kasus penggunaan Anda, saya kira. Untuk tujuan saya, saya selesai dengan simpanan setelah dipulihkan.
Erik Koopmans
3

Selain jawaban lain, saya melakukan sedikit trik

  • Menghapus semua file baru ( file yang sudah ada, misalnya foo.txt dan bar.txt dalam pertanyaan)
  • git stash apply (dapat menggunakan perintah apa saja, misalnya terapkan, pop, dll.)
Sanjay Nishad
sumber
Tidak berfungsi .. File yang Dihapus hanya muncul lagi di simpanan berlaku .. dan begitu juga kesalahannya!
Aditya Rewari