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-pluginvà pluginsbấ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 pluginsvà plugins/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ừ .gitvà my-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.0và zsh --version 5.2. Số dặm của bạn có thể thay đổi.
Người giới thiệu: