前言

这是一篇极其没有节(nei)操(rong)的文章。除非你真的无聊,请不要阅读,否则后果自负~

正文

最近,在忙活微博话题组的日构建工具。工具主要的功能并不算复杂。写着写着,外面雨过天晴,居然还放起爆竹了,什么鬼。

构建工具的主要功能正如介绍中所述的那样,提取产品、测试等基本信息、提取版本库(git)信息、检查(编译)源文件、自动部署项目与发送邮件等。在提取 git 库信息时,相对于之前利用 shell_exec PHP 原生函数提取 svn 信息的方式,打算利用扩展来提取信息。一来更规范、更有效率(微乎其微),二来专业。缺点是相对而言部署环境麻烦,因为需要安装 git 扩展到当前 php 运行环境中来。

但是,万万没想到官方推荐的 php-git 扩展库开发版本已有3年没有维护了。索性用吧,又能怎样。

安装

还算顺利,由于公司开发机没有 cmake,yum 源也不可用,懒得配置,直接 download 一套源码。

cd /root/down/php-git
git submodule init
git submodule update
cd /root/down/php-git/libgit2/build
/root/down/cmake-3.6.1-Linux-x86_64/bin/cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=OFF -DBUILD_CLAR=OFF -DCMAKE_C_FLAGS=-fPIC ..
/root/down/cmake-3.6.1-Linux-x86_64/bin/cmake --build .
cd /root/down/php-git
phpize
./configure --enable-git2-debug --with-php-config=/your/php/config/path/php-config
make
make install

上述步骤自行领会,注意上述适用于 64 位 Linux 系统,32 位的 Linux 请见上述提到的 php-git 库说明文档。

使用

我们提取的 git 信息基本也就是从上一次部署版本,到该次部署版本之间的 log 和 diff 信息。所以,第一件事情,就是拿到这段的 commit hash 值。关于下文中提到的 commit hash 和 tree hash 概念可以自行 google 或参见博客libgit2使用教程(特别篇)几个基本概念说明

<?php
/**
 * Get commit hash array through revwalk.
 *
 * @param $commitHash string
 * @return array
 */
function revwalk($commitHash) {
    $walker = git_revwalk_new($this->repository);
    $result = array();
    git_revwalk_push_range($walker, "{$commitHash}..HEAD");
    while ($id = git_revwalk_next($walker)) {
        $result[] = $id;
    }
    return $result;
}

在拿到 commit hash 之后,将通过 git_diff_tree_to_tree 方法拿到每个 commit hash 之间的 diff 信息。通过方法名可以知道,这个 diff 信息是通过两个 tree 拿到的。

<?php
/**
 * Diff.
 *
 * @param $oldTreeHash string
 * @param $newTreeHash string
 * @format $format int such as:
 *								GIT_DIFF_FORMAT_PATCH        = 1u  full git diff
 *								GIT_DIFF_FORMAT_PATCH_HEADER = 2u  just the file headers of patch
 *								GIT_DIFF_FORMAT_RAW          = 3u  like git diff --raw
 *								GIT_DIFF_FORMAT_NAME_ONLY    = 4u  like git diff --name-only
 *								GIT_DIFF_FORMAT_NAME_STATUS  = 5u  like git diff --name-status
 *  @return array
 */
function diffTreeToTree($oldTreeHash, $newTreeHash, $format = GIT_DIFF_FORMAT_PATCH) {
    $newTree = git_tree_lookup($this->repository, $newTreeHash);
    $oldTree = git_tree_lookup($this->repository, $oldTreeHash);
    $opts = git_diff_options_init();
    $result = array();
    $fcc = array();
    $this->lastDiff = array();
    $fci = function ($diff_delta, $diff_hunk, $diff_line, $payload) {
        $result = '';
        if ($diff_line['origin'] == "-" || $diff_line['origin'] == "+") {
            $result = $diff_line['origin'];
        }
        $result.= trim($diff_line['content'], "\n");
        $this->lastDiff[] = $result;
    };
    /*
    $opts['flags'] = 0;
    $opts['version'] = 1;
    $opts['context_lines'] = 2;
    $opts['interhunk_lines'] = 10;
    $opts['old_prefix'] = 'a';
    $opts['new_prefix'] = 'b';
    $opts['pathspec'] = array();
    $opts['max_size'] = -1;
    $opts['notify_cb'] = function() {
    echo "notiry_cb...\n";
    };
    $opts['notify_payload'] = null;
    */
    $diff = git_diff_tree_to_tree($this->repository, $oldTree, $newTree, $opts);
    git_diff_print($diff, $format, $fci, $fcc);
    return $this->lastDiff;
}
/**
 * Get commit info.
 *
 * @param $commitHash string
 * @return array
 */
public function commitInfo($commitHash) {
    $result = array();
    $commit = git_commit_lookup_prefix($this->repository, $commitHash, strlen($commitHash));
    $result['author'] = git_commit_author($commit);
    $result['message'] = git_commit_message($commit);
    $result['tree_id'] = git_commit_tree_id($commit);
    $result['parent_count'] = git_commit_parentcount($commit);
    for ($i = 0;$i & lt;$result['parent_count'];$i++) {
        $result['parent_id'][$i] = git_commit_parent_id($commit, $i);
    }
    $result['committer'] = git_commit_committer($commit);
    $result['time'] = git_commit_time($commit);
    $result['id'] = git_commit_id($commit);
    return $result;
}

调用代码:

<?php
$repository = Config::get("common.product.cmd_path");
$gitModel = new GitModel($repository);
$lastCommitHash = '942382fb26ca94f381f5c84597b4974b1acbf027';
$walks = $gitModel->revwalk($lastCommitHash);
array_push($walks, $lastCommitHash);
$length = count($walks);
$commits = array();
$commitsMap = array();
$diffsMap = array();
foreach ($walks as $walk) {
    $commits[] = $gitModel->commitInfo($walk);
}
for ($i = 0;$i < $length - 1;$i++) {
    $commitsMap[$commits[$i]['id']] = $commits[$i];
    $diffsMap[$commits[$i]['id']] = $gitModel->diffTreeToTree($commits[$i]['tree_id'], $commits[$i + 1]['tree_id'], GIT_DIFF_FORMAT_NAME_STATUS);
}

然而,在运行到 git_diff_tree_to_tree 方法时,却来个:segmentation fault。这个错误一般是内存使用错误,这我怎么晓得。只好扒开源码,硬着脑皮看吧(C 我是小白)。

正所谓千里之行,始于足下。一直想着研究研究 PHP 扩展,机会终于来了,带着兴奋我打开了”messy”的代码。

PHP_FUNCTION(git_diff_tree_to_tree) {
  int result = 0;
  git_diff * diff = NULL;
  zval * repo = NULL;
  php_git2_t * _repo = NULL;
  zval * old_tree = NULL;
  php_git2_t * _old_tree = NULL;
  zval * new_tree = NULL;
  php_git2_t * _new_tree = NULL;
  zval * opts = NULL;
  git_diff_options options = {
    0
  };

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
      "rrra", &repo, &old_tree, &new_tree, &opts) == FAILURE) {
    return;
  }

  ZEND_FETCH_RESOURCE(_repo, php_git2_t * , &repo, -1, PHP_GIT2_RESOURCE_NAME, git2_resource_handle);
  ZEND_FETCH_RESOURCE(_old_tree, php_git2_t * , &old_tree, -1, PHP_GIT2_RESOURCE_NAME, git2_resource_handle);
  ZEND_FETCH_RESOURCE(_new_tree, php_git2_t * , &new_tree, -1, PHP_GIT2_RESOURCE_NAME, git2_resource_handle);
  php_git2_array_to_git_diff_options( &options, opts TSRMLS_CC);
  result = git_diff_tree_to_tree( &diff, PHP_GIT2_V(_repo, repository), PHP_GIT2_V(_old_tree, tree), PHP_GIT2_V(_new_tree, tree), opts);
  php_git2_git_diff_options_free( &options);
  RETURN_LONG(result);
}

显而易见,该函数的返回值不对,应该是资源却返回布尔值。当然,上述问题不在这里。由于没有经验,直接用 php_printf 打印信息跟踪调试。

在此期间,也曾给github上维护最活跃的技术牛人发过邮件和Twitter,可是未果。

libgit git_diff_tree_to_tree 邮件咨询
邮件咨询
libgit git_diff_tree_to_tree Twitter回复
Twitter回复

本人非常理解,通常来说我也没有太多时间和心思为其他人解决所谓的“难题”。对我来说,那些很没礼貌的询问我直接忽略。尽管我没得到答案,但是也算是尊总对方的。

最后发现,原来是 PHP 传过来的 $opts 参数不对。说是简单,就这一句话我整整用了一天时间,晚上10点钟才走。

第二天中午,头脑清醒后看着 C 代码,如醍醐灌顶一般,尼玛 opts 参数传的居然是 zval 类型,在下图中的 51、52 行可知,需要的是 git_diff_options 类型。将上述代码改正如下(已提交pull request),编译安装顺利跑通(目前为止已经看到源码的些许问题,抽时间会继续fix bug)。

QQ截图20160827171047

这次的提交,代表着我慢慢在工作中步入 C 的世界。just go for it~


参考文章:

文章来源:

基于 Libgit2 C 语言库的 Php-Git 扩展 Fix Bug 辛酸史

转载请注明出处,违者必究!

Share:

2 comments

Leave a Reply to zonghua Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.