當初搭建 Windows / Mac Git 系統,最重要的不是省錢,也不是避開網際網路,而是早就定下的底線:長期投入、具有商業價值的核心原始碼,不以明文放到任何第三方雲端平台。 就算 GitHub 倉庫標成 Private,我也不會把平台永遠不會越權、被入侵或遭遇供應鏈事件當成唯一保障。

這不是說 GitHub 一定會私自取得我的程式碼。Private 提供的是存取權限控制,不等於營運平台在技術上完全無法讀取 Git 物件。我想要的是自己保有金鑰,雲端只負責儲存密文;即便雲端帳號出事,也不能僅靠它就得到完整原始碼。

因此,我用 Windows bare repo 當本地 Central,Mac 透過 SMB 與它同步,後來才加上 E 碟 mirror、斷開連線的 7z 歸檔,以及 Cryptomator 加密後的 OneDrive / Google Drive 異地副本。密文雲端備份不等於支援 push/pull 的加密 Git 遠端倉庫,這個差別在我開始想用手機讓 ChatGPT/Codex 協助開發後,成了整套架構最大的痛點。

以下保留 2024–2026 年的操作筆記,以及 Git 2.47.3 小型合成倉庫的實際測試。原有測試只驗證 Git refs、mirror 與還原語義,不能當作密文即時 Git 推送或手機遠端開發已完成的證據。

為什麼我沒有直接選擇 GitHub Private?

我的威脅模型關注的是第三方雲端平台的信任邊界,不是指控平台會濫用資料。GitHub 的 Private Repository 條款提供保密與存取規則,但不等同端到端、由使用者獨立持有金鑰的整倉加密。對一般開發者來說 Private 可能已足夠;對我這類長期高價值核心程式碼,設計起點不同。

路線雲端可能看見的內容對我的用途
一般 GitHub PrivateGit 物件、原始碼與提交歷史,由平台權限保護不符合「核心程式碼不明文上雲」
本地 Git + Cryptomator 密文同步加密檔案與部分大小、時間等中繼資料可靠的異地備份方向,但不是 Git 網頁協作
用戶端先加密整個 Git remote目標是讓遠端只接收密文;細節依實作而定可研究的遠端 push/pull 方向,尚未驗證
離線 7z 加密歸檔脫離線上同步的加密快照防誤刪與事故,不負責即時開發

這種安排也不是「絕對安全」:若本機遭惡意程式控制、解密金鑰外洩,或 AI 工具已取得明文工作目錄,雲端加密無法補救。Cryptomator 的安全說明也清楚指出這些邊界與檔案中繼資料的限制。

核心原始碼不明文上雲的 Git 備份架構:Windows 與 Mac 透過本地 Windows Central 裸倉庫同步,E 碟鏡像進入 Cryptomator 密文雲端備份及 7z 離線歸檔。

架構示意:日常 Git 協作留在可信本地,雲端只保存用戶端加密後的備份,離線歸檔獨立保留歷史。加密 Git remote 和手機開發仍是設計方向。

Win/Mac 區域網路 Git 同步:為什麼用裸倉庫做 Central

先看運行時的關係:

Windows 开发仓库 ─── push / pull ──┐
                                  ├── Windows Central(bare repo)
Mac 开发仓库 ─ SMB ─ push / pull ──┘           │
                                             │ mirror 更新
                                             ▼
                                      E 盘镜像裸仓库
                                             │
                                 加密归档 / 离线复制
                                             ▼
                                    移动硬盘、异地网盘

四層裡其實只有後三層才承擔備份職責。把它們攤開看,哪一層會跟著出問題就清楚了:

層級與位置關鍵動作主要應對不能解決的事
協作 Central:Windows D 碟裸倉庫Win/Mac push、pull已推送程式碼在單台開發機損壞時仍可取回Windows 關機、SMB 斷線、Central 本身損壞;它不算獨立冷備
同機鏡像:Windows E 碟 mirrorclone --mirror、remote updateCentral 目錄級損壞後從最近鏡像復原D/E 同機失效;--prune 可能同步刪除分支
離線歸檔:斷開的行動硬碟7z 加密包、7z t主機故障、勒索軟體影響線上副本更新靠人工;最新版本取決於歸檔時間
異地密文:Cryptomator + OneDrive / Google Driverobocopy、密文清單校驗、下載驗證本地裝置全部遺失、異地取回雲端同步也能傳播錯誤;不含未提交程式碼及外部資料

RPO(最多損失到哪個版本)由各層最後一次成功提交、同步或歸檔時間決定;RTO(多久能復原)還取決於介質速度、解密和演練情況。以前沒有專門記錄實際復原耗時,所以這裡只比較復原路徑,不用假定的分鐘數冒充 RTO/RPO。D/E 同屬一台 Windows,兩個磁碟代號也消除不了整機故障這個共同風險。

Central 的關鍵不是「有個共享資料夾」,而是裡面存放的是一個 bare repository(裸倉庫)。它有 Git 的物件、參照和倉庫設定,卻沒有普通工作區。兩台開發機都持有自己的工作樹,修改在本機完成,提交以後才推到 Central。

按照 Git 官方的 git init 文件,新增裸倉庫的基本形式是:

# 示例路径,不是我原机器的目录
git init --bare "D:/git-central/project.git"

# 已有 Windows 工作仓库中配置 Central
git remote add central "D:/git-central/project.git"
git push -u central master

Mac 側先把 SMB 共享掛載到本地,再把掛載路徑設為 central 的 remote。下面的 master 是當年倉庫使用的分支名稱;如果你的預設分支是 main,不要照抄 master。

git remote add central "/Volumes/git-central/project.git"
git fetch central
git pull --ff-only central master

舊操作手冊裡還有幾行非常具體的 Mac 端排查指令,省去了「究竟是 Git 沒更新,還是 SMB 根本沒掛上」的猜測:

# 在 Mac 上挂载 Windows 的 SMB 共享(示例地址)
open "smb://<windows-host>/<share-name>"

# 先确定共享真正挂载到了哪里
ls /Volumes
ls "/Volumes/<share-name>"

# 再从项目工作区确认 Git remote 指向的位置和当前分支
git remote -v
git branch --show-current
git status --short

原筆記記錄的是先檢查共享目錄確實能看到裸倉庫,再操作 central remote;不是看到 git pull 報錯就急著 reset --hard。Windows 側也遵循先看 git status、提交,再 git push central master 的順序。這些指令來自當年的操作筆記,文中把內網主機名稱與共享路徑換成了範例佔位符。

在原來的日常習慣裡,開發前先 git pull central master,開發結束後看 git status,再 git add .、git commit、git push central master。另一台電腦從同一個入口拉取。範例中的 --ff-only 是我現在更願意保留的保護:遇到分叉就停下來處理,避免普通 pull 默默產生合併提交。

這種安排有個很實在的好處:Central 壞掉時,兩台開發機的 Git 歷史還在;其中一台開發機壞掉,也不等於歷史隨它消失。我一直把「完整倉庫可以作為重建的種子」當成設計原則。不過這句話有條件:那份倉庫必須真的保有你需要的分支、標籤和物件。只 clone 過單分支、做了淺層複製,或者把未提交檔案放在工作樹裡的情況,不能自動算作完整備份。

還有一個容易忽略的邊界:透過 SMB 直接存取裸倉庫,實際依賴共享檔案系統的權限、鎖、網路穩定性。舊方案能滿足自己的雙機習慣,不代表適合跨地域、多使用者或者不穩定網路。新增長期 Git 服務時,我會更願意使用 Git over SSH 這樣的遠端傳輸,而不是把共享碟當成通用 Git 伺服器。

Git mirror 冷備:同步刪除和歷史快照是兩回事

真正拉開安全距離的是 Central 後面那幾層。原來的 E 碟放一份 --mirror 方式複製的裸倉庫,不參與開發,只做更新;行動硬碟留加密歸檔,平時離線保存;雲端硬碟再留一份經過加密處理的異地副本。它們不是四個相同的 git clone,而是應對不同故障範圍。

Git 官方對 git clone --mirror 的定義值得仔細看:mirror 不僅包含分支,還會按鏡像 refspec 同步其他參照。它與普通開發複製不同,後續 git remote update 可能覆蓋對應 refs。初次建立和日常更新的範例可以寫成:

# 只在初始化冷备仓库时执行一次
git clone --mirror "D:/git-central/project.git" "E:/git-cold/project.git"

# 后续更新镜像。--prune 可能删除源端已经不存在的引用
git --git-dir="E:/git-cold/project.git" remote update --prune

# 只检查 Git 对象,并不等于完成一次恢复演练
git --git-dir="E:/git-cold/project.git" fsck --full

當年的 E 碟冷備操作手冊除了 git remote update,還明確寫了一個確認目前提交的辦法:

# 在 mirror 裸仓库内执行;本例沿用历史 master 分支
git remote update
git log origin/master -1 --oneline

# 不只比最近一条 log;正式恢复还需要核对 refs 和对象
git for-each-ref --format='%(refname) %(objectname)' refs/heads refs/tags

這裡 origin/master 是歷史筆記中的指令寫法;裸鏡像能否以該名稱存取參照,要以實際設定和 git show-ref 結果為準。更可靠的通用做法是直接查看 refs/heads/master 以及其他需要保存的 refs。

我用 Git 2.47.3 建了一個臨時倉庫,走了一遍 --prune 的刪除鏈:推送 test-prune 分支、同步 mirror,然後從 Central 刪除這個分支,再更新鏡像。最關鍵的輸出只有幾行:

git_version=git version 2.47.3
feature_after_sync=present 733c4dc75735672f2db8b4ed8185058ceddaf8c7
feature_after_prune=absent
git_fsck_exit=0
git_fsck_output=dangling commit 733c4dc75735672f2db8b4ed8185058ceddaf8c7
restored_master_sha=c7b763f667cbbba6c1bb39a6719236a554257a8c

注意看:git fsck 傳回 0,還能找到 dangling commit,但鏡像已經沒有 test-prune 的分支參照。懸空物件還可能在後續垃圾回收時消失。鏡像不是「時光機」——它會同步最新狀態,也會同步刪除。這個實驗驗證的是 Git 參照行為;7z 和雲端硬碟的可靠性仍需要用真實歸檔做復原演練。

按照 git remote 文件,remote update 做的是 fetch;--prune 會按設定清除已經失效的遠端參照。每次更新通常只傳輸需要的新物件,不必把整個倉庫重新拷一遍。但 mirror 是同步副本,不是不可變快照。上游誤刪了分支,下一次帶 prune 的同步也可能把這種刪除傳播到鏡像。它很適合快速復原目前狀態,卻不能獨自承擔「防誤刪、防勒索」的所有責任。

所以當年還保留了兩條不同的備份支線:一條把冷備倉庫打成帶日期的 7z 加密歸檔,放到平時斷開的行動硬碟;另一條把 Git 鏡像保存在 Cryptomator 的加密 vault,再將 vault 的密文目錄複製到 OneDrive / Google Drive 同步目錄。它們可能保護同一份 Git 歷史,但不能簡單畫成「所有雲端硬碟副本都先經過 7z,再經過 Cryptomator」。真正有價值的是某一份歷史歸檔在 Central 下一次更新時不會被覆蓋。加密口令不能與歸檔放在一起,複製成功也不等於復原成功。

同一台 Windows 上的 D 碟 Central 與 E 碟鏡像,能緩解倉庫誤操作或單卷損壞,卻擋不住整機故障、被盜或者同機勒索。行動硬碟只有在備份完成後真正斷開,才有離線隔離的意義;雲端儲存如果同步了刪除或損壞,也未必保留可用舊版。這樣的差異,是我後來不再滿足於「已經有好幾份備份」的原因。

還要補一處範圍:Git 保存已提交的 Git 物件和參照,並不會自動替你備份 .gitignore 排除的大資料、未提交的工作區變更、外部資料庫。使用 Git LFS 或子模組的專案,也需要分別確認相應的物件和依賴是否已備份。倉庫鏡像的完整,不等於整個開發環境的完整。類似 Docker volume 這樣的運行資料,要用另一套備份方案處理;我之前也單獨寫過 Docker 資料卷備份到 GitHub 的容災實踐。

Cryptomator + 雲端硬碟:SHA-256 如何校驗加密備份

雲端硬碟這一層最麻煩的不是上傳,而是上傳之後如何確定沒漏檔案。當時我專門為 Cryptomator + OneDrive、Cryptomator + Google Drive 留了兩份操作筆記,裡面有密文基準清單、VaultIntegrityCheck.ps1 的呼叫順序,以及複製完成後如何定位 MISSING、CHANGED、EXTRA。

Cryptomator 負責對檔案內容、名稱做客戶端加密,雲端硬碟客戶端負責傳輸,它們不是同一個軟體的兩項功能。Cryptomator 官方安全說明也強調加密不能代替備份。實際舊流程更接近:

E: 冷备镜像 / Cryptomator 加密 vault(基准)
     │
     ├─ 对稳定的密文目录生成指纹清单(Update)
     │
     ├─ robocopy 到 OneDrive 或 Google Drive 本地同步目录
     │
     ├─ 等网盘客户端显示同步完成
     │
     └─ 对目标密文目录只读比对(Verify)
          ├─ MISSING:源有、目标没有
          ├─ CHANGED:路径相同,但文件指纹变化
          ├─ EXTRA:目标多了文件,需判断冲突/临时文件
          └─ 匹配:本地同步目录与清单对应

當時自用的 VaultIntegrityCheck.ps1 固定了 Update 與 Verify 兩種模式:只在確認 E 碟密文源副本正確時更新基準,驗證目標目錄時不更新基準。以下是歷史呼叫方式(路徑為範例);沒有該腳本,也能用後面的原生 PowerShell 比對兩份本地密文目錄,但不具備持久基準功能:

# 只在确认 E: 源 vault 停止写入并且内容正确时更新基准
powershell -NoProfile -File "D:\BackupTools\VaultIntegrityCheck.ps1" `
  -VaultPath "E:\BackupVault" `
  -ManifestPath "D:\BackupTools\vault-manifest.json" -Mode Update

# 等 OneDrive 上传完成,再对目标密文目录做只读校验
powershell -NoProfile -File "D:\BackupTools\VaultIntegrityCheck.ps1" `
  -VaultPath "C:\CloudSync\OneDrive\BackupVault" `
  -ManifestPath "D:\BackupTools\vault-manifest.json" -Mode Verify

Google Drive 版本在兩步中間還保留了一條 robocopy 複製指令,參數為 /E /R:2 /W:2 /MT:8 /FFT /Z /NP。換成中性目錄後的可複製骨架是:

robocopy "E:\BackupVault" "G:\MyDrive\BackupVault" /E /R:2 /W:2 /MT:8 /FFT /Z /NP
if ($LASTEXITCODE -ge 8) { throw "Robocopy failed with code $LASTEXITCODE" }

微軟的 Robocopy 結束碼說明確認:傳回碼 8 及以上才表示至少有一次複製失敗。但 0–7 只是複製階段沒有報告這種失敗,絕不能把它寫成「所有檔案 SHA-256 均通過」。/E 會包含子目錄,但不負責刪除目標已有的多餘檔案;我也不會在唯一雲端副本上隨手換成會刪目標檔案的 /MIR。

歷史筆記留下的校驗回饋有 [OK] Vault matches the manifest exactly.、MISSING、CHANGED、EXTRA 等類型。看到 MISSING 或 CHANGED,應先查上傳是否完成、檔案是否仍在寫入,不要對雲端硬碟目錄執行 Update 來把異常覆蓋成新基準。Google Drive 筆記還記過 .tmp.driveupload 暫存檔;它可能是同步中間狀態,不能無條件忽略,等完成同步後重新驗證更穩妥。

如果讀者沒有那份私人指令碼,至少能先用 Windows 自帶的 Get-FileHash 檢查單一檔案的 SHA-256。它是 PowerShell 的標準 cmdlet,演算法和 -LiteralPath 參數見 微軟官方文件:

Get-FileHash -Algorithm SHA256 -LiteralPath "E:\BackupVault\vault.cryptomator"
Get-FileHash -Algorithm SHA256 -LiteralPath "C:\CloudSync\OneDrive\BackupVault\vault.cryptomator"

但只查一個設定檔遠遠不夠。整個 vault 的清單至少應覆蓋相對路徑、檔案大小和每個密文檔案的 SHA-256,並偵測目標端多檔案、少檔案、同名變更;還要有明確的來源基準日期,不能在兩端一邊寫入一邊比。沒有現成校驗工具,也可以用一段通用的唯讀 PowerShell 指令碼處理:遞迴列出來源、目標檔案的相對路徑、位元組數和 SHA-256,再用 Compare-Object 找出差異:

function Get-Inventory([string]$root) {
  $base = (Resolve-Path -LiteralPath $root -ErrorAction Stop).ProviderPath.TrimEnd([char]'\')
  Get-ChildItem -LiteralPath $base -File -Recurse -Force -ErrorAction Stop |
    ForEach-Object {
      [pscustomobject]@{
        Path   = $_.FullName.Substring($base.Length).TrimStart([char]'\')
        Bytes  = $_.Length
        SHA256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 -ErrorAction Stop).Hash
      }
    }
}
$original = @(Get-Inventory "E:\BackupVault")
$replica  = @(Get-Inventory "C:\CloudSync\OneDrive\BackupVault")
if (!$original.Count -or !$replica.Count) { throw "Empty or unavailable vault" }
$diff = Compare-Object $original $replica -Property Path,Bytes,SHA256
if ($diff) {
  $diff | Format-Table -AutoSize
  throw "Local encrypted vaults differ"
}
Write-Host "[OK] Local encrypted vault file hashes match"

這份新範例只覆蓋檔案,不覆蓋空目錄或雲端伺服器狀態,也沒有歷史 Update/Verify 的持久基準能力。真正的雲端校驗還差一步:把雲端某一版加密歸檔重新下載到與同步目錄無關的位置,再拿下載檔案的雜湊去比上傳前留下的可信記錄。只有「雲端硬碟同步圖示已完成 + 本地目錄一致」,並不能單獨證明遠端檔案可取回。

對離線行動硬碟,我當年採用 7z 全量加密包而不是始終依賴熱鏡像。7z 的 -mhe=on 會加密 7z 格式內的檔名,-p 表示使用口令;不要把口令明文拼進共享的指令碼、指令歷史或日誌。7-Zip 官方 7z 格式說明確認支援 AES-256。建立歸檔時,我更建議在 GUI 裡確認口令和「加密檔名」;下面保留 CLI 參數範例,操作前先用本機 7z -h 核對版本和密碼輸入方式,不要把密碼直接寫進指令碼或指令歷史:

# 只在冷备仓库没有写入时,对其目录创建带口令的 7z 归档
7z a -t7z -p -mhe=on "git-snapshot-YYYYMMDD.7z" "E:\GitCold\project.git"

# 测试归档是否能读完:输入口令后检查退出码
7z t "git-snapshot-YYYYMMDD.7z"

例如把這個歸檔從雲端重新下載到 D:\Downloads,可以這樣驗證上傳前後位元組一致:

$before = (Get-FileHash "D:\GitArchive\git-snapshot-YYYYMMDD.7z" -Algorithm SHA256).Hash
$after  = (Get-FileHash "D:\Downloads\git-snapshot-YYYYMMDD.7z" -Algorithm SHA256).Hash
if ($before -ne $after) { throw "Downloaded archive SHA-256 mismatch" }

雜湊相同只能說明這兩份歸檔位元組一致,不證明基準本身未被竄改,也不能替代 7z t、解壓、git fsck、refs 比對和實際複製。基準清單最好獨立保留並防止被同一故障一起竄改;行動硬碟備份完成後要真的斷開。這樣把「密文傳輸正確」和「程式碼歷史可復原」分成不同的檢查,才不會在出事時把一個 [OK] 當成萬能保險。

我還需要的是能 push 的密文 Git,而不只是雲端同步

原先的日常 git push、git pull 是 Windows / Mac 對本地明文 bare Central 進行;Cryptomator 則把 mirror 副本轉成密文檔案,再交給 OneDrive / Google Drive 傳輸。兩個功能都很重要,但後者沒有提供 Git 網頁介面、遠端分支協作,不能寫成「我已經即時把加密 Git 物件推到 GitHub」。

要讓提交後的程式碼儘快以密文送到異地,必須在離開可信設備前加密、確認金鑰能單獨恢復、測試斷線重試與並發推送,而且不能把強制覆寫當作普通成功。這只處理已提交的 Git 歷史,不包含未提交工作區。

值得研究的是 git-remote-gcrypt:它透過 GnuPG 表示加密的遠端 Git 倉庫,也能掛接標準 Git 傳輸。問題是官方說明指出,使用一般 Git URL(例如雲端 Git 平台)時,每次 push 需要重傳整份歷史;而且有非快進保護不足的已知問題。相對地,rsync 傳輸方向支援增量,卻不是一般 GitHub Private 倉庫的使用方式。這些都需要先用隔離倉庫實測,不能當成已部署成果。

單靠 git-crypt 加密部分檔案也不等價:檔名、提交訊息和部分 Git 中繼資料仍可見。另一條路是維持本地 Git 協作,讓加密的異地備份自動、頻繁執行;這能改善備份延遲,但仍無法讓 GitHub 的 PR、diff 或雲端 Agent 直接理解密文。

Git 災難復原:從哪份副本恢復程式碼和歷史

原先給自己留了兩套復原路線。遇到事故時,我會先判斷手裡哪一份倉庫還保有目標 refs,而不是立刻重建一套空專案:

需要恢复 Git 项目
  ├─ 开发机还有完整的提交 / 分支 / 标签
  │    └─ 新建空 bare Central → 检查 refs → 推送本地分支与 tags
  ├─ 开发机不完整,但 E 盘 mirror 仍能读取
  │    └─ fsck → 查看 refs → clone 出工作区,再比对历史
  └─ 在线副本都不可用
       └─ 找离线或异地历史归档 → 校验 SHA-256 → 7z 测试与解压
                                     → git fsck → refs 对照 → clone

這個判斷也提醒了我:git push --all 只推送本地分支。不能因為目前工作區能啟動,就預設過去所有開發機的全部分支都在這一份裡。

一種是 Central 丟了,但還有完整的開發倉庫。此時可以重新新增一個裸倉庫,再從開發機推回需要的分支和標籤:

# 在新 Central 的可写位置创建空裸仓库
git init --bare "/path/to/recovered/project.git"

# 在幸存的开发仓库内执行
git remote add recovered "/path/to/recovered/project.git"
git push recovered --all
git push recovered --tags

這裡必須說明:--all 推送的是這台開發機的本地分支,不是所有 refs/remotes/*。如果另一台機器曾有獨立分支,而這台開發機沒有保存為本地分支,不能聲稱 git push --all 會替你憑空找回來。正式切換 Central 前,需要對照現存倉庫的 refs 清單,必要時從其他倖存副本補齊。

另一種更極端:本地開發機和 Central 都不可用了。復原的入口就變成行動硬碟或雲端硬碟裡的歸檔。順序應該是拿到一個明確日期的備份、完成解密和解壓、檢查裸倉庫物件、核對預期 refs,最後 clone 出工作區。先不要把剛解出來的備份覆蓋掉,更不要第一步就執行帶 --force 或 --prune 的鏡像推送。

# 在解压得到的裸仓库上执行
git --git-dir="/restore/project.git" fsck --full
git --git-dir="/restore/project.git" for-each-ref \
  --format='%(refname) %(objectname)' refs/heads refs/tags

# 确认仓库可读后,再建工作区
git clone "/restore/project.git" recovered-worktree

git fsck 檢查 Git 物件的連通性與有效性;git for-each-ref 能列出分支和標籤對應的物件 ID。兩者一起,至少能回答「包是不是損壞了」「預期的分支和標籤在不在」。但要證明真的可復原,還得檢查檔案內容、依賴、外部資料,以及是否能在新的機器上繼續建構和工作。

後來整理 verify-coldbackup.ps1 時,我把檢查順序固定成一條鏈:7z 完整性測試 → 解密解包 → clone 出工作區 → git fsck 檢查物件 → 對照全部分支和標籤 SHA。只看「壓縮包能打開」遠遠不夠;要確認程式碼真的備住了,就得在獨立目錄完整走一次復原。

順帶說一句,另一條「把上游公開專案鏡像到自己 GitHub 私有倉」的路徑,與這個區域網路 Central 是兩件事。那條路線要處理 GitHub 的受限 refs、分支覆蓋和 --force --prune 的目標刪除風險,不應該把個人開發分支混在鏡像目標裡。具體鏡像問題我已經單獨放在GitHub 公開倉庫自動同步至 Private 鏡像的實踐裡,不在這裡重複展開。官方 git push 文件對 --mirror、--prune 和 refspec 的差異也說得很清楚。

手機開發卡住的地方,正是保留明文邊界的代價

以前 Windows 和 Mac 都持有完整程式碼,我只要在本地完成提交就好。現在只有手機時,有時需要 ChatGPT/Codex 幫忙檢視某段程式碼、修改問題或跑測試;偏偏 Windows 是唯一 Central,關機或網路不可用就無法跨設備操作。

問題不只是 Mac 的本地儲存空間。partial clone、sparse checkout 能省空間,不能決定誰能解密。一般雲端 AI 開發服務必須讀取原始碼才能分析、執行測試或產生 PR;倘若把完整明文放進雲端工作區,就違反我最初的架構原則。

我更傾向讓手機當控制台,明文仍留在可信的自有 Mac/Linux 主機:透過受控的私人網路與 SSH 連線,在受信任設備上執行 Git 和開發工具。這仍是待驗證的方向,不是已經完成的手機端生產系統;必須考慮設備持續在線、金鑰、強認證和權限隔離。如果把原始碼內容傳給遠端 AI 模型,仍可能構成另一條明文出站路徑,不可假裝完全離線。

就算未來能從加密 Git remote 抓回密文歷史,雲端 Agent 也不會因此自動看懂它。我需要分別回答:誰能解密、明文在哪裡執行、手機離線後任務怎麼繼續。密文遠端同步、手機操作與災難復原是三個不同問題。

Git 裸倉庫、SMB 和備份復原的幾個常見問題

為什麼不用 SMB 直接共享普通 Git 工作區?

一般工作倉庫有工作樹,Git 預設拒絕向非裸倉庫中目前檢出的分支直接 push(receive.denyCurrentBranch)。裸倉庫沒有工作樹,適合作為 Central;開發仍在 Win/Mac 各自的工作目錄進行。

Mac 掛載 SMB 後 git pull 失敗,該先檢查什麼?

先確認 ls /Volumes、git remote -v 與共享權限,再檢查 Git 處理程序是否仍在執行。鎖檔可能位於工作倉庫或裸倉庫 refs;未確認程序結束前,不要直接刪除 .lock。跨網路頻繁操作可考慮 Git over SSH。

git clone --mirror 比普通 git clone 更安全嗎?

--mirror 會同步覆寫 refs,來源刪掉的分支也可能從鏡像消失。避免誤刪要保留不跟著來源更新的歷史歸檔,不是再複製另一份相同鏡像。

OneDrive / Google Drive 顯示「已同步」,就算通過雜湊校驗了嗎?

不算。客戶端顯示同步、本地密文目錄清單吻合、重新下載後 SHA-256 一致,以及解密、git fsck、refs 核對和實際 clone,都是不同檢查。舊腳本的 [OK] 只對它檢查的本地目標與基準成立。

為何不直接用 GitHub Private?復原要多久演練一次?

GitHub Private 的權限控制不代表雲端沒有明文原始碼,不符合我對核心商業程式碼的要求。首次設定或調整加密備份後至少獨立復原一次,確認金鑰、Git 物件與 refs,再實際 clone;遺失金鑰就無法解密。

參考資料:Git init · Git clone · Git remote · Git push · Git fsck · Robocopy · Get-FileHash · Cryptomator 安全邊界 · 7-Zip 官方 7z 格式