Git 全景課 / 第四部分 應用 · M12

M12 知識的版本管理

Git 為程式碼而生,但它管理的其實是「文字的變化」。筆記、論文、講義、知識庫同樣受益:可以回到任何一版,知道每一處為甚麼改,幾個人同時寫也不互相覆蓋。前提是用對格式和寫法。
約 60 分鐘互動 ×3 · 小測 4 題先修:M5、M9
學完本單元,你能夠:
  1. 說出為甚麼純文字(Markdown)比 Word 更適合版本管理;
  2. 用一句一行的寫法,讓 diff 和合併都清楚;
  3. 判斷哪些檔案進 Git、哪些不應該,以及大檔案怎樣處理;
  4. 用 Git 管理 Obsidian 等筆記庫,並分清 Git 和雲端同步。

1知識也需要版本管理 Why version knowledge

M0 的三個問題,在知識工作裏一樣存在:論文改了十幾稿,想找回導師說「這段寫得好」的那一版;講義每學期修訂,想知道某個定義是何時、為何改的;三個人合寫報告,不想再用「最終版_v3_真的最終」。Git 把這些都變成命令:git log、git blame(M14)、git diff、PR(M10)。

2純文字勝過 Word Plain text first

Markdown/純文字Word(.docx)、PowerPoint
Git 看到的是一行一行的文字一個壓縮檔(內含 XML),改一個字整個檔案都不同
git diff清楚列出改了哪幾句只說「二進位檔案不同」
合併逐行三方合併,衝突可讀無法合併,只能二選一
倉庫大小每版只多存改動過的小檔每存一次就多一份完整的檔案
輸出可以轉成 Word、PDF、網頁(如 Pandoc)本身就是成品

實用的分工:寫作和修訂用 Markdown,交付時才轉成 Word 或 PDF;成品不必進 Git,或只在正式版本時附在 release 上(M11)。

3一句一行 One sentence per line

M5 已經看到:Git 以「行」為單位比較和合併。文章若一段寫成一行,改一個字,整段都會顯示為改動;兩個人改同一段的不同句子,也一定衝突。改成一句一行,diff 只標出真正改了的那句;兩人改的句子之間隔着至少一行未改動的句子,Git 就能自動合併。

但一句一行不是萬靈丹:Git 把相鄰兩行的改動視為同一處修改,所以一人改第 2 句、另一人改第 3 句,仍然會衝突;改第 2 句和第 4 句,才會自動合併。好處是衝突範圍只有那兩句,而不是整段。

互動 1 同一個修改,兩種寫法的 diff

貼上一段文字(或用預設的;段落之間空一行)。第一步:你改第 2 句的一個詞,比較兩種寫法下 git diff 的樣子。第二步:同事在另一條分支改了第 N 句,看看合併時兩種寫法誰會衝突。

整段一行

一句一行

寫法細節 在句號、問號、分號之後換行;很長的句子也可在逗號後換行。段落之間仍空一行。整段一行的舊文件,可以用 git diff --word-diff-regex=.(或 git diff --color-words=.)逐字顯示改動。注意不要只寫 --word-diff:它預設以空格切分「詞」,而中文句子之間沒有空格,整段會被當成一個詞,結果和普通 diff 差不多;=. 的意思是「每一個字元算一個詞」。不過合併時 Git 仍以行為單位,所以新寫的文件最好一句一行。
換行在顯示時會變成甚麼,視乎工具和設定 按 Markdown 標準(CommonMark),段落內的單一換行會顯示成一個空格:英文剛好需要這個空格,中文句子之間卻會多出一個空隙。例如 Pandoc 預設就會這樣輸出,要加上 east_asian_line_breaks 擴充(pandoc -f markdown+east_asian_line_breaks …)才會去掉中文之間的空格。反過來,有些編輯器會把單一換行顯示成真正的換行,令一句一行的段落看起來每句一行;Obsidian 的設定裏有「Strict line breaks」選項控制這一點。採用一句一行之前,先在自己的編輯器和輸出工具試一段,確認顯示和輸出都是你要的效果。

4大檔案與媒體:放對地方 Large and binary files

M3 說過,物件一旦寫入就不會改變,也不會消失。所以一個 500 MB 的影片提交進去,即使下一個 commit 刪除它,它仍然永遠留在倉庫裏,每個人 clone 都要下載。二進位檔(Word、圖片、影片)每次修改都會多存一份完整的檔案。Git 打包(pack)時會嘗試把相似的物件存成差異(delta),二進位檔也不例外;但 .docx、.mp4、.jpg 本身已經是壓縮格式,改一點內容整個檔案的位元組都會變,所以幾乎省不了空間。

互動 2 倉庫會長多大

選擇倉庫裏有甚麼檔案,以及修改多少次,估算倉庫大小(以未壓縮大小粗略估算;文字檔每版只多存改了的那份)。

存放原則 文字進 Git;媒體放儲存;倉庫留指標。影片、原始照片、大型數據放在雲端硬碟、NAS 或物件儲存,倉庫裏只寫一行連結或路徑(指標),說明檔案在哪裏、哪個版本。真的需要版本管理的大型二進位檔,可以用 Git LFS:倉庫只存一個小小的指標檔,真正的內容放在 LFS 伺服器。
git lfs install                # 每部電腦做一次(先安裝 Git LFS)
git lfs track "*.psd"          # 寫進 .gitattributes
git add .gitattributes design.psd
git commit -m "加入設計原稿(LFS)"

git lfs track 只是在 .gitattributes 加了一行,告訴 Git 這類檔案要經 LFS 處理:

*.psd filter=lfs diff=lfs merge=lfs -text

提交後,倉庫裏的 design.psd 其實只是一個三行的指標檔(用 git show HEAD:design.psd 可以看到);真正的內容按 oid 存在 LFS 伺服器:

version https://git-lfs.github.com/spec/v1
oid sha256:d7eac9f7…(內容的 SHA-256)
size 200000

互動 3 這個檔案放哪裏

為每個檔案選擇去處,再按「對答案」。

5Obsidian 筆記庫+Git An Obsidian vault in Git

Obsidian 的筆記庫(vault)就是一個裝滿 Markdown 檔的資料夾,所以可以直接 git init。幾個要點:

6Git 與雲端同步 Git vs file sync

Dropbox、iCloud、SyncthingGit
何時同步自動、即時你決定(commit、push、pull)
歷史有限的舊版本,沒有說明完整歷史,每次改動有說明
兩人同時改同一檔產生「衝突副本」(如 .sync-conflict 檔),要人手比對逐行合併,衝突處清楚標示
適合個人檔案在幾部電腦間同步、大型媒體、暫存區需要追溯和協作的文字知識
不要讓兩者管同一個資料夾 Git 寫入一次 commit 要改動 .git 裏好幾個檔案(index、物件、分支指標)。同步工具不知道 Git 正在寫,可能把寫到一半的檔案傳到另一部電腦;兩部電腦都動過時,還會為分支指標等內部檔案產生 .sync-conflict 副本。結果是另一邊的倉庫缺物件、分支指向不存在的 commit,也就是壞了。實務上的分工是:Git 管知識庫,同步工具管其他資料夾;兩部電腦之間的知識庫靠 push/pull 傳遞。

7容易混淆的對照 Easily confused

常見誤解
  • 「Git 只適合程式員。」它管理的是文字,任何以文字為主的工作都適用;介面可以用 GitHub Desktop、VS Code 或 Obsidian 外掛,不一定要命令列。
  • 「刪掉大檔再提交,倉庫就會變小。」不會;舊 commit 仍保存着它。要真正移除須改寫整段歷史(例如 git filter-repo),而且所有人都要重新 clone。
  • 「一句一行會讓文章變得零碎。」只影響原始檔;顯示和輸出時通常仍合成同一段,但中文之間會否多出空格、會否變成真換行,視乎工具和設定(第 3 節)。
  • 「一句一行就永遠不會衝突。」改相鄰兩句仍會衝突;只是衝突範圍縮小到那幾句。
  • 「Git 和雲端同步是同一回事。」同步工具自動把最新版複製到每部電腦,兩人同時改只會得到衝突副本;Git 由你決定何時記錄,保證每一版都找得到、知道為甚麼改,並能逐行合併(第 6 節)。兩者分工,不要管同一個資料夾。
  • 「git mv 會把改名記錄下來。」Git 只存快照,改名是事後按內容相似度推斷的(第 5 節)。

8速查 Cheat sheet

命令作用
git diff --word-diff-regex=.逐字顯示改動(中文要加 =.,每個字元算一個詞;適合整段一行的文字)
git log --follow 筆記.md追蹤檔案改名前後的歷史
git mv 舊名.md 新名.md改名並暫存(等於搬檔後 git add 兩邊;Git 不另記「改名」)
git lfs track "*.psd"指定由 LFS 管理的檔案類型(寫進 .gitattributes)
git lfs migrate import --include="*.psd" --everything把已提交的檔案改由 LFS 管理(改寫歷史)
git count-objects -vH查看倉庫佔用的空間
pandoc 報告.md -o 報告.docx從 Markdown 輸出 Word(另需安裝 Pandoc)

9小測 Quiz

1. 兩人同時修改同一段的不同句子。哪一種寫法 Git 能自動合併?
2. 一段 800 MB 的課堂錄影,最合適的做法是:
3. 為甚麼 .obsidian/workspace.json 應該寫進 .gitignore?
4. 同一個知識庫資料夾,同時用 Syncthing 同步、又用 Git 管理。風險是:

本單元重點 Summary

延伸閱讀