Git: Tổng quan về Git rebase --onto
Xóa các xác nhận khỏi nhánh hiện tại hoặc thay đổi nhánh cha.
Trong bài viết Cách thay đổi nhánh cha trong git ta đã nói nhanh về việc sử dụng git rebase --onto
trong trường hợp bạn muốn thay thế nhánh cha hiện tại. Nhưng git rebase --onto
không chỉ thay thế nhánh cha. Ta có thể làm nhiều hơn nữa với sự giúp đỡ của git rebase --onto
. Đó là một chủ đề tốt để xem xét kỹ lưỡng. Để sử dụng nó một cách thoải mái trước hết bạn cần phải hiểu nó.
Có hai trường hợp bạn có thể thực hiện git rebase --onto
:
- Bạn có một nhánh, nơi bạn muốn thay đổi nhánh cha của nó.
- Bạn muốn nhanh chóng xóa một số commit khỏi nhánh hiện tại của mình.
Tất nhiên, bạn có thể kết hợp hai lý do này. Bạn có thể thay đổi nhánh cha và xóa một số xác nhận cùng một lúc. Chúng ta sẽ tìm hiểu về điểm này. Nhưng trước khi đến đó, chúng ta cần hiểu sự khác biệt giữa git rebase --onto
hai và ba đối số.
Đầu tiên, chúng ta sẽ chỉ tập trung vào một git rebase
đơn giản. Tôi đã viết một bài báo riêng về git rebase và bạn có thể tham khảo.
Git base
git rebase <newparent> <branch>
là một lệnh cho phép ta có quyền truy cập vào commit mới nhất có thể truy cập được từ <newparent>
và chuyển các commit <branch>
của ta lên trên commit đó.
Trong trường hợp ta sử dụng lệnh:
git rebase master next-feature
Thì ta sẽ lấy:
Before After A---B---C---F---G (HEAD master) A---B---C---F---G (master) \ \ D---E (next-feature) D'---E' (HEAD next-feature)
Như bạn thấy ở trên, sau git rebase
thì HEAD
của ta luôn là đối số cuối cùng. Trong trường hợp này ta có nhánh next-feature
. Vì vậy, ta chuyển nhánh sang next-feature
. Trên nhánh này, chúng ta vẫn có quyền truy cập vào mã của mình trong các lần xác nhận D
và E
, nhưng chúng không giống nhau. Mã định danh duy nhất của chúng được tạo bởi hàm băm mã SHA-1 (dce79fd
) mà chúng ta thường gọi là SHA đã thay đổi. Đây là lý do tại sao ta đánh dấu chúng là D'
và E'
.
Khi ta sử dụng git rebase
và ta đã ở trên nhánh mà ta muốn khởi động lại, thì ta có thể bỏ qua đối số thứ hai, cụ thể là:
git rebase master
Và kết quả vẫn giống nhau:
Before After A---B---C---F---G (master) A---B---C---F---G (master) \ \ D---E (HEAD next-feature) D'---E' (HEAD next-feature)
Trong cả hai trường hợp trên nhánh master
, ta có hai lần xác nhận F
và G
không thể truy cập được từ nhánh next-feature
đó. Khi ta thực hiện git rebase
thì ta thực hiện commit D
(là lần xác nhận đầu tiên trên nhánh next-feature
) với tất cả các lần xác nhận tiếp theo trên nhánh này và ta di chuyển chúng lên trên lần xác nhận cuối cùng trên nhánh master
, vì vậy nó ở trên G
. Trong trường hợp của ta, khi nhìn vào sơ đồ, sẽ tốt hơn nếu nói rằng chúng tôi di chuyển các commit của mình vào cuối nhánh master
. Tuy nhiên, hãy nhớ rằng khi bạn sử dụng các công cụ như git log
, bạn sẽ thấy các thay đổi ở đầu nhánh master
. Nói cách khác, chúng ta thay đổi cha của nhánh next-feature
của chúng ta từ commit C
sang commit G
trên nhánh master
.
Git rebase –onto
Nhánh cha thay đổi chính xác hơn
Trong trường hợp git rebase --onto
thì ta có thể thay đổi điểm mà nhánh của ta bắt đầu không chỉ thành lần xác nhận cuối cùng trên nhánh cha, mà có thể chọn commit cụ thể nơi bắt đầu và cả nơi kết thúc. Điều này không chỉ đúng với một nhánh cụ thể mà còn đúng với các nhánh khác (tất cả các commit hợp lệ). Ta có thể nói rằng git rebase --onto
là giải pháp chính xác và linh hoạt. Nó cấp cho bạn quyền kiểm soát những gì và ở đâu đang bị hủy bỏ.
Ví dụ: bạn muốn thay đổi điểm bắt đầu của nhánh từ C
thành F
và bạn muốn xóa commit D
khỏi nhánh next-feature
của mình. Để làm điều đó, chúng ta cần sử dụng lệnh này:
git rebase --onto FD
Hiệu ứng sẽ như thế này:
Before After A---B---C---F---G (branch) A---B---C---F---G (branch) \ \ D---E---H---I (HEAD my-branch) E'---H'---I' (HEAD my-branch)
Chúng ta rebase lại commit có thể truy cập từ HEAD
(my-branch
) trong đó commit gốc là D
nằm trên commit F
. Vì vậy, ta có thể nói rằng ta đã thay đổi cha của commit E
từ D
thành F
.
Ta cũng nhận được hiệu ứng tương tự khi ta gọi:
git rebase --onto FD my-branch
Tình huống có vẻ khác khi thay vì HEAD
làm đối số thứ ba, chúng ta sẽ sử dụng lần xác nhận cuối cùng. Trong trường hợp của ta là I
thì khi này ta sẽ gọi:
git rebase --onto F D I
Thì hiệu ứng sẽ trông như sau:
Before After A---B---C---F---G (branch) A---B---C---F---G (branch) \ | \ D---E---H---I (HEAD my-branch) | E'---H'---I' (HEAD) \ D---E---H---I (my-branch)
Như bình thường, git rebase
ta chuyển HEAD
sang đối số cuối cùng của lệnh git rebase --onto
. Trong trường hợp này, đây là một commit I'
. Ta thấy rằng nhánh my-branch
vẫn như trước đây. Không có gì thay đổi trên my-branch
. Nhưng ta có nhánh mới là nhánh mới HEAD
của ta. Ngay bây giờ nó không phải là nhánh thực sự, nhưng ta có thể đặt tên cho nó. Điều gì đã xảy ra ở đây? Ta đã nói với git rằng ta thay đổi cha của commit E
. Bạn có thể nghĩ: “Nhưng tại sao phải là commit E
? Chúng tôi không có commit E
trong lệnh của chúng tôi. Vì cha hiện tại là commit D
, nên con của nó là commit E
. Vì vậy, chúng ta thay đổi cha của commit E
từ D
thành F
, nhưng chúng ta cũng chuyển HEAD
từ nhánh my-branch
đến commit mới I'
.
Hiệu ứng tương tự, chúng ta sẽ nhận được khi chúng ta gọi:
git rebase --onto FD HEAD
Một tình huống tương tự là khi ta muốn chuyển HEAD
sang commit H
. Chúng ta sẽ sử dụng lệnh:
git rebase --onto FDH
Nhánh của ta khi này sẽ trông giống như:
Before After A---B---C---F---G (branch) A---B---C---F---G (branch) \ | \ D---E---H---I (HEAD my-branch) | E'---H' (HEAD) \ D---E---H---I (my-branch)
Lúc này điều duy nhất thay đổi là ta không hoàn thành HEAD
mới trên commit I'
mà là trên commit H'
. Ta đang bỏ qua nơi HEAD
được trỏ đến và ta chọn một commit trước HEAD
cũ - commit H
. Hành vi tương đương chúng ta sẽ nhận được khi chúng ta thực hiện các lệnh sau:
git rebase --onto FDH git rebase --onto FD HEAD^ git rebase --onto FD HEAD ~ git rebase --onto FD HEAD~1
Xóa các xác nhận khỏi nhánh hiện tại
Đây là một giải pháp tốt. Khi bạn muốn nhanh chóng xóa một số commit khỏi nhánh hiện tại của mình mà không cần sử dụng rebase tương tác. Nếu ta có một nhánh và muốn xóa các commit C
và D
, thì ta có thể làm điều đó bằng cách sử dụng:
git rebase --onto B D
Kết quả:
Before After A---B---C---D---E---F (HEAD branch) A---B---E'---F' (HEAD branch)
Trong ví dụ này, ta nói rebase HEAD
ở trên cùng của commit B
, trong đó nhánh gốc cũ là một commit D
. Ta cũng nhận được hiệu ứng tương tự với lệnh:
git rebase --onto B D my-branch
Nếu ta sử dụng git rebase --onto
với ba đối số, trong đó đối số cuối cùng là mã định danh commit, thì tình huống sẽ hơi khác một chút. Chúng ta có thể nói rebase HEAD
ở trên cùng của commit B
, trong đó nhánh gốc cũ là một commit D
, nhưng chỉ để thực hiện E
và chuyển đổi HEAD
ở đó. Để đạt được điều này thì ta sẽ sử dụng lệnh:
git rebase --onto B D E
Ta sẽ nhận được nhánh mới chỉ với commit E'
với commit gốc B
.
Before After A---B---C---D---E---F (HEAD branch) A---B---C---D---E---F (branch) \ E' (HEAD)
Tóm tắt git rebase –onto
Chúng ta có thể gọi git rebase --onto
với hai hoặc ba đối số. Khi chúng ta sử dụng hai đối số, cú pháp chung sẽ như sau:
git rebase --onto <newparent> <oldparent>
Điều này sẽ cho phép thay đổi cha hiện tại <oldparent>
thành cha mới <newparent>
. Vì không chỉ định đối số thứ ba nên ta sẽ ở lại nhánh hiện tại của mình. Đối với git rebase ---onto
ba đối số, tình hình sẽ khác. Đây là cú pháp chung:
git rebase --onto <newparent> <oldparent> <until>
Lúc này thì ta có thể thay đổi cha cũ <oldparent>
thành cha mới <newparent>
, nhưng chúng ta sẽ chỉ thực hiện các commit cho đến khi commit <until>
. Nhớ rằng <until>
sẽ trở thành HEAD
mới sau khi rebase hoàn thành.