Làm thế nào để anh đào chọn một loạt các cam kết và hợp nhất vào một chi nhánh khác?


640

Tôi có cách bố trí kho lưu trữ sau:

  • chi nhánh chính (sản xuất)
  • hội nhập
  • đang làm việc

Những gì tôi muốn đạt được là để anh đào chọn một loạt các cam kết từ nhánh làm việc và hợp nhất nó vào nhánh tích hợp. Tôi khá mới với git và tôi không thể tìm ra cách thực hiện chính xác điều này (việc chọn phạm vi cam kết trong một thao tác không phải là hợp nhất) mà không làm hỏng kho lưu trữ. Bất kỳ con trỏ hoặc suy nghĩ về điều này? Cảm ơn!


Câu trả lời:


808

Khi nói đến một loạt các cam kết, chọn anh đào đã không thực tế.

Như đề cập dưới đây bởi Keith Kim , Git 1.7.2+ giới thiệu khả năng anh đào-chọn một loạt các cam kết (nhưng bạn vẫn cần phải nhận thức được những hậu quả của cherry-chọn cho tương lai hợp nhất )

git cherry-pick "đã học cách chọn một loạt các cam kết
(ví dụ" cherry-pick A..B"và" cherry-pick --stdin"), do đó" git revert"; những điều này không hỗ trợ điều khiển trình tự đẹp hơn" rebase [-i]", mặc dù vậy.

bình luận damian và cảnh báo chúng tôi:

Ở dạng " cherry-pick A..B", Anên cũ hơnB .
Nếu chúng sai thứ tự, lệnh sẽ âm thầm thất bại .

Nếu bạn muốn chọn phạm vi Bthông qua D(bao gồm) đó sẽ là B^..D.
Xem " Git tạo chi nhánh từ phạm vi của các cam kết trước đó? " Như một minh họa.

Như Jubobs đề cập trong các ý kiến :

Điều này giả định rằng đó Bkhông phải là một cam kết gốc; bạn sẽ gặp unknown revisionlỗi "" nếu không.

Lưu ý: kể từ Git 2.9.x / 2.10 (quý 3 năm 2016), bạn có thể chọn một loạt các cam kết trực tiếp trên một nhánh mồ côi (đầu trống): xem " Cách biến nhánh hiện có thành một đứa trẻ mồ côi trong git ".


Câu trả lời gốc (tháng 1 năm 2010)

A rebase --ontosẽ tốt hơn, nơi bạn phát lại phạm vi cam kết nhất định trên đầu nhánh tích hợp của bạn, như Charles Bailey đã mô tả ở đây .
(ngoài ra, hãy tìm "Đây là cách bạn sẽ ghép một nhánh chủ đề dựa trên nhánh này sang nhánh khác" trong trang man gase rebase , để xem một ví dụ thực tế về git rebase --onto)

Nếu chi nhánh hiện tại của bạn là tích hợp:

# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration

Điều đó sẽ phát lại mọi thứ giữa:

  • sau cha mẹ của first_SHA-1_of_working_branch_range(vì thế ~1): cam kết đầu tiên bạn muốn phát lại
  • lên đến " integration" (chỉ ra cam kết cuối cùng bạn muốn phát lại, từ workingchi nhánh)

đến " tmp" (chỉ đến nơi integrationđã chỉ trước đó)

Nếu có bất kỳ xung đột nào khi một trong những cam kết đó được phát lại:

  • hoặc giải quyết nó và chạy " git rebase --continue".
  • hoặc bỏ qua bản vá này và thay vào đó chạy " git rebase --skip"
  • hoặc hủy bỏ tất cả mọi thứ với một " git rebase --abort" (và đặt lại integrationnhánh trên tmpnhánh)

Sau đó rebase --onto, integrationsẽ trở lại ở lần xác nhận cuối cùng của nhánh tích hợp (đó là " tmp" nhánh + tất cả các cam kết được phát lại)

Với việc hái anh đào hoặc rebase --onto, đừng quên nó có hậu quả đối với các lần sáp nhập tiếp theo, như được mô tả ở đây .


Một cherry-pickgiải pháp " " thuần túy được thảo luận ở đây và sẽ liên quan đến một cái gì đó như:

Nếu bạn muốn sử dụng phương pháp vá lỗi thì "git format-patch | git am" và "git cherry" là những lựa chọn của bạn.
Hiện tại, git cherry-pickchỉ chấp nhận một cam kết duy nhất, nhưng nếu bạn muốn chọn phạm vi Bthông qua Dđó sẽ là B^..Dtrong biệt ngữ git, vì vậy

git rev-list --reverse --topo-order B^..D | while read rev 
do 
  git cherry-pick $rev || break 
done 

Nhưng dù sao đi nữa, khi bạn cần "phát lại" một loạt các cam kết, từ "phát lại" sẽ thúc đẩy bạn sử dụng rebasetính năng "" của Git.


1
Nếu bạn có các xác nhận có cha mẹ yêu cầu -mtùy chọn, làm thế nào để bạn xử lý các cam kết đó? Hoặc có cách nào để lọc ra những cam kết này?
Tháng Tám

@aug -mcó nhiệm vụ xử lý chúng cho bạn, bằng cách chọn dòng chính được tham chiếu bởi -mtham số bạn đã chọn cho việc chọn anh đào này.
VonC

Vấn đề là nếu bạn chọn cherry một loạt các cam kết, thì cherry sẽ chọn cha mẹ xác nhận chính xác nhưng sau đó khi nó đạt được một cam kết bình thường, nó sẽ thất bại và nói rằng cam kết không phải là hợp nhất. Tôi đoán câu hỏi của tôi được đặt ra tốt hơn làm thế nào để làm cho nó vượt qua -mtùy chọn chỉ khi nó đạt được cam kết của phụ huynh khi phạm vi chọn anh đào? Ngay bây giờ nếu tôi vượt qua -mnhư git cherry-pick a87afaaf..asfa789 -m 1nó áp dụng cho tất cả các cam kết trong phạm vi.
Tháng Tám

@aug Lạ, tôi không tái tạo vấn đề. Phiên bản git của bạn là gì và whayt là thông báo lỗi chính xác mà bạn nhìn thấy?
VonC

Ah tôi đang chạy phiên bản git 2.6.4 (Apple Git-63). Các lỗi tôi thấy sẽ là một cái gì đó giống như error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.tôi thực sự nhận ra bạn chỉ có thể git cherry-pick --continuevà nó sẽ là tốt (nhưng nó sẽ không bao gồm phụ huynh cam kết)
Tháng Tám

136

Kể từ git v1.7.2, cherry pick có thể chấp nhận một loạt các cam kết:

git cherry-pickđã học cách chọn một loạt các cam kết (ví dụ cherry-pick A..Bcherry-pick --stdin), cũng vậy git revert; những cái này không hỗ trợ điều khiển trình tự đẹp hơn rebase [-i], mặc dù.


103
Lưu ý rằng cherry-pick A..Bsẽ không nhận được cam kết A (bạn sẽ cần A~1..Bđiều đó) và nếu có bất kỳ xung đột nào, git sẽ không tự động tiếp tục như rebase (ít nhất là từ 1.7.3.1)
Gabe Moothart

3
Thật tốt khi lưu ý rằng git cherry-pick A..B Ckhông hoạt động như bạn mong đợi, ngây thơ. Nó sẽ không chọn mọi thứ trong phạm vi A..Bvà cam kết C! Để làm điều này, bạn cần chia thành hai dòng, đầu tiên git cherry-pick A..Bvà sau đó git cherry-pick C. Vì vậy, bất cứ khi nào bạn có một phạm vi, bạn cần phải thực hiện nó một cách riêng biệt.
MicroVirus

42

Giả sử rằng bạn có 2 chi nhánh,

"BranchA": bao gồm các xác nhận bạn muốn sao chép (từ "commitA" sang "commitB"

"BranchB": nhánh bạn muốn các cam kết được chuyển từ "BranchA"

1)

 git checkout <branchA>

2) nhận ID của "commitA" và "commitB"

3)

git checkout <branchB>

4)

git cherry-pick <commitA>^..<commitB>

5) Trong trường hợp bạn có xung đột, hãy giải quyết nó và gõ

git cherry-pick --continue

để tiếp tục quá trình hái cherry.


Làm việc như quyến rũ! Cảm ơn rất nhiều!
snukone

28

Bạn có chắc chắn không muốn thực sự hợp nhất các chi nhánh? Nếu nhánh làm việc có một số cam kết gần đây mà bạn không muốn, bạn chỉ có thể tạo một nhánh mới với một ĐẦU ở điểm bạn muốn.

Bây giờ, nếu bạn thực sự muốn chọn một loạt các cam kết, vì bất kỳ lý do gì, một cách thanh lịch để làm điều này là chỉ cần kéo một bản vá và áp dụng nó vào nhánh tích hợp mới của bạn:

git format-patch A..B
git checkout integration
git am *.patch

Đây thực chất là những gì git-rebase đang làm, nhưng không cần phải chơi game. Bạn có thể thêm --3wayvào git-amnếu bạn cần hợp nhất. Đảm bảo không có tệp * .patch nào khác trong thư mục nơi bạn thực hiện việc này, nếu bạn làm theo hướng dẫn nguyên văn ...


1
Lưu ý rằng, giống như với các phạm vi sửa đổi khác, nó cần phải được A^đưa vào A.
Gino Mempin

9

Tôi đã bọc mã của VonC thành một tập lệnh bash ngắn git-multi-cherry-pick, để dễ chạy:

#!/bin/bash

if [ -z $1 ]; then
    echo "Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo "";
    echo "Usage:  $0 start^..end";
    echo "";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev 
do 
  git cherry-pick $rev || break 
done 

Tôi hiện đang sử dụng điều này khi tôi xây dựng lại lịch sử của một dự án có cả mã của bên thứ 3 và các tùy chỉnh được trộn lẫn với nhau trong cùng một thân cây svn. Bây giờ tôi đang phân tách mã bên thứ 3 cốt lõi, mô-đun của bên thứ 3 và các tùy chỉnh trên các nhánh git của riêng họ để hiểu rõ hơn về các tùy chỉnh trong tương lai. git-cherry-picklà hữu ích trong tình huống này vì tôi có hai cây trong cùng một kho lưu trữ, nhưng không có tổ tiên chung.


3

Tất cả các tùy chọn trên sẽ nhắc bạn giải quyết xung đột hợp nhất. Nếu bạn đang hợp nhất các thay đổi đã cam kết cho một nhóm, rất khó để giải quyết xung đột hợp nhất từ ​​các nhà phát triển và tiến hành. Tuy nhiên, "git merge" sẽ thực hiện hợp nhất trong một lần chụp nhưng bạn không thể vượt qua một loạt các sửa đổi làm đối số. chúng ta phải sử dụng các lệnh "git diff" và "git áp dụng" để thực hiện phạm vi hợp nhất của vòng quay. Tôi đã quan sát thấy rằng "git áp dụng" sẽ thất bại nếu tệp vá có khác biệt cho quá nhiều tệp, vì vậy chúng tôi phải tạo một bản vá cho mỗi tệp và sau đó áp dụng. Lưu ý rằng tập lệnh sẽ không thể xóa các tệp bị xóa trong nhánh nguồn. Đây là một trường hợp hiếm gặp, bạn có thể xóa các tệp đó khỏi nhánh mục tiêu theo cách thủ công. Trạng thái thoát của "git áp dụng" không bằng 0 nếu không thể áp dụng bản vá,

Dưới đây là kịch bản.

enter code here



  #!/bin/bash

    # This script will merge the diff between two git revisions to checked out branch
    # Make sure to cd to git source area and checkout the target branch
    # Make sure that checked out branch is clean run "git reset --hard HEAD"


    START=$1
    END=$2

    echo Start version: $START
    echo End version: $END

    mkdir -p ~/temp
    echo > /tmp/status
    #get files
    git --no-pager  diff  --name-only ${START}..${END} > ~/temp/files
    echo > ~/temp/error.log
    # merge every file
    for file in `cat  ~/temp/files`
    do
      git --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diff
      if [ $? -ne 0 ]
      then
#      Diff usually fail if the file got deleted 
        echo Skipping the merge: git diff command failed for $file >> ~/temp/error.log
        echo Skipping the merge: git diff command failed for $file
        echo "STATUS: FAILED $file" >>  /tmp/status
        echo "STATUS: FAILED $file"
    # skip the merge for this file and continue the merge for others
        rm -f ~/temp/git-diff
        continue
      fi

      git apply  --ignore-space-change --ignore-whitespace  --3way --allow-binary-replacement ~/temp/git-diff

      if [ $? -ne 0 ]
       then
#  apply failed, but it will fall back to 3-way merge, you can ignore this failure
         echo "git apply command filed for $file"
       fi
       echo
       STATUS=`git status -s $file`


       if [ ! "$STATUS" ]
       then
#   status is null if the merged diffs are already present in the target file
         echo "STATUS:NOT_MERGED $file"
         echo "STATUS: NOT_MERGED $file$"  >>  /tmp/status
       else
#     3 way merge is successful
         echo STATUS: $STATUS
         echo "STATUS: $STATUS"  >>  /tmp/status
       fi
    done

    echo GIT merge failed for below listed files

    cat ~/temp/error.log

    echo "Git merge status per file is available in /tmp/status"

2

Tôi đã thử nghiệm điều đó vài ngày trước, sau khi đọc lời giải thích rất rõ ràng về Vonc.

Bước của tôi

Khởi đầu

  • Chi nhánh dev : ABCDEFGHIJ
  • Chi nhánh target : ABCD
  • Tôi không muốn Evà cũng khôngH

Các bước để sao chép các tính năng mà không có bước E và H trong nhánh dev_feature_wo_E_H

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff Dnơi tôi đặt droptrước EH trong trình soạn thảo rebase
  • giải quyết xung đột, tiếp tục và commit

Các bước để sao chép các chi nhánh dev_feature_wo_E_Htrên mục tiêu.

  • git checkout target
  • git merge --no-ff --no-commit dev_feature_wo_E_H
  • giải quyết xung đột, tiếp tục và commit

Một số nhận xét

  • Tôi đã làm điều đó vì quá nhiều cherry-picktrong những ngày trước
  • git cherry-pick mạnh mẽ và đơn giản nhưng

    • nó tạo ra các bản sao cam kết
    • và khi tôi muốn mergegiải quyết xung đột của các cam kết ban đầu và các cam kết trùng lặp, vì vậy đối với một hoặc hai cherry-pick, bạn có thể "chọn anh đào" nhưng đối với nó thì quá dài dòng và chi nhánh sẽ trở nên quá phức tạp
  • Trong tâm trí tôi, các bước tôi đã làm rõ ràng hơn git rebase --onto

1
Bài đăng tốt. Nâng cao. Nó làm tôi nhớ đến stackoverflow.com/a/38418941/6309 . Tôi đã tóm lại những hạn chế của việc hái anh đào vào năm 2012: stackoverflow.com/a/13524494/6309 .
VonC

1

Một tùy chọn khác có thể là hợp nhất với chiến lược của chúng tôi với cam kết trước phạm vi và sau đó là hợp nhất 'bình thường' với cam kết cuối cùng của phạm vi đó (hoặc chi nhánh khi đó là cam kết cuối cùng). Vì vậy, giả sử chỉ có 2345 và 3456 cam kết của chủ được sáp nhập vào nhánh tính năng:

bậc thầy:
1234
2345
3456
4567

trong nhánh tính năng:

git merge -s chúng ta 4567
hợp nhất git 2345
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.