two - github merge repo




你如何合併兩個Git倉庫? (14)

git-subtree很好,但它可能不是你想要的。

例如,如果projectA是在B中創建的目錄,那麼在git subtree

git log projectA

列出一個提交:合併。 合併項目的提交適用於不同的路徑,所以它們不顯示。

Greg Hewgill的答案最接近,但實際上並沒有說如何重寫路徑。

解決方案非常簡單。

(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

注意:這會重寫歷史記錄,因此如果您打算繼續使用此回購A,則可能需要先克隆(複製)它的一次性拷貝。

(2)然後在B中運行

git pull path/to/A

瞧! 您在B中有一個projectA目錄。如果您運行git log projectA ,您將看到來自A的所有提交。

在我的情況下,我想要兩個子目錄, projectAprojectB 。 在那種情況下,我也對B進行了步驟(1)。

考慮以下情況:

我在自己的Git回購中開發了一個小型實驗項目A. 它現在已經成熟了,我希望A成為大型項目B的一部分,它有自己的大型倉庫。 我現在想添加A作為B的一個子目錄。

如何將A合併到B中,而不會在任何一方丟失歷史記錄?


Given command is the best possible solution I suggest.

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

Merging 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

This function will clone remote repo into local repo dir, after merging all commits will be saved, git log will be show the original commits and proper paths:

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"
}

如何使用:

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

If make a little changes you can even move files/dirs of merged repo into different paths, for example:

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"

Notices
Paths replaces via sed , so make sure it moved in proper paths after merging.
The --allow-unrelated-histories parameter only exists since git >= 2.9.


另一個存儲庫的單個分支可以輕鬆放置在保留其歷史記錄的子目錄下。 例如:

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

這將顯示為一個單獨的提交,Rails主分支的所有文件都被添加到“rails”目錄中。 然而,提交的標題包含對舊曆史樹的引用。

Add 'rails/' from commit <rev>

其中<rev>是SHA-1提交散列。 你仍然可以看到歷史,並指責一些變化。

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

請注意,您從這裡看不到目錄前綴,因為這是一個完整的實際舊分支。 你應該像通常的文件移動提交一樣對待它:當達到它時你需要額外的跳轉。

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

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

有其他更複雜的解決方案,如手動執行此操作或重寫其他答案中所述的歷史記錄。

git-subtree命令是官方git-contrib的一部分,一些數據包管理器默認安裝它(OS X Homebrew)。 但是除了git之外,你可能還得自己安裝它。


如果你想合併project-a -a到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

取自: git合併不同的存儲庫?

這種方法對我來說效果不錯,它更短,在我看來更清潔。

注意: -- --allow-unrelated-histories參數僅在git> = 2.9之後才存在。 請參閱Git - git merge Documentation / --allow-unrelated-history


如果您試圖簡單地將兩個存儲庫粘合在一起,則子模塊和子樹合併是錯誤的使用工具,因為它們不保留所有文件歷史記錄(正如人們在其他答案中註意到的)。 here看到這個答案的簡單和正確的方法來做到這一點。


如果要分開維護項目,子模塊方法很好。 但是,如果您真的想將兩個項目合併到同一個存儲庫中,那麼您需要做更多工作。

第一件事是使用git filter-branch將第二個存儲庫中的所有內容的名稱重新寫入子目錄中,最後讓它們結束。 因此,而不是foo.cbar.html ,你會有projb/foo.cprojb/bar.html

然後,你應該能夠做到以下幾點:

git remote add projb [wherever]
git pull projb

git pull會執行git fetch然後是git merge 。 如果您正在使用的存儲庫尚未具有projb/目錄,則不應該有衝突。

進一步的搜索表明,將gitk合併到git做了類似的事情。 Junio C Hamano在這裡寫下: http://www.mail-archive.com/[email protected]/msg03395.html : http://www.mail-archive.com/[email protected]/msg03395.html


就我而言,我有一個my-plugin存儲庫和一個main-project存儲庫,我想假裝my-plugin一直是在main-projectplugins子目錄下main-project

基本上,我重寫了my-plugin存儲庫的歷史記錄,以便它看起來所有的開發發生在plugins/my-plugin子目錄中。 然後,我將my-plugin的開發歷史記錄添加到main-project歷史記錄中,並將兩棵樹合併在一起。 由於main-project存儲庫中不存在plugins/my-plugin目錄,因此這是一個無關緊要的無衝突合併。 生成的存儲庫包含兩個原始項目的所有歷史記錄,並且有兩個根。

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

長版

首先,創建一個my-plugin存儲庫的副本,因為我們將重寫此存儲庫的歷史記錄。

現在,導航到my-plugin存儲庫的根目錄,檢查你的主分支(可能是master ),然後運行以下命令。 當然,無論你的名字是什麼,你都應該替換my-pluginplugins

$ 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

現在解釋一下。 git filter-branch --tree-filter (...) HEAD在每個可從HEAD訪問的提交上運行(...)命令。 請注意,這直接針對每次提交所存儲的數據進行操作,因此我們不必擔心“工作目錄”,“索引”,“分段”等概念。

如果運行失敗的filter-branch命令,它會在.git目錄中留下一些文件,下次嘗試filter-branch它會抱怨這一點,除非將-f選項提供給filter-branch

至於實際的命令,我沒有太多的運氣讓bash去做我想做的事情,所以我使用zsh -c來讓zsh執行一個命令。 首先,我設置了extended_glob選項,這是在mv命令中啟用^(...)語法的原因,以及glob_dots選項,它允許我使用glob( ^(...) )選擇點文件(例如.gitignore^(...) )。

接下來,我使用mkdir -p命令同時創建pluginsplugins/my-plugin

最後,我使用zsh “negative glob”特性^(.git|my-plugin)來匹配存儲庫根目錄下除.git和新創建的my-plugin文件夾之外的所有文件。 (不包括.git在這裡可能不需要,但是嘗試將目錄移入本身是一個錯誤。)

在我的存儲庫中,初始提交沒有包含任何文件,因此mv命令在初始提交時返回了一個錯誤(因為沒有任何可移動的東西)。 因此,我添加了一個|| true || true以便git filter-branch不會中止。

--all選項告訴filter-branch重寫存儲庫中所有分支的歷史記錄,extra --有必要告訴git將它解釋為分支要重寫的選項列表的一部分,而不是作為選項filter-branch本身。

現在,導航到您的main-project存儲庫並查看想要合併到的任何分支。 將my-plugin庫的本地副本(修改其歷史記錄)添加為遠程main-project

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

現在,您將在提交歷史記錄中擁有兩棵不相關的樹,您可以很好地使用它們可視化:

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

要合併它們,請使用:

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

請注意,在2.9.0之前的Git中,-- --allow-unrelated-histories選項不存在。 如果您正在使用這些版本中的一個,只需省略該選項: 2.9.0 添加了 --allow-unrelated-histories可防止的錯誤消息

你不應該有任何合併衝突。 如果這樣做,可能意味著filter-branch命令無法正常工作,或者main-project已經有一個plugins/my-plugin目錄。

確保輸入一個解釋性的提交信息,以供將來的貢獻者想知道什麼hackery正在創建一個有兩個根的倉庫。

您可以使用上面的git log命令可視化新提交圖,該圖應該有兩個根提交。 請注意, 只有master分支將被合併 。 這意味著,如果您在其他要插入main-project樹的my-plugin分支上有重要工作,則應該避免刪除my-plugin遠程,直到完成這些合併。 如果你不這樣做,那麼來自這些分支的提交將仍然在main-project存儲庫中,但有些將無法訪問並容易被最終垃圾收集。 (另外,您將不得不通過SHA來引用它們,因為刪除遠程設備會刪除其遠程跟踪分支。)

或者,在合併了所有要從my-plugin保留的所有內容之後,可以使用以下命令刪除my-plugin remote:

$ git remote remove my-plugin

現在可以安全地刪除其歷史記錄已更改的my-plugin存儲庫的副本。 就我而言,我還在合併完成並推送後向真實的my-plugin存儲庫添加了棄用通知。

使用git --version 2.9.0zsh --version 5.2測試Mac OS X El Capitan。 你的旅費可能會改變。

參考文獻:


我一直試圖做同樣的事情幾天,我使用git 2.7.2。 子樹不保存歷史記錄。

如果您不再使用舊項目,則可以使用此方法。

我建議你先分支B,然後在分支上工作。

以下是沒有分支的步驟:

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

如果您現在記錄子目錄A中的任何文件,您將獲得完整的歷史記錄

git log --follow A/<file>

這是幫助我這樣做的帖子:

saintgimp.org/2013/01/22/…


我在使用合併時一直丟失歷史記錄,所以我最終使用了rebase,因為在我的情況下,這兩個存儲庫足夠不同,最終不會在每次提交時合併:

git clone [email protected]/projA.git projA
git clone [email protected]/projB.git projB

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

=>解決衝突,然後根據需要多次繼續......

git rebase --continue

這樣做會導致一個項目包含來自projA的所有提交,然後是來自projB的提交


我知道事情過了很久,但我對我在這裡找到的其他答案並不滿意,所以我寫了這樣的:

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

與@Smar類似,但使用文件系統路徑,在PRIMARY和SECONDARY中設置:

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

然後你手動合併。

由Anar Manafov改編)


這裡有兩種可能的解決方案

子模塊

將存儲庫A複製到大型項目B中的獨立目錄中,或者(可能更好)將存儲庫A複製到項目B中的子目錄中。然後使用git子模塊將此存儲庫作為存儲庫B的子模塊

對於鬆散耦合的存儲庫而言,這是一個很好的解決方案,存儲庫A中的開發仍在繼續,而且大部分開發都是在A中單獨獨立開發。另請參閱Git Wiki上的SubmoduleSupportGitSubmoduleTutorial頁面。

子樹合併

您可以使用子樹合併策略將存儲庫A合併到項目B的子目錄中。 這在Markus Prinz的Subtree Merging and You中有描述。

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需要選項--allow-unrelated-histories允許--allow-unrelated-histories

或者你可以使用apenwarr(Avery Pennarun)的git subtree工具( github上的倉庫),例如在他的博客文章中公佈git子模塊的新選擇:git subtree

我認為你的情況(A是要成為大型項目B的一部分),正確的解決方案是使用子樹合併





git-subtree