Chính xác thì những gì rebit của git --preserve-merges do do (và tại sao?)


355

Tài liệurebase của Git cho lệnh khá ngắn gọn:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

Vì vậy, những gì thực sự xảy ra khi bạn sử dụng --preserve-merges? Nó khác với hành vi mặc định (không có cờ đó) như thế nào? "Tái tạo" hợp nhất có nghĩa là gì, v.v.


20
Cảnh báo: bắt đầu với Git 2.18 (quý 2 năm 2018, 5 năm sau), git --rebase-mergescuối cùng sẽ thay thế cái cũ git --preserve-merges. Xem câu trả lời của tôi dưới đây
VonC

Câu trả lời:


464

Như với một rebase git bình thường, git với --preserve-mergesđầu tiên xác định danh sách các xác nhận được thực hiện trong một phần của biểu đồ cam kết, sau đó phát lại các cam kết đó trên đầu một phần khác. Sự khác biệt --preserve-mergesliên quan đến cam kết nào được chọn để phát lại và cách thức phát lại đó hoạt động cho các cam kết hợp nhất.

Để rõ ràng hơn về sự khác biệt chính giữa rebase bình thường và bảo toàn hợp nhất:

  • Rebase bảo toàn hợp nhất sẵn sàng phát lại (một số) cam kết hợp nhất, trong khi rebase bình thường hoàn toàn bỏ qua các cam kết hợp nhất.
  • Bởi vì nó sẵn sàng phát lại các cam kết hợp nhất, rebase bảo toàn hợp nhất phải xác định ý nghĩa của việc phát lại một cam kết hợp nhất và xử lý một số nếp nhăn thêm
    • Phần thú vị nhất, về mặt khái niệm, có lẽ là trong việc chọn những gì cha mẹ hợp nhất của cam kết mới nên là.
    • Phát lại các cam kết hợp nhất cũng yêu cầu kiểm tra rõ ràng các cam kết cụ thể ( git checkout <desired first parent>), trong khi rebase bình thường không phải lo lắng về điều đó.
  • Rebase bảo toàn hợp nhất xem xét một tập hợp cam kết nông hơn để phát lại:
    • Cụ thể, nó sẽ chỉ xem xét phát lại các cam kết được thực hiện kể từ khi (các) cơ sở hợp nhất gần đây nhất - tức là lần gần đây nhất hai nhánh chuyển hướng -, trong khi rebase bình thường có thể phát lại cam kết trở lại lần đầu tiên khi hai nhánh chuyển hướng.
    • Để tạm thời và không rõ ràng, tôi tin rằng đây cuối cùng là một phương tiện để sàng lọc lại "các cam kết cũ" đã được "kết hợp" vào một cam kết hợp nhất.

Đầu tiên tôi sẽ cố gắng mô tả "đủ chính xác" những gì rebase --preserve-mergeslàm, và sau đó sẽ có một số ví dụ. Tất nhiên người ta có thể bắt đầu với các ví dụ, nếu điều đó có vẻ hữu ích hơn.

Thuật toán trong "Tóm tắt"

Nếu bạn muốn thực sự đi sâu vào cỏ dại, hãy tải xuống nguồn git và khám phá tệp git-rebase--interactive.sh. (Rebase không phải là một phần của lõi C của Git, mà được viết bằng bash. Và, đằng sau hậu trường, nó chia sẻ mã với "rebase tương tác".)

Nhưng ở đây tôi sẽ phác họa những gì tôi nghĩ là bản chất của nó. Để giảm số lượng những điều cần suy nghĩ, tôi đã thực hiện một vài sự tự do. (vd

Đầu tiên, lưu ý rằng một rebase không bảo toàn hợp nhất là khá đơn giản. Nó ít nhiều:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Rebase --preserve-mergestương đối phức tạp. Đây là đơn giản như tôi đã có thể làm cho nó mà không mất những thứ có vẻ khá quan trọng:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebase với một --onto Cđối số nên rất giống nhau. Thay vì bắt đầu phát lại cam kết tại ĐẦU của B, thay vào đó, bạn bắt đầu phát lại cam kết tại ĐẦU của C. (Và sử dụng C_new thay vì B_new.)

ví dụ 1

Ví dụ: lấy biểu đồ cam kết

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m là một cam kết hợp nhất với cha mẹ E và G.

Giả sử chúng ta đã khởi động lại chủ đề (H) trên đỉnh của chủ (C) bằng cách sử dụng một rebase bình thường, không hợp nhất. (Ví dụ: chủ đề thanh toán; chủ đề rebase .) Trong trường hợp đó, git sẽ chọn các cam kết sau để phát lại:

  • chọn D
  • chọn E
  • chọn F
  • chọn G
  • chọn H

và sau đó cập nhật biểu đồ cam kết như vậy:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D 'là tương đương được phát lại của D, v.v.)

Lưu ý rằng hợp nhất m không được chọn để phát lại.

Nếu chúng ta thay vì làm một --preserve-mergesrebase của H trên đầu trang của C. (Ví dụ, kiểm tra chủ đề; rebase --preserve-hòa trộn tổng thể .) Trong trường hợp mới này, git sẽ chọn các cam kết sau đây để phát lại:

  • chọn D
  • chọn E
  • chọn F (vào D 'trong nhánh' subtopic ')
  • chọn G (vào F 'trong nhánh' subtopic ')
  • chọn Hợp nhất nhánh 'chủ đề' vào chủ đề
  • chọn H

Bây giờ m đã được chọn để phát lại. Cũng lưu ý rằng hợp nhất cha mẹ E và G đã được chọn để đưa vào trước khi hợp nhất cam kết m.

Dưới đây là biểu đồ cam kết kết quả:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Một lần nữa, D 'là phiên bản được chọn (tức là được tạo lại) của D. Tương tự cho E', v.v. Mọi cam kết không phải trên bản gốc đã được phát lại. Cả E và G (cha mẹ hợp nhất của m) đã được tạo lại thành E 'và G' để phục vụ như là cha mẹ của m '(sau khi rebase, lịch sử cây vẫn giữ nguyên).

Ví dụ 2

Không giống như rebase thông thường, rebase bảo toàn hợp nhất có thể tạo ra nhiều con của đầu dòng ngược.

Ví dụ: xem xét:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

Nếu chúng ta rebase H (topic) trên đầu C (master), thì các cam kết được chọn cho rebase là:

  • chọn D
  • chọn E
  • chọn F
  • chọn G
  • chọn m
  • chọn H

Và kết quả là như vậy:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Ví dụ 3

Trong các ví dụ trên, cả cam kết hợp nhất và hai cha mẹ của nó là các cam kết được phát lại, thay vì các cha mẹ ban đầu mà cam kết hợp nhất ban đầu có. Tuy nhiên, trong các cuộc nổi loạn khác, một cam kết hợp nhất được phát lại có thể kết thúc với các cha mẹ đã có trong biểu đồ cam kết trước khi hợp nhất.

Ví dụ: xem xét:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

Nếu chúng tôi phản hồi chủ đề lên chủ (bảo toàn các kết hợp), thì các cam kết phát lại sẽ là

  • chọn hợp nhất cam kết m
  • chọn F

Biểu đồ cam kết viết lại sẽ trông như vậy:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Ở đây, phát lại hợp nhất cam kết m 'có được các bậc cha mẹ tồn tại trước trong biểu đồ cam kết, cụ thể là D (TRƯỚC của chủ) và E (một trong các cha mẹ của cam kết hợp nhất ban đầu m).

Ví dụ 4

Rebase bảo toàn hợp nhất có thể bị lẫn lộn trong một số trường hợp "cam kết trống". Ít nhất điều này chỉ đúng với một số phiên bản cũ hơn của git (ví dụ 1.7.8.)

Lấy biểu đồ cam kết này:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Lưu ý rằng cả cam kết m1 và m2 nên kết hợp tất cả các thay đổi từ B và F.

Nếu chúng ta cố gắng thực hiện git rebase --preserve-mergesH (chủ đề) lên D (chính), thì các cam kết sau đây được chọn để phát lại:

  • chọn m1
  • chọn H

Lưu ý rằng các thay đổi (B, F) hợp nhất trong m1 nên được kết hợp vào D. (Những thay đổi đó đã được kết hợp thành m2, vì m2 hợp nhất với con của B và F.) Do đó, về mặt khái niệm, phát lại m1 trên đầu trang D có lẽ nên là một no-op hoặc tạo một cam kết trống (tức là một trong đó sự khác biệt giữa các phiên bản kế tiếp là trống rỗng).

Tuy nhiên, thay vào đó, git có thể từ chối nỗ lực phát lại m1 trên đầu D. Bạn có thể gặp lỗi như vậy:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

Có vẻ như người ta quên truyền cờ cho git, nhưng vấn đề tiềm ẩn là git không thích tạo ra các cam kết trống.


6
Tôi đã nhận thấy rằng git rebase --preserve-mergesnhiều chậm hơn so với một rebasekhông --preserve-merges. Đó có phải là tác dụng phụ của việc tìm kiếm các cam kết đúng không? Có điều gì người ta có thể làm để tăng tốc nó không? (Nhân tiện, cảm ơn vì câu trả lời rất chi tiết!)
David Alan Hjelle

7
Có vẻ như bạn nên luôn luôn sử dụng --preserve-merges. Nếu không, có khả năng mất lịch sử tức là hợp nhất cam kết.
DarVar

19
@DarVar Bạn luôn mất lịch sử trên một rebase, bởi vì bạn cho rằng những thay đổi được tạo ra cho một cơ sở mã khác với thực tế.
Chronial 4/12/13

5
Đây có còn là "câu trả lời tạm thời" không?
Andrew Grimm

5
@Chronial Tất nhiên là bạn đúng, việc nổi loạn luôn kết hợp mất lịch sử, nhưng có lẽ DarVar đã ám chỉ thực tế rằng bạn không chỉ mất lịch sử mà còn thay đổi cơ sở mã. Việc giải quyết xung đột chứa thông tin bị mất theo tất cả các cách có thể thực hiện một cuộc nổi loạn. Bạn luôn phải làm lại nó. Có thực sự không có cách nào, để cho phép git làm lại giải quyết xung đột của bạn? Tại sao không thể git cherry-pick cam kết hợp nhất?
Nils_M

94

Git 2.18 (quý 2 năm 2018) sẽ cải thiện đáng kể --preserve-mergetùy chọn bằng cách thêm tùy chọn mới.

" git rebase" Đã học " --rebase-merges" để ghép toàn bộ cấu trúc liên kết của đồ thị cam kết ở nơi khác .

(Lưu ý: Git 2.22, Q2 2019, thực sự không dùng nữa --preserve-merge và Git 2.25, Q1 2020, ngừng quảng cáo nó trong git rebase --helpđầu ra "" )

Xem cam kết 25cff9f , cam kết 7543f6f , cam kết 1131ec9 , cam kết 7ccdf65 , cam kết 537e7d6 , cam kết a9be29c , cam kết 8f6aed7 , cam kết 1644c73 , cam kết d1e8b01 , cam kết 4c68e7d , cam kết 9055e40 , cam kết cb5206e , cam kết a01c2a5 , cam kết 2f6b1d1 , cam kết bf5c057 (ngày 25 tháng 4 năm 2018) của tác giả Julian Schindelin ( dscho) .
Xem cam kết f431d73 (25 tháng 4 năm 2018) của Stefan Beller ( stefanbeller) .
Xem cam kết 2429335 (25 tháng 4 năm 2018) của Phillip Wood ( phillipwood) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 2c18e6a , ngày 23 tháng 5 năm 2018)

pull: chấp nhận --rebase-mergesđể tạo lại cấu trúc liên kết chi nhánh

Tương tự như preservechế độ chỉ đơn giản là truyền --preserve-merges tùy chọn cho rebaselệnh, mergeschế độ chỉ đơn giản là chuyển --rebase-mergestùy chọn.

Điều này sẽ cho phép người dùng thuận tiện khởi động lại các cấu trúc liên kết cam kết không tầm thường khi kéo các xác nhận mới, mà không làm phẳng chúng.


git rebasetrang man bây giờ có một phần đầy đủ dành riêng cho lịch sử nổi loạn với sự hợp nhất .

Trích xuất:

Có nhiều lý do chính đáng tại sao nhà phát triển có thể muốn tạo lại các cam kết hợp nhất: để giữ cấu trúc nhánh (hoặc "cấu trúc liên kết cam kết") khi làm việc trên nhiều nhánh liên quan đến nhau.

Trong ví dụ sau, nhà phát triển làm việc trên nhánh chủ đề tái cấu trúc các nút được xác định và trên nhánh chủ đề khác sử dụng phép tái cấu trúc đó để thực hiện nút "Báo cáo lỗi".
Đầu ra của git log --graph --format=%s -5có thể trông như thế này:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

Nhà phát triển có thể muốn khởi động lại các cam kết đó thành mới hơn master trong khi vẫn giữ cấu trúc liên kết nhánh, ví dụ như khi nhánh chủ đề đầu tiên được dự kiến ​​sẽ được tích hợp vào mastersớm hơn nhiều so với thứ hai, để giải quyết xung đột hợp nhất với các thay đổi đối với DownloadButtonlớp đã tạo nó vào master.

Rebase này có thể được thực hiện bằng cách sử dụng --rebase-mergestùy chọn.


Xem cam kết 1644c73 cho một ví dụ nhỏ:

rebase-helper --make-script: giới thiệu một lá cờ để hợp nhất rebase

Trình sắp xếp thứ tự chỉ học các lệnh mới nhằm tái tạo cấu trúc nhánh ( tương tự về tinh thần --preserve-merges, nhưng với thiết kế ít bị phá vỡ hơn ).

Hãy cho phép rebase--helpertạo danh sách việc cần làm bằng cách sử dụng các lệnh này, được kích hoạt bởi --rebase-mergestùy chọn mới .
Đối với một cấu trúc liên kết cam kết như thế này (trong đó CHÍNH chỉ đến C):

- A - B - C (HEAD)
    \   /
      D

danh sách việc cần làm sẽ như thế này:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

Sự khác biệt với là --preserve-mergegì?
Cam kết 8f6aed7 giải thích:

Ngày xửa ngày xưa, nhà phát triển ở đây nghĩ rằng: sẽ không hay nếu nói, các bản vá Git cho Windows trên lõi Git có thể được biểu diễn dưới dạng một nhánh dày và được đặt lại trên đỉnh Git để duy trì một bộ vá có thể chọn anh đào?

Nỗ lực ban đầu để trả lời điều này là : git rebase --preserve-merges.

Tuy nhiên, thí nghiệm đó không bao giờ được dự định là một tùy chọn tương tác và nó chỉ được hỗ trợ git rebase --interactivebởi vì việc triển khai lệnh đó trông rất, rất quen thuộc: nó được thiết kế bởi cùng một người đã thiết kế --preserve-merges: thật sự của bạn.

Và bởi "của bạn thật sự", tác giả đề cập đến chính mình: Johannes Schindelin ( dscho) , người là lý do chính (với một vài anh hùng khác - Hannes, Steffen, Sebastian, ...) rằng chúng tôi có Git For Windows (mặc dù trở lại trong ngày - 2009 - điều đó không dễ dàng ).
Anh ấy làm việc tại Microsoft từ tháng 9 năm 2015 , điều này hợp lý khi xem xét Microsoft hiện đang sử dụng rất nhiều Git và cần các dịch vụ của anh ấy. Xu hướng
đó thực sự bắt đầu vào năm 2013, với TFS . Kể từ đó, Microsoft quản lý kho Git lớn nhất hành tinh ! Và, kể từ tháng 10 năm 2018, Microsoft đã mua lại GitHub .

Bạn có thể thấy Johannes phát biểu trong video này cho Git Merge 2018 vào tháng 4 năm 2018.

Một thời gian sau, một số nhà phát triển khác (tôi đang nhìn bạn, Andreas! ;-)) đã quyết định rằng sẽ là một ý tưởng tốt khi cho phép --preserve-mergeskết hợp với --interactive(với sự cẩn thận!) Và người duy trì Git (tốt, người duy trì Git tạm thời trong thời gian Junio ​​vắng mặt, điều đó đã được đồng ý, và đó là khi sự quyến rũ của --preserve-mergesthiết kế bắt đầu tan rã khá nhanh chóng và vô duyên.

Ở đây Jonathan đang nói về Andreas Schwab từ Suse.
Bạn có thể thấy một số cuộc thảo luận của họ trở lại vào năm 2012 .

Nguyên nhân? Trong --preserve-mergeschế độ, cha mẹ của một cam kết hợp nhất (hoặc cho vấn đề đó, của bất kỳ cam kết nào ) không được nêu rõ ràng, nhưng được ngụ ý bởi tên cam kết được truyền cho picklệnh .

Điều này làm cho nó không thể, ví dụ, để sắp xếp lại các cam kết .
Không đề cập đến việc di chuyển các cam kết giữa các nhánh hoặc, thần cấm, để chia các nhánh chủ đề thành hai.

Than ôi, những thiếu sót này cũng ngăn cản chế độ đó (với mục đích ban đầu là phục vụ nhu cầu của Git cho Windows, với hy vọng bổ sung rằng nó cũng có thể hữu ích cho những người khác) để phục vụ nhu cầu của Git cho Windows.

Năm năm sau, khi nó trở nên thực sự không thể có được một loạt các bản vá lỗi lớn, khó sử dụng của các bản vá liên quan một phần, không liên quan một phần trong Git cho Windows, đôi khi đã bị từ chối bởi các thẻ Git cốt lõi (gây ra sự phẫn nộ không đáng có của nhà phát triển của git-remote-hgloạt game xấu số mà cách tiếp cận cạnh tranh của Git đối với Windows, chỉ bị bỏ rơi mà không có người bảo trì sau đó) thực sự không thể thực hiện được, " kéo cắt vườn Git " đã ra : một kịch bản, ủng hộ heo trên đỉnh của cuộc nổi loạn tương tác, đầu tiên sẽ xác định cấu trúc liên kết nhánh của các bản vá được khởi tạo lại, tạo một danh sách giả cần làm để chỉnh sửa thêm, chuyển đổi kết quả thành một danh sách việc cần làm thực sự (sử dụng rất nhiềuexec lệnh "thực hiện" các lệnh danh sách việc cần làm bị thiếu) và cuối cùng tạo lại chuỗi bản vá trên đầu trang của cam kết cơ sở mới.

(Kịch bản kéo cắt vườn Git được tham chiếu trong bản vá này trong cam kết 9055e40 )

Đó là vào năm 2013.
Và phải mất khoảng ba tuần để đưa ra thiết kế và thực hiện nó như một kịch bản ngoài luồng. Không cần phải nói, việc thực hiện cần một vài năm để ổn định, tất cả trong khi bản thân thiết kế đã chứng minh âm thanh của nó.

Với bản vá này, sự tốt đẹp của kéo cắt vườn Git git rebase -itự nó đến .
Vượt qua --rebase-mergestùy chọn sẽ tạo ra một danh sách việc cần làm có thể hiểu được một cách dễ dàng và rõ ràng làm thế nào để sắp xếp lại các cam kết .
Các nhánh mới có thể được giới thiệu bằng cách chèn labellệnh và gọi merge <label>.
Và một khi chế độ này sẽ trở nên ổn định và được chấp nhận rộng rãi, chúng ta có thể loại bỏ lỗi thiết kế đó--preserve-merges .


Git 2.19 (Q3 2018) cải thiện --rebase-mergestùy chọn mới bằng cách làm cho nó hoạt động với --exec.

Các " --exec" tùy chọn " git rebase --rebase-merges" đặt các lệnh exec tại địa điểm sai, đã được sửa chữa.

Xem cam kết 1ace63b (ngày 09 tháng 8 năm 2018) và cam kết f0880f7 (ngày 06 tháng 8 năm 2018) của tác giả Julian Schindelin ( dscho) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 750eb11 , ngày 20 tháng 8 năm 2018)

rebase --exec: làm cho nó hoạt động với --rebase-merges

Ý tưởng --execlà nối thêm một execcuộc gọi sau mỗi cuộc gọi pick.

Kể từ khi giới thiệu fixup!/ s quash!cam kết, ý tưởng này đã được mở rộng để áp dụng cho "pick, có thể theo sau là chuỗi sửa lỗi / squash", tức là một exec sẽ không được chèn giữa a pickvà bất kỳ dòng fixuphoặc tương ứng nào của nó squash.

Việc triển khai hiện tại sử dụng một mánh khóe bẩn thỉu để đạt được điều đó: nó giả định rằng chỉ có các lệnh pick / fixup / squash, sau đó chèn các execdòng trước bất kỳ pickngoại trừ đầu tiên và nối thêm một lệnh cuối cùng.

Với danh sách todo tạo ra bởi git rebase --rebase-merges, đây đơn giản chương trình thực hiện vấn đề của nó: nó tạo ra chính xác những điều sai khi có label, resetmergelệnh.

Hãy thay đổi cách thực hiện để thực hiện chính xác những gì chúng ta muốn: tìm kiếm các pickdòng, bỏ qua bất kỳ chuỗi sửa lỗi / squash nào, sau đó chèn exec dòng . Lót, rửa sạch, lặp lại.

Lưu ý: chúng tôi chịu khó chèn trước các dòng bình luận bất cứ khi nào có thể, vì các cam kết trống được thể hiện bằng các dòng chọn nhận xét (và chúng tôi muốn chèn một dòng exec của pick trước trước một dòng như vậy, không phải sau đó).

Trong khi ở đó, cũng thêm execcác dòng sau mergecác lệnh, bởi vì chúng giống nhau về tinh thần với pickcác lệnh: chúng thêm các xác nhận mới.


Git 2.22 (Q2 2019) sửa lỗi sử dụng refs / viết lại / phân cấp để lưu trữ trạng thái trung gian rebase, vốn đã tạo ra hệ thống phân cấp cho mỗi worktree.

Xem cam kết b9317d5 , cam kết 90d31ff , cam kết 09e6564 (07 tháng 3 năm 2019) của Nguyễn Thái Ngọc Duy ( pclouds) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 917f2cd , ngày 09 tháng 4 năm 2019)

Hãy chắc chắn rằng refs / viết lại / là trên mỗi worktree

a9be29c (sequencer: tạo các ref được tạo bởi labellệnh worktree-local, 2018-04-25, Git 2.19) thêm vào refs/rewritten/dưới dạng không gian tham chiếu trên mỗi worktree.
Thật không may (xấu của tôi) có một vài nơi cần cập nhật để đảm bảo rằng đó thực sự là một công việc.

- add_per_worktree_entries_to_dir()được cập nhật để đảm bảo danh sách giới thiệu nhìn vào per-worktree refs/rewritten/thay vì per-repo.

  • common_list[]được cập nhật để git_path()trả về vị trí chính xác. Điều này bao gồm " rev-parse --git-path".

Mớ hỗn độn này được tạo ra bởi tôi.
Tôi bắt đầu cố gắng khắc phục nó bằng việc giới thiệu refs/worktree,nơi tất cả các ref sẽ là per-worktree mà không cần các phương pháp điều trị đặc biệt.
Refs không may / viết lại đã đến trước refs / worktree vì vậy đây là tất cả những gì chúng ta có thể làm.


Với Git 2.24 (Q4 2019), " git rebase --rebase-merges" đã học cách điều khiển các chiến lược hợp nhất khác nhau và chuyển các tùy chọn cụ thể cho chiến lược cho chúng.

Xem cam kết 476998d (04 tháng 9 năm 2019) của Elijah Newren ( newren) .
Xem cam kết e1fac53 , cam kết a63f990 , cam kết 5dcdd74 , cam kết e145d99 , cam kết 4e6023b , cam kết f67336d , cam kết a9c7107 , cam kết b8c6f24 , cam kết d51b771 , cam kết c248d32 , cam kết 8c1e240 , cam kết 5efed0e , cam kết 68b54f6 , cam kết 2e7bbac , cam kết 6180b20 , cam kết d5b581f (31 Tháng 7 năm 2019) bởiJulian Schindelin ( dscho) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 917a319 , ngày 18 tháng 9 năm 2019)


Với Git 2.25 (Q1 2020), logic được sử dụng để phân biệt các giới thiệu toàn cầu của kho lưu trữ cục bộ và kho lưu trữ được cố định, để tạo điều kiện thuận lợi cho việc hợp nhất bảo toàn.

Xem cam kết f45f88b , cam kết c72fc40 , cam kết 8a64881 , cam kết 7cb8c92 , cam kết e536b1f (ngày 21 tháng 10 năm 2019) bởi SZEDER Gábor ( szeder) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết db806d7 , ngày 10 tháng 11 năm 2019)

path.c: không gọi matchhàm không có giá trị trongtrie_find()

Đã ký tắt: SZEDER Gábor

'Nhật ký / refs' không phải là đường dẫn dành riêng cho cây hoạt động, nhưng vì cam kết b9317d55a3 (Đảm bảo refs / viết lại / là per-worktree, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' đã trở lại đường dẫn không có thật nếu có dấu ' /':

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

Chúng tôi sử dụng triecấu trúc dữ liệu để quyết định một cách hiệu quả liệu một đường dẫn thuộc về thư mục chung hay là làm việc cụ thể theo cây.

Khi nó xảy ra, b9317d55a3 đã kích hoạt một lỗi cũ như triebản thân việc triển khai, được thêm vào 4e09cf2acf (" path: tối ưu hóa kiểm tra thư mục chung", 2015-08-31, Git v2.7.0-rc0 - hợp nhất được liệt kê trong lô # 2 ).

  • Theo nhận xét mô tả trie_find(), nó chỉ nên gọi hàm so khớp đã cho là 'fn' cho tiền tố kết thúc "/ -or- \ 0 của khóa mà bộ ba chứa giá trị".
    Điều này không đúng: có ba nơi trie_find () gọi hàm so khớp, nhưng một trong số đó thiếu kiểm tra sự tồn tại của giá trị.

  • b9317d55a3 đã thêm hai khóa mới vào trie:

    • ' logs/refs/rewritten' và
    • ' logs/refs/worktree', bên cạnh đã tồn tại ' logs/refs/bisect'.
      Điều này dẫn đến một trienút có đường dẫn ' logs/refs/', không tồn tại trước đó và không có giá trị kèm theo.
      Một truy vấn cho ' logs/refs/' tìm thấy nút này và sau đó nhấn vào một điểm gọi của matchhàm không kiểm tra sự tồn tại của giá trị và do đó gọi matchhàm với NULLgiá trị.
  • Khi matchhàm check_common()được gọi với một NULLgiá trị, nó trả về 0, cho biết đường dẫn được truy vấn không thuộc về thư mục chung, cuối cùng dẫn đến đường dẫn không có thật được hiển thị ở trên.

Thêm điều kiện còn thiếu để trie_find()nó sẽ không bao giờ gọi hàm so khớp với giá trị không tồn tại.

check_common() sau đó sẽ không còn phải kiểm tra xem nó có giá trị không phải là NULL hay không, vì vậy hãy loại bỏ điều kiện đó.

Tôi tin rằng không có con đường nào khác có thể gây ra đầu ra không có thật tương tự.

AFAICT chỉ có khóa khác dẫn đến chức năng khớp được gọi với NULLgiá trị là ' co' (vì các phím ' common' và ' config').

Tuy nhiên, vì chúng không nằm trong một thư mục thuộc về thư mục chung, nên sẽ có đường dẫn cụ thể cho cây làm việc.


3
Tôi nghĩ rằng đây nên là câu trả lời hàng đầu, --preserve-mergeskhông thực sự "bảo tồn" sự hợp nhất như bạn muốn, nó rất ngây thơ. Điều này cho phép bạn duy trì các cam kết hợp nhất và các mối quan hệ cam kết cha mẹ của họ trong khi cung cấp cho bạn tính linh hoạt của một rebase tương tác. Tính năng mới này là tuyệt vời và nếu không có câu trả lời SO được viết tốt này, tôi sẽ không biết!
egucciar

@egucciar Cảm ơn bạn. Và nó không phải là tính năng duy nhất của Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ) và Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC

1
Vô cùng hữu ích nếu bạn đang cố gắng di chuyển hàng loạt các cam kết như trong Q / A này, stackoverflow.com/questions/45059039/
Kẻ

1
Ồ, đó thực sự là những gì tôi đã tìm kiếm trong một thời gian! Tôi đã có một cách giải quyết thủ công cho các trường hợp như thế mà người ta sẽ tạo ra một cam kết hư cấu tham gia tất cả các sự hợp nhất.
carnicer

Git điển hình. Bạn không dám hỏi một câu hỏi đơn giản và rất có thể bạn phải tìm hiểu lịch sử, thuật toán nội bộ của Git, tất cả các chi tiết triển khai lộn xộn cộng với bạn cũng cần một chuyên ngành về lý thuyết đồ thị để hiểu những gì đang diễn ra.
Dimitris
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.