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


1622

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

Tôi đã phát triển một dự án thử nghiệm nhỏ A trong repo Git của riêng mình. Hiện tại nó đã trưởng thành và tôi muốn A là một phần của dự án B lớn hơn, có kho lưu trữ lớn của riêng mình. 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 vào B, mà không mất lịch sử ở bất kỳ bên nào?


8
Nếu bạn chỉ đang cố gắng kết hợp hai kho lưu trữ thành 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/
Lỗi

Để hợp nhất git repo trong thư mục tùy chỉnh với việc lưu tất cả các comit, hãy sử dụng stackoverflow.com/a/43340714/1772410
Andrey Izman

Câu trả lời:


437

Một nhánh của kho lưu trữ khác có thể dễ dàng được đặt dưới 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 chính 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 'rails /' từ cam kết <rev>

Trường hợp <rev>băm xác nhận SHA-1 ở đâu. Bạn vẫn có thể xem 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 coi điều này giống như một cam kết di chuyển tệp thông thường: bạn sẽ cần một bước nhảy thêm 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ư làm điều này bằng tay 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 theo mặc định (OS X Homebrew). Nhưng bạn có thể phải tự cài đặt nó ngoài git.


2
Dưới đây là hướng dẫn về cách cài đặt Git SubTree (kể từ tháng 6 năm 2013): stackoverflow.com/a/11613541/694469 (và tôi đã thay thế git co v1.7.11.3 bằng ... v1.8.3).
KajMagnus

1
Cảm ơn cho những người đứng đầu về câu trả lời dưới đây. Kể từ git 1.8.4, 'cây con' vẫn chưa được đưa vào (ít nhất là không có trên Ubuntu 12.04 git ppa (ppa: git-core / ppa))
Matt Klein

1
Tôi có thể xác nhận rằng sau này, git log rails/somefilesẽ không hiển thị lịch sử cam kết của tệp ngoại trừ cam kết hợp nhất. Như @artfulrobot đã đề xuất, hãy kiểm tra câu trả lời của Greg Hewgill . Và bạn có thể cần phải sử dụng git filter-branchtrên repo bạn muốn đưa vào.
Jifeng Zhang

6
Hoặc đọc "Hợp nhất hai kho lưu trữ Git của Eric Lee vào một kho lưu trữ mà không làm mất lịch sử tập tin", thánhgimp.org / 2013/01/22 / //
Jifeng Zhang

4
Như những người khác đã nói, git subtreecó thể không làm những gì bạn nghĩ! Xem ở đây để có một giải pháp đầy đủ hơn.
Paul Draper

1908

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

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

Lấy từ: git hợp nhất các kho 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 tôi thì sạch sẽ hơn rất nhiều.

Trong trường hợp bạn muốn đưa project-avào thư mục con, bạn có thể sử dụng git-filter-repo( khôngfilter-branch được khuyến khích ). Chạy các lệnh sau trước các lệnh trên:

cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a

Một ví dụ về việc hợp nhất 2 kho lưu trữ lớn, đưa một trong số chúng vào thư mục con: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Lưu ý: Các --allow-unrelated-historiesthông số chỉ tồn tại kể từ khi git> = 2,9. Xem Tài liệu hợp nhất Git - git / --allow-không liên quan-lịch sử

Cập nhật : Đã thêm --tagstheo đề xuất của @jstadler để giữ thẻ.


8
Điều này đã làm kinh doanh cho tôi. Làm việc như một cơ duyên lần đầu tiên chỉ với một xung đột trong tệp .gitignore! Nó bảo tồn hoàn hảo 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 phải có một tài liệu tham khảo liên tục đến repo hợp nhất. Tuy nhiên, một điều cần chú ý - nếu bạn là nhà phát triển iOS như tôi - phải hết sức 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

30
Cảm ơn. Đã làm cho tôi. Tôi cần chuyển thư mục đã hợp nhất vào thư mục con để sau khi làm theo các bước trên tôi chỉ đơn giản sử dụnggit mv source-dir/ dest/new-source-dir
Sid

13
Các git mergebước thất bại ở đây với fatal: refusing to merge unrelated histories; --allow-unrelated-historiessửa lỗi đó như được giải thích trong các tài liệu .
ssc

19
--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

11
Ngắn hơn : git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD.
jthill

614

Đây là hai giải pháp khả thi:

Submodules

Sao chép kho lưu trữ A vào một thư mục riêng trong dự án B lớn hơn hoặc (có lẽ tốt hơn) kho lưu trữ A vào thư mục con trong dự án B. Sau đó sử dụng mô đun con git để biến kho lưu trữ này thành mô hình con của kho lưu trữ B.

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

Hợp nhất phụ

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 chiến lược hợp nhất cây con . Điều này được mô tả trong Subtree Merging and You của 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

(Cần có tùy chọn --allow-unrelated-historiescho Git> = 2.9.0.)

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


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


1
Điều này hoạt động và dường như để lưu giữ lịch sử, nhưng không phải là bạn có thể sử dụng nó để tìm các tệp khác biệt hoặc chia đôi thông qua hợp nhất. Tôi có thiếu một bước không?
jettero

56
Điều này là không đầy đủ . Có, bạn nhận được vô số cam kết, nhưng chúng không còn đề cập đến các đường dẫn đúng. git log dir-B/somefilesẽ không hiển thị bất cứ điều gì ngoại trừ một sự 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

2
QUAN TRỌNG: git pull --no-rebase -s Subree Bproject master Nếu bạn không làm điều đó và bạn đã tự động thiết lập để rebase, 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 - tóm tắt -

4
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 được hợp nhất khi trong câu hỏi đó là A. Kết quả của một bản sao và dán?
vfclists

11
Nếu bạn đang cố gắng đơn giản kết dính hai kho lưu trữ lại với nhau, các mô hình con và phép hợp nhất là công cụ sai để sử dụng vì chúng không lưu giữ tất cả lịch sử tệp (như các nhà bình luận khác đã lưu ý). Xem stackoverflow.com/questions/13040958/ Mạnh .
Eric Lee

194

Cách tiếp cận mô hình con là tốt nếu bạn muốn duy trì dự án 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ó thêm một chút việc phải làm.

Điều đầu tiên sẽ là sử dụng git filter-branchđể viết lại tên của mọi thứ trong kho lưu trữ 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.htmlbạn sẽ có projb/foo.cprojb/bar.html.

Sau đó, bạn sẽ có thể làm một cái gì đó như sau:

git remote add projb [wherever]
git pull projb

Các git pullsẽ làm một git fetchtheo sau là một git merge. Không nên có xung đột, nếu kho lưu trữ mà bạn đang kéo đến chưa có projb/thư 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 gitkvào git. Junio ​​C Hamano viết về nó ở đây: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


4
hợp nhất cây con 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 được bao gồm
Jakub Narębski

8
Tôi muốn biết làm thế nào để sử dụng git filter-branchđể đạt được điều này. Trong trang man, nó nói về cách ngược lại: tạo subir / trở thành root, nhưng không phải là cách khác.
artfulrobot

31
câu trả lời này sẽ rất tuyệt nếu nó giải thích cách sử dụng nhánh lọc để đạt được kết quả mong muốn
Anentropic

14
Tôi đã tìm thấy cách sử dụng nhánh bộ lọc tại đây: stackoverflow.com/questions / 4042816 / trên
David Minor

3
Xem câu trả lời này để thực hiện phác thảo của Greg.
Paul Draper

75

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

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

git log projectA

chỉ liệt kê một cam kết: hợp nhất. Các cam kết từ dự án được 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 là gần nhất, mặc dù thực tế không nói cách viết lại các đường dẫn.


Giải pháp đơn giản đến bất ngờ.

(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 ý: Điều này viết lại lịch sử, vì vậy nếu bạn có ý định tiếp tục sử dụng repo A này, trước tiên bạn có thể muốn sao chép (sao chép) một bản sao của nó.

Lưu ý Bene: Bạn phải sửa đổi tập lệnh thay thế bên trong lệnh sed trong trường hợp bạn sử dụng các ký tự không phải mã ascii (hoặc ký tự trắng) trong tên tệp hoặc đường dẫn. Trong trường hợp đó, vị trí tệp bên trong một bản ghi được tạo bởi "ls-files -s" bắt đầu bằng dấu ngoặc kép.

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

git pull path/to/A

Voila! Bạn có một projectAthư mục trong B. Nếu bạn chạy git log projectA, bạn sẽ thấy tất cả các xác nhận từ A.


Trong trường hợp của tôi, tôi muốn có hai thư mục con projectAprojectB. Trong trường hợp đó, tôi cũng đã thực hiện bước (1) đến B.


1
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

1
@AndrewMao, tôi nghĩ vậy ... Tôi thực sự không thể nhớ. Tôi đã sử dụng kịch bản này khá nhiều.
Paul Draper

6
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

2
"$GIT_INDEX_FILE"phải được trích dẫn (hai lần), nếu không phương thức của bạn sẽ thất bại nếu ví dụ: đường dẫn chứa khoảng trắng.
Cướp

4
Nếu bạn đang tự hỏi, để chèn <tab> vào osx, bạn cần phảiCtrl-V <tab>
casey

48

Nếu cả hai kho lưu trữ có cùng loại tệp (như hai kho lưu trữ Rails cho các dự án khác nhau), bạn có thể tìm nạp 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 chẳng hạn với git mergetool. kdiff3chỉ có thể được sử dụng 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.

Nhớ hoàn thành việc hợp nhất:

git commit

25

Tôi liên tục bị 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 hợp nhất ở mỗi lần xác nhận:

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, bao 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 và theo sau là các cam kết từ projB


25

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

Về cơ bản, tôi viết lại lịch sử của my-pluginkho lưu trữ để nó xuất hiện tất cả sự phát triển diễn ra trong plugins/my-pluginthư mục con. Sau đó, tôi thêm lịch sử phát triển my-pluginvào main-projectlịch sử và hợp nhất hai cây lại với nhau. Vì không có plugins/my-pluginthư mục nào có trong main-projectkho lưu trữ, đây là một sự hợp nhất không xung đột tầm thường. Kho lưu trữ kết quả chứa tất cả lịch sử từ cả hai dự án ban đầu 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|plugins) 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-pluginkho 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-pluginkho lưu trữ, kiểm tra nhánh chính của bạn (có thể master) và chạy lệnh sau. Tất nhiên, bạn nên thay thế my-pluginpluginsbất kể 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|plugins) plugins/my-plugin || true)'" -- --all

Bây giờ để giải thích. git filter-branch --tree-filter (...) HEADchạ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 lần xác nhận, vì vậy chúng tôi không phải lo lắng về các 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 một filter-branchlệnh không thành công, nó sẽ để lại một số tệp trong .gitthư mục và lần sau bạn thử, filter-branchnó sẽ phàn nàn về điều này, trừ khi bạn cung cấp -ftùy chọn này 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để thực zshhiện một lệnh. Đầu tiên tôi đặt extended_globtùy chọn, đó là cái cho phép ^(...)cú pháp trong mvlệnh, cũng như glob_dotstùy chọn, cho phép tôi chọn dotfiles (chẳng hạn như .gitignore) với một global ( ^(...)).

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

Cuối cùng, tôi sử dụng tính năng zsh"tiêu cực toàn cầu" ^(.git|plugins)để khớp với tất cả các tệp trong thư mục gốc của kho lưu trữ ngoại trừ .gitmy-pluginthư mục mới được tạo . (Không bao gồm .gitcó 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 mvlệnh đã trả về lỗi trên cam kết ban đầu (vì không có gì để di chuyển). Vì vậy, tôi đã thêm một || trueđể git filter-branchkhông phá thai.

Các --alltùy chọn bảo filter-branchphải viết lại lịch sử cho tất cả các chi nhánh trong kho, và thêm --là cần thiết để nói gitđể giải thích nó như là một phần của danh sách lựa chọn cho các chi nhánh để viết lại, thay vì như một tùy chọn để filter-branchbản thân.

Bây giờ, điều hướng đến main-projectkho lưu trữ của bạn và kiểm tra bất kỳ chi nhánh nào bạn muốn hợp nhất vào. Thêm bản sao cục bộ của my-pluginkho lưu trữ (với lịch sử đã được sửa đổi) dưới dạng từ xa main-projectvớ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 mình, bạn có thể hình dung độc đáo bằng cách sử dụng:

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

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

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

Lưu ý rằng trong Git trước 2.9.0, --allow-unrelated-historiestùy chọn không tồn tại. Nếu bạn đang sử dụng một trong những phiên bản này, chỉ cần bỏ qua tùy chọn: thông báo lỗi --allow-unrelated-historiesngăn chặn cũng đượ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ếu bạn làm như vậy, điều đó có thể có nghĩa là filter-branchlệnh không hoạt động chính xác hoặc đã có một plugins/my-pluginthư mục trong đó main-project.

Hãy chắc chắn nhập một 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 không biết tin tặc nào đang diễn ra để tạo một kho lưu trữ có hai gốc.

Bạn có thể hình dung biểu đồ cam kết mới, cần có hai xác nhận gốc, sử dụng git loglệnh trên . Lưu ý rằng chỉ có masterchi 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 trên các my-pluginnhánh khác mà bạn muốn hợp nhất vào main-projectcây, bạn nên hạn chế xóa my-pluginđiều khiển từ xa cho đến khi bạn thực hiện các kết hợp này. Nếu bạn không, thì các cam kết từ các nhánh đó sẽ vẫn nằm trong main-projectkho lưu trữ, nhưng một số sẽ không thể truy cập được và dễ bị thu gom rác cuối cùng. (Ngoài ra, bạn sẽ phải tham khảo chúng bằng SHA, vì việc xóa một điều khiển 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ữ my-plugin, bạn có thể xóa my-pluginđiều khiển từ xa bằng cách sử dụng:

$ git remote remove my-plugin

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


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

Người giới thiệu:


1
Nơi --allow-unrelated-historiesđến từ đâu?
xpto

3
@MarceloFilho Kiểm tra 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ẻ 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 chúng một cách độc lập. Vì đó là một trường hợp rất hiếm, không có biến cấu hình để kích hoạt tính năng 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?
xpto

2
@MarceloFilho Điều này đã được thêm vào 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/ cường
Radon Rosborough

Điều này làm việc tốt. Và tôi đã có thể sử dụng nhánh bộ lọc để viết lại tên tệp vào nơi tôi muốn trong cây trước khi hợp nhất. Tôi cho rằng sẽ có nhiều công việc liên quan hơn nếu bạn cần di chuyển lịch sử bên cạnh nhánh chính.

9

Tôi đã cố gắng làm điều tương tự trong nhiều ngày, tôi đang sử dụng git 2.7.2. Subtree không bảo tồn lịch sử.

Bạn có thể sử dụng phương pháp này nếu bạn sẽ không sử dụng lại dự án cũ.

Tôi muốn đề nghị bạn chi nhánh B trước và làm việc trong chi nhánh.

Dưới đây là các bước không phân nhánh:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Nếu bây giờ bạn đăng nhập bất kỳ tệp nào trong thư mục con A, bạn sẽ nhận được toàn bộ lịch sử

git log --follow A/<file>

Đây là bài viết giúp tôi làm điều này:

http://saintgimp.org/2013/01/22/merging-two-git-reposeocate-into-one-reposeective-without-loses-file-history/


8

Nếu bạn muốn đặt các tệp từ một nhánh trong repo B trong một cây con của repo A cũng bảo tồn lịch sử, hãy tiếp tục đọc. (Trong ví dụ dưới đây, tôi giả sử rằng chúng tôi muốn chi nhánh chính của repo B sáp nhập vào chi nhánh chính của repo A.)

Trong repo A, trước tiên hãy làm như sau để cung cấp repo B:

git remote add B ../B # Add repo B as a new remote.
git fetch B

Bây giờ chúng tôi tạo một chi nhánh hoàn toàn mới (chỉ có một cam kết) trong repo A mà chúng tôi gọi new_b_root. Cam kết kết quả sẽ có các tệp đã được cam kết trong cam kết đầu tiên của nhánh chính của repo B nhưng được đặt trong thư mục con được gọi path/to/b-files/.

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

Giải thích: --orphanTùy chọn cho lệnh thanh toán kiểm tra các tệp từ nhánh chính của A nhưng không tạo ra bất kỳ cam kết nào. Chúng tôi có thể đã chọn bất kỳ cam kết nào vì tiếp theo chúng tôi sẽ xóa tất cả các tệp. Sau đó, chưa cam kết ( -n), chúng tôi sẽ chọn cam kết đầu tiên từ nhánh chính của B. (Cherry-pick giữ thông điệp cam kết ban đầu mà thanh toán thẳng dường như không thực hiện.) Sau đó, chúng tôi tạo ra cây con nơi chúng tôi muốn đặt tất cả các tệp từ repo B. Sau đó chúng tôi phải di chuyển tất cả các tệp được giới thiệu trong cherry-pick đến cây con. Trong ví dụ trên, chỉ có một READMEtệp để di chuyển. Sau đó, chúng tôi cam kết cam kết gốc B-repo của chúng tôi, đồng thời, chúng tôi cũng duy trì dấu thời gian của cam kết ban đầu.

Bây giờ, chúng tôi sẽ tạo một B/masterchi nhánh mới trên đầu trang mới được tạo new_b_root. Chúng tôi gọi chi nhánh mới b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

Bây giờ, chúng tôi hợp nhất bchi nhánh của chúng tôi vào A/master:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

Cuối cùng, bạn có thể xóa các Bnhánh từ xa và tạm thời:

git remote remove B
git branch -D new_b_root b

Biểu đồ cuối cùng sẽ có cấu trúc như thế này:

nhập mô tả hình ảnh ở đây


Câu trả lời tuyệt vời, cảm ơn! Tôi thực sự đã bỏ lỡ trong các câu trả lời khác với "git Subree" hoặc "merge --allow-không liên quan đến lịch sử" từ Andresch Serj rằng thư mục con không có nhật ký.
Ilendir

8

Tôi đã thu thập rất nhiều thông tin ở đây trên Stack OverFlow, v.v., và đã quản lý để đặt một tập lệnh cùng nhau giải quyết vấn đề cho tôi.

Thông báo trước là nó chỉ tính đến nhánh 'phát triển' của mỗi kho lưu trữ và hợp nhất nó vào một thư mục riêng trong một kho lưu trữ hoàn toàn mới.

Thẻ và các chi nhánh khác bị bỏ qua - đây có thể không phải là những gì bạn muốn.

Kịch bản thậm chí xử lý các nhánh và thẻ tính năng - đổi tên chúng trong dự án mới để bạn biết chúng đến từ đâu.

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi


# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi


# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo


# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})


  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done


# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

Bạn cũng có thể lấy nó từ http://paste.ubfox.com/11732805

Đầu tiên tạo một tệp có URL tới từng kho lưu trữ, ví dụ:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

Sau đó gọi kịch bản đưa ra tên của dự án và đường dẫn đến kịch bản:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

Bản thân kịch bản có rất nhiều ý kiến ​​cần giải thích những gì nó làm.


Thay vì hướng người đọc đến một câu trả lời, xin vui lòng gửi câu trả lời ở đây (hay chỉnh sửa những gì bạn đã nói trong nhận xét đó vào câu trả lời này).
josliber

1
Chắc chắn, chỉ cần nghĩ rằng tốt hơn là đừng lặp lại chính mình ... =)
eitch

Nếu bạn nghĩ rằng câu hỏi này giống hệt với câu hỏi khác, thì bạn có thể gắn cờ nó thành một bản sao bằng cách sử dụng liên kết "cờ" bên dưới câu hỏi và chỉ ra câu hỏi khác. Nếu đó không phải là một câu hỏi trùng lặp nhưng bạn nghĩ rằng cùng một câu trả lời có thể được sử dụng để giải quyết cả hai vấn đề, thì chỉ cần đăng cùng một câu trả lời cho cả hai vấn đề (như bạn đã làm bây giờ). Cảm ơn đã đóng góp!
josliber

Kinh ngạc! Không hoạt động trên dấu nhắc bash của Windows, nhưng nó chạy hoàn hảo tạo thành một hộp Vagrant chạy ubfox. Thật là tiết kiệm thời gian!
xverges

Rất vui khi được phục vụ =)
eitch

7

Tôi biết rằng rất lâu sau đó, nhưng tôi không hài lòng với những câu trả lời khác mà tôi tìm thấy ở đây, vì vậy tôi đã viết điều này:

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done

2
Đây chính xác là những gì tôi đang tìm kiếm. Cảm ơn! Tuy nhiên, tôi đã phải đổi dòng 22 thành:if [[ $dirname =~ ^.*\.git$ ]]; then
heyman

2
^. * blarg $ là RE tham lam một cách lãng phí. Tốt hơn để nói .blarg $ và bỏ qua neo phía trước.
jettero

7

Nếu bạn đang cố gắng chỉ đơn giản là dán hai kho lưu trữ lại với nhau, các mô hình con và phép hợp nhất là công cụ sai để sử dụng vì chúng không lưu giữ tất cả lịch sử tệp (như mọi người đã lưu ý trong các câu trả lời khác). Xem câu trả lời này ở đây để biết cách đơn giản và chính xác để làm điều này.


1
Giải pháp của bạn chỉ hoạt động tốt cho kho lưu trữ mới, nhưng làm thế nào để hợp nhất repo trong một kho khác với xung đột tệp?
Andrey Izman

6

Tôi đã có một thử thách tương tự, nhưng trong trường hợp của tôi, chúng tôi đã phát triển một phiên bản của cơ sở mã trong repo A, sau đó nhân bản nó thành một repo mới, repo B, cho phiên bản mới của sản phẩm. Sau khi sửa một số lỗi trong repo A, chúng tôi cần FI thay đổi thành repo B. Đã kết thúc như sau:

  1. Thêm một điều khiển từ xa để repo B mà chỉ vào repo A (git remote add ...)
  2. Kéo nhánh hiện tại (chúng tôi không sử dụng master để sửa lỗi) (git pull remoteForRepoA bugFixBranch)
  3. Đẩy sáp nhập vào github

Làm việc một điều trị :)


5

Tương tự như @Smar nhưng sử dụng đường dẫn hệ thống tệp, được đặt trong CHÍNH và THỨ HAI:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

Sau đó, bạn hợp nhất thủ công.

(phỏng theo bài của Anar Manafov )


5

Sáp nhập 2 repos

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master

4

Khi bạn muốn kết hợp ba hoặc nhiều dự án trong một đơn cam kết, làm các bước như mô tả trong câu trả lời khác ( remote add -f, merge). Sau đó, (mềm) đặt lại chỉ mục về đầu cũ (nơi không xảy ra hợp nhất). Thêm tất cả các tệp ( git add -A) và cam kết chúng (thông báo "Hợp nhất các dự án A, B, C và D vào một dự án). Đây hiện là id cam kết của chủ.

Bây giờ, tạo .git/info/graftsvới nội dung sau:

<commit-id of master> <list of commit ids of all parents>

Chạy đi git filter-branch -- head^..head head^2..head head^3..head. Nếu bạn có nhiều hơn ba nhánh, chỉ cần thêm nhiều head^n..headnhư bạn có các nhánh. Để cập nhật thẻ, nối thêm --tag-name-filter cat. Không phải lúc nào cũng thêm điều đó, bởi vì điều này có thể gây ra việc viết lại một số cam kết. Để biết chi tiết, xem trang hướng dẫn của chi nhánh bộ lọc , tìm kiếm "mảnh ghép".

Bây giờ, cam kết cuối cùng của bạn có cha mẹ đúng liên quan.


1
Đợi đã, tại sao bạn muốn hợp nhất ba dự án trong một cam kết?
Steve Bennett

Tôi bắt đầu với kho lưu trữ, kho lưu trữ-máy khách và nhà tạo mô hình như các dự án git riêng biệt. Điều này là khó khăn cho các đồng nghiệp, vì vậy tôi đã tham gia cùng họ trong một dự án git duy nhất. Để có thể "gốc" của dự án mới bắt nguồn từ ba dự án khác, tôi muốn có một cam kết hợp nhất.
koppor

4

Để hợp nhất A trong B:

1) Trong dự án A

git fast-export --all --date-order > /tmp/ProjectAExport

2) Trong dự án B

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

Trong nhánh này thực hiện tất cả các hoạt động bạn cần làm và cam kết chúng.

C) Sau đó trở lại tổng thể và hợp nhất cổ điển giữa hai nhánh:

git checkout master
git merge projectA

2

Chức năng này sẽ sao chép repo từ xa vào thư mục repo cục bộ, sau khi hợp nhất tất cả các xác nhận sẽ được lưu, git logsẽ hiển thị các cam kết ban đầu và các đường dẫn thích hợp:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

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

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Cách sử dụng:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Nếu thực hiện một số thay đổi, bạn thậm chí có thể di chuyển tệp / thư mục của repo đã hợp nhất vào các đường dẫn khác nhau, ví dụ:

repo="https://github.com/example/example"
path="$(pwd)"

tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g' | sed 's/\./_/g')"

git clone "$repo" "$tmp"
cd "$tmp"

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"

Thông báo
Đường dẫn thay thế thông qua sed, vì vậy hãy chắc chắn rằng nó di chuyển theo các đường dẫn thích hợp sau khi hợp nhất.
Các --allow-unrelated-historiestham số chỉ tồn tại kể từ khi git> = 2,9.


1

Lệnh đã cho là giải pháp tốt nhất có thể tôi đề nghị.

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

1

Tôi hợp nhất các dự án một cách thủ công, điều này cho phép tôi tránh phải xử lý các xung đột hợp nhất.

đầu tiên, sao chép trong các tập tin từ dự án khác theo cách bạn muốn.

cp -R myotherproject newdirectory
git add newdirectory

lần kéo tiếp theo trong lịch sử

git fetch path_or_url_to_other_repo

nói với git để hợp nhất trong lịch sử của điều cuối cùng

echo 'FETCH_HEAD' > .git/MERGE_HEAD

bây giờ cam kết tuy nhiên bạn thường cam kết

git commit

0

Tôi muốn chuyển một dự án nhỏ sang một thư mục con của một dự án lớn hơn. Vì dự án nhỏ của tôi không có nhiều cam kết, tôi đã sử dụng git format-patch --output-directory /path/to/patch-dir. Sau đó, trên dự án lớn hơn, tôi đã sử dụng git am --directory=dir/in/project /path/to/patch-dir/*.

Điều này cảm thấy cách ít đáng sợ và cách hơn sạch hơn một filter-branch. Cấp, nó có thể không được áp dụng cho tất cả các trường hợp.

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.