在Git中处理文件重命名

514

我曾经读到,在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.css重命名为mobile.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第一次没有识别到这个重命名呢?


33
Git追踪内容而非文件,所以无论您如何将索引置于适当状态——使用add+rm或者mv——Git都会生成相同的结果。然后,Git利用其重命名/复制检测功能来让您知道这是一次重命名操作。您引用的源的说法也不准确。无论您是否在同一提交中进行修改和重命名,都没有关系。当您跨越修改和重命名执行diff操作时,重命名检测会将其视为重命名+修改操作,如果修改是完全重新编写,则会显示为添加和删除操作——无论您如何执行都没有关系。 - Cascabel
7
如果这是真的,为什么我用Finder重命名后它没有被检测出来? - Greg K
30
git mv old new 命令会自动更新索引。当你在 Git 之外重命名文件时,你需要执行 git add newgit rm old 命令来将更改添加到索引中。一旦完成这些步骤后,运行 git status 命令就可以按照你的预期工作了。 - Chris Johnsen
4
我刚把一堆跟踪在 Git 中的文件移动到了 public_html 目录。进行了 git add .git commit 操作后,在 git status 中仍然显示了一堆“deleted”文件。我执行了 git commit -a,删除操作被提交了,但现在我找不到那些位于 public_html 中的文件的历史记录了。这个工作流程并不如我所愿的那么顺畅。 - Greg K
简单来说:Git只在暂存区中显示重命名。在你将文件添加到暂存区之前,它会显示为已删除的文件和未跟踪的文件。使用git mv命令可以立即将重命名添加到暂存区,而手动重命名则需要你自己将重命名添加到暂存区,然后才能在git状态中看到它被标记为"重命名"。回答关于“为什么使用Finder重命名时它没有被检测到”的问题,是因为你还没有将它添加到暂存区。一旦你将删除和未跟踪的文件都添加到暂存区,它就会被检测到。(此外,文件内容变化的百分比超过阈值后,它将不再被视为重命名。) - undefined
14个回答

2

刚刚遇到了这个问题 - 如果您更新了一堆文件并且不想对所有文件都进行 git mv,那么也可以使用以下方法:

  1. 将父目录从 /dir/RenamedFile.js 重命名为 /whatever/RenamedFile.js
  2. git add -A 将此更改提交到暂存区
  3. 将父目录重新命名为 /dir/RenamedFile.js
  4. 再次运行 git add -A,它会重新提交这个更改,强制 git 捕捉到文件名的更改。

2
对于Xcode用户:如果你在Xcode中重命名文件,你会看到徽章图标变成追加。如果你使用Xcode提交,你实际上会创建一个新文件并且丢失历史记录。
解决方法很简单,但是你必须在使用Xcode提交之前进行操作:
  1. Do a Git status on your folder. You should see that the staged changes are correct:

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

    Then go back to Xcode and you will see the badge changed from A to M and it is saved to commit future changes in using Xcode now.


1

在Windows 10上测试通过git 2.33

  1. 在Windows资源管理器中重命名所需的文件夹和文件。
  2. 运行git add .
  3. 运行git commit -m "提交信息"
  4. 运行git push

Git应该能检测到重命名操作。您可以通过运行git status来检查(在提交之后)

enter image description here


1
遗憾的是,这对OSX不起作用。它认为旧文件已被删除,新文件已添加。供参考。 - StainlessSteelRat

0

当我将我的文件夹和文件大写时,我使用Node.js' file-system来解决这个问题。

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

  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脚本文件或命令


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接