為什麼剛克隆的 Linux 核心程式碼倉庫中部分檔案丟失?你肯定也會遇到!!!

小猿來也發表於2021-08-12

一個肯定能讓你節省幾個小時的小知識

大家好,我是 小猿來也,一個人稱擼(劃)碼(水)小能手的程式猿。

最近一段時間,每次經過旁邊大佬工位,總是發現他在快速的切屏,不知道在搞什麼?難道他發現了快樂星球?

終於有一天當他沉浸其中的時候,讓我發現了,原來他是在擼 Linux 的原始碼。

擼程式碼又不是划水,至於這樣藏著掖著?

我也試一試?

Linux 的原始碼會不會太難了?有點慫。

最終我還是爬上了 GitHub,找到了 Linux 原始碼的倉庫。

Linux 永遠的神

30年的祖傳老程式碼

不愧為現在網際網路的基石,7.8k 的 watch,115k 的 star,37.9k 的 fork,star 數全 GitHub 排名第19名,真是666。

Linux 起源於1991年,距離今天已經30年了,30年的祖傳老程式碼,應該可以說是人類歷史上最最有價值的程式碼啦,你值得擁有。

開啟 iTerm 鍵入git clone git@github.com:torvalds/linux.git之後,我的 Linux 原始碼下載之旅這就開始了。

不得不說 Linux 的原始碼倉庫可太大了,最終下載下來大概佔用了 4.7G 的磁碟空間,有 824.1W個 Enumerating object,72865 個檔案。下載的時候等的我都要吐血了,中間我還跑出去吃了個飯,回來的時候發現它...它竟然還在下載。

du -sh linux/
4.6G	linux/
remote: Enumerating objects: 8241432, done.
接收物件中:  20% (1682045/8241432), 1.44 GiB | 235.00 KiB/s
.
.
.
接收物件中: 100% (8227041/8227041), 3.18 GiB | 232.00 KiB/s, 完成.
處理 delta 中: 100% (6846961/6846961), 完成.
正在檢出檔案: 100% (72865/72865), 完成.

前前後後應該等了大概 4 個小時,終於它下完了,這時間可太久了。

下載下來之後,我啥也沒幹,第一件事兒就是看看它的目錄結構,嗯它的目錄結構是這樣的:

$ tree linux/ -L 1
linux/
├── COPYING
├── CREDITS
├── Documentation
├── Kbuild
├── Kconfig
├── LICENSES
├── MAINTAINERS
├── Makefile
├── README
├── arch
├── block
├── certs
├── crypto
├── drivers
├── fs
├── include
├── init
├── ipc
├── kernel
├── lib
├── mm
├── net
├── samples
├── scripts
├── security
├── sound
├── tools
├── usr
└── virt

22 directories, 7 files

哈哈不管程式碼擼不擼的動,我總算是位見過 Linux 原始碼的目錄結構的程式設計師了。

是誰動了我剛克隆的原始碼

剛克隆的 Linux 原始碼莫名其妙少了幾個檔案

在一個帖子裡看到擼 Linux 的原始碼可以基於v4.13這個版本,所以我就在 iTerm 中鍵入 git checkout v4.13來嘗試 check out 對應的程式碼。

本以為會很順利,結果:

$ git checkout v4.13
error: 您對下列檔案的本地修改將被檢出操作覆蓋:
        include/uapi/linux/netfilter/xt_CONNMARK.h
        include/uapi/linux/netfilter/xt_DSCP.h
        include/uapi/linux/netfilter/xt_MARK.h
        include/uapi/linux/netfilter/xt_RATEEST.h
        include/uapi/linux/netfilter/xt_TCPMSS.h
        include/uapi/linux/netfilter_ipv4/ipt_ECN.h
        include/uapi/linux/netfilter_ipv4/ipt_TTL.h
        include/uapi/linux/netfilter_ipv6/ip6t_HL.h
        net/netfilter/xt_DSCP.c
        net/netfilter/xt_HL.c
        net/netfilter/xt_RATEEST.c
        net/netfilter/xt_TCPMSS.c
        tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus
請在切換分支前提交或貯藏您的修改。
error: 工作區中下列未跟蹤的檔案將會因為檢出操作而被覆蓋:
        Documentation/arm/Samsung/clksrc-change-registers.awk
        Documentation/security/LSM.rst
        drivers/staging/rtl8188eu/hal/odm_HWConfig.c
        drivers/staging/rtl8188eu/hal/odm_RTL8188E.c
        drivers/staging/rtl8188eu/include/odm_HWConfig.h
        drivers/staging/rtl8188eu/include/odm_RTL8188E.h
        tools/testing/selftests/rcutorture/configs/rcu/SRCU-t
        tools/testing/selftests/rcutorture/configs/rcu/SRCU-t.boot
        tools/testing/selftests/rcutorture/configs/rcu/SRCU-u
        tools/testing/selftests/rcutorture/configs/rcu/SRCU-u.boot
請在切換分支前移動或刪除。
終止中

這是怎麼事兒?

然後我連忙在 iTerm 中鍵入git status來追蹤檔案狀態

$ git status
位於分支 master
您的分支與上游分支 'origin/master' 一致。

尚未暫存以備提交的變更:
  (使用 "git add <檔案>..." 更新要提交的內容)
  (使用 "git checkout -- <檔案>..." 丟棄工作區的改動)

	修改:     include/uapi/linux/netfilter/xt_CONNMARK.h
	修改:     include/uapi/linux/netfilter/xt_DSCP.h
	修改:     include/uapi/linux/netfilter/xt_MARK.h
	修改:     include/uapi/linux/netfilter/xt_RATEEST.h
	修改:     include/uapi/linux/netfilter/xt_TCPMSS.h
	修改:     include/uapi/linux/netfilter_ipv4/ipt_ECN.h
	修改:     include/uapi/linux/netfilter_ipv4/ipt_TTL.h
	修改:     include/uapi/linux/netfilter_ipv6/ip6t_HL.h
	修改:     net/netfilter/xt_DSCP.c
	修改:     net/netfilter/xt_HL.c
	修改:     net/netfilter/xt_RATEEST.c
	修改:     net/netfilter/xt_TCPMSS.c
	修改:     tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

這就更尷尬了,我剛克隆的熱乎的程式碼,我確定我什麼都沒改。然而這是怎麼回事兒?整地我都有點兒懷疑我自己是不是真地幹了啥了?

但是我真地啥都沒動呀,程式碼卻又真地被改了,這簡直太不可思議了。

無奈之下我決定隨便找個檔案一探究竟,於是我進入了include/uapi/linux/netfilter_ipv6/這個目錄下。

$ cd include/uapi/linux/netfilter_ipv6/
$ pwd
github/linux/include/uapi/linux/netfilter_ipv6
$ ls
ip6_tables.h		ip6t_NPT.h		ip6t_ah.h		ip6t_hl.h		ip6t_mh.h		ip6t_rt.h
ip6t_LOG.h		ip6t_REJECT.h		ip6t_frag.h		ip6t_ipv6header.h	ip6t_opts.h		ip6t_srh.h

不知道你有沒有注意到ip6t_hl.h這個檔案,它在git status給出的資訊中是ip6t_HL.h,而在檔案目錄下的實際情況是ip6t_hl.h
看不清的圖片點選可以檢視大圖

也就是說不知道因為什麼原因ip6t_HL.h這個檔案它變成了ip6t_hl.h這個檔案。

接著我就找到了include/uapi/linux/netfilter_ipv6/ip6t_hl.h這個檔案,嘗試把它恢復到它變更之前的狀態。

$ cd -
github/linux
$ git  checkout -- include/uapi/linux/netfilter_ipv6/ip6t_HL.h

執行完檔案恢復指令後,我再次執行git status檢視檔案的狀態

$ git status
位於分支 master
您的分支與上游分支 'origin/master' 一致。

尚未暫存以備提交的變更:
  (使用 "git add <檔案>..." 更新要提交的內容)
  (使用 "git checkout -- <檔案>..." 丟棄工作區的改動)

	修改:     include/uapi/linux/netfilter/xt_CONNMARK.h
	修改:     include/uapi/linux/netfilter/xt_DSCP.h
	修改:     include/uapi/linux/netfilter/xt_MARK.h
	修改:     include/uapi/linux/netfilter/xt_RATEEST.h
	修改:     include/uapi/linux/netfilter/xt_TCPMSS.h
	修改:     include/uapi/linux/netfilter_ipv4/ipt_ECN.h
	修改:     include/uapi/linux/netfilter_ipv4/ipt_TTL.h
	修改:     include/uapi/linux/netfilter_ipv6/ip6t_hl.h
	修改:     net/netfilter/xt_DSCP.c
	修改:     net/netfilter/xt_HL.c
	修改:     net/netfilter/xt_RATEEST.c
	修改:     net/netfilter/xt_TCPMSS.c
	修改:     tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

於此同時我又進入了include/uapi/linux/netfilter_ipv6/這個目錄下檢視對應的檔案

$ cd include/uapi/linux/netfilter_ipv6/
$ ls
ip6_tables.h		ip6t_LOG.h		ip6t_REJECT.h		ip6t_frag.h		ip6t_mh.h		ip6t_rt.h
ip6t_HL.h		ip6t_NPT.h		ip6t_ah.h		ip6t_ipv6header.h	ip6t_opts.h		ip6t_srh.h

同樣的情況,我注意到先前的ip6t_hl.h這個檔案,這個時候在git status給出的資訊中是ip6t_hl.h,而在檔案目錄下的實際情況是ip6t_HL.h
看不清的圖片點選可以檢視大圖

這就更讓我鬱悶了,對檔案ip6t_hl.h進行恢復之後它變成了檔案ip6t_HL.h,但是它(ip6t_HL.h)依然是一個被變更的檔案。

看樣子是檔案變更並沒有恢復成功,我單純地只是想下載 Linux 的原始碼裝(看)個(下)逼,不成想下載之後,莫名其妙的多出一些檔案變更,而且還撤銷不了。

難道今日不宜擼程式碼?

緊接著我又去看了下檔案內容的變更情況

檔案內容差異

看來不只是檔名稱的變更,檔案內容同樣也變更了。

Git 對檔名稱大小寫不敏感

預設的情況下 Git 對檔名的大小寫是不敏感的

感覺應該是有兩個檔案,這個時候我有點懷疑會不會是 Git 對檔名稱大小寫不敏感,把兩個檔案當成一個檔案來處理了?

抱著這個疑問我再次爬上了 Github 進入到了https://github.com/torvalds/linux/tree/master/include/uapi/linux/netfilter_ipv6這個目錄

GitHub 上真的有兩個檔案。

這兩個檔案分別是include/uapi/linux/netfilter_ipv6/ip6t_HL.hinclude/uapi/linux/netfilter_ipv6/ip6t_hl.h,一個檔名大寫,一個檔名小寫。

檔案的內容和「檔案內容差異」這張圖片中的內容也相符合。

但是現在的情況是程式碼克隆下來之後卻只有一個檔案了,是因為 Git 對檔名稱大小寫不敏感,後下載的那個檔案覆蓋了先下載的檔案嗎?是ip6t_hl.h覆蓋了ip6t_HL.h,所以才會出現上文中我們所看到的現象嗎?

一番檢索後我發現 Git 真地有一個配置是設定它是否忽略檔名大小寫的。

這一點不知道大家是否知道,我平時真的是沒有注意到,難道是我平時的檔案命名太過規範了嗎,所以遇不到嗎?

Git 略檔名大小寫這個配置預設是開啟的,也就是說預設的情況下 Git 對檔名的大小寫是不敏感的。

我們可以通過設定core.ignorecase這個引數改變 Git 對檔名大小寫敏感性。

注意到這個方向後,感覺抓到了救命稻草,於是我就嘗試先在 Linux 這個倉庫內設定 Git 的大小寫敏感策略。

設定前我先檢視了一下它本來的設定情況

$ ls -A  linux/
.DS_Store               .gitignore              Kbuild                  arch                    include                 net                     usr
.clang-format           .idea                   Kconfig                 block                   init                    samples                 virt
.cocciconfig            .mailmap                LICENSES                certs                   ipc                     scripts
.get_maintainer.ignore  COPYING                 MAINTAINERS             crypto                  kernel                  security
.git                    CREDITS                 Makefile                drivers                 lib                     sound
.gitattributes          Documentation           README                  fs                      mm                      tools

$ cat linux/.git/config 
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        precomposeunicode = true
[remote "origin"]
        url = git@github.com:torvalds/linux.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master

通過 Git 客戶端直接從遠端倉庫克隆程式碼,預設是沒有對大小寫敏感做顯示的設定的。

然後我就通過git config core.ignorecase false這條指令嘗試去對這個倉庫做單獨的大小寫敏感策略設定。

$ pwd
github/linux
$ git config core.ignorecase false

下面是設定之後的配置項內容

$ cat .git/config 
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        precomposeunicode = true
        ignorecase = false
[remote "origin"]
        url = git@github.com:torvalds/linux.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master  

設定完之後我又一次嘗試對檔案進行恢復操作,然而我發現這並沒有起到任何作用。

這個時候我意識到,如果是因為 Git 對檔名稱大小寫不敏感,後下載的那個檔案覆蓋了先下載的檔案這一假設引起的,那麼檔名大小寫衝突應該只會發生在 Linux 倉庫下載的過程中,因此在下載之後設定大小寫敏感策略是不會起到任何作用的。

最後我決定先為 Git 設定一個全域性的大小寫敏感策略,然後再克隆 Linux 倉庫的原始碼。

Git 設定全域性的大小寫敏感策略對應的指令是git config --global core.ignorecase false

$ git config --global core.ignorecase false
$ git config --global  --get core.ignorecase
false

設定完我就換了個目錄重新從 GitHub 克隆 Linux 的原始碼。

因為有了第一次克隆的經驗,我意識到它等待的時間會特別長,所以在克隆 Linux 原始碼的同時我決定在 GitHub 上建了個實驗倉庫來驗證這個假設。

我爬上了 GitHub 建立瞭如下的這個倉庫為了顯得更規範,寫這篇文章的時候倉庫我重新建過)。

這個倉庫很簡單,整個倉庫除了README.md檔案之外,只有兩個檔案,一個檔名是YEAH,另一個檔名是yeah,是的,如你所見這個兩個檔案的檔名在忽略大小寫之後是一樣的。

然後我在自己的本本上嘗試克隆這個簡單地不能再簡單的倉庫。

$ git clone git@github.com:tobrainto/git-ignore-case-same-file-name.git
Cloning into 'git-ignore-case-same-file-name'...
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (9/9), done.
Resolving deltas: 100% (1/1), done.

克隆很順利,然而當我檢視克隆下來的檔案時

$ cd git-ignore-case-same-file-name/
$ ls
README.md	yeah
$ git status
位於分支 main
您的分支與上游分支 'origin/main' 一致。

尚未暫存以備提交的變更:
  (使用 "git add <檔案>..." 更新要提交的內容)
  (使用 "git checkout -- <檔案>..." 丟棄工作區的改動)

        修改:     YEAH

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

同樣的結果,丟了一個檔案,也就是說在git config --global core.ignorecase false這個全域性引數設定之後我克隆倉庫,依然未能解決問題。

到這裡上面的假設自然就被推翻了,顯然 Git 的確有對檔名大小寫是否敏感的設定引數,但是它不是引起我在克隆 Linux 原始碼過程中丟失檔案的原因,然後我就取消了上面正在緩慢進行對 Linux 原始碼的第二次克隆。

作業系統預設對檔案(夾)名大小寫不敏感

預設情況下,macOS 系統和 Windows 系統是不允許同一目錄下存在忽略大小寫之後檔名相同的檔案(夾)的。

折騰了這麼一圈我都有點兒想放棄了,我決定直接把有問題的檔案建立出來,然後從 GitHub 上拷貝下對應的內容。

先從ip6t_HL.h開始,當我拷貝ip6t_hl.h檔案準備改名為ip6t_HL.h時,作業系統直接提示我ip6t_HL.h已被佔用。

是 macOS 不允許同一目錄下存在忽略大小寫之後同名的檔案嗎?

為了排除 Git 的影響,我換個目錄又確認了一遍。

$ cd /Users/yeah/
$ pwd
/Users/yeah/
$ touch A
$ touch a
$ ls
A
$ pwd
/Users/yeah/
rm -rf *
$ ls
$ touch a
$ touch A
$ ls
a

還真是的呢,macOS 不允許同一目錄下存在忽略大小寫之後同名的檔案。

原來是這個原因。

Linux 系統中檔案(夾)名是區分大小寫的,而 Windows 系統和 macOS 系統中檔案(夾)名是不區分大小寫的,當在同一目錄下存在區分大小寫的同名檔案(夾)時,處理起來就會變得比較麻煩。

簡單說就是:Linux 系統可以在同一路徑下同時建立 YEAH 和 yeah 這兩個區分大小寫的資料夾或者檔案,而在 Windows 系統和 macOS 系統中這樣就做會提示檔名已被佔用,從而無法建立。

所以克隆 Linux 原始碼的時候,當檔案include/uapi/linux/netfilter_ipv6/ip6t_hl.h被儲存之後,檔案include/uapi/linux/netfilter_ipv6/ip6t_HL.h就無法儲存了;在我嘗試恢復的時候一個檔案又會覆蓋另一個檔案。

隱約的記得之前我應該也有遇到過,但是這種場景出現的概率非常小,然後我就完全沒有印象了。

到這裡原因已經很明確了,是因為作業系統對檔案(夾)名大小寫不敏感。

那 Mac OS 可以支援區分檔名大小寫嗎?

這個當然,但是區分檔名大小寫不是 Mac OS 的預設選項,如果需要支援區分檔名大小寫,需要我們稍微做一些功課,進行一些設定。

Apple 檔案系統 (APFS)

基於 Apple 官網的這個頁面
https://support.apple.com/zh-cn/guide/disk-utility/dsku19ed921c/20.0/mac/11.0
我們可以看出 Mac 支援多種檔案系統格式。

APFS 適用於 macOS 10.13 或後續版本使用的檔案系統,APFS 支援以下4種格式。

  • APFS:使用 APFS 格式。如果不需要加密或區分大小寫格式,請選取此選項。
  • APFS(加密):使用 APFS 格式且加密宗卷。
  • APFS(區分大小寫):使用 APFS 格式並區分檔案和資料夾名稱的大小寫。例如,名稱為"Homework"和"HOMEWORK"的資料夾是兩個不同的資料夾。
  • APFS(區分大小寫,加密):使用 APFS 格式,區分檔案和資料夾名稱的大小寫且加密宗卷。例如,名稱為"Homework"和"HOMEWORK"的資料夾是兩個不同的資料夾。

使用 APFS 的 Mac 我們可以輕鬆的通過新增 APFS 容器中的宗卷(具體可以參考這個頁面https://support.apple.com/zh-cn/guide/disk-utility/dskua9e6a110/20.0/mac/11.0 ),在新增宗卷時指定格式為「 APFS(區分大小寫)」來低成本的開闢一塊區分大小寫的儲存塊。

Mac OS 擴充套件

Mac OS 擴充套件適用於 macOS 10.12 或之前版本使用的檔案系統,Mac OS 擴充套件 支援以下4種格式。

  • Mac OS 擴充套件(日誌式):使用 Mac 格式(日誌式 HFS Plus)來保護分層檔案系統的完整性。如果不需要加密或區分大小寫格式,請選取此選項。
  • Mac OS 擴充套件(日誌式,加密):使用 Mac 格式,要求密碼,並加密分割槽。
  • Mac OS 擴充套件(區分大小寫,日誌式):使用 Mac 格式並區分資料夾名稱的大小寫。例如,名稱為"Homework"和"HOMEWORK"的資料夾是兩個不同的資料夾。
  • Mac OS 擴充套件(區分大小寫,日誌式,加密):使用 Mac 格式,區分資料夾名稱的大小寫,要求密碼,並加密分割槽。

使用 Mac OS 擴充套件的 Mac 我們可以通過分割槽,在分割槽的時候指定格式為「Mac OS 擴充套件(區分大小寫,日誌式)」來獲得區分大小寫的一個分割槽。

由於我的本本已經升級到了 macOS Big Sur(11.2.1)(升級過程還蠻坑的,感興趣的我之前的文章:升級 macOS Big Sur 差點丟了我多年的珍藏檔案(夾)!!!),所以這裡我採用的是通過新增 APFS 宗卷的方式來獲得一塊區分大小寫的儲存塊,整個過程是這樣的。

  1. 找到 mac 的磁碟工具

  2. 選擇磁碟,點選 "新增APFS宗卷"

  3. 設定宗卷的名稱、選擇「 APFS(區分大小寫)」格式,設定宗卷的大小。

    10G應該夠我用來存 Linux 的原始碼了

  4. 確認宗卷資訊,確定"新增"

  5. 檢視已新增的宗卷

    到這裡一個支援檔名區分大小寫的宗卷就新增完畢了。

  6. 進入新新增的宗卷
    檢視宗卷的掛栽點,掛載點為/Volumes/x

    前往掛栽點去看看

    或者直接

$ cd /Volumes/x
$ pwd
/Volumes/x
  1. 建立檔案試一把
$ touch A
$ touch a
$ ls
A	a

同一目錄下已經可以同時存在區分大小寫同名的檔案。

在區分大小寫的宗捲上再次克隆 Linux 原始碼

強迫症的我在區分大小寫的宗捲上再次克隆 Linux 原始碼

準備好區分檔名大小寫的宗卷之後,我就在/Volumes/x這個目錄下重新從 GitHub 上克隆 Linux 倉庫的原始碼。

又是漫長地等待,4 個小時過去了,第二次真正意義上克隆才算完成。

當我再次敲下git status指令時

$ pwd
/Volumes/x
$ cd linux/
$ git status
位於分支 master
您的分支與上游分支 'origin/master' 一致。

無檔案要提交,乾淨的工作區

我太難了!!!

1991年,Linus Torvalds 永遠的神。。。

Checkout 到v4.13的 Commit

$ git checkout v4.13
正在檢出檔案: 100% (82007/82007), 完成.
注意:正在檢出 'v4.13'。

您正處於分離頭指標狀態。您可以檢視、做試驗性的修改及提交,並且您可以通過另外
的檢出分支操作丟棄在這個狀態下所做的任何提交。

如果您想要通過建立分支來保留在此狀態下所做的提交,您可以通過在檢出命令新增
引數 -b 來實現(現在或稍後)。例如:

  git checkout -b <新分支名>

HEAD 目前位於 569dbb88e80d Linux 4.13

心情豁然開朗,好像後面馬上就可以愉快的擼(劃)碼(水)啦!

有沒有漲知識呢?收藏一波吧?

最後補充一點

Windows 怎麼整?

如果你的環境是 Windows 也是有解決辦法的,從 Windows 10 Version 1803 更新開始,微軟為 NTFS 檔案系統新增了一個 SetCaseSensitiveInfo 標誌。

你可以有選擇的為所需資料夾啟用此 flag,啟用之後 NTFS 檔案系統就會針對該資料夾將其子檔案視為區分大小寫的檔案系統。

那麼如何開啟檔案的 SetCaseSensitiveInfo 標誌呢,只需要你用管理員身份執行一下這個指令就可以了

fsutil file SetCaseSensitiveInfo you-dir-path enable

我是小猿來也,大家擼碼愉快呀!!!

相關文章