如何在保留历史logging的同时将git repo重新生成到父文件夹?

我有一个大的提交历史logging和多个分支的/foo/bar的git仓库。

我现在想要/foo/baz/foo/bar存在相同的回购,这(我认为)意味着我需要在/foo创build一个新的回购。 但是,我想保留我对/foo/bar所做的更改的历史logging。

我首先想到了git format-patch后面跟apply,但是commit消息没有保存。

你想要的是git filter-branch ,它可以将整个存储库移动到一个子树中,通过使它看起来好像总是这样来保存历史。 在使用前备份你的仓库!

这是魔法。 在/foo/bar ,运行:

 git filter-branch --commit-filter ' TREE="$1"; shift; SUBTREE=`echo -e 040000 tree $TREE"\tbar" | git mktree` git commit-tree $SUBTREE "$@"' -- --all 

这将使得/foo/bar版本库在其整个历史logging中都有另一个“bar”子目录及其所有内容。 然后,您可以将整个回购协议移至foo级别,并向其添加baz码。

更新

好吧,这是怎么回事 一个提交是一个“树”的链接(把它想象成代表整个文件系统子目录的内容的SHA)加上一些“父”SHA和一些元数据链接作者/消息等。 git commit-tree命令是将所有这些包装在一起的低级别的位。 参数--commit-filter被视为一个shell函数,并在过滤过程中代替git commit-tree ,并且必须像这样。

我正在做的是采取第一个参数,原始树提交,并通过git mktree ,另一个低级别的git命令build立一个新的“树对象”,说它在子文件夹中。 要做到这一点,我必须把它看成是一个看起来像git树的东西,也就是一组(SP模式SP SHA TAB文件名)行。 因此是回声命令。 当我链接到真正的commit-tree时, mktree的输出被replace为第一个参数; "$@"是完整地传递所有其他参数的一种方式,通过shift将第一个参数删除。 有关信息,请参阅git help mktreegit help commit-tree

所以,如果你需要多个层次,你必须嵌套一些额外的树对象(这是没有testing,但是一般的想法):

 git filter-branch --commit-filter ' TREE="$1" shift SUBTREE1=`echo -e 040000 tree $TREE"\tbar" | git mktree` SUBTREE2=`echo -e 040000 tree $SUBTREE1"\tb" | git mktree` SUBTREE3=`echo -e 040000 tree $SUBTREE2"\ta" | git mktree` git commit-tree $SUBTREE3 "$@"' -- --all 

这应该将实际内容转换成a/b/bar (注意颠倒的顺序)。

更新 :综合改进从马修阿尔珀特的答案下面。 没有-- --all这些只能在当前检出的分支上运行,但是因为问题是询问整个回购,所以这样做比分支分支更有意义。

而不是创build一个新的仓库,把你当前版本库中的内容移到正确的地方:在当前目录中创build一个新的目录bar ,并移动当前内容(所以你的代码在/foo/bar/bar )。 然后在你的新bar目录( /foo/bar/baz )旁边创build一个baz目录。 mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2 mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2 ,你完成了:)。

Git的重命名跟踪意味着你的历史logging仍然可以工作,而Git的内容散列意味着即使你已经移动了东西,你仍然在引用存储库中的同一个对象。

这增加了Walter Mundt接受的答案。 我宁愿评论他的答案,但我没有名誉。

所以Walter Mundt的方法很好,但是它一次只能用于一个分支。 在第一个分支之后,可能会有警告,要求强行通过。 因此,要一次为所有分支做到这一点,只需将“ – – all”添加到最后:

 git filter-branch --commit-filter ' tree="$1"; shift; subtree=`echo -e 040000 tree $tree"\tsrc" | git mktree` git commit-tree $subtree "$@"' -- --all 

而为了做到这一点特定的分支机构,而不是他们的名字,但我不能想象你为什么要改变只有一些分支的目录结构。

在git filter-branch的手册页阅读更多关于这个的信息。 但是,请注意使用这样的命令后可能遇到的困难的警告。 只要确保你知道你在做什么。

我会很感激这个方法的任何潜在问题的更多的意见。

我有一个似乎没有人说过的解决scheme:

我特别需要的是从我的资源库中的父目录中包含文件(有效地将回购上移到一个目录)。

我通过这样做了

  • 将所有文件(.git除外)移动到同名的新子目录中。 并告诉混帐 (与git mv
  • 将所有文件从父目录移到现在为空(除了.git /)当前目录并告诉git (用git add
  • 把整个东西放到没有移动的回购( git commit )中。
  • 将当前目录在目录层次结构中上移一级。 (用命令行jiggery-pokery)

我希望这可以帮助下一个人 – 我可能只是一个无脑的日子,但我发现上面的答案过于复杂和可怕(为所需要的)。我知道这是类似于安德鲁Aylett上面的答案但是我的情况似乎有点不同,我想要一个更普遍的观点。

除了被接受的答案之外,它帮助我实现了这一点:当我将列出的文本放在一个shell脚本中时,由于某种原因,-e被保留了下来。 (很可能是因为我太厚,无法使用shell脚本)

当我删除-e,并移动报价包含一切,它的工作。 SUBTREE2 = echo "040000 tree $SUBTREE1 modules" | git mktree echo "040000 tree $SUBTREE1 modules" | git mktree

注意$ SUBTREE1和模块之间有一个标签,这与-e应该解释的是一样的。

最常见的解决scheme

在大多数正常情况下,git会查看所有相对于其位置(即.git目录)的文件,而不是使用绝对文件path。

因此,如果您不介意在您的历史logging中提交一个表明您已经将所有内容都提交的提交,则有一个非常简单的解决scheme,即移动git目录。 唯一有点棘手的是确保git明白这些文件是相同的,而且只是相对于他而言:

 # Create sub-directory with the same name in /foo/bar mkdir bar # Move everything down, notifying git : git mv file1 file2 file3 bar/ # Then move everything up one level : mv .git ../.git mv bar/* . mv .gitignore ../ # Here, take care to move untracked files # Then delete unused directory rmdir bar # and commit cd ../ git commit 

唯一要小心的是在移动到新目录时正确更新.gitignore,以避免暂存不需要的文件或忘记一些文件。

奖金解决scheme

在一些设置中,git设法自己发现当文件看到与删除文件完全相同的新文件时文件已被移动。 在这种情况下,解决scheme更简单:

 mv .git ../.git mv .gitignore ../.gitignore cd ../ git commit 

再次,小心你的.gitignore

你可以在foo创build一个git repo,并通过git submodules引用bazbar

然后, barbaz共同保存完整的历史。


如果你真的只想要一个回购(foo),既有bar和baz的历史,那么一些移植技术或者子树合并策略也是合适的。