Làm thế nào để bạn loại bỏ một sửa đổi cụ thể trong lịch sử git?


213

Giả sử lịch sử git của bạn trông như thế này:

1 2 3 4 5

1155 là phiên bản riêng biệt. Bạn cần xóa 3 trong khi vẫn giữ 1, 2, 4 và 5. Làm thế nào để thực hiện điều này?

Có một phương pháp hiệu quả khi có hàng trăm phiên bản sau khi bị xóa?


Câu hỏi này không rõ ràng. Nó không nói rõ ràng rằng tác giả muốn 1-2- (3 + 4) -5 hoặc 1-2-4-5
RandyTek

14
8 năm sau, tôi không thể nói cho bạn biết chính xác vấn đề mà tôi đang cố gắng giải quyết. Nhưng trong git luôn có rất nhiều cách để làm một cái gì đó, và có rất nhiều câu trả lời mà nhiều người thích, vì vậy tôi đoán rằng sự mơ hồ không gây ra quá nhiều khó khăn cho mọi người
1800 THÔNG TIN

1
git rebase --onto 2 3 HEADkhá nhiều có nghĩa là rebase lên 2, với các cam kết giữa 3 và CHÍNH (ĐẦU là tùy chọn, hoặc 5 trong trường hợp này)
neaumusic

Câu trả lời:


76

Để kết hợp sửa đổi 3 và 4 thành một phiên bản duy nhất, bạn có thể sử dụng git rebase. Nếu bạn muốn xóa các thay đổi trong phiên bản 3, bạn cần sử dụng lệnh chỉnh sửa trong chế độ rebase tương tác. Nếu bạn muốn kết hợp các thay đổi thành một phiên bản duy nhất, hãy sử dụng bí đao.

Tôi đã sử dụng thành công kỹ thuật squash này, nhưng chưa bao giờ cần phải sửa đổi trước đây. Tài liệu git-rebase trong phần "Chia tách cam kết" hy vọng sẽ cung cấp cho bạn đủ ý tưởng để tìm ra nó. (Hoặc người khác có thể biết).

Từ tài liệu git :

Bắt đầu với cam kết cũ nhất mà bạn muốn giữ nguyên trạng:

git rebase -i <after-this-commit>

Một trình soạn thảo sẽ được kích hoạt với tất cả các cam kết trong nhánh hiện tại của bạn (bỏ qua các cam kết hợp nhất), xuất hiện sau cam kết đã cho. Bạn có thể sắp xếp lại các cam kết trong danh sách này theo nội dung trái tim của bạn và bạn có thể xóa chúng. Danh sách trông ít nhiều như thế này:

chọn deadbee Đường dây của cam kết này
chọn fa1afe1 Dòng trên của cam kết tiếp theo
...

Các mô tả trực tuyến là hoàn toàn cho niềm vui của bạn; git-rebase sẽ không nhìn vào chúng mà chỉ nhìn vào tên cam kết ("deadbee" và "fa1afe1" trong ví dụ này), vì vậy đừng xóa hoặc chỉnh sửa tên.

Bằng cách thay thế lệnh "chọn" bằng lệnh "chỉnh sửa", bạn có thể yêu cầu git-rebase dừng lại sau khi áp dụng cam kết đó, để bạn có thể chỉnh sửa các tệp và / hoặc thông báo cam kết, sửa đổi cam kết và tiếp tục khởi động lại.

Nếu bạn muốn gấp hai hoặc nhiều lần xác nhận thành một, hãy thay thế lệnh "pick" bằng "squash" cho lần xác nhận thứ hai và tiếp theo. Nếu các xác nhận có các tác giả khác nhau, nó sẽ gán thuộc tính cam kết cho tác giả của cam kết đầu tiên.


42
-1 Câu hỏi được xác định rõ nhưng câu trả lời này không quá rõ ràng. Tác giả không cho biết giải pháp chính xác là gì.
Alexanderr Levchuk

1
Đây là một dẫn sai. Phần CAM KẾT không phải là phần chính xác. Bạn muốn đọc cao hơn nhiều trong hướng dẫn - xem câu trả lời của @Rares Vernica.
Alexanderr Levchuk

1
@AleksandrLevchuk Câu hỏi không được xác định rõ: không rõ ràng từ cách đặt câu hỏi liệu bộ thay đổi trong 3 sẽ được giữ hay loại bỏ. Tôi sẽ đồng ý rằng nếu các thay đổi sẽ bị loại bỏ, các câu trả lời khác cung cấp một cách tiếp cận dễ dàng hơn. Tuy nhiên, nếu những thay đổi được giữ, đây sẽ là một hoạt động thẩm mỹ hoàn toàn. Cả hai cách tiếp cận đều viết lại lịch sử theo những cách nguy hiểm nếu những người khác có thể đã làm việc dựa trên lịch sử thiếu sót; nếu đó là trường hợp, việc dọn dẹp mỹ phẩm không nên được thực hiện và việc xóa thay đổi sẽ được thực hiện tốt hơn thông qua git Revert.
Theodore Murdock

124

Mỗi nhận xét này (và tôi đã kiểm tra rằng điều này là đúng), câu trả lời rado là rất gần nhưng lá git trong tình trạng đầu tách ra. Thay vào đó, hãy xóa HEADvà sử dụng phần này để xóa <commit-id>khỏi nhánh bạn đang bật:

git rebase --onto <commit-id>^ <commit-id>

1
Nếu bạn có thể cho tôi biết làm thế nào để loại bỏ id-id đó khỏi lịch sử dựa trên chỉ id-id, bạn sẽ là người hùng của tôi.
kayleeFrye_onDeck

Bạn có thể vui lòng bao gồm giải thích những gì lệnh ma thuật làm trong câu trả lời? Tức là những gì mỗi tham số biểu thị?
alexandroid

Điều này thật tuyệt vời nhưng nếu tôi muốn xóa bỏ cam kết đầu tiên trong lịch sử thì sao? (đó là lý do tại sao tôi đến đây: P)
Sterling Camden

2
Vì một số lý do, không có gì xảy ra khi tôi chạy cái này. Tuy nhiên, thay đổi ^để ~1làm cho nó hoạt động.
Antimon

Cách muộn, nhưng tôi sẽ nói thêm rằng nếu bạn gặp phải xung đột hợp nhất, hãy hủy bỏ cuộc nổi loạn và sau đó chạy lại nó với --strategy-option theirs .
Laststar007

122

Đây là một cách để loại bỏ không tương tác một cách cụ thể <commit-id>, chỉ biết những <commit-id>gì bạn muốn xóa:

git rebase --onto <commit-id>^ <commit-id> HEAD

2
Làm việc cho tôi quá. Btw, toán tử ^ làm gì? Có nghĩa là cam kết tiếp theo sau khi xác định?
hopia

3
@hopia có nghĩa là cha mẹ (đầu tiên) của cam kết đã chỉ định. Xem "sửa đổi trợ giúp git"
Emil Styrke

12
Xem khuyến nghị của @ kareem trong câu trả lời của anh ấy để bỏ qua HEADđể tránh một cái đầu tách ra.
mkuity0

1
Dễ dàng hơn nhiều so với việc phải tìm ra có bao nhiêu cam kết quay lại tìm kiếm.
Dana Woodman

1
Điều này thật tuyệt vời nhưng nếu tôi muốn xóa bỏ cam kết đầu tiên trong lịch sử thì sao? (đó là lý do tại sao tôi đến đây: P)
Sterling Camden

76

Như đã lưu ý trước khi git-rebase (1) là bạn của bạn. Giả sử các cam kết nằm trong masterchi nhánh của bạn , bạn sẽ làm:

git rebase --onto master~3 master~2 master

Trước:

1---2---3---4---5  master

Sau:

1---2---4'---5' master

Từ git-rebase (1):

Một loạt các cam kết cũng có thể được loại bỏ bằng rebase. Nếu chúng ta có tình huống sau:

E---F---G---H---I---J  topicA

sau đó là lệnh

git rebase --onto topicA~5 topicA~3 topicA

sẽ dẫn đến việc loại bỏ các cam kết F và G:

E---H'---I'---J'  topicA

Điều này rất hữu ích nếu F và G bị thiếu sót theo một cách nào đó hoặc không phải là một phần của topicA. Lưu ý rằng đối số tới --onto và tham số có thể là bất kỳ xác nhận hợp lệ nào.


3
cái này không nên --onto master~3 master~1sao?
Matthias

3
Nếu bạn chỉ đơn giản muốn xóa lần xác nhận cuối cùng, thì đó là - Master master ~ 1 master
MikeHoss 24/07 '

Tôi muốn mang theo sol này. nhưng tôi nhận được MERGE CONFLICTlỗi. Tôi đã sử dụng kịch bản được đề cập trong stackoverflow.com/questions/2938602/remove-specific-commit và tôi không thể xóa cam kết thứ 2 trong ví dụ đó.
maan81

22

Nếu tất cả những gì bạn muốn làm là xóa các thay đổi được thực hiện trong phiên bản 3, bạn có thể muốn sử dụng git Revert.

Git hoàn nguyên chỉ đơn giản là tạo một bản sửa đổi mới với các thay đổi hoàn tác tất cả các thay đổi trong bản sửa đổi mà bạn đang hoàn nguyên.

Điều này có nghĩa là, bạn giữ lại thông tin về cả cam kết không mong muốn và cam kết loại bỏ những thay đổi đó.

Điều này có lẽ thân thiện hơn rất nhiều nếu có thể ai đó đã rút từ kho lưu trữ của bạn trong thời gian đó, vì về cơ bản hoàn nguyên chỉ là một cam kết tiêu chuẩn.


4
Thật không may, không phải là một giải pháp tốt cho tôi vì ai đó đã vô tình cam kết 100 MB tào lao cho repo, làm tăng kích thước và làm cho giao diện web chậm chạp.
Stephen Smith

18

Tất cả các câu trả lời cho đến nay không giải quyết được mối quan tâm kéo dài:

Có một phương pháp hiệu quả khi có hàng trăm phiên bản sau khi bị xóa?

Các bước tiếp theo, nhưng để tham khảo, hãy giả sử lịch sử sau đây:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : cam kết chỉ sau khi cam kết được xóa (sạch)

R : Cam kết được xóa

B : cam kết ngay trước cam kết bị xóa (cơ sở)

Do ràng buộc "hàng trăm sửa đổi", tôi giả sử các điều kiện tiên quyết sau:

  1. có một số cam kết đáng xấu hổ mà bạn muốn không bao giờ tồn tại
  2. có các cam kết tiếp theo ZERO thực sự phụ thuộc vào cam kết xấu hổ đó (không có xung đột khi hoàn nguyên)
  3. bạn không quan tâm rằng bạn sẽ được liệt kê là 'Người ủy thác' trong số hàng trăm cam kết can thiệp ('Tác giả' sẽ được giữ nguyên)
  4. bạn chưa bao giờ chia sẻ kho lưu trữ
    • hoặc bạn thực sự có đủ ảnh hưởng đối với tất cả những người đã từng nhân bản lịch sử với cam kết trong đó để thuyết phục họ sử dụng lịch sử mới của bạn
    • và bạn không quan tâm đến việc viết lại lịch sử

Đây là một tập hợp các ràng buộc khá hạn chế, nhưng có một câu trả lời thú vị thực sự hoạt động trong trường hợp góc này.

Dưới đây là các bước:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Nếu thực sự không có xung đột, thì điều này sẽ tiếp tục mà không bị gián đoạn. Nếu có mâu thuẫn, bạn có thể giải quyết chúng và rebase --continuehoặc quyết định chỉ sống với sự bối rối vàrebase --abort .

Bây giờ bạn nên ở trên masterđó không còn cam kết R trong đó. Các saveđiểm chi nhánh đến nơi mà bạn đã trước đó, trong trường hợp bạn muốn hòa giải.

Bạn muốn sắp xếp việc chuyển nhượng của người khác sang lịch sử mới như thế nào là tùy thuộc vào bạn. Bạn sẽ cần phải được làm quen với stash, reset --hard, và cherry-pick. Và bạn có thể xóa base, remove-mesavechi nhánh


Làm thế nào tôi có thể thích điều này 150000 lần?
Gena Moroz

Làm việc cho tôi để xóa 3 lần xác nhận theo thứ tự từ giữa lịch sử của một chi nhánh nơi ai đó đã cam kết một loạt các tệp 150mb.
Marcos

3

Tôi cũng hạ cánh trong một tình huống tương tự. Sử dụng rebase tương tác bằng cách sử dụng lệnh bên dưới và trong khi chọn, thả cam kết thứ 3.

git rebase -i remote/branch

2

Vì vậy, đây là kịch bản mà tôi phải đối mặt, và cách tôi giải quyết nó.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

đây Rlà cam kết mà tôi cần phải được gỡ bỏ, vàI là một cam kết duy nhất xuất hiện sauR

Tôi đã thực hiện một cam kết hoàn nguyên và đè bẹp chúng lại với nhau

git revert [commit id of R]
git rebase -i HEAD~3

Trong cuộc nổi loạn tương tác squash, 2 lần xác nhận cuối cùng.


0

Câu trả lời của rado và kareem không làm gì cho tôi (chỉ thông báo "Chi nhánh hiện tại đã cập nhật." Xuất hiện). Có thể điều này xảy ra vì biểu tượng '^' không hoạt động trong bảng điều khiển Windows. Tuy nhiên, theo nhận xét này , thay thế '^' bằng '~ 1' sẽ giải quyết vấn đề.

git rebase --onto <commit-id>^ <commit-id>
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.