Tại sao git stash pop nói rằng nó không thể khôi phục các tệp chưa được theo dõi từ mục nhập stash?


94

Tôi đã có một loạt các thay đổi theo giai đoạn và chưa theo giai đoạn và tôi muốn nhanh chóng chuyển sang một chi nhánh khác rồi chuyển lại.

Vì vậy, tôi đã tổ chức các thay đổi của mình bằng cách sử dụng:

$ git stash push -a

(Trong nhận thức muộn màng, tôi có lẽ đã có thể sử dụng --include-untrackedthay vì --all)

Sau đó, khi tôi mở kho lưu trữ, tôi nhận được rất nhiều lỗi dọc theo các dòng:

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

Dường như không có bất kỳ thay đổi nào được khôi phục từ kho lưu trữ.

Tôi cũng đã thử $ git stash branch tempnhưng điều đó cho thấy các lỗi tương tự.

Tôi đã tìm ra một cách để giải quyết vấn đề này là sử dụng:

$ git stash show -p | git apply

Thiên tai đã được ngăn chặn ngay bây giờ nhưng điều này đặt ra một số câu hỏi.

Tại sao lỗi này xảy ra ngay từ đầu và làm cách nào để tránh nó trong lần sau?


29
Tôi đã phải sử dụng:git stash show -p | git apply --3
xmedeko

2
Điều duy nhất hiệu quả với tôi là BÌNH LUẬN TRÊN !!
Mehraj Malik

4
Cảm ơn @xmedeko, có ai có thể cho biết sự khác biệt giữa git stash show -p | git apply và git stash show -p | git áp dụng --3?
Deepak Mohandas

3
Nếu bạn lo lắng, chỉ cần liệt kê các file của bạn giấu với git stash showvà hơn file cứu hộ từng người một: $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt. Thao tác này sẽ lấy tệp từ kho lưu trữ và lưu nó dưới một tên khác. Bây giờ bạn có thể an toàn để thử nghiệm các phương pháp cứu hộ thích hợp (xem câu trả lời bên dưới). Nếu mọi thứ đi chệch hướng, bạn luôn có các tệp được giải cứu như một nguồn tài nguyên cuối cùng.
Danijel

Chà, cảm ơn @xmedeko! Một lần nữa nhận xét của bạn là thứ duy nhất hoạt động, và nó rất đơn giản. +1!
pcdev

Câu trả lời:


99

Như một chút giải thích bổ sung, hãy lưu ý rằng git stashthực hiện hai cam kết hoặc ba cam kết. Mặc định là hai; bạn nhận được ba nếu bạn sử dụng bất kỳ chính tả nào của --allhoặc --include-untrackedcác tùy chọn.

Hai hoặc ba cam kết này đặc biệt theo một cách quan trọng: chúng không có chi nhánh. Git định vị chúng thông qua tên đặc biệt stash. 1 Tuy nhiên, điều quan trọng nhất là Git cho phép bạn — và khiến bạn — thực hiện với hai hoặc ba cam kết này. Để hiểu điều này, chúng ta cần xem xét những gì trong những cam kết đó.

Có gì bên trong kho

Mỗi cam kết có thể liệt kê một hoặc nhiều cam kết mẹ . Chúng tạo thành một biểu đồ, nơi các cam kết sau đó sẽ trỏ lại các biểu đồ trước đó. Kho lưu trữ thường chứa hai cam kết, mà tôi muốn gọi icho nội dung chỉ mục / khu vực dàn và wcho nội dung cây công việc. Ngoài ra, hãy nhớ rằng mỗi cam kết giữ một ảnh chụp nhanh. Trong một cam kết bình thường, ảnh chụp nhanh này được tạo từ nội dung chỉ mục / khu vực dàn dựng. Vì vậy, icommit trên thực tế là một commit hoàn toàn bình thường! Nó chỉ không nằm trên bất kỳ chi nhánh nào:

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

Nếu bạn đang thực hiện một kho lưu trữ thông thường, git stashmã tạo ra wngay bây giờ bằng cách sao chép tất cả các tệp cây công việc được theo dõi của bạn (vào một chỉ mục bổ trợ tạm thời). Git đặt cha mẹ đầu tiên của wcam kết này để trỏ đến HEADcam kết và cha mẹ thứ hai để trỏ đến cam kết i. Cuối cùng, nó thiết lập stashđể trỏ đến wcam kết này :

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

Nếu bạn thêm --include-untrackedhoặc --all, Git sẽ thực hiện thêm một cam kết, ugiữa việc thực hiện iw. Nội dung ảnh chụp nhanh ulà những tệp chưa được theo dõi nhưng không bị bỏ qua ( --include-untracked) hoặc các tệp chưa được theo dõi ngay cả khi chúng bị bỏ qua ( --all). Cam ukết bổ sung này không có cha mẹ và sau đó khi được git stashtạo w, nó đặt cha mẹ thứ baw của cam kết này , để bạn nhận được:u

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

Tại thời điểm này, Git cũng xóa mọi tệp cây công việc nằm trong ucam kết (sử dụng git cleanđể làm điều đó).

Khôi phục kho lưu trữ

Khi bạn khôi phục kho lưu trữ, bạn có tùy chọn sử dụng --indexhoặc không sử dụng. Điều này cho git stash apply(hoặc bất kỳ các lệnh nội bộ sử dụng apply, chẳng hạn như pop) rằng nó nên sử dụng các icam kết cố gắng để thay đổi chỉ số hiện tại của bạn. Việc sửa đổi này được thực hiện với:

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

(dù ít hay nhiều; có rất nhiều chi tiết tiêu cực cản trở ý tưởng cơ bản ở đây).

Nếu bạn bỏ qua --index, git stash applyhoàn toàn bỏ qua icam kết.

Nếu kho chỉ có hai cam kết, git stash applybây giờ có thể áp dụng wcam kết. Nó thực hiện điều này bằng cách gọi git merge2 (mà không cho phép nó cam kết hoặc coi kết quả như một hợp nhất thông thường), sử dụng cam kết ban đầu mà kho lưu trữ được tạo ( icha mẹ và wcha mẹ đầu tiên của nó) làm cơ sở hợp nhất, wnhư --theirscam kết và cam kết hiện tại (HEAD) của bạn là mục tiêu của hợp nhất. Nếu hợp nhất thành công, tất cả đều tốt - tốt, ít nhất Git cũng nghĩ như vậy - và git stash applybản thân nó thành công. Nếu bạn đã từng git stash popáp dụng kho lưu trữ, thì mã hiện tại sẽ giảm mức lưu trữ. 3 Nếu hợp nhất không thành công, Git tuyên bố áp dụng không thành công. Nếu bạn đã sử dụnggit stash pop, mã vẫn lưu trữ và cung cấp tình trạng lỗi tương tự như đối với git stash apply.

Nhưng nếu bạn có cam kết thứ ba — nếu có một ucam kết trong kho mà bạn đang áp dụng — thì mọi thứ sẽ thay đổi! Không có tùy chọn nào để giả vờ rằng ucam kết không tồn tại. 4 Git nhấn mạnh vào việc giải nén tất cả các tệp từu cam kết đó vào cây công việc hiện tại. Điều này có nghĩa là các tệp không được tồn tại hoặc có cùng nội dung như trong bản ucam kết.

Để điều đó xảy ra, bạn có thể sử dụng git cleanchính mình — nhưng hãy nhớ rằng các tệp không được theo dõi (bị bỏ qua hoặc không) không có sự tồn tại nào khác bên trong kho lưu trữ Git, vì vậy hãy đảm bảo rằng tất cả các tệp này đều có thể bị hủy! Hoặc, bạn có thể tạo một thư mục tạm thời và di chuyển các tệp đến đó để lưu giữ an toàn — hoặc thậm chí làm một thư mục khác git stash save -uhoặc git stash save -avì những tệp đó sẽ chạy git cleancho bạn. Nhưng điều đó chỉ để lại cho bạn một ukho lưu trữ kiểu khác để giải quyết sau này.


1 Đây là trên thực tế refs/stash. Điều này quan trọng nếu bạn đặt tên cho chi nhánh stash: tên đầy đủ của chi nhánh là refs/heads/stash, vì vậy chúng không mâu thuẫn. Nhưng đừng làm vậy: Git sẽ không bận tâm, nhưng bạn sẽ tự nhầm lẫn. :-)

2 Các git stashmã thực sự sử dụng git merge-recursivetrực tiếp tại đây. Điều này là cần thiết vì nhiều lý do và cũng có tác dụng phụ là đảm bảo Git không coi nó như một hợp nhất khi bạn giải quyết xung đột và cam kết.

3 Đây là lý do tại sao tôi khuyên bạn nên tránh git stash pop, ủng hộ git stash apply. Bạn có cơ hội xem lại những gì đã được áp dụng và quyết định xem nó có thực sự được áp dụng đúng hay không. Nếu không, bạn vẫn có kho của mình , nghĩa là bạn có thể sử dụng git stash branchđể khôi phục mọi thứ một cách hoàn hảo. Vâng, giả sử thiếu ucam kết khó chịu đó .

4 Thực sự nên có: git stash apply --skip-untrackedhoặc một cái gì đó. Cũng nên có một biến thể có nghĩa là thả tất cả các utệp cam kết đó vào một thư mục mới , ví dụ git stash apply --untracked-into <dir>, có lẽ.


8
Câu trả lời chi tiết đáng kinh ngạc. Cảm ơn bạn đã dành thời gian để viết điều này. Tôi đã học được rất nhiều!
steinybot

Lời giải thích này xứng đáng được huy hiệu anh hùng. Cảm ơn vì đã đưa chi tiết tuyệt vời như vậy vào!
Lucas Fowler

1
@seelts Thật không may, Git liên tục phát triển, vì vậy sách nhanh chóng lỗi thời. Nhưng Git nên được hiểu là một tập hợp các công cụ — các lệnh điều khiển một tập tin cam kết hoặc thao tác biểu đồ cam kết, hoặc bất cứ thứ gì — mà bạn tập hợp thành bất kỳ thứ gì hữu ích cho mình và dường như không có quá nhiều sách tiếp cận nó một trong hai cách.
torek

3
Tôi không hiểu giải pháp cho vấn đề. Là nó chỉ thêm --index: git stash apply --index?
Danijel

1
Cảm ơn @torek. Lần đầu tiên tôi làm git stash save --all, sau đó tôi ngay lập tức làm được git stash apply, nhưng một số tệp bị thiếu vì tôi đã đổi tên chúng và sau đó tạo lại (trước khi lưu trữ). Điều hữu ích là: git checkout stash@{0} -- .Tôi thậm chí sẽ không bận tâm git checkout stash^3 -- .vì tất cả dường như đã ổn. Tôi không có thời gian để thực sự hiểu chuyện gì đang xảy ra. Cảm ơn.
Danijel

101

Tôi đã quản lý để tạo lại vấn đề của bạn. Có vẻ như nếu bạn lưu trữ các tệp chưa được kiểm soát và sau đó bạn tạo các tệp đó (trong ví dụ của bạn foo.txtbar.txt), thì bạn có các thay đổi cục bộ đối với các tệp chưa được kiểm soát sẽ bị ghi đè khi bạn áp dụng git stash pop.

Để khắc phục sự cố này, bạn có thể sử dụng lệnh sau. Điều này sẽ ghi đè mọi thay đổi cục bộ chưa được lưu, vì vậy hãy cẩn thận.

git checkout stash -- .

Đây là một số thông tin khác mà tôi tìm thấy trong lệnh trước .


Cây làm việc của tôi chắc chắn đã sạch mặc dù có thể có những thay đổi đối với các tệp bị bỏ qua.
steinybot

1
Có vẻ như việc sử dụng --all/ -a sẽ bao gồm các tệp bị bỏ qua , vì vậy có thể có liên quan.
Daniel Smith

1
Tôi sẽ giả định rằng logic tương tự áp dụng cho các tệp bị bỏ qua và đánh dấu đây là câu trả lời (mặc dù tôi nghĩ git merge --squash --strategy-option=theirs stashcách tiếp cận tốt hơn trong trường hợp này).
steinybot

Đồng ý, tôi thích cách tiếp cận thứ hai! Chúc may mắn với công việc của bạn!
Daniel Smith

Điều này rất hữu ích nhưng nó không khôi phục các tệp chưa được theo dõi - nếu bạn gặp vấn đề tương tự ( already exists, no checkout), hãy kiểm tra câu trả lời của tôi bên dưới.
Erik Koopmans

25

Để mở rộng câu trả lời của Daniel Smith : mã đó chỉ khôi phục các tệp được theo dõi , ngay cả khi bạn đã sử dụng --include-untracked(hoặc -u) khi tạo kho lưu trữ. Mã đầy đủ được yêu cầu là:

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

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

Thao tác này khôi phục hoàn toàn nội dung được theo dõi (trong stash) và nội dung không được theo dõi (trong stash^3), sau đó xóa kho lưu trữ. Một số lưu ý:

  • Hãy cẩn thận - điều này sẽ ghi đè mọi thứ với nội dung lưu trữ của bạn!
  • Khôi phục các tệp với git checkoutnguyên nhân khiến tất cả chúng tự động trở thành phân đoạn, vì vậy tôi đã thêm vào git resetđể loại bỏ mọi thứ.
  • Một số tài nguyên sử dụng stash@{0}stash@{0}^3, trong thử nghiệm của tôi, nó hoạt động giống nhau có hoặc không có@{0}

Nguồn:


1
Tại sao bạn lại xóa kho, tại sao không để nó ở đó một thời gian vì lý do an toàn?
Danijel

@Danijel Chắc chắn bạn có thể giữ lại kho - tôi đoán tùy thuộc vào trường hợp sử dụng của bạn. Đối với mục đích của tôi, tôi đã hoàn tất việc lưu trữ sau khi nó được khôi phục.
Erik Koopmans

3

ngoài các câu trả lời khác, tôi đã thực hiện một mẹo nhỏ

  • Đã xóa tất cả các tệp mới ( các tệp đã tồn tại, ví dụ: foo.txt và bar.txt trong câu hỏi)
  • git stash apply (có thể sử dụng bất kỳ lệnh nào, ví dụ: áp dụng, bật, v.v.)

Không hoạt động .. các Tệp đã Xóa chỉ xuất hiện lại tại kho áp dụng .. và lỗi cũng vậy!
Aditya Rewari
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.