問(wèn):
考慮以下場(chǎng)景:
我在自己的 Git 存儲(chǔ)庫(kù)中開(kāi)發(fā)了一個(gè)小型實(shí)驗(yàn)項(xiàng)目 A。它現(xiàn)在已經(jīng)成熟,我希望 A 成為更大的項(xiàng)目 B 的一部分,它有自己的大存儲(chǔ)庫(kù)。我現(xiàn)在想將 A 添加為 B 的子目錄。
如何在不丟失任何歷史記錄的情況下將 A 合并到 B 中?
huntsbot.com高效搞錢(qián),一站式跟進(jìn)超10+任務(wù)平臺(tái)外包需求
答1:
打造屬于自己的副業(yè),開(kāi)啟自由職業(yè)之旅,從huntsbot.com開(kāi)始!
如果要將 project-a 合并到 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
取自:git merge different repositories?
這種方法對(duì)我來(lái)說(shuō)效果很好,它更短,在我看來(lái)更干凈。
如果要將 project-a 放入子目錄,可以使用 git-filter-repo(filter-branch 是 discouraged)。在上述命令之前運(yùn)行以下命令:
cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a
合并 2 個(gè)大型存儲(chǔ)庫(kù)并將其中一個(gè)放入子目錄的示例:https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731
注意: --allow-unrelated-histories 參數(shù)僅在 git >= 2.9 后才存在。請(qǐng)參閱Git - git merge Documentation / --allow-unrelated-histories
更新:按照@jstadler 的建議添加了–tags,以保留標(biāo)簽。
這為我做了生意。第一次像魅力一樣工作,在 .gitignore 文件中只有一個(gè)沖突!它完美地保留了提交歷史。除了簡(jiǎn)單性之外,其他方法的最大優(yōu)點(diǎn)是不需要持續(xù)引用合并的 repo。但是,需要注意的一件事——如果你是像我這樣的 iOS 開(kāi)發(fā)人員——將目標(biāo) repo 的項(xiàng)目文件放入工作區(qū)時(shí)要非常小心。
謝謝。為我工作。我需要將合并的目錄移動(dòng)到一個(gè)子文件夾中,所以按照上述步驟后,我只使用了 git mv source-dir/ dest/new-source-dir
@sg 一種間接的方法是將 project-a 中的所有這些文件移動(dòng)到 project-a 的子目錄中(這樣 project-a 的頂層只有一個(gè)目錄),然后按照上述過(guò)程進(jìn)行操作。
git merge 步驟在此處以 fatal: refusing to merge unrelated histories 失??; --allow-unrelated-histories 修復(fù)了 docs 中所述的問(wèn)題。
更短:git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD。
答2:
huntsbot.com精選全球7大洲遠(yuǎn)程工作機(jī)會(huì),涵蓋各領(lǐng)域,幫助想要遠(yuǎn)程工作的數(shù)字游民們能更精準(zhǔn)、更高效的找到對(duì)方。
以下是兩種可能的解決方案:
子模塊
將存儲(chǔ)庫(kù) A 復(fù)制到較大項(xiàng)目 B 中的單獨(dú)目錄中,或者(也許更好)將存儲(chǔ)庫(kù) A 克隆到項(xiàng)目 B 中的子目錄中。然后使用 git submodule 將此存儲(chǔ)庫(kù)設(shè)為 子模塊< /strong> 存儲(chǔ)庫(kù) B.
對(duì)于松散耦合的存儲(chǔ)庫(kù)來(lái)說(shuō),這是一個(gè)很好的解決方案,其中存儲(chǔ)庫(kù) A 中的開(kāi)發(fā)繼續(xù)進(jìn)行,并且開(kāi)發(fā)的主要部分是 A 中的單獨(dú)獨(dú)立開(kāi)發(fā)。另請(qǐng)參閱 Git Wiki 上的 SubmoduleSupport 和 GitSubmoduleTutorial 頁(yè)面。
子樹(shù)合并
您可以使用 subtree merge 策略將存儲(chǔ)庫(kù) A 合并到項(xiàng)目 B 的子目錄中。 Markus Prinz 在 Subtree Merging and You 中對(duì)此進(jìn)行了描述。
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
(Git >= 2.9.0 需要選項(xiàng) --allow-unrelated-histories。)
或者您可以使用 apenwarr (Avery Pennarun) 的 git subtree 工具 (repository on GitHub),例如在他的博文 A new alternative to Git submodules: git subtree 中宣布。
我認(rèn)為在您的情況下(A 將成為較大項(xiàng)目 B 的一部分),正確的解決方案是使用子樹(shù)合并。
這有效并且似乎保留了歷史記錄,但不能讓您使用它來(lái)區(qū)分文件或通過(guò)合并一分為二。我錯(cuò)過(guò)了一步嗎?
這是不完整的。是的,您會(huì)收到大量提交,但它們不再引用正確的路徑。 git log dir-B/somefile 除了一個(gè)合并之外不會(huì)顯示任何內(nèi)容。請(qǐng)參閱 Greg Hewgill's answer 引用此重要問(wèn)題。
重要提示: git pull --no-rebase -s subtree Bproject master 如果你不這樣做,并且你已經(jīng)將 pull 設(shè)置為自動(dòng)變基,你最終會(huì)得到“無(wú)法解析對(duì)象”。請(qǐng)參閱osdir.com/ml/git/2009-07/msg01576.html
這個(gè)答案可能會(huì)令人困惑,因?yàn)樗趩?wèn)題中是 A 時(shí)將 B 作為合并的子樹(shù)。復(fù)制和粘貼的結(jié)果?
如果您試圖簡(jiǎn)單地將兩個(gè)存儲(chǔ)庫(kù)粘合在一起,則子模塊和子樹(shù)合并是錯(cuò)誤的工具,因?yàn)樗鼈儾粫?huì)保留所有文件歷史記錄(正如其他評(píng)論者所指出的那樣)。請(qǐng)參閱stackoverflow.com/questions/13040958/…。
答3:
huntsbot.com精選全球7大洲遠(yuǎn)程工作機(jī)會(huì),涵蓋各領(lǐng)域,幫助想要遠(yuǎn)程工作的數(shù)字游民們能更精準(zhǔn)、更高效的找到對(duì)方。
另一個(gè)存儲(chǔ)庫(kù)的單個(gè)分支可以很容易地放置在保留其歷史記錄的子目錄下。例如:
git subtree add --prefix=rails git://github.com/rails/rails.git master
這將顯示為單個(gè)提交,其中 Rails 主分支的所有文件都添加到“rails”目錄中。然而,提交的標(biāo)題包含對(duì)舊歷史樹(shù)的引用:
從提交 添加’rails/’
其中 是 SHA-1 提交哈希。你仍然可以看到歷史,責(zé)怪一些變化。
git log
git blame -- README.md
請(qǐng)注意,您無(wú)法從此處看到目錄前綴,因?yàn)檫@是一個(gè)完好無(wú)損的實(shí)際舊分支。您應(yīng)該將其視為通常的文件移動(dòng)提交:到達(dá)它時(shí)您將需要額外的跳轉(zhuǎn)。
# finishes with all files added at once commit
git log rails/README.md
# then continue from original tree
git log -- README.md
有更復(fù)雜的解決方案,例如手動(dòng)執(zhí)行此操作或重寫(xiě)其他答案中描述的歷史記錄。
git-subtree 命令是官方 git-contrib 的一部分,一些數(shù)據(jù)包管理器默認(rèn)安裝它(OS X Homebrew)。但是除了 git 之外,您可能還必須自己安裝它。
以下是有關(guān)如何安裝 Git SubTree 的說(shuō)明(截至 2013 年 6 月):stackoverflow.com/a/11613541/694469(我將 git co v1.7.11.3 替換為 ... v1.8.3)。
感謝您對(duì)以下答案的提醒。截至 git 1.8.4 'subtree' 仍然不包括在內(nèi)(至少不在 Ubuntu 12.04 git ppa (ppa:git-core/ppa) 上)
我可以確認(rèn),在此之后,git log rails/somefile 將不會(huì)顯示該文件的提交歷史記錄,但合并提交除外。正如@artfulrobot 建議的那樣,檢查 Greg Hewgill's answer。您可能需要在要包含的存儲(chǔ)庫(kù)上使用 git filter-branch。
或者閱讀 Eric Lee 的“將兩個(gè) Git 存儲(chǔ)庫(kù)合并到一個(gè)存儲(chǔ)庫(kù)而不丟失文件歷史記錄”saintgimp.org/2013/01/22/…
正如其他人所說(shuō),git subtree 可能不會(huì)像您想的那樣!有關(guān)更完整的解決方案,請(qǐng)參閱 here。
答4:
huntsbot.com全球7大洲遠(yuǎn)程工作機(jī)會(huì),探索不一樣的工作方式
如果您想單獨(dú)維護(hù)項(xiàng)目,則子模塊方法很好。但是,如果您真的想將兩個(gè)項(xiàng)目合并到同一個(gè)存儲(chǔ)庫(kù)中,那么您還有更多工作要做。
首先是使用 git filter-branch 將第二個(gè)存儲(chǔ)庫(kù)中所有內(nèi)容的名稱重寫(xiě)為您希望它們結(jié)束的子目錄中。因此,您將擁有 projb/foo.c 和 projb/bar.html,而不是 foo.c、bar.html。
然后,您應(yīng)該能夠執(zhí)行以下操作:
git remote add projb [wherever]
git pull projb
git pull 將執(zhí)行 git fetch,然后執(zhí)行 git merge。如果您要拉入的存儲(chǔ)庫(kù)還沒(méi)有 projb/ 目錄,則應(yīng)該沒(méi)有沖突。
進(jìn)一步搜索表明進(jìn)行了類似的操作以將 gitk 合并到 git。 Junio C Hamano 在這里寫(xiě)到:http://www.mail-archive.com/git@vger.kernel.org/msg03395.html
子樹(shù)合并將是更好的解決方案,并且不需要重寫(xiě)包含項(xiàng)目的歷史記錄
我想知道如何使用 git filter-branch 來(lái)實(shí)現(xiàn)這一點(diǎn)。在手冊(cè)頁(yè)中它說(shuō)的是相反的方式:使 subdir/ 成為根,但不是相反。
如果它解釋了如何使用 filter-branch 來(lái)達(dá)到預(yù)期的結(jié)果,這個(gè)答案會(huì)很棒
我在這里找到了如何使用過(guò)濾器分支:stackoverflow.com/questions/4042816/…
有關(guān) Greg 大綱的實(shí)施,請(qǐng)參見(jiàn) this answer。
答5:
huntsbot.com提供全網(wǎng)獨(dú)家一站式外包任務(wù)、遠(yuǎn)程工作、創(chuàng)意產(chǎn)品分享與訂閱服務(wù)!
git-subtree 不錯(cuò),但可能不是您想要的。
例如,如果 projectA 是在 B 中創(chuàng)建的目錄,則在 git subtree 之后,
git log projectA
僅列出一個(gè)提交:合并。合并項(xiàng)目的提交用于不同的路徑,因此它們不會(huì)顯示。
Greg Hewgill 的答案最接近,盡管它實(shí)際上并沒(méi)有說(shuō)明如何重寫(xiě)路徑。
解決方案非常簡(jiǎn)單。
(1) 在 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
注意:這會(huì)改寫(xiě)歷史;您可能需要先備份 A。
注意:如果您在文件名或路徑中使用非 ascii 字符(或白色字符),則必須修改 sed 命令中的替換腳本。在這種情況下,由“l(fā)s-files -s”生成的記錄中的文件位置以引號(hào)開(kāi)頭。
(2) 然后在 B 中運(yùn)行
git pull path/to/A
瞧!您在 B 中有一個(gè) projectA 目錄。如果您運(yùn)行 git log projectA,您將看到來(lái)自 A 的所有提交。
在我的例子中,我想要兩個(gè)子目錄,projectA 和 projectB。在這種情況下,我也對(duì) B 執(zhí)行了步驟 (1)。
您似乎從 stackoverflow.com/a/618113/586086 復(fù)制了您的答案?
@AndrewMao,我想是的......我實(shí)際上不記得了。這個(gè)腳本我用過(guò)不少。
我要補(bǔ)充一點(diǎn) \t 在 OS X 上不起作用,你必須輸入
"$GIT_INDEX_FILE" 必須被引用(兩次),否則如果路徑包含空格,您的方法將失敗。
如果你想知道,插入一個(gè) 在 osx 中,您需要 Ctrl-V
答6:
huntsbot.com洞察每一個(gè)產(chǎn)品背后的需求與收益,從而捕獲靈感
如果兩個(gè)存儲(chǔ)庫(kù)具有相同類型的文件(例如不同項(xiàng)目的兩個(gè) Rails 存儲(chǔ)庫(kù)),您可以將輔助存儲(chǔ)庫(kù)的數(shù)據(jù)獲取到當(dāng)前存儲(chǔ)庫(kù):
git fetch git://repository.url/repo.git master:branch_name
然后將其合并到當(dāng)前存儲(chǔ)庫(kù):
git merge --allow-unrelated-histories branch_name
如果您的 Git 版本小于 2.9,請(qǐng)刪除 --allow-unrelated-histories。
在此之后,可能會(huì)發(fā)生沖突。例如,您可以使用 git mergetool 解決它們。 kdiff3 可以單獨(dú)與鍵盤(pán)一起使用,因此讀取代碼時(shí)只需幾分鐘就需要 5 個(gè)沖突文件。
記得完成合并:
git commit
我喜歡這個(gè)解決方案的簡(jiǎn)單性,它看起來(lái)就像我正在尋找的東西,但它基本上不等同于 git pull --allow-unrelated-histories 嗎?
@Prometheus 有點(diǎn)像。我現(xiàn)在沒(méi)有測(cè)試它,但可能 pull 需要將遠(yuǎn)程存儲(chǔ)庫(kù)添加為真正的遠(yuǎn)程,這只會(huì)將必要的內(nèi)容獲取到分支并合并該內(nèi)容。
答7:
huntsbot.com提供全網(wǎng)獨(dú)家一站式外包任務(wù)、遠(yuǎn)程工作、創(chuàng)意產(chǎn)品分享與訂閱服務(wù)!
在使用合并時(shí),我一直在丟失歷史記錄,所以我最終使用了 rebase,因?yàn)樵谖业那闆r下,兩個(gè)存儲(chǔ)庫(kù)不同,以至于不會(huì)在每次提交時(shí)合并:
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
=> 解決沖突,然后繼續(xù),根據(jù)需要多次…
git rebase --continue
這樣做會(huì)導(dǎo)致一個(gè)項(xiàng)目擁有來(lái)自 projA 的所有提交,然后是來(lái)自 projB 的提交
答8:
打造屬于自己的副業(yè),開(kāi)啟自由職業(yè)之旅,從huntsbot.com開(kāi)始!
在我的例子中,我有一個(gè) my-plugin 存儲(chǔ)庫(kù)和一個(gè) main-project 存儲(chǔ)庫(kù),我想假設(shè) my-plugin 一直是在 main-project 的 plugins 子目錄中開(kāi)發(fā)的。
基本上,我重寫(xiě)了 my-plugin 存儲(chǔ)庫(kù)的歷史,以便所有開(kāi)發(fā)都發(fā)生在 plugins/my-plugin 子目錄中。然后,我將 my-plugin 的發(fā)展歷史添加到 main-project 歷史中,并將兩棵樹(shù)合并在一起。由于 main-project 存儲(chǔ)庫(kù)中沒(méi)有 plugins/my-plugin 目錄,因此這是一個(gè)簡(jiǎn)單的無(wú)沖突合并。生成的存儲(chǔ)庫(kù)包含兩個(gè)原始項(xiàng)目的所有歷史記錄,并且有兩個(gè)根源。
TL;博士
$ 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
長(zhǎng)版
首先,創(chuàng)建 my-plugin 存儲(chǔ)庫(kù)的副本,因?yàn)槲覀儗⒅貙?xiě)此存儲(chǔ)庫(kù)的歷史記錄。
現(xiàn)在,導(dǎo)航到 my-plugin 存儲(chǔ)庫(kù)的根目錄,檢查您的主分支(可能是 master),然后運(yùn)行以下命令。當(dāng)然,無(wú)論您的實(shí)際姓名是什么,您都應(yīng)該替換 my-plugin 和 plugins。
$ 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
現(xiàn)在解釋一下。 git filter-branch --tree-filter (…) HEAD 對(duì)可從 HEAD 訪問(wèn)的每個(gè)提交運(yùn)行 (…) 命令。請(qǐng)注意,這直接對(duì)每次提交存儲(chǔ)的數(shù)據(jù)進(jìn)行操作,因此我們不必?fù)?dān)心“工作目錄”、“索引”、“暫存”等概念。
如果您運(yùn)行的 filter-branch 命令失敗,它會(huì)在 .git 目錄中留下一些文件,并且下次嘗試 filter-branch 時(shí)它會(huì)抱怨此問(wèn)題,除非您向 filter-branch 提供 -f 選項(xiàng).
至于實(shí)際的命令,我沒(méi)有讓 bash 做我想做的事,所以我使用 zsh -c 讓 zsh 執(zhí)行命令。首先,我設(shè)置了 extended_glob 選項(xiàng),它啟用了 mv 命令中的 ^(…) 語(yǔ)法,以及 glob_dots 選項(xiàng),它允許我選擇帶有 glob 的點(diǎn)文件(例如 .gitignore) (^(…))。
接下來(lái),我使用 mkdir -p 命令同時(shí)創(chuàng)建 plugins 和 plugins/my-plugin。
最后,我使用 zsh“負(fù) glob”功能 ^(.git|plugins) 來(lái)匹配存儲(chǔ)庫(kù)根目錄中除 .git 和新創(chuàng)建的 my-plugin 文件夾之外的所有文件。 (此處可能不需要排除 .git,但嘗試將目錄移動(dòng)到自身是錯(cuò)誤的。)
在我的存儲(chǔ)庫(kù)中,初始提交不包含任何文件,因此 mv 命令在初始提交時(shí)返回錯(cuò)誤(因?yàn)闆](méi)有可移動(dòng)的內(nèi)容)。因此,我添加了一個(gè) || true,以便 git filter-branch 不會(huì)中止。
–all 選項(xiàng)告訴 filter-branch 重寫(xiě)存儲(chǔ)庫(kù)中 所有 分支的歷史記錄,并且需要額外的 – 告訴 git 將其解釋為選項(xiàng)列表的一部分用于重寫(xiě)分支,而不是作為 filter-branch 本身的選項(xiàng)。
現(xiàn)在,導(dǎo)航到您的 main-project 存儲(chǔ)庫(kù)并檢查您想要合并到的任何分支。添加 my-plugin 存儲(chǔ)庫(kù)的本地副本(修改其歷史)作為 main-project 的遠(yuǎn)程:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
現(xiàn)在,您的提交歷史中將有兩個(gè)不相關(guān)的樹(shù),您可以使用以下方法很好地可視化它們:
$ git log --color --graph --decorate --all
要合并它們,請(qǐng)使用:
$ git merge my-plugin/master --allow-unrelated-histories
請(qǐng)注意,在 2.9.0 之前的 Git 中,–allow-unrelated-histories 選項(xiàng)不存在。如果您使用的是這些版本之一,只需省略該選項(xiàng):–allow-unrelated-histories 阻止的錯(cuò)誤消息也被添加到 2.9.0 中。
您不應(yīng)該有任何合并沖突。如果這樣做,則可能意味著 filter-branch 命令無(wú)法正常工作,或者 main-project 中已經(jīng)存在 plugins/my-plugin 目錄。
確保為任何未來(lái)的貢獻(xiàn)者輸入一個(gè)解釋性的提交消息,他們想知道黑客正在做什么來(lái)創(chuàng)建一個(gè)有兩個(gè)根的存儲(chǔ)庫(kù)。
您可以使用上面的 git log 命令可視化新的提交圖,它應(yīng)該有兩個(gè)根提交。請(qǐng)注意,只會(huì)合并 master 分支。這意味著,如果您在其他 my-plugin 分支上有重要的工作要合并到 main-project 樹(shù)中,則在完成這些合并之前,您應(yīng)該避免刪除 my-plugin 遠(yuǎn)程。如果您不這樣做,那么來(lái)自這些分支的提交仍將位于 main-project 存儲(chǔ)庫(kù)中,但有些將無(wú)法訪問(wèn)并且容易受到最終垃圾收集的影響。 (此外,您必須通過(guò) SHA 引用它們,因?yàn)閯h除遠(yuǎn)程會(huì)刪除其遠(yuǎn)程跟蹤分支。)
或者,在您合并所有要保留在 my-plugin 中的內(nèi)容后,您可以使用以下方法刪除 my-plugin 遠(yuǎn)程:
$ git remote remove my-plugin
您現(xiàn)在可以安全地刪除您更改了其歷史記錄的 my-plugin 存儲(chǔ)庫(kù)的副本。就我而言,在合并完成并推送后,我還在真正的 my-plugin 存儲(chǔ)庫(kù)中添加了棄用通知。
在帶有 git --version 2.9.0 和 zsh --version 5.2 的 Mac OS X El Capitan 上進(jìn)行了測(cè)試。你的旅費(fèi)可能會(huì)改變。
參考:
https://git-scm.com/docs/git-filter-branch
https://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-include-hidden-from-one-directory-to-another
http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
從 Git 存儲(chǔ)庫(kù)清除文件失敗,無(wú)法創(chuàng)建新備份
git,所有分支上的過(guò)濾器分支
--allow-unrelated-histories 來(lái)自哪里?
@MarceloFilho 檢查 man git-merge。 默認(rèn)情況下,git merge 命令拒絕合并不共享共同祖先的歷史。當(dāng)合并兩個(gè)獨(dú)立開(kāi)始的項(xiàng)目的歷史時(shí),此選項(xiàng)可用于覆蓋此安全性。由于這種情況很少見(jiàn),因此默認(rèn)情況下不存在啟用此功能的配置變量,因此不會(huì)添加。
應(yīng)該在 git version 2.7.2.windows.1 上可用嗎?
@MarceloFilho 這是在 2.9.0 中添加的,但在舊版本中,您不必傳遞該選項(xiàng)(它會(huì)起作用)。 github.com/git/git/blob/…
這運(yùn)作良好。而且我能夠使用過(guò)濾器分支將文件名重寫(xiě)到合并之前樹(shù)中我想要的位置。我想如果您需要在主分支之外移動(dòng)歷史記錄,則需要做更多的工作。
答9:
打造屬于自己的副業(yè),開(kāi)啟自由職業(yè)之旅,從huntsbot.com開(kāi)始!
幾天來(lái)我一直在嘗試做同樣的事情,我使用的是 git 2.7.2。子樹(shù)不保留歷史。
如果您不再使用舊項(xiàng)目,您可以使用此方法。
我建議你先分支 B 并在分支中工作。
以下是沒(méi)有分支的步驟:
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 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
git merge A/
mkdir A
# move all the files into subdir A, excluding .git
git mv 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
如果您現(xiàn)在在 subdir A 中記錄任何文件,您將獲得完整的歷史記錄
git log --follow A/
這是幫助我做到這一點(diǎn)的帖子:
http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/
答10:
huntsbot.com聚合了超過(guò)10+全球外包任務(wù)平臺(tái)的外包需求,尋找外包任務(wù)與機(jī)會(huì)變的簡(jiǎn)單與高效。
如果您想將 repo B 中的分支中的文件放在 repo A 的子樹(shù)中并保留歷史記錄,請(qǐng)繼續(xù)閱讀。 (在下面的示例中,我假設(shè)我們希望將 repo B 的 master 分支合并到 repo A 的 master 分支中。)
在 repo A 中,首先執(zhí)行以下操作以使 repo B 可用:
git remote add B ../B # Add repo B as a new remote.
git fetch B
現(xiàn)在我們?cè)?repo A 中創(chuàng)建一個(gè)全新的分支(只有一個(gè)提交),我們稱之為 new_b_root。生成的提交將包含在 repo B 的主分支的第一次提交中提交的文件,但放在名為 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))"
說(shuō)明: checkout 命令的 --orphan 選項(xiàng)從 A 的主分支中簽出文件,但不創(chuàng)建任何提交。我們可以選擇任何提交,因?yàn)榻酉聛?lái)我們無(wú)論如何都會(huì)清除所有文件。然后,還沒(méi)有提交(-n),我們從 B 的主分支中挑選第一個(gè)提交。 (cherry-pick 保留了直接簽出似乎無(wú)法做到的原始提交消息。)然后我們創(chuàng)建子樹(shù),我們希望將所有文件從 repo B 中放入其中。然后我們必須移動(dòng)引入的所有文件櫻桃采摘到子樹(shù)。在上面的示例中,只有一個(gè) README 文件要移動(dòng)。然后我們提交我們的 B-repo 根提交,同時(shí),我們還保留原始提交的時(shí)間戳。
現(xiàn)在,我們將在新創(chuàng)建的 new_b_root 之上創(chuàng)建一個(gè)新的 B/master 分支。我們將新分支稱為 b:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
現(xiàn)在,我們將 b 分支合并到 A/master:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
最后,您可以刪除 B 遠(yuǎn)程和臨時(shí)分支:
git remote remove B
git branch -D new_b_root b
最終的圖形將具有如下結(jié)構(gòu):
https://i.stack.imgur.com/CB80G.png
很好的答案,謝謝!我真的錯(cuò)過(guò)了來(lái)自 Andresch Serj 的“git subtree”或“merge --allow-unrelated-histories”的其他答案,即子目錄沒(méi)有日志。
答11:
保持自己快人一步,享受全網(wǎng)獨(dú)家提供的一站式外包任務(wù)、遠(yuǎn)程工作、創(chuàng)意產(chǎn)品訂閱服務(wù)–huntsbot.com文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-493788.html
我在這里收集了很多關(guān)于 Stack OverFlow 等的信息,并設(shè)法將一個(gè)腳本放在一起,為我解決了這個(gè)問(wèn)題。
需要注意的是,它只考慮每個(gè)存儲(chǔ)庫(kù)的“開(kāi)發(fā)”分支,并將其合并到一個(gè)全新存儲(chǔ)庫(kù)中的單獨(dú)目錄中。
標(biāo)簽和其他分支被忽略 - 這可能不是你想要的。
該腳本甚至處理功能分支和標(biāo)簽 - 在新項(xiàng)目中重命名它們,以便您知道它們來(lái)自哪里。
#!/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
## - where is the name of the new project to create
## - and 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 "
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
您也可以從 http://paste.ubuntu.com/11732805 獲得它
首先創(chuàng)建一個(gè)包含每個(gè)存儲(chǔ)庫(kù)的 URL 的文件,例如:
git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git
然后調(diào)用提供項(xiàng)目名稱和腳本路徑的腳本:
./mergeGitRepositories.sh eitchnet_test eitchnet.lst
腳本本身有很多注釋?xiě)?yīng)該解釋它的作用。
與其將讀者引導(dǎo)至答案,請(qǐng)?jiān)诖颂幇l(fā)布答案(也就是將您在該評(píng)論中所說(shuō)的內(nèi)容編輯到此答案中)。
當(dāng)然,只是認(rèn)為最好不要重復(fù)自己... =)
如果您認(rèn)為這個(gè)問(wèn)題與另一個(gè)問(wèn)題相同,那么您可以使用問(wèn)題本身下方的“標(biāo)記”鏈接將其標(biāo)記為重復(fù),并指出另一個(gè)問(wèn)題。如果它不是重復(fù)的問(wèn)題,但您認(rèn)為完全相同的答案可用于解決這兩個(gè)問(wèn)題,那么只需對(duì)這兩個(gè)問(wèn)題發(fā)布相同的答案(就像您現(xiàn)在所做的那樣)。感謝您的貢獻(xiàn)!
驚人!在 Windows bash 提示符下不起作用,但它完美地從運(yùn)行 ubuntu 的 Vagrant 盒子中運(yùn)行。多么節(jié)省時(shí)間!
舊的但是.. 用粗體(和/或更大的字體)警告可能是個(gè)好主意?
原文鏈接:https://www.huntsbot.com/qa/7Q53/how-do-you-merge-two-git-repositories?lang=zh_CN&from=csdn文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-493788.html
保持自己快人一步,享受全網(wǎng)獨(dú)家提供的一站式外包任務(wù)、遠(yuǎn)程工作、創(chuàng)意產(chǎn)品訂閱服務(wù)–huntsbot.com
到了這里,關(guān)于如何合并兩個(gè) Git 存儲(chǔ)庫(kù)?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!