制作git format-patch遵循重命名,如git log --follow

时间:2013-11-13 05:15:25

标签: git

我正在尝试将文件从一个本地 git存储库移动到另一个本地git存储库,以便从原始存储库中获取另一个项目,同时保留历史记录。到目前为止,我有这个,工作正常如果文件从未在源代码库中移动或重命名:

# Executed from a directory in the target repository
( cd $SOURCE_REPOSITORY_DIRECTORY && git format-patch -B -M --stdout --root $SOURCE_FILENAME) | git am --committer-date-is-author-date

这恰好起作用,因为两个存储库的目录结构是相同的。如果它们不同,我必须使用sed或其他东西创建补丁文件并修复目录名称。

无论如何,在我点击已重命名的文件之前,这一直都是膨胀的。即使我正在指定-B -M(并使用-B -M -C --find-copies-harder得到相同的结果),即使文件被清晰地移动(相似性指数100%),我也不会在移动之前获得补丁。

这是特别奇怪的,因为git log --follow显示所有提交,git log --follow -p提供所有差异。除非它以相反的顺序提供它们,所以我无法将它们提供给git am

另请注意,git log --follow -p filename会显示以下“补丁”以显示重命名:

diff --git a/old_dir_name/dir1/dir2/filename b/new_dir_name/dir0/dir1/dir2/filename
similarity index 100%
rename from old_dir_name/dir1/dir2/filename
rename to new_dir_name/dir0/dir1/dir2/filename

现在,如果git log以正确的格式和正确的顺序显示补丁以便git am应用它们,我可以使用它,但事实并非如此。使用git log --reverse --follow -p filename仅输出名称更改补丁,没有其他内容。

所以,如何让git format-patch真正按照帮助文件/手册页所说的方式重命名,同时只输出单个文件的补丁?或者,如何让git log -p生成补丁,我可以将它们提供给git am以重新创建具有历史记录的文件?

我正在使用git版本1.8.4.3。

3 个答案:

答案 0 :(得分:2)

我已经取得了一些进展,但现在更加手动了。

  • 对于每个文件,使用带有和不带log的{​​{1}}来查看已重命名/移动/复制的文件(为简单起见,将它们全部“重命名”)。
  • 对于已重命名的文件,从--follow输出中提取以前的完整路径和文件名。
  • 然后使用log,但在命令行中提供所有旧名称和当前名称

所以现在我有这样的事情:

format-patch

创建补丁以创建旧文件,将其重命名为新名称,然后继续修补文件。当然,对我来说问题是旧的目录没有在新的仓库中退出而且目录级别已经改变了,所以仍然有一些问题与使目录名工作有关。

这应该更容易......

答案 1 :(得分:2)

我最近遇到了与此问题相同的用例,并且我使用Bash实现了一个解决方案,所以我想分享它,因为这段代码可能对其他人有用。

它由一个可用的脚本git-format-patch-follow组成 https://github.com/erikmd/git-scripts,可以在OP的问题中使用如下: ( cd "$SOURCE_REPOSITORY_DIRECTORY" && git format-patch-follow -B -M --stdout --root --follow -- "$SOURCE_FILENAME" ) | git am --committer-date-is-author-date

更一般地说,语法是: git format-patch-follow <options/revisions...> --follow -- <paths...>

因此,可以将此Bash脚本视为运行@OldPro概述的算法的自动方式,并且我特别注意应对极端情况,例如具有空格的文件名,在CLI上传递的多个文件或运行来自Git源代码库子目录的脚本。

最后正如this blog post指出的那样,只需将这样的脚本放在一个PATH中,Git就可以像git子命令git format-patch-follow那样集成脚本。

答案 2 :(得分:1)

呸。我认为问题是一些破碎的逻辑。特别是,当您合并--reverse--follow时,必须指定文件名:

[rename foo to bar]
$ git log --follow bar  # works
$ git log --follow --reverse -- foo # requires "--" because foo is not in HEAD

这...有点工作。除此之外,它会在文件命中重命名时将其视为已删除,并且所有内容都会停止。

tree-diff.c包含此功能:

static void try_to_follow_renames(...)
{
        ...
        /* Remove the file creation entry from the diff queue, and remember it */
        choice = q->queue[0];
        q->nr = 0;
如果diff_might_be_rename返回true,则调用

static inline int diff_might_be_rename(void)
{
        return diff_queued_diff.nr == 1 &&
                !DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
}

...
int diff_tree_sha1(...)
{
        ...
        if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {

我在这里做了一些大的假设,但当你进入另一个顺序时,我们只是创建了一个bar,而不是“foo”。重命名为“,如果日志被撤消,则需要删除文件foo,让我们看看我们是否可以找到重命名的bar”,这只是...缺失,如果评论是准确的。

如果您有很多要做的事情,我建议您尝试在此处添加一些内容以记住差异是否相反(因为它适用于format-patch {{ 1}})并根据需要更改log --reversediff_might_be_rename()代码。

如果你只有一个,那么,手动攻击一些差异可能更容易。 : - )