「Diffs」是變更的通用語。它們是緊湊的敘述,告訴你一件事物的兩個版本之間 什麼移動了——原始碼、散文、資料集——而無需 強迫你重讀所有內容。在那幾個符號(+、 -、@@)背後,存在著一個由演算法、啟發式方法和格式組成的深層堆疊, 它們平衡了最佳性、速度和人類理解。本文是diffs從演算法到工作流程的實踐之旅:它們如何計算,如何格式化, 合併工具如何使用它們,以及如何為更好的審查調整它們。在此過程中,我們將把 主張建立在主要來源和官方文件上——因為像空白是否 計數這樣的小細節真的非常重要。
形式上,diff描述了一個最短編輯腳本(SES),用於透過插入和刪除將「舊」序列轉換為「新」序列(有時還包括替換, 可以建模為刪除+插入)。在實踐中,大多數面向程式設計師的diff是面向行的,然後為了可讀性可選擇地細化為單詞或字元。 規範的輸出是 上下文 和 統一 格式;後者——你通常在程式碼審查中看到的——用一個簡潔的標頭 和「hunks」來壓縮輸出,每個hunk都顯示了變更周圍的上下文鄰域。統一格式 透過-u/--unified選擇,是補丁的事實標準; patch通常受益於上下文行 來穩健地應用變更。
GNU diff手冊列出了當你想要更少 噪音和更多信號時可以使用的開關——忽略空格、擴展定位點以進行對齊,或者要求一個 「最小」編輯腳本,即使它更慢 (選項參考)。這些選項不會改變兩個檔案不同的意義;它們改變的是 演算法搜尋更小腳本的積極程度以及結果如何呈現給人類。
大多數文字diffs都建立在 最長公共子序列(LCS) 的抽象之上。經典的動態規劃在O(mn)時間和空間內解決LCS,但 對於大檔案來說太慢且耗費記憶體。 Hirschberg的演算法 展示了如何使用分治法在線性空間(仍然是 O(mn)時間)內計算最佳對齊,這是一種影響了 實用diff實現的基本節省空間技術。
在速度和品質方面,突破是 Eugene W. Myers於1986年提出的演算法,它在O(ND)時間(N ≈ 總行數,D ≈ 編輯距離) 和近線性空間內找到一個SES。Myers在一個「編輯圖」中建模編輯,並沿著最遠到達的邊界前進,產生的結果在行diff設定中既快又接近 最小。這就是為什麼「Myers」在許多工具中仍然是預設設定。
還有 Hunt–Szymanski 家族,當匹配位置很少時,它會加速LCS(透過預先索引匹配並追蹤 遞增子序列),並且在歷史上與 早期的diff變體有關。 這些演算法揭示了權衡:在匹配稀疏的輸入中,它們可以 以亞二次方式運行。有關連接理論和實現的從業員概述,請參閱 Neil Fraser的筆記。
Myers旨在實現最小的編輯腳本,但「最小」≠「最可讀」。重新排序或複製的大塊 可能會欺騙純粹的SES演算法,使其產生尷尬的對齊。進入 patience diff,歸功於Bram Cohen:它錨定在獨特的、低頻的行上以 穩定對齊,通常產生人類認為更清晰的diffs——尤其是在有 移動函數或重組塊的程式碼中。許多工具透過「耐心」選項(例如,diff.algorithm)來暴露這一點。
直方圖diff 透過頻率直方圖擴展了耐心,以更好地處理低出現率的元素,同時 保持快速(在 JGit中普及)。如果你曾發現--histogram為嘈雜的檔案產生更清晰的hunks, 那是設計使然。在現代Git上,你可以全域或每次呼叫時選擇演算法:git config diff.algorithm myers|patience|histogram或 git diff --patience。
行diffs簡潔但可能掩蓋微小的編輯。 詞級diffs (--word-diff)在不淹沒審查的情況下為行內變更著色, 非常適合散文、長字串或單行程式碼。
重新格式化後,空白可能會淹沒diffs。Git和GNU diff都允許你 忽略空白變更 在不同程度上,並且 GNU diff的空白選項 (-b, -w, -B)在格式化程式執行時有幫助;你將看到邏輯編輯而不是對齊噪音。
當程式碼整體移動時,Git可以 高亮移動的塊 使用--color-moved,在視覺上將「移動」與「修改」分開,這有助於 審查員審計移動是否隱藏了意外的編輯。透過 diff.colorMoved使其持久化。
diff3一個雙向diff精確比較兩個版本;它無法判斷雙方是否 編輯了相同的基礎行,因此經常過度衝突。 三向合併(由現代VCS使用)計算從共同祖先到每一方的diffs,然後協調兩個變更集。這極大地 減少了虛假衝突並提供了更好的上下文。這裡的經典演算法核心是 diff3,它將變更從「O」(基礎)合併到「A」和「B」,並在必要時標記衝突 。
學術和工業界的工作繼續形式化和改進合併的正確性;例如, 經過驗證的三向合併提出了無衝突的語義概念。在日常 Git中,現代的 ort合併策略 建立在diffing和重命名檢測之上,以產生更少意外的合併。對於使用者來說, 關鍵提示是:在衝突中顯示基礎行,使用 merge.conflictStyle=diff3,並經常整合以保持diffs小。
傳統的diffs無法「看到」重命名,因為內容尋址將檔案視為blobs; 它們只看到一個刪除和一個新增。 重命名檢測啟發式 透過比較新增/刪除對之間的相似性來彌合這一差距。在Git中,透過-M/--find-renames[=<n>](預設為~50% 相似性)啟用或調整。對於更嘈雜的移動,降低它。你可以 限制候選比較 使用diff.renameLimit(以及合併期間的 merge.renameLimit )。要跨重命名追蹤歷史,請使用 git log --follow -- <path>。最近的Git還執行 目錄重命名檢測 以在合併期間傳播資料夾移動。
改變的不僅僅是文字。對於二進位檔案,你通常想要 增量編碼——發出複製/新增指令以從 來源重建目標。 rsync演算法 開創了使用滾動校驗和在網路上對齊塊來高效進行遠端差異比較的先河,從而最小化了頻寬。
IETF標準化了一個通用的增量格式, VCDIFF (RFC 3284),描述了ADD、COPY和RUN的位元組碼, 像 xdelta3 這樣的實現用它來進行二進位補丁。對於可執行檔案上的緊湊補丁, bsdiff 通常透過後綴陣列和壓縮產生非常小的增量;當補丁大小 佔主導地位且生成可以離線進行時選擇它。
當你需要面對並行編輯或稍微錯位的上下文時進行穩健的補丁——想想編輯器或協作系統——考慮 diff-match-patch。它將Myers風格的差異比較與 Bitap 模糊匹配結合起來,以找到近似匹配並「盡力而為」地應用補丁,外加預diff加速 和後diff清理,這些清理以犧牲一點點最小性為代價換取更美觀的人類輸出。關於如何在連續同步循 環中結合diff和模糊補丁,請參閱Fraser的 差分同步。
CSV/TSV上的行diffs很脆弱,因為一個單元格的變更可能看起來像整行編輯。 表格感知的diff工具(daff) 將資料視為行/列,發出針對特定單元格的補丁,並渲染 使新增、刪除和修改顯而易見的視覺化(參見 R小插圖)。對於快速檢查,專門的CSV差異比較器可以突顯單元格間的變更和類型 轉換;它們在演算法上並不奇特,但它們透過 比較你實際關心的結構來增加審查信號。
--patience ,或者對於重複文字上的快速、可讀的diffs,請嘗試 --histogram 。使用 git config diff.algorithm …設定預設值。-b、 -w、 --ignore-blank-lines)以專注於實質性變更。在Git之外,請參閱 GNU diff的空白控制。--word-diff 有助於長行和散文。--color-moved (或 diff.colorMoved)將「移動」與「修改」分開。-M 或調整相似性閾值(-M90%、-M30%)以捕捉 重命名;請記住預設值約為50%。對於深層樹,設定 diff.renameLimit。git log --follow -- <path>。合併計算兩個diffs(BASE→OURS, BASE→THEIRS)並嘗試將兩者都應用於BASE。 像 ort 這樣的策略在規模上協調這一點,包括重命名檢測(包括目錄規模的移動)和 最小化衝突的啟發式方法。當衝突發生時, --conflict=diff3 用基礎上下文豐富標記,這對於理解 意圖是無價的。Pro Git關於 進階合併 的章節介紹了解決模式,Git的文件列出了像 -X ours和-X theirs這樣的旋鈕。為了在重複的衝突上節省時間,啟用 rerere 來記錄和重播你的解決方案。
如果你正在透過網路同步大型資產,你更接近 rsync 世界而不是本地diff。Rsync計算滾動校驗和以遠端發現匹配的塊, 然後只傳輸必要的內容。對於打包的增量, VCDIFF/xdelta 為你提供了一個標準的位元組碼和成熟的工具;當你同時控制編碼器和 解碼器時選擇它。如果補丁大小至關重要(例如,無線韌體), bsdiff 在建置時用CPU/記憶體換取非常小的補丁。
像 diff-match-patch 這樣的函式庫接受,在現實世界中,你正在打補丁的檔案可能已經發生了變化。透過將 一個堅實的diff(通常是Myers)與模糊匹配 (Bitap)和可設定的清理規則相結合,它們可以找到正確的位置來應用補丁並 使diff更具可讀性——這對於協作編輯和同步至關重要。
-u/-U<n>)緊湊且對補丁友好;它們是程式碼審查和CI所期望的 (參考)。git diff文件; GNU空白選項)。diff3 風格不那麼混亂; ort 加上重命名檢測減少了流失; rerere 節省時間。因為肌肉記憶很重要:
# 顯示帶有額外上下文的標準統一diff
git diff -U5
diff -u -U5 a b
# 為長行或散文獲取詞級清晰度
git diff --word-diff
# 重新格式化後忽略空白噪音
git diff -b -w --ignore-blank-lines
diff -b -w -B a b
# 在審查期間高亮移動的程式碼
git diff --color-moved
git config --global diff.colorMoved default
# 用重命名檢測馴服重構並跨重命名追蹤歷史
git diff -M
git log --follow -- <file>
# 為可讀性優先選擇演算法
git diff --patience
git diff --histogram
git config --global diff.algorithm patience
# 在衝突標記中查看基礎行
git config --global merge.conflictStyle diff3偉大的diffs與其說是為了證明最小性,不如說是為了在最小的認知成本下 最大化審查員的理解。這就是為什麼 生態系統發展了多種演算法(Myers、patience、histogram)、多種表示方法 (統一、詞diff、顏色移動)和領域感知工具(用於表格的daff、用於二進位檔案的xdelta/bsdiff) 。學習權衡,調整旋鈕,你將花更多的時間來推理意圖,而不是從紅綠線中重新組裝上下文。
diff3 • 空白選項Diff 是一種在版控系統中使用的工具或功能,用以突顯檔案的兩個版本或實例之間的差異。它通常被用來跟蹤檔案隨時間所做的變更或更新。
diff 會逐行比較兩個檔案。它會掃描並將第一個檔案中的每一行與第二個檔案中的對應行配對,並注意所有的重要差異,如添加、刪除或更改。
Patch 是一個包含兩個檔案差異的檔案,由 diff 工具產生。它可能被應用到檔案的某個版本中,通過 'patch' 指令更新到新的版本。
統一差異是一種 diff 檔案格式,將變更展示在適合文本檔案的文件格式中 。它顯示原始檔案中的刪除項,前綴為 '-',而添加到原始檔案的項目則前綴為 '+'。
Diff 在版控系統中是關鍵的,因為他們讓團隊能夠跟蹤到隨時間對檔案所做的變更。這種跟蹤使得維護一致性,防止重複工作,找出錯誤或不一致,以及有效管理多個檔案的版本變得更容易。
最長公共子序列(LCS)算法是 diff 工具常用的一種方式,用來找出在原始檔案和修改過的檔案中從左到右出現的最長字元序列。這個算法有助於識別兩個檔案的主要相似處和差異。
大部分基本的 diff 工具只能比較文字檔案。然而,一些專門的 diff 工具則被設計成能比較二進制檔案,並以可讀的格式顯示差異。
一些最流行的 diff 工具包括 GNU diff、DiffMerge、KDiff3、WinMerge(Windows)和 FileMerge(Mac)。許多整合開發環境(IDE)也包括內建的 diff 功能。
在 Git 中,您可以使用 `git diff` 指令後接您要比較的檔案兩個版本來創建 diff。輸出將顯示兩個檔案間的差異。
是的,許多 diff 工具具有比較目錄以及單一檔案的能力。當比較具有多個檔案的大型專案的版本時 ,這個特性可以特別有用。