如何在Visual Studio中执行git移动操作,在解决方案资源管理器中重命名或移动文件时,不使用git删除和git添加?

18

背景

我经常在Visual Studio 2022中移动、重命名文件。重命名是标准的重构实践。然而,当我在“解决方案资源管理器”中重命名文件时,并没有执行git mv操作,而是执行了git删除和git添加。

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

问题

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

git mv myoldfile.cs mynewfile.cs

光是使用版本控制工具来保留历史记录并不能解决离开IDE会影响生产效率的问题,特别是当需要重构和改名多个类/文件时。

如何在Visual Studio中执行git mv命令来重命名、移动Solution Explorer中的文件,而不是使用git delete和git add命令?


2
有趣的事实:git没有“移动”或“重命名”的概念。请记住:git提交是一个快照,而不是差异/增量。 - Dai
@dai,也许我的问题没有表达清楚,我知道如何使用git移动文件(git mv),我想知道如何在不离开IDE的情况下执行此操作,并发出git mv oldname.cs newname.cs命令。我将编辑问题。 - g.pickardou
2
你知道 git mv 在你的仓库中并不存储任何“特殊”的或独特的东西吗?一个 git mv 命令与你自己手动移动文件是完全相同的,或者使用其他任何工具。这就是为什么没有 IDE 支持它的原因:因为根本不需要。 - Dai
https://stackoverflow.com/search?q=%5Bgit%5D+rename+detection - phd
@Dai,你关于Git移动检测的两个评论和最后一个有关重构更改的评论就是答案。如果你要发布回答,我会接受它,否则我可能会删除这个问题。 - g.pickardou
显示剩余8条评论
1个回答

22

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

  • git提交是在特定时间点上您整个仓库的快照。
  • git提交不是差异或变更集。
  • git提交不包含任何文件"重命名"信息。
  • git本身不记录、监视、记录或以其他方式关注移动或重命名的文件(在创建提交时)。

以上可能与一些人的直觉相反,甚至对于一些人来说可能是惊人的(当我第一次了解到这个时,我自己也是如此),因为它与所有主要的前置源代码控制系统(SVN、TFS、CSV、Perforce (Helix之前)和其他系统)相反,因为所有这些系统都存储差异或变更集,这是它们模型的基础。

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

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

...但是我的git GUI明显显示了文件重命名,而不是文件删除+添加或编辑!

虽然git不会存储文件重命名的信息,但它仍然能够启发式地检测出在任何两个git提交之间被重命名的文件,以及检测出在您未提交的repo工作目录树和您的HEAD提交之间被重命名/移动的文件(也称为“与未修改文件比较”)。
例如:
考虑有2个文件的提交“快照1”:Foo.txt和Bar.txt。
然后将Foo.txt重命名为Qux.txt(并且没有进行其他更改)。
然后将其保存为新提交(“快照2”)。
如果您要求git使用“快照1”和“快照2”进行diff,则git可以看到Foo.txt已重命名为Qux.txt(而Bar.txt未更改),因为它们的内容(因此是文件的密码散列)相同,因此它推断从Foo.txt到Qux.txt进行了文件重命名。
趣闻:如果您要求git执行相同的diff,但使用“快照2”作为基本提交并使用“快照1”作为后续提交,则git将向您显示它检测到从Qux.txt返回到Foo.txt的重命名。
然而,如果您在两个提交之间做了更多操作,例如同时编辑文件,则git可能会将该文件视为一个新的单独文件而不是重命名文件。
这不是错误,而是一种特性:这种行为意味着git可以比文件中心的源代码控制(如TFS和SVN)更好地处理常见的文件系统级重构操作(例如拆分文件),并且您也不会看到与重构相关的虚假重命名。
例如,考虑一种重构场景,您将包含多个类定义的MultipleClasses.cs文件拆分为单独的.cs文件,每个文件一个类。在这种情况下,没有真正的“重命名”正在执行,而git的diff将向您显示1个文件被删除(MultipleClassesw.cs),同时新的SingleClass1.cs、SingleClass2.cs等文件被添加。
我想象您不希望它作为从MultipleClasses.cs重命名为SingleClass1.cs的重命名保存到源代码控制历史记录中,就像如果您允许第一个重命名在SVN/TFS中保存为重命名一样。
但是,正如您可以想象的那样,有时候git的启发式算法不起作用,需要使用--follow和/或--find-renames=(也称为-M)在此处很好地解释了这一点
我个人首选的做法是将基于文件系统的和编辑代码文件的更改分别保留在不同的git提交中(因此提交仅包含编辑文件,或仅包含添加+删除文件,或仅包含拆分更改),这样您就可以更轻松地让git的--follow启发式算法检测重命名/移动。
(这确实意味着在使用VS的Refactor Rename功能时,我需要暂时将文件重新命名回来,以便我可以进行编辑文件但不包含任何重命名文件的提交)。

这与Visual Studio有什么关系呢?

考虑以下情况:
  • 您有一个现有的git repo,用于一个C#项目,没有待处理的更改(暂存或其他)。 该项目具有位于Project / Foobar.cs的文件,其中包含class Foobar。 文件大小仅约为1KB。
  • 然后,您使用Visual Studio的Refactor>Rename ...功能将class Foobar重命名为class Barfoo
    • Visual Studio不仅会将class Foobar重命名为class 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内置的git GUI)仅看到带有Foobar.cs的最后一次提交,并且看到当前HEAD(带有未提交的更改)具有与Foobar.cs不同1%的Barfoo.cs,因此它认为这是一个重命名/移动而不是删除+添加或编辑,因此Visual Studio的Solution Explorer将在该文件旁边使用“移动/重命名”git状态图标,而不是“文件已编辑”或“新文件”状态图标。
    • 但是,如果您对Barfoo.cs进行了更大的更改(尚未提交),超过默认更改%阈值50%,则Solution Explorer将开始显示“新文件”图标而不是“重命名/移动文件”图标。
      • 如果您手动还原了Barfoo.cs的某些更改(同样:尚未保存任何提交),使其低于50%的更改阈值,则VS的Solution Explorer将再次显示重命名图标。
  • 关于git不存储提交中的实际文件重命名/移动的一个好处是,这意味着您可以安全地使用git与任何软件,包括任何重命名/移动文件的软件! 特别是不是源代码控制感知的软件。

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

    • 这就

2
有点令人困惑,因为如果你重命名它并执行git状态,它不会像git mv那样告诉你它已经被移动。但是当你提交它时,它会告诉你。 - dan-klasson
这是因为git mv命令同时将旧文件和新文件添加到暂存区。如果你手动重命名文件,然后对旧文件和新文件都执行git add命令,使得修改被暂存,即使在提交之前,你也会看到它作为一次重命名操作。(反之亦然:如果你使用git mv命令,但随后使用git reset命令取消暂存的修改,你将会看到一个未暂存的删除+添加操作。) - CherryDT
这是因为git mv命令会将旧文件和新文件都加入到暂存区。如果你手动重命名文件,然后对旧文件和新文件都执行git add命令,使得改动被暂存,即使在提交之前,你也会看到它被识别为重命名操作。(反之亦然:如果你使用git mv命令,然后使用git reset命令取消暂存的改动,你会看到一个未暂存的删除和添加操作。) - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接