Làm cách nào để chọn lọc hợp nhất hoặc chọn thay đổi từ một chi nhánh khác trong Git?


1450

Tôi đang sử dụng git trên một dự án mới có hai nhánh phát triển song song - nhưng hiện đang thử nghiệm:

  • master: nhập mã cơ sở hiện có cộng với một vài mod mà tôi thường chắc chắn về
  • exp1: nhánh thử nghiệm # 1
  • exp2: nhánh thử nghiệm # 2

exp1exp2đại diện cho hai cách tiếp cận kiến ​​trúc rất khác nhau. Cho đến khi tôi đi xa hơn, tôi không có cách nào biết cái nào (nếu có) sẽ hoạt động. Khi tôi đạt được tiến bộ trong một nhánh, đôi khi tôi có các chỉnh sửa sẽ hữu ích ở nhánh khác và chỉ muốn hợp nhất các nhánh đó.

Cách tốt nhất để hợp nhất các thay đổi có chọn lọc từ nhánh phát triển này sang nhánh phát triển khác trong khi bỏ lại mọi thứ khác là gì?

Phương pháp tiếp cận tôi đã xem xét:

  1. git merge --no-commit tiếp theo là không chỉnh sửa thủ công một số lượng lớn các chỉnh sửa mà tôi không muốn phổ biến giữa các chi nhánh.

  2. Sao chép thủ công các tệp phổ biến vào một thư mục tạm thời theo sau git checkoutđể di chuyển sang nhánh khác và sau đó sao chép thủ công ra khỏi thư mục tạm thời vào cây làm việc.

  3. Một biến thể ở trên. Từ bỏ các expchi nhánh bây giờ và sử dụng hai kho lưu trữ cục bộ bổ sung cho thử nghiệm. Điều này làm cho việc sao chép thủ công các tập tin đơn giản hơn nhiều.

Tất cả ba cách tiếp cận này có vẻ tẻ nhạt và dễ bị lỗi. Tôi hy vọng có một cách tiếp cận tốt hơn; một cái gì đó giống với một tham số đường dẫn bộ lọc sẽ làm cho git-mergenhiều lựa chọn hơn.


5
Nếu các thay đổi trong các nhánh thử nghiệm của bạn được tổ chức tốt trong các cam kết riêng biệt, tốt hơn là bạn nên suy nghĩ về việc hợp nhất các cam kết chọn lọc thay vì các tệp chọn lọc. Hầu hết các câu trả lời dưới đây cho rằng đây là trường hợp.
akaihola

2
Sẽ không một sự kết hợp git merge -s ours --no-committheo sau bởi một số git read-treelà một giải pháp tốt cho việc này? Xem stackoverflow.com/questions/1214906/
Mạnh

34
Một câu hỏi gần đây hơn có câu trả lời một dòng, được viết tốt: stackoverflow.com/questions/10784523/ Lời
brahn

Hãy kiểm tra blog này để hợp nhất các tệp cụ thể chỉ jasonrudolph.com/blog/2009/02/25/ trên
Ashutosh Chamoli

Câu trả lời:


475

Bạn sử dụng lệnh cherry-pick để nhận các cam kết riêng lẻ từ một nhánh.

Nếu (các) thay đổi bạn muốn không nằm trong các cam kết riêng lẻ, thì hãy sử dụng phương thức hiển thị ở đây để phân chia cam kết thành các cam kết riêng lẻ . Nói một cách đơn giản, bạn sử dụng git rebase -iđể có được cam kết ban đầu để chỉnh sửa, sau đó git reset HEAD^chọn lọc hoàn nguyên các thay đổi, sau đó git commitcam kết bit đó như một cam kết mới trong lịch sử.

Có một phương pháp hay khác ở đây trong Tạp chí Red Hat, nơi họ sử dụng git add --patchhoặc có thể git add --interactivecho phép bạn thêm chỉ một phần của một hunk, nếu bạn muốn phân chia các thay đổi khác nhau cho một tệp riêng lẻ (tìm kiếm trong trang đó để "tách").

Sau khi phân chia các thay đổi, giờ đây bạn có thể chọn những thứ bạn muốn.


14
Theo hiểu biết của tôi, điều này không cần thiết phải phức tạp hơn câu trả lời được bình chọn cao hơn.
Alexander Bird

54
Về mặt kỹ thuật, đây là câu trả lời đúng, câu trả lời đúng có vẻ "phức tạp". --- Câu trả lời được bình chọn cao hơn chỉ là câu trả lời nhanh "bẩn" và bẩn thỉu, mà đối với hầu hết mọi người là tất cả những gì họ muốn (:
Jacob

3
@akaihola: TRƯỚC ^ là chính xác. Xem man git-rev-parse: Hậu tố ^ cho tham số sửa đổi có nghĩa là cha mẹ đầu tiên của đối tượng cam kết đó. Tiền tố ^ ký hiệu được sử dụng để loại trừ các xác nhận có thể truy cập từ một cam kết.
Tyler Rick

13
Tôi chỉ muốn chia sẻ một cách tiếp cận khác có vẻ sạch sẽ và ít gây khó chịu nhất trong tất cả: jasonrudolph.com/blog/2009/02/26/ trên tổng số đơn giản và tuyệt vời
superuseroi

14
Bối rối bởi các cuộc tranh luận về cách tiếp cận nào là 'chính xác'? Xem xét sự khác biệt giữa các tệp và cam kết (xem Ghi chú ở dưới cùng) . OP muốn hợp nhất PHIM & không đề cập đến CAM KẾT. Câu trả lời được bình chọn cao hơn là cụ thể cho các tập tin; câu trả lời được chấp nhận sử dụng cherry-pick, đặc trưng cho các cam kết. Cherry-pick có thể là chìa khóa để hợp nhất các cam kết có chọn lọc, nhưng có thể rất đau đớn khi di chuyển các tệp từ nhánh này sang nhánh khác. Mặc dù cam kết là trái tim của sức mạnh của git, nhưng đừng quên các tệp vẫn có vai trò!
Kay V

970

Tôi đã có cùng một vấn đề như bạn đã đề cập ở trên. Nhưng tôi thấy điều này rõ ràng hơn trong việc giải thích câu trả lời.

Tóm lược:

  • Kiểm tra (các) đường dẫn từ nhánh bạn muốn hợp nhất,

    $ git checkout source_branch -- <paths>...
    

    Gợi ý: Nó cũng hoạt động mà không --như thấy trong bài được liên kết.

  • hoặc để chọn lọc hợp nhất hunk

    $ git checkout -p source_branch -- <paths>...
    

    Hoặc, sử dụng thiết lập lại và sau đó thêm với tùy chọn -p,

    $ git reset <paths>...
    $ git add -p <paths>...
    
  • Cuối cùng cam kết

    $ git commit -m "'Merge' these changes"
    

9
Bài viết liên kết của Bart J là cách tiếp cận tốt nhất. Rõ ràng, đơn giản, một lệnh. Đây là cái tôi sắp sử dụng. :)
Pistos

256
Đây không phải là một sự hợp nhất thực sự. Bạn đang chọn các thay đổi theo tệp thay vì theo cam kết và bạn sẽ mất mọi thông tin cam kết hiện có (tác giả, tin nhắn). Cấp, điều này là tốt nếu bạn muốn hợp nhất tất cả các thay đổi trong một số tệp và bạn phải thực hiện lại tất cả các cam kết. Nhưng nếu các tệp chứa cả hai thay đổi để hợp nhất và các thay đổi khác để loại bỏ, một trong những phương pháp được cung cấp trong các câu trả lời khác sẽ phục vụ bạn tốt hơn.
akaihola

10
@mykhal và những người khác: điều này sẽ tự động tạo các tệp trong chỉ mục, vì vậy, nếu bạn đã kiểm tra, foo.chãy git reset HEAD foo.cbỏ qua tệp đó và sau đó bạn có thể tìm khác. Tôi đã tìm thấy điều này sau khi thử nó và quay lại đây để tìm câu trả lời cho điều này
michiakig

12
để xem những thay đổi bạn cũng có thể sử dụng:git diff --cached
OderWat

9
Theo câu trả lời git checkout -p <revision> -- <path> này sẽ giống như ban hành ba lệnh đầu tiên bạn mô tả :)
7hi4g0

338

Để chọn lọc hợp nhất các tệp từ nhánh này sang nhánh khác, hãy chạy

git merge --no-ff --no-commit branchX

nơi branchXlà chi nhánh bạn muốn kết hợp từ thành chi nhánh hiện hành.

Các --no-committùy chọn sẽ dàn dựng các tập tin đã được sáp nhập bởi Git mà không thực sự cam kết họ. Điều này sẽ cung cấp cho bạn cơ hội để sửa đổi các tệp được hợp nhất theo cách bạn muốn và sau đó tự cam kết chúng.

Tùy thuộc vào cách bạn muốn hợp nhất các tệp, có bốn trường hợp:

1) Bạn muốn hợp nhất thực sự.

Trong trường hợp này, bạn chấp nhận các tệp được hợp nhất theo cách Git tự động hợp nhất chúng và sau đó cam kết chúng.

2) Có một số tệp bạn không muốn hợp nhất.

Ví dụ: bạn muốn giữ lại phiên bản trong nhánh hiện tại và bỏ qua phiên bản trong nhánh bạn đang hợp nhất.

Để chọn phiên bản trong nhánh hiện tại, hãy chạy:

git checkout HEAD file1

Điều này sẽ lấy phiên bản của file1nhánh hiện tại và ghi đè lên file1tự động được Git.

3) Nếu bạn muốn phiên bản trong nhánhX (và không phải là hợp nhất thực sự).

Chạy:

git checkout branchX file1

Điều này sẽ lấy lại phiên bản file1trong branchXvà ghi đè file1tự động được sáp nhập bởi Git.

4) Trường hợp cuối cùng là nếu bạn muốn chỉ chọn hợp nhất cụ thể trong file1.

Trong trường hợp này, bạn có thể chỉnh sửa file1trực tiếp sửa đổi , cập nhật nó thành bất cứ điều gì bạn muốn phiên bản file1trở thành và sau đó cam kết.

Nếu Git không thể hợp nhất một tập tin tự động, nó sẽ báo cáo nội dung tập tin " Đã hủy " và tạo ra một bản sao, nơi bạn sẽ cần phải giải quyết mâu thuẫn bằng tay.



Để giải thích thêm với một ví dụ, giả sử bạn muốn hợp nhất branchXvào nhánh hiện tại:

git merge --no-ff --no-commit branchX

Sau đó, bạn chạy git statuslệnh để xem trạng thái của các tệp sửa đổi.

Ví dụ:

git status

# On branch master
# Changes to be committed:
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      file4
#

Trường hợp file1, file2file3các tệp git đã được tự động hợp nhất thành công.

Điều này có nghĩa là những thay đổi trong masterbranchXcho cả ba tệp này đã được kết hợp với nhau mà không có bất kỳ xung đột nào.

Bạn có thể kiểm tra cách hợp nhất được thực hiện bằng cách chạy git diff --cached;

git diff --cached file1
git diff --cached file2
git diff --cached file3

Nếu bạn tìm thấy một số hợp nhất không mong muốn thì bạn có thể

  1. chỉnh sửa tập tin trực tiếp
  2. tiết kiệm
  3. git commit

Nếu bạn không muốn hợp nhất file1và muốn giữ lại phiên bản trong nhánh hiện tại

Chạy

git checkout HEAD file1

Nếu bạn không muốn hợp nhất file2và chỉ muốn phiên bản trongbranchX

Chạy

git checkout branchX file2

Nếu bạn muốn file3được hợp nhất tự động, đừng làm gì cả.

Git đã hợp nhất nó vào thời điểm này.


file4ở trên là một sự hợp nhất thất bại của Git. Điều này có nghĩa là có những thay đổi trong cả hai nhánh xảy ra trên cùng một dòng. Đây là nơi bạn sẽ cần giải quyết các xung đột bằng tay. Bạn có thể loại bỏ việc hợp nhất được thực hiện bằng cách chỉnh sửa tệp trực tiếp hoặc chạy lệnh kiểm tra cho phiên bản trong nhánh bạn muốn file4trở thành.


Cuối cùng, đừng quên git commit.


10
Mặc dù cẩn thận: Nếu git merge --no-commit branchXchỉ là một chuyển tiếp nhanh, con trỏ sẽ được cập nhật và --no-commit sẽ âm thầm bị bỏ qua
cfi

16
@cfi Còn việc thêm vào --no-ffđể ngăn chặn hành vi đó thì sao?
Eduardo Costa

5
Tôi chắc chắn đề nghị cập nhật câu trả lời này với tùy chọn "--no-ff" của Eduardo. Tôi đọc tất cả mọi thứ (điều này thật tuyệt vời) chỉ để hợp nhất của tôi được chuyển tiếp nhanh chóng.
Funktr0n

7
Giải pháp này cho kết quả tốt nhất và linh hoạt.
Thiago Macedo

20
Không giống như câu trả lời có nhiều phiếu bầu nhất, giải pháp này bảo tồn lịch sử hợp nhất của tôi, điều này rất quan trọng đối với tôi khi tôi thực hiện một phần cam kết qua lại giữa các chi nhánh. Tôi đã không thử tất cả các giải pháp được đề xuất khác, vì vậy có lẽ một số trong số họ cũng làm điều này.
ws_e_c421

107

Tôi không thích cách tiếp cận trên. Sử dụng cherry-pick là tuyệt vời để chọn một thay đổi duy nhất, nhưng sẽ là một nỗi đau nếu bạn muốn mang lại tất cả các thay đổi ngoại trừ một số thay đổi xấu. Đây là cách tiếp cận của tôi.

Không có --interactiveđối số bạn có thể vượt qua để hợp nhất git.

Đây là cách thay thế:

Bạn có một số thay đổi trong nhánh 'tính năng' và bạn muốn mang một số nhưng không phải tất cả chúng để 'làm chủ' theo cách không cẩu thả (ví dụ: bạn không muốn chọn anh đào và cam kết từng người)

git checkout feature
git checkout -b temp
git rebase -i master

# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change

# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

git checkout master
git pull . temp
git branch -d temp

Vì vậy, chỉ cần gói nó trong một tập lệnh shell, thay đổi master thành $ to và thay đổi tính năng thành $ từ và bạn sẽ ổn:

#!/bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp

Tôi đã sửa định dạng - đây là một phương pháp khá hay, nếu bạn muốn thực hiện lựa chọn các cam kết
1800 THÔNG TIN

Tôi đang sử dụng kỹ thuật này và nó dường như đã hoạt động rất tốt.
dylanfm

4
Bạn có thể muốn thay đổi git rebase -i $tothành git rebase -i $to || $SHELL, để người dùng có thể gọi git --skip, v.v., nếu cần nếu rebase thất bại. Cũng có giá trị xâu chuỗi các dòng cùng với &&thay vì dòng mới.
sircolinton

2
Thật không may, nó xuất hiện các liên kết trong câu trả lời là chết.
ThomasW

Liên kết không chỉ chết mà còn có cảnh báo danh tiếng kém WOT. Vì vậy, tôi đã loại bỏ nó.
Jean-François Corbett

93

Có một cách khác để đi:

git checkout -p

Nó là sự pha trộn giữa git checkoutgit add -pcó thể chính xác là những gì bạn đang tìm kiếm:

   -p, --patch
       Interactively select hunks in the difference between the <tree-ish>
       (or the index, if unspecified) and the working tree. The chosen
       hunks are then applied in reverse to the working tree (and if a
       <tree-ish> was specified, the index).

       This means that you can use git checkout -p to selectively discard
       edits from your current working tree. See the “Interactive Mode”
       section of git-add(1) to learn how to operate the --patch mode.

10
Đây là phương pháp đơn giản nhất, đơn giản nhất, miễn là bạn chỉ có một số thay đổi có thể quản lý để hợp nhất. Tôi hy vọng nhiều người sẽ chú ý đến câu trả lời này và nâng cao nó. Ví dụ: kiểm tra git --patch exp1 file_to_merge
Tyler Rick

1
Câu trả lời tương tự được đăng trên câu hỏi này: stackoverflow.com/a/11593308/47185
Tyler Rick

Ồ, tôi không biết thanh toán có một bản vá! Tôi đã kiểm tra / thiết lập lại / thêm -p thay thế.
Daniel C. Sobral

2
Thực sự là phương pháp đơn giản nhất. kiểm tra git -p tên tập tin featurebranch. Và điều tốt nhất là khi lệnh được chạy, nó cung cấp cho bạn ay / n / e /? / ... vv. tùy chọn để quyết định làm thế nào để hợp nhất các tập tin. Tôi đã thử với e và tôi thậm chí có thể chỉnh sửa bản vá trước khi áp dụng ... Thật tuyệt vời. Một lớp lót thực sự để hợp nhất các tệp chọn lọc từ các nhánh khác.
đăng

55

Mặc dù một số câu trả lời này khá hay, tôi cảm thấy như không có câu trả lời nào thực sự ràng buộc ban đầu của OP: chọn các tệp cụ thể từ các nhánh cụ thể. Giải pháp này thực hiện điều đó, nhưng có thể tẻ nhạt nếu có nhiều tệp.

Hãy nói rằng bạn có master, exp1exp2các chi nhánh. Bạn muốn hợp nhất một tệp từ mỗi nhánh thử nghiệm thành chủ. Tôi sẽ làm một cái gì đó như thế này:

git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b

# save these files as a stash
git stash
# merge stash with master
git merge stash

Điều này sẽ cung cấp cho bạn các khác biệt trong tệp cho mỗi tệp bạn muốn. Chỉ có bấy nhiêu thôi. Không có gì ít hơn. Thật hữu ích khi bạn thay đổi hoàn toàn các tệp khác nhau giữa các phiên bản - trong trường hợp của tôi, thay đổi một ứng dụng từ Rails 2 sang Rails 3.

EDIT : điều này sẽ hợp nhất các tập tin, nhưng thực hiện hợp nhất thông minh. Tôi không thể tìm ra cách sử dụng phương pháp này để lấy thông tin khác biệt trong tệp (có thể nó vẫn sẽ có sự khác biệt lớn. Làm phiền những điều nhỏ như khoảng trắng được hợp nhất trở lại trừ khi bạn sử dụng -s recursive -X ignore-all-spacetùy chọn)


4
Cũng lưu ý: bạn có thể làm nhiều file từ một chi nhánh cung cấp tất cả nội tuyến, ví dụgit checkout exp1 path/to/file_a path/to/file_x
EMiller

2
Thật là đẹp Tôi đã làm git checkout feature <path>/*để có được các nhóm các tập tin.
isherwood

Điều này hoạt động tốt, nhưng thêm hai đối tượng cam kết thêm. Không phải là một thỏa thuận lớn nhưng hơi lộn xộn
MightyPork

@MightyPork bạn nói đúng. Thật không may, vì tôi đã viết bài này từ rất lâu rồi, tôi không còn biết tại sao các bước "git stash" và "git merge stash" lại ở đó thay vì "git commit".
Eric Hu

2
Ồ, điều đó rõ ràng, tôi nghĩ. Bằng cách này, nó hợp nhất một tệp, không nhất thiết phải ghi đè các thay đổi trước đó trên nhánh đích.
MightyPork

48

Câu trả lời của 1800 THÔNG TIN là hoàn toàn chính xác. Tuy nhiên, với tư cách là một git noob, "sử dụng git cherry-pick" không đủ để tôi tìm ra điều này mà không cần đào thêm một chút trên internet vì vậy tôi nghĩ rằng tôi đã đăng một hướng dẫn chi tiết hơn trong trường hợp có ai khác ở trong thuyền tương tự.

Trường hợp sử dụng của tôi là muốn chọn lọc kéo các thay đổi từ nhánh github của người khác vào của tôi. Nếu bạn đã có một chi nhánh địa phương với các thay đổi, bạn chỉ cần thực hiện bước 2 và 5-7.

  1. Tạo (nếu không được tạo) một nhánh cục bộ với những thay đổi bạn muốn đưa vào.

    $ git branch mybranch <base branch>

  2. Chuyển vào nó.

    $ git checkout mybranch

  3. Kéo xuống những thay đổi bạn muốn từ tài khoản của người khác. Nếu bạn chưa muốn thêm chúng dưới dạng điều khiển từ xa.

    $ git remote add repos-w-changes <git url>

  4. Kéo tất cả mọi thứ từ chi nhánh của họ.

    $ git pull repos-w-changes branch-i-want

  5. Xem nhật ký cam kết để xem những thay đổi bạn muốn:

    $ git log

  6. Chuyển về nhánh bạn muốn kéo các thay đổi vào.

    $ git checkout originalbranch

  7. Cherry chọn cam kết của bạn, từng người một, với băm.

    $ git cherry-pick -x hash-of-commit

Mũ lưỡi trai: http://www.sourcemage.org/Git_Guide


3
Mẹo: trước tiên hãy sử dụng git cherrylệnh (xem hướng dẫn trước) để xác định các cam kết bạn chưa hợp nhất.
akaihola

Điều này hoạt động .. 1. đã tạo một nhánh mới 2. tạo một số tệp / thực hiện một số thay đổi 3. cam kết 4. kiểm tra nhánh chính 5. Chạy git cherry-pick -x hash-of-commit và giải quyết xung đột hợp nhất là bạn tốt đi.
RamPrasadBismil

Liên kết của bạn không hoạt động nữa. Bạn có thể cập nhật nó được không?
creep3007

42

Đây là cách bạn có thể thay thế Myclass.javatập tin trong masterngành với Myclass.javatrong feature1chi nhánh. Nó sẽ hoạt động ngay cả khi Myclass.javakhông tồn tại master.

git checkout master
git checkout feature1 Myclass.java

Lưu ý điều này sẽ ghi đè - không hợp nhất - và bỏ qua các thay đổi cục bộ trong nhánh chính.


6
Điều này sẽ không hợp nhất. Nó sẽ chỉ ghi đè lên các thay đổi trên bản gốc với các thay đổi từ nhánh Feature1.
Skunkwaff

3
Hoàn hảo, tôi đã tìm kiếm loại hợp nhất này trong đó theirsghi đè ours=> +1 Chúc mừng;)
olibre

1
Đôi khi tất cả những gì bạn muốn làm là thay thế toàn bộ tệp, vì vậy đây là điều tôi muốn, nhưng bạn cần chắc chắn rằng bạn muốn mất tất cả những thay đổi bạn đã thực hiện đối với tệp này.
MagicLAMP

1
Giải pháp sạch nhất, cho rằng OP đặc biệt muốn thay thế toàn bộ tệp bằng tương đương trên một nhánh khác:2. Manual copying of common files into a temp directory followed by ...copying out of the temp directory into the working tree.
Brent Faust

29

Cách đơn giản, để thực sự hợp nhất các tệp cụ thể từ hai nhánh, không chỉ thay thế các tệp cụ thể bằng các tệp từ nhánh khác.

Bước một: Khác biệt các chi nhánh

git diff branch_b > my_patch_file.patch

Tạo một tệp vá của sự khác biệt giữa nhánh hiện tại và nhánh_b

Bước hai: Áp dụng bản vá trên các tệp khớp với mẫu

git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch

ghi chú hữu ích về các tùy chọn

Bạn có thể sử dụng *làm ký tự đại diện trong mẫu bao gồm.

Chém không cần phải thoát.

Ngoài ra, bạn có thể sử dụng --exclude thay thế và áp dụng nó cho mọi thứ trừ các tệp khớp với mẫu hoặc đảo ngược bản vá với -R

Tùy chọn -p1 là một sự nắm giữ từ lệnh * unix patch và thực tế là nội dung của tệp vá sẽ bổ sung cho mỗi tên tệp bằng a/hoặc b/(hoặc nhiều hơn tùy thuộc vào cách tạo tệp vá) mà bạn cần phải loại bỏ để có thể tìm ra tập tin thực sự đến đường dẫn đến tập tin mà bản vá cần được áp dụng.

Kiểm tra trang man cho git - áp dụng để có thêm tùy chọn.

Bước ba: không có bước ba

Rõ ràng là bạn muốn thực hiện các thay đổi của mình, nhưng ai đó nói rằng bạn không có một số điều chỉnh liên quan khác mà bạn muốn thực hiện trước khi thực hiện cam kết của mình.


1
Điều này rất hữu ích khi current_branch có nhiều thay đổi "bổ sung" cần được bảo tồn. Có sự khác biệt của chỉ những thay đổi được đưa vào bởi Branch_b như: git diff HEAD ... Branch_b (có - ba giai đoạn thực hiện trò ảo thuật).
Saad Malik

@masukomi, Ở bước 2, bạn có nên thêm tệp vá được tạo ở bước 1 làm đối số không?
Xoắn ốc

Đối với tôi, tất cả các thay đổi đều bị từ chối. Bất cứ ý tưởng tại sao?
LinusGeffarth

Suy nghĩ ban đầu @LinusGeffarth là có thể bạn có các nhánh ngược khi thực hiện bản vá? sẽ theo dõi bên ngoài SO để xem liệu chúng ta có thể tìm ra nó không.
masukomi

24

Đây là cách bạn có thể lấy lịch sử để theo dõi chỉ một vài tệp từ một chi nhánh khác với mức tối thiểu phiền phức, ngay cả khi một sự hợp nhất "đơn giản" hơn sẽ mang lại nhiều thay đổi hơn mà bạn không muốn.

Trước tiên, bạn sẽ thực hiện bước bất thường trước khi tuyên bố rằng những gì bạn sắp cam kết là hợp nhất, mà không cần git làm bất cứ điều gì với các tệp trong thư mục làm việc của bạn:

git merge --no-ff --no-commit -s ours branchname1

. . . trong đó "tên nhánh" là bất cứ điều gì bạn tuyên bố sẽ hợp nhất từ ​​đó. Nếu bạn cam kết ngay lập tức, nó sẽ không thay đổi nhưng nó vẫn hiển thị tổ tiên từ nhánh khác. Bạn có thể thêm nhiều chi nhánh / thẻ / vv. đến dòng lệnh nếu bạn cũng cần. Tuy nhiên, tại thời điểm này, không có thay đổi nào để cam kết, vì vậy hãy lấy các tệp từ các phiên bản khác, tiếp theo.

git checkout branchname1 -- file1 file2 etc

Nếu bạn đã hợp nhất từ ​​nhiều chi nhánh khác, hãy lặp lại nếu cần.

git checkout branchname2 -- file3 file4 etc

Bây giờ các tệp từ nhánh khác nằm trong chỉ mục, sẵn sàng để được cam kết, với lịch sử.

git commit

và bạn sẽ có rất nhiều giải thích để làm trong thông điệp cam kết đó.

Tuy nhiên, xin lưu ý rằng trong trường hợp không rõ ràng, điều này sẽ gây rối. Đây không phải là tinh thần của một "nhánh" dành cho, và cherry-pick là một cách trung thực hơn để làm những gì bạn đang làm ở đây. Nếu bạn muốn thực hiện một "hợp nhất" khác cho các tệp khác trên cùng một nhánh mà bạn không mang lại lần trước, nó sẽ ngăn bạn với một thông báo "đã cập nhật". Đó là một triệu chứng của việc không phân nhánh khi chúng ta nên có, trong nhánh "từ" nên có nhiều hơn một nhánh khác nhau.


3
Lệnh đầu tiên của bạn ( git merge --no-ff --no-commit -s outs branchname1) chính xác là những gì tôi đang tìm kiếm! Cảm ơn!
RobM

1
Với nhiều nhánh, lịch sử bắt buộc, cần hợp nhất (các) tệp duy nhất và phải thay đổi nội dung của tệp trước khi đẩy, đây có vẻ là một thay thế hợp lý. Ví dụ: dev => master, nhưng bạn muốn thay đổi định nghĩa máy chủ hoặc tương tự trước khi đẩy sang master.
timss

15

Tôi biết tôi đến hơi muộn nhưng đây là quy trình làm việc của tôi để hợp nhất các tệp chọn lọc.

#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes 
git merge --no-commit  featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit

Tôi đã sử dụng một biến thể nhỏ về điều này. Thay vì hợp nhất tôi chọn anh đào. Nó làm công việc. Nhược điểm duy nhất của phương pháp này là bạn mất tham chiếu đến hàm băm cam kết ban đầu.
Matt Florence

15

Cách dễ nhất là đặt repo của bạn thành nhánh bạn muốn hợp nhất sau đó chạy,

git checkout [branch with file] [path to file you would like to merge]

Nếu bạn chạy

git status

bạn sẽ thấy tập tin đã được dàn dựng ...

Sau đó chạy

git commit -m "Merge changes on '[branch]' to [file]"

Đơn giản.


3
Đây gần như là câu trả lời tốt nhất mà tôi tìm thấy. vui lòng xem jasonrudolph.com/blog/2009/02/26/ Vì vậy, rõ ràng, súc tích và nó chỉ hoạt động!
superuseroi

1
sẽ thay thế hoàn toàn nội dung tệp từ nhánh nguồn thay vì hợp nhất
Amare

Tôi vừa mới trả lời như thế này, tôi nghĩ tôi đã phát minh ra những điều mới chưa được trả lời! Nhưng đây là cách đơn giản nhất để làm điều đó. Điều này nên được đặt lên hàng đầu!
Irfandy Jip

15

Tôi tìm thấy bài đăng này để chứa câu trả lời đơn giản nhất. Chỉ làm

$ #git checkout <branch from which you want files> <file paths>

Thí dụ:

$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore

Xem bài để biết thêm.


3
Điều này không thực sự hợp nhất, nó ghi đè lên tệp trên nhánh hiện tại.
Igor Ralic

1
@igrali Đó là một nhận xét hữu ích, nhưng so với độ khó của các cách "phù hợp" để làm điều này, đây là một cách giải quyết tốt. Một chỉ cần phải rất cẩn thận.
owensmartin

12

Thật kỳ lạ khi git vẫn không có một công cụ tiện lợi như "ngoài luồng". Tôi sử dụng nó rất nhiều khi cập nhật một số nhánh phiên bản cũ (vẫn còn nhiều người dùng phần mềm) chỉ bằng một số lỗi từ nhánh phiên bản hiện tại. Trong trường hợp này, thường chỉ cần nhanh chóng lấy một số dòng mã từ tệp trong thân cây, bỏ qua rất nhiều thay đổi khác (không được phép chuyển sang phiên bản cũ) ... Và tất nhiên hợp nhất ba chiều tương tác là cần thiết trong trường hợp này, git checkout --patch <branch> <file path>không thể sử dụng cho mục đích hợp nhất có chọn lọc này.

Bạn có thể làm điều đó một cách dễ dàng:

Chỉ cần thêm dòng này vào [alias]phần trong tệp toàn cầu .gitconfighoặc cục bộ của bạn .git/config:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;' -"

Nó ngụ ý bạn sử dụng Beyond So sánh. Chỉ cần thay đổi phần mềm bạn chọn nếu cần. Hoặc bạn có thể thay đổi nó thành tự động hợp nhất ba chiều nếu bạn không cần hợp nhất chọn lọc tương tác:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;' -"

Sau đó sử dụng như thế này:

git mergetool-file <source branch> <file path>

Điều này sẽ cung cấp cho bạn cơ hội hợp nhất theo cách chọn cây thực sự của bất kỳ tệp nào trong nhánh khác.


10

Nó không chính xác là những gì bạn đang tìm kiếm, nhưng nó rất hữu ích với tôi:

git checkout -p <branch> -- <paths> ...

Nó là một kết hợp của một số câu trả lời.


2
Điều này thực sự hữu ích và có thể được thêm vào câu trả lời hay nhất cho tôi, câu trả lời của @ alvinabad. Khi thực hiện: git checkout HEAD file1để giữ phiên bản hiện tại và hủy tập tin file1, người ta có thể sử dụng -ptùy chọn để chọn một phần của tập tin sẽ được hợp nhất. cảm ơn vì mánh khóe
Simon C.

Đây là câu trả lời yêu thích của tôi. Đơn giản, đến mức và hoạt động
Jesse Reza Khorasanee

8

Tôi sẽ làm một

git diff commit1..commit2 filepotype | git-áp dụng --index && git cam kết

Bằng cách này, bạn có thể giới hạn phạm vi cam kết cho một tệp mẫu từ một nhánh.

Bị đánh cắp từ: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html


Trong một số tình huống, điều này có thể rất tiện dụng. Tuy nhiên, nếu các thay đổi ở một nhánh khác, bạn chỉ có thể kiểm tra từ đầu của nhánh đó, như trong câu trả lời của Bart J ở trên.
cdunn2001

Bart Js là ai?
Đen

8

Tôi đã có cùng một vấn đề như bạn đã đề cập ở trên. Nhưng tôi thấy blog git này rõ ràng hơn trong việc giải thích câu trả lời.

Lệnh từ liên kết trên:

#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>

bạn đã kiểm tra cái này chưa? tôi chắc chắn các tệp sẽ được thay thế từ <Branch_you_want_to_merge_from> thay vì được hợp nhất
Amare

7

Tôi thích câu trả lời 'git-tương tác-hợp nhất' ở trên, nhưng có một câu dễ hơn. Hãy để git làm điều này cho bạn bằng cách sử dụng kết hợp rebase của tương tác và lên:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o master

Vì vậy, trường hợp là bạn muốn C1 và C2 từ nhánh 'tính năng' (điểm nhánh 'A'), nhưng hiện tại không có phần còn lại.

# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp

Mà, như trên, đưa bạn vào trình chỉnh sửa tương tác nơi bạn chọn các dòng 'chọn' cho C1 và C2 (như trên). Lưu và thoát, và sau đó nó sẽ tiến hành rebase và cung cấp cho bạn nhánh 'temp' và cả Head ở master + C1 + C2:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o-master--C1---C2 [HEAD, temp]

Sau đó, bạn có thể chỉ cần cập nhật chính lên CHÍNH và xóa nhánh tạm thời và bạn vẫn ổn:

# git branch -f master HEAD
# git branch -d temp

7

Thế còn git reset --soft branch? Tôi ngạc nhiên rằng không ai đã đề cập đến nó.

Đối với tôi, đó là cách dễ nhất để chọn lọc các thay đổi từ một nhánh khác, vì lệnh này đặt trong cây làm việc của tôi, tất cả các thay đổi khác nhau và tôi có thể dễ dàng chọn hoặc hoàn nguyên cái nào tôi cần. Theo cách này, tôi có toàn quyền kiểm soát các tệp đã cam kết.


6

Tôi biết câu hỏi này đã cũ và có nhiều câu trả lời khác, nhưng tôi đã viết kịch bản của riêng mình có tên là 'pmerge' để hợp nhất một phần các thư mục. Đó là một công việc đang tiến triển và tôi vẫn đang học cả kịch bản git và bash.

Lệnh này sử dụng git merge --no-commitvà sau đó không áp dụng các thay đổi không khớp với đường dẫn được cung cấp.

Sử dụng: git pmerge branch path
Ví dụ:git merge develop src/

Tôi đã không thử nó rộng rãi. Thư mục làm việc không được có bất kỳ thay đổi nào và các tệp không được theo dõi.

#!/bin/bash

E_BADARGS=65

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` branch path"
    exit $E_BADARGS
fi

git merge $1 --no-commit
IFS=$'\n'
# list of changes due to merge | replace nulls w newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
    [[ $f == $2* ]] && continue
    if git reset $f >/dev/null 2>&1; then
        # reset failed... file was previously unversioned
        echo Deleting $f
        rm $f
    else
        echo Reverting $f
        git checkout -- $f >/dev/null 2>&1
    fi
done
unset IFS

4

Bạn có thể sử dụng read-treeđể đọc hoặc hợp nhất cây từ xa đã cho vào chỉ mục hiện tại, ví dụ:

git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

Để thực hiện hợp nhất, sử dụng -m thay thế.

Xem thêm: Làm cách nào để hợp nhất một thư mục con trong git?


3

Một cách tiếp cận đơn giản để hợp nhất / cam kết chọn lọc theo tệp:

git checkout dstBranch git merge srcBranch // make changes, including resolving conflicts to single files git add singleFile1 singleFile2 git commit -m "message specific to a few files" git reset --hard # blow away uncommitted changes


3

Nếu bạn không có quá nhiều tệp đã thay đổi, điều này sẽ khiến bạn không có thêm cam kết nào.

1. Chi nhánh trùng lặp tạm thời
$ git checkout -b temp_branch

2. Đặt lại về cam kết mong muốn cuối cùng
$ git reset --hard HEAD~n , nsố lần xác nhận bạn cần quay lại là bao nhiêu

3. Kiểm tra từng tệp từ chi nhánh ban đầu
$ git checkout origin/original_branch filename.ext

Bây giờ bạn có thể cam kết và buộc đẩy (ghi đè từ xa), nếu cần.


3

Nếu bạn chỉ cần nhập một thư mục và nghỉ đặc biệt mọi thứ khác còn nguyên vẹn và chưa bảo tồn lịch sử, bạn có thể có thể thử này ... tạo ra một mới target-branchtắt của mastertrước khi bạn thử nghiệm.

Các bước bên dưới giả sử bạn có hai nhánh target-branchsource-branchthư mục dir-to-mergebạn muốn hợp nhất nằm trong source-branch. Cũng giả sử rằng bạn có các thư mục khác như dir-to-retaintrong mục tiêu mà bạn không muốn thay đổi và lưu giữ lịch sử. Ngoài ra, giả sử có xung đột hợp nhất trong dir-to-merge.

git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict. 
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.

# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.

2

Khi chỉ có một vài tệp đã thay đổi giữa các cam kết hiện tại của hai nhánh, tôi hợp nhất các thay đổi bằng cách đi qua các tệp khác nhau.

git difftoll <branch-1>..<branch-2>

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.