是否可以在Git中移动/重命名文件并保留其历史记录?

您所在的位置:网站首页 git重命名文件 是否可以在Git中移动/重命名文件并保留其历史记录?

是否可以在Git中移动/重命名文件并保留其历史记录?

2023-06-08 00:54| 来源: 网络整理| 查看: 265

我想在Git中重命名/移动项目子树,将其从

1/project/xyz

1/components/xyz

如果我使用一个普通的git mv project components,那么xyz project的所有提交历史都会丢失。有没有办法让历史得以延续?

相关讨论 关于git mv:stackoverflow.com/questions/1094269/git‌&8203;-mv的目的是什么 我只想注意,我刚刚测试了通过文件系统移动文件,在提交(通过intellij)之后,当查看历史(再次在intellij中)时,我可以看到整个历史(包括在不同位置时的历史)。我假设Intellij并没有做任何特别的事情,所以很高兴知道至少历史可以追溯。 有关检测目录重命名时Git遵循的规则,请参阅下面的我的答案 我在这里写了一个答案。我希望它能奏效。stackoverflow.com/questions/10828267/…

Git检测重命名,而不是使用commit来持久化操作,因此使用git mv或mv并不重要。

log命令接受一个--follow参数,该参数在重命名操作之前继续历史记录,即使用启发式搜索类似内容:

网址:http://git-scm.com/docs/git-log

要查找完整历史记录,请使用以下命令:

1git log --follow ./path/to/file

相关讨论 如果不需要——跟在后面,那就太好了,但我想这就必须了。谢谢你的回答。 我怀疑这是一个性能考虑。如果您不需要完整的历史记录,扫描内容肯定需要更长的时间。最简单的方法是设置一个别名git config alias.logf"log --follow",只需编写git logf ./path/to/file。 我很喜欢!谢谢你的小费。 但是您指定的路径是旧路径还是新路径?如果是旧路径,你怎么知道指定旧路径? 你应该使用新的路径。它的工作方式是当检测到重命名时,Git遍历历史并切换到旧路径。 在配置中没有设置这个的方法吗?我在Docs或Google上找不到任何东西。 @这封由Linus Torvalds发来的邮件,与这个答案联系在一起,表明这是一个有意选择的Git,因为它据称比跟踪Renames等功能强大得多。 这个答案有点误导人。Git确实"检测重命名",但在游戏的后期;问题是,你如何确保Git跟踪重命名,阅读本文的人可以很容易地推断Git会自动为你检测它们并记录下来。但事实并非如此。Git对重命名没有真正的处理,取而代之的是,有一些合并/日志工具试图弄清楚发生了什么,但很少能正确处理。关于为什么Git永远不应该以正确的方式来做,并且明确地跟踪Renames,Linus有一个错误但激烈的争论。所以,我们被困在这里。 @Chrismoschini是的,来自Atlassian的源树没有检测到一个简单的文件夹移动,而是将其视为一系列的添加和删除。可能是在到来时,甚至是在推的时候,它可能已经检测到了,但我没有冒险,所以我做了控制台Gitmv的事情。 请注意,"git log--follow"只适用于单个文件。 重要:如果您重命名目录,例如在重命名Java包时,请确保执行两个提交,首先是"Git MV {Ord}{Ne}}"命令,第二个是更新引用的已更改包目录的所有Java文件的更新。否则,即使使用--follow参数,Git也无法跟踪单个文件。 git log --follow ./path/to/file不适合我,git log --follow -- ./path/to/file适合我。而--follow并不是不必要的。--埃多克斯1〔9〕 我认为在所有情况下,使用git mv或仅仅使用mv都不重要。简单的重命名或移动似乎可以很好地被检测到,但例如,在我的例子中没有检测到将文件移动到子文件夹(是的,有理由这样做)。 @据我所知,在mv之后检测到的所有东西都是删除。必须通过git add手动添加重命名的文件,之后,即使文件不在同一目录中,Git也会检测到重命名。 我尝试移动文件,它git报告为删除和添加。Git MV将这些报告为Renames。 所以我要从颠覆到Git,我想澄清一些事情。我知道Git没有添加任何文件重命名元数据。我知道git log将使用试探法来猜测文件何时移动。我知道如果我在Git中移动一个文件,我应该在修改该文件之前提交移动,以帮助Git在将来跟踪历史记录。但我怀疑一个真实的场景:当我移动某个东西时,我通常重构源代码,因此,例如.java文件不会移动——至少是指示包将改变的行。这会阻止"git log"跟踪历史记录吗? 我看到有人问了同样的问题:stackoverflow.com/q/3520023/421049。但到目前为止,答案还不清楚。 尽管莱纳斯可能很少犯错误,但这似乎是一个错误。简单地重命名一个文件夹会导致一个巨大的增量被上传到GitHub。这让我在重命名文件夹时很谨慎……但对于程序员来说,这是一件相当大的直截了当的工作。有时候,我必须重新定义事物的意义,或者改变事物的分类方式。换句话说,我是对的。我总是对的,但有时我比其他时候更对。该死的,当我说‘文件不重要’的时候,我真的是对的(t m)。"……我对那个有疑问。 @加勒特威尔逊:如果至少50%的文件是相同的,那么git log --follow将遵循历史。您可以随时更改这个百分比(使用-M),因为它是git log,遵循相似性,而不是一般磁盘上的git。(一般来说,不可能解决重命名+编辑应被视为"同一文件"的问题。) @gabehalsmer:简单地重命名一个文件夹不会造成巨大的增量(除非这包括其他更改,如对文件中文件夹名称的引用)。当然,有可能Github将重命名显示为一个巨大的delta。 有人知道答案吗?不幸的是,我无法从这篇文章中理解。 通过避免在特定提交中对这些文件进行其他更改,可以确保正确的重命名检测。如果您重命名=>commit=>edit它们,Git将能够永久地跟踪这个重命名。

可以重命名一个文件并保持历史完整,尽管它会导致在存储库的整个历史中重命名该文件。这可能只适用于痴迷于Git日志的爱好者,并有一些严重的影响,包括:

您可以重写共享的历史记录,这是使用Git时最重要的不要。如果有人克隆了存储库,那么您将破坏它。为了避免头痛,他们必须重新克隆。如果重命名足够重要,这可能没问题,但您需要仔细考虑这一点——最终可能会让整个开源社区感到不安! 如果您在存储库历史记录的早期版本中引用了使用旧名称的文件,那么实际上您正在破坏早期版本。要解决这个问题,你得多跳一点。这不是不可能的,只是乏味,可能不值得。

现在,既然你还和我在一起,你可能是一个单独的开发者,正在重命名一个完全独立的文件。让我们用filter-tree移动一个文件!

假设您要将一个文件old移动到dir文件夹中,并将其命名为new。

这可以用git mv old dir/new && git add -u dir/new完成,但这打破了历史。

相反:

1git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

将重做分支中的每个提交,在每次迭代的标记中执行命令。当你这样做的时候,很多事情都会出错。我通常会测试文件是否存在(否则它还没有移动),然后执行必要的步骤按我的喜好把树推到合适的位置。在这里,您可以通过文件来更改对文件的引用,等等。振作起来!:)

完成后,文件将被移动,日志将保持完整。你觉得自己像个忍者海盗。

当然,只有将文件移到新文件夹中时,mkdir目录才是必需的。if将避免在历史记录中早于文件存在的时间创建此文件夹。

相关讨论 作为一个痴迷于Git日志的爱好者,我不会这么做的。这些文件在当时并没有被命名,因此历史反映了一种从未存在的情况。谁知道过去哪些测试可能会失败!破坏早期版本的风险几乎在所有情况下都不值得。 @文森特,你说得对,我尽量澄清这个解决方案的不可能性是否合适。我还认为我们在讨论"历史"这个词的两个含义。在这种情况下,我很感激这两个词。 我发现有些情况下人们可能需要这个。比如说,我在自己的个人分支中开发了一些东西,现在我想在上游合并。但我发现,文件名不合适,所以我把它改成了我的个人分支。这样我就可以保持一个干净的正确的历史,从一开始就有正确的名字。 @用户2291758这是我的用例。这些更强大的git命令是危险的,但这并不意味着如果你知道你在做什么,它们就没有非常引人注目的用例! 如果可能的话,使用--index-filter进行重命名将更快,因为不必在每次提交时签出和重新签入该树。--index-filter直接作用于每个提交索引。 我不明白海军陆战队司令部在这里是怎么工作的。不是应该是吉特MV吗? @mattijokipii:mv命令用于在存储库历史中每次提交之前移动文件,因此使用普通的unix mv是正确的。我甚至不知道如果你使用git mv会发生什么。如果您使用的是Windows,那么应该使用move命令。 这是正确的答案。它起作用了。

不。

简短的回答是"否",不可能在git中重命名文件并记住历史。这是一种痛苦。

有传言说,git log --follow--find-copies-harder会起作用,但对我来说不起作用,即使文件内容没有任何变化,而且这些移动都是用git mv做的。

(最初我使用Eclipse在一个操作中重命名和更新包,这可能会混淆Git。但这是一件很常见的事情。如果只执行mv,然后执行commit和mv不太远,--follow似乎确实有效。)

Linus说,应该从整体上理解软件项目的全部内容,而不需要跟踪单个文件。可悲的是,我的小脑袋不能做到这一点。

这么多人无心重复了Git自动跟踪移动的声明,真的很烦人。他们浪费了我的时间。吉特不做这种事。按设计!!)Git根本不跟踪移动。

我的解决方案是将文件重命名回其原始位置。更改软件以适合源代码管理。有了Git,你似乎第一次就需要正确的Git。

不幸的是,这打破了Eclipse,后者似乎使用了--follow。埃多克斯1〔9〕有时不显示具有复杂重命名历史的文件的完整历史记录,即使埃多克斯1〔18〕做。(我不知道为什么。)

(有一些太聪明的黑客回去重新投入旧的工作,但他们相当可怕。参见Github要旨:Emiller/Git MV和历史。)

相关讨论 我相信你是对的。我只是想用php cs fixer重新格式化laravel 5项目的源代码,但它坚持要更改名称空间子句的大小写,以匹配app文件夹的小写值。但是名称空间(或作曲家的自动加载)只适用于camelcase。我需要将文件夹的大小写更改为app,但这会导致我的更改丢失。这是最简单的例子,但显示了Git启发式方法是如何无法遵循即使是最简单的名称更改(--follow和--find copies harder应该是规则,而不是例外)。 Git-1,颠覆+1

1git log --follow [file]

将通过重命名向您显示历史。

相关讨论 这似乎要求您在开始修改文件之前只提交重命名。如果移动文件(在shell中),然后对其进行更改,则所有赌注都将取消。 @Yoyo:那是因为Git不跟踪Renames,它会检测到它们。一个git mv基本上就是一个git rm && git add。有一些选项,如-M90/--find-renames=90,当文件90%相同时,可以考虑将其重命名。

我愿意:

12git mv {old} {new} git add -u {new} 相关讨论 -u似乎没有为我做任何事情,它是否应该更新历史? 请参见:git-scm.com/docs/git-add 也许你想用-A的行为来代替?同样,请参见:git-scm.com/docs/git-add 它确实添加了文件,但是它不更新历史记录,因此"git日志文件名"显示完整的历史记录。如果仍然使用--follow选项,它只显示完整的历史记录。 哦,我明白了。真倒霉。。。。 我做了一个复杂的重构,移动了一个include目录(使用mv,而不是gitmv),然后在重命名的文件中更改了许多include路径。Git找不到足够的相似性来跟踪历史。但吉特加-u正是我需要的。Git状态现在表示"重命名",在此之前它显示"已删除"和"新文件"。 为我工作不需要git add -u {new}。 这个答案可能与这个问题毫无关联,但不需要描述如何解决。据我所知,如果你已经使用过git mv或git rm,git add -u没有任何影响。OP已经注意到git mv并不能解决他们的问题。显然,这个答案帮助了一些想要提高git status产量的人。 有很多问题可以解决git add -u的目的。Git文档往往没有帮助,是我最不想看到的地方。这里有一篇文章展示了git add -u的实际应用:stackoverflow.com/a/2117202。 在我的例子中,git add -u没有改变任何东西。显示文件在使用git add -u之前被重命名,显示文件在使用git -add -u之后被重命名。

目标 use git am(inspired from smar,borrowed from exherbo) 添加复制/移动文件的提交历史记录 从一个目录到另一个目录 或者从一个存储库到另一个存储库

限制

不保留标签和分支 在路径文件重命名(目录重命名)上剪切历史记录

总结

用电子邮件格式提取历史记录江户十一〔一〕号 重新组织文件树并更新文件名 使用附加新历史记录埃多克斯1〔2〕

1。以电子邮件格式提取历史记录

示例:提取file3、file4和file5的历史记录

123456789101112my_repo ├── dirA │;; ├── file1 │;; └── file2 ├── dirB            ^ │   ├── subdir      | To be moved │   │   ├── file3   | with history │   │   └── file4   | │;; └── file5       v └── dirC     ├── file6     └── file7

设置/清理目的地

12export historydir=/tmp/mail/dir       # Absolute path rm -rf"$historydir"    # Caution when cleaning the folder

以电子邮件格式提取每个文件的历史记录

12cd my_repo/dirB find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p"$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary --"$0">"$historydir/$0"' {} ';'

不幸的是,选项--follow或--find-copies-harder不能与--reverse组合。这就是重命名文件(或重命名父目录)时剪切历史的原因。

电子邮件格式的临时历史记录:

12345/tmp/mail/dir     ├── subdir     │   ├── file3     │   └── file4     └── file5

dan bonachea建议在第一步中反转git log generation命令的循环:不要为每个文件运行一次git log,而是使用命令行上的文件列表运行它一次,并生成一个统一的日志。通过这种方式提交,修改多个文件在结果中保持单个提交,并且所有新提交都保持其原始相对顺序。注意:在(现在是统一的)日志中重写文件名时,还需要在下面的第二步中进行更改。

2。重新组织文件树并更新文件名

假设您想在另一个repo中移动这三个文件(可以是同一个repo)。

123456789101112my_other_repo ├── dirF │   ├── file55 │   └── file56 ├── dirB              # New tree │   ├── dirB1         # from subdir │   │   ├── file33    # from file3 │   │   └── file44    # from file4 │   └── dirB2         # new dir │        └── file5    # from file5 └── dirH     └── file77

因此,重新组织文件:

123456cd /tmp/mail/dir mkdir -p dirB/dirB1 mv subdir/file3 dirB/dirB1/file33 mv subdir/file4 dirB/dirB1/file44 mkdir -p dirB/dirB2 mv file5 dirB/dirB2

您的临时历史记录现在是:

1234567/tmp/mail/dir     └── dirB         ├── dirB1         │   ├── file33         │   └── file44         └── dirB2              └── file5

同时更改历史记录中的文件名:

12cd"$historydir" find * -type f -exec bash -c 'sed"/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i"$0"' {} ';'

三。应用新历史记录

你的其他回购协议是:

123456my_other_repo ├── dirF │   ├── file55 │   └── file56 └── dirH     └── file77

从临时历史文件应用提交:

12cd my_other_repo find"$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date

--committer-date-is-author-date保留了最初的提交时间戳(dan bonacha的评论)。

您的其他回购协议现在是:

123456789101112my_other_repo ├── dirF │   ├── file55 │   └── file56 ├── dirB │   ├── dirB1 │   │   ├── file33 │   │   └── file44 │   └── dirB2 │        └── file5 └── dirH     └── file77

使用git status查看准备推送的提交量:—)

额外技巧:检查repo中重命名/移动的文件

要列出已重命名的文件:

1find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

更多定制:您可以使用选项--find-copies-harder或--reverse完成命令git log。您还可以使用cut -f3-和grepping complete pattern'.*=>.*删除前两列。

1find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

相关讨论 注意:此技术将更改2个或多个文件的提交拆分为单独的分段提交,并通过对文件名进行排序来扰乱它们的顺序(因此一个原始提交的片段不会出现在线性历史中相邻的位置)。因此,生成的历史记录仅在逐个文件的基础上"正确"。如果要移动多个文件,则结果历史记录中的任何新提交都不代表原始回购历史记录中存在的已移动文件的一致快照。 嗨,Danbonachea。感谢您的有趣反馈。我已经成功地迁移了一些包含多个文件的repos(即使重命名的文件和跨目录移动的文件也是如此)。你建议在这个答案中改变什么?你认为我们应该在这个答案的顶部添加一个警告横幅来解释这个技术的局限性吗?干杯 我采用了这种技术来避免这个问题,方法是在步骤1中反转git log generation命令的循环。也就是说,不是每个文件运行一次git日志,而是在命令行上使用文件列表运行一次,并生成一个统一的日志。通过这种方式提交修改的2个或多个文件在结果中保持单个提交,并且所有新提交都保持其原始的相对顺序。注意:当在(现在统一的)日志中重写文件名时,这还需要在步骤2中进行更改。我还使用了git am--committer日期是作者日期来保留原始提交时间戳。 感谢您的实验和分享。我已经为其他读者更新了一些答案。不过,我花了些时间来测试您的处理过程。如果您想提供命令行示例,请随意编辑此答案。干杯;)

I would like to rename/move a project subtree in Git moving it from

1/project/xyz

to

/components/xyz

If I use a plain git mv project components, then all the commit history for the xyz project gets lost.

没有(8年后,Git2.19,2018年第3季度),因为Git将检测到目录重命名,现在这是更好的记录。

见Elijah Newren(newren提交的commit b00bf1c、commit 1634688、commit 0661e49、commit 4d34dff、commit 983f464、commit c840e1a、commit 9929430(2018年6月27日)和commit d4e8062、commit 5dac4a(2018年6月25日)。(由Junio C Hamano--gitster于2018年7月24日在Commit 0ce5a69中合并)

这在Documentation/technical/directory-rename-detection.txt中解释:

例子:

When all of x/a, x/b and x/c have moved to z/a, z/b and z/c, it is likely that x/d added in the meantime would also want to move to z/d by taking the hint that the entire directory 'x' moved to 'z'.

但也有很多其他情况,比如:

one side of history renames x -> z, and the other renames some file to x/e, causing the need for the merge to do a transitive rename.

为了简化目录重命名检测,这些规则由git强制执行:

一些基本规则限制了应用目录重命名检测:

If a given directory still exists on both sides of a merge, we do not consider it to have been renamed. If a subset of to-be-renamed files have a file or directory in the way (or would be in the way of each other),"turn off" the directory rename for those specific sub-paths and report the conflict to the user. If the other side of history did a directory rename to a path that your side of history renamed away, then ignore that particular rename from the other side of history for any implicit directory renames (but warn the user).

您可以在t/t6043-merge-rename-directories.sh中看到许多测试,它们还指出:

a) If renames split a directory into two or more others, the directory with the most renames,"wins". b) Avoid directory-rename-detection for a path, if that path is the source of a rename on either side of a merge. c) Only apply implicit directory renames to directories if the other side of history is the one doing the renaming.

虽然Git的核心是Git管道,但它不跟踪重命名,但如果您愿意,可以通过Git日志"瓷质"来检测它们。

对于给定的git log,使用-m选项:

git log -p -M

使用当前版本的Git。

这也适用于其他命令,如git diff。

有一些选项可以使比较更严格或更不严格。如果重命名文件时不同时对文件进行重大更改,则Git日志和朋友更容易检测到重命名。因此,有些人在一次提交中重命名文件,在另一次提交中更改文件。

每当你要求Git查找文件的重命名位置时,CPU的使用都会有一定的代价,所以无论你是否使用它,以及何时使用,都取决于你自己。

如果希望在特定存储库中始终报告具有重命名检测功能的历史记录,则可以使用:

git config diff.renames 1

检测到从一个目录移动到另一个目录的文件。下面是一个例子:

123456789101112131415161718192021commit c3ee8dfb01e357eba1ab18003be1490a46325992 Author: John S. Gruber Date:   Wed Feb 22 22:20:19 2017 -0500     test rename again diff --git a/yyy/power.py b/zzz/power.py similarity index 100% rename from yyy/power.py rename to zzz/power.py commit ae181377154eca800832087500c258a20c95d1c3 Author: John S. Gruber Date:   Wed Feb 22 22:19:17 2017 -0500     rename test diff --git a/power.py b/yyy/power.py similarity index 100% rename from power.py rename to yyy/power.py

请注意,无论何时使用diff,这都是有效的,而不仅仅是与git log一起使用。例如:

12345$ git diff HEAD c3ee8df diff --git a/power.py b/zzz/power.py similarity index 100% rename from power.py rename to zzz/power.py

作为试验,我在一个功能分支中的一个文件中做了一个小的更改并提交了它,然后在主分支中重命名了该文件,提交了,然后在文件的另一部分中做了一个小的更改并提交了它。当我转到功能分支并从master合并时,合并重命名了文件并合并了更改。下面是合并的输出:

123456 $ git merge -v master  Auto-merging single  Merge made by the 'recursive' strategy.   one => single | 4 ++++   1 file changed, 4 insertions(+)   rename one => single (67%)

结果是一个工作目录,文件被重命名,两个文本都做了更改。所以Git可以做正确的事情,尽管它没有明确跟踪重命名。

这是一个老问题的迟回答,因此其他答案可能对当时的Git版本是正确的。

我移动文件,然后做

1git add -A

将所有已删除/新文件放入Sataging区域。在这里,Git意识到文件被移动了。

12git commit -m"my message" git push

我不知道为什么,但这对我有用。

相关讨论 不为我工作:( 这里的诀窍是您不必更改单个字母,即使您更改了某些内容并按ctrl+z,历史记录也将被破坏。所以在这种情况下,如果你写了一些东西,恢复文件,然后再次移动它,为它做一个单独的add->commit。 也不为我工作:(



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3