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

我手动移动了一个文件,然后修改了它。根据Git的说法,这是一个新文件和一个删除的文件。有没有办法强制Git将其视为文件移动?

225635 次浏览

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

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

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

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

如果git状态仍然显示“新文件”而不是“重命名”,您需要遵循汉克·盖伊的建议并在两个单独的提交中进行移动和修改。

这都是感性的东西。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 diff -Mgit log -M应该自动检测重命名与微小的变化的此类更改,只要它们确实是。 如果你的微小变化不是次要的,你可以减少相似性阈值,例如

$ git log -M20 -p --stat

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

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

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

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

    mv bar side
    
  2. Checkout foo:

    git checkout HEAD foo
    
  3. Rename foo to bar with Git:

    git mv foo bar
    
  4. Now rename your temporary file back to bar.

    mv side bar
    

This last step is what gets your changed content back into the file.

While this can work, if the moved file is too different in content from the original git will consider it more efficient to decide this is a new object. Let me demonstrate:

$ 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.

如果您使用的是TortoiseGit,请务必注意Git的自动重命名检测是在提交期间发生的,但软件并不总是事先显示这一事实。我将两个文件移动到不同的目录并进行了一些轻微的编辑。我使用TortoiseGit作为我的提交工具,更改列表显示文件被删除和添加,而不是移动。从命令行运行git状态显示类似的情况。然而,提交文件后,它们在日志中显示为正在重命名。所以你问题的答案是,只要你没有做任何过于激烈的事情,Git应该会自动获取重命名。

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

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

在这里提出了同样的问题:https://tortoisegit.org/issue/1389,并已记录为bug在这里修复:https://tortoisegit.org/issue/1440事实证明,这是一个显示问题与TortoiseGit的提交对话框,也有点存在于git状态,如果你没有添加新文件。

可能有一个更好的“命令行”方法来做到这一点,我知道这是一个黑客,但我从来没有能够找到一个好的解决方案。

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

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

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

如果您正在谈论git status不显示重命名,请尝试git commit --dry-run -a

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

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

请注意,git mv命令仅存在于Git版本1.8.5及更高版本中。因此,您可能必须更新您的Git才能使用此命令。

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

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

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

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

另一种方法如下

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

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

或者你可以试试这个问题的答案这里琥珀! 再次引用:

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

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

然后,使用Git移动文件:

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

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

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

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

但是,如果您已经修改了工作目录中的文件,add+rm方法会将修改添加到索引中,这在某些情况下可能是不希望的(例如,在实质性修改的情况下,Git可能不再识别它是文件重命名)。

假设您想将重命名添加到索引中,但不进行任何修改。实现这一目标的明显方法是来回重命名mv NEW OLD && 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"