最近整改仓库,打算使用mono-repo的仓库管理模式来管理公司代码。
所以需要将原先散落的仓库合并,但是开发要求将原来仓库的提交历史保留。
场景:
我有两个仓库repoA, repoB, 我想把repoA的master合并到repoB中的子目录:project/repoA中并保留repoA的提交历史。
我试了很多方法,速度总结一下。
1.git官方文档给出的方法,但我没有跑成功。
但是我在试验这个的时候解决了一个问题,解决了我一个问题,因为后面会经常遇到。
fatal: refusing to merge unrelated histories
解决方法:命令后面加上:--allow-unrelated-histories
git merge master --allow-unrelated-histories
2.这篇博文提供的方法我试了一下是可以的:
如何用 Git 合并两个库(合并历史记录,解决冲突/改写路径)_asd131531的专栏-程序员宅基地 - 程序员宅基地
cd repoB
git remote add -f repoA_remote /fullpath/to/repoA
git merge --strategy ours --no-commit repoA_remote/master
mkdir -p project/repoA
git read-tree --prefix=project/repoA/ -u repoA_remote/master
git commit --message '完成 repoA 的迁移,新目录为 project/repoA'
但是就有一个问题,就是大部分人会提到的一个问题,
git log -- project/repoA时只显示我的合并提交,并不会显示repoA的历史提交。
这个里面有一段是修改index的,可能就是修复上面我那个问题的,但是脚本我没有跑成功,遂放弃。
有空的时候又试了一下上面这篇文章中提供的脚本,的确好用。
在开始脚本之前,现在原来的仓库找到首尾提交:
git filter-branch --tree-filter '(echo === $GIT_COMMIT:; git ls-tree $GIT_COMMIT) >> /tmp/tree.log'
在/tmp/tree.log中找到第一个commit和最后一个commit,接下来脚本会根据首尾comimt点进行运行。
#!/usr/bin/env bash
first=6b6391aee8beef6b5de8cd358502902859683e0e # 替换为自己的首节点
last=f04bdc9098ac3aa08bd37b704d846fc5cc862dc8 # 替换为最近的节点
subdir=test_sub # 子目录名称
git filter-branch --tree-filter '
first='"$first"'
last='"$last"'
subdir='"$subdir"'
log_file=/tmp/filter.log
[ "$GIT_COMMIT" = "$first" ] && seen_first=true
if [ "$seen_first" = "true" ] && [ "$seen_last" != "true" ]; then
echo "=== $GIT_COMMIT: making changes"
files=$(git ls-tree --name-only $GIT_COMMIT)
mkdir -p $subdir
for i in $files; do
mv $i $subdir || echo "ERR: mv $i $subdir failed" #主要就是通过这条命令来修改目录的,你可以根据自己的需要来调整,比如我想改名字,就不需要mkdir了,直接mv $i $subdir就可以了,非常万能。
done
else
echo "=== $GIT_COMMIT: ignoring"
fi \
>> $log_file
[ "$GIT_COMMIT" = "$last" ] && seen_last=true
status=0 # tell tree-filter never to fail
'
在repoA下新建一个脚本,将以上这段代码拷贝进去并运行,运行完可以在/tmp/filter.log中查看日志。
=== 6b6391aee8beef6b5de8cd358502902859683e0e: making changes
=== daffede59c65e59aaa20a74e5f72c1fd37527cb0: making changes
=== 79d1663c8cf6486c47382c203b5f97f68c15444f: making changes
=== 0b3064423f903f0e13b8a37d8201312ca4f0e16f: making changes
=== 9cbdec606d3bb60fd4196c47d8a015bea1cfa3cc: making changes
=== 79aad516fc1f43cbaf382ebd29a0bc459a00aff3: making changes
=== f04bdc9098ac3aa08bd37b704d846fc5cc862dc8: making changes
最后再查看该repoA,发现本地文件都已经被放到test_sub下了,可以用git ls-files -s查看,文件前面也都加上了test_sub的路径。
修改完历史后,将这个仓库合并到新仓库就可以了:
git pull /repo/repoA/ --allow-unrelated-histories #/repo/repoA就是我们刚刚处理完的本地路径
3. git patch的方法
现在原来的仓库上导出patch,加上相对路径:
参考文档:合并2个仓库并保留完整git历史信息 - 知乎 (zhihu.com)
(1)先导出patch,在原仓库(repoA)中进行
--root后面跟的是当前你想导出的起点,可以是HEAD,也可以是分支:master
git format-patch --root HEAD --no-stat --no-renames --full-index -o patches --src-prefix=a/project/repoA/ --dst-prefix=b/project/repoA/
(2)应用patch(patch的路径自己)
git apply ~/patch/patch/*.patch # patch的路径自己修改
在应用patch时似乎会卡住,但是git log -- project/repoA是可以的
4. git subtree
其实是方法2的更友好版,git出了一个subtree的工具,一条命令就可以完成仓库合并,但是存在问题跟方法2一样,下载地址:
(1) 从github上将git-subtree.sh下载下来:https://github.com/git/git 脚本路径:git/contrib/subtree/git-subtree.sh
(2) 在git-core路径做软链接, 我本机的git-core路径是:/usr/libexec/git-core
cd /usr/libexec/git-core
ln -s /usr/local/share/git-subtree.sh git-subtree
再运行git subtree就会出来help了
(3)一条命令完成迁移:(在repoB中输入)
git subtree add -P project/repoA /path/to/repoA.git master
5. git pull
最终,我参考了这篇文章里的答案,终于实现了我想要的效果。
How do you merge two Git repositories? - Stack Overflow
(1)在本地repoA里先修改提交历史,会将所有的提交都加上project/repoA的相对路径
PREFIX=project/repoA #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
(2) 将本地的repoA合并到repoB中
在reopB中输入:
git pull localpath-of-repoA --allow-unrelated-histories