Hầu hết các câu trả lời trước là sai nguy hiểm!
KHÔNG làm điều này:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
Vì lần sau khi bạn chạy git rebase
(hoặc git pull --rebase
) 3 cam kết đó sẽ bị loại bỏ trong âm thầm newbranch
! (xem giải thích bên dưới)
Thay vào đó hãy làm điều này:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- Đầu tiên, nó loại bỏ 3 cam kết gần đây nhất (
--keep
giống như --hard
, nhưng an toàn hơn, vì thất bại thay vì vứt bỏ những thay đổi không được cam kết).
- Sau đó nó rẽ nhánh
newbranch
.
- Sau đó, anh đào chọn 3 cam kết trở lại
newbranch
. Vì chúng không còn được tham chiếu bởi một chi nhánh, nên nó sử dụng reflog của git : HEAD@{2}
là cam kết HEAD
được sử dụng để đề cập đến 2 thao tác trước đây, tức là trước khi chúng tôi 1. kiểm tra newbranch
và 2. được sử dụng git reset
để loại bỏ 3 cam kết.
Cảnh báo: reflog được bật theo mặc định, nhưng nếu bạn đã tắt nó theo cách thủ công (ví dụ: bằng cách sử dụng kho git "trần"), bạn sẽ không thể lấy lại 3 lần xác nhận sau khi chạy git reset --keep HEAD~3
.
Một thay thế không dựa vào reflog là:
# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3
(nếu bạn thích bạn có thể viết @{-1}
- nhánh đã kiểm tra trước đó - thay vì oldbranch
).
Giải thích kỹ thuật
Tại sao sẽ git rebase
loại bỏ 3 cam kết sau ví dụ đầu tiên? Đó là bởi vì git rebase
không có đối số cho phép --fork-point
tùy chọn theo mặc định, sử dụng reflog cục bộ để cố gắng mạnh mẽ chống lại nhánh ngược dòng bị đẩy mạnh.
Giả sử bạn đã phân nhánh gốc / chủ khi nó chứa các xác nhận M1, M2, M3, sau đó tự thực hiện ba lần xác nhận:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
nhưng sau đó ai đó viết lại lịch sử bằng cách đẩy gốc / chủ để loại bỏ M2:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
Sử dụng reflog cục bộ của bạn, git rebase
có thể thấy rằng bạn đã rẽ nhánh từ một phiên bản trước đó của nhánh gốc / nhánh chính và do đó, các cam kết M2 và M3 không thực sự là một phần của nhánh chủ đề của bạn. Do đó, nó giả định một cách hợp lý rằng vì M2 đã bị xóa khỏi nhánh ngược dòng, bạn không còn muốn nó trong nhánh chủ đề của mình nữa khi nhánh chủ đề bị từ chối:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
Hành vi này có ý nghĩa, và nói chung là điều đúng đắn khi làm lại.
Vì vậy, lý do mà các lệnh sau không thành công:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
là bởi vì họ để lại reflog ở trạng thái sai. Git thấy newbranch
như đã chia hai ra khỏi chi nhánh thượng nguồn ở một phiên bản bao gồm 3 cam kết, thì reset --hard
viết lại lịch sử của thượng nguồn để loại bỏ các cam kết, và thời gian để sau khi bạn chạy git rebase
nó loại bỏ chúng giống như bất kỳ khác cam kết rằng đã bị xóa khỏi thượng lưu.
Nhưng trong trường hợp cụ thể này, chúng tôi muốn 3 cam kết đó được coi là một phần của nhánh chủ đề. Để đạt được điều đó, chúng ta cần tách ra khỏi thượng nguồn trong lần sửa đổi trước đó không bao gồm 3 cam kết. Đó là những gì các giải pháp được đề xuất của tôi làm, do đó cả hai đều để lại reflog ở trạng thái chính xác.
Để biết thêm chi tiết, xem định nghĩa --fork-point
trong tài liệu cơ sở git rebase và git merge-base .