我在一个名为XXX的文件夹中有一个Git存储库,还有一个名为YYY的Git存储库。

我想将XXX存储库作为名为ZZZ的子目录导入到YYY存储库中,并将所有XXX的更改历史添加到YYY中。

之前的文件夹结构:

├── XXX
│   ├── .git
│   └── (project files)
└── YYY
    ├── .git
    └── (project files)

文件夹结构后:

YYY
├── .git  <-- This now contains the change history from XXX
├──  ZZZ  <-- This was originally XXX
│    └── (project files)
└──  (project files)

这可以做到吗,或者我必须使用子模块?


当前回答

我可以为你的问题建议另一个解决方案(替代git-submodules) - gil (git链接)工具

它允许描述和管理复杂的git存储库依赖关系。

同时也为git递归子模块依赖问题提供了解决方案。

假设你有以下项目依赖项: 示例git存储库依赖关系图

然后你可以用存储库关系描述定义.gitlinks文件:

# Projects
CppBenchmark CppBenchmark https://github.com/chronoxor/CppBenchmark.git master
CppCommon CppCommon https://github.com/chronoxor/CppCommon.git master
CppLogging CppLogging https://github.com/chronoxor/CppLogging.git master

# Modules
Catch2 modules/Catch2 https://github.com/catchorg/Catch2.git master
cpp-optparse modules/cpp-optparse https://github.com/weisslj/cpp-optparse.git master
fmt modules/fmt https://github.com/fmtlib/fmt.git master
HdrHistogram modules/HdrHistogram https://github.com/HdrHistogram/HdrHistogram_c.git master
zlib modules/zlib https://github.com/madler/zlib.git master

# Scripts
build scripts/build https://github.com/chronoxor/CppBuildScripts.git master
cmake scripts/cmake https://github.com/chronoxor/CppCMakeScripts.git master

每一行描述git链接的格式如下:

存储库的唯一名称 存储库的相对路径(从.gitlinks文件的路径开始) Git存储库,将用于Git克隆命令 要检出的存储库分支 空行或以#开头的行不会被解析(作为注释处理)。

最后,你必须更新你的根示例库:

# Clone and link all git links dependencies from .gitlinks file
gil clone
gil link

# The same result with a single command
gil update

因此,您将克隆所有必需的项目,并以适当的方式将它们相互链接。

如果你想提交一些存储库中的所有更改,以及子链接存储库中的所有更改,你可以用一个命令来完成:

gil commit -a -m "Some big update"

Pull、push命令的工作原理类似:

gil pull
gil push

Gil (git链接)工具支持以下命令:

usage: gil command arguments
Supported commands:
    help - show this help
    context - command will show the current git link context of the current directory
    clone - clone all repositories that are missed in the current context
    link - link all repositories that are missed in the current context
    update - clone and link in a single operation
    pull - pull all repositories in the current directory
    push - push all repositories in the current directory
    commit - commit all repositories in the current directory

更多关于git递归子模块的依赖问题。

其他回答

如果您希望保留第二个存储库的确切提交历史,并因此保留将来轻松合并上游更改的能力,那么下面是您想要的方法。它会导致子树的未修改历史被导入到repo中,再加上一个合并提交,将合并的存储库移动到子目录中。

git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit --allow-unrelated-histories XXX_remote/master
git read-tree --prefix=ZZZ/ -u XXX_remote/master
git commit -m "Imported XXX as a subtree."

你可以像这样跟踪上游的变化:

git pull -s subtree XXX_remote master

在进行合并之前,Git会自己计算出根的位置,因此您不需要在后续的合并中指定前缀。

缺点是在合并的历史中文件没有前缀(不在子目录中)。因此,git log ZZZ/a会显示除了合并历史之外的所有更改(如果有的话)。你可以:

git log --follow -- a

但这不会显示合并历史中其他的变化。

换句话说,如果不更改存储库XXX中的ZZZ文件,则需要指定——follow和一个无前缀路径。如果在两个存储库中都更改它们,则有两个命令,其中没有一个显示所有更改。

2.9之前的Git版本:你不需要给Git merge传递——allow-unrelated-histories选项。

另一个答案中的方法使用read-tree并跳过merge -s ours步骤,实际上与使用cp复制文件并提交结果没有什么不同。

原始来源来自github的“子树合并”帮助文章。这是另一个有用的链接。

我认为你可以使用'git mv'和'git pull'来做到这一点。

我是一个公平的git新手-所以要小心你的主存储库-但我刚刚在一个临时目录中尝试了这一点,它似乎工作。

首先-重命名XXX的结构,以匹配你想要它在YYY中的样子:

cd XXX
mkdir tmp
git mv ZZZ tmp/ZZZ
git mv tmp ZZZ

现在XXX是这样的:

XXX
 |- ZZZ
     |- ZZZ

现在使用'git pull'来获取更改:

cd ../YYY
git pull ../XXX

现在YYY是这样的:

YYY
 |- ZZZ
     |- ZZZ
 |- (other folders that already were in YYY)

这个函数会将远程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

如果做一点改变,你甚至可以移动文件/dirs合并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-unrelated-histories参数只存在于git >= 2.9之后。

没有足够的代表给x-yuri的回答加上评论,但它工作得很漂亮,保存了历史。 我正在与两个工作的本地回购工作,并收到这个错误:

中止:拒绝破坏性地覆盖此后的回购历史 这看起来不像一个新的克隆体。 (预计新包装的回购) 请改用一个新的克隆体做手术。如果你想继续,那就用武力。

与其担心——force标志的含义,我先在本地克隆了这个repo:

cd tempDir
git clone <location of repo to be merged> --no-local

用这个新克隆的拷贝来执行x-尤里布置的一系列命令。 最后,在:git filter-repo——to-subdirectory-filter a中,a是你要导入的repo根文件夹的名称。

简单的方法是使用git format-patch。

假设我们有两个git存储库foo和bar。

foo包含:

福.txt 。去

栏包含:

酒吧.txt 。去

我们希望以foo结束,其中包含bar历史记录和这些文件:

foo . txt。 git。 雷丁酒吧。. txt。

要做到这一点:

 1. create a temporary directory eg PATH_YOU_WANT/patch-bar
 2. go in bar directory
 3. git format-patch --root HEAD --no-stat -o PATH_YOU_WANT/patch-bar --src-prefix=a/foobar/ --dst-prefix=b/foobar/
 4. go in foo directory
 5. git am PATH_YOU_WANT/patch-bar/*

如果我们想重写所有从bar提交的消息,我们可以这样做,例如在Linux上:

git filter-branch --msg-filter 'sed "1s/^/\[bar\] /"' COMMIT_SHA1_OF_THE_PARENT_OF_THE_FIRST_BAR_COMMIT..HEAD

这将在每条提交消息的开头添加“[bar]”。