我们的Git存储库最初是一个大型SVN存储库的一部分,其中每个项目都有自己的树,如下所示:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

显然,使用svn mv将文件从一个文件移动到另一个文件非常容易。但是在Git中,每个项目都在它自己的存储库中,今天我被要求将一个子目录从project2移动到project1。我是这样做的:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

但这似乎相当复杂。有没有更好的方法来做这类事情呢?还是我采取了正确的方法?

注意,这涉及到将历史合并到现有的存储库中,而不是简单地从另一个存储库的一部分创建一个新的独立存储库(如前面的问题中所述)。


当前回答

是的,点击filter-branch的——subdirectory-filter是关键。您使用它的事实本质上证明了没有更简单的方法—您别无选择,只能重写历史,因为您希望最终只得到文件的一个(重命名的)子集,而这根据定义改变了哈希值。由于没有任何标准命令(例如pull)重写历史,因此您无法使用它们来完成此任务。

当然,您可以细化细节—您的一些克隆和分支并不是严格必要的—但是总体方法是好的!遗憾的是它很复杂,但是git的意义当然不是让重写历史变得容易。

其他回答

是的,点击filter-branch的——subdirectory-filter是关键。您使用它的事实本质上证明了没有更简单的方法—您别无选择,只能重写历史,因为您希望最终只得到文件的一个(重命名的)子集,而这根据定义改变了哈希值。由于没有任何标准命令(例如pull)重写历史,因此您无法使用它们来完成此任务。

当然,您可以细化细节—您的一些克隆和分支并不是严格必要的—但是总体方法是好的!遗憾的是它很复杂,但是git的意义当然不是让重写历史变得容易。

在我的例子中,我不需要保存我正在迁移的回购,也不需要保存任何以前的历史记录。我用不同的遥控器,截取了同一根树枝的补丁

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

在这两个步骤中,我能够让另一个回购的分支出现在同一个回购中。

最后,我设置这个分支(我从另一个回购导入)遵循目标回购的主线(所以我可以准确地区分它们)

git br --set-upstream-to=origin/mainline

现在它的行为就好像它只是我在同一次回购中推的另一个分支。

如果你的历史记录是正常的,你可以将提交作为补丁取出,并将它们应用到新的存储库中:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

或者在一行里

git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

提示:如果源项目子目录中的提交应该被提取到一个新的存储库根目录,git am可以被赋予-p2这样的参数来从补丁中删除额外的目录。

(摘自Exherbo的文件)

试试这个

cd repo1

这将删除除上述目录之外的所有目录,仅为这些目录保留历史记录

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

现在,您可以在git远程中添加新的repo并将其推到该位置

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

添加-f来覆盖

下面是通过维护所有分支和保存历史记录来将我的GIT Stash迁移到GitLab的方法。

将旧的存储库克隆到本地。

git clone --bare <STASH-URL>

在GitLab中创建一个空存储库。

git push --mirror <GitLab-URL>

当我们将代码从stash迁移到GitLab时,我执行了上述操作,效果非常好。