Câu hỏi Làm thế nào để bạn hợp nhất hai kho Git?


Hãy xem xét kịch bản sau:

Tôi đã phát triển một dự án thử nghiệm nhỏ A trong repo Git của riêng mình. Nó bây giờ đã trưởng thành, và tôi muốn A là một phần của dự án lớn hơn B, có kho lưu trữ lớn của riêng nó. Bây giờ tôi muốn thêm A làm thư mục con của B.

Làm cách nào để hợp nhất A thành B, mà không làm mất lịch sử ở bất kỳ phía nào?


1222
2017-09-15 08:31


gốc


Nếu bạn chỉ cố gắng kết hợp hai kho lưu trữ vào một, mà không cần giữ cả hai kho lưu trữ, hãy xem câu hỏi này: stackoverflow.com/questions/13040958/… - Flimm
Để kết hợp git repo trong thư mục tùy chỉnh với việc lưu tất cả các sử dụng comits stackoverflow.com/a/43340714/1772410 - Andrey Izman


Các câu trả lời:


Một chi nhánh duy nhất của một kho lưu trữ khác có thể dễ dàng được đặt trong một thư mục con giữ lại lịch sử của nó. Ví dụ:

git subtree add --prefix=rails git://github.com/rails/rails.git master

Điều này sẽ xuất hiện dưới dạng một cam kết duy nhất trong đó tất cả các tệp của nhánh master Rails được thêm vào thư mục "rails". Tuy nhiên tiêu đề của cam kết chứa tham chiếu đến cây lịch sử cũ:

Thêm 'đường ray /' từ cam kết <rev>

Ở đâu <rev> là một băm cam kết SHA-1. Bạn vẫn có thể thấy lịch sử, đổ lỗi cho một số thay đổi.

git log <rev>
git blame <rev> -- README.md

Lưu ý rằng bạn không thể thấy tiền tố thư mục từ đây vì đây là một nhánh cũ thực sự còn nguyên vẹn. Bạn nên đối xử với điều này như một cam kết di chuyển tập tin thông thường: bạn sẽ cần thêm một bước nhảy khi tiếp cận nó.

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

Có nhiều giải pháp phức tạp hơn như thực hiện việc này theo cách thủ công hoặc viết lại lịch sử như được mô tả trong các câu trả lời khác.

Lệnh git-subtree là một phần của git-contrib chính thức, một số trình quản lý gói cài đặt nó theo mặc định (OS X Homebrew). Nhưng bạn có thể phải cài đặt nó một mình ngoài git.


325
2018-02-20 23:44



@ Brad Mace, repo git-subtree giờ đã lỗi thời vì nó được đưa vào git. Xem github.com/apenwarr/git-subtree/blob/master/… - Simon Perepelitsa
Đừng ngừng đọc ... câu trả lời hoàn chỉnh hơn nhiều bên dưới. - Ryan Shillington
Dưới đây là hướng dẫn về cách cài đặt Git SubTree (tính đến tháng 6 năm 2013): stackoverflow.com/a/11613541/694469  (và tôi đã thay thế git co v1.7.11.3  với ... v1.8.3). - KajMagnus
Hoặc đọc cuốn sách "sáp nhập hai kho lưu trữ Git vào một kho lưu trữ mà không làm mất lịch sử" của Eric Lee saintgimp.org/2013/01/22/… - Jifeng Zhang
Như những người khác đã nói, git subtree có thể không làm những gì bạn nghĩ! Xem đây cho một giải pháp hoàn chỉnh hơn. - Paul Draper


Nếu bạn muốn hợp nhất project-a vào project-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

Được lấy từ: git hợp nhất các kho lưu trữ khác nhau?

Phương pháp này hoạt động khá tốt đối với tôi, nó ngắn hơn và theo ý kiến ​​của tôi thì sạch hơn rất nhiều.

Chú thích: Các --allow-unrelated-histories tham số chỉ tồn tại từ git> = 2.9. Xem Git - git merge Tài liệu / --allow-unrelated-history 


1284
2018-05-11 09:37



có vẻ như điều này cần một bản sao làm việc: fatal: This operation must be run in a work tree, tôi muốn kết hợp hai kho git trống. - LiuYan 刘研
Điều này đã làm kinh doanh cho tôi. Đã làm việc như một sự quyến rũ lần đầu tiên chỉ với một xung đột trong tệp .gitignore! Nó hoàn toàn bảo tồn lịch sử cam kết. Điểm cộng lớn so với các cách tiếp cận khác - ngoài sự đơn giản - là với điều này, không cần tham chiếu liên tục đến repo đã hợp nhất. Tuy nhiên, một điều cần lưu ý - nếu bạn là nhà phát triển iOS như tôi - hãy cẩn thận để thả tệp dự án của repo mục tiêu vào không gian làm việc. - Max MacLeod
Cảm ơn. Đã làm cho tôi. Tôi cần di chuyển thư mục đã hợp nhất vào thư mục con, vì vậy sau khi làm theo các bước trên, tôi đã sử dụng đơn giản git mv source-dir/ dest/new-source-dir - Sid
Các git merge bước không thành công ở đây với fatal: refusing to merge unrelated histories; --allow-unrelated-histories sửa lỗi như được giải thích trong tài liệu. - ssc
--allow-unrelated-histories đã được giới thiệu trong git 2.9. Trong các phiên bản trước đó, đó là hành vi mặc định. - Douglas Royds


Dưới đây là hai giải pháp khả thi:

Submodules

Hoặc là sao chép kho lưu trữ A vào một thư mục riêng biệt trong dự án lớn hơn B, hoặc (có lẽ tốt hơn) kho bản sao A vào một thư mục con trong dự án B. Sau đó sử dụng git submodule để làm cho kho lưu trữ này trở thành submodule của một kho lưu trữ B.

Đây là một giải pháp tốt cho kho lưu trữ lỏng lẻo, nơi phát triển trong kho A tiếp tục, và phần lớn phát triển là một sự phát triển độc lập riêng biệt trong A. Xem thêm SubmoduleSupport và GitSubmoduleTutorial các trang trên Git Wiki.

Hợp nhất Subtree

Bạn có thể hợp nhất kho lưu trữ A vào thư mục con của dự án B bằng cách sử dụng hợp nhất subtree chiến lược. Điều này được mô tả trong Hợp nhất Subtree và bạn bởi Markus Prinz.

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

(Tùy chọn --allow-unrelated-histories là cần thiết cho Git> = 2.9.0.)

Hoặc bạn có thể sử dụng git subtree dụng cụ (kho lưu trữ trên GitHub) bởi apenwarr (Avery Pennarun), đã công bố ví dụ trong bài đăng trên blog của mình Một thay thế mới cho mô-đun con Git: git subtree.


Tôi nghĩ trong trường hợp của bạn (A là một phần của dự án lớn hơn B), giải pháp chính xác sẽ là sử dụng hợp nhất subtree


583
2017-09-15 08:38



Điều này làm việc và dường như để bảo tồn lịch sử, nhưng không phải như vậy mà bạn có thể sử dụng nó để phân biệt các tập tin hoặc bisect thông qua việc hợp nhất. Tôi có bỏ lỡ một bước không? - jettero
điều này chưa hoàn chỉnh. Có, bạn nhận được một tải của cam kết, nhưng họ không còn tham khảo các đường dẫn bên phải. git log dir-B/somefile sẽ không hiển thị bất cứ điều gì ngoại trừ một hợp nhất. Xem Câu trả lời của Greg Hewgill tham khảo vấn đề quan trọng này. - artfulrobot
QUAN TRỌNG: git pull --no-rebase -s subtree Bproject master Nếu bạn không làm điều đó, và bạn đã kéo thiết lập để rebase tự động, bạn sẽ kết thúc với "Không thể phân tích đối tượng". Xem osdir.com/ml/git/2009-07/msg01576.html - Eric Bowman - abstracto -
Câu trả lời này có thể gây nhầm lẫn bởi vì nó có B là cây con sáp nhập khi trong câu hỏi đó là A. Kết quả của một bản sao và dán? - vfclists
Nếu bạn đang cố gắng đơn giản dán hai kho lưu trữ lại với nhau, các mô-đun con và sự phối trộn phụ là công cụ sai để sử dụng vì chúng không bảo toàn tất cả lịch sử tệp (như các người nhận xét khác đã lưu ý). Xem stackoverflow.com/questions/13040958/…. - Eric Lee


Cách tiếp cận submodule là tốt nếu bạn muốn duy trì dự án một cách riêng biệt. Tuy nhiên, nếu bạn thực sự muốn hợp nhất cả hai dự án vào cùng một kho lưu trữ, thì bạn có nhiều việc phải làm hơn.

Điều đầu tiên sẽ là sử dụng git filter-branch để viết lại tên của tất cả mọi thứ trong kho thứ hai ở trong thư mục con nơi bạn muốn chúng kết thúc. Vì vậy, thay vì foo.c, bar.html, bạn sẽ có projb/foo.c và projb/bar.html.

Sau đó, bạn sẽ có thể thực hiện một số việc như sau:

git remote add projb [wherever]
git pull projb

Các git pull sẽ làm một git fetch Theo sau là một git merge. Không nên có xung đột, nếu kho lưu trữ bạn đang kéo đến chưa có projb/ danh mục.

Tìm kiếm thêm chỉ ra rằng một cái gì đó tương tự đã được thực hiện để hợp nhất gitk vào git. Junio ​​C Hamano viết về nó ở đây: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


188
2018-02-01 08:10



Cảm ơn rất nhiều, đó là chính xác những gì tôi muốn làm. - static_rtti
hợp nhất subtree sẽ là giải pháp tốt hơn, và không yêu cầu viết lại lịch sử của dự án đi kèm - Jakub Narębski
Tôi muốn biết cách sử dụng git filter-branch để đạt được điều này. Trong trang người đàn ông nó nói về cách đối diện xung quanh: làm cho tiểu thư / trở thành gốc, nhưng không phải là cách khác xung quanh. - artfulrobot
câu trả lời này sẽ tuyệt vời nếu nó giải thích cách sử dụng bộ lọc-chi nhánh để đạt được kết quả mong muốn - Anentropic
Tôi tìm thấy cách sử dụng bộ lọc-chi nhánh tại đây: stackoverflow.com/questions/4042816/… - David Minor


git-subtree là tốt đẹp, nhưng nó có lẽ không phải là một trong những bạn muốn.

Ví dụ, nếu projectA là thư mục được tạo trong B, sau git subtree,

git log projectA

danh sách chỉ một cam kết: hợp nhất. Các cam kết từ dự án đã hợp nhất là cho các đường dẫn khác nhau, vì vậy chúng không hiển thị.

Câu trả lời của Greg Hewgill đến gần nhất, mặc dù nó không thực sự nói cách viết lại đường dẫn.


Các giải pháp là đáng ngạc nhiên đơn giản.

(1) Trong A,

PREFIX=projectA #adjust this

git filter-branch --index-filter '
    git ls-files -s |
    sed "s,\t,&'"$PREFIX"'/," |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD

Lưu ý: Lịch sử viết lại này, vì vậy nếu bạn dự định tiếp tục sử dụng repo A này, bạn có thể sao chép (copy) bản sao của nó trước.

(2) Sau đó, trong B, chạy

git pull path/to/A

Thì đấy! Bạn có một projectA thư mục trong B. Nếu bạn chạy git log projectA, bạn sẽ thấy tất cả các cam kết từ A.


Trong trường hợp của tôi, tôi muốn có hai thư mục con, projectA và projectB. Trong trường hợp đó, tôi cũng đã từng bước (1) đến B.


62
2018-06-11 14:31



Có vẻ như bạn đã sao chép câu trả lời của mình từ stackoverflow.com/a/618113/586086? - Andrew Mao
@AndrewMao, tôi nghĩ vậy ... Tôi thực sự không thể nhớ được. Tôi đã sử dụng kịch bản này khá một chút. - Paul Draper
Tôi muốn thêm rằng \ t không hoạt động trên OS X và bạn phải nhập <tab> - Muneeb Ali
"$GIT_INDEX_FILE" phải được trích dẫn (hai lần), nếu không phương pháp của bạn sẽ thất bại nếu ví dụ: đường dẫn chứa dấu cách. - Rob W
Nếu bạn đang tự hỏi, để chèn một <tab> trong osx, bạn cần phải Ctrl-V <tab> - casey


Nếu cả hai kho lưu trữ có cùng một loại tệp (như hai kho lưu trữ Rails cho các dự án khác nhau), bạn có thể lấy dữ liệu của kho lưu trữ thứ cấp vào kho lưu trữ hiện tại của mình:

git fetch git://repository.url/repo.git master:branch_name

và sau đó hợp nhất nó vào kho lưu trữ hiện tại:

git merge --allow-unrelated-histories branch_name

Nếu phiên bản Git của bạn nhỏ hơn 2.9, hãy xóa --allow-unrelated-histories.

Sau này, xung đột có thể xảy ra. Bạn có thể giải quyết chúng ví dụ với git mergetool. kdiff3 có thể được sử dụng chỉ với bàn phím, vì vậy 5 tệp xung đột sẽ mất khi đọc mã chỉ vài phút.

Hãy nhớ hoàn tất quá trình hợp nhất:

git commit

39
2018-02-27 03:09





Tôi tiếp tục mất lịch sử khi sử dụng hợp nhất, vì vậy tôi đã kết thúc bằng cách sử dụng rebase vì trong trường hợp của tôi, hai kho lưu trữ khác nhau, đủ để không kết thúc ở mọi cam kết:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> giải quyết xung đột, sau đó tiếp tục, nhiều lần nếu cần ...

git rebase --continue

Làm điều này dẫn đến một dự án có tất cả các cam kết từ projA theo sau bởi cam kết từ projB


20
2017-08-18 21:06



Làm mới đơn giản và hiệu quả! - Ivan


Trong trường hợp của tôi, tôi đã có một my-plugin kho lưu trữ và main-project và tôi muốn giả vờ rằng my-plugin đã luôn luôn được phát triển trong plugins thư mục con của main-project.

Về cơ bản, tôi viết lại lịch sử của my-plugin kho lưu trữ để nó xuất hiện tất cả các phát triển đã diễn ra trong plugins/my-plugin thư mục con. Sau đó, tôi đã thêm lịch sử phát triển của my-plugin vào main-project lịch sử, và sáp nhập hai cây lại với nhau. Vì không có plugins/my-plugin thư mục đã có trong main-project kho lưu trữ, đây là một cuộc xung đột không xung đột tầm thường. Kho chứa kết quả chứa tất cả lịch sử từ cả hai dự án gốc và có hai gốc.

TL; DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|my-plugin) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

Phiên bản dài

Đầu tiên, tạo một bản sao của my-plugin kho lưu trữ, bởi vì chúng ta sẽ viết lại lịch sử của kho lưu trữ này.

Bây giờ, điều hướng đến thư mục gốc của my-plugin kho lưu trữ, kiểm tra nhánh chính của bạn (có lẽ master) và chạy lệnh sau. Tất nhiên, bạn nên thay thế my-plugin và plugins tên thật của bạn là gì.

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|my-plugin) plugins/my-plugin || true)'" -- --all

Bây giờ cho một lời giải thích. git filter-branch --tree-filter (...) HEAD chạy (...) lệnh trên mọi cam kết có thể truy cập từ HEAD. Lưu ý rằng điều này hoạt động trực tiếp trên dữ liệu được lưu trữ cho mỗi cam kết, vì vậy chúng tôi không phải lo lắng về khái niệm "thư mục làm việc", "chỉ mục", "dàn dựng", v.v.

Nếu bạn chạy filter-branch lệnh không thành công, nó sẽ để lại một số tệp trong .git và lần sau bạn thử filter-branch nó sẽ phàn nàn về điều này, trừ khi bạn cung cấp -f tùy chọn để filter-branch.

Đối với lệnh thực tế, tôi không có nhiều may mắn bash để làm những gì tôi muốn, vì vậy thay vào đó tôi sử dụng zsh -c để làm cho zsh thực hiện một lệnh. Đầu tiên tôi đặt extended_glob tùy chọn, đó là những gì cho phép ^(...) cú pháp trong mv lệnh, cũng như glob_dots tùy chọn, cho phép tôi chọn dotfiles (chẳng hạn như .gitignore) với một glob (^(...)).

Tiếp theo, tôi sử dụng mkdir -p lệnh để tạo cả hai plugins và plugins/my-plugin cùng một lúc.

Cuối cùng, tôi sử dụng zsh tính năng "glob âm" ^(.git|my-plugin) để khớp tất cả các tệp trong thư mục gốc của kho lưu trữ ngoại trừ .git và mới được tạo my-plugin thư mục. (Không bao gồm .git có thể không cần thiết ở đây, nhưng cố gắng di chuyển một thư mục vào chính nó là một lỗi.)

Trong kho lưu trữ của tôi, cam kết ban đầu không bao gồm bất kỳ tệp nào, vì vậy mv lệnh trả về lỗi trên cam kết ban đầu (vì không có gì khả dụng để di chuyển). Do đó, tôi đã thêm || true để vậy git filter-branch sẽ không hủy bỏ.

Các --all tùy chọn cho biết filter-branch để viết lại lịch sử cho tất cả các các chi nhánh trong kho lưu trữ và phần bổ sung -- là cần thiết để nói git để diễn giải nó như là một phần của danh sách tùy chọn cho các nhánh viết lại, thay vì tùy chọn filter-branch chinh no.

Bây giờ, điều hướng đến main-project lưu trữ và kiểm tra bất kỳ nhánh nào bạn muốn hợp nhất. Thêm bản sao cục bộ của bạn my-plugin kho lưu trữ (với lịch sử của nó được sửa đổi) như một điều khiển từ xa main-project với:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Bây giờ bạn sẽ có hai cây không liên quan trong lịch sử cam kết của bạn, mà bạn có thể trực quan hóa bằng cách sử dụng:

$ git log --color --graph --decorate --all

Để hợp nhất chúng, hãy sử dụng:

$ git merge my-plugin/master --allow-unrelated-histories

Lưu ý rằng trong Git trước 2.9.0, --allow-unrelated-histories tùy chọn không tồn tại. Nếu bạn đang sử dụng một trong các phiên bản này, chỉ cần bỏ qua tùy chọn: thông báo lỗi --allow-unrelated-histories ngăn cản là cũng thế được thêm vào 2.9.0.

Bạn không nên có bất kỳ xung đột hợp nhất nào. Nếu bạn làm vậy, điều đó có thể có nghĩa là filter-branch lệnh không hoạt động chính xác hoặc đã có plugins/my-plugin thư mục trong main-project.

Đảm bảo nhập thông điệp cam kết giải thích cho bất kỳ người đóng góp nào trong tương lai tự hỏi tin tặc đang thực hiện kho lưu trữ nào với hai gốc.

Bạn có thể hình dung biểu đồ cam kết mới, cần có hai commit gốc, sử dụng ở trên git log chỉ huy. Lưu ý rằng chỉ master nhánh sẽ được hợp nhất. Điều này có nghĩa là nếu bạn có công việc quan trọng khác my-plugin các chi nhánh mà bạn muốn hợp nhất vào main-project cây, bạn nên tránh xóa my-plugin từ xa cho đến khi bạn đã thực hiện các kết hợp này. Nếu không, thì các cam kết từ những nhánh đó sẽ vẫn ở trong main-project kho lưu trữ, nhưng một số sẽ không thể tiếp cận và dễ bị thu gom rác cuối cùng. (Ngoài ra, bạn sẽ phải đề cập đến chúng bằng SHA, vì việc xóa một từ xa sẽ loại bỏ các nhánh theo dõi từ xa của nó.)

Tùy chọn, sau khi bạn đã hợp nhất mọi thứ bạn muốn giữ lại my-plugin, bạn có thể xóa my-plugin sử dụng từ xa:

$ git remote remove my-plugin

Bây giờ bạn có thể xóa một cách an toàn bản sao của my-plugin kho lưu trữ có lịch sử bạn đã thay đổi. Trong trường hợp của tôi, tôi cũng đã thêm thông báo không dùng nữa vào thực tế my-plugin kho lưu trữ sau khi hợp nhất đã được hoàn thành và đẩy.


Thử nghiệm trên Mac OS X El Capitan với git --version 2.9.0 và zsh --version 5.2. Số dặm của bạn có thể thay đổi.

Tham khảo:


13
2018-03-10 12:51



Ở đâu --allow-unrelated-histories đến từ đâu? - Marcelo Filho
@MarceloFilho Check man git-merge. Theo mặc định, lệnh git merge từ chối hợp nhất các lịch sử không chia sẻ một tổ tiên chung. Tùy chọn này có thể được sử dụng để ghi đè sự an toàn này khi hợp nhất lịch sử của hai dự án đã bắt đầu cuộc sống của họ một cách độc lập. Vì đó là một dịp rất hiếm hoi, không có biến cấu hình nào cho phép điều này theo mặc định tồn tại và sẽ không được thêm vào. - Radon Rosborough
Nên có sẵn trên git version 2.7.2.windows.1? - Marcelo Filho
@MarceloFilho Điều này đã được thêm vào trong 2.9.0, nhưng trong các phiên bản cũ hơn, bạn không cần phải vượt qua tùy chọn (nó sẽ chỉ hoạt động). github.com/git/git/blob/… - Radon Rosborough
Điều này làm việc tốt. Và tôi đã có thể sử dụng chi nhánh bộ lọc để ghi lại tên tệp đến nơi tôi muốn trong cây trước khi hợp nhất. Tôi cho rằng có nhiều công việc liên quan hơn nếu bạn cần phải di chuyển lịch sử bên cạnh nhánh chủ. - codeDr