[您也可以查看 單頁版本 的文件。]

編碼和提交慣例

程式碼模組化與介面可見度

Subversion 的程式碼和標頭檔案沿著幾個關鍵路線區分:特定於程式庫與程式庫間;公開與私有。此區分主要是因為我們專注於適當的模組化和程式碼組織,但也因為我們身為廣泛採用的公開 API 的提供者和維護者的承諾。當您在 Subversion 中撰寫新函式時,您需要仔細考慮這些事情,並在進行時問自己一些問題

「我的新程式碼的使用者是否只限於單一程式庫中的特定原始程式碼檔案?」如果是,您可能需要在同一個原始程式碼檔案中使用靜態函式。

「我的新函式是否為此程式庫中的其他原始程式碼會需要使用,但程式庫*外部*不會使用的類型?」如果是,您需要使用非靜態、雙底線命名的函式(例如 svn_foo__do_something),其原型在適當的特定於程式庫的標頭檔案中。

「我的程式碼是否需要從不同的程式庫存取?」在此,您需要回答一些其他問題,例如「我的程式碼是否應該存放在我打算放置它的原始程式庫中,或者它應該存放在更通用的公用程式庫中,例如 libsvn_subr?」無論如何,您現在正在考慮使用程式庫間標頭檔案。但在您決定使用哪一個之前,請參閱下一個問題...

「我的程式碼是否具有乾淨、可維護的 API,可以合理地永久維護,並為 Subversion 公開 API 提供價值?」如果是,您將在 subversion/include/ 內部立即將原型新增到公開 API。如果不是,請再次確認您的計畫——也許您尚未選擇抽象功能的最佳方式。但有時程式庫確實需要共用某些函式,而這些函式對 Subversion 本身以外的其他軟體來說可能沒有用。在這種情況下,請使用 subversion/include/private/ 中的私有標頭檔案。

編碼風格

Subversion 使用 ANSI C,並遵循 GNU 編碼標準,但我們不會在函式名稱和其參數清單的開啟括號之間放置空格。Emacs 使用者可以載入 svn-dev.el 以取得正確的縮排行為(如果適當地設定 `enable-local-eval`,這裡的大部分原始檔都會自動載入)。

請閱讀 https://gnu.dev.org.tw/prep/standards.html 以取得 GNU 編碼標準的完整說明。以下是展示最重要的格式化指南的簡短範例,包括我們不使用空格-在-參數清單-括號之前的例外

   char *                                     /* func type on own line */
   argblarg(char *arg1, int arg2)             /* func name on own line */
   {                                          /* first brace on own line */
     if ((some_very_long_condition && arg2)   /* indent 2 cols */
         || remaining_condition)              /* new line before operator */
       {                                      /* brace on own line, indent 2 */
         arg1 = some_func(arg1, arg2);        /* NO SPACE BEFORE PAREN */
       }                                      /* close brace on own line */
     else
       {
         do                                   /* format do-while like this */
           {
             arg1 = another_func(arg1);
           }
         while (*arg1);
       }
   }

一般來說,即使您確定運算子優先順序,也要大方地使用括號,並願意加入空格和換行符號以避免「程式碼緊縮」。不要太擔心垂直密度;讓程式碼易於閱讀比在螢幕上塞入額外一行更重要。

使用分頁符號

我們在程式碼和純文字散文檔中使用分頁符號(Ctrl-L 字元,ASCII 12)作為區段界線。每個區段都以分頁符號開始,緊接在分頁符號之後的是區段標題。

這有助於使用 Emacs 分頁指令的人,例如 `pages-directory` 和 `narrow-to-page`。這樣的人並不像您想像的那麼稀少,如果您想成為其中一員,請將 (require 'page-ext) 加入您的 .emacs,然後輸入 C-x C-p C-h。

錯誤訊息慣例

錯誤訊息適用下列慣例

  • 僅在有資訊要新增到 subversion/include/svn_error_codes.h 中找到的常見錯誤訊息時,才提供特定錯誤訊息。

  • 訊息以大寫字母開頭。

  • 嘗試讓訊息保持在 70 個字元以下。

  • 不要以句點(「。」)結尾錯誤訊息。

  • 錯誤訊息中不要包含換行字元。

  • 引用資訊時使用單引號(例如:「'some info'」)。

  • 錯誤訊息中不要包含發生錯誤的函式名稱。如果使用「--enable-maintainer-mode」組態旗標編譯 Subversion,它會自行提供此資訊。

  • 在錯誤字串中包含路徑或檔案名稱時,請務必加上引號(例如:「找不到 '/path/to/repos/userfile'」)。

  • 在錯誤字串中包含路徑或檔案名稱時,請務必在包含之前使用 svn_dirent_local_style() 將其轉換(因為傳遞至 Subversion API 的路徑假設為 正規形式)。

  • 不要使用 Subversion 特定的縮寫(例如:使用「儲存庫」而非「repo」,使用「工作副本」而非「wc」)。

  • 如果您想要在錯誤中加入說明,請在說明後加上冒號和說明,如下所示

           "Invalid " SVN_PROP_EXTERNALS " property on '%s': "
           "target involves '.' or '..'".
         
  • 建議或其他補充事項可以在分號後加上,如下所示

           "Can't write to '%s': object of same name already exists; remove "
           "before retrying".
         
  • 請盡量遵守這些慣例,因此請避免使用其他分隔符號(例如「--」等)來區分錯誤訊息的不同部分。

另請參閱 在地化

APR 池使用慣例

(這假設您已經大致了解 APR 池的運作方式;有關詳細資訊,請參閱 apr_pools.h。)

使用 Subversion 函式庫的應用程式必須在呼叫任何 Subversion 函式之前呼叫 apr_initialize()。

Subversion 的一般池使用策略可以歸納為兩個原則

  1. 建立池的呼叫層級是清除或銷毀該池的唯一位置。

  2. 在進行無限次數的迭代時,請在進入迭代之前建立一個子池,在迴圈內使用它,並在每次迭代開始時清除它,然後在迴圈結束後銷毀它,如下所示

             apr_pool_t *iterpool = svn_pool_create(scratch_pool);
    
             for (i = 0; i < n; ++i)
             {
               svn_pool_clear(iterpool);
               do_operation(..., iterpool);
             }
    
             svn_pool_destroy(iterpool);
           

為了支援上述規則,我們使用以下池名稱作為慣例,以表示各種池的生命週期

  • result_pool:應在其中配置函數輸出的池。結果池宣告務必出現在函數引數清單中,且絕不可出現在區域區塊內。(但並非所有函數都需要或具有結果池。)

  • scratch_pool:應在其中配置所有函數區域資料的池。此池也由呼叫者提供,呼叫者可在控制權回傳給它時選擇立即清除此池。

  • iterpool反覆運算池,用於迴圈內部,如上述範例所示。

(注意:部分舊有程式碼使用單一 pool 函數引數,同時作為結果池和 scratch 池。)

透過對迴圈受限資料使用 iterpool,您可以確保在函數從迴圈內部突然回傳時(例如,由於錯誤),記憶體外洩為 O(1) 而不是 O(N)。這就是為什麼您不應為在整個函數中持續存在的資料建立子池,而應使用呼叫者傳入的池。當呼叫者的池被清除或銷毀時,該記憶體將會被回收。如果呼叫者在迴圈中呼叫被呼叫者,則相信呼叫者會在每次反覆運算中清除池。相同的邏輯會傳播到整個呼叫堆疊。

您使用的池也有助於程式碼的讀者了解物件的生命週期。給定的物件是否僅在迴圈的一次反覆運算期間使用,或者需要持續到迴圈結束後?例如,池選擇會顯示出此程式碼中發生了哪些事

      apr_hash_t *persistent_objects = apr_hash_make(result_pool);
      apr_pool_t *iterpool = svn_pool_create(scratch_pool);

      for (i = 0; i < n; ++i)
      {
        const char *intermediate_result;
        const char *key, *val;
        
        svn_pool_clear(iterpool);
        SVN_ERR(do_something(&intermediate_result, ..., iterpool));
        SVN_ERR(get_result(intermediate_result, &key, &val, ...,
                           result_pool));
        apr_hash_set(persistent_objects, key, APR_HASH_KEY_STRING, val);
      }

      svn_pool_destroy(iterpool);
      return persistent_objects;

除了部分舊有程式碼(在這些原則尚未完全了解之前撰寫),Subversion 中幾乎所有池使用都遵循上述準則。

其中一種此類的傳統模式是傾向於在池中配置一個物件,將池儲存在物件中,然後釋放該池(直接或透過 close_foo() 函式)以銷毀物件。

例如

   /*** Example of how NOT to use pools.  Don't be like this. ***/

   static foo_t *
   make_foo_object(arg1, arg2, apr_pool_t *pool)
   {
      apr_pool_t *subpool = svn_pool_create(pool);
      foo_t *foo = apr_palloc(subpool, sizeof(*foo));

      foo->field1 = arg1;
      foo->field2 = arg2;
      foo->pool   = subpool;
   }

   [...]

   [Now some function calls make_foo_object() and returns, passing
   back a new foo object.]

   [...]

   [Now someone, at some random call level, decides that the foo's
   lifetime is over, and calls svn_pool_destroy(foo->pool).]

這很誘人,但它違背了使用池的重點,也就是不必太擔心個別配置,而更應關注整體效能和生命週期群組。相反地,foo_t 通常不應有 `pool' 欄位。只要在目前的池中配置與您需要的 foo 物件數量即可,當該池被清除或銷毀時,它們將同時消失。

另請參閱 例外處理 區段,以了解當池被銷毀時,如何清除與池相關聯的資源。

總之

  • 物件不應有自己的池。物件會配置到建構函式呼叫者定義的池中。呼叫者知道物件的生命週期,並會透過池來管理它。

  • 函式不應為其操作建立/銷毀池;它們應使用呼叫者提供的池。同樣地,呼叫者更了解函式將如何使用、多久使用、使用幾次等,因此,它應負責函式的記憶體使用。

    例如,想想一個在緊密迴圈中多次被呼叫的函式。呼叫者會在每次反覆運算中清除暫存池。因此,建立一個內部子池是不必要的,而且可能會造成顯著的負擔;相反地,函式應僅使用傳入的池。

  • 每當發生無界反覆運算時,應使用反覆運算子池。

  • 綜上所述,將儲存區傳遞給每個函數是相當必要的。由於物件不會為自己記錄儲存區,而呼叫者通常會管理記憶體,因此每個函數都需要一個儲存區,而不是依賴於某個隱藏的魔法儲存區。在有限的情況下,物件可能會記錄用於其建構的儲存區,以便它們可以建構子部分,但應仔細檢查這些情況。

另請參閱 追蹤記憶體外洩,以取得診斷儲存區使用問題的秘訣。

APR 狀態碼

請務必使用 APR_STATUS_IS_...() 巨集檢查 APR 狀態碼(APR_SUCCESS 除外),而不是直接比較。這對於移植到非 Unix 平台是必需的。

例外處理

好的,以下是如何在 Subversion 中使用例外。

  1. 例外儲存在 svn_error_t 結構中

    typedef struct svn_error_t
    {
      apr_status_t apr_err;      /* APR error value, possibly SVN_ custom err */
      const char *message;       /* details from producer of error */
      struct svn_error_t *child; /* ptr to the error we "wrap" */
      apr_pool_t *pool;          /* place to generate message strings from */
      const char *file;          /* Only used iff SVN_DEBUG */
      long line;                 /* Only used iff SVN_DEBUG */
    } svn_error_t;
    
  2. 如果您是錯誤的原始建立者,您會執行類似下列的動作

    return svn_error_create(SVN_ERR_FOO, NULL, 
                            "User not permitted to write file");
        

    請注意 NULL 欄位... 表示此錯誤沒有子項,亦即它是最低層的錯誤。

    另請參閱 撰寫錯誤訊息 區段。

    Subversion 內部使用 UTF-8 儲存其資料。這也適用於「訊息」字串。假設 APR 以目前的語言環境傳回其資料,因此 APR 傳回的任何文字在包含在訊息字串中之前都需要轉換為 UTF-8。

  3. 如果您收到錯誤,您有三個選擇

    1. 自行處理錯誤。使用您自己的程式碼,或僅呼叫原始的 svn_handle_error(err)。(此常式會解開錯誤堆疊並列印訊息,將它們從 UTF-8 轉換為目前的語言環境。)

      當您的常式收到它打算忽略或自行處理的錯誤時,請務必使用 svn_error_clear() 清除它。任何時候不清除此類錯誤都構成記憶體外洩

      傳回錯誤的函式不需要初始化其輸出參數。

    2. 向上拋出錯誤,不修改

              error = some_routine(foo);
              if (error)
                return svn_error_trace(error);
              

      其實,更好的方法是使用 SVN_ERR() 巨集,它會執行相同的工作

              SVN_ERR(some_routine(foo));
              
    3. 向上拋出錯誤,將其包覆在新的錯誤結構中,方法是將其包含為「子項」引數

              error = some_routine(foo);
              if (error)
                {
                 svn_error_t *wrapper = svn_error_create(SVN_ERR_FOO, error,
                                                         "Authorization failed");
                 return wrapper;
                }
              

      當然,有一個方便的常式,它會建立一個包覆錯誤,其欄位與子項相同,唯獨自訂訊息例外

              error = some_routine(foo);
              if (error)
                {
                 return svn_error_quick_wrap(error, 
                                             "Authorization failed");
                }
              

      使用 SVN_ERR_W() 巨集可以(而且應該)執行相同的工作

                SVN_ERR_W(some_routine(foo), "Authorization failed");
              

    在案例 (b) 和 (c) 中,重要的是要知道由常式配置的資源與池相關聯,當池被銷毀時,這些資源會自動清除。這表示在傳遞錯誤之前不需要清除這些資源。因此,沒有理由不使用 SVN_ERR() 和 SVN_ERR_W() 巨集。與池相關聯的資源為

    • 記憶體

    • 檔案

      使用 apr_file_open 開啟的所有檔案都會在池清除時關閉。Subversion 在其 svn_io_file_* api 中使用此函式,這表示使用 svn_io_file_* 或 apr_file_open 開啟的檔案會在池清除時關閉。

      有些檔案(例如鎖定檔案)需要在作業完成時移除。APR 有 APR_DELONCLOSE 旗標可供此目的使用。下列函式會建立在池清除時移除的檔案

      • apr_file_open 和 svn_io_file_open(傳遞 APR_DELONCLOSE 旗標時)

      • svn_io_open_unique_file(在其 delete_on_close 中傳遞 TRUE 時)

      如果已使用 svn_io_file_lock 鎖定檔案,則會解除其鎖定。

  4. 當定義 SVN_ERR__TRACING 時,SVN_ERR() 巨集會建立包覆錯誤。這有助於開發人員判斷錯誤原因,而且可以使用 --enable-maintainer-mode 選項搭配 configure 來啟用。

  5. 有時,您只想傳回呼叫函式傳回的任何內容,通常是在您自己的函式結束時。避免直接傳回結果的誘惑

        /* Don't do this! */
        return some_routine(foo);

    相反地,使用 svn_error_trace 元函數來傳回值。這可確保在啟用時堆疊追蹤正確發生。

        return svn_error_trace(some_routine(foo));

安全編碼指南

就像幾乎所有其他程式語言一樣,C 有不良的功能,讓攻擊者能以可預測的方式讓您的程式失敗,通常對攻擊者有利。這些指南的目的在於讓您了解 C 的陷阱,因為它們適用於 Subversion 專案。當檢閱同儕的程式碼時,建議您記住這些陷阱,因為即使是最熟練和偏執的程式設計師也偶爾會犯錯。

輸入驗證是定義合法輸入並拒絕其他所有內容的行為。程式碼必須對所有不受信任的輸入執行輸入驗證。

安全性界線

Subversion 伺服器程式碼中的安全性界線必須識別為此類,因為這能讓稽核人員快速確定界線的品質。安全性界線存在於執行中程式碼有權存取使用者沒有的資訊,或程式碼以高於提出要求使用者的權限執行的地方。此類的典型範例是執行存取控制或設定 SUID 位元的應用程式。

呼叫安全性界線的函數必須包含傳遞引數的驗證檢查。本身為安全性界線的函數應稽核接收的輸入,並在以不適當值呼叫時發出警報。

[### todo:需要一些來自 Subversion 的範例...]

字串操作

使用 apr_strings.h 中提供的字串函數,而不是會寫入字串的標準 C 函式庫函數。APR 函數較安全,因為它們會自動執行界線檢查和目標配置。雖然在理論上可能有一些情況可以使用純粹的 C 字串函數是安全的(例如,當您已經知道來源和目標的長度時),但請還是使用 APR 函數,這樣程式碼會比較不容易出錯且更易於檢閱。

密碼儲存

幫助使用者保密他們的密碼:當客戶端在本地讀取或寫入密碼時,它應該確保檔案的模式為 0600。如果其他使用者可以讀取檔案,客戶端應當退出並顯示一條訊息,告訴使用者由於外洩風險,應變更檔案模式。

堆疊資源的毀損

有些資源需要毀損才能確保應用程式的正確運作。此類資源包括檔案,特別是因為在 Windows 上無法刪除開啟的檔案。

在撰寫建立並傳回串流的 API 時,在背景中,此串流可能會堆疊在檔案或其他串流上。為了確保串流所建置的資源正確毀損,它必須正確呼叫其所建置(擁有)的串流的毀損函式。

最初在 https://svn.haxx.se/dev/archive-2005-12/0487.shtml,稍後在 https://svn.haxx.se/dev/archive-2005-12/0633.shtml 中,這以更通用的術語討論了檔案、串流、編輯器和視窗處理常式。

正如 Greg Hudson 所說

經過考量,以下是我希望我們能做到的

  • 從底層物件讀取或寫入資料的串流擁有該物件,亦即關閉串流會關閉底層物件(如果適用)。
  • 建立串流的層(函式或資料類型)負責關閉它,除非適用上述規則。
  • 視窗處理常式被視為一種奇怪的串流,而傳遞最後一個 NULL 視窗會被視為關閉串流。

如果您將 apply_textdelta 視為建立視窗處理常式,那麼我不認為我們相去甚遠。svn_stream_from_aprfile 沒有擁有其子檔案,svn_txdelta_apply 錯誤地承擔了關閉傳遞給它的視窗串流的責任,而且可能還有一些其他偏差。

不過,上述規則有一個例外。當串流作為引數傳遞給函式時(例如:svn_client_cat2() 的「out」參數),該常式無法呼叫串流的毀損函式,因為它沒有建立該資源。

如果 svn_client_cat2() 建立串流,它也必須呼叫該串流的解構函式。根據上述模型,該串流會呼叫「out」參數的解構函式。不過這是錯誤的,因為解構「out」參數的責任在其他地方。

為了解決這個問題,至少在串流的情況下,已導入 svn_stream_disown()。此函式會包裝串流,確保它不會被銷毀,即使堆疊在它上面的任何串流都可能會嘗試這麼做。

變數長度引數清單

當您呼叫接受變數個引數且預期清單以 null 指標常數終止的函式(此類函式的範例為 apr_pstrcat)時,請勿使用 NULL 符號終止清單。根據編譯器和平台,NULL 可能或可能不是指標大小的常數;如果它不是,函式可能會讀取超過引數清單結尾的資料。

請改用 SVN_VA_NULL(自 1.9 版起在 svn_types.h 中定義),保證它會是 null 指標常數。例如

   return apr_pstrcat(cmd->temp_pool, "Cannot parse expression '",
                      arg2, "' in SVNPath: ", expr_err, SVN_VA_NULL);

其他編碼慣例

除了 GNU 標準外,Subversion 使用下列慣例

  • 當將路徑或檔案名稱作為輸入提供給大多數 Subversion API 時,請務必使用 svn_dirent_internal_style() API 將它們轉換為 Subversion 的內部/正規形式。或者,當從 Subversion API 接收路徑或檔案名稱作為輸出時,請使用 svn_dirent_local_style() API 將它們轉換為平台預期的形式。

  • 僅使用空格縮排程式碼,絕不使用 tab。Tab 顯示寬度尚未標準化,而且使用空格調整縮排較為容易。

  • 將行數限制在 79 個字元,如此一來,程式碼將能在最小標準顯示視窗中順利顯示。(可能會有些例外,例如在宣告 80 個字元區塊文字時,加上縮排、引號等額外字元,如果將每一行拆成兩行會過於雜亂。)

  • 所有已發布的函式、變數和結構都必須標示對應的函式庫名稱,例如 libsvn_wc 的 svn_wc_adm_open。所有在函式庫私人標頭檔中(例如 libsvn_wc/wc.h)進行的函式庫內部宣告,都必須在函式庫前綴後加上兩個底線(例如 svn_wc__ensure_directory)。所有僅限於單一檔案的宣告(例如 libsvn_wc/update_editor.c 內的靜態函式 get_entry_url)不需要任何額外的命名空間裝飾。需要在函式庫外使用但仍非公開的符號,會放在 include/private/ 目錄中的共用標頭檔,並使用雙底線表示法。此類符號僅能由 Subversion 核心程式碼使用。

    回顧

             /* Part of published API: subversion/include/svn_wc.h */
             svn_wc_adm_open()            
             #define SVN_WC_ADM_DIR_NAME ...
             typedef enum svn_wc_schedule_t ...
    
             /* For use within one library only: subversion/libsvn_wc/wc.h */
             svn_wc__ensure_directory()   
             #define SVN_WC__BASE_EXT ... 
             typedef struct svn_wc__compat_notify_baton_t ...
    
             /* For use within one file: subversion/libsvn_wc/update_editor.c */ 
             get_entry_url()
             struct handler_baton {
    
             /* For internal use in svn core code only:
                subversion/include/private/svn_wc_private.h */
             svn_wc__entry_versioned()
          

    在 Subversion 1.5 之前,需要在函式庫外使用的私人符號會放入公開標頭檔中,並使用雙底線表示法。此做法已廢棄,任何此類符號都是舊版,保留以維持 向後相容性

  • 在可能會列印出來(或以其他方式提供給)使用者的文字字串中,僅在路徑和其他可引用的項目周圍使用正向引號。例如

             $ svn revert foo
             svn: warning: svn_wc_is_wc_root: 'foo' is not a versioned resource
             $
          

    過去有很多字串在第一個引號使用反引號(`foo` 而不是 'foo'),但在某些字型中看起來很糟,而且也會搞亂一些人的自動突顯,因此我們決定一律使用正向引號。

  • 如果您使用 Emacs,請在 .emacs 檔案中放置類似以下內容,以便在需要時取得 svn-dev.el 和 svnbook.el

             ;;; Begin Subversion development section
             (defun my-find-file-hook ()
               (let ((svn-tree-path (expand-file-name "~/projects/subversion"))
                     (book-tree-path (expand-file-name "~/projects/svnbook")))
                 (cond
                  ((string-match svn-tree-path buffer-file-name)
                   (load (concat svn-tree-path "/tools/dev/svn-dev")))
                  ((string-match book-tree-path buffer-file-name)
                   ;; Handle load exception for svnbook.el, because it tries to
                   ;; load psgml, and not everyone has that available.
                   (condition-case nil
                       (load (concat book-tree-path "/src/tools/svnbook"))
                     (error
                      (message "(Ignored problem loading svnbook.el.)")))))))
    
             (add-hook 'find-file-hooks 'my-find-file-hook)
             ;;; End Subversion development section
          

    當然,您需要自訂設定的的路徑。您也可以讓正規表示式與字串比對更具選擇性;例如,一位開發人員表示

          > Here's the regexp I'm using:
          > 
          >     "src/svn/[^/]*/\\(subversion\\|tools\\|build\\)/"
          >
          > Two things to notice there: (1) I sometimes have several
          > working copies checked out under ...src/svn, and I want the
          > regexp to match all of them; (2) I want the hook to catch only
          > in "our" directories within the working copy, so I match
          > "subversion", "tools" and "build" explicitly; I don't want to
          > use GNU style in the APR that's checked out into my repo. :-)
          
  • 我們有一個傳統,不會在檔案中標記個別作者的名字(也就是說,我們不會在原始檔的最上方放「作者:foo」或「@作者 foo」這類的字行)。這是為了避免領域性——即使一個檔案只有一個作者,我們也希望其他人可以自由地進行更動。如果有人似乎對檔案宣示了個人所有權,其他人可能會不必要地感到猶豫。

  • 在一個句子的結尾和下一個句子的開頭之間,放兩個空格。這有助於閱讀,並讓人們可以使用編輯器的句子移動和操作指令。

  • 程式碼中還有許多其他不成文的慣例,只有當有人不小心沒有遵守時才會注意到。只要試著敏銳地觀察事情的執行方式,有疑問時,就發問。

撰寫記錄訊息

每次提交都需要一個記錄訊息。

記錄訊息的目標受眾是已經熟悉 Subversion,但可能不熟悉此特定提交的開發人員。通常,當有人回去閱讀變更時,他腦中已經沒有該變更周遭的所有脈絡。即使他是變更的作者,也一樣!所有討論、郵件串和所有其他事項都可能被遺忘;變更內容的唯一線索來自記錄訊息和 diff 本身。人們也會驚人地頻繁地重新檢視變更:例如,可能在原始提交幾個月後,現在變更正被移植到維護分支。

記錄訊息是變更的引言。

  • 如果你正在分支上作業,請在記錄訊息前加上

       On the 'name-of-branch' branch: (Start of your log message)
    
  • 在記錄訊息開頭寫一行指出變更的概略性質,必要時再接一段描述性的段落。

這不僅有助於讓開發人員在正確的心態下閱讀日誌訊息的其餘部分,還能與「ASFBot」機器人配合良好,後者會將每個提交的第一行傳送到 IRC 等即時論壇。(有關詳細資訊,請參閱 https://wilderness.apache.org/

如果提交只有一個檔案的簡單變更,則可以省略一般描述,直接進入詳細描述,採用以下所示的標準檔名然後符號格式。

在整個日誌訊息中,請使用完整的句子,而不是句子片段。片段通常比較模稜兩可,而且寫出你的意思只需要多花幾秒鐘。某些片段(例如「文件修正」、「新檔案」或「新函式」)是可以接受的,因為它們是標準慣用語,而且所有進一步的詳細資料都應出現在原始碼中。

日誌訊息應命名每個受影響的函式、變數、巨集、makefile 目標、語法規則等,包括在此提交中移除的符號名稱。這有助於人們稍後搜尋日誌。不要將名稱隱藏在萬用字元中,因為 globbed 部分可能是某人稍後搜尋的內容。例如,這是錯誤的

   * subversion/libsvn_ra_pigeons/twirl.c
     (twirling_baton_*): Removed these obsolete structures.
     (handle_parser_warning): Pass data directly to callees, instead
      of storing in twirling_baton_*.

   * subversion/libsvn_ra_pigeons/twirl.h: Fix indentation.

稍後,當有人試圖找出「twirling_baton_fast」發生了什麼事時,如果他們只搜尋「_fast」,他們可能找不到。較好的輸入會是

   * subversion/libsvn_ra_pigeons/twirl.c
     (twirling_baton_fast, twirling_baton_slow): Removed these
      obsolete structures. 
     (handle_parser_warning): Pass data directly to callees, instead
      of storing in twirling_baton_*. 

   * subversion/libsvn_ra_pigeons/twirl.h: Fix indentation.

萬用字元在「handle_parser_warning」的描述中是可以的,但僅限於兩個結構在日誌輸入的其他地方以完整名稱提及的情況。

你也應在日誌訊息中包含屬性變更。例如,如果你要修改主幹上的「svn:ignore」屬性,你可能會在日誌中放入類似這樣的內容

   * trunk/ (svn:ignore): Ignore 'build'.

上述內容僅適用於你維護的屬性,而不適用於 Subversion 維護的屬性,例如「svn:mergeinfo」。

請注意每個檔案如何取得自己的輸入,並以「*」為前綴,而檔案中的變更會依符號分組,符號列在括號中,後面跟著冒號,再接著描述變更的文字。請遵守此格式,即使只變更一個檔案也是如此 — 一致性不僅有助於可讀性,還能讓軟體自動為日誌輸入著色。

做為上述例外的,如果你在多個檔案中做了完全相同的變更,請在一個條目中列出所有變更的檔案。舉例來說

   * subversion/libsvn_ra_pigeons/twirl.c,
     subversion/libsvn_ra_pigeons/roost.c:
     Include svn_private_config.h.

如果所有變更的檔案都深藏在原始碼樹中,你可以透過在變更條目之前標示出共用字首,來縮短檔案名稱條目

   [in subversion/bindings/swig/birdsong]

   * dialects/nightingale.c (get_base_pitch): Allow 3/4-tone
     pitch variation to account for trait variability amongst
     isolated populations Erithacus megarhynchos.

   * dialects/gallus_domesticus.c: Remove. Unreliable due to
     extremely low brain-to-body mass ratio.

如果你的變更與問題追蹤器中的特定問題有關,請在記錄訊息中包含「問題 #N」之類的字串,但請務必仍摘要變更的內容。舉例來說,如果一個修補程式解決了問題 #1729,則記錄訊息可能是

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

嘗試將相關變更放在一起。舉例來說,如果你建立了 svn_ra_get_ansible2(),並棄用了 svn_ra_get_ansible(),則這兩件事應該在記錄訊息中彼此相鄰

   * subversion/include/svn_ra.h
     (svn_ra_get_ansible2): New prototype, obsoletes svn_ra_get_ansible.
     (svn_ra_get_ansible): Deprecate.

對於大型變更或變更群組,請將記錄條目分組成以空白行分隔的段落。每個段落都應該是一組變更,用以達成單一目標,且每個群組都應該以一或兩句摘要變更的句子開始。當然,真正獨立的變更應該在個別提交中進行。

請參閱 歸功,了解如果你正在提交他人的修補程式,或提交他們建議的變更,該如何歸功於他人。

永遠不應該需要記錄條目來了解目前的程式碼。如果你發現自己在記錄中寫下重要的說明,你應該仔細考慮你的文字是否實際上應該放在註解中,與它說明的程式碼並列。以下是一個正確執行的範例

   (consume_count): If `count' is unreasonable, return 0 and don't
    advance input pointer.

然後,在 `cplus-dem.c` 中的 `consume_count` 中

   while (isdigit((unsigned char)**type))
     {
       count *= 10;
       count += **type - '0';
       /* A sanity check.  Otherwise a symbol like
         `_Utf390_1__1_9223372036854775807__9223372036854775'
         can cause this function to return a negative value.
         In this case we just consume until the end of the string.  */
      if (count > strlen(*type))
        {
          *type = save;
          return 0;
        }

這就是為什麼一個新函數,例如,只需要一個記錄條目說「新函數」--- 所有細節都應該在來源中。

你可以對命名所有已變更事項的需求做出常識性的例外。例如,如果你已做出一個變更,這需要在程式其餘部分做出微不足道的變更(例如,重新命名一個變數),你不需要命名所有受影響的函數,你可以只說「所有呼叫者已變更」。在重新命名任何符號時,請記得提到舊名稱和新名稱,以利追蹤;請參閱 r861020 以取得範例。

一般而言,在透過搜尋識別碼輕鬆找到條目,以及透過詳盡無遺而浪費時間或產生無法讀取的條目之間存在著緊張關係。使用上述準則和你的最佳判斷,並體貼你的開發人員夥伴。(此外,使用「svn log」以查看其他人如何撰寫其記錄條目。)

用於文件或翻譯的記錄訊息有較為寬鬆的準則。顯然地,命名每個符號的需求並不適用,而且如果變更只是翻譯等持續流程中的另一個增量,甚至不需要命名每個檔案。只需簡要總結變更,例如:「馬達加斯加語翻譯的更多工作。」請用英文撰寫你的記錄訊息,以便參與專案的每個人都能了解你所做的變更。

如果你正在使用分支來「檢查點」你的程式碼,而且不認為它已準備好供審查,請在記錄訊息的頂端放置某種通知,在「在「xxx」分支通知」之後,例如

   *** checkpoint commit -- please don't waste your time reviewing it ***

而如果該分支上的後續提交進行審查,請在記錄訊息中提供適當的「svn diff」指令,因為 diff 可能會涉及該分支上的兩個非相鄰提交,審查者不應花時間找出它們。

致謝

以一致且可解析的方式記錄程式碼貢獻非常重要。這讓我們可以撰寫腳本找出積極貢獻的人員,以及他們的貢獻內容,因此我們可以 快速找出潛在的新提交者。Subversion 專案使用記錄訊息中的人類可讀但機器可解析欄位來達成此目的。

提交由他人撰寫的修補程式時,請在行的開頭使用「修補程式:」來標示作者

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

   Patch by: J. Random <jrandom@example.com>

如果有多人撰寫修補程式,請在個別行中列出他們,並確保每個換行行都以空白開頭。非提交者應列出姓名(如果已知)和電子郵件。完整和部分提交者應列出他們在 COMMITTERS 中的正規使用者名稱(該檔案最左邊的欄)。此外,「我」是可以接受的簡寫,表示實際提交變更的人員。

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

   Patch by: J. Random <jrandom@example.com>
             Enrico Caruso <codingtenor@codingtenor.com>
             jcommitter
             me

如果有人發現錯誤或指出問題,但未撰寫修補程式,請使用「發現者:」或「報告者:」來標示他們的貢獻

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

   Found by: J. Random <jrandom@example.com>

如果有人提出有用的建議,但未撰寫修補程式,請使用「建議者:」來標示他們的貢獻

   Extend the Contribulyzer syntax to distinguish finds from ideas.

   * www/hacking.html (crediting): Adjust accordingly.

   Suggested by: dlr

如果有人測試修補程式,請使用「測試者:」

    Fix issue #23: random crashes on FreeBSD 3.14.
    
    Tested by: Old Platformer
    (I couldn't reproduce the problem, but Old hasn't seen any crashes since
    he applied the patch.)
    
    * subversion/libsvn_fs_sieve/obliterate.c
      (cover_up): Account for sieve(2) returning 6.

如果有人審查變更,請使用「審查者:」或「審閱者:」(如果您喜歡)

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

   Review by: Eagle Eyes <eeyes@example.com>

欄位可以有多行,而記錄訊息可以包含欄位的任何組合

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

   Patch by: J. Random <jrandom@example.com>
             Enrico Caruso <codingtenor@codingtenor.com>
             me
   Found by: J. Random <jrandom@example.com>
   Review by: Eagle Eyes <eeyes@example.com>
              jcommitter

貢獻的進一步詳細資料應在對應欄位後面的括弧中列出。此類括弧總是套用於其正上方的欄位;在以下範例中,欄位已間隔開以方便閱讀,但請注意間隔是可選的,且對於可解析性並非必要

   Fix issue #1729: Don't crash because of a missing file.

   * subversion/libsvn_ra_ansible/get_editor.c
     (frobnicate_file): Check that file exists before frobnicating.

   Patch by: J. Random <jrandom@example.com>
   (Tweaked by me.)

   Review by: Eagle Eyes <eeyes@example.com>
              jcommitter
   (Eagle Eyes caught an off-by-one-error in the basename extraction.)

目前,這些欄位

   Patch by:
   Suggested by:
   Found by:
   Review by:
   Tested by:

是唯一官方支援的信用欄位(其中「支援」表示腳本知道要尋找的欄位),且在 Subversion 記錄訊息中廣泛使用。未來的欄位可能會是「VERB by: 」格式,偶爾有人可能會使用聽起來像是官方欄位但實際上並非如此的欄位,例如有幾個「Inspired by: 」的範例。這些範例是可以的,但請盡量使用官方欄位或括號旁註,而不是建立您自己的欄位。此外,當報告者已在問題中記錄時,請勿使用「Reported by: 」,而是直接參照問題。

檢視 Subversion 現有的記錄訊息,了解如何實際使用這些欄位。主幹工作副本頂端的這個指令將會有幫助

svn log | contrib/client-side/search-svnlog.pl "(Patch|Review|Suggested) by: "

注意:在某些提交訊息中看到的「Approved by: 」欄位與這些信用欄位完全無關,而且通常不會由腳本分析。這只是標準語法,用來表示誰核准了部分提交者的提交(在他們慣常的區域以外),或者(在合併到發行分支的情況下)誰投票贊成合併變更。

Github

Subversion 儲存庫會鏡像到 GitHub 的 https://github.com/apache/subversion/

有些使用者可能會在 GitHub 中建立拉取要求。如果程式碼已提交到 Subversion 儲存庫,請務必在記錄訊息中包含文字,以自動關閉拉取要求

This fixes #NNN in GitHub

若要管理拉取要求而不提交程式碼,您的 ASF ID 必須連結到 GitHub 帳戶,且 ASF Infra 必須將 triager 角色指派給您的帳戶。