Git cherry pick vs rebase


120

Gần đây tôi đã bắt đầu làm việc với Git.

Xem qua sách Git trực tuyến, tôi tìm thấy những thứ sau trong phần "Git Rebase":

Với lệnh rebase, bạn có thể thực hiện tất cả các thay đổi đã được cam kết trên một nhánh và phát lại chúng trên một nhánh khác.

(Trích dẫn từ: http://git-scm.com/book/vi/Git-Branching-Rebasing )

Tôi nghĩ đây là định nghĩa chính xác của git cherry-pick (áp dụng lại một cam kết hoặc một tập hợp các đối tượng cam kết trên nhánh hiện đã được kiểm tra).

Sự khác biệt giữa hai là gì?

Câu trả lời:


166

Kể từ thời gian git cherry-pickđược học để có thể áp dụng nhiều cam kết, sự khác biệt thực sự đã trở nên tranh cãi, nhưng đây là thứ được gọi là sự tiến hóa hội tụ ;-)

Sự khác biệt thực sự nằm ở mục đích ban đầu để tạo ra cả hai công cụ:

  • git rebaseNhiệm vụ của nhiệm vụ là chuyển tiếp một loạt các thay đổi mà nhà phát triển có trong kho lưu trữ riêng tư của họ, được tạo dựa trên phiên bản X của một nhánh ngược dòng nào đó, sang phiên bản Y của cùng nhánh đó (Y> X). Điều này có hiệu quả thay đổi cơ sở của chuỗi cam kết đó, do đó "phục hồi".

    (Nó cũng cho phép nhà phát triển ghép một loạt cam kết vào bất kỳ cam kết tùy ý nào, nhưng điều này ít rõ ràng hơn.)

  • git cherry-picklà để mang lại một cam kết thú vị từ dòng phát triển này sang dòng phát triển khác. Một ví dụ cổ điển là sao lưu một bản sửa lỗi bảo mật được thực hiện trên một nhánh phát triển không ổn định sang một nhánh ổn định (bảo trì), điều này mergekhông có ý nghĩa gì, vì nó sẽ mang lại rất nhiều thay đổi không mong muốn.

    Kể từ lần xuất hiện đầu tiên, git cherry-pickđã có thể chọn nhiều cam kết cùng một lúc, từng cái một.

Do đó, có thể sự khác biệt nổi bật nhất giữa hai lệnh này là cách chúng xử lý nhánh mà chúng hoạt động: git cherry-pickthường đưa một cam kết từ một nơi khác và áp dụng nó trên nhánh hiện tại của bạn, ghi lại một cam kết mới , trong khi git rebaselấy nhánh hiện tại của bạn và viết lại một loạt các cam kết về mẹo của riêng nó theo cách này hay cách khác. Đúng vậy, đây là một mô tả sâu sắc về những gì git rebasecó thể làm, nhưng nó có chủ đích, để cố gắng làm cho ý tưởng chung đi sâu vào.

Cập nhật để giải thích thêm một ví dụ về việc sử dụng git rebaseđang được thảo luận.

Trước tình hình này,
trạng thái của repo trước khi phục hồi
Sách nói:

Tuy nhiên, có một cách khác: bạn có thể lấy bản vá của thay đổi đã được giới thiệu trong C3 và áp dụng lại nó trên C4. Trong Git, điều này được gọi là phục hồi. Với lệnh rebase, bạn có thể thực hiện tất cả các thay đổi đã được cam kết trên một nhánh và áp dụng chúng cho nhánh khác.

Trong ví dụ này, bạn sẽ chạy như sau:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

"Điểm nổi bật" ở đây là trong ví dụ này, nhánh "thử nghiệm" (đối tượng phục hồi) ban đầu được tách ra khỏi nhánh "chính" và do đó nó chia sẻ cam kết từ C0 đến C2 với nó - một cách hiệu quả, "thử nghiệm" là " master "lên đến, và bao gồm, C2 cộng với cam kết C3 ở trên nó. (Đây là trường hợp đơn giản nhất có thể xảy ra; tất nhiên, "thử nghiệm" có thể chứa hàng chục cam kết trên cơ sở ban đầu của nó.)

Bây giờ git rebaseđược yêu cầu căn cứ lại "thử nghiệm" vào mẹo hiện tại của "bậc thầy" và git rebasediễn ra như sau:

  1. Chạy git merge-baseđể xem cam kết cuối cùng được chia sẻ bởi cả "thử nghiệm" và "chủ" (nói cách khác, điểm chuyển hướng là gì). Đây là C2.
  2. Lưu tất cả các cam kết đã thực hiện kể từ điểm chuyển hướng; trong ví dụ đồ chơi của chúng tôi, nó chỉ là C3.
  3. Tua lại HEAD (trỏ đến cam kết mẹo của "thử nghiệm" trước khi hoạt động bắt đầu chạy) để trỏ đến mẹo của "chủ" - chúng tôi đang phục hồi nó.
  4. Cố gắng áp dụng từng cam kết đã lưu (như thể với git apply) theo thứ tự. Trong ví dụ đồ chơi của chúng tôi, nó chỉ là một cam kết, C3. Giả sử ứng dụng của nó sẽ tạo ra một cam kết C3 '.
  5. Nếu mọi việc suôn sẻ, tham chiếu "thử nghiệm" sẽ được cập nhật để trỏ đến cam kết có được từ việc áp dụng cam kết đã lưu cuối cùng (C3 'trong trường hợp của chúng tôi).

Bây giờ trở lại câu hỏi của bạn. Như bạn có thể thấy, ở đây thực sự về mặt kỹ thuật git rebase cấy ghép một loạt các cam kết từ "thử nghiệm" đến mẹo của "bậc thầy", vì vậy bạn có thể biết chính xác rằng thực sự có "một nhánh khác" trong quá trình này. Nhưng ý chính là cam kết mẹo từ "thử nghiệm" đã trở thành cam kết mẹo mới trong "thử nghiệm", nó chỉ thay đổi cơ sở của nó:
trạng thái sau khi hợp nhất

Một lần nữa, về mặt kỹ thuật, bạn có thể nói rằng git rebaseở đây đã kết hợp một số cam kết nhất định từ "chủ" và điều này hoàn toàn chính xác.


2
Cảm ơn. Tôi vẫn không hoàn toàn hiểu ý bạn ở đây. Trong cuốn sách, ví dụ được đưa ra rằng rebase áp dụng một loạt các cam kết mẹo từ một nhánh khác, trong khi bạn nói rằng đó là từ "cùng một nhánh". Hoặc có lẽ có một vài trường hợp nó hoạt động như thế nào?
lysergic acid

1
Cố gắng giải thích vấn đề bằng cách cập nhật câu trả lời của tôi.
kostix

98

Với cherry-pick, các cam kết / nhánh gốc sẽ bám xung quanh và các cam kết mới được tạo ra. Với rebase, toàn bộ nhánh được di chuyển với nhánh trỏ đến các cam kết được phát lại.

Giả sử bạn đã bắt đầu với:

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

Rebase:

$ git rebase master topic

Bạn lấy:

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

Cherry-hái:

$ git checkout master -b topic_new
$ git cherry-pick A^..C

Bạn lấy:

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

để biết thêm thông tin về git cuốn sách này có hầu hết nó (http://git-scm.com/book)


3
Trả lời tốt. Thông thường, bạn có thể muốn chọn cherry chỉ cam kết A và B nhưng để C treo trong những trường hợp đó, bạn có thể muốn giữ nhánh và chỉ chọn cherry chọn những thay đổi mà đồng nghiệp có thể cần thấy. Git được tạo ra để làm việc với mọi người, vì vậy nếu bạn không thấy lợi ích của điều gì đó khi làm việc một mình, nó thường được sử dụng phổ biến hơn khi làm việc trong các nhóm lớn hơn.
Pablo Jomer

Nếu thay vào đó, một rebase tương tác được thực hiện, bỏ đi một hoặc nhiều cam kết, thì cuối cùng bạn sẽ có những nhánh nào? nếu nó chỉ được topicphục hồi ở phía trên master, nó không chứa các cam kết bên trái, vậy chúng sẽ là một phần của nhánh nào?
Anthony

Tôi chỉ muốn nói thêm một điều nữa: nếu bạn git checkout topicvà sau git reset --hard C'khi hái anh đào, thì bạn sẽ có kết quả giống như sau khi phục hồi. Tôi đã tự cứu mình khỏi nhiều cuộc xung đột hợp nhất bằng cách sử dụng anh đào hái anh đào thay vì phục hồi, bởi vì tổ tiên chung là con đường trở lại.
sorrymissjackson

@anthony - stackoverflow.com/questions/11835948/… : theo như tôi hiểu thì chúng đã bị mất. Tôi không phải git-guru nhưng đây rebase/ cherry-picklà về tất cả các chi tiết gitmà tôi có một vấn đề khi hiểu.
thoni56

1
Các biểu đồ của bạn gây hại nhiều hơn lợi, bởi vì chúng giống hệt nhau về mặt chức năng. Sự khác biệt duy nhất là nhánh được tạo bởi git checkout -b, không liên quan gì git cherry-pick. Một cách tốt hơn để giải thích những gì bạn đang cố gắng nói sẽ là “bạn chạy git rebasetrên topicnhánh cây và vượt qua nó master; bạn chạy git cherry-picktrên masternhánh và vượt qua nó (cam kết từ) topic. ”
Rory O'Kane

14

Việc hái anh đào hoạt động cho những cam kết cá nhân .

Khi bạn giảm giá, nó sẽ áp dụng tất cả các cam kết trong lịch sử cho HEAD của chi nhánh bị thiếu ở đó.


Cảm ơn. Bạn có biết nếu chúng hoạt động giống nhau dưới vỏ bọc? (lưu trữ kết quả đầu ra trung gian của chúng vào tệp "vá", v.v.).
axit lysergic-acid

Afaik có. Nó áp dụng tất cả các bản vá từng cái một. Đó là lý do tại sao đôi khi bạn phải giải quyết xung đột hợp nhất ở giữa rebase trước khi tiếp tục.
iltempo

6
@iltempo, nó chỉ hoạt động cho các cam kết cá nhân trong các phiên bản cũ hơn của Git; tại thời điểm hiện tại, bạn có thể làm điều gì đó như git cherry-pick foo~3..foovà nhận các cam kết trên ngọn cây từ "foo" đã chọn từng cái một.
kostix

1
git-rebase sử dụng api giống như anh đào hái thực hiện trong codebase, iirc
thay thế

Tôi không nghĩ rằng chúng thực sự hoạt động giống nhau. Tôi đã thử khôi phục hàng nghìn cam kết và tôi nghĩ rằng git tạo ra một tệp hộp thư lớn và sau đó chạy git amtrên đó. Trong khi một lựa chọn anh đào áp dụng cam kết theo cam kết (có thể bằng cách tạo một hộp thư một tin nhắn cho mỗi bản vá). Rebase của tôi không thành công vì tệp hộp thư mà nó đang tạo hết dung lượng trên ổ đĩa, nhưng cherry-pick với cùng phạm vi sửa đổi đã thành công (và có vẻ chạy nhanh hơn).
onlynone

11

Một câu trả lời ngắn gọn:

  • git cherry-pick là "cấp thấp" hơn
  • Như vậy, nó có thể mô phỏng git rebase

Các câu trả lời được đưa ra ở trên là tốt, tôi chỉ muốn đưa ra một ví dụ để cố gắng chứng minh mối tương quan giữa chúng.

Không nên thay thế "git rebase" bằng chuỗi hành động này, nó chỉ là "một bằng chứng về khái niệm" mà tôi hy vọng sẽ giúp hiểu được cách mọi thứ hoạt động.

Đưa ra kho đồ chơi sau:

$ git log --graph --decorate --all --oneline
* 558be99 (test_branch_1) Test commit #7
* 21883bb Test commit #6
| * 7254931 (HEAD -> master) Test commit #5
| * 79fd6cb Test commit #4
| * 48c9b78 Test commit #3
| * da8a50f Test commit #2
|/
* f2fa606 Test commit #1

Giả sử, chúng tôi có một số thay đổi rất quan trọng (cam kết từ # 2 đến # 5) mà chúng tôi muốn đưa vào test_branch_1 của mình. Thông thường chúng ta chỉ chuyển sang một nhánh và thực hiện "git rebase master". Nhưng vì chúng tôi đang giả vờ rằng chúng tôi chỉ được trang bị "git cherry-pick", chúng tôi thực hiện:

$ git checkout 7254931                # Switch to master (7254931 <-- master <-- HEAD)
$ git cherry-pick 21883bb^..558be99   # Apply a range of commits (first commit is included, hence "^")    

Sau tất cả các hoạt động này, biểu đồ cam kết của chúng ta sẽ trông như thế này:

* dd0d3b4 (HEAD) Test commit #7
* 8ccc132 Test commit #6
* 7254931 (master) Test commit #5
* 79fd6cb Test commit #4
* 48c9b78 Test commit #3
* da8a50f Test commit #2
| * 558be99 (test_branch_1) Test commit #7
| * 21883bb Test commit #6
|/
* f2fa606 Test commit #1

Như chúng ta có thể thấy, các cam kết # 6 và # 7 đã được áp dụng cho 7254931 (một cam kết mẹo của bậc thầy). HEAD đã được di chuyển và chỉ ra một cam kết, về cơ bản, là một đầu của một nhánh giảm giá. Bây giờ, tất cả những gì chúng ta cần làm là xóa một con trỏ nhánh cũ và tạo một con trỏ mới:

$ git branch -D test_branch_1
$ git checkout -b test_branch_1 dd0d3b4

test_branch_1 hiện đã được root từ vị trí chính mới nhất. Làm xong!


Nhưng rebase cũng có thể mô phỏng git cherry-pick?
Number945 02/02/19

Kể từ khi cherry-pickcó thể áp dụng một loạt các cam kết, tôi nghĩ rằng, có. Mặc dù cách làm này hơi kỳ lạ, nhưng không có gì ngăn cản bạn chọn tất cả các cam kết trong nhánh tính năng của bạn ở trên cùng master, sau đó xóa nhánh tính năng và tạo lại sao cho nó trỏ đến đỉnh master. Bạn có thể nghĩ về git rebasemột chuỗi git cherry-pick feature_branch, git branch -d feature_branchgit branch feature_branch master.
raiks

7

Cả hai đều là lệnh để viết lại các cam kết của một nhánh lên trên nhánh khác: sự khác biệt là ở nhánh nào - "của bạn" (hiện đã được kiểm tra HEAD) hoặc "của họ" (nhánh được chuyển làm đối số cho lệnh) - là các cơ sở cho viết lại này.

git rebasemất một khởi đầu cam kếtreplay của bạn cam kết là đến sau khi họ (thời gian bắt đầu cam kết).

git cherry-pickthực hiện một tập hợp các cam kếtphát lại các cam kết của họ như sau của bạn (của bạn HEAD).

Nói cách khác, hai lệnh, trong hành vi cốt lõi của chúng (bỏ qua các đặc điểm hiệu suất khác nhau, quy ước gọi và tùy chọn nâng cao), đối xứng : kiểm tra nhánh barvà chạy git rebase foođặt barnhánh về cùng lịch sử như kiểm tra nhánh foovà chạy git cherry-pick ..barsẽ thiết lập foothành (các thay đổi từ foo, theo sau là các thay đổi từ bar).

Đặt tên khôn ngoan, sự khác biệt giữa hai lệnh có thể được ghi nhớ ở chỗ mỗi lệnh mô tả những gì nó làm với nhánh hiện tại : rebaselàm cho đầu kia làm cơ sở mới cho các thay đổi của bạn, trong khi cherry-pickchọn các thay đổi từ nhánh khác và đặt chúng lên trên của bạnHEAD (như quả anh đào trên bánh su).


1
Tôi không thể hiểu bất kỳ câu trả lời nào ngoại trừ câu trả lời của bạn! Nó ngắn gọn và có ý nghĩa hoàn hảo mà không cần từ ngữ thừa.
tân

4

Cả hai đều làm những việc rất giống nhau; sự khác biệt về khái niệm chính là (theo thuật ngữ đơn giản) rằng:

  • rebase di chuyển các commit từ nhánh hiện tại sang nhánh khác .

  • cherry-pick bản sao cam kết từ một nhánh khác đến nhánh hiện tại .

Sử dụng sơ đồ tương tự như câu trả lời của @Kenny Ho :

Với trạng thái ban đầu này:

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

... và giả sử rằng bạn muốn nhận các cam kết từ topicnhánh được phát lại trên đầu masternhánh hiện tại , bạn có hai tùy chọn:

  1. Sử dụng rebase: Trước tiên bạn phải topicthực hiện bằng cách thực hiện git checkout topic, sau đó di chuyển nhánh bằng cách chạy git rebase master, tạo:

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

    Kết quả: chi nhánh hiện tại của bạn topicđã được phục hồi (chuyển) lên master.
    Các topicchi nhánh đã được cập nhật, trong khi masterchi nhánh vẫn tại chỗ.

  2. Sử dụng cherry-pick : trước tiên bạn sẽ đến masterbằng cách thực hiện git checkout master, sau đó sao chép nhánh bằng cách chạy git cherry-pick topic~3..topic(hoặc, tương đương, git cherry-pick B..G), tạo ra:

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

    Kết quả: các cam kết từ topicđã được sao chép vào master.
    Các masterchi nhánh đã được cập nhật, trong khi topicchi nhánh vẫn tại chỗ.


Tất nhiên, ở đây bạn phải nói rõ ràng với cherry-pick để chọn một chuỗi các cam kết , sử dụng ký hiệu phạm vi foo..bar . Nếu bạn chỉ chuyển qua tên nhánh, như trong git cherry-pick topic, nó sẽ chỉ chọn cam kết ở đầu nhánh, dẫn đến:

A---B---C---D---G' master
     \
      E---F---G topic
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.