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

mas*_*onk 51 git

我有一个/foo/bar大型提交历史和多个分支的git repo .

我现在想要/foo/baz处于相同的回购中/foo/bar,这(我认为)意味着我需要创建一个新的回购/foo.但是,我想保留我所做的变化的历史/foo/bar.

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

Wal*_*ndt 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
Run Code Online (Sandbox Code Playgroud)

这将使/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
Run Code Online (Sandbox Code Playgroud)

这应该将实际内容转移到a/b/bar(注意相反的顺序).

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

  • 应该使用`/ bin/echo` else`echo -e`可能无效.也许还有`--tag-name-filter cat`以避免"`警告:你说要重写标记的提交,而不是相应的标记."? (2认同)
  • 在MacOSX上,我首先必须下载gnu coreutils(我使用自制软件),然后用`/ usr/local/bin/gecho -e`替换`echo -e`来删除`input format error`消息 - 然后这很棒. (2认同)

And*_*ett 16

而不是创建一个新的存储库,将当前存储库中的内容移动到正确的位置:bar在当前目录中创建一个新目录并移动当前内容(因此您的代码所在/foo/bar/bar).然后baz在新bar目录(/foo/bar/baz)旁边创建一个目录. mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2你完成了:).

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

  • 哦,我明白了.也许做一个图形会简化这个过程.我认为这并不像接受的答案那么黑,因为它不会涉及搞砸整个存储库内部的风险. (3认同)

cue*_*dee 8

这具体回答了“如何将我的 git 存储库移到一个或多个目录并使其看起来总是这样?

随着 的出现,git >= 2.22.0 git filter-repo可以利用它来重写历史记录,使其看起来好像父目录始终是其中的一部分。

这与@Walter-Mundt 的答案使用 完成的任务相同git filter-branch,但更简单并且执行起来不那么脆弱。

请注意,如今,它本身git filter-repo宣传git filter-branch为更安全的替代方案


因此,考虑到您的存储库位于/foo/bar/baz并且您希望将其移至/foo

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

cd /foo/bar/baz
git config --local --bool core.bare true
Run Code Online (Sandbox Code Playgroud)

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

cd ./.git
git filter-repo --path-rename :bar/baz/
Run Code Online (Sandbox Code Playgroud)

这将重写存储库的完整历史记录,就好像每条路径总是预先添加bar/baz/到它之前(如果存储库的根目录向上两级,它们就会这样做)。此操作不会影响实际文件,因为现在这是一个裸存储库。

最后,再次将其打开,将.git目录移动到指定位置,然后重置:

git config --local --bool core.bare false
cd ..
mv ./.git ../..
cd ../..
git reset
Run Code Online (Sandbox Code Playgroud)

我认为,这git reset消除了存储库被裸露并再次返回的后遗症。git status在做之前尝试一下git reset看看我的意思。

现在,决赛git status应该证明一切都很好,模数一些新的未跟踪文件需要/foo/qux处理。

注意- 如果您在未克隆的存储库上尝试上述操作,git filter-repo将拒绝发挥其魔力,除非--force...准备好备份并认为自己受到警告。


Jon*_*ter 6

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

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

我通过以下方式实现了

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

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

  • 我简直不敢相信 - 谢谢你!另外 - 小心你的.gitignore.其中的文件夹路径也是相对的&我第一次尝试这个我最终错过了一个我希望b/c它在.gitignore中列出的目录. (2认同)
  • 我做了这个已经有一段时间了,我的第一个评论来自于我意识到一种更简单的方法来做几乎相同的事情.试试这个:只需将.git文件夹移动到一个目录中,然后使用`git add .`和`git commit`告诉git所有内容.实际上,您可以使用gui窗口移动目录,确切地说你如何使用git取决于你如何使用git.我给出的命令是你如何从终端窗口(如果你使用的是Windows,可能是Git Bash). (2认同)

Mat*_*ert 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
Run Code Online (Sandbox Code Playgroud)

要为特定分支执行此操作,请将其名称添加到最后,尽管我无法想象为什么要更改某些分支的目录结构.

在git filter-branch的手册页中阅读更多相关信息.但是,请注意使用此命令后可能出现的困难警告.只要确保你知道你在做什么.

我对这种方法的任何潜在问题有更多的意见.

  • 这个最适合我.但是我的bash(Ubuntu Server 12.04)不能正确解析echo -e参数(我死了:"...输入格式错误:-e 040000 tree ..."),所以我创建了别名echo ='echo -e'并从命令中删除参数以使其工作. (3认同)

Vic*_*yew 5

最常见的解决方案

在大多数正常情况下,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
Run Code Online (Sandbox Code Playgroud)

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

奖金解决方案

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

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

cd ../
git commit
Run Code Online (Sandbox Code Playgroud)

同样,请小心您的.gitignore