5

我正在尝试将文件从一个本地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。

4

3 回答 3

3

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

它由https://github.com/erikmd/git-scriptsgit-format-patch-follow上可用 的脚本组成,可按如下方式用于 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 源代码库的子目录。

最后,正如这篇博文所指出的,将这样的脚本放在一个脚本中就足够了PATH,Git 可以像 git 子命令一样集成脚本git format-patch-follow

免责声明: git format-patch,因此git-format-patch-follow不能应用于非线性历史(涉及合并提交)。

于 2018-01-02T14:38:11.393 回答
2

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

  • 对于每个文件,使用logwith 和 without--follow查看哪些文件已被重命名/移动/复制(为简单起见,将它们全部称为“重命名”)。
  • 对于已重命名的文件,请从log输出中提取先前的完整路径和文件名。
  • 然后在命令行上使用format-patch但给出所有旧名称以及当前名称。

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

 git format-patch -B -M -o /tmp/patches --root -- old_dir_name/dir1/dir2/filename new_dir_name/dir0/dir1/dir2/filename

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

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

于 2013-11-13T22:08:31.153 回答
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 and log --reverse)并根据需要更改diff_might_be_rename()andtry_to_follow_renames()代码。

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

于 2013-11-13T16:12:35.473 回答