這些合併資訊從何而來?

本文經原始位置 http://blogs.collab.net/subversion/where-did-that-mergeinfo-come-from 許可轉載。已移除或更新失效連結。

作者:Paul Burba

張貼 2009-11-19

合併追蹤的一個領域會造成混淆,那就是 svn:mergeinfo 屬性是如何設定的。在許多常見的使用案例中,只會設定合併目標上的 mergeinfo。然而,有許多情況會建立或更新目標下的 mergeinfo,也就是所謂的「子樹 mergeinfo」。這些情況通常會讓使用者懷疑合併過程中是否出錯,以及是否應該提交這些子樹 mergeinfo 變更。這種混淆會加劇,因為給定的子樹可能沒有變更,除了 svn:mergeinfo 屬性本身的變更之外。

這篇文章說明了在合併過程中建立或更新子樹 mergeinfo 的一些常見情況。

預先存在的子樹 Mergeinfo

到目前為止,子樹合併資訊變更最常見的原因是在合併之前,您的工作副本合併目標具有子樹合併資訊。

例如,我們有一個從主幹建立的分支 B1.0,它有一些子樹合併資訊

1.6.6>svn propget svn:mergeinfo branches/B1.0 -vR
「branches/B1.0/A/D/H/psi」的屬性
  svn:mergeinfo
    /trunk/A/D/H/psi:4-10

我們將修訂版 12 從主幹合併到 B1.0,而合併的輸出顯示新增了一個檔案

1.6.6>svn merge ^/trunk branches/B1.0 -c12
— 將 r12 合併到「branches/B1.0」中
A    branches/B1.0/A/C/nu

但是,當我們檢查工作副本的狀態時,我們會看到一些屬性變更

1.6.6>svn status
 M      branches/B1.0
A  +    branches/B1.0/A/C/nu
 M      branches/B1.0/A/D/H/psi

僅檢查 B1.0 的差異,我們會看到已新增合併資訊,說明我們剛剛執行的合併,也就是從主幹來的 r12。這很有道理,因為這正是我們剛剛執行的動作!

1.6.6>svn diff –depth empty branches/B1.0

屬性變更:branches/B1.0
___________________________________________________________________
已新增:svn:mergeinfo
   已合併 /trunk:r12

但是 A/D/H/psi 的屬性變更呢?

1.6.6>svn diff branches/B1.0/A/D/H/psi

屬性變更:branches/B1.0/A/D/H/psi
___________________________________________________________________
已修改:svn:mergeinfo
   已合併 /trunk/A/D/H/psi:r12

喔,這是合併資訊變更。但是等等,我們碰巧知道 r12 沒有影響 psi,而且我們檢查記錄檔來確認這一點

1.6.6>svn log -v -r12
————————————————————————
r12 | pburba | 2009-11-17 18:21:47 -0500 (週二,2009 年 11 月 17 日) | 1 行
已變更路徑
   A /trunk/A/C/nu.c

那麼 psi 上的合併資訊為何會變更?簡短的答案是,對於所有 1.6.x 之前的 Subversion 版本,子樹合併資訊總是會更新以描述合併,而不管子樹是否受到合併影響。這是為了支援更容易的合併資訊省略*,以及在單一合併範圍和多個個別合併等於相同結果之間的合併資訊一致性(例如 svn merge ^/trunk branch -r 100:103 或 svn merge ^/trunk branch -c101、svn merge ^/trunk branch -c102 和 svn merge ^/trunk branch -c103 都會產生相同的合併資訊)。

早期,我們在開發社群中了解到此行為所造成的混淆,大於其所帶來的些微好處。當然,只有受到特定合併影響的子樹才應該更新其合併資訊。在這個觀點上幾乎沒有異議,而且從編碼的角度來看,這個變更幾乎簡單到令人發笑。不幸的是,進行這個變更對後續合併造成了一些無法預見的後果。我不會在此深入探討細節,但其中一個後果是合併效能大幅降低的可能性。

好消息是,我們最終能夠保留(而且在許多情況下改善)合併效能,同時停止記錄未受合併影響的子樹上的合併資訊變更。壞消息是,這些變更很廣泛,而且不會回溯移植到 1.5.x 或 1.6.x 版本,而只會在 1.7 中提供。

在此同時,如果您想知道是否應該提交這些子樹合併資訊變更,您應該這麼做。如果您選擇在提交合併之前還原它們,您將無法再使用合併 –reintegrate 選項,更糟的是,對於長期分支,您將在後續合併中遭受效能損失,而這很容易達到無法忍受的程度。

屬性差異

svn:mergeinfo 屬性是版本化屬性,就像任何其他版本化屬性一樣,合併套用的差異可能包括該屬性的變更或新增。

例如,我們可能有一個完全沒有 mergeinfo 的分支工作副本

1.6.6>svn propget svn:mergeinfo branches/B3.0 -vR

我們從主幹中挑選單一版次

1.6.6>svn merge ^/trunk branches/B3.0 -c21
— 將 r21 合併到 'branches/B3.0'
UU   branches/B3.0/A/D/gamma

注意第二欄中的「U」嗎?屬性變更是 r21 的一部分,我們可以在檢查工作副本狀態時看到

1.6.6>svn status
 M      branches/B3.0
MM      branches/B3.0/A/D/gamma

由於這是本部落格的主題,因此毫不意外地,傳入的屬性變更是對 svn:mergeinfo 屬性

1.6.6>svn propget svn:mergeinfo branches/B3.0 -vR
「branches/B3.0」上的屬性
  svn:mergeinfo
    /trunk:21
「branches/B3.0/A/D/gamma」上的屬性
  svn:mergeinfo
    /branches/B1.0/A/D/gamma:20
    /trunk/A/D/gamma:21

如果我們檢查合併來源的 diff,我們可以看到在 r21 中已將 svn:mergeinfo 屬性新增到 trunk/A/D/gamma

1.6.6>svn diff -r20:21 ^/trunk
索引:A/D/gamma
===================================================================
— A/D/gamma  (版次 20)
+++ A/D/gamma  (版次 21)
@@ -1 +1 @@
- 新的 gamma 檔案
+nc

A/D/gamma 上的屬性變更
___________________________________________________________________
已新增:svn:mergeinfo
   已合併 /branches/B1.0/A/D/gamma:r20

類似這樣的狀況可能以各種方式發生,但通常涉及子樹合併(即針對分支根目錄以下某個目標的合併),稍後再將其合併為整個分支合併(即針對分支根目錄的合併)到其他分支。

遺失的子樹

其餘案例有一個共同點,會記錄子樹 mergeinfo,因為合併目標的部分「遺失」。它們可能由於許多不同原因而遺失,但產生的 mergeinfo 非常類似。

淺層工作副本

如果您合併到淺層工作副本(即設定為無限大以外的深度),您將會看到建立子樹合併資訊。

例如,假設我們在深度立即檢查出一個分支工作副本

1.6.6>svn checkout %ROOT_URL%/branches/B2.0–depth immediates
A    B2.0/A
已檢出版本 13。

在這種情況下,分支沒有任何合併資訊

1.6.6>svn propget svn:mergeinfo -vR B2.0

現在,我們合併主幹的所有可用版本。由於淺層檢出導致工作副本的一部分遺失,我們得到幾個樹狀衝突

1.6.6>svn merge ^/trunk B2.0
— 將 r4 到 r13 合併到 'B2.0/A'
   C B2.0/A/mu
   C B2.0/A/C
   C B2.0/A/D
衝突摘要
  樹狀衝突:3

我們也得到新增到 B2.0/A 的新合併資訊

1.6.6>svn status B2.0
 M      B2.0
 M      B2.0/A
!     C B2.0/A/mu
      >   本地遺失,合併時有傳入編輯
!     C B2.0/A/C
      >   本地刪除,合併時有傳入編輯
!     C B2.0/A/D
      >   本地刪除,合併時有傳入編輯

1.6.6>svn propget svn:mergeinfo -vR B2.0
「B2.0」的屬性
  svn:mergeinfo
    /trunk:4-13
「B2.0/A」的屬性
  svn:mergeinfo
    /trunk/A:4-13*

發生這種情況是因為 svn:mergeinfo 屬性是可繼承的。由於我們的工作副本中實際上沒有 B2.0/A 的任何子項,因此我們實際上尚未將 r3:13 從 trunk 合併到它們。B2.0/A 上不可繼承的 mergeinfo 範圍「/trunk/A:4-13*」實際上表示「我們已將 r3:13 從 trunk 合併到此深度,但不會更深入」。

如果 B2.0/A 上沒有這個不可繼承的 mergeinfo,如果我們解決樹狀結構衝突並提交此合併,然後另一位使用者檢出分支的完整深度工作副本,對他們來說,r3:13 似乎已完全合併到分支,這顯然不是事實。

如果您總是合併到完整深度工作副本,則可以輕鬆避免這種子樹 mergeinfo。如果您需要合併到淺層工作副本,請記住此行為,並務必提交所有子樹 mergeinfo。

淺層合併

與淺層工作副本密切相關的是淺層合併。在此,不是合併目標的深度很淺,而是合併本身的深度很淺,如合併命令的 –depth 選項所指定(您甚至可以將淺層合併合併到淺層工作副本中)。

例如,我們檢出分支的完整深度工作副本

1.6.6>svn checkout %ROOT_URL%/branches/B2.0/branches/B2.0 B2
A    B2/A
A    B2/A/B
A    B2/A/B/lambda
A    B2/A/B/E
A    B2/A/B/E/alpha
A    B2/A/B/E/beta
A    B2/A/B/F
A    B2/A/mu
A    B2/A/C
A    B2/A/D
A    B2/A/D/gamma
A    B2/A/D/G
A    B2/A/D/G/pi
A    B2/A/D/G/rho
A    B2/A/D/G/tau
A    B2/A/D/H
A    B2/A/D/H/chi
A    B2/A/D/H/omega
A    B2/A/D/H/psi
已檢出版本 13。

淺層合併「只會」下降到請求的深度,在本例中為目標的直接子節點

1.6.6>svn merge ^/trunk/A B2/A –depth immediates
— 將 r4 到 r13 合併到「B2/A/mu」中
U    B2/A/mu

如同合併到淺層工作副本中,非繼承式合併資訊會設定在合併到的限制範圍

1.6.6>svn status B2
 M      B2
 M      B2/A/B
M       B2/A/mu
 M      B2/A/C
 M      B2/A/D

1.6.6>svn propget svn:mergeinfo -vR B2
「B2」的屬性
  svn:mergeinfo
    /trunk:4-13
「B2/A/B」的屬性
  svn:mergeinfo
    /trunk/A/B:4-13*
「B2/A/C」的屬性
  svn:mergeinfo
    /trunk/A/C:4-13*
「B2/A/D」的屬性
  svn:mergeinfo
    /trunk/A/D:4-13*

同樣地,這可以用執行完整的 –depth infinity 合併來避免。

已切換的子樹

如果您在合併目標中切換了子樹,這些子樹的處理方式幾乎與這些子樹在深度為空時相同。主要的差異在於合併會在已切換子樹的根目錄上記錄正常的繼承式合併資訊,因為從該點向下,您確實擁有完整的作業副本(排除已切換子樹中的已切換子樹,或淺層已切換子樹等)。

授權限制

「遺失子樹」的另一種情況是,當您合併到您沒有完全讀取權限的工作副本時。您將再次看到在遺失的子樹周圍新增不可繼承的合併資訊。我懷疑這是一種罕見的情況,但如果您在合併後突然看到子樹合併資訊出現,且上述原因均不適用,這可能是原因。

* 如果您不熟悉合併資訊省略,您可以在此處閱讀相關資訊 http://www.open.collab.net/community/subversion/articles/merge-info.html。

關於作者

Paul Burba 是 Apache 軟體基金會 Subversion 專案的提交者,且已在 Subversion 上工作九年。他擔任 Collabnet 的軟體工程師,在家鄉新罕布夏州工作,且當他沒有在編寫程式碼時,通常可以找到他與姪子一起滑雪、與朋友一起騎登山車或與妻子一起旅行。在遙遠的過去,Paul 從新罕布夏大學畢業,取得商業學位。最近,他從波士頓大學取得電腦科學碩士學位。