如何在文件移动时使git标记为已删除和新文件?


503

我手动移动了文件,然后对其进行了修改。根据Git的说法,它是一个新文件,并且已删除文件。有什么方法可以迫使Git将其视为文件移动?


3
对于给定的文件old_file.txt,那么git mv old_file.txt new_file.txt就相当于git rm --cached old_file.txtmv old_file.txt new_file.txtgit add new_file.txt
Jarl 2012年

3
Jarl:不,不是。如果文件内也有更改,git mv则不会将它们添加到缓存中git add。我更喜欢将文件移回以便可以使用git mv,然后git add -p查看我的更改集。
dhardy 2014年



Git在过去8年中有所改进,如果只是一个文件,则最重要的答案stackoverflow.com/a/433142/459没有做任何事情…但是您可以按照stackoverflow.com/a/1541072/459进行更新,以获取rm / add转换为mv / modify状态。
dlamblin

Answers:


432

如果您的修改不太严格,Git将自动检测到移动/重命名。只是git add新文件和git rm旧文件。git status然后将显示它是否检测到重命名。

此外,对于目录移动,您可能需要:

  1. cd到该目录结构的顶部。
  2. git add -A .
  3. 运行git status以验证“新文件”现在是“重命名”文件

如果git status仍然显示“新文件”而不是“重命名”,则需要遵循Hank Gay的建议,并在两次单独的提交中进行移动和修改。


75
澄清:“不太严重”表示基于git使用的一些相似性索引,新文件和旧文件“相似性”> 50%。
pjz 2010年

150
这让我有些烦恼了几分钟:如果未重命名文件和已删除文件来提交,则它们将显示为删除和新文件。将它们添加到暂存索引后,它将识别为重命名。
marczych 2012年

19
值得一提的是,当谈到“ Git将自动检测到移动/重命名 ”时。它这样做是在你使用的时候git statusgit log或者git diff不是在你这样做的时候git addgit mvgit rm。进一步讲,检测重命名仅对暂存文件有意义。因此git mv,在文件中进行更改之后,git status就好像将其视为一样rename,但是当您在文件上使用git stage(与相同git add)时,很明显该更改太大而无法检测为重命名。
Jarl 2012年

5
真正的关键似乎是将新位置和旧位置都添加到同一位置git add,正如@ jrhorn424建议的那样,可能应该一次将整个存储库添加进去。
brianary

4
不过,这并不是万无一失的,尤其是在文件已更改且很小的情况下。有没有一种方法可以专门告诉git有关此举的信息?
Rebs

111

在单独的提交中进行移动和修改。


12
我知道Git是可以同时处理移动和修改的。使用Java进行编程并使用IDE时,重命名类既是修改又是移动。我知道Git甚至应该能够在移动时(从删除和创建中)自动找出来。
pupeno

1
Perl也需要这样做,而我从来没有让Git检测到移动/重命名。
jrockway

10
@jrockway,我有。小文件很容易发生,我想它们“变得太不同了”。
2010年

2
有什么办法可以自动化吗?我索引中有很多文件,想要先提交移动,然后进行更改,但是很难手动进行
ReDetection

5
需要注意的一件事是,如果git diff在一次提交中无法识别重命名,则在两次提交中将无法识别重命名。您将需要使用-Maka --find-renames来做到这一点。因此,如果导致您提出此问题的动机是在请求请求中看到重命名(从经验上来讲),将其分为两个提交将不会进一步实现该目标。
mattliu

44

都是感性的东西。Git通常很擅长识别动作,因为GIT内容跟踪器

真正取决于您的“状态”如何显示它。唯一的区别是-M标志。

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log --stat

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git帮助日志

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.

76
抱歉,这似乎有点书呆子,但是“ Git通常很擅长识别动作,因为GIT是内容跟踪器”对我来说似乎很不讲道。是的,它是一个内容跟踪器,也许它恰好可以检测移动,但是一个声明并没有真正跟随另一个声明。仅仅因为它是内容跟踪器,所以移动检测并不一定是件好事。实际上,内容跟踪器可能根本没有移动检测。
劳伦斯·贡萨尔维斯

1
@WarrenSeine重命名目录时,该目录中文件的SHA1不会更改。您所拥有的只是一个具有相同SHA1的新TREE对象。没有理由重命名目录会导致重大的数据更改。
肯特·弗雷德里克

1
@KentFredric您展示了将-M与“ git log”一起使用,我们可以检查文件是否已重命名,并且我尝试了该文件及其工作正常,但是当我发布代码进行审查并在gerrit中查看该文件时(gerritcodereview.com)那里显示文件是新添加的,以前的已删除。所以在“ git commit”中有一个选项,我可以使用它进行提交,而gerrit可以正确显示它。
帕特里克

1
不,我一点都没有显示。我显示Git能够假装添加+删除是重命名,因为内容相同。它无法知道发生了什么,也无关紧要。git记住的只是“添加”和“删除”。从未记录“重命名”。
肯特·弗雷德里克

1
例如,最近我在仓库中做了很多“复制+编辑”操作。查看它的大多数方式仅显示“新文件”,但是如果您通过git log -M1 -C1 -B1 -D --find-copies-hardergit可以“发现”新文件可能已被首先复制。有时,它会执行正确的操作,而有时,它会发现恰好具有相同内容的完全不相关的文件。
肯特·弗雷德里克

36

git diff -Mgit log -M应自动将此类更改检测为重命名,只要确实存在,即可进行较小的更改。如果您的次要更改不是次要更改,则可以降低相似性阈值,例如

$ git log -M20 -p --stat

将其从默认的50%减少到20%。


13
是否可以在配置中定义阈值?
ReDetection '16

34

对于一个或几个未提交的重命名和修改的文件,这是一个快速而肮脏的解决方案。

假设文件已命名foo,现在命名为bar

  1. 重命名bar为临时名称:

    mv bar side
    
  2. 结帐foo

    git checkout HEAD foo
    
  3. 用Git 重命名foobar

    git mv foo bar
    
  4. 现在,将您的临时文件重命名为bar

    mv side bar
    

最后一步是将更改后的内容重新存储到文件中的步骤。

尽管这可以解决问题,但是如果所移动的文件与原始git的内容差异太大,则将其确定为新对象会更有效。让我示范一下:

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

    new file:   .gitignore
    renamed:    README -> README.md

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:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

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:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README.md
    modified:   work.js

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

    Rmd

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

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   work.js

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

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.

27
此过程没有任何目的。git不会为重命名添加任何元数据,git mv只是对git rm/ 的方便git add。如果你已经做了“MV酒吧富”,那么所有你需要做的是确保你已经git add foogit rm bar在提交前。这可以作为单个git add -A命令完成,也可以按git add foo; git commit -a顺序完成。
CB Bailey

17
我所知道的是,在我这样做之前,Git并不认为这是一个举措。我这样做之后,Git意识到这是一个举措。

8
它认为这是一个举措。但是,它现在也具有未分级的变化。一旦你add再次文件,git的休息移动/修改成删除/再次添加。
迈克尔·皮费尔

4
如果您git commit在执行第3步之后,此过程将起作用,否则不正确。另外,@ CharlesBailey是正确的,您可以轻松地mv blah foo在第3步中执行常规操作,然后提交,并获得相同的结果。
DaFlame 2014年

5
“这个过程没有任何目的。” -当git感到困惑并认为已删除文件,并且已添加另一个文件时,它可以达到目的。这消除了Git的混乱,并将文件标记为显式移动,而无需进行中间提交。
Danack 2015年

20

如果你谈论的是git status没有显示的重命名,尝试git commit --dry-run -a代替


11

如果您使用的是TortoiseGit,请务必注意,Git的自动重命名检测是在提交过程中发生的,但是这种情况不会始终由软件预先显示。我已经将两个文件移到了另一个目录,并进行了一些轻微的编辑。我使用TortoiseGit作为提交工具,“所做的更改”列表显示正在删除和添加而不是移动的文件。从命令行运行git status显示了类似的情况。但是,提交文件后,它们在日志中显示为已重命名。因此,您的问题的答案是,只要您还没有做任何太过激烈的事情,Git就会自动选择重命名。

编辑:显然,如果您添加新文件,然后从命令行执行git status,则在提交之前应显示重命名。

编辑2:此外,在TortoiseGit中,在提交对话框中添加新文件,但不要提交它们。然后,如果进入Show Log命令并查看工作目录,则会看到Git在提交之前是否已检测到重命名。

此处提出了相同的问题:https : //tortoisegit.org/issue/1389并已记录为要在此处修复的错误:https : //tortoisegit.org/issue/1440 原来这是TortoiseGit提交的显示问题对话框,如果您尚未添加新文件,则还处于git状态。


2
您是对的,即使TortoiseGit显示删除+添加,即使git status显示删除+添加,即使git commit --dry-run显示删除+添加,在git commit之后我也看到重命名而不是删除+添加。
Tomas Kubes,2014年

1
我很确定在历史记录检索过程中会发生自动重命名检测;在提交中,它始终是add + delete。这也解释了您描述的精神分裂症行为。因此,这里的选项如下:stackoverflow.com/a/434078/155892
Mark Sowul

10

或者您可以在这里尝试Amber回答这个问题的方法!再次引用:

首先,为手动移动的文件取消分阶段添加:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

然后,使用Git移动文件:

$ git mv path/to/oldfile path/to/newfile

当然,如果您已经进行了手动移动,则可能需要在移动之前重置为修订版,然后从那里简单地git mv即可。



3

最近,在移动(但不修改)某些文件时遇到了这个问题。

问题在于,当我移动文件时,Git更改了某些行尾,然后无法分辨出文件是否相同。

使用git mv解决了问题,但是它仅适用于单个文件/目录,并且我在存储库的根目录中有很多文件要做。

解决此问题的一种方法是使用一些bash /批处理魔术。

另一种方法是以下

  • 移动文件和git commit。这将更新行尾。
  • 现在,有了新的行尾,将文件移回其原始位置,并且 git commit --amend
  • 再次移动文件和git commit --amend。这次行尾没有变化,所以Git很高兴

1

对我来说,它可以将所有更改保存在提交之前,然后再次弹出。这使得git重新分析添加/删除的文件,并将其正确标记为已移动。


0

有一种更好的“命令行”方式可以做到这一点,我知道这是一个hack,但是我从来没有找到一个好的解决方案。

使用TortoiseGIT:如果您有一个GIT提交,其中一些文件移动操作显示为添加/删除而不是重命名的负载,即使文件只有很小的变化,也可以这样做:

  1. 检查您在本地所做的事情
  2. 在第二次提交中签入迷你单行更改
  3. 转到GIT登录git乌龟
  4. 选择两个提交,右键单击,然后选择“合并为一个提交”

现在,新提交将正确显示文件重命名……这将有助于维护正确的文件历史记录。


0

当我同时编辑,重命名和移动文件时,这些解决方案均无效。解决方案是在两次提交中进行此操作(分别进行编辑和重命名/移动),然后fixup在第二次提交中进行git rebase -i一次提交。


0

我理解这个问题的方式是“如何使git识别旧文件的删除并在文件移动时创建新文件”。

删除旧文件并插入旧文件后,在工作目录中是,git status将显示“ deleted: old_file”和“ Untracked files: ... new_file

但是在暂存索引/级别中,一旦您使用git添加和删除文件,它将被识别为文件移动。为此,假设您已经使用操作系统完成了删除和创建,请提供以下命令:

git add new_file
git rm old_file

如果文件的内容相似度超过50%,则运行git status命令应为您提供:

renamed: old_file -> new_file

1
git status会说“ deleted: old_file”和“ Untracked files: ... new_file”:从Git 2.18开始不存在:stackoverflow.com/a/50573107/6309
VonC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.