Unix系统是怎么处理文件移动的?

最近在用perforce,它检测文件移动真的是蛋疼,一定要这个软件online的情况下才行。

然而git却并不需要,却能很好的检测文件的转移。

有个比较有趣的情况是,比方说在terminal里,cd到一个路径下,然后手动把这个文件夹移动到其他目录,此时再 cd .. 出去,会发现上级目录已经变化了。感觉OS应该记录下了文件夹的地址的指针,但我具体不清楚是怎么做的。

image

挺好奇unix是怎么处理文件移动的。求教~

linux文件系统,每个文件/文件夹都是一个inode, 文件移动的时候, 文件的inode没有变化,你可以观察到已经打开的文件是可以继续访问的. 文件移走之后,当前文件夹里面的记录会被删除, 目标文件夹会建立一个新的记录指向这个inode, 当整个目录被移走之后,当前目录的inode以及目录中的".."inode记录也被删除了, 所以cd ..用当前inode编号去找有关记录已经找不到了.

请指正.

重新配了下图

打个比方,就像是门牌号码换了,已经在房间里的人不受影响,但是后来的人要通过新的号码才能找到房间。

git 能侦测到 renamed foo.txt -> bar.txt, 我猜是基于 git 已有的数据做出的判断,跟系统无关。并且也不是 100% 准确,有时候就判断不出来,所以还是应该 git mv

这取决于你所使用的 Shell 的实现,可能有的 Shell 不处理,直接报错;而有的 Shell 甚至跟踪文件的去向,比如 Fish Shell

~ $ mkdir hello
~ $ cd hello/
~/hello $ mv ~/hello ~/.emacs.d/hello-emacs
~/.emacs.d/hello-emacs $

要做到这点就需要相应的 API 并且 Shell 使用它。

Emacs 下有 (elisp) File Notifications,如果它能支持监测 rename 的话,比如 Dired 能实现类似的功能。

git 并没有立刻侦测文件移动,而是当作两个文件:

$ mv {foo,bar}.txt
$ git status -s
 D foo.txt                   #  D - Deleted
?? bar.txt                   # ?? - Untracked

add 之后,才显示为 renamed:

$ git add .
$ git status -s
R foo.txt -> bar.txt         #  R - Renamed

我猜 git 是根据文件相似度,来推测删除和新增的两个文件的关系。

以下是完整的测试脚本:

test-git.sh
#!/usr/bin/env bash
#
# Test if the git can detect file movement.
#
# @date 2018-07-05

folder=$(dirname $0)/13BA00E5-F929-4466-9BB4-5E126EC3431D
echo folder: $folder

[[ -e $folder ]] && rm -rf $folder
mkdir $folder && cd $folder

echo $(date) > foo.txt
echo "foo" >> foo.txt
git init
git add .
git commit -m 'Init commit'

mv {foo,bar}.txt
echo "bar" >> bar.txt

printf "\n--- status 1 ---------------------------------------------------------------\n"

git status -s
# =>
# .------------------------------.
# | $ git status -s              |
# |  D foo.txt                   | #  D - Deleted
# | ?? bar.txt                   | # ?? - Untracked
# |                              |
# '------------------------------'

printf "\n--- status 2 ---------------------------------------------------------------\n"

git add .
git status -s
# =>
# .------------------------------.
# | $ git status -s              |
# | R foo.txt -> bar.txt         | #  R - Renamed
# |                              |
# '------------------------------'
1 个赞

我移动文件夹是用了其他软件做的,如果在shell里移动的话,倒是好理解,实现起来也方便,但是用其他软件移动了文件夹,还是能track到这个文件夹,shell里还能用,就感觉有点奇怪了。

如果理解成,每个文件夹有一个unique的id,然后在系统下是有查询表的,所以移动(改名)的时候会更新查询表,所以总是能找到对应的地址的。这只是我的猜测~感觉不太靠谱。

我的猜测是对的,把上边 test-git.sh 脚本稍作修改:

mv {foo,bar}.txt
- echo "bar" >> bar.txt
+ echo "bar" > bar.txt

add 之后,因为前后内容差异太大,判定为两个不同的文件:

$ git add .
$ git status -s
A  bar.txt
D  foo.txt

inode 其实就包含楼主说的 unique id,只不过这个 “id” 是磁盘扇区的地址。git 是不可能记录这个信息的,所以也不能要求 git 通过 “id” 来判断是否进行了 mv 操作。

inode 直接用 ls -i 就能查看。

1 个赞

也可以用 stat 查看。

提供文件系统变化检测的 API 不会管你文件是谁修改的,比如你用 Vim 改了个文件,Emacs 能自动检测到。

有源码 可以看看

https://ftp.gnu.org/gnu/coreutils/coreutils-8.30.tar.xz

对哦,我就是挺好奇这个系统的api是怎么个实现方式~