在解决方案资源管理器中重命名、移动文件时,如何在 Visual Studio 中执行 git move,而不是 git delete 和 git add?

g.p*_*dou 21 git visual-studio

语境

我经常在 Visual Studio 2022 中移动、重命名文件。重命名是标准的重构实践。但是,当我在解决方案资源管理器中重命名文件时,不会git mv执行任何操作,而是执行 git delete 和 git add 。

这会导致丢失该特定文件/类的历史记录,这在许多情况下都是巨大的损失。

问题

我可以离开 IDE 并使用命令行来执行移动操作

git mv myoldfile.cs mynewfile.cs
Run Code Online (Sandbox Code Playgroud)

这将完美地保留历史记录,但离开 IDE 会成为生产力杀手,尤其是在谈论重构和重命名多个类/文件时。

在解决方案资源管理器中重命名、移动文件时,如何git mv在 Visual Studio 中执行,而不是 git delete 和 git add?

Dai*_*Dai 24

首先,让我们澄清一些误解......

  • 提交git是整个存储库在给定时间点的快照。
  • 提交git不是差异或变更集。
  • 提交git不包含任何文件“重命名”信息。
  • 并且git它本身不会记录、监视、记录或以其他方式关注移动或重命名的文件(...在创建提交时)。

上述内容可能是反直觉的,甚至对某些人(包括我自己,当我第一次了解到这一点时)来说是令人兴奋的,因为它与所有主要的先前源代码控制系统(如 SVN、TFS、CSV、Perforce (Helix 之前))相反等等,因为所有这些系统存储差异或变更集,并且这是它们模型的基础。

在内部,git 确实使用了各种形式的比较和增量压缩,但是这些是故意对用户隐藏的,因为它们被认为是实现细节。这是因为 git 的域模型完全建立在原子提交的概念之上,原子提交代表整个存储库在特定时间点的快照状态。此外,使用操作系统的低级文件更改检测功能来检测哪些特定文件已更改,而无需重新扫描整个工作目录:在 Linux/POSIX 上它使用lstat,在 Windows 上(lstat不可用)它使用fscache. 当 git 计算存储库的哈希值时,它使用默克尔树结构来避免不断重新计算存储库中每个文件的哈希值。

那么如何git处理移动或重命名的文件呢?

...但我的gitGUI 清楚地显示文件重命名,而不是文件删除+添加或编辑!

  • 虽然git存储有关文件重命名的信息,但它仍然能够启发式检测任意两个 git 提交之间的重命名文件,以及检测在未提交的存储库工作目录树和提交之间重命名/移动的文件HEAD(又名“与未修改”)。

  • 例如:

    • 考虑使用 2 个文件提交“快照 1”:Foo.txtBar.txt.
    • 然后重命名Foo.txtQux.txt(并且不进行其他更改)。
    • 然后将其保存为新提交(“快照 2”)。
    • 如果你要求gitdiff快照 1”和“快照 2”,那么 git 可以看到它Foo.txt被重命名为Qux.txt(并且Bar.txt没有改变),因为内容(以及文件的加密哈希值)是相同的,因此它推断文件重命名Foo.txtQux.txt发生。
      • 有趣的事实:如果您要求git执行相同的 diff,但使用“快照 2”作为基本提交,使用“快照 1”作为后续提交,那么 git 会显示它检测到从Qux.txtback 到Foo.txt.
  • 但是,如果您所做的不仅仅是重命名或在两次提交之间移动文件,例如同时编辑文件,那么 git可能会或可能不会将该文件视为新的单独文件,而不是重命名的文件。

    • 这不是一个错误,而是一个功能:这种行为意味着可以git比以文件为中心的源代码控制(如 TFS 和 SVN)更好地处理常见的文件系统级重构操作(如拆分文件),并且您赢了也没有看到与重构相关的错误重命名
    • 例如,考虑一种重构场景,您将MultipleClasses.cs包含多个class定义的文件拆分为单独的.cs文件,每个class文件一个。在这种情况下,没有执行真正的“重命名”,并且gitdiff 会显示 1 个文件在添加新的 、 等文件 的MultipleClassesw.cs同时被删除 ( ) 。SingleClass1.csSingleClass2.cs
      • 我想如果您允许将第一个重命名保存为 SVN/TFS 中的重命名,您不会希望将其作为重命名 from 保存到源代码管理历史记录MultipleClasses.csSingleClass1.cs,就像SVN 或 TFS 中一样。
  • 但是,正如您可以想象的那样,有时git启发式不起作用,您需要使用--followand/or --find-renames=<percentage>(又名-M<percentage>来刺激它。

  • 我个人的首选做法是将基于文件系统的更改和编辑代码文件的更改保存在单独的 git 提交中(因此提交包含已编辑的文件,或添加+删除的文件,或包含拆分更改),这样您就可以git 的--follow启发式检测重命名/移动要容易得多。

    • (这确实意味着在使用 VS 的重构重命名功能时,我确实需要临时重命名文件,fwiw,这样我就可以使用已编辑的文件进行提交,但没有任何重命名的文件)。

但这与 Visual Studio 有什么关系呢?

  • 考虑这种情况:

    • 您有一个 C# 项目的现有 git 存储库,没有挂起的更改(暂存或其他方式)。该项目有一个文件位于Project/Foobar.cs包含class Foobar. 该文件大小仅为 1KB 左右。
    • 然后,您可以使用 Visual Studio 的Refactor > Rename...功能将 a 重命名class Foobarclass Barfoo.
      • Visual Studio 不仅会重命名class Foobarclass Barfoo 编辑Foobar项目中其他位置Foobar.cs出现的所有Barfoo.cs.
      • 在此示例中,标识符Foobar仅在 1KB 大小的Foobar.cs文件中出现两次(第一次在 中class Foobar,然后再次在构造函数定义中Foobar() {}),因此仅更改了 12 个字节(2 * 6 个字符)。在 1KB 文件中,有 1% 的变化 ( 12 / 1024 == 0.0117 --> 1.17%)。
      • git(和 Visual Studio 的内置gitGUI)只能看到最后一次提交Foobar.cs,并且看到当前 HEAD(带有未提交的更改)与Barfoo.cs1% 不同,Foobar.cs因此它认为重命名/移动而不是删除+添加或编辑,因此 Visual Studio 的解决方案资源管理器将使用该文件旁边的“移动/重命名”git 状态图标,而不是“文件已编辑”或“新文件”状态图标。
      • 但是,如果您进行的更改Barfoo.cs(尚未提交)超过默认更改百分比阈值 50%,则解决方案资源管理器将开始显示“新文件”图标,而不是“重命名/移动的文件”图标。
        • 如果您手动还原某些更改Barfoo.cs(再次强调:尚未保存任何提交),使其滑落到 50% 更改阈值以下,则 VS 的解决方案资源管理器将再次显示“重命名”图标。
  • git在提交中不存储实际文件重命名/移动的一个巧妙之处在于,这意味着您可以安全地使用git任何软件,包括任何重命名/移动文件的软件!尤其是不具备源代码控制意识的软件。

    • 以前,使用 SVN 和 TFS,您需要将自己限制为对您所使用的任何源代码控制系统(并自行处理重命名)具有内置支持的软件程序或支持 MSSCCI 的软件(因此通过 MSSCCI 保存重命名),否则,您必须使用单独的 SVN 或 TFS 客户端来保存/提交文件重命名(例如,分别为 TortoiseSvn 和 Team Foundation Explorer)。这是一个乏味且容易出错的过程,我很高兴看到它的结束。
  • 因此,Visual Studio(有或没有 git内置支持)不需要通知git文件已重命名/移动。

    • 这就是为什么 IDE 不支持它:因为根本不需要它。
  • 事实上,git 提交不是增量,而是快照,这意味着您可以更轻松地重新排序提交,并以最小的痛苦重新调整整个分支的基础。这在 SVN 或 TFS 中根本不可能实现。

    • (毕竟,如何才能有意义地重新排序文件重命名操作?)

  • 这有点令人困惑,因为如果您重命名它并执行 git status,它不会像“git mv”那样告诉您它已被移动。但当你提交它时,它就会发生。 (2认同)