标签 Subversion 下的文章

使用svn pre-commit hook

一直以来我们对项目代码的提交管理都是粗放型的,即对大家提交代码的时间、频率和提交日志的形式都没有严格的要求,可谓比较随意。主要发现的问题包括:
- 某些提交没有规划,甚至随意增加一些并无太大意义的注释都作一次提交。
- 提交的代码甚至没有经过REVIEW和UT,这样的代码即使内部发布,也会带来后续工作量的严重浪费(测试、发现问题、定位问题、重新fix、重新验证等);
- 提交日志无实际意义,如commit log为空、commit log没能真实反映出这次提交的真实目的和意义、多次提交却采用同一条提交日志等等;
… …

以上,有些问题是需要通过过程要求改善的,有些问题则可以通过技术手段引导大家去完成,比如对commit log的校验。从Tim的博客中了解到twiiter内部对每次commit的log都做严格要求,至少必须填写此次代码变动的代码评审人。这个idea很好!这样开发人员每次尝试提交代码时都要想着填写reviewed by xxx。xxx是要对这次提交代码的质量负责任的;绝对禁止提交代码者随意填写上一个并未真实review其代码的人的名字。

使用SVN来进行代码版本控制工具的项目可采用svn pre-commit hook来实现对commit log的检查。在SVN服务器侧你的项目repos下有一个hook目录,该目录下存放着一些hook的模板(以.tmpl为后缀名)。各个hook模板中都有对该类型hook的说明,甚至还包括一段代码样例。如果你想使该hook启用,需要将xxx.tmpl改名为xxx,这样你再提交代码时,hook就会被svn server端自动调用。svn的hook其实就可以理解为一个可执行的文件,你可用各种语言(如shell脚本、C、Java、Python、Ruby等)实现hook。svn server端在调用hook时,会按照规定次序给hook传入N个确定含义的命令行参数供hook的实现使用。以pre-commit hook为例,svn server会依次传入REPOS和TXN;其中REPOS存储的是项目repository的路径信息;TXN则是此次提交的一个事务号名称。hook实现的返回值将作为svn server判断是否继续此次提交事务的依据:如果返回0,则svn server继续此次提交事务,否则svn server停止此次提交,并将hook实现中输出到标准错误的信息回送到客户端以作为错误提示。

下面是一个用C语言实现的pre-commit hook的简单例子:
/* pre-commit.c */
/* gcc -o pre-commit pre-commit.c */
int main(int argc, char *argv[]) {
    char     repos[PATH_MAX];
    char     txn[64];

    memset(repos, 0, sizeof(repos));
    memset(txn, 0, sizeof(txn));

    strcpy(repos, argv[1]);
    strcpy(txn, argv[2]);

    /* 只对repos下的特定路径下的文件ci进行log检查 */
    if (!filter_repos_subdir(txn, repos)) {
        return check_log(txn, repos);
    }

    return 0;
}

对于一个repos,其下面有些folder中的文件可能并不一定是代码,可能不需要严格执行ci log格式的要求,filter_repos_subdir这个函数就旨在过滤此次提交的各个文件的路径信息:若判断出此次提交的文件路径均是不需要严格执行ci log格式要求的,则后续不作log check。

通过repos和txn两个参数我们如何获取此次提交的文件路径信息呢?svn提供了svnlook工具,我们利用svnlook changed -t txn repos可以获取文件路径信息。

#define SVNLOOK "/usr/local/bin/svnlook"
int filter_repos_subdir(const char *txn, const char *repos) {
    FILE        *fp;
    char        buf[PATH_MAX];
    char        cmd[PATH_MAX];

    memset(cmd, 0, sizeof(cmd));
    memset(buf, 0, sizeof(buf));
    sprintf(cmd, "%s changed -t %s %s", SVNLOOK, txn, repos);

    fp = popen(cmd, "r");
    if (fp == NULL) {
        fprintf(stderr, "%s\n", "popen failed");
        return 1;
    }

    while (fgets(buf, PATH_MAX, fp) != NULL) {
        if ((strstr(buf, "dog/") != NULL)
            || (strstr(buf, "cat/") != NULL)
            || (strstr(buf, "tiger/") != NULL) {
            memset(buf, 0, sizeof(buf));
            continue;
        } else {
            pclose(fp);
            return 1;
        }
    }

    pclose(fp);
    return 0;
}
filter_repos_subdir利用popen与shell交互获取svnlook执行后输出的信息,如:
U   dog/test1.c
U   cat/test2.c
A   tiger/test3.c
并对多行信息逐一进行过滤。

check_log与filter_repos_subdir类似,它通过svnlook log -t TXN REPOS获取此次提交的日志信息,并根据日志格式要求对日志进行校验,如发现不合格则返回失败;svn server将停止本次commit事务。

int check_log(const char *txn, const char *repos) {
    FILE        *fp;
    char        buf[PATH_MAX];
    char        cmd[PATH_MAX];

    memset(cmd, 0, sizeof(cmd));
    memset(buf, 0, sizeof(buf));
    sprintf(cmd, "%s log -t %s %s", SVNLOOK, txn, repos);

    fp = popen(cmd, "r");
    if (fp == NULL) {
        fprintf(stderr, "%s\n", "popen failed");
        return 1;
    }

    while (fgets(buf, PATH_MAX, fp) != NULL) {
        if (strstr(buf, "reviewed by")) {
            pclose(fp);
            return 0;
        }
        memset(buf, 0, sizeof(buf));
    }
    fprintf(stderr, "%s\n", "请填写此次提交代码的reviewer, log格式:… reviewed by xxx …");
    pclose(fp);
    return 1;
}

以上这个pre-commit hook demo只是为了说明hook的实现思路,如果你要打造自己的pre-commit hook可能还需要更严谨一些,另外还可加上更多有创意性的idea在里面!其他类型hook的实现思路大致一样,详细内容请参考svn manual

说说用xml做配置文件的优劣

最近收到客户的一个需求,要求我们将产品的系统配置数据和业务配置数据定期导出备份,以防万一数据库宕掉后可以用来"救火"。产品从起初0.1版本就一直延续着一种"section-key-value"的配置文件方式,同时我们也有可复用的库来完成配置数据的读取,可是在长期的使用过程中我们发现的不少问题,特别是在存储多样化的业务数据的时候,这样的配置方式带来维护上的很大不便。

"section-key-value"这样的配置文件方式或者类似于环境变量似的配置文件方式用来做系统自己的配置可以说既简单又实用,像著名的Apache服务器、版本控制系统svn等都是采用这种方式。在我们产品的初期,那时的业务相对简单,采用这样的一种配置方式还算是合适的。但随着业务复杂度的上升,种类繁多的业务数据的出现,这种配置方式的弊端渐渐显现。

最大的弊端就是"同步"问题。每当我们的产品升级时,我们一般都会先升级数据库的配置,然后再同步文档,最后同步配置文件以及相关读取代码。如果工作量较大,升级工期较紧的时候,常常忽略了将新增或修改的配置同步到配置文件;这样累积到一定时间之后,除了花费大力气去"补",别无它法。另外一个弊端就是上面提到的那个问题:从数据库导出配置文件始终是那么"别扭",曾经尝试过使用脚本导出、Java程序导出,但是始终不能让人很满意,挖掘一下深层原因:配置文件和数据库表设计"不搭调"。业务数据千变万化,导致从数据库导出配置文件的逻辑甚是复杂,维护起来十分不便;而且像"section-key-value"这样的结构,数据库中由于没有section的字串,导致这些section必须"写死"在代码中。

不错,我们在寻找替代品,目标锁定在xml。曾经在2005年做的一个项目中尝试过使用xml作为配置数据,取得了很好的效果。记得当时参考Ant的build.xml的配置方式,顺利解决了一个"自动化处理"的配置设计,那应该是部门第一次采用xml做为后台C实现的系统的配置文件。也是自那以后,我感受到了xml的强大描述能力。xml在Java世界可以说占据了大部江山,从DB导出数据到xml可以说轻而易举,这又恰好解决了本篇所提到的"同步"难题。

坐在公司的Bus上,大致想出了如下xml作为配置文件的好处:
1) 与DB表几乎无缝转换,方便导入导出;
2) 作为元语言,其描述能力毋庸置疑;
3) 在Java世界几乎是配置文件的首选或者说是标准也不过分,选择标准的,总会被支持的很好;
4) 诸多开源工具支持对XML的读写甚至支持加密;
5) 文本形式,方便浏览和信息查找(grep or find均可派得上用场),这也符合Unix编程艺术(TAOUP)作者在书中阐述的一个原则-尽量文本化。
6) DTD或schema验证,自动验证格式是否OK。
… …

当然缺点也是有的:
1) 如果不加密,是明文,保存账户、密码等数据时要小心,当然这也是文本配置的通病;
2) 如果设计不当,会导致"xml地狱",xml太多也太烦,很多Java世界的产品就有此弊病。

大致在心里估算了一下,读取xml承载的配置与读取传统的配置的代码量没有太大出入,但是如果xml设计的足够精致,后期的维护工作将大为减少。xml配置改造工作看来势在必行了。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats