Hoàn tác đặt lại git - cứng với các tệp chưa được cam kết trong khu vực dàn dựng


109

Tôi đang cố gắng phục hồi công việc của mình. Tôi đã làm một cách ngu ngốc git reset --hard, nhưng trước đó tôi chỉ làm get add .và không làm git commit. Xin vui lòng giúp đỡ! Đây là nhật ký của tôi:

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

Có thể hoàn tác git reset --hardtrong tình huống này không?


@MarkLongair người đàn ông tuyệt vời! Bạn vừa nhận được công việc của tôi trở lại! Tôi đã viết một tập lệnh Python để tạo các tệp của tất cả đầu ra! Tôi sẽ thêm kịch bản làm câu trả lời
Cậu bé

4
Không phải 'ngu ngốc' .. mà là 'ngây thơ' ... bởi vì tôi vừa làm CÙNG!
Rosdi Kasim

Vẫn có thể là ngớ ngẩn ;-)
Duncan McGregor

Đây là một bài viết tuyệt vời về cách đảo ngược một số điều này. Nó sẽ mất một số công việc thủ công.
Jan Swart

@MarkLongair `` tìm .git / objects / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | phân loại `` làm việc cho tôi. ngày cũng xuất hiện, bắt đầu kiểm tra các đốm màu từ cuối.
ZAky

Câu trả lời:


175

Bạn sẽ có thể khôi phục bất kỳ tệp nào trở lại mà bạn đã thêm vào chỉ mục (ví dụ: như trong trường hợp của bạn, với git add .) mặc dù có thể hơi vất vả. Để thêm tệp vào chỉ mục, git sẽ thêm tệp đó vào cơ sở dữ liệu đối tượng, có nghĩa là tệp có thể được phục hồi miễn là việc thu gom rác chưa diễn ra. Có một ví dụ về cách làm điều này được đưa ra trong câu trả lời của Jakub Narębski ở đây:

Tuy nhiên, tôi đã thử điều đó trên một kho lưu trữ thử nghiệm và có một vài vấn đề - --cachednên xảy ra --cachevà tôi thấy rằng nó không thực sự tạo ra .git/lost-foundthư mục. Tuy nhiên, các bước sau đã hiệu quả với tôi:

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

Điều đó sẽ xuất ra tất cả các đối tượng trong cơ sở dữ liệu đối tượng mà bất kỳ ref nào không thể truy cập được, trong chỉ mục hoặc thông qua reflog. Đầu ra sẽ giống như sau:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... và đối với mỗi đốm màu đó, bạn có thể làm:

git show 907b308

Để xuất nội dung của tệp.


Quá nhiều sản lượng?

Cập nhật phản hồi cho bình luận của sehe bên dưới:

Nếu bạn thấy rằng bạn có nhiều cam kết và cây được liệt kê trong đầu ra từ lệnh đó, bạn có thể muốn xóa khỏi đầu ra bất kỳ đối tượng nào được tham chiếu từ các cam kết không được tham chiếu. (Thông thường, bạn vẫn có thể quay lại các cam kết này thông qua reflog - chúng tôi chỉ quan tâm đến các đối tượng đã được thêm vào chỉ mục nhưng không bao giờ có thể được tìm thấy thông qua cam kết.)

Đầu tiên, lưu đầu ra của lệnh, với:

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

Bây giờ bạn có thể tìm thấy tên đối tượng của những cam kết không thể truy cập đó bằng:

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

Vì vậy, bạn chỉ có thể tìm thấy các cây và đối tượng đã được thêm vào chỉ mục, nhưng không được cam kết tại bất kỳ điểm nào, với:

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

Điều đó làm giảm đáng kể số lượng đối tượng mà bạn sẽ phải xem xét.


Cập nhật: Philip Oakley dưới đây đề xuất một cách khác để cắt giảm số lượng đối tượng cần xem xét, đó là chỉ xem xét các tệp được sửa đổi gần đây nhất .git/objects. Bạn có thể tìm thấy những thứ này với:

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

(Tôi tìm thấy findlời gọi đó ở đây .) Phần cuối của danh sách đó có thể giống như sau:

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

Trong trường hợp đó, bạn có thể xem các đối tượng đó với:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Lưu ý rằng bạn phải loại bỏ /phần cuối của đường dẫn để lấy tên đối tượng.)


trước khi tôi đăng câu trả lời của mình, tôi đã xem xét bao gồm chính xác các bước này. Tuy nhiên, khi thử điều này với một trong những đại diện cục bộ của tôi, tôi đã nhận được hàng trăm lần không thể kết nối và tôi không thể đưa ra cách tiếp cận phù hợp để sắp xếp chúng theo ngày gửi. Bây giờ sẽ là tuyệt vời
sehe

3
Không thể xem tem thời gian đối tượng cho các đối tượng gần đây nhất muộn hơn cam kết cuối cùng của bạn? hoặc tôi đang thiếu một cái gì đó.
Philip Oakley

1
@Philip Oakley: cảm ơn vì đề xuất đó, tôi đã thêm đề xuất để tìm các đối tượng được sửa đổi gần đây nhất trong cơ sở dữ liệu đối tượng.
Mark Longair

2
Anh bạn, điều đó thật tuyệt vời! Điều đó đã tiết kiệm @ $$ của tôi vừa rồi. Và tôi đã học được một số khoa học git. Better xem những gì bạn thêm vào chỉ số ...
bowsersenior

1
không ăn cắp đèn vôi ở đây. Nhưng chỉ là một ghi chú vì tôi vừa gặp phải vấn đề tương tự và nghĩ rằng tôi đã mất tất cả công việc của mình. Đối với những người sử dụng IDE , IDE thường có giải pháp khôi phục một cú nhấp chuột. Trong trường hợp PHPstorm, tôi chỉ cần nhấp chuột phải vào thư mục và chọn hiển thị lịch sử cục bộ và sau đó hoàn nguyên về trạng thái hợp lệ cuối cùng.
Shalom Sam

58

Tôi chỉ làm một git reset --hardvà mất một cam kết. Nhưng tôi biết băm cam kết, vì vậy tôi có thể làm git cherry-pick COMMIT_HASHđể khôi phục nó.

Tôi đã làm điều này trong vòng vài phút sau khi mất cam kết, vì vậy nó có thể hiệu quả với một số bạn.


5
Cảm ơn bạn cảm ơn bạn cảm ơn bạn Managed để khôi phục một ngày làm việc nhờ câu trả lời này
Dace

21
Có lẽ là đáng nói, bạn có thể nhìn thấy những băm sử dụng git reflog, ví dụ git reset --hard-> git reflog(nhìn vào các HEAD @ {1} băm) và cuối cùnggit cherry-pick COMMIT_HASH
Javier López

2
Các bạn đang đọc cái này, hãy cẩn thận rằng nó chỉ hoạt động cho một lần băm cam kết duy nhất, nếu có nhiều hơn một lần cam kết, nó sẽ không giải quyết được vấn đề cùng một lúc!
Ain Tohvri

4
Bạn thực sự có thể đặt lại "chuyển tiếp". Vì vậy, nếu bạn đã đặt lại thông qua nhiều lần cam kết, thực hiện một 'git reset --hard <commit>' khác sẽ khôi phục toàn bộ chuỗi cam kết cho đến thời điểm đó. (cho rằng họ không GC'ed chưa)
akimsko

6
Các one-liner cho việc khôi phục cam kết bị mất với git reset --hard(giả sử bạn đã không thực hiện bất cứ điều gì khác sau khi lệnh đó) làgit reset --hard @{1}
Ajedi32

13

Nhờ Mark Longair, tôi đã lấy lại được đồ của mình!

Đầu tiên, tôi đã lưu tất cả các băm vào một tệp:

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

tiếp theo, tôi đặt tất cả chúng (loại bỏ 'thứ không thể truy cập được') vào một danh sách và đặt tất cả dữ liệu vào các tệp mới ... bạn phải chọn tệp của mình và đổi tên lại chúng mà bạn cần ... nhưng tôi chỉ cần một số files..hope này sẽ giúp ai đó ...

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
Đúng! Đây chính xác là những gì tôi cần. Tôi cũng thích tập lệnh Python để tạo lại tất cả các tệp. Những câu trả lời khác khiến tôi lo lắng về việc mất dữ liệu của tôi với thu gom rác thải, do đó bán phá giá các tập tin là một chiến thắng đối với tôi :)
Eric Olson

1
Tôi đã viết kịch bản này dựa trên câu trả lời này. Tác phẩm ra khỏi hộp: github.com/pendashteh/git-recover-index
Alexar

1
Tôi tìm thấy nó dễ dàng hơn để tự động hóa nó như thế này: 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. Các tệp sẽ kết thúc saulost/*.blob
Matija Nalis,

6

Giải pháp của @ Ajedi32 trong các nhận xét phù hợp với tôi trong tình huống này.

git reset --hard @{1}

Lưu ý rằng tất cả các giải pháp này đều dựa trên việc không có git gc và một số trong số chúng có thể gây ra một lỗi, vì vậy tôi sẽ nén nội dung trong thư mục .git của bạn trước khi thử bất kỳ điều gì để bạn có một ảnh chụp nhanh để quay lại nếu không không làm việc cho bạn.


Tôi đã có một số tệp không giới hạn. Sau đó, tôi đã commit 1 tệp và đã thực hiện một tệp, điều này đã làm git reset --hardxáo trộn kho lưu trữ của tôi theo bất kỳ cách nào không xác định. @Duncan, @ {1} làm gì? và bạn đang đề cập đến bình luận nào? Nó giống như một git reset?
alpha_989

Tôi nghĩ rằng @ {1} là một tài liệu tham khảo liên quan đến người cuối cùng nhưng có một cam kết, nhưng tôi không có chuyên môn, chỉ cần báo cáo những gì làm việc cho tôi
Duncan McGregor

3

Chạy vào cùng một vấn đề, nhưng không thêm các thay đổi vào chỉ mục. Vì vậy, tất cả các lệnh trên không mang lại cho tôi những thay đổi mong muốn.

Sau tất cả các câu trả lời phức tạp ở trên, đây là một gợi ý ngây thơ, nhưng có thể nó sẽ cứu một người không nghĩ về nó trước, như tôi đã làm.

Trong tuyệt vọng, tôi đã cố gắng nhấn CTRL-Z trong trình chỉnh sửa của mình (LightTable), một lần trong mỗi tab đang mở - điều này may mắn đã khôi phục tệp trong tab đó về trạng thái mới nhất trước đó git reset --hard. HTH.


1
Chính xác là tình huống tương tự, đã bỏ lỡ thêm chỉ mục và đã thiết lập lại cứng và không có giải pháp nào hoạt động ở trên. Đây là một động thái THÔNG MINH, cảm ơn tôi đã thực hiện Ctrl + Z và tiết kiệm ngày của tôi. Biên tập viên của tôi: SublimeText. Một lên từ phía tôi!
Vivek

1

Điều này có lẽ là hiển nhiên đối với các chuyên gia git ngoài đó, nhưng tôi muốn đưa nó lên vì trong quá trình tìm kiếm điên cuồng của mình, tôi đã không thấy điều này được đưa ra.

Tôi đã dàn dựng một số tệp, và git reset --hardhơi bối rối một chút, và sau đó nhận thấy rằng trạng thái của tôi hiển thị tất cả các tệp của tôi vẫn được sắp xếp cũng như tất cả các lần xóa của chúng đã được xóa.

Tại thời điểm này, bạn có thể thực hiện các thay đổi theo giai đoạn đó, miễn là bạn không xóa chúng theo giai đoạn. Sau đó, bạn chỉ cần lấy hết can đảm để làm git reset --hardthêm một lần nữa, điều này sẽ đưa bạn trở lại những thay đổi mà bạn đã thực hiện và bây giờ vừa mới cam kết.

Một lần nữa, điều này có lẽ không có gì mới đối với hầu hết, nhưng tôi hy vọng rằng vì nó đã giúp tôi và tôi không tìm thấy bất kỳ điều gì gợi ý điều này, nó có thể giúp ích cho người khác.


1

Chúa ơi, tôi giật tóc cho đến khi tôi gặp câu hỏi này và câu trả lời của nó. Tôi tin rằng câu trả lời chính xác và ngắn gọn cho câu hỏi được hỏi chỉ có sẵn nếu bạn kéo hai trong số các nhận xét ở trên lại với nhau, vì vậy ở đây tất cả đều ở cùng một nơi:

  1. Như đã đề cập bởi chilicuil, hãy chạy git reflogđể xác định trong đó hàm băm cam kết mà bạn muốn quay lại

  2. Như đã đề cập bởi akimsko, bạn có thể KHÔNG muốn hái anh đào trừ khi bạn chỉ mất một lần cam kết, vì vậy bạn nên chạy git reset --hard <hash-commit-you-want>

Lưu ý cho người dùng egit Eclipse: Tôi không thể tìm thấy cách thực hiện các bước này trong Eclipse với egit. Đóng Eclipse, chạy các lệnh ở trên từ cửa sổ đầu cuối và sau đó mở lại Eclipse hoạt động tốt đối với tôi.


0

Các giải pháp trên có thể hiệu quả, tuy nhiên, có những cách đơn giản hơn để khôi phục điều này thay vì thực hiện githoàn tác phức tạp -s. Tôi đoán rằng hầu hết các lần đặt lại git xảy ra trên một số lượng nhỏ tệp và nếu bạn đã sử dụng VIM, đây có thể là giải pháp tiết kiệm thời gian nhất. Lưu ý là bạn đã phải sử dụng ViM'sliên tục-hoàn tác, mà bạn nên sử dụng theo cả hai cách, vì nó cung cấp cho bạn khả năng hoàn tác số lượng thay đổi không giới hạn.

Đây là các bước:

  1. Trong vim, nhấn :và gõ lệnh set undodir. Nếu bạn đã persistent undobật trong của mình .vimrc, nó sẽ hiển thị kết quả tương tự như undodir=~/.vim_runtime/temp_dirs/undodir.

  2. Sử dụng repo của bạn git logđể tìm ra ngày / giờ cuối cùng bạn thực hiện cam kết cuối cùng

  3. Trong shell của bạn, điều hướng đến việc undodirsử dụng của bạn cd ~/.vim_runtime/temp_dirs/undodir.

  4. Trong thư mục này, sử dụng lệnh này để tìm tất cả các tệp mà bạn đã thay đổi kể từ lần cam kết cuối cùng

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

    Ở đây "2018-03-20 11:24:44" là ngày và thời gian của lần cam kết cuối cùng. Nếu ngày bạn làm git reset --hardlà "2018-03-22", thì hãy sử dụng "2018-03-22", sau đó sử dụng "2018-03-23". Điều này là do một kết quả khó tìm, trong đó giới hạn dưới là bao gồm và giới hạn trên là loại trừ. https://unix.stackexchange.com/a/70404/242983

  5. Tiếp theo, chuyển đến từng tệp, mở chúng bằng vim và thực hiện "sớm hơn 20 m". Bạn có thể tìm thêm chi tiết về "sớm hơn" bằng cách sử dụng "h sớm hơn". Ở đây earlier 20mcó nghĩa là quay lại trạng thái của tệp trước 20 phút, giả sử rằng bạn đã làm git hard --reset, 20 phút trở lại. Lặp lại điều này trên tất cả các tệp được xuất ra từ findlệnh. Tôi chắc rằng ai đó có thể viết một kịch bản kết hợp những điều này.


0

Tôi đang sử dụng IntelliJ và có thể chỉ cần xem qua từng tệp và thực hiện:

Edit -> reload from disk

May mắn thay, tôi vừa thực hiện một git statusquyền trước khi xóa các thay đổi hoạt động của mình, vì vậy tôi biết chính xác những gì tôi phải tải lại.


0

Ghi nhớ cấu trúc phân cấp tệp của bạn và sử dụng kỹ thuật của Mark Longair với sửa đổi Phil Oakley mang lại kết quả ấn tượng.

Về cơ bản nếu bạn ít nhất đã thêm các tệp vào repo nhưng không cam kết, bạn có thể khôi phục bằng cách sử dụng tương tác git show, kiểm tra nhật ký và sử dụng chuyển hướng shell để tạo từng tệp (ghi nhớ đường dẫn bạn quan tâm).

HTH!


0

Nếu gần đây bạn đã xem lại một git diffthì có một cách khác để khôi phục sau những sự cố như thế này, ngay cả khi bạn chưa thực hiện các thay đổi: nếu đầu ra của git diffvẫn còn trong bộ đệm bảng điều khiển của bạn, bạn có thể chỉ cần cuộn lên, sao chép và dán diff vào một tập tin và sử dụng các patchcông cụ để áp dụng các diff cho cây của bạn: patch -p0 < file. Cách tiếp cận này đã cứu tôi một vài lần.

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.