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


84

Có thể chỉ hợp nhất các thay đổi cho một thư mục con từ một nhánh Git cục bộ sang một nhánh Git từ xa hay là "tất cả hoặc không có gì"?

Ví dụ, tôi có:

branch-a
 - content-1
 - dir-1
   - content-2

branch-b
 - content-1
 - dir-1
   - `content-2

Tôi chỉ muốn hợp nhất nội dung của branch-a dir-1 với nội dung của branch-b dir-1.


1
Tôi nghĩ rằng đây là một bản sao của: stackoverflow.com/questions/449541/...
Karl Voigtland

Câu trả lời:


81

Cũng giống như một giải pháp thay thế cho câu hỏi SO " Làm cách nào để hợp nhất các tệp chọn lọc với git-merge? ", Tôi vừa tìm thấy luồng GitHub này có thể thích nghi hơn để hợp nhất toàn bộ thư mục con, dựa trên git read-tree :

  • Kho lưu trữ của tôi => Thư cookbooks
    mục đích kho lưu trữ của tôi =>cookbooks/cassandra
  • Kho lưu trữ từ xa => infochimps
    Nguồn kho lưu trữ từ xa tôi muốn hợp nhất vào cookbooks/cassandra=>infochimps/cookbooks/cassandra

Đây là các lệnh tôi đã sử dụng để hợp nhất chúng

  • Thêm kho lưu trữ và tìm nạp nó
git từ xa add -f infochimps git: //github.com/infochimps/cluster_chef.git
  • Thực hiện hợp nhất
hợp nhất git - cho phép-không liên quan-lịch sử-của chúng tôi - không cam kết thông tin / chính

(điều này thực hiện hợp nhất bằng cách sử dụng chiến lược 'của chúng tôi' ( -s ours), loại bỏ các thay đổi từ nhánh nguồn. Điều này ghi lại thực tế là infochimps/masterđã được hợp nhất, mà không thực sự sửa đổi bất kỳ tệp nào trong nhánh đích)

  • Chỉ hợp nhất infochimps/cookbooks/cassandravàocassandra
git read-tree --prefix = cassandra / -u infochimps / master: cookbooks / cassandra

Điều này chỉ đọc cây cho thư mục con nguồn bắt buộc cookbooks/cassandra, tức là trên nhánh ngược của kho lưu trữ nguồn .

Lưu ý rằng tên thư mục con đích cũng phải là cookbooks/cassandra, hoặc bạn sẽ thấy:

fatal: Not a valid object name
  • Cam kết thay đổi
 git commit -m 'merge in infochimps cassandra'

Phụ lục

Thật kỳ lạ, [sửa cho tôi] - nhưng read-treebước này có thể thất bại như thế này:

error: Entry 'infochimps/cookbooks/cassandra/README' overlaps with 'cookbooks/cassandra/README'. Cannot bind.

... ngay cả khi cả hai tệp đều giống hệt nhau . Điều này có thể giúp:

git rm -r cassandra
git read-tree --prefix=cassandra/ -u infochimps/master:cookbooks/cassandra

Nhưng tất nhiên, hãy xác minh theo cách thủ công rằng điều này làm những gì bạn muốn.


3
@ Martin git-scm.com/docs/git-rev-parse#_specifying_revisions tìm kiếm <rev>:<path>, ví dụ như HEAD:README, :README,master:./README
VonC

6
Các git read-treebước thất bại cho tôi:error: Entry 'foo/bar/baz.php' overlaps with 'bar/baz.php'. Cannot bind.
Weston Ruter

1
@VonC nhưng không, tôi cần lịch sử. Câu trả lời đó không tính đến trường hợp có các sửa đổi cục bộ đối với các tệp trong cây. Vì vậy, nó cần phải hợp nhất.
Weston Ruter

Đối với tôi, overlaps withlỗi được đề cập ở trên cũng bật lên, trên các tệp giống hệt nhau . Làm thế nào kỳ lạ là vậy?
ulidtko

1
@ChrisHalcrow Để ghi lại thực tế infochimps/masterđã được hợp nhất, nhưng không thực sự sửa đổi bất kỳ tệp nào trong nhánh đích. Bởi vì bước tiếp theo git read-tree --prefix=cassandrasẽ thực hiện sửa đổi. Bản cam kết cuối cùng sẽ ghi lại nội dung "hợp nhất" thực tế.
VonC

29

Đối với ví dụ của tôi, giả sử bạn có một chi nhánh 'nguồn' và một 'đích' chi nhánh, cả hai đều phản ánh các phiên bản ngược dòng của chính chúng (hoặc không, nếu chỉ cục bộ) và được kéo đến mã mới nhất. Giả sử tôi muốn thư mục con trong kho được gọi là newFeature chỉ tồn tại trong nhánh 'nguồn'.

git checkout destination
git checkout source newFeature/
git commit -am "Merged the new feature from source to destination branch."
git pull --rebase
git push

Nó ít phức tạp hơn đáng kể so với mọi thứ khác mà tôi đã thấy và điều này làm việc hoàn hảo cho tôi, tìm thấy ở đây .

Lưu ý rằng đây không phải là 'hợp nhất thực sự', vì vậy bạn sẽ không có thông tin cam kết về newFeature trong nhánh đích, chỉ là các sửa đổi đối với các tệp trong thư mục con đó. Nhưng vì bạn có thể sẽ hợp nhất toàn bộ chi nhánh trở lại sau này hoặc loại bỏ nó, điều đó có thể không phải là vấn đề.


2
Nó có bảo tồn lịch sử không?
Bibrak

2
@Bibrak cách tiếp cận này KHÔNG bảo tồn lịch sử. Ít nhất là không với các lệnh hiện tại.
Ravi Gidwani

6

Tôi nhận được điều này từ một chuỗi diễn đàn tại Eclipse và nó hoạt động như một sự quyến rũ:

git checkout source-branch
git checkout target-branch <directories-or-files-you-do-**NOT**-want> 
git commit
git checkout target-branch
git merge source-branch

1
Tôi đã thử điều này và kết thúc với một thư mục từ nhánh nguồn trong nhánh đích mà tôi không muốn. Mặc dù đã chỉ định nó với tập hợp các dirs, tôi không muốn ở lệnh thứ 2 đó.
marathon

2
Điều này không hợp nhất, nó thay thế thư mục bằng thư mục từ nhánh nguồn.
Gp2mv3

@ Gp2mv3 Tôi nghĩ nó có vẻ chắc chắn. checkoutlàm cho các thư mục giống nhau => khác biệt chỉ là bỏ checkoutthư mục => mergekhác biệt. Đó là một chiến lược hợp lệ.
Caveman

5

Với kịch bản của OP trong đó họ có hai nhánh, nhưng chỉ muốn hợp nhất lịch sử của dir-1 từ nhánh-a vào nhánh-b :

# Make sure you are in the branch with the changes you want
git checkout branch-a

# Split the desired folder into its own temporary branch
# This replays all commits, so it could take a while
git subtree split -P dir-1 -b temp-branch

# Enter the branch where you want to merge the desired changes into
git checkout branch-b

# Merge the changes from the temporary branch
git subtree merge -P dir-1 temp-branch

# Handle any conflicts
git mergetool

# Commit
git commit -am "Merged dir-1 changes from branch-a"

# Delete temp-branch
git branch -d temp-branch

0

Tạo một kho lưu trữ Git để chứa cả branch-a và branch-b:

git checkout branch-a
git diff branch-b dir-1 > a.diff
patch -R -p1 < a.diff

14
Câu trả lời này cần thêm thông tin. Điều gì trong số này là mã thực tế so với nhận xét?
qodeninja

2
Người yêu cầu muốn hợp nhất. Sử dụng một bản vá để thực hiện các thay đổi sẽ tự động thu gọn tất cả các cam kết thành một bản vá và lịch sử sẽ bị mất.
Eric

0

Sử dụng git cherry-pickđể chọn các cam kết bạn muốn và chỉ hợp nhất các cam kết này. Bí quyết quan trọng ở đây là để có được những cam kết này một cách dễ dàng (để bạn không phải tìm ra chúng bằng cách kiểm tra thủ công nhật ký Git và nhập chúng bằng tay). Đây là cách thực hiện: sử dụng git logđể in id SHA-1 của cam kết , như thế này:

git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>

'commit-a' là cam kết ngay trước điểm bắt đầu của nhánh để hợp nhất và 'commit-b' là cam kết cuối cùng trên nhánh để hợp nhất. '--reverse' in các cam kết này theo thứ tự ngược lại cho việc hái anh đào sau này.

Sau đó, làm điều đó như:

git cherry-pick $(git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>)

Nó là hai bước, đơn giản và ổn định!


cách tuyệt vời và duy nhất để hợp nhất một dir từ một chi nhánh khác và bảo tồn lịch sử. nhưng ... ... nếu bạn muốn chỉ là một cam kết, tạo ra một chi nhánh từ tất cả các cam kết từ lệnh đầu tiên, sau đó hợp nhất mà chi nhánh mới tại một merge bí ....
tom

1
Điều này giả định rằng các cam kết được tìm thấy chỉ ảnh hưởng đến thư mục được đề cập. Nếu một cam kết ảnh hưởng đến cả thư mục bạn muốn hợp nhất và những thư mục bạn không muốn, thì điều này sẽ khiến bạn chọn quá nhiều.
Scott

Two steps, simple and stable!không đơn giản chút nào :)
Rafa

Vậy bạn nghĩ cách nào đơn giản? @Rafa
Robert

@Robert Tôi không có ý ám chỉ câu trả lời của bạn không đơn giản; lỗi là tất cả git, có một giao diện khủng khiếp và mô hình tinh thần khủng khiếp đầy bí ẩn và tên ngẫu nhiên và ý nghĩa.
Rafa
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.