考虑以下场景:
我在自己的Git repo中开发了一个小型实验项目a。它现在已经成熟,我希望A成为更大的项目B的一部分,该项目有自己的大仓库。现在我想将A添加为B的子目录。
我如何将A合并为B,而不丢失任何方面的历史?
考虑以下场景:
我在自己的Git repo中开发了一个小型实验项目a。它现在已经成熟,我希望A成为更大的项目B的一部分,该项目有自己的大仓库。现在我想将A添加为B的子目录。
我如何将A合并为B,而不丢失任何方面的历史?
当前回答
git子树很好,但它可能不是您想要的。
例如,如果projectA是在B中创建的目录,在git子树之后,
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。
注意Bene:如果在文件名或路径中使用非ascii字符(或白色字符),则必须修改sed命令中的替代脚本。在这种情况下,“ls files-s”生成的记录中的文件位置以引号开头。
(2) 然后在B中,运行
git pull path/to/A
瞧!在B中有一个projectA目录。如果运行git-log-projectA,您将看到a中的所有提交。
在我的例子中,我需要两个子目录,projectA和projectB。在这种情况下,我也执行了步骤(1)到B。
其他回答
给定命令是我建议的最佳解决方案。
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
与@Smar类似,但使用在PRIMARY和SECONDARY中设置的文件系统路径:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
然后手动合并。
(改编自阿纳尔·马纳福夫的帖子)
我在使用merge时不断丢失历史记录,所以我最终使用了rebase,因为在我的情况下,两个存储库的不同程度足以避免每次提交时合并:
git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB
cd projB
git remote add projA ../projA/
git fetch projA
git rebase projA/master HEAD
=>解决冲突,然后根据需要继续。。。
git rebase --continue
这样做会导致一个项目具有来自projA的所有提交,然后是来自projB的提交
除了使用远程add->fetch->merge策略的所有答案之外:如果您想保留其他存储库中的标记,但不想将它们全部泄漏到一个公共命名空间中(可能会发生冲突),您可能需要稍微更改fetch命令:
git fetch --no-tags other_repo
git fetch --no-tags other_repo 'refs/tags/*:refs/tags/other_repo/*'
第一个命令像往常一样获取所有分支,但省略了附加到提交的标记,第二个命令也省略了通常的标记获取机制(git help fetch了解更多信息),并使用git的refspec功能获取将它们从X映射到other_repo/X的所有标记。
引用(分支、标记)只是git中的文件,您可以使用目录来命名。上面的两个命令将保留第一个存储库中的标记,而另一个存储库的标记将以other_repo作为前缀/
操作完成后,最好移除另一个遥控器,这样您就不会意外地以正常方式获取标签并造成混乱。
如果您想将来自存储库B分支的文件放在存储库a的子树中,并保留历史记录,请继续阅读。(在下面的示例中,我假设我们希望回购协议B的主分支合并为回购协议A的主分支。)
在回购协议A中,首先执行以下操作以使回购协议B可用:
git remote add B ../B # Add repo B as a new remote.
git fetch B
现在我们在回购a中创建了一个全新的分支(只有一个提交),我们称之为new_b_root。生成的提交将包含在repo B的主分支的第一次提交中提交的文件,但这些文件放在名为path/to/B-files/的子目录中。
git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"
解释:checkout命令的--孤儿选项从A的主分支检出文件,但不创建任何提交。我们可以选择任何提交,因为接下来我们无论如何都要清除所有文件。然后,在尚未提交(-n)的情况下,我们从B的主分支中选择第一个提交。(cherry pick保留了原始的提交消息,而直接签出似乎无法做到这一点。)然后我们创建一个子树,将所有来自repo B的文件放在那里。然后我们必须将cherry stick中引入的所有文件移动到子树中。在上面的示例中,只有一个README文件可以移动。然后我们提交B-repo根提交,同时,我们还保留原始提交的时间戳。
现在,我们将在新创建的new_B_root上创建一个新的B/master分支。我们称新分支为b:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
现在,我们将b分支合并为A/master:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
最后,您可以删除B个远程和临时分支:
git remote remove B
git branch -D new_b_root b
最终图形的结构如下: