在Git中处理文件重命名

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

然而,今晚这样做,我最终恢复到git mv

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

我在中将样式表从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
#

看起来我们没事了。那么,为什么Git在我第一次使用Finder时没有识别出这个重命名呢?

448492 次浏览

你必须git add css/mobile.css新文件和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只不过是一个包装器脚本,它确实做到了:从索引中删除文件,并以不同的名称添加它

对于git mv 手册页

索引成功完成后更新, […]< / p >

所以,首先,你必须自己更新索引 (通过使用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得到不同的输出,它的结果是你 期望:< / p >

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 statusgit commit --dry-run -a之间,但是 这里是莱纳斯的暗示:

git实际上甚至没有护理关于整体 “重命名detection"内部,以及您拥有的任何提交 所做的重命名完全独立于 然后我们使用启发式方法显示重命名

A dry-run使用真正的重命名机制,而A

. git status可能没有

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

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

这就是你出错的地方。

你只添加文件,Git会从内容中识别它。

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

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

mv old new那么git add -A也可以工作。

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

看到 git add -A"和" git添加。"

在确实需要手动重命名文件的情况下,例如,使用脚本批量重命名一堆文件,然后使用git add -A .对我有用。

对于Xcode用户:如果你在Xcode中重命名你的文件,你会看到徽章图标更改为追加。如果你用Xcode提交,你会创建一个新文件,丢失历史记录。

解决方法很简单,但你必须在使用Xcode提交之前完成:

  1. 在你的文件夹上执行Git 状态。你应该看到分阶段的变化是正确的:

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

    然后回到Xcode,你会看到徽章从A变成M,它被保存以提交将来使用Xcode的更改。

最好的办法是自己去试试。

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 statusgit commit --dry-run -a显示了两种不同的结果,其中git status显示了bbb.txt作为一个新文件/ aaa.txt被删除,而--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"”如果它显示文件已重命名,那么就可以开始了。

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

git mv #oldfile #newfile

步骤2:git commit并添加注释

git commit -m "rename oldfile to newfile"

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

git push origin #localbranch:#remotebranch

让我们从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)提出建议,但只有当文件位于index或HEAD中时,Git才会开始检查它们的内容。

此时此刻,“iphone.css deletion"也没有mobile.css在索引中。

将iphone.css delete添加到索引中:

$ git rm iphone.css

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

然后添加新文件mobile.css:

$ git add mobile.css

这次,删除文件和新建文件都在索引中。现在Git检测到上下文是相同的,并将其公开为重命名。事实上,如果文件有50%的相似度,它会检测到这是一个重命名,允许你稍微改变mobile.css,同时保持操作的重命名。

这在git diff上是可复制的。现在你的文件在索引中,你必须使用--cached。编辑mobile.css一点,将其添加到index,并查看以下两者的区别:

$ git diff --cached

而且

$ git diff --cached -M

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

对于Git 1.7。下面的命令对我有用:

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

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

当我把我的文件夹大写时;我使用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脚本文件或命令

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

  1. 将父目录从/dir/RenamedFile.js重命名为/whatever/RenamedFile.js
  2. git add -A阶段的变化
  3. 将父目录重命名为/dir/RenamedFile.js
  4. git add -A,将重新stage vs该更改,迫使git接受文件名更改。

测试在git 2.33在windows 10上

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

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

enter image description here