Tôi không thể hiểu hành vi của git rebase --onto


167

Tôi đã nhận thấy rằng hai khối lệnh git sau có hành vi khác nhau và tôi không hiểu tại sao.

Tôi có một Avà một Bnhánh phân kỳ với mộtcommit

---COMMIT--- (A)
\
 --- (B)

Tôi muốn rebase Bchi nhánh cuối cùng A(và có cam kết trên Bchi nhánh)

---COMMIT--- (A)
         \
          --- (B)

Không có vấn đề gì nếu tôi làm:

checkout B
rebase A

Nhưng nếu tôi làm:

checkout B
rebase --onto B A

Nó hoàn toàn không hoạt động, không có gì xảy ra. Tôi không hiểu tại sao hai hành vi khác nhau.

Phpstorm git client sử dụng cú pháp thứ hai và do đó dường như tôi hoàn toàn bị hỏng, đó là lý do tại sao tôi yêu cầu vấn đề cú pháp này.


Câu trả lời:


410

tl; dr

Cú pháp chính xác để rebase Btrên đầu Asử dụng git rebase --ontotrong trường hợp của bạn là:

git checkout B
git rebase --onto A B^

hoặc rebase Btrên đầu Abắt đầu từ cam kết là cha mẹ củaB tham chiếu với B^hoặc B~1.

Nếu bạn quan tâm đến sự khác biệt giữa git rebase <branch>git rebase --onto <branch>đọc tiếp.

Nhanh: git rebase

git rebase <branch>sẽ rebase nhánh bạn hiện đã kiểm tra ra, tham chiếu bởi HEAD, trên đỉnh mới nhất cam kết rằng có thể truy cập từ <branch>nhưng không khỏi HEAD.
Đây là trường hợp phổ biến nhất của việc nổi loạn và được cho là trường hợp đòi hỏi ít kế hoạch trước.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

Trong ví dụ này, FGlà các cam kết có thể truy cập từ branchnhưng không phải từ HEAD. Nói git rebase branchsẽ mất D, đó là cam kết đầu tiên sau điểm phân nhánh và khởi động lại nó (tức là thay đổi cha mẹ của nó ) trên đầu trang của cam kết mới nhất có thể tiếp cận từ branchnhưng không phải từ HEADđó G.

Chính xác: git rebase --onto với 2 đối số

git rebase --ontocho phép bạn rebase bắt đầu từ một cam kết cụ thể . Nó cấp cho bạn quyền kiểm soát chính xác đối với những gì đang bị phản đối và ở đâu. Điều này là cho các kịch bản mà bạn cần phải chính xác.

Ví dụ, hãy tưởng tượng rằng chúng ta cần phải nổi loạn HEADchính xác trên đầu Fbắt đầu từ E. Chúng tôi chỉ quan tâm đến việc đưa Fvào chi nhánh hoạt động của mình, đồng thời, chúng tôi không muốn giữ Dvì nó chứa một số thay đổi không tương thích.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

Trong trường hợp này, chúng tôi sẽ nói git rebase --onto F D. Điều này có nghĩa là:

Rebase cam kết có thể truy cập từ HEADcha mẹ của bạn là Dtrên F.

Nói cách khác, thay đổi cha mẹ của Etừ Dthành F. Cú pháp của git rebase --ontolà sau đó git rebase --onto <newparent> <oldparent>.

Một kịch bản khác mà điều này có ích là khi bạn muốn nhanh chóng loại bỏ một số cam kết khỏi nhánh hiện tại mà không phải thực hiện một rebase tương tác :

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

Trong ví dụ này, để loại bỏ CEra khỏi chuỗi bạn sẽ nói git rebase --onto B E, hoặc rebase HEADtrên đầu trang của Bcha mẹ cũ E.

Bác sĩ phẫu thuật: git rebase --onto với 3 đối số

git rebase --ontocó thể tiến thêm một bước về độ chính xác. Trong thực tế, nó cho phép bạn khởi động lại một loạt các cam kết tùy ý trên một phạm vi khác.

Đây là một ví dụ:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

Trong trường hợp này, chúng tôi muốn đánh lại phạm vi chính xác E---Hở trên cùng F, bỏ qua nơi HEADhiện đang trỏ đến. Chúng ta có thể làm điều đó bằng cách nói git rebase --onto F D H, có nghĩa là:

Rebase phạm vi của các cam kết có cha mẹ Dlên đến Hđỉnh F.

Cú pháp git rebase --ontovới một loạt các cam kết sau đó trở thành git rebase --onto <newparent> <oldparent> <until>. Thủ thuật ở đây là nhớ rằng cam kết được tham chiếu bởi <until>được bao gồm trong phạm vi và sẽ trở thành mới HEADsau khi quá trình rebase hoàn tất.


1
Câu trả lời tốt đẹp. Chỉ là một bổ sung nhỏ cho trường hợp chung: <oldparent>Tên bị hỏng nếu hai phần của phạm vi nằm trên các nhánh khác nhau. Nói chung là: "Bao gồm mọi cam kết có thể truy cập được <until>nhưng loại trừ mọi cam kết có thể truy cập được <oldparent>."
musiKk

50
git rebase --onto <newparent> <oldparent>là lời giải thích tốt nhất về hành vi --onto tôi đã thấy!
ronkot

4
Cảm ơn! Tôi đã hơi vật lộn với --ontolựa chọn này nhưng điều này làm cho nó rõ ràng! Tôi thậm chí không hiểu nó như thế nào tôi không thể hiểu nó trước đây: D Cảm ơn vì "hướng dẫn" tuyệt vời :-)
grongor

3
Trong khi câu trả lời này là tuyệt vời, tôi cảm thấy nó không bao gồm tất cả các trường hợp có thể. Dạng cú pháp cuối cùng cũng có thể được sử dụng để diễn tả một kiểu rebase tinh tế hơn. Lấy ví dụ trong Pro Git (2nd Ed.), D không nhất thiết phải là tổ tiên của H. Thay vào đó, D và H cũng có thể được cam kết với một tổ tiên chung - trong trường hợp này, Git sẽ tìm ra tổ tiên chung của họ và phát lại từ tổ tiên đó đến H lên F.
Pastafarian

1
Điều này rất hữu ích. Trang người đàn ông không giải thích tất cả các đối số.
a544jh

61

Đây là tất cả những gì bạn cần biết để hiểu --onto:

git rebase --onto <newparent> <oldparent>

Bạn đang chuyển đổi cha mẹ trên một cam kết, nhưng bạn không cung cấp sha của cam kết, chỉ là sha của cha mẹ hiện tại (cũ).


4
Ngắn gọn và dễ dàng. Trên thực tế, việc nhận ra rằng tôi phải cung cấp cho cha mẹ của cam kết mà tôi muốn phản đối thay vì cam kết đó đã khiến tôi mất nhiều thời gian nhất.
Antoniossss

1
Một chi tiết quan trọng là bạn chọn một đứa con của một ông già từ chi nhánh hiện tại bởi vì một cam kết có thể là cha mẹ đối với nhiều cam kết nhưng khi bạn giới hạn bản thân ở chi nhánh hiện tại thì cam kết chỉ có thể là cha mẹ đối với một cam kết. Nói cách khác, tàu quan hệ cha mẹ là duy nhất trên nhánh nhưng không phải là nếu bạn không chỉ định nhánh.
Trismegistos

2
Để lưu ý: Bạn cần ở trong nhánh hoặc thêm tên nhánh làm tham số thứ 3 git rebase --onto <newparent> <oldparent> <Feature-Branch>
Jason Portnoy

1
Câu trả lời này thật tuyệt vời, đi thẳng vào vấn đề cần thiết trong chủ đề này
John Culviner

13

Đặt ngắn gọn, đưa ra:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

Điều này giống như (vì --ontolấy một đối số):

git rebase D H --onto F

Phương tiện rebase cam kết trong phạm vi (D, H] trên đầu trang của F. Thông báo phạm vi là trái tay độc quyền. Đó là độc quyền bởi vì nó dễ dàng hơn để xác định 1 cam kết bằng cách gõ ví dụ branchđể cho gitthấy ngày 1 tách ra cam kết từ branchví dụ Dmà dẫn đến H.

Vỏ OP

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

Có thể thay đổi thành một lệnh duy nhất:

git rebase --onto B A B

Điều trông giống như lỗi ở đây là vị trí Bcó nghĩa là "di chuyển một số cam kết dẫn đến chi nhánh Btrên đầu B". Các câu hỏi là "một số cam kết" là gì. Nếu bạn thêm -icờ, bạn sẽ thấy đó là cam kết duy nhất được chỉ bởi HEAD. Cam kết bị bỏ qua vì nó đã được áp dụng cho --ontomục tiêu Bvà vì vậy không có gì xảy ra.

Lệnh này là vô nghĩa trong mọi trường hợp tên nhánh được lặp lại như thế. Điều này là do phạm vi của các xác nhận sẽ là một số xác nhận đã có trong nhánh đó và trong quá trình rebase, tất cả chúng sẽ bị bỏ qua.

Giải thích thêm và sử dụng git rebase <upstream> <branch> --onto <newbase>.

git rebase mặc định

git rebase master

Mở rộng thành một trong hai:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

Kiểm tra tự động sau khi rebase.

Khi được sử dụng theo cách tiêu chuẩn, như:

git checkout branch
git rebase master

Bạn sẽ không nhận thấy rằng sau khi rebase gitchuyển branchsang cam kết gần đây nhất và thực hiện git checkout branch(xem git refloglịch sử). Điều thú vị khi đối số thứ 2 là cam kết băm thay vì tên nhánh rebase vẫn hoạt động nhưng không có nhánh nào để di chuyển nên cuối cùng bạn sẽ bị "tách ra", thay vào đó được kiểm tra để di chuyển nhánh.

Bỏ qua cam kết chuyển hướng chính.

Các mastertrong --ontođược lấy từ git rebaseđối số 1 .

                   git rebase master
                              /    \
         git rebase --onto master master

Vì vậy, thực tế nó có thể là bất kỳ cam kết hoặc chi nhánh khác. Bằng cách này, bạn có thể giới hạn số lần xác nhận rebase bằng cách lấy những lần mới nhất và để lại các cam kết được phân kỳ chính.

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

Sẽ rebase cam kết duy nhất nhọn bởi HEADđến mastervà kết thúc trong "ĐẦU tách ra".

Tránh kiểm tra rõ ràng.

Mặc định HEADhoặc current_branchđối số được lấy theo ngữ cảnh từ vị trí bạn đang ở. Đây là lý do tại sao hầu hết mọi người thanh toán đến chi nhánh mà họ muốn phản đối. Nhưng khi đối số rebase thứ 2 được đưa ra một cách rõ ràng, bạn không cần phải kiểm tra trước khi rebase vượt qua nó theo cách ngầm định.

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

Điều này có nghĩa là bạn có thể rebase cam kết và các chi nhánh từ bất kỳ nơi nào . Vì vậy, cùng với thanh toán tự động sau khi rebase. bạn không phải kiểm tra riêng chi nhánh bị từ chối trước hoặc sau khi rebase.

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.

8

Nói một cách đơn giản, git rebase --ontochọn một loạt các xác nhận và khởi động lại chúng trên cam kết được cung cấp dưới dạng tham số.

Đọc các trang hướng dẫn git rebase, tìm kiếm "lên". Các ví dụ rất tốt:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

Trong trường hợp này bạn nói với git để rebase các cam kết từ topicAđể topicBtrên đầu trang của master.


7

Để hiểu rõ hơn về sự khác biệt giữa git rebasegit rebase --ontothật tốt khi biết các hành vi có thể có cho cả hai lệnh là gì. git rebasecho phép chúng tôi di chuyển các cam kết của chúng tôi trên đầu của chi nhánh được chọn. Giống như ở đây:

git rebase master

và kết quả là:

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontolà nhiều tiền đề. Nó cho phép chúng tôi chọn cam kết cụ thể nơi chúng tôi muốn bắt đầu và cũng là nơi chúng tôi muốn kết thúc. Giống như ở đây:

git rebase --onto F D

và kết quả là:

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)

Để biết thêm chi tiết, tôi khuyên bạn nên xem bài viết của riêng tôi về tổng quan về git rebase --onto


@Makyen Chắc chắn, tôi sẽ ghi nhớ nó trong tương lai :)
Womanonrails

Vì vậy, chúng ta có thể đọc git rebase --onto F Dcon của cha mẹ D là F , phải không?
Giải thưởng

2

Đối với ontobạn cần hai chi nhánh bổ sung. Với lệnh đó, bạn có thể áp dụng các xác nhận từ branchBđó dựa trên branchAmột nhánh khác, vd master. Trong mẫu dưới đây branchBdựa trên branchAvà bạn muốn áp dụng các thay đổi branchBtrên mastermà không áp dụng các thay đổi của branchA.

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

bằng cách sử dụng các lệnh:

checkout branchB
rebase --onto master branchA 

bạn sẽ nhận được sau phân cấp cam kết.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)

1
Bạn có thể vui lòng giải thích thêm một chút, nếu chúng tôi muốn nổi loạn lên chủ, làm thế nào để nó trở thành chi nhánh hiện tại? Nếu bạn làm điều rebase --onto branchA branchBđó sẽ đặt toàn bộ chi nhánh chủ trên đầu chi nhánhA?
polymerase

8
cái này không nên checkout branchB: rebase --onto master branchAsao?
Goldenratio

4
Tại sao điều này đã được nâng cấp? Điều này không làm những gì nó nói.
De Novo

Tôi đã chỉnh sửa và sửa câu trả lời, vì vậy trước tiên mọi người không cần phải phá các nhánh repo của họ và ngay sau đó hãy đến và đọc các bình luận ...
Kamafeather

0

Có một trường hợp khác git rebase --ontorất khó nắm bắt: khi bạn khởi động lại một cam kết dẫn đến bộ chọn chênh lệch đối xứng (ba dấu chấm '... ')

Git 2.24 (Q4 2019) thực hiện công việc quản lý trường hợp đó tốt hơn:

Xem cam kết 414d924 , cam kết 4effc5b , cam kết c0efb4c , cam kết 2b318aa (27 tháng 8 năm 2019) và cam kết 793ac7e , cam kết 359eceb (25 tháng 8 năm 2019) bởi Denton Liu ( Denton-L) .
Được giúp đỡ: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjorð Bjarmason ( avar) , và Julian Schindelin ( dscho) .
Xem cam kết 6330209 , cam kết c9efc21 (27 tháng 8 năm 2019) và cam kết 4336d36 (25 tháng 8 năm 2019) của Ævar Arnfjorð Bjarmason (avar ).
Được giúp đỡ: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjorð Bjarmason ( avar) , và Julian Junio ​​C Hamano - - trong cam kết 640f9cd , ngày 30 tháng 9 năm 2019)Julian Schindelin ( dscho) .
(Sáp nhập bởigitster

rebase: chuyển tiếp nhanh --ontotrong nhiều trường hợp

Trước đây, khi chúng ta có biểu đồ sau,

A---B---C (master)
     \
      D (side)

chạy ' git rebase --onto master... master side' sẽ dẫn đến Dluôn bị phản đối, không có vấn đề gì.

Tại thời điểm này, hãy đọc " Sự khác biệt giữa dấu chấm kép ' ..' và dấu ba chấm" là gì?... "trong phạm vi cam kết Git diff là gì? "

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

Ở đây: " master..." đề cập đến master...HEAD, đó là B: ĐẦU là phía TRƯỚC (hiện đã được kiểm tra): bạn đang khởi động lại B.
Bạn đang nổi loạn là gì? Bất kỳ cam kết không thành thạo và có thể truy cập từ sidechi nhánh: chỉ có một cam kết phù hợp với mô tả đó: D... đã ở trên đỉnh B!

Một lần nữa, trước Git 2.24, như vậy rebase --onto sẽ dẫn đến Dviệc luôn bị đánh bại, bất kể điều gì.

Tuy nhiên, hành vi mong muốn là rebase nên chú ý rằng điều này có thể chuyển tiếp nhanh và thay vào đó hãy thực hiện điều đó.

Đó là giống như rebase --onto B Acủa OP, không làm gì cả.

Thêm phát hiện để can_fast_forwardtrường hợp này có thể được phát hiện và chuyển tiếp nhanh sẽ được thực hiện.
Trước hết, viết lại hàm để sử dụng gotos giúp đơn giản hóa logic.
Tiếp theo, kể từ khi

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

điều kiện đã được loại bỏ trong cmd_rebase, chúng tôi giới thiệu lại một sự thay thế trong can_fast_forward.
Cụ thể, kiểm tra các cơ sở hợp nhất upstreamheadsửa một trường hợp không thành công t3416.

Biểu đồ viết tắt cho t3416 như sau:

        F---G topic
       /
  A---B---C---D---E master

và lệnh thất bại là

git rebase --onto master...topic F topic

Trước đây, Git sẽ thấy rằng có một cơ sở hợp nhất ( C, kết quả của master...topic) và việc hợp nhất và lên là như nhau để nó trả về 1 không chính xác, cho thấy rằng chúng tôi có thể chuyển tiếp nhanh. Điều này sẽ khiến biểu đồ bị đảo ngược là ' ABCFG' khi chúng ta đang mong đợi ' ABCG'.

A rebase --onto C F topiccó nghĩa là bất kỳ cam kết nào sau đó F , có thể truy cập bởi topicCHÍNH: đó là Gduy nhất, không phải Fchính nó.
Chuyển tiếp nhanh trong trường hợp này sẽ bao gồm Ftrong nhánh bị từ chối, đó là sai.

Với logic bổ sung, chúng tôi phát hiện rằng cơ sở hợp nhất ngược dòng và đầu là F. Vì không phải vậy F, điều đó có nghĩa là chúng tôi không từ chối toàn bộ các cam kết từ đó master..topic.
Vì chúng tôi không bao gồm một số cam kết, nên chuyển tiếp nhanh không thể thực hiện và vì vậy chúng tôi trả về 0 chính xác.

Thêm ' -f' vào các trường hợp thử nghiệm thất bại do thay đổi này vì họ không mong đợi một chuyển tiếp nhanh để bắt buộc một cuộc nổi loạn.

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.