Tôi có thể xóa một cam kết git nhưng giữ các thay đổi


1057

Trong một trong những nhánh phát triển của mình, tôi đã thực hiện một số thay đổi cho cơ sở mã của mình. Trước khi tôi có thể hoàn thành các tính năng mà tôi đang làm việc, tôi đã phải chuyển chi nhánh hiện tại của mình thành chủ để giới thiệu một số tính năng. Nhưng chỉ cần sử dụng "git checkout master" đã bảo toàn những thay đổi tôi cũng đã thực hiện trong nhánh phát triển của mình, do đó phá vỡ một số chức năng trong master. Vì vậy, những gì tôi đã làm là cam kết các thay đổi trên nhánh phát triển của mình với thông báo cam kết "cam kết tạm thời" và sau đó kiểm tra tổng thể cho bản demo.

Bây giờ tôi đã hoàn thành bản demo và quay lại làm việc trong nhánh phát triển của mình, tôi muốn xóa "cam kết tạm thời" mà tôi đã thực hiện trong khi vẫn giữ nguyên các thay đổi tôi đã thực hiện. Điều đó có thể không?


65
Lần tới:git stash
Matt Ball

51
@MattBall, không nhất thiết. Mặc dù git stashlà một công cụ tốt, nhưng các công việc vứt bỏ "đang tiến hành" cũng là một thiết bị hợp pháp.
kostix

3
Đây là một eo biển tài nguyên tuyệt vời từ Github: Cách hoàn tác (gần như) mọi thứ với Git
jasonleonhard

9
@MattBall @kostix Vâng, stash đặc biệt không phù hợp với stash "dài hạn", với điều kiện đó là một ngăn xếp toàn cầu cho toàn bộ repo. Tôi muốn có thể bỏ các thay đổi trên một nhánh và sau đó đi đến các nhánh khác, làm bất cứ điều gì khác, quay lại những ngày sau đó mà không lo lắng rằng tôi có thể đã sử dụng git stashtrên một số nhánh khác trong thời gian tạm thời.
Alec

4
Một điều đáng chú ý stashlà nó hoàn toàn cục bộ và sẽ dễ bị mất mã do xóa repo hoặc giải trí hoặc, lỗi hoặc mất phần cứng. IMO, nó thực sự chỉ nên được sử dụng cho WIP rất ngắn hạn. Yêu một cam kết WIP trước khi đi nghỉ: P .. gọi đó là cam kết đổ não!
ΣοδεMεδις

Câu trả lời:


1619

Nó đơn giản như thế này:

git reset HEAD^

Lưu ý: một số shell được coi ^là một ký tự đặc biệt (ví dụ: một số shell Windows hoặc ZSH có bật hình cầu ), vì vậy bạn có thể phải trích dẫn "HEAD^"trong những trường hợp đó.

git resetkhông có --hardhoặc --softdi chuyển HEADđể trỏ đến cam kết đã chỉ định mà không thay đổi bất kỳ tệp nào. HEAD^đề cập đến cam kết cha mẹ (đầu tiên) của cam kết hiện tại của bạn, trong trường hợp của bạn là cam kết trước khi cam kết tạm thời.

Lưu ý rằng một tùy chọn khác là tiếp tục như bình thường, và sau đó tại điểm cam kết tiếp theo thay vì chạy:

git commit --amend [-m … etc]

thay vào đó sẽ chỉnh sửa các cam kết gần đây nhất, có tác dụng tương tự như trên.

Lưu ý rằng điều này (như với gần như mọi câu trả lời git) có thể gây ra vấn đề nếu bạn đã đẩy cam kết xấu đến một nơi mà người khác có thể đã rút nó từ đó. Cố gắng tránh điều đó


47
Điều này là đơn giản nhất, nhưng nếu bạn đã đẩy cam kết của mình đến một điều khiển từ xa và người khác đã kéo nó, tôi sẽ rất do dự để làm bất cứ điều gì ngoại trừ xin lỗi.
atw13

11
@sicophrenic, đừng bỏ lỡ cơ hội để đọc "Đặt lại phân tách" trong dịp này.
kostix

33
Tôi nhận được More?sau khi làm điều này. Bất cứ điều gì tôi gõ vào dấu nhắc đó đều mang lại cho tôifatal: ambiguous argument 'HEADwhateverItypedIn': unknown revision or path not in the working tree.
DaAwgieP

50
@DaAw đũaP nghe có vẻ như bạn đang sử dụng một cái vỏ được coi ^là một ký tự đặc biệt. Bạn có thể trích dẫn tài liệu tham khảo "HEAD^"hoặc sử dụng cú pháp thay thế không HEAD~1được trích dẫn
Gareth

8
Làm việc cho tôi, đã phải thoát khỏi nhân vậtgit reset HEAD\^
cevaris

188

Có hai cách để xử lý việc này. Cái nào dễ hơn phụ thuộc vào hoàn cảnh của bạn

Cài lại

Nếu cam kết bạn muốn loại bỏ là cam kết cuối cùng và bạn chưa thực hiện bất kỳ công việc bổ sung nào, bạn chỉ có thể sử dụng git-reset

git reset HEAD^

Đưa chi nhánh của bạn trở lại cam kết ngay trước TRƯỚC hiện tại của bạn. Tuy nhiên, nó không thực sự thay đổi các tệp trong cây làm việc của bạn. Kết quả là, những thay đổi trong cam kết đó hiển thị dưới dạng đã sửa đổi - nó giống như một lệnh 'không phổ biến'. Trong thực tế, tôi có một bí danh để làm điều đó.

git config --global alias.uncommit 'reset HEAD^'

Sau đó, bạn chỉ có thể sử dụng git uncommittrong tương lai để sao lưu một cam kết.

Bóp

Bóp một cam kết có nghĩa là kết hợp hai hoặc nhiều cam kết thành một. Tôi làm điều này khá thường xuyên. Trong trường hợp của bạn, bạn đã hoàn thành một nửa tính năng, và sau đó bạn sẽ hoàn thành nó và cam kết lại với thông điệp cam kết phù hợp, vĩnh viễn.

git rebase -i <ref>

Tôi nói ở trên bởi vì tôi muốn làm rõ rằng đây có thể là bất kỳ số lượng cam kết nào. Chạy git logvà tìm cam kết mà bạn muốn loại bỏ, sao chép SHA1 của nó và sử dụng nó thay thế <ref>. Git sẽ đưa bạn vào chế độ rebase tương tác. Nó sẽ hiển thị tất cả các cam kết giữa trạng thái hiện tại của bạn và bất cứ điều gì bạn đặt vào vị trí <ref>. Vì vậy, nếu <ref>là 10 lần xác nhận trước, nó sẽ hiển thị cho bạn tất cả 10 lần xác nhận.

Trước mỗi cam kết, nó sẽ có từ pick. Tìm cam kết bạn muốn thoát khỏi và thay đổi nó từ pickthành fixuphoặc squash. Sử dụng fixupchỉ cần loại bỏ cam kết thông báo và hợp nhất các thay đổi vào tiền thân trực tiếp của nó trong danh sách. Các squashtừ khóa làm điều tương tự, nhưng cho phép bạn chỉnh sửa các cam kết thông báo của mới kết hợp cam kết.

Lưu ý rằng các cam kết sẽ được cam kết lại theo thứ tự chúng hiển thị trên danh sách khi bạn thoát khỏi trình chỉnh sửa. Vì vậy, nếu bạn đã thực hiện một cam kết tạm thời, sau đó thực hiện các công việc khác trên cùng một nhánh và hoàn thành tính năng trong một cam kết sau đó, thì việc sử dụng rebase sẽ cho phép bạn sắp xếp lại các cam kết và xóa chúng.

CẢNH BÁO:

Rebasing sửa đổi lịch sử - KHÔNG làm điều này với bất kỳ cam kết nào bạn đã chia sẻ với các nhà phát triển khác.

Đâm

Trong tương lai, để tránh vấn đề này, hãy cân nhắc sử dụng git stashđể tạm thời lưu trữ công việc không được cam kết.

git stash save 'some message'

Điều này sẽ lưu trữ các thay đổi hiện tại của bạn sang một bên trong danh sách stash của bạn. Trên đây là phiên bản rõ ràng nhất của lệnh stash, cho phép nhận xét để mô tả những gì bạn đang lưu trữ. Bạn cũng có thể chỉ cần chạy git stashvà không có gì khác, nhưng sẽ không có tin nhắn nào được lưu trữ.

Bạn có thể duyệt danh sách stash của bạn với ...

git stash list

Điều này sẽ cho bạn thấy tất cả các stash của bạn, chúng đã được thực hiện trên các nhánh nào, và thông báo và ở đầu mỗi dòng, và định danh cho stash đó trông giống như thế này trong stash@{#}đó # là vị trí của nó trong mảng stash .

Để khôi phục stash (có thể được thực hiện trên bất kỳ nhánh nào, bất kể nơi nào stash ban đầu được tạo), bạn chỉ cần chạy ...

git stash apply stash@{#}

Một lần nữa, có # là vị trí trong mảng stash. Nếu stash bạn muốn khôi phục nằm ở 0vị trí - nghĩa là, nếu đó là stash gần đây nhất. Sau đó, bạn chỉ có thể chạy lệnh mà không chỉ định vị trí stash, git sẽ cho rằng bạn có nghĩa là người cuối cùng : git stash apply.

Vì vậy, ví dụ, nếu tôi thấy mình làm việc sai nhánh - tôi có thể chạy chuỗi lệnh sau.

git stash
git checkout <correct_branch>
git stash apply

Trong trường hợp của bạn, bạn di chuyển xung quanh các chi nhánh nhiều hơn một chút, nhưng ý tưởng tương tự vẫn được áp dụng.

Hi vọng điêu nay co ich.


6
git config --global alias.uncommit reset HEAD^chỉ bí danh không phổ biến để thiết lập lại. Thay vào đó, hãy làmgit config --global alias.uncommit 'reset HEAD^'
mernst

3
Lưu ý rằng bạn sẽ phải sử dụng ^^ thay vì ^ nếu sử dụng trong dấu nhắc lệnh của windows.
Pramod BR

106

Tôi nghĩ rằng bạn đang tìm kiếm này

git reset --soft HEAD~1

Nó hoàn tác các cam kết gần đây nhất trong khi vẫn giữ các thay đổi được thực hiện trong cam kết đó để dàn dựng.


7
Cảm ơn. Điều này làm việc cho tôi. Gọi git reset HEAD^trên Windows chỉ nhắc "Thêm?" - bất cứ điều gì có nghĩa là
Tyron

12
@Tyron ^là một nhân vật thoát trong DOS. Khi được ghép nối với một dòng mới, nó được dùng như một dấu nhắc tiếp tục cho lệnh trước đó. Gõ git reset HEAD^^nên hoạt động trên Windows.
trk

40

Có, bạn có thể xóa cam kết của mình mà không xóa các thay đổi: git reset @ ~


7
Đây thực sự là những gì tôi muốn, và nó sẽ là câu trả lời được chấp nhận theo ý kiến ​​của tôi, cảm ơn rất nhiều!
Carlos Liu

2
Thú vị, cú pháp nhỏ gọn, tôi chưa thấy người sử dụng nó trước đây. Làm thế nào là khác nhau từ git reset --softhoặc git reset --keep?
user776686

18

Bạn đang tìm kiếm một trong hai git reset HEAD^ --softhoặc git reset HEAD^ --mixed.

Có 3 chế độ cho lệnh đặt lại như đã nêu trong tài liệu :

git reset HEAD^ --soft

khôi phục trở lại git commit. Thay đổi vẫn tồn tại trong cây làm việc (thư mục dự án) + chỉ mục (--cached)

git reset HEAD^ --mixed

hoàn tác git commit+ git add. Thay đổi vẫn tồn tại trong cây làm việc

git reset HEAD^ --hard

Giống như bạn chưa bao giờ thực hiện những thay đổi này cho codebase. Những thay đổi đã biến mất khỏi cây làm việc.


9

Đối với những người sử dụng zsh, bạn sẽ phải sử dụng như sau:

git reset --soft HEAD\^

Giải thích tại đây: https://github.com/robbyrussell/oh-my-zsh/issues/449

Trong trường hợp URL bị chết, phần quan trọng là:

Thoát khỏi ^ trong lệnh của bạn

Ngoài ra, bạn có thể sử dụng CHÍNH ~ để bạn không phải thoát nó mỗi lần.


15
Tôi không bao giờ có thể nhớ lệnh này và phải google nó và tìm câu trả lời của riêng mình hahahaha
Greg Hilston

2
git reset HEAD^đang làm việc trong zsh cho tôi, có thể đã được sửa chữa.
Ben Kolya Mansley

@BenKolyaMansley Phiên bản zsh nào bạn đang chạy?
Greg Hilston

Tôi đang sử dụngzsh 5.3 (x86_64-apple-darwin18.0)
Ben Kolya Mansley

7

Trong trường hợp của tôi, tôi đã được đẩy đến repo. Ôi!

Bạn có thể hoàn nguyên một cam kết cụ thể trong khi vẫn giữ các thay đổi trong tệp cục bộ của mình bằng cách thực hiện:

git revert -n <sha>

Bằng cách này, tôi có thể giữ những thay đổi mà tôi cần và hủy bỏ một cam kết đã được thúc đẩy.


Trong trường hợp của tôi, tôi cần hoàn nguyên một cái gì đó tôi đã đẩy vào kho lưu trữ từ xa. Thậm chí nhiều hơn! Cách tốt nhất là git revert bad-commit-sha, sau đó git revert -n revert-commit-just-created-shavà sau đó sửa chữa từ đó. Bạn đã cho tôi nửa chừng. Cảm ơn!
TinkerTenorSoftwareGuy

Điều này dường như làm ngược lại, không? Nó tạo ra những thay đổi mới tương ứng với sự hoàn nguyên của những thay đổi được thực hiện trong cam kết đã chọn. Nếu bạn đã cam kết những thay đổi mới này, bạn sẽ hoàn tác công việc bạn muốn thực sự giữ.
svaens

3

Sử dụng git reset HEAD^lời nhắc git 2.9 (chính xác là 2.9.2.windows.1) để biết thêm; không chắc chắn những gì được mong đợi đầu vào ở đây. Vui lòng tham khảo ảnh chụp màn hình bên dưới

nhập mô tả hình ảnh ở đây

Tìm giải pháp khác git reset HEAD~#numberOfCommits bằng cách sử dụng mà chúng tôi có thể chọn để chọn số lượng xác nhận cục bộ bạn muốn đặt lại bằng cách giữ nguyên các thay đổi của bạn. Do đó, chúng tôi có cơ hội để vứt bỏ tất cả các cam kết địa phương cũng như số lượng cam kết địa phương hạn chế.

Tham khảo ảnh chụp màn hình bên dưới hiển thị git reset HEAD~1trong hành động: nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


Bạn có thể cần phải thoát khỏi ký tự ^ - hãy thử git reset "HEAD ^" hoặc git reset HEAD \ ^
Mark Fisher

Thêm vào đó, một git reset ĐẦU ^^ hoạt động như một ^ được coi là một dòng mới.
John Oss

2

Một cách nữa để làm điều đó.

Thêm cam kết vào đầu cam kết tạm thời và sau đó thực hiện:

git rebase -i

Để hợp nhất hai cam kết thành một (lệnh sẽ mở tệp văn bản với các hướng dẫn rõ ràng, chỉnh sửa nó).


2
Kỹ thuật chính xác nhưng không nơi nào thanh lịch như chỉ đơn giản là làm git reset HEAD^. Git rebase có rất nhiều chỗ cho lỗi ở đây.
TheBuzzSaw

0

2020 Cách đơn giản:

git reset <commit_hash>

(Hàm băm cam kết của lần xác nhận cuối cùng bạn muốn giữ).

Nếu cam kết được đẩy, bạn có thể thực hiện:

git push -f

Bạn sẽ giữ các thay đổi hiện không được cam kết tại địa phương

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.