【问题标题】:How to make git mark a deleted and a new file as a file move?如何使 git 将已删除文件和新文件标记为文件移动?
【发布时间】:2010-09-30 19:01:14
【问题描述】:

我手动移动了一个文件,然后我修改了它。根据 Git,它是一个新文件和一个已删除文件。有什么方法可以强制 Git 将其视为文件移动?

【问题讨论】:

标签: git


【解决方案1】:

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

【讨论】:

  • 我知道 Git 可以同时处理移动和修改。在使用 Java 编程和使用 IDE 时,重命名类既是修改也是移动。我知道 Git 甚至应该能够在移动时自动计算出来(从删除和创建中)。
  • @jrockway,我有。小文件很容易发生,我猜它们“变得‘太不一样’而不意味着移动”。
  • 有什么办法可以自动完成这项工作吗?我在索引中有很多文件,想先提交移动,然后再进行更改,但是很难手动完成
  • 需要注意的一点是,如果git diff 在一次提交中无法识别重命名,则它不会在两次提交中识别它。你需要使用-M aka --find-renames 来做到这一点。因此,如果导致您提出这个问题的动机是在拉取请求中查看重命名(根据经验),那么将其拆分为两个提交不会使您进一步实现该目标。
  • 这似乎是一个低知识的答案。像 GIT 这样的高级版本控制系统应该有一种方法,可以在一次提交中将添加的文件设置为已删除文件的重命名。
【解决方案2】:

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

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

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

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

【讨论】:

  • 澄清:“不太严重”是指根据git使用的一些相似性索引,新文件和旧文件>50%“相似”。
  • 让我愣了几分钟的事情:如果重命名的文件和已删除的文件没有准备提交,那么它们将显示为删除和新文件。将它们添加到暂存索引后,它会将其识别为重命名。
  • 值得一提的是,说到“Git 会自动检测移动/重命名”。它在您使用 git statusgit loggit diff 时这样做,而在您使用 git addgit mvgit rm 时,不是。进一步谈到检测重命名,这只对暂存文件有意义。因此,git mv 后跟文件中的更改可能会在 git status 中看起来好像它认为它是 rename,但是当您在文件上使用 git stage(与 git add 相同)时,它变得清晰更改太大而无法检测为重命名。
  • 虽然这不是绝对可靠的,尤其是在文件更改且很小的情况下。有没有办法专门告诉 git 搬家?
  • @YarekT 这不是纯粹的装饰功能,因为如果 git on commit 检测到重命名,那么查看该文件的历史记录将返回旧文件名的历史记录。对我来说,重命名与删除+添加的重点。 (在 git 2.20 上测试)
【解决方案3】:

这都是感性的事情。 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.

【讨论】:

  • 对不起,如果这看起来有点迂腐,但是“Git 通常很擅长识别动作,因为 GIT 是一个内容跟踪器”对我来说似乎是不合逻辑的。它是一个内容跟踪器,是的,也许它恰好擅长检测动作,但一个声明并没有真正遵循另一个声明。仅仅因为它是一个内容跟踪器,移动检测不一定是好的。事实上,内容跟踪器可能根本没有移动检测。
  • @WarrenSeine 重命名目录时,该目录中文件的 SHA1 不会更改。您所拥有的只是一个具有相同 SHA1 的新树对象。目录重命名不会导致重大数据更改。
  • @KentFredric 您展示了将 -M 与“git log”一起使用,我们可以检查文件是否被重命名,我尝试过它并且工作正常,但是当我发布我的代码以供审查并看到gerrit (gerritcodereview.com) 中的文件,它显示该文件是新添加的,而以前的文件已被删除。那么“git commit”中是否有一个选项,我使用它来提交并且gerrit正确显示它。
  • 没有。我根本没有表现出来。我展示了 Git 能够 假装 由于内容相同,添加 + 删除是重命名。它无法知道发生了什么,它也不在乎。 git 记住的所有内容都是“添加”和“删除”。 “重命名”从未被记录。
  • 例如,我最近在一个 repo 上做了很多“复制 + 编辑”。大多数查看它的方式只能看到“新文件”,但如果你通过git log -M1 -C1 -B1 -D --find-copies-harder,git 可以“发现”新文件可能已被首先复制。有时它会正确执行此操作,有时它会找到完全不相关但内容相同的文件。
【解决方案4】:

git diff -Mgit log -M 应该自动检测此类更改,例如带有微小更改的重命名,只要它们确实如此。 如果您的微小变化不是微小的,您可以降低相似度阈值,例如

$ git log -M20 -p --stat

将其从默认的 50% 降低到 20%。

【讨论】:

  • 是否可以在配置中定义阈值?
  • 它与 git log 本身提供的“交互窗口”相同,它允许在终端滚动并使用q 退出。别担心,在 git log 视图中自己搞砸是很困难的。
【解决方案5】:

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

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

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

    mv bar side
    
  2. 结帐foo:

    git checkout HEAD foo
    
  3. 使用 Git 将 foo 重命名为 bar

    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.

【讨论】:

  • 这个过程没有任何意义。 git 不会为重命名添加任何元数据,git mv 只是为了方便 git rm/git add 对。如果你已经完成了'mv bar foo',那么你所要做的就是确保在提交之前你已经git add foogit rm bar。这可以像单个 git add -A 命令一样完成,也可以是 git add foo; git commit -a 序列。
  • 我所知道的是,在我这样做之前,Git 并没有将其识别为移动。在我这样做之后,Git 将其识别为一个动作。
  • 它将其识别为移动。但它现在也有作为非阶段性变化的变化。一旦您再次add 文件,git 会将移动/修改再次分解为已删除/添加。
  • 如果您在第 3 步之后git commit,则此过程将起作用,否则不正确。此外,@CharlesBailey 是正确的,您可以轻松地在第 3 步执行普通的mv blah foo,然后提交并获得相同的结果。
  • “这个过程没有任何意义。” - 当 git 感到困惑并认为一个文件已被删除并添加了另一个文件时,它会起作用。这消除了 Git 的困惑,并将文件标记为显式移动,而无需进行中间提交。
【解决方案6】:

如果您使用的是 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 状态中t 添加了新文件。

【讨论】:

  • 你是对的,即使 TortoiseGit 显示 delete + add,即使 git status 显示 delete + add 即使 git commit --dry-run 显示 delete + add,在 git commit 之后我看到 rename 而不是删除+添加。
  • 我很确定自动重命名检测发生在历史检索期间;在提交中它总是添加+删除。这也解释了你描述的精神分裂症行为。因此这里的选项:stackoverflow.com/a/434078/155892
【解决方案7】:

可能有一个更好的“命令行”方式来执行此操作,我知道这是一个 hack,但我一直无法找到一个好的解决方案。

使用 TortoiseGIT:如果您有一个 GIT 提交,其中一些文件移动操作显示为添加/删除而不是重命名的负载,即使文件只有很小的更改,那么请执行以下操作:

  1. 检查您在本地所做的工作
  2. 在第二次提交中签入一个小型的单行更改
  3. 龟甲git进入git登录
  4. 选中两个commit,右键,选择“merge into one commit”

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

【讨论】:

    【解决方案8】:

    如果您说 git status 没有显示重命名,请尝试使用 git commit --dry-run -a 代替

    【讨论】:

      【解决方案9】:

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

      【讨论】:

        【解决方案10】:

        使用git mv 命令移动文件,而不是操作系统移动命令: https://git-scm.com/docs/git-mv

        请注意,git mv 命令仅存在于 Git 1.8.5 及更高版本中。所以你可能需要更新你的 Git 才能使用这个命令。

        【讨论】:

          【解决方案11】:

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

          问题是当我移动文件时 Git 更改了一些行尾,然后无法判断文件是否相同。

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

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

          另一种方式如下

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

          【讨论】:

            【解决方案12】:

            当我同时编辑、重命名和移动文件时,这些解决方案都不起作用。解决方案是在两次提交中进行(编辑和重命名/移动单独),然后通过git rebase -ifixup 第二次提交以在一次提交中进行。

            【讨论】:

            • -1:这样做的结果与在同一个提交中移动和修改文件相同——只是需要额外的步骤。它不起作用。
            【解决方案13】:

            或者你可以试试这个问题的答案hereAmber! 再次引用它:

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

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

            然后,使用 Git 移动文件:

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

            当然,如果您已经提交了手动移动,您可能希望重置为移动之前的修订,然后从那里简单地 git mv。

            【讨论】:

            • 请注意,如果您也有大量更改(通常发生在小文件上),这将不起作用。它被提交为“删除”和“创建”。在这种情况下,我想唯一的选择是按照另一个答案的建议“在单独的提交中进行移动和修改”
            【解决方案14】:

            我理解这个问题的方式是“如何让 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
            

            【讨论】:

            【解决方案15】:

            其他答案已经涵盖了您可以简单地git add NEW &amp;&amp; git rm OLD 以使 git 识别移动。

            但是,如果你已经修改了工作目录中的文件,那么 add+rm 方法会将修改添加到索引中,这在某些情况下可能是不受欢迎的(例如,在大量修改的情况下,git 可能不再识别这是一个文件重命名)。

            假设您想将 rename 添加到索引中,但不进行任何修改。实现这一点的明显方法是来回重命名mv NEW OLD &amp;&amp; git mv OLD NEW

            但也有一种(稍微复杂一点)的方法可以直接在索引中执行此操作,而无需在工作树中重命名文件:

            info=$(git ls-files -s -- "OLD" | cut -d' ' -f-2 | tr ' ' ,)
            git update-index --add --cacheinfo "$info,NEW" &&
            git rm --cached "$old"
            

            这也可以作为~/.gitconfig 中的别名:

            [alias]
                mv-index = "!f() { \
                  old=\"$1\"; \
                  new=\"$2\"; \
                  info=$(git ls-files -s -- \"$old\" | cut -d' ' -f-2 | tr ' ' ,); \
                  git update-index --add --cacheinfo \"$info,$new\" && \
                  git rm --cached \"$old\"; \
                }; f"
            

            【讨论】:

              猜你喜欢
              • 2011-10-17
              • 1970-01-01
              • 2014-09-06
              • 2023-04-11
              • 1970-01-01
              • 1970-01-01
              • 2020-03-16
              • 2013-07-18
              相关资源
              最近更新 更多