[您也可以查看此文档的 单页版本。]

编码和提交约定

代码模块化和接口可见性

Subversion 的代码和头文件沿着几条关键线进行隔离:库特定与库间;公共与私有。这种分离主要是由于我们对适当的模块化和代码组织的关注,但也因为我们作为广泛采用的公共 API 的提供者和维护者的承诺。当您在 Subversion 中编写新函数时,您需要仔细考虑这些问题,在进行过程中问自己一些问题

“我的新代码的使用者是否位于单个库中的特定源代码文件中?” 如果是这样,您可能需要在同一个源文件中使用静态函数。

“我的新函数是其他库中的源代码需要使用的,但外部代码不需要使用吗?” 如果是这样,您需要使用非静态的双下划线命名函数(例如 svn_foo__do_something),并在相应的库特定头文件中包含其原型。

“我的代码需要从其他库访问吗?” 这里您需要回答一些额外的问题,例如“我的代码应该放在我最初要放置它的库中,还是应该放在更通用的实用程序库中,例如 libsvn_subr?” 无论哪种方式,您现在都在考虑使用库间头文件。但在您决定使用哪个之前,请查看下一个问题...

“我的代码是否具有干净、可维护的 API,可以合理地永久维护,并为 Subversion 公共 API 提供价值?” 如果是这样,您将向公共 API 添加原型,直接放在 subversion/include/ 中。如果不是,请仔细检查您的计划——也许您没有选择最佳的方式来抽象您的功能。但有时库需要共享一些对 Subversion 本身以外的其他软件来说毫无用处的函数。在这种情况下,请使用 subversion/include/private/ 中的私有头文件。

编码风格

Subversion 使用 ANSI C,并遵循 GNU 编码标准,但我们不在函数名称和其参数列表的左括号之间添加空格。Emacs 用户只需加载 svn-dev.el 即可获得正确的缩进行为(这里的大多数源文件会自动加载它,如果 `enable-local-eval' 设置得当)。

阅读 https://www.gnu.org/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'。这样的人并不像你想象的那么少,如果你想成为其中一员,那么在你的 .emacs 中添加 (require 'page-ext),然后输入 C-x C-p C-h。

错误消息约定

对于错误消息,以下约定适用

  • 仅当有信息要添加到 subversion/include/svn_error_codes.h 中找到的通用错误消息时,才提供特定错误消息。

  • 消息以大写字母开头。

  • 尝试将消息保持在 70 个字符以内。

  • 不要在错误消息末尾添加句号(“.”)。

  • 错误消息中不要包含换行符。

  • 引用信息使用单引号(例如:"'some info'")。

  • 不要在错误消息中包含发生错误的函数名称。如果 Subversion 使用 '--enable-maintainer-mode' 配置标志编译,它会自行提供此信息。

  • 在错误字符串中包含路径或文件名时,请确保用引号引起来(例如:"Can't find '/path/to/repos/userfile'")。

  • 在错误字符串中包含路径或文件名时,请确保使用 svn_dirent_local_style() 转换它们,然后再包含(因为传递到和从 Subversion API 的路径被假定为 规范形式)。

  • 不要使用 Subversion 特定的缩写(例如,使用 "repository" 而不是 "repo",使用 "working copy" 而不是 "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函数参数,它同时充当结果池和临时池。)

通过为循环限定的数据使用 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. 将错误向上抛出,并将其包装在一个新的错误结构中,将其作为“child”参数包含在内。

              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() 宏将创建一个包装错误。这有助于开发人员确定错误的原因,并且可以通过 configure--enable-maintainer-mode 选项启用。

  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 中,针对文件、流、编辑器和窗口处理程序,对这个问题进行了更一般的讨论。

正如格雷格·哈德森所说

经过考虑,我希望我们能做到以下几点

  • 从底层对象读取或写入底层对象的流拥有该对象,即关闭流会关闭底层对象(如果适用)。
  • 创建流的层(函数或数据类型)负责关闭它,除非上述规则适用。
  • 窗口处理程序被认为是一种奇怪的流,传递最终的 NULL 窗口被认为是关闭流。

如果你认为 apply_textdelta 是创建窗口处理程序,那么我认为我们并没有偏离太多。svn_stream_from_aprfile 并不拥有其子文件,svn_txdelta_apply 错误地承担了关闭传递给它的窗口流的责任,并且可能存在一些其他偏差。

不过,上述规则有一个例外。当一个流作为参数传递给一个函数时(例如:svn_client_cat2() 的 'out' 参数),该例程不能调用流的析构函数,因为它没有创建该资源。

如果 svn_client_cat2() 创建了一个流,它也必须调用该流的析构函数。根据上述模型,该流将调用 'out' 参数的析构函数。然而,这是错误的,因为销毁 'out' 参数的责任在于其他地方。

为了解决这个问题,至少在流的情况下,引入了 svn_stream_disown()。此函数包装一个流,确保它不会被销毁,即使任何堆叠在其上的流可能尝试这样做。

可变参数列表

当你调用一个接受可变数量参数的函数,并期望该列表以一个空指针常量(例如 apr_pstrcat)终止时,不要使用 NULL 符号来终止该列表。根据编译器和平台的不同,NULL 可能是一个指针大小的常量,也可能不是;如果不是,该函数可能会读取超出参数列表末尾的数据。

相反,请使用 SVN_VA_NULL(从 1.9 版本开始在 svn_types.h 中定义),它保证是一个空指针常量。例如

   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 将它们转换为平台期望的形式。

  • 仅使用空格缩进代码,不要使用制表符。制表符显示宽度没有标准化,而且手动调整使用空格的缩进更容易。

  • 将行限制为 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”或“@author foo”的行)。这样做是为了避免领地意识——即使文件只有一个作者,我们也希望确保其他人可以自由地进行更改。如果有人似乎对文件提出了个人主张,人们可能会不必要地犹豫。

  • 在一个句子结束和下一个句子开始之间留两个空格。这有助于提高可读性,并允许人们使用其编辑器的句子移动和操作命令。

  • 在整个代码中维护着许多其他不成文的约定,只有当有人无意中没有遵循这些约定时才会注意到。只需尝试对事物完成的方式保持敏感,如有疑问,请询问。

编写日志消息

每次提交都需要一条日志消息。

日志消息的目标受众是熟悉 Subversion 但不一定熟悉此特定提交的开发人员。通常,当有人回溯并阅读更改时,他不再记得有关该更改的所有上下文。即使他是更改的作者,情况也是如此!所有讨论、邮件列表线程以及其他一切可能都被遗忘;关于更改内容的唯一线索来自日志消息和差异本身。人们也以惊人的频率重新访问更改:例如,可能是原始提交后的几个月,现在更改正在移植到维护分支。

日志消息是更改的介绍。

  • 如果您正在处理分支,请在日志消息前面加上

       On the 'name-of-branch' branch: (Start of your log message)
    
  • 以一行指示更改的总体性质开始您的日志消息,并在必要时添加描述性段落。

这不仅有助于将开发人员置于阅读其余日志消息的正确心态,而且与在 IRC 等实时论坛中回显每个提交的第一行的“ASFBot”机器人配合良好。(有关详细信息,请参阅 https://wilderness.apache.org/

如果提交只是对一个文件进行一个简单的更改,那么您可以省略一般描述,直接进入详细描述,使用下面显示的标准文件名-符号格式。

在整个日志消息中,使用完整的句子,而不是句子片段。片段更容易产生歧义,而写出你的意思只需要几秒钟。某些片段,如“文档修复”、“新文件”或“新函数”是可以接受的,因为它们是标准的习惯用语,所有其他细节都应该出现在源代码中。

日志消息应该命名所有受影响的函数、变量、宏、makefile 目标、语法规则等,包括在此提交中删除的符号的名称。这有助于人们以后搜索日志。不要将名称隐藏在通配符中,因为通配符部分可能是人们以后搜索的内容。例如,以下代码不好

   * 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` 的描述中是可以的,但仅仅是因为这两个结构在日志条目中的其他地方以全名提及。

您还应该在日志消息中包含属性更改。例如,如果您要修改 trunk 上的“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.

如果您的更改与问题跟踪器中的特定问题相关,请在日志消息中包含类似“issue #N”的字符串,但请确保您仍然总结了更改的内容。例如,如果一个补丁解决了 issue #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 项目在日志消息中使用人类可读但机器可解析的字段来实现这一点。

当提交其他人编写的补丁时,在行首使用“Patch by: ”来指示作者

   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 <[email protected]>

如果多个个人编写了补丁,请将他们分别列在单独的行上——确保每行续行都以空格开头。如果已知,应按姓名列出非提交者,以及电子邮件。应按其来自 COMMITTERS(该文件中的最左侧列)的规范用户名列出完整和部分提交者。此外,“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.

   Patch by: J. Random <[email protected]>
             Enrico Caruso <[email protected]>
             jcommitter
             me

如果有人发现了错误或指出了问题,但没有编写补丁,请使用“Found by: ”(或“Reported by: ”)来指示他们的贡献

   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 <[email protected]>

如果有人提出了有用的建议,但没有编写补丁,请使用“Suggested by: ”来指示他们的贡献

   Extend the Contribulyzer syntax to distinguish finds from ideas.

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

   Suggested by: dlr

如果有人试用了补丁,请使用“Tested by: ”

    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.

如果有人审查了更改,请使用“Review by: ”(或“Reviewed by: ”,如果您更喜欢)

   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 <[email protected]>

一个字段可能有多行,日志消息可能包含任何字段组合

   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 <[email protected]>
             Enrico Caruso <[email protected]>
             me
   Found by: J. Random <[email protected]>
   Review by: Eagle Eyes <[email protected]>
              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 <[email protected]>
   (Tweaked by me.)

   Review by: Eagle Eyes <[email protected]>
              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 角色分配给您的帐户。