我正在尝试将文件从一个本地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。
我已经取得了一些进展,但现在更加手动了。
log
的--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
创建补丁以创建旧文件,将其重命名为新名称,然后继续修补该文件。当然,对我来说问题是旧目录没有退出新的仓库并且目录级别已经改变,所以仍然有一些与使目录名称起作用有关的问题。
这应该更容易....
我最近遇到了与此问题相同的用例,我使用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指出的那样,只需将这样的脚本放在一个用于Git的PATH
中就可以像git子命令git format-patch-follow
一样集成脚本。
呸。我认为问题是一些破碎的逻辑。特别是,当您组合--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
和log --reverse
)并根据需要更改diff_might_be_rename()
和try_to_follow_renames()
代码。
如果你只有一个,那么,手动攻击一些差异可能更容易。 :-)