Làm thế nào để git-cherry-pick chỉ thay đổi đối với một số tệp nhất định?


587

Nếu tôi muốn hợp nhất vào một nhánh Git, những thay đổi chỉ được thực hiện đối với một số tệp được thay đổi trong một cam kết cụ thể bao gồm các thay đổi đối với nhiều tệp, làm thế nào để đạt được điều này?

Giả sử git commit gọi là stuffcó thay đổi file A, B, C, và Dnhưng tôi muốn kết hợp chỉ stuffthay đổi của các tập tin AB. Nghe có vẻ như một công việc cho git cherry-picknhưng cherry-pickchỉ biết làm thế nào để hợp nhất toàn bộ các cam kết, không phải là một tập hợp con của các tệp.

Câu trả lời:


689

Tôi sẽ làm điều đó với cherry-pick -n( --no-commit) cho phép bạn kiểm tra (và sửa đổi) kết quả trước khi cam kết:

git cherry-pick -n <commit>

# unstage modifications you don't want to keep, and remove the
# modifications from the work tree as well.
# this does work recursively!
git checkout HEAD <path>

# commit; the message will have been stored for you by cherry-pick
git commit

Nếu phần lớn các sửa đổi là những thứ bạn không muốn, thay vì kiểm tra các đường dẫn riêng lẻ (bước giữa), bạn có thể đặt lại mọi thứ trở lại, sau đó thêm vào những gì bạn muốn:

# unstage everything
git reset HEAD

# stage the modifications you do want
git add <path>

# make the work tree match the index
# (do this from the top level of the repo)
git checkout .

10
Ngoài ra, git checkout .tôi cũng khuyên bạn nên git clean -fxóa mọi tệp mới nhưng không mong muốn được giới thiệu bởi cam kết được chọn.
rlat

4
Lưu ý bổ sung cho phương pháp sau: Tôi sử dụng git add -pcho phép bạn quyết định tương tác những thay đổi bạn muốn thêm vào chỉ mục trên mỗi tệp
matthaeus

6
Điều này không tuyệt vời lắm trong trường hợp cam kết chọn anh đào không áp dụng cho bản sao làm việc hiện tại vì nó quá khác biệt, nhưng một tệp sẽ được áp dụng sạch.
Chuộc tội có giới hạn

3
Bạn cũng có thể unstage chọn lọc với git reset -p HEAD. Nó tương đương với add -pnhưng rất ít người biết rằng nó tồn tại.
Patrick Schlüter

1
Thủ thuật rất hữu ích. Tôi đã đặt nó trong một ý chính trong trường hợp ai đó cần nó như một kịch bản nhanh gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
Damiano

146

Các phương thức khác không hiệu quả với tôi vì cam kết có nhiều thay đổi và xung đột với nhiều tệp khác. Những gì tôi nghĩ ra chỉ đơn giản là

git show SHA -- file1.txt file2.txt | git apply -

Nó không thực sự là addcác tập tin hoặc thực hiện một cam kết cho bạn vì vậy bạn có thể cần phải theo dõi nó với

git add file1.txt file2.txt
git commit -c SHA

Hoặc nếu bạn muốn bỏ qua phần bổ sung, bạn có thể sử dụng --cachedđối số đểgit apply

git show SHA -- file1.txt file2.txt | git apply --cached -

Bạn cũng có thể làm điều tương tự cho toàn bộ thư mục

git show SHA -- dir1 dir2 | git apply -

2
Phương pháp thú vị, cảm ơn. Nhưng không show SHA -- file | applyvề cơ bản làm tương tự như checkout SHA -- filenhư trong câu trả lời Đánh dấu Longair của ?
Tobias Kienzler

4
Không, checkout SHA -- filesẽ kiểm tra chính xác phiên bản tại SHA, trong khi show SHA -- file | applysẽ chỉ áp dụng các thay đổi trong SHA (giống như cherry-pick). Sẽ có vấn đề nếu (a) có nhiều hơn một cam kết thay đổi tệp đã cho trong nhánh nguồn hoặc (b) có một cam kết thay đổi tệp trong nhánh mục tiêu hiện tại của bạn.
Michael Anderson

9
Chỉ tìm thấy một cách sử dụng tuyệt vời khác cho việc này: hoàn nguyên chọn lọc, khi bạn chỉ muốn hoàn nguyên một tệp (vì git reverthoàn tác toàn bộ cam kết). Trong trường hợp đó, chỉ cần sử dụnggit show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson

2
@RoeiBahumi có ý nghĩa hoàn toàn khác. git diff SHA -- file1.txt file2.txt | git apply -có nghĩa là áp dụng tất cả sự khác biệt giữa phiên bản hiện tại của tệp và phiên bản tại SHA cho phiên bản hiện tại. Về bản chất nó cũng giống như git checkout SHA -- file1.txt file2.txt. Xem bình luận trước đó của tôi để biết tại sao nó khác với git showphiên bản.
Michael Anderson

5
Trong trường hợp bạn phải giải quyết xung đột, hãy sử dụng git apply -3 -thay vì chỉ git apply -, sau đó nếu xảy ra xung đột, bạn có thể sử dụng kỹ thuật giải quyết xung đột tiêu chuẩn của mình, bao gồm cả việc sử dụng git mergetool.
qwertzguy

87

Tôi thường sử dụng -pcờ với thanh toán git từ nhánh khác mà tôi thấy dễ dàng và chi tiết hơn so với hầu hết các phương pháp khác mà tôi đã gặp.

Về nguyên tắc:

git checkout <other_branch_name> <files/to/grab in/list/separated/by/spaces> -p

thí dụ:

git checkout mybranch config/important.yml app/models/important.rb -p

Sau đó, bạn nhận được một hộp thoại hỏi bạn những thay đổi nào bạn muốn trong "các đốm màu" này khá giống với từng đoạn thay đổi mã liên tục mà sau đó bạn có thể báo hiệu y(Có) n(Không), v.v. cho mỗi đoạn mã.

Các -phoặc patchlựa chọn làm việc cho một loạt các lệnh trong git bao gồm git stash save -pcho phép bạn chọn những gì bạn muốn giấu từ công việc hiện tại của bạn

Đôi khi tôi sử dụng kỹ thuật này khi tôi đã thực hiện rất nhiều công việc và muốn tách nó ra và cam kết trong các cam kết dựa trên chủ đề nhiều hơn bằng cách sử dụng git add -pvà chọn những gì tôi muốn cho mỗi cam kết :)


3
Tôi thường xuyên sử dụng git-add -p, nhưng tôi không biết git-checkoutcũng có một -plá cờ - điều đó có khắc phục được các vấn đề hợp nhất -pcâu trả lời không có không?
Tobias Kienzler

1
ít nhất -psẽ cho phép chỉnh sửa thủ công cho một phần xung đột như vậy, điều cherry-picknày có lẽ cũng sẽ mang lại kết quả. Tôi sẽ kiểm tra điều này vào lần tới khi tôi cần nó, chắc chắn là một cách tiếp cận thú vị
Tobias Kienzler

2
Một trong hai câu trả lời hay nhất không giết chết các thay đổi đồng thời đối với các nhánh.
akostadinov

1
Xem câu trả lời này để biết cách chọn người đi xe đạp nào: stackoverflow.com/a/10605465/4816250 Đặc biệt tùy chọn 's' rất hữu ích.
jvd10

1
git reset -p HEADcũng cho phép -pcái có thể được bỏ tiện dụng khi bạn chỉ muốn xóa một số bản vá khỏi chỉ mục.
Patrick Schlüter

42

Có lẽ ưu điểm của phương pháp này so với câu trả lời của Jefromi là bạn không cần phải nhớ hành vi nào của git reset là đúng :)

 # Create a branch to throw away, on which we'll do the cherry-pick:
 git checkout -b to-discard

 # Do the cherry-pick:
 git cherry-pick stuff

 # Switch back to the branch you were previously on:
 git checkout -

 # Update the working tree and the index with the versions of A and B
 # from the to-discard branch:
 git checkout to-discard -- A B

 # Commit those changes:
 git commit -m "Cherry-picked changes to A and B from [stuff]"

 # Delete the temporary branch:
 git branch -D to-discard

2
cảm ơn câu trả lời của bạn. Bây giờ điều đó đã thôi thúc tôi suy nghĩ, tại sao không bỏ qua cherry-pickvà trực tiếp sử dụng git checkout stuff -- A B? Và với git commit -C stuffthông điệp cam kết cũng sẽ giữ nguyên
Tobias Kienzler

8
@Tobias: Điều đó sẽ chỉ hoạt động nếu các tệp được sửa đổi trên stuffchưa được sửa đổi trên nhánh hiện tại của bạn hoặc bất kỳ nơi nào giữa tổ tiên chung HEADstuffvà mẹo của stuff. Nếu họ có, sau đó cherry-picktạo kết quả chính xác (về cơ bản là kết quả của hợp nhất), trong khi phương thức của bạn sẽ loại bỏ các thay đổi trong nhánh hiện tại và giữ tất cả các thay đổi từ tổ tiên chung cho đến stuff- không chỉ các thay đổi trong đó cam kết duy nhất.
Nhãn dán

2
@Tobias Kienzler: Tôi đã giả định rằng điểm xuất phát của bạn đủ khác biệt so với phụ huynh về stuffkết quả của việc hái cherry sẽ để lại ABvới nội dung khác với nội dung của họ trong cam kết stuff. Tuy nhiên, nếu nó giống nhau, bạn đã đúng - bạn có thể làm như bạn nói.
Mark Longair

@Jeromi, @Mark: cảm ơn phản hồi của bạn, trong trường hợp của tôi, tôi đang xử lý các chi nhánh với các tệp hoàn toàn không phù hợp dẫn đến đề xuất của tôi. Nhưng thực sự tôi đã gặp rắc rối với nó sớm hay muộn, vì vậy cảm ơn bạn đã đưa ra điều này
Tobias Kienzler

Tôi nghĩ rằng câu trả lời của tôi trong chủ đề khác này có thể là những gì bạn đang theo đuổi.
Ian

30

Chọn anh đào là chọn những thay đổi từ một "cam kết" cụ thể. Giải pháp đơn giản nhất là chọn tất cả các thay đổi của một số tệp nhất định là sử dụng

 git checkout source_branch <paths>...

Trong ví dụ:

$ git branch
* master
  twitter_integration
$ git checkout twitter_integration app/models/avatar.rb db/migrate/20090223104419_create_avatars.rb test/unit/models/avatar_test.rb test/functional/models/avatar_test.rb
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   app/models/avatar.rb
#   new file:   db/migrate/20090223104419_create_avatars.rb
#   new file:   test/functional/models/avatar_test.rb
#   new file:   test/unit/models/avatar_test.rb
#
$ git commit -m "'Merge' avatar code from 'twitter_integration' branch"
[master]: created 4d3e37b: "'Merge' avatar code from 'twitter_integration' branch"
4 files changed, 72 insertions(+), 0 deletions(-)
create mode 100644 app/models/avatar.rb
create mode 100644 db/migrate/20090223104419_create_avatars.rb
create mode 100644 test/functional/models/avatar_test.rb
create mode 100644 test/unit/models/avatar_test.rb

Nguồn và giải thích đầy đủ http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

CẬP NHẬT:

Với phương pháp này, git sẽ không MERGE tệp, nó sẽ chỉ ghi đè bất kỳ thay đổi nào khác được thực hiện trên nhánh đích. Bạn sẽ cần hợp nhất các thay đổi theo cách thủ công:

$ git diff Tên tệp CHÍNH


5
Tôi cũng nghĩ vậy , nhưng điều này thất bại khủng khiếp nếu các tệp đã thay đổi trên cả hai nhánh vì nó loại bỏ những thay đổi của nhánh hiện tại của bạn
Tobias Kienzler

Bạn nói đúng, cần phải làm rõ rằng cách này git không MERGE, nó chỉ ghi đè. Sau đó, bạn có thể thực hiện "git diff tên tệp CHÍNH" để xem những gì đã thay đổi và thực hiện hợp nhất thủ công.
cminatti

18

Tình huống:

Bạn đang ở trong chi nhánh của mình, giả sử mastervà bạn có cam kết với bất kỳ chi nhánh nào khác. Bạn phải chọn chỉ một tệp từ cam kết cụ thể đó.

Tiếp cận:

Bước 1: Thanh toán trên chi nhánh yêu cầu.

git checkout master

Bước 2: Hãy chắc chắn rằng bạn đã sao chép hàm băm cam kết cần thiết.

git checkout commit_hash path\to\file

Bước 3: Bây giờ bạn có các thay đổi của tệp yêu cầu trên nhánh bạn muốn. Bạn chỉ cần thêm và cam kết chúng.

git add path\to\file
git commit -m "Your commit message"

1
Tuyệt vời! Cũng hoạt động cho tất cả các thay đổi trong một thư mục có \ path \ to \ thư mục \ cho tôi
zaggi

13

Tôi sẽ chọn anh đào mọi thứ, sau đó làm điều này:

git reset --soft HEAD^

Sau đó, tôi sẽ hoàn nguyên các thay đổi tôi không muốn, sau đó thực hiện một cam kết mới.


11

Sử dụng git merge --squash branch_nameđiều này sẽ nhận được tất cả các thay đổi từ các chi nhánh khác và sẽ chuẩn bị một cam kết cho bạn. Bây giờ loại bỏ tất cả các thay đổi không cần thiết và để lại những thay đổi bạn muốn. Và git sẽ không biết rằng đã có một sự hợp nhất.


Cảm ơn, tôi không biết về tùy chọn hợp nhất đó. Đó là một lựa chọn khả thi nếu bạn muốn hái cherry hầu hết toàn bộ chi nhánh (nhưng ngược lại với cherry-pick, nó sẽ không hoạt động nếu không có tổ tiên chung)
Tobias Kienzler

4

Tôi đã tìm thấy một cách khác để ngăn chặn bất kỳ sự hợp nhất mâu thuẫn nào trong việc chọn anh đào mà IMO là loại dễ nhớ và dễ hiểu. Vì bạn thực sự không chọn anh đào, nhưng là một phần của nó, bạn cần tách nó trước và sau đó tạo một cam kết phù hợp với nhu cầu của bạn và chọn anh đào.

Đầu tiên tạo một nhánh từ cam kết bạn muốn tách và kiểm tra nó:

$ git checkout COMMIT-TO-SPLIT-SHA -b temp

cam kết sau đó Hoàn nguyên theo thời gian:

$ git reset HEAD~1

Sau đó thêm các tệp / thay đổi bạn muốn chọn cherry:

$ git add FILE

và cam kết:

$ git commit -m "pick me"

lưu ý hàm băm cam kết, hãy gọi nó là PICK-SHA và quay trở lại chi nhánh chính của bạn, ví dụ, chủ nhân buộc phải thanh toán:

$ git checkout -f master

và cherry-pick cam kết:

$ git cherry-pick PICK-SHA

bây giờ bạn có thể xóa nhánh temp:

$ git branch -d temp -f

2

Hợp nhất một nhánh thành một nhánh mới (squash) và loại bỏ các tệp không cần thiết:

git checkout master
git checkout -b <branch>
git merge --squash <source-branch-with-many-commits>
git reset HEAD <not-needed-file-1>
git checkout -- <not-needed-file-1>
git reset HEAD <not-needed-file-2>
git checkout -- <not-needed-file-2>
git commit

2

Để hoàn thiện, điều làm việc tốt nhất với tôi là:

git show YOURHASH --no-color -- file1.txt file2.txt dir3 dir4 | git apply -3 --index -

git status

Nó thực hiện chính xác những gì OP muốn. Nó giải quyết xung đột khi cần thiết, tương tự như thế nào merge. Nó không addnhưng commitnhững thay đổi mới của bạn.


1

Bạn có thể dùng:

git diff <commit>^ <commit> -- <path> | git apply

Ký hiệu <commit>^chỉ định cha mẹ (đầu tiên) của <commit>. Do đó, lệnh diff này chọn các thay đổi được thực hiện <path>trong cam kết <commit>.

Lưu ý rằng điều này sẽ không cam kết bất cứ điều gì (như git cherry-pickvậy). Vì vậy, nếu bạn muốn điều đó, bạn sẽ phải làm:

git add <path>
git commit
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.