477

我读到在 Git 中重命名文件时,您应该提交任何更改,执行重命名,然后暂存重命名的文件。Git 将从内容中识别文件,而不是将其视为新的未跟踪文件,并保留更改历史记录。

但是,今晚就这样做,我最终恢复到git mv.

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

我将Finder中的样式表从重命名iphone.cssmobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#    modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#    deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#    css/mobile.css

所以 Git 现在认为我删除了一个 CSS 文件,并添加了一个新文件。这不是我想要的。让我们撤消重命名,让 Git 完成这项工作。

> $ git reset HEAD .
Unstaged changes after reset:
M    css/iphone.css
M    index.html

我回到我开始的地方:

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

让我们git mv改用:

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#    renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#    modified:   index.html
#

看起来我们很好。那么为什么当我第一次使用 Finder 时 Git 没有识别出重命名呢?

4

14 回答 14

370

对于git mv 手册页

索引在成功完成后更新,[…]

因此,首先,您必须自己更新索引(使用git add mobile.css)。但是git status 仍然会显示两个不同的文件:

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

您可以通过运行获得不同的输出git commit --dry-run -a,这会产生您所期望的结果:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

git status我不能确切地告诉你为什么我们会看到和之间的这些差异git commit --dry-run -a,但这里有一个来自 Linus 的提示

git 实际上甚至不关心内部的整个“重命名检测”,并且您对重命名所做的任何提交都完全独立于我们随后用来显示重命名的启发式方法。

Adry-run使用真正的重命名机制,而 a git status可能没有。

于 2010-04-14T21:35:39.857 回答
88

在 Git 将其识别为移动之前,您必须将这两个修改的文件添加到索引中。

mv old new和之间的唯一区别git mv old new是它git mv还将文件添加到索引中。

mv old new那么git add -A也会起作用。

请注意,您不能只使用git add .,因为这不会将删除添加到索引中。

请参阅“git add -A”和“git add”之间的区别。

于 2011-10-21T07:18:07.280 回答
34

对于 Git 1.7.x,以下命令对我有用:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.'

不需要git add,因为原始文件(即css/mobile.css)之前已经在提交的文件中。

于 2015-07-08T22:36:17.303 回答
19

最好的办法是自己尝试一下。

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

现在显示两个不同的结果,其中git status显示bbb.txt作为新文件/ aaa.txt被删除,命令显示实际重命名。git commit --dry-run -agit status--dry-run

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# 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:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

现在继续办理登机手续。

git commit -a -m "Rename"

现在您可以看到该文件实际上已重命名,并且显示的git status内容是错误的,至少在这种情况下是这样。下划线 GIT 实现可能会分别处理这两个命令。

故事的寓意:如果您不确定您的文件是否被重命名,请发出“git commit --dry-run -a”。如果显示文件已重命名,那么您就可以开始了。

于 2014-04-01T06:07:40.763 回答
13

步骤 1:将文件从 oldfile 重命名为 newfile

git mv #oldfile #newfile

第二步:git commit添加评论

git commit -m "rename oldfile to newfile"

第 3 步:将此更改推送到远程服务器

git push origin #localbranch:#remotebranch
于 2014-05-27T18:29:47.850 回答
10

你必须git add css/mobile.css得到新文件 and git rm css/iphone.css,所以 Git 知道它。然后它将在 中显示相同的输出git status

您可以在状态输出中清楚地看到它(文件的新名称):

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

和(旧名称):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

我认为幕后git mv只不过是一个包装脚本,它正是这样做的:从索引中删除文件并以不同的名称添加它

于 2010-04-14T21:34:55.613 回答
9

让我们从 Git 的角度考虑您的文件。

请记住,Git 不会跟踪有关文件的任何元数据

您的存储库(除其他外)

$ cd repo
$ ls
...
iphone.css
...

它在 Git 控制之下:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

使用以下方法进行测试:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

当你这样做

$ mv iphone.css mobile.css

从 Git 的角度来看,

  • 没有任何iphone.css(它已被删除-git 对此发出警告-)。
  • 有一个新文件mobile.css
  • 这些文件完全不相关。

因此,Git 会建议它已经知道的文件(iphone.css)和它检测到的新文件(mobile.css),但只有当文件在索引或 HEAD 中时,Git 才会开始检查它们的内容。

此时,“iphone.css 删除”和mobile.css均不在索引中。

将 iphone.css 删除添加到索引中:

$ git rm iphone.css

Git 会告诉你到底发生了什么(iphone.css已被删除。没有更多的事情发生):

然后添加新文件mobile.css

$ git add mobile.css

这次删除和新文件都在索引中。现在 Git 检测到上下文相同并将其公开为重命名。事实上,如果文件有 50% 相似,它会检测为重命名,让您可以稍微更改mobile.css,同时将操作保持为重命名。

看到这是可重现的git diff。现在您的文件在您必须使用的索引中--cached。稍微编辑一下mobile.css,将其添加到索引中,看看两者之间的区别:

$ git diff --cached

$ git diff --cached -M

-M是“检测重命名”选项git diff-M代表-M50%(50% 或更多的相似性将使 Git 将其表示为重命名),但如果您经常编辑文件mobile.css-M20% ,则可以将其减少到(20%)。

于 2015-02-10T21:16:09.233 回答
8

Git 将从内容中识别文件,而不是将其视为新的未跟踪文件

那就是你出错的地方。

只有您添加文件之后,Git 才会从内容中识别它。

于 2010-04-15T00:29:11.963 回答
3

您没有展示 Finder 移动的结果。我相信如果您通过 Finder 进行移动,然后git add css/mobile.css ; git rm css/iphone.css执行,Git 将计算新文件的哈希值,然后才意识到文件的哈希值匹配(因此它是重命名)。

于 2010-04-14T21:37:13.647 回答
2

在您确实必须手动重命名文件的情况下,例如,使用脚本批量重命名一堆文件,然后使用git add -A .为我工作。

于 2012-05-02T11:31:29.410 回答
2

对于 Xcode 用户:如果您在 Xcode 中重命名文件,您会看到徽章图标更改为附加。如果您使用 Xcode 进行提交,您实际上会创建一个新文件并丢失历史记录。

解决方法很简单,但您必须在使用 Xcode 提交之前执行此操作:

  1. 对您的文件夹执行 Git状态。您应该看到分阶段的更改是正确的:

    renamed:    Project/OldName.h -> Project/NewName.h
    renamed:    Project/OldName.m -> Project/NewName.m
    
  2. commit -m 'name change'

    然后返回 Xcode,您将看到徽章从 A 更改为 M,并且它已保存以提交将来在使用 Xcode 时所做的更改。

于 2012-08-17T07:47:27.387 回答
1

刚刚遇到这个问题 - 如果您更新了一堆文件并且不想git mv全部执行,这也可以:

  1. 将父目录从 重命名/dir/RenamedFile.js/whatever/RenamedFile.js
  2. git add -A进行这种改变
  3. 将父目录重命名为/dir/RenamedFile.js.
  4. git add -A再次,将重新进行与该更改的对比,迫使 git 获取文件名更改。
于 2020-12-14T19:58:14.227 回答
1

在 Window 10上的git 2.33上测试

  1. 在资源管理器中重命名您需要的文件夹和文件。
  2. git add .
  3. git commit -m "commit message"
  4. git push

Git 将检测重命名。您可以通过运行检查git status(提交后)

在此处输入图像描述

于 2021-11-19T16:02:56.073 回答
0

当我大写我的文件夹和文件时,我使用Node.js 的文件系统来解决这个问题。

不要忘记在开始之前进行提交。我不确定它有多稳定:)

  1. 在项目根目录中创建脚本文件。
// File rename.js

const fs = require('fs').promises;
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const args = process.argv.slice(2);
const path = args[0];

const isCapitalized = (s) => s.charAt(0) === s.charAt(0).toUpperCase();

const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);

async function rename(p) {
  const files = await fs.readdir(p, { withFileTypes: true });
  files.forEach(async (file) => {
    const { name } = file;
    const newName = capitalize(name);
    const oldPath = `${p}/${name}`;
    const dumbPath = `${p}/dumb`;
    const newPath = `${p}/${newName}`;
    if (!isCapitalized(name) && name !== 'i18n' && name !== 'README.md') {
      // 'git mv' won't work if we only changed size of letters.
      // That's why we need to rename to dumb name first, then back to current.
      await exec(`git mv ${oldPath} ${dumbPath}`);
      await exec(`git mv ${dumbPath} ${newPath}`);
    }
    if (file.isDirectory()) {
      rename(newPath);
    }
  });
}

rename(path);

重命名目录(或单个文件)脚本中的所有文件更简单:

// rename.js

const fs = require('fs').promises;
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const args = process.argv.slice(2);
const path = args[0];

async function rename(p) {
  const files = await fs.readdir(p, { withFileTypes: true });
  files.forEach(async (file) => {
    const { name } = file;
    const currentPath = `${p}/${name}`;
    const dumbPapth = `${p}/dumb`;
    await exec(`git mv ${currentPath} ${dumbPath}`);
    await exec(`git mv ${dumbPath} ${currentPath}`);
    if (file.isDirectory()) {
      rename(newPath);
    }
  });
}

rename(path);
  1. 使用参数运行脚本
node rename.js ./frontend/apollo/queries

如何使用 Node.js 运行 shell 脚本文件或命令

于 2020-02-27T08:09:13.343 回答