考虑以下场景:

我在自己的Git repo中开发了一个小型实验项目a。它现在已经成熟,我希望A成为更大的项目B的一部分,该项目有自己的大仓库。现在我想将A添加为B的子目录。

我如何将A合并为B,而不丢失任何方面的历史?


当前回答

谷歌有一个Copybara工具,用于更复杂的用例-https://github.com/google/copybara

其他回答

与@Smar类似,但使用在PRIMARY和SECONDARY中设置的文件系统路径:

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

然后手动合并。

(改编自阿纳尔·马纳福夫的帖子)

以下是两种可能的解决方案:

子模块

要么将存储库A复制到较大项目B中的单独目录中,要么(也许更好)将存储库B克隆到项目B的子目录中。然后使用git子模块将此存储库设置为存储库B的子模块。

对于松散耦合的存储库来说,这是一个很好的解决方案,存储库a中的开发仍在继续,而开发的主要部分是a中的独立开发。另请参阅GitWiki上的SubmoduleSupport和GitSubmoduleTutorial页面。

子树合并

您可以使用子树合并策略将存储库A合并到项目B的子目录中。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

(选项--Git>=2.9.0需要允许不相关的历史记录。)

或者你可以使用apenwarr(Avery Pennarun)的git子树工具(GitHub上的存储库),例如,在他的博客文章《git子模块的新替代方案:git子树》中宣布了这一点。


我认为在您的情况下(A是大型项目B的一部分),正确的解决方案是使用子树合并。

https://github.com/hraban/tomono作为基于脚本的解决方案的另一个例子。

我不是作者,但使用了它,它完成了任务。

一个积极的方面是,你将所有分支机构和所有历史记录纳入最终回购。对于我的repo(repo中没有重复的文件夹-实际上,它们来自tfs2git迁移),没有冲突,一切都是自动运行的。

它主要用于(参见名称)创建monoreos。

对于Windows用户:gitbash可以执行.sh文件。它带有标准的git安装。

如果您试图简单地将两个存储库粘合在一起,那么子模块和子树合并是错误的工具,因为它们不能保留所有的文件历史记录(正如人们在其他答案中所指出的)。请参阅此处的答案,了解简单而正确的方法。

此函数将远程repo克隆到本地repo目录中,合并后将保存所有提交,git日志将显示原始提交和正确路径:

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

如果进行一些更改,您甚至可以将合并的repo的文件/目录移动到不同的路径中,例如:

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"

通知路径将通过sed替换,因此请确保在合并后将其移动到正确的路径中。--allow非相关历史参数仅在git>=2.9之后存在。