如何在没有后续编辑的情况下进行重命名? [英] How to stage a rename without subsequent edits in git?

查看:174
本文介绍了如何在没有后续编辑的情况下进行重命名?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个我已经重新命名并编辑的文件。我想告诉Git进行重命名,但不是内容修改。也就是说,我希望删除旧的文件名,并添加旧的文件内容与新的文件名。



所以我有这个:

 未进行提交的更改:

已删除:old-name.txt

未记录的文件:

new-name.txt

这:

 要提交的更改:

新文件:new-name.txt
已删除:old-name.txt

不进行提交的更改:

修改:new-name.txt

或者:

 要提交的更改:

已更名为:old-name.txt - > new-name.txt

不进行提交的更改:

修改:new-name.txt

(其中相似性度量应该是100%)。

我想不出一种简单的方法来做这是。



是否有获取特定文件的特定修订内容的语法,并将其添加到指定路径下的git临时区域?



删除部分( git rm )很好:

  $ git rm old-name.txt 

这是添加部分我正在努力争取的重新命名。 (我可以保存新内容,在shell中检出新副本(用于旧内容), mv git add

谢谢!

解决方案

Git并没有真的重命名。它们都是以事后的方式计算出来的:git比较一个提交与另一个提交,而比较时间决定是否有重命名。这意味着git是否认为重命名是动态变化的。我知道你问的是一个你还没有做出的承诺,但是忍耐着我,这真的都是配合的(但答案会很长)。



< hr>

当你问git(通过 git show git log -p git diff HEAD ^ HEAD )上次提交时发生了什么,它运行前一个提交( HEAD ^ HEAD〜1 或前一个提交的实际原始SHA-1,其中任何一个都可以识别它)和当前提交( HEAD )。在制作这个差异时,它可能会发现过去有一个 old.txt ,而不再是这样;并且没有 new.txt 但现在有了。



这些文件名称 - 曾经存在的文件但是没有,并且现在没有的文件被放入标有重命名候选人的桩中。然后,对于堆中的每个名称,git比较旧内容和新内容。由于git将内容减少到SHA-1的方式,精确匹配的比较是非常容易的;如果完全匹配失败,git会切换到一个可选的至少是类似的内容diff来检查重命名。使用 git diff 这个可选步骤由 -M 标志控制。使用其他命令,它可以由 git config 值设置,或者硬编码到命令中。



现在回到暂存区和 git status :索引/暂存区中存储的内容基本上是下一次提交的原型。当你 git add >时,git将文件内容存储在该点,在这个过程中计算SHA-1,然后将SHA-1存储在索引中。当你 git rm 时,git会在索引中存储一条记录,说明在下一次提交时有意删除此路径名称。

git status 命令然后简单地做一个差异 - 或者真的是两个差异: HEAD 对索引,为什么要承诺;以及索引与工作树之间的关系,以确定可以(但尚未)被提交。



git使用与往常一样的机制来检测重命名。如果 HEAD 提交中有一个路径在索引中消失了,并且索引中的路径是新的,而不是 HEAD commit,它是重命名检测的候选对象。 git status 命令将硬件重新命名检测设置为开启(并且文件计数限制为200;只有一个重新命名检测候选人,此限制很多)。






这对你的情况意味着什么?那么,你重命名一个文件(不使用 git mv ,但它并不重要,因为 git status 找到重命名,或无法找到它,在 git status 时间),现在有一个更新的,不同版本的新文件。



如果您的 git add 新版本,该新版本进入回购库,并且其SHA-1位于索引中,当 git status 做一个差异,它会比较新的和旧的。如果它们至少50%相似( git status 的硬连线值),git会告诉你文件被重命名。



当然, git add - 修改过的内容并不完全符合您的要求:您希望执行仅在 中重新命名的中间提交,即使用新名称的树进行提交,但使用旧内容。



因为所有上述动态重命名检测都有这样做。如果您希望这样做(无论出于何种原因)......好吧,git并不是那么容易。



最直接的方法就像你所建议的那样:把修改后的内容移到某处,使用 git checkout - old-name.txt ,然后 git mv old-name.txt new-name.txt ,然后提交。 git mv 将重命名索引/登台区域中的文件,并重命名工作树版本。



<如果 git mv 有一个 - 缓存的选项,比如 git rm 可以,只需 git mv --cached old-name.txt new-name.txt 然后 git commit 。第一步将重命名索引中的文件,而不触及工作树。但它并没有:它坚持重写工作树版本,并坚持要求旧的名字必须存在于工作树中才能开始。



单曲在不触及工作树的情况下执行此操作的一步方法是使用 git update-index --index-info ,但这也有点麻烦(我会显示无论如何它在一瞬间)。幸运的是,我们还有最后一件事可以做。我已经设置了相同的情况,将旧名称重命名为新名称并修改文件:

  $ git status 
在分支主机上
更改没有为commit提交:
(使用git add / rm< file> ...更新将提交的内容)
(使用git checkout - < file>放弃工作目录中的更改)

deleted:old-name.txt

未记录的文件:
(使用git add< file> ...来包含将要提交的内容)

new-name.txt

现在我们要做的是,首先,手动将文件重新放回其旧名称,然后使用 git mv 再次切换到新名称:

  $ mv new-name.txt old-name.txt 
$ git mv old-name.txt new-name.txt

这次 git mv 更新索引中的名称,但是保留原始内容作为索引SHA-1,但移动工作

  $ git status 
(新内容)到工作树中:

在分支大师
要提交的更改:
(使用git reset HEAD< file> ...停用)

已更名:old-name.txt - > ; new-name.txt

没有为commit提交更改:
(使用git add< file> ...更新将提交的内容)
(使用 git checkout - < file> ...放弃工作目录中的更改)

修改:new-name.txt
git commit
就可以进行重命名,而不是新内容。

(请注意,这取决于没有旧名称的新文件!)






如何使用 git update-index ?首先让我们回到改变工作树,索引匹配HEAD提交状态:

  $ git reset --mixed HEAD#set index = HEAD,离开工作树

现在让我们来看看index old-name.txt

  $ git ls-files --stage  -  old-name.txt 
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0 old-name.txt

因此,我们需要 git update-index --index-info 来消除 old-name.txt 但为 new-name.txt 创建一个相同的条目:

  $(git ls-files --stage  -  old-name.txt; 
git ls-files --stage - old-name.txt)|
sed -e \
'1s / ^ [0-9] * [0-9a-f] * / 000000 0000000000000000000000000000000000000000 /'\
-e'2s / old-name .txt $ / new-name.txt /'|
git update-index --index-info

(注意:作为发布目的,当我输入它时,它全部是一行;在sh / bash中,它应该像这样工作,因为我添加了反斜杠以继续sed命令)。

还有其他一些方法可以做到这一点,但只需简单地提取索引条目两次,并将第一个条目修改为删除条件,第二个条目最好使用新名称,因此 sed 命令。第一个替换改变了文件模式(100644,但任何模式都将变为全零)和SHA-1(与任何SHA-1匹配,替换为git的特殊全零SHA-1),第二个替换将离开模式并当更新索引完成时,索引记录了删除旧路径和添加新路径(与与旧路径相同的模式和SHA-1)。



请注意,如果索引没有为 old- name.txt ,因为文件可能有其他阶段(1到3)。


I have a file that I've renamed and then edited. I would like to tell Git to stage the rename, but not the content modifications. That is, I wish to stage the deletion of the old file name, and the addition of the old file contents with the new file name.

So I have this:

Changes not staged for commit:

        deleted:    old-name.txt

Untracked files:

        new-name.txt

but want either this:

Changes to be committed:

        new file:   new-name.txt
        deleted:    old-name.txt

Changes not staged for commit:

        modified:   new-name.txt

or this:

Changes to be committed:

        renamed:    old-name.txt -> new-name.txt

Changes not staged for commit:

        modified:   new-name.txt

(where the similarity measure should be 100%).

I can't think of a straightforward way to do this.

Is there syntax for getting the contents of a specific revision of a specific file, and adding this to the git staging area under a specified path?

The delete part, with git rm, is fine:

$ git rm old-name.txt

It's the add part of the rename I'm struggling with. (I could save the new contents, checkout a fresh copy (for the old contents), mv in the shell, git add, and then recover the new contents, but that seems like a very long way around!)

Thanks!

解决方案

Git doesn't really do renames. They're all computed in an "after the fact" fashion: git compares one commit with another and, at compare time, decides if there was a rename. This means that whether git considers something "a rename" changes dynamically. I know you're asking about a commit you haven't even made yet, but bear with me, this really all does tie in (but the answer will be long).


When you ask git (via git show or git log -p or git diff HEAD^ HEAD) "what happened in the last commit", it runs a diff of the previous commit (HEAD^ or HEAD~1 or the actual raw SHA-1 for the previous commit—any of these will do to identify it) and the current commit (HEAD). In making that diff it may discover that there used to be an old.txt and isn't any more; and there was no new.txt but there is now.

These file names—files that used to be there but aren't, and files that are there now that weren't—are put into pile marked "candidates for rename". Then, for each name in the pile, git compares "old contents" and "new contents". The comparison for exact match is super-easy because of the way git reduces contents to SHA-1s; if the exact-match fails, git switches to an optional "are the contents at least similar" diff to check for renames. With git diff this optional step is controlled by the -M flag. With other commands it's either set by your git config values, or hardcoded into the command.

Now, back to the staging area and git status: what git stores in the index / staging-area is basically "a prototype for the next commit". When you git add something, git stores the file contents right at that point, computing the SHA-1 in the process and then storing the SHA-1 in the index. When you git rm something, git stores a note in the index saying "this path name is being deliberately removed on the next commit".

The git status command, then, simply does a diff—or really, two diffs: HEAD vs index, for what is going to be committed; and index vs work-tree, for what could be (but isn't yet) going to be committed.

In that first diff, git uses the same mechanism as always to detect renames. If there's a path in the HEAD commit that is gone in the index, and a path in the index that's new and not in the HEAD commit, it's a candidate for rename-detection. The git status command hardwires rename detection to "on" (and the file count limit to 200; with just one candidate for rename detection this limit is plenty).


What does all this mean for your case? Well, you renamed a file (without using git mv, but it doesn't really matter because git status finds the rename, or fails to find it, at git status time), and now have a newer, different version of the new file.

If you git add the new version, that newer version goes into the repo, and its SHA-1 is in the index, and when git status does a diff it will compare the new and old. If they're at least "50% similar" (the hardwired value for git status), git will tell you the file is renamed.

Of course, git add-ing the modified contents is not quite what you asked for: you wanted to do an intermediate commit where the file is only renamed, i.e., a commit with a tree with the new name, but the old contents.

You don't have to do this, because of all of the above dynamic rename detection. If you want to do it (for whatever reason) ... well, git doesn't make it all that easy.

The most straightforward way is just as you suggest: move the modified contents somewhere out of the way, use git checkout -- old-name.txt, then git mv old-name.txt new-name.txt, then commit. The git mv will both rename the file in the index/staging-area, and rename the work-tree version.

If git mv had a --cached option like git rm does, you could just git mv --cached old-name.txt new-name.txt and then git commit. The first step would rename the file in the index, without touching the work-tree. But it doesn't: it insists on overwriting the work-tree version, and it insists that the old name must exist in the work-tree to start.

The single step method for doing this without touching the work-tree is to use git update-index --index-info, but that, too, is somewhat messy (I'll show it in a moment anyway). Fortunately, there's one last thing we can do. I've set up the same situation you had, by renaming the old name to the new one and modifying the file:

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    old-name.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    new-name.txt

What we do now is, first, manually put the file back under its old name, then use git mv to switch again to the new name:

$ mv new-name.txt old-name.txt
$ git mv old-name.txt new-name.txt

This time git mv updates the name in the index, but keeps the original contents as the index SHA-1, yet moves the work-tree version (new contents) into place in the work-tree:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    old-name.txt -> new-name.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   new-name.txt

Now just git commit to make a commit with the rename in place, but not the new contents.

(Note that this depends on there not being a new file with the old name!)


What about using git update-index? Well, first let's get things back to the "changed in work-tree, index matches HEAD commit" state:

$ git reset --mixed HEAD  # set index=HEAD, leave work-tree alone

Now let's see what's in the index for old-name.txt:

$ git ls-files --stage -- old-name.txt
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0   old-name.txt

So, what we need git update-index --index-info to do is to wipe out the entry for old-name.txt but make an otherwise identical entry for new-name.txt:

$ (git ls-files --stage -- old-name.txt;
   git ls-files --stage -- old-name.txt) |
  sed -e \
'1s/^[0-9]* [0-9a-f]*/000000 0000000000000000000000000000000000000000/' \
      -e '2s/old-name.txt$/new-name.txt/' | 
  git update-index --index-info

(note: I broke the above up for posting purposes, it was all one line when I typed it in; in sh/bash, it should work broken-up like this, given the backslashes I added to continue the "sed" command).

There are some other ways to do this, but simply extracting the index entry twice and modifying the first one into a removal and the second with the new name seemed the easiest here, hence the sed command. The first substitution changes the file mode (100644 but any mode would be turned into all-zeros) and SHA-1 (match any SHA-1, replace with git's special all-zeros SHA-1), and the second leaves the mode and SHA-1 alone while replacing the name.

When the update-index finishes, the index has recorded the removal of the old path and the addition of the new path (with same mode and SHA-1 as were in the old path).

Note that this could fail badly if the index had unmerged entries for old-name.txt since there might be other stages (1 to 3) for the file.

这篇关于如何在没有后续编辑的情况下进行重命名?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆