如何在保留历史记录的同时将git repo重新发布到父文件夹?

时间:2010-07-09 12:20:31

标签: git

我在/foo/bar中有一个git repo,它有一个大的提交历史记录和多个分支。

我现在希望/foo/baz/foo/bar位于同一个回购中,我认为这意味着我需要在/foo中创建一个新的回购。但是,我想保留我对/foo/bar所做的更改的历史记录。

我首先想到的是git format-patch,然后是apply,但是不保留提交消息。

编辑:我的问题不是this question的愚蠢,因为我的具体是关于保存历史,这里的答案包括有关如何保存历史的额外细节以及有关如何更难以重新创建的额外细节到了一个任意更高的层次,而不仅仅是一个目录。

8 个答案:

答案 0 :(得分:25)

你想要的是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存储库具有另一个'bar'子目录,其子目录包含其整个历史记录中的所有内容。然后,您可以将整个仓库移至foo级别,并向其添加baz代码。

更新

好的,这是正在发生的事情。提交是指向“树”的链接(将其视为表示整个文件系统子目录内容的SHA)以及一些“父”SHA和一些元数据链接作者/消息/等。 git commit-tree命令是将所有这些包装在一起的低级位。 --commit-filter的参数被视为shell函数,并在过滤过程中代替git commit-tree运行,并且必须像它一样。

我正在做的是获取第一个参数,提交原始树,以及通过git mktree(另一个低级git命令)构建一个新的“树对象”,表明它在子文件夹中。要做到这一点,我必须管它看起来像一个git树,即一组(模式SP类型SP SHA TAB文件名)行;因此,回声命令。然后,当我链接到真实mktree时,commit-tree的输出将替换第一个参数; "$@"是一种完整传递所有其他参数的方法,已使用shift删除了第一个参数。有关信息,请参阅git help mktreegit help commit-tree

所以,如果你需要多个级别,你必须嵌套一些额外级别的树对象(这不是测试但是一般的想法):

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(请注意相反的顺序)。

更新:综合改进来自Matthew Alpert的答案如下。如果没有-- --all,这只适用于当前检出的分支,但由于问题是询问整个回购,因此以分支方式执行此操作更有意义。

答案 1 :(得分:16)

不是创建新的存储库,而是将当前存储库中的内容移动到正确的位置:在当前目录中创建新目录bar并移动当前内容(因此您的代码位于{{1} })。然后在新/foo/bar/bar目录(baz)旁边创建一个bar目录。 /foo/bar/baz你已经完成了:)。

Git的重命名跟踪意味着您的历史记录仍然有用,而Git的内容散列意味着即使您已经移动了东西,您仍然会在存储库中引用相同的对象。

答案 2 :(得分:6)

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

我特别需要的是在我的存储库中包含父目录中的文件(有效地将repo移到一个目录中)。

我是通过以下方式实现的:

  • 将所有文件(.git除外)移动到具有相同名称的新子目录中。并且告诉git关于它(与git mv
  • 将所有文件从父目录移动到现在为空的(.git /除外)当前目录并告诉git关于它(使用git add
  • 将整个内容提交到repo中,没有移动(git commit)。
  • 将当前目录向上移动到目录层次结构中的一个级别。 (使用命令行jiggery-pokery)

我希望这有助于下一个人的到来 - 我可能只是一个没脑子的日子,但我发现上面的答案过于精细和可怕(因为需要。)我知道这与Andrew Aylett上面的答案类似,但我的情况似乎有点不同,我想要一个更普遍的观点。

答案 3 :(得分:5)

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

所以Walter Mundt的方法效果很好,但它一次只适用于一个分支。在第一个分支之后,可能会有警告要求-f强制执行操作。因此,要立即为所有分支执行此操作,只需在末尾添加“ - --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的手册页中阅读更多相关信息。但是,请注意使用此命令后可能出现的困难警告。只要确保你知道你在做什么。

我很欣赏有关此方法的任何潜在问题的更多意见。

答案 4 :(得分:3)

最常见的解决方案

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

因此,如果您不介意在历史记录中提交表明您已将所有内容都移动的提交,那么有一个非常简单的解决方案,其中包括移动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,以避免转储不需要的文件或忘记某些文件。

奖金解决方案

在某些设置中,当git看到与已删除文件完全相同的新文件时,它会自行弄清楚文件是否已被移动。在这种情况下,解决方案更简单:

mv .git ../.git
mv .gitignore ../.gitignore

cd ../
git commit

再次注意你的.gitignore

答案 5 :(得分:3)

这专门回答了“ 我如何将我的git repo移至一个或多个目录,并使它看起来总是这样?

随着git >= 2.22.0 git filter-repo的出现,可以用来重写历史记录,使其看起来好像该父目录一直在其中。

这与@Walter-Mundt's answer使用git filter-branch完成的事情相同,但是更简单并且执行起来也不那么容易。

请注意,这几天git filter-repoadvertised by git filter-branch itself as the safer alternative


因此,鉴于您的仓库位于/foo/bar/baz中,并且您希望将其移至/foo

首先,为防止在重写历史记录时对工作空间中的文件进行任何更改,请暂时将存储库变成所谓的"bare",如下所示:

cd /foo/bar/baz
git config --local --bool core.bare true

现在可以直接在.git目录本身中完成实际的历史记录重写:

cd ./.git
git filter-repo --path-rename :bar/baz/

这将重写存储库的完整历史记录,就好像每个路径始终都带有bar/baz/一样。实际操作不会影响实际文件,因为现在这是一个裸仓库。

要结束,请再次使其变得平淡,将.git目录移至指定位置,然后重置:

git config --local --bool core.bare false
cd ..
mv ./.git ../..
cd ../..
git reset

我认为,git reset消除了存储库变光并再次返回的后遗症。在进行git status之前先尝试git reset来了解我的意思。

最后的git status现在应该证明一切都很好,对/foo/qux中的一些新的未跟踪文件进行模处理。

注意-如果您在未克隆的存储库中尝试上述操作,git filter-repo将拒绝执行其魔术操作,除非您--force要...准备好备份并考虑警告自己。

答案 6 :(得分:2)

除了接受的答案之外,还帮助我开始工作: 当我将列出的文本放在shell脚本中时,出于某种原因保留了-e。 (很可能因为我太厚而无法使用shell脚本)

当我删除-e并移动引号以包含所有内容时,它起作用了。 SUBTREE2 = echo "040000 tree $SUBTREE1 modules" | git mktree

请注意$ SUBTREE1和模块之间有一个标签,它与-e应该解释相同。

答案 7 :(得分:0)

您可以在foo中创建一个git仓库,并通过 git submodules 引用bazbar

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


如果你真的只想要一个repo(foo),其中包含bar和baz历史记录,那么有些grafts technique or subtree merge strategy是有序的。