我在一个名为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)
这可以做到吗,或者我必须使用子模块?
这个函数会将远程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之后。
在我的例子中,我只想从另一个存储库(XXX)导入一些文件。子树对我来说太复杂了,其他的解都不行。这就是我所做的:
ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')
这将为您提供一个以空格分隔的列表,其中包括所有影响我想要导入的文件(ZZZ)的反向顺序(您可能还必须添加——follow以捕获重命名)。然后我进入目标存储库(YYY),将另一个存储库(XXX)添加为远程,从它中获取,最后:
git cherry-pick $ALL_COMMITS
这会将所有提交添加到分支,因此您将拥有所有具有历史记录的文件,并且可以对它们做任何您想做的事情,就像它们一直在这个存储库中一样。
下面是脚本,将立即工作。
#!/bin/bash -xe
# script name: merge-repo.sh
# To merge repositories into the current.
# To see the log of the new repo use 'git log --follow -- unprefixed-filename'
# So if the file is repo/test.cpp use 'git log --follow -- test.cpp'
# I'm not sure how this will work when two files have the same name.
#
# `git branch -a` will show newly created branches.
# You can delete them if you want.
merge_another() {
repo="$1" # url of the remote repo
rn="$2" # new name of the repo, you can keep the same name as well.
git remote add ${rn} ${repo}
git fetch ${rn}
git merge -s ours --no-commit --allow-unrelated-histories ${rn}/master
git read-tree --prefix=${rn}/ -u ${rn}/master
git commit -m "Imported ${rn} as a subtree."
git pull -s subtree ${rn} master
}
merge_another $1 $2
运行脚本。转到您希望合并另一个repo的repo,并运行脚本。
cd base-repo
./merge-repo.sh git@github.com:username/repo-to-be-merged.git repo-to-be-merged-new-name
现在将主分支上的更改推到remote/origin。根据您要做的事情,可能不需要此步骤。
git push origin master
Git-subtree正是为这样的用例设计的脚本:将多个存储库合并为一个,同时保留历史(和/或分割子树的历史,尽管这似乎与这个问题无关)。从1.7.11版开始,它作为git树的一部分发布。
要合并一个<repo> at revision <rev> as subdirectory <prefix>的仓库,使用git子树add,如下所示:
git subtree add -P <prefix> <repo> <rev>
Git-subtree以一种更用户友好的方式实现子树合并策略。
对于您的情况,在存储库YYY中,您将运行:
git subtree add -P ZZZ /path/to/XXX.git master
缺点是在合并的历史中文件没有前缀(不在子目录中)。因此,git log ZZZ/a会显示除了合并历史之外的所有更改(如果有的话)。你可以:
git log --follow -- a
但这不会显示合并历史中其他的变化。
换句话说,如果不更改存储库XXX中的ZZZ文件,则需要指定——follow和一个无前缀路径。如果在两个存储库中都更改它们,则有两个命令,其中没有一个显示所有更改。
这里有更多。