Hoàn tác một cuộc nổi loạn git


3179

Làm thế nào tôi có thể dễ dàng hoàn tác một rebase git?

Ý tưởng hiện tại của tôi chỉ là cách tiếp cận thủ công:

  1. git checkout trên cha mẹ cam kết cho cả hai chi nhánh
  2. Tạo một nhánh tạm thời từ đó
  3. git cherry-pick tất cả các cam kết bằng tay
  4. thay thế nhánh mà tôi đã khởi động lại bởi nhánh được tạo thủ công

Trong tình huống hiện tại của tôi, điều này sẽ hoạt động vì tôi có thể dễ dàng nhận ra các cam kết từ cả hai chi nhánh (một là công cụ của tôi, bên kia là công cụ của đồng nghiệp của tôi).

Tuy nhiên, cách tiếp cận của tôi đánh tôi là tối ưu và dễ bị lỗi (giả sử tôi vừa mới nổi loạn với 2 nhánh của chính mình).

Làm rõ : Tôi đang nói về một cuộc nổi loạn trong đó một loạt các cam kết đã được phát lại. Không chỉ một.


6
Cũng lưu ý rằng trong quá trình rebase, bạn có thể loại trừ các xác nhận hoặc xóa chúng; những thay đổi này không thể hoàn nguyên nếu không có con trỏ đến các nút ban đầu hoặc chọn lọc thông qua reflog, do đó, việc đánh dấu anh đào sẽ không hoạt động.
ANeves

Câu trả lời:


4337

Cách đơn giản nhất sẽ được tìm ra đầu cam kết của chi nhánh như nó đã được ngay trước rebase bắt đầu vào reflog ...

git reflog

và để đặt lại nhánh hiện tại cho nó (với những lưu ý thông thường về việc hoàn toàn chắc chắn trước khi đặt lại với --hardtùy chọn).

Giả sử cam kết cũ có HEAD@{5}trong nhật ký ref:

git reset --hard HEAD@{5}

Trong Windows, bạn có thể cần trích dẫn tài liệu tham khảo:

git reset --hard "HEAD@{5}"

Bạn có thể kiểm tra lịch sử của người đứng đầu ứng cử viên cũ bằng cách thực hiện git log HEAD@{5}( Windows git log "HEAD@{5}" :).

Nếu bạn không bị vô hiệu hóa trên mỗi lần cập nhật chi nhánh, bạn có thể chỉ cần thực hiện git reflog branchname@{1}như một cuộc nổi loạn sẽ tách đầu người đứng đầu trước khi gắn lại vào đầu cuối cùng. Tôi sẽ kiểm tra lại điều này, mặc dù gần đây tôi chưa xác minh điều này.

Theo mặc định, tất cả các reflog được kích hoạt cho các kho lưu trữ không trống:

[core]
    logAllRefUpdates = true

115
Git reflog là tuyệt vời, chỉ cần nhớ rằng bạn có thể có đầu ra được định dạng tốt hơn với git log -g(mẹo từ progit.org/book của Scott Chacon ).
karmi

60
@Zach: git rebase --abort( -ikhông có ý nghĩa gì --abort) là từ bỏ một cuộc nổi loạn chưa hoàn thành - vì có xung đột hoặc vì nó tương tác hoặc cả hai; đó không phải là về việc hoàn tác một cuộc nổi loạn thành công mà đó là câu hỏi nói về cái gì. Bạn có thể sử dụng rebase --aborthoặc reset --hardtùy thuộc vào tình huống bạn đang ở. Bạn không cần phải làm cả hai.
CB Bailey

311
Chỉ trong trường hợp, tạo một bản sao lưu đầu tiên : git tag BACKUP. Bạn có thể quay lại nếu có sự cố xảy ra:git reset --hard BACKUP
kolypto

6
Nếu bạn đã thực hiện nhiều cam kết thì ĐẦU @ {#} mà bạn đang tìm kiếm sẽ được mở đầu bằng commit:trái ngược với rebase:. Nghe có vẻ rõ ràng nhưng nó làm tôi bối rối một chút.
Warpling

4
Tham gia bữa tiệc sau một cuộc nổi loạn tình cờ: D. Sẽ không phải là một git reset --hard ORIG_HEADthủ thuật ngay lập tức sau cuộc nổi loạn tình cờ?
quay

1487

Trên thực tế, rebase lưu điểm bắt đầu của bạn để ORIG_HEADđiều này thường đơn giản như:

git reset --hard ORIG_HEAD

Tuy nhiên, các reset, rebasemergetất cả tiết kiệm ban đầu của bạn HEADtrỏ vào ORIG_HEADnhư vậy, nếu bạn đã thực hiện bất kỳ của những lệnh từ rebase bạn đang cố gắng lùi lại sau đó bạn sẽ phải sử dụng reflog.


34
Trong trường hợp ORIG_HEADkhông còn hữu ích, bạn cũng có thể sử dụng branchName@{n}cú pháp, nvị trí trước thứ n của con trỏ nhánh. Vì vậy, ví dụ, nếu bạn rebase featureAnhánh lên nhánh của bạn master, nhưng bạn không thích kết quả của rebase, thì bạn chỉ cần làm git reset --hard featureA@{1}lại để đặt lại nhánh trở lại chính xác vị trí của nó trước khi bạn thực hiện rebase. Bạn có thể đọc thêm về cú pháp nhánh @ {n} tại các tài liệu Git chính thức để sửa đổi .

15
Đây là cách dễ nhất. Theo dõi nó với một git rebase --abortmặc dù.
Seph

1
@DaBlick điều này đã làm việc tốt với tôi sau một cuộc nổi loạn hoàn toàn thành công mà không có xung đột trên git 2.17.0.
dvlsg

4
Và hãy để tôi bổ sung cho nó: git reset --hard ORIG_HEADcó thể sử dụng nhiều lần để quay đi quay lại. Nói, nếu A --- rebase thành --- B --- rebase thành --- C, bây giờ tôi đang ở C, tôi có thể quay lại A bằng cách sử dụng hai lầngit reset --hard ORIG_HEAD
CalvinChe

5
@Seph Bạn có thể giải thích lý do tại sao bạn đề nghị theo dõi với git rebase --abort?
UpTheCux

386

Câu trả lời của Charles hoạt động, nhưng bạn có thể muốn làm điều này:

git rebase --abort

để dọn dẹp sau reset.

Nếu không, bạn có thể nhận được thông báo “ Interactive rebase already started”.


4
Điều này đã loại bỏ phần "| REBASE" trong lời nhắc của tôi. +1
Wouter Thielen

62
Đó không phải là câu hỏi. Câu hỏi hỏi làm thế nào để hoàn tác một cuộc nổi loạn đã hoàn thành.
Arunav Sanyal

2
Nên là một nhận xét về câu trả lời của Charles, bởi vì nó không trả lời câu hỏi trên
Murmel

Hừm, tôi không phải làm thế.
Viktor Seč

1
Người duy nhất làm việc cho tôi!
Alexandre Picard

90

Đặt lại nhánh vào đối tượng cam kết lơ lửng của mẹo cũ tất nhiên là giải pháp tốt nhất, bởi vì nó khôi phục trạng thái trước đó mà không tốn bất kỳ nỗ lực nào. Nhưng nếu bạn đã mất các cam kết đó (ví dụ: vì bạn đã thu thập rác trong kho lưu trữ của mình trong thời gian này hoặc đây là một bản sao mới), bạn luôn có thể khởi động lại chi nhánh một lần nữa. Chìa khóa cho việc này là công --ontotắc.

Giả sử bạn có một nhánh chủ đề được gọi một cách tưởng tượng topic, rằng bạn đã phân nhánh masterkhi tiền boa master0deadbeefcam kết. Tại một số thời điểm trong khi trên topicchi nhánh, bạn đã làm git rebase master. Bây giờ bạn muốn hoàn tác điều này. Đây là cách thực hiện:

git rebase --onto 0deadbeef master topic

Điều này sẽ thực hiện tất cả các cam kết topickhông bật mastervà phát lại chúng trên đầu trang 0deadbeef.

Với --onto, bạn có thể sắp xếp lại lịch sử của mình thành bất kỳ hình dạng nào .

Chúc vui vẻ. :-)


3
Tôi nghĩ rằng đây là lựa chọn tốt nhất vì tính linh hoạt của nó. Tôi đã phân nhánh b1 khỏi master, sau đó chuyển b1 thành một nhánh mới b2, sau đó muốn hoàn nguyên b1 để dựa vào master một lần nữa. Tôi chỉ yêu git - cảm ơn!
ripper234

2
Đây là lựa chọn tốt nhất ở đây! Nó giữ tất cả những thay đổi tôi có trên chi nhánh hiện tại của mình và loại bỏ tất cả những thay đổi không mong muốn!
Alicia Tang

Tôi muốn nói rằng với sự kết hợp --onto-ibạn có thể sắp xếp lại lịch sử của mình thành bất kỳ hình dạng nào. Sử dụng gitk (hoặc gitx trên mac) để xem hình dạng bạn tạo :-).
rjmunro

69

Tôi thực sự đặt một thẻ dự phòng trên chi nhánh trước khi tôi thực hiện bất kỳ hoạt động không cần thiết nào (hầu hết các cuộc nổi loạn là tầm thường, nhưng tôi sẽ làm điều đó nếu nó trông phức tạp ở bất cứ đâu).

Sau đó, khôi phục là dễ dàng như git reset --hard BACKUP.


2
Tôi cũng làm điều này Nó cũng hữu ích để git diff BACKUP..HAD để đảm bảo bạn đã thay đổi những gì bạn có ý định.
Paul Bone

5
Tôi cũng đã từng làm điều đó, nhưng vì tôi cảm thấy thoải mái hơn với việc reflog nên tôi không còn cảm thấy cần thiết nữa. Các reflog về cơ bản là làm điều này thay mặt bạn mỗi khi bạn thay đổi CHÍNH.
Pete Hodgson

4
Chà, tôi thích những cái tên có ý nghĩa, bởi vì tìm kiếm đúng mặt hàng trong reflog đôi khi không vui chút nào.
Alex Gontmakher

12
Bạn thực sự thậm chí không cần phải tạo một nhánh sao lưu, bạn chỉ cần sử dụng branchName@{n}cú pháp, đây nlà vị trí thứ n của con trỏ nhánh. Vì vậy, ví dụ, nếu bạn rebase featureAnhánh lên nhánh của bạn master, nhưng bạn không thích kết quả của rebase, thì bạn chỉ cần làm git reset --hard featureA@{1}lại để đặt lại nhánh trở lại chính xác vị trí của nó trước khi bạn thực hiện rebase. Bạn có thể đọc thêm về branch@{n}cú pháp tại các tài liệu Git chính thức để sửa đổi .

2
Trên thực tế, bạn thậm chí không cần sử dụng cú pháp ở trên, theo câu trả lời của Pat Notz , bản gốc HEADcủa chi nhánh tạm thời được lưu trữ trong ORIG_HEAD. Nhưng kỹ thuật sử dụng nhãn nhánh sao lưu cũng hoạt động, đó chỉ là các bước nữa.

68

Trong trường hợp bạn đã đẩy chi nhánh của mình đến kho lưu trữ từ xa (thường là nguồn gốc) và sau đó bạn đã thực hiện một rebase thành công (không hợp nhất) ( git rebase --abortđưa ra "Không có rebase trong tiến trình"), bạn có thể dễ dàng đặt lại chi nhánh bằng lệnh:

thiết lập lại git - nguồn gốc gốc / {BranchName}

Thí dụ:

$ ~/work/projects/{ProjectName} $ git status
On branch {branchName}
Your branch is ahead of 'origin/{branchName}' by 135 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

$ ~/work/projects/{ProjectName} $ git reset --hard origin/{branchName}
HEAD is now at 6df5719 "Commit message".

$ ~/work/projects/{ProjectName} $ git status
On branch {branchName}
Your branch is up-to-date with 'origin/{branchName}.

nothing to commit, working directory clean

Đó là câu trả lời đúng cho tôi. Rebase và commit trước khi rebase có cùng ID xác nhận và quay trở lại HEAD {1} sẽ không hoàn nguyên rebase!
Bill Kotsias

23

Sử dụng reflogkhông làm việc cho tôi.

Những gì làm việc cho tôi là tương tự như được mô tả ở đây . Mở tệp trong .git / log / refs được đặt tên theo nhánh được khởi động lại và tìm dòng có chứa "rebase finsihed", đại loại như:

5fce6b51 88552c8f Kris Leech <me@example.com> 1329744625 +0000  rebase finished: refs/heads/integrate onto 9e460878

Kiểm tra cam kết thứ hai được liệt kê trên dòng.

git checkout 88552c8f

Sau khi xác nhận điều này có chứa những thay đổi đã mất của tôi, tôi rẽ nhánh và thở phào nhẹ nhõm.

git log
git checkout -b lost_changes


3
Whoa - từ liên kết đó, "Có một cảnh báo: Tôi đã mất lịch sử của chi nhánh nhưng trong trường hợp này nó không thực sự quan trọng. Tôi rất vui khi nhận được những thay đổi của mình." ?
ruffin

16

Đối với nhiều cam kết, hãy nhớ rằng bất kỳ cam kết nào tham chiếu tất cả lịch sử dẫn đến cam kết đó. Vì vậy, trong câu trả lời của Charles, hãy đọc "cam kết cũ" là "cam kết mới nhất". Nếu bạn đặt lại cam kết đó, thì tất cả lịch sử dẫn đến cam kết đó sẽ xuất hiện lại. Điều này nên làm những gì bạn muốn.


11

Theo giải pháp của @ ALLan và @Zearin, tôi ước mình chỉ có thể bình luận nhưng tôi không đủ danh tiếng, vì vậy tôi đã sử dụng lệnh sau:

Thay vì làm git rebase -i --abort (chú ý -i ) Tôi đã phải chỉ đơn giản là làm git rebase --abort( mà không cần sự -i ).

Sử dụng cả hai -i--abortcùng một lúc khiến Git hiển thị cho tôi danh sách các tùy chọn sử dụng.

Vì vậy, trạng thái chi nhánh trước đây và hiện tại của tôi với giải pháp này là:

matbhz@myPc /my/project/environment (branch-123|REBASE-i)
$ git rebase --abort

matbhz@myPc /my/project/environment (branch-123)
$

11

Nếu bạn nổi dậy thành công chống lại chi nhánh từ xa và git rebase --abortbạn vẫn không thể thực hiện một số thủ thuật để cứu công việc của mình và không bị ép buộc. Giả sử chi nhánh hiện tại của bạn bị từ chối do nhầm lẫn được gọi your-branchvà đang theo dõiorigin/your-branch

  • git branch -m your-branch-rebased # đổi tên chi nhánh hiện tại
  • git checkout origin/your-branch # kiểm tra trạng thái mới nhất được biết là có nguồn gốc
  • git checkout -b your-branch
  • kiểm tra git log your-branch-rebased, so sánh git log your-branchvà xác định các cam kết bị thiếuyour-branch
  • git cherry-pick COMMIT_HASH cho mọi cam kết trong your-branch-rebased
  • đẩy những thay đổi của bạn Xin lưu ý rằng hai chi nhánh địa phương được liên kết với remote/your-branchvà bạn chỉ nên đẩyyour-branch

4

Giả sử tôi khởi động lại chủ nhân cho nhánh tính năng của mình và tôi nhận được 30 cam kết mới phá vỡ thứ gì đó. Tôi đã thấy rằng thường thì dễ dàng nhất để loại bỏ các cam kết xấu.

git rebase -i HEAD~31

Rebase tương tác trong 31 lần xác nhận gần nhất (sẽ không hại nếu bạn chọn quá nhiều).

Chỉ cần thực hiện các cam kết mà bạn muốn loại bỏ và đánh dấu chúng bằng "d" thay vì "chọn". Bây giờ các xác nhận đã bị xóa một cách hiệu quả khi hoàn tác rebase (nếu bạn chỉ xóa các xác nhận bạn vừa nhận được khi khởi động lại).


3

Đối với người mới / bất kỳ ai quá sợ thực hiện thiết lập lại cứng, bạn có thể kiểm tra cam kết từ reflog, và sau đó lưu nó dưới dạng một nhánh mới.

git reflog

Tìm cam kết ngay trước khi bạn bắt đầu nổi loạn. Bạn có thể cần phải cuộn xuống dưới để tìm nó (nhấn Enter hoặc PageDown). Lưu ý số CHÍNH và thay thế 57:

git checkout HEAD@{57}

Xem lại nhánh / cam kết, nếu nó trông tốt, hãy tạo một nhánh mới bằng cách sử dụng ĐẦU này:

git checkout -b new_branch_name

2

Nếu bạn ở trên một chi nhánh, bạn có thể sử dụng:

git reset --hard @{1}

Không chỉ có một nhật ký tham chiếu cho CHÍNH (thu được bởi git reflog), còn có các bản cập nhật cho mỗi nhánh (thu được bởi git reflog <branch>). Vì vậy, nếu bạn đang ở trên masterthì git reflog mastersẽ liệt kê tất cả các thay đổi cho chi nhánh đó. Bạn có thể tham khảo rằng những thay đổi bằng cách master@{1}, master@{2}vv

git rebase thường sẽ thay đổi CHÍNH nhiều lần nhưng chi nhánh hiện tại sẽ chỉ được cập nhật một lần.

@{1}chỉ đơn giản là một phím tắt cho nhánh hiện tại , vì vậy nó tương đương với master@{1}nếu bạn đang bật master.

git reset --hard ORIG_HEADsẽ không hoạt động nếu bạn sử dụng git resettrong quá trình tương tác rebase.


1

Những gì tôi thường làm là git reset #commit_hash

đến lần cam kết cuối cùng mà tôi nghĩ rebase không có hiệu lực.

sau đó git pull

Bây giờ chi nhánh của bạn phải khớp chính xác như các cam kết chính và bị từ chối không nên ở trong đó.

Bây giờ người ta có thể chọn cherry trên nhánh này.


1

Tôi đã thử tất cả các đề xuất với thiết lập lại và reflog mà không thành công. Khôi phục lịch sử địa phương của IntelliJ đã giải quyết vấn đề về các tệp bị mất


Cảm ơn! Chưa bao giờ sử dụng lịch sử địa phương trước đây, nhưng điều này hóa ra là lựa chọn dễ dàng nhất để tôi khôi phục một số mã vô tình bị từ chối. Rất đẹp nhưng hơi ẩn tính năng.
Magnus W

0

thiết lập lại git - nguồn gốc gốc / {BranchName}

là giải pháp chính xác để đặt lại tất cả các thay đổi cục bộ của bạn được thực hiện bằng rebase.


1
nếu bạn khó thiết lập lại, origin/branchbạn có thể mất các thay đổi giữa CHÍNH và điểm đó. Bạn không muốn nói chung.
bluesmonk

Đây chính xác là những gì tôi muốn trong trường hợp của tôi. Vì vậy, nâng cao.
Mandar Vaze

-4

Nếu bạn làm hỏng một cái gì đó trong một rebase git, ví dụ git rebase --abort, trong khi bạn có các tệp không được cam kết, chúng sẽ bị mất và git reflogsẽ không giúp ích. Điều này đã xảy ra với tôi và bạn sẽ cần phải suy nghĩ bên ngoài hộp ở đây. Nếu bạn may mắn như tôi và sử dụng IntelliJ Webstorm thì bạn có thể right-click->local historyvà có thể trở lại trạng thái trước đó của tệp / thư mục của mình cho dù bạn có làm gì với phần mềm phiên bản. Nó luôn luôn tốt để có một failafe chạy.


5
git rebase --aborthủy bỏ một rebase hoạt động, nó không hoàn tác một rebase. Ngoài ra, sử dụng hai VCS cùng một lúc là một ý tưởng tồi. Đây là một tính năng hay trong phần mềm Jetbrains nhưng bạn không nên sử dụng cả hai. Tốt hơn hết là bạn nên học Git, đặc biệt là khi trả lời các câu hỏi về Stack Overflow liên quan đến Git.
dudewad
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.