帮助Git工具跟踪重命名

wgxvkvu9  于 2023-04-28  发布在  Git
关注(0)|答案(2)|浏览(203)

Git不会在历史中“跟踪”文件重命名;Git跟踪文件 * 内容 *,而不是文件名。然而,有两个简单而实际的问题我仍然不清楚。
我知道查看重命名文件的历史记录需要调整一些启发式参数,不同的Git工具在不同程度上实现了这一点。也就是说,仅仅因为它 * 有可能 * 告诉Git基于一些e来检测重命名。例如,95%相似度阈值,这并不意味着所有的Git工具都允许这样做,或者如果他们这样做了,他们就正确地实现了它。(试图在Eclipse中查看重命名文件的历史记录会带来麻烦。)然而,如果文件没有重命名,大多数工具似乎能够跟踪同一文件中的更改。
作为一个开发人员,我可以做两件简单的事情,从表面上看,这将有助于工具跟踪重命名。我只是想知道他们是否真的会帮助,或者如果它是毫无意义的。
让我们假设我想将文件foo.txt重命名为bar.txt,并更改一小部分内容(假设为1%)。(是的,我理解提交一个git mv与在同一次提交中提交一个git rm和一个git add是一样的。)之后,我希望能够看到foo.txtbar.txt之间的变化。以下是我可以做到的三种方法:

  • 同时提交重命名和更改。
git mv foo.txt bar.txt
# make changes to bar.txt
git commit …
  • 在一次提交中重命名,在另一次提交中修改。
git mv foo.txt bar.txt
git commit …
# make changes to bar.txt
git commit …

在这种情况下,重命名涉及100%相同的内容。然后,对同一个文件bar.txt进行更改。
如果Git只关注内容而不关注文件名,那么我使用这种方法有什么收获吗?特别是从技术Angular 来看,我有什么收获吗?这种方法是否有助于大多数工具在查看历史记录时以任何方式跟踪重命名?

  • 在一个提交中重命名,在另一个提交中修改,然后合并。

如果上面的第二种方法有任何帮助,那么如果我在一个分支中做同样的事情会怎么样:

git checkout -b foobar
git mv foo.txt bar.txt
git commit …
# make changes to bar.txt
git commit …
git checkout main
git merge foobar --no-ff

在这种情况下,重命名涉及100%相同的内容。然后对同一个文件bar.txt进行更改。然而,merge 将在同一合并提交中重命名文件并修改其1%的内容。
尽管合并了,我在分支中使用这种方法 * 有什么收获吗?或者分支合并是否否定了在分支中使用这种技术的任何好处(看到合并提交将不可避免地将重命名和内容修改合并到一个提交中)?在分支中使用这种方法是否有助于大多数工具在查看历史时以任何方式跟踪重命名?
如果没有别的,知道这些技术是否有助于使用Git CLI,VS Code和Eclipse会很有帮助。

kupeojn6

kupeojn61#

Git对文件历史的记录是提交父链的一个 emergent 属性。
我的意思是,如果你重命名一个文件,然后提交,那么从Git的Angular 来看,相对于那个提交的父提交,一个文件被删除,一个文件被创建。就这样这些文件在某种程度上是“同一个”文件的概念是在你的头脑中-但不是在Git的头脑中。Git对这种事一无所知。
但是,当跟踪一个文件时,Git会 * 尝试 * 注意文件可能被重命名的情况。如果文件的重命名是在提交和其父提交之间发生的,那么Git可以注意到删除的文件和创建的文件具有相同的内容,并且它可以将此报告为重命名。
所以,总结一下:

  • 如果提交A中的一个文件和提交A-parent中的一个文件具有相同的名称,则它们“是”相同的文件。
  • 如果提交A中的一个文件在提交A-parent中没有以该名称出现,但有一个具有相同内容的文件出现,则它们“是”具有重命名的同一文件。

因此,如果您小心地重命名并提交,然后编辑并提交,您就可以实现可靠的跟踪。
另一方面,如果提交A中的文件在提交A-父代中没有以该名称出现,但是具有 * 不同 * 内容的文件出现在A-父代中而不是A中,那么以下历史是否会将这些报告为“相同”文件是一种废话。这取决于Git的算法,它与文件之间的相似性等有关。
故事中合并提交的参与并没有改变我刚才所说的内容。合并提交只是一个提交-它只是碰巧有两个父级。但是在将合并提交与父节点中的任何一个进行比较时,Git将遵循我刚才阐述的逻辑。另一个父对象的存在并不会告知Git关于这个父对象的逻辑。
为了说明,我将从这个图表开始:

* ace8b40 (HEAD -> main) Merge branch 'br'
|\
| * 487c79f (br) edited c
| * d9d1fec renamed a to c
* | c6abcc2 b
|/  
* 139a494 edited a
* 6b3d147 a
* 4eb0507 initial

这里,main是一个合并提交,有两个父提交:c6 abcc 2和487 c79 f。我们先来谈谈第一位家长。
就Git而言,比较main和它的第一个父文件,文件 a 被删除,文件 c 被创建。他们毫无共同之处。所以如果你跟踪链中的第一个父节点,这就是Git报告的内容:

% git log --first-parent --stat --oneline
ace8b40 (HEAD -> main) Merge branch 'br'
 a | 1 -
 c | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)
c6abcc2 b
 b | 1 +
 1 file changed, 1 insertion(+)
139a494 edited a
 a | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
6b3d147 a
 a | 1 +
 1 file changed, 1 insertion(+)
4eb0507 initial

正如你所看到的,Git说文件 a 被删除,文件 c 被添加。但是,如果我们跟踪 both parents,我们会在分支br上看到一个非常不同的报告:

% git log --stat --oneline --graph 
*   ace8b40 (HEAD -> main) Merge branch 'br'
|\  
| * 487c79f (br) edited c
| |  c | 2 +-
| |  1 file changed, 1 insertion(+), 1 deletion(-)
| * d9d1fec renamed a to c
| |  a => c | 0
| |  1 file changed, 0 insertions(+), 0 deletions(-)
* | c6abcc2 b
|/  
|    b | 1 +
|    1 file changed, 1 insertion(+)
* 139a494 edited a
|  a | 2 +-
|  1 file changed, 1 insertion(+), 1 deletion(-)
* 6b3d147 a
|  a | 1 +
|  1 file changed, 1 insertion(+)
* 4eb0507 initial

符号a => c告诉你,在 this commit和其父提交之间,Git认为文件 a 已经被重命名为 c
因此,如果我们只要求跟踪文件 c 的历史,我们得到的结果将取决于我们如何遍历父文件。如果我们沿着第一个父链跟踪它,历史会立即停止,因为就第一个父链而言,c 只是凭空创建的:

% git log --stat --oneline --graph --first-parent --follow -- c
* ace8b40 (HEAD -> main) Merge branch 'br'
|  c | 1 +
|  1 file changed, 1 insertion(+)

但如果我们遍历父母双方,情况就不同了:

% git log --stat --oneline --graph --follow -- c 
... 
| * 487c79f (br) edited c
| |  c | 2 +-
| |  1 file changed, 1 insertion(+), 1 deletion(-)
| * d9d1fec renamed a to c
| |  a => c | 0
| |  1 file changed, 0 insertions(+), 0 deletions(-)
...
* 139a494 edited a
|  a | 2 +-
|  1 file changed, 1 insertion(+), 1 deletion(-)
* 6b3d147 a
|  a | 1 +
|  1 file changed, 1 insertion(+)

在这里,a 从创建到创建的整个过程都被算作 c 的历史的一部分,因为遍历的历史包括br,因此重命名被考虑在内。

k4emjkb1

k4emjkb12#

第二种方法应该使git log --follow -- bar.txt完美地工作。
如果您的唯一目标是对该特定文件进行重命名检测,那么我看不到您的第三种“一边修改,另一边重命名”方法的收益。
我只看到在重命名检测方面有更多的复杂性,切断了遵循更简单路径的方法(如git log --first-parent),以及在您的历史中添加了一个fork&merge序列,恕我直言,这不是一个奖励。
请参考matt's answer,他给出了一个非常详细和解释性的答案,说明了在合并提交时如何处理重命名检测

相关问题