Git Invert-grep Bug

前段时间部门技术老板要求每个组统计每个研发同学在某个版本的千行 bug 率,所以第一步就要能够统计某个人在某个指定分支的提交信息。

这个很容易做,利用

1
git log d313a36e199^..HEAD --shortstat --author="Kingsley Chen"

就可以拿到包含修改记录的提交信息。但是要同时排除掉 merge 提交的数据,避免出现重复和乱记。

对于普通的 git merge,只需要在前面的命令中加入 --no-merge 即可,但是问题在于,我们项目的分支管理采用的不是 git flow branching model,而是类似 chromium 的 trunk-based model。

我们所有的提交都会在 develop 上完成,发布某个版本时切除 release 分支,版本的修改在 release 上完成,发布后通过 git merge --squash 将 release 分支的新提交合会 develop;所以前面的 --no-merge 便没有了效果。

想到的一个方案是使用 --grep='Merge commits from' 来获得和并提交,然后利用 --invert-grep 进行取反。

但是在实践中我发现,--invert-grep 虽然在文档上说是 invert the previous grep,但是它实际上会导致 --author='' 过滤失效,因为 --author 内部实质上也是等价于一个 grep

这里有一个相关的问题 https://public-inbox.org/git/xmqqshjj4ce5.fsf@gitster.mtv.corp.google.com/ 但是看起来官方认为这个行为并不是一个 bug。

最后我选择的解决方案是用 python 写一个完整的 tool,手动做了排除,代码也比较简单,看了一下都不超过 80 LOC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!python3
# -*- coding: utf-8 -*-
# 0xCCCCCCCC

import re
import shlex
import subprocess
import sys

FIRST_REV = ''
LAST_REV = ''

def run(cmd):
#print('-> ' + cmd)
return subprocess.check_output(shlex.split(cmd)).decode('utf-8')

def query_authors():
return list(map(lambda l: l.strip().split('\t')[1],
run('git shortlog -sn {}^..{}'.format(FIRST_REV, LAST_REV)).splitlines()))

def sum_commits(records):
insertion_num = 0
deletion_num = 0

for stat in records:
mr = re.search(r'(\d+)(?= insertion)', stat)
if mr:
insertion_num += int(mr.groups()[0])

mr = re.search(r'(\d+)(?= deletion)', stat)
if mr:
deletion_num += int(mr.groups()[0])

return insertion_num, deletion_num

def print_analyzed(results):
print('{0:>20}Insertion{0:>10}Deletion{0:>10}Total'.format(' '))
for author, records in results.items():
print('{1:<20}{2:>6}{0:<10}{3:>6}{0:<10}{4:>6}'.format(' ',
author, records[0], records[1], records[2]))

def main():
if len(sys.argv) < 3:
print('No full range specified! Analyze entire history for current branch.')
return

global FIRST_REV, LAST_REV
FIRST_REV = sys.argv[1]
LAST_REV = sys.argv[2]

authors = query_authors()
results = {}
for author in authors:
commits_query = 'git log {}^..{} --shortstat --author="{}"'.\
format(FIRST_REV, LAST_REV, author)
commits = list(filter(lambda s: re.search('files? changed', s),
run(commits_query).splitlines()))
total_insertions, total_deletions = sum_commits(commits)

excluded_query = commits_query + ' --grep="Merge commits"'
excluded_commits = list(filter(lambda s: re.search('files? changed', s),
run(excluded_query).splitlines()))
excluded_insertions, excluded_deletions = sum_commits(excluded_commits)

total_insertions -= excluded_insertions
total_deletions -= excluded_deletions

results[author] = (total_insertions, total_deletions, total_insertions + total_deletions)

print_analyzed(results)

if __name__ == '__main__':
main()

Monthly Read Posts in Aug 2018

Programming Languages

CppCon 2015: Fedor Pikus “The Unexceptional Exceptions”

Handling exceptions is just as to handle errors using error code, exception is just a tool.

The key point is to maintain the program state in a well defiend state.

Turn error handling into resource management and use RAII to automate it; use explicit try…catch only when absolute necessary.

Bonus tip: avoid uses of pthread_cancel.


Pros and cons of functional programming

Pure function: avoid shared state; immutable structures; function composition.

Declarative: instead of answers the question ‘how to do’ in imperative style, it answers the question ‘what to do’; imperative relies on instructions while declarative relies more on expressions.

Cons of FP: not suitable for graph algorithms; major shift on mind patterns.


Understanding Shell Script’s idiom: 2>&1

General usage:

1
cat foo.txt > result.log 2>&1
  1. fd for stdout and stderr are 1 and 2 respectively
  2. a > b is a shortcut for a 1> b and 1 here is fd value for stdout
  3. you can use &fd to reference a fd value
  4. therefore, using 2>&1 would redirect stderr to stdout, and 1>&2 would do the opposite.

开了个新坑 ezio

前段时间分别在 Linux 和 Windows 下实现了一个简单的 TCP 网络框架之后,感觉一些必要的坑都踩的差不多了,可以开始动手写一个真正的跨平台的 TCP 网络框架了。

于是就有了这个新坑:ezio。

ezio 这个名字是某天跑步的时候突然想到的,有几重含义:

  • 致敬刺客信条里的 Ezio Auditore,寓意希望这个库能够和刺客一样,虽轻量,但需要时恰到好处,不含糊
  • 致敬(碰瓷)Boost.ASIO,因为两个名字发音非常接近
  • 第三个是朋友发现的,他把 ezio 念成了 e-z-io(easy io),于是赋予了一个新的含义

ezio 的目标是:提供同时支持 Linux 和 Windows 的轻量型非阻塞且具备扩展性(non-blocking and scalable)的 TCP 网络框架;针对 Linux 做性能侧优化,针对 Windows 做一致性开发体验的优化。

这个目标初看起来有点怪异,但是确实非常实际的做法。

因为 Linux 和 Windows 在网络编程上存在根本的范式差异性,所以为了提供一致的对外接口,必须要有所侧重和牺牲。Linux 目前占据了绝大多数互联网产品服务器系统的份额,而 Windows 则提供了可以说是当前最好的开发环境。

ezio 这个设计目标可以使得绝大多数业务逻辑在 Windows 上开发,然后在 Linux 上进行最后的部署。

Dealing With Multiple Types As a Category

Macro ENSURE() from KBase can ‘capture’ variables by outputing their content to the internal stringtream, provided the type of captured variables has overloaded operator<<().

However, for variables of enum-class types, which disallow implicit conversion to underlying integer, there is no easy way to enable ENSURE() to capture them, because every enum-class is a type, and it is not realistic to define an overloaded operator<<() for each of them.

We need to handle all enum-class types as a category.

std::is_enum<T> can identify a type if it is an enum at compile-time (it is one of type-traits utils), and in conjunction with some SFINAE tricks, we can categorize types at compile-time and handle them with different policies.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct general_category_tag {};
struct wide_string_category_tag {};
struct enum_category_tag {};

template<typename T, typename = void>
struct map_type_to_category
: general_category_tag
{};

template<typename T>
struct map_type_to_category<T, std::enable_if_t<std::is_enum<T>::value>>
: enum_category_tag
{};

template<typename T>
struct map_type_to_category<T, std::enable_if_t<std::is_same<T, std::wstring>::value ||
std::is_same<T, kbase::WStringView>::value>>
: wide_string_category_tag
{};

Now that we can decide which category a type should fall into, we then simply use tag dispatching to locate target handler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template<typename T>
Guarantor& CaptureVar(const char* name, const T& value)
{
HandleCapturedVar(name, value, internal::map_type_to_category<T>{});
return *this;
}

template<typename T>
void HandleCapturedVar(const char* name, const T& value, internal::general_category_tag)
{
RecordCapturedVar(name, value);
}

template<typename T>
void HandleCapturedVar(const char* name, const T& value, internal::wide_string_category_tag)
{
RecordCapturedVar(name, WideToUTF8(value));
}

template<typename T>
void HandleCapturedVar(const char* name, T value, internal::enum_category_tag)
{
RecordCapturedVar(name, enum_cast(value));
}

template<typename T>
void RecordCapturedVar(const char* name, const T& value)
{
exception_desc_ << " " << name << " = " << value << "\n";
}

This design also has great extensibility.

Monthly Read Posts in July 2018

Programming Language

Clearer interfaces with optional

Partial queries with optional

optional<T> 介绍 & 简单使用例子


CppCon 2015: John R. Bandela “Simple, Extensible Pattern Matching in C++14”

一个轻量级的 pattern matching 库简要介绍。


CppCon 2015: Greg Miller “Time Programming Fundamentals”

Google 内部实现的一个处理 date-time 的轮子。

不过讲道理,这块内容还不如关注一下之前 monthly read posts 里出现的这篇 里提到的 date library。

看起来差不多会在 C++ 20 引入


When MSDN says NULL, is it okay to use nullptr?

Short anwser: YES.

在 Windows 上运行 Linux GUI 程序

自从 Windows 10 提供 WSL 之后,在 Windows 上运行 Linux CLI 程序并不是一个复杂的事情;然而俗话说饱暖思淫欲,既然可以做到在 Windows 上跑 Linux CLI 了,下一步自然想的是在 Windows 上跑 Linux GUI 程序。

之所以要跑 GUI 是因为,无论是 vscode 还是 CLion,写 C++ 的体验都比在 terminal 里开一个 vim 要好太多了,无论你是花了多少时间配置了 .vimrc,在 CLion 面前都是战五渣。更何况我买了 Jetbrains 的 All product license,不用用难道留着过年吗。

这里先解答两个常见疑问:

Q:为什么不直接在虚拟机里使用?
A:因为 Linux 对 4K 屏原生支持太糟糕。哪怕 Mint 提供了 double scaling,解决了一些外观上的问题,我在 4K 的环境下一开 vscode 程序就崩溃,而且运行操作明显掉帧。
另外一个问题是,单纯的虚拟机操作和外界宿主太隔离,等于我得配置两套完全一样的环境(比如浏览器,常见的应用,甚至 SS 等),而且和宿主的交互非常不够便利。

Q:为什么不买 macbook?
A:??你是认真的么?OS X 那么垃圾的系统。再说我要写 C++ server-end 的代码,要是可以用 OS X 我为什么不直接在 Windows 上跑?

另外有一个剧透:我尝试过运行 WSL 里的 Linux GUI 程序,例如 CLion,但是目前 WSL 的文件系统性能过于糟糕,CLion 一个劲的冒错误提示,所以,下面的环境假定是虚拟机里的 Linux 或者一台单独的 Linux 设备。

Monthly Read Posts in Jun 2018

Programming Language

CppCon 2015: Richard Powell “Intro to the C++ Object Model”

基本覆盖了 POD,无需函数类,单继承的情况,扫盲效果俱佳。

至于为什么没有包含 multiple inheritance / virtual inheritance,作者说自己不是很 condifent with that。

讲道理,MI / VI 在一般的设计里都会尽量避免,而且各种乱七八糟的 case 很多,这里不涉及反而是个正确的做法。


Assertions

首先,我很赞同文中作者对于 assertion 是 guarantees given by the author of a piece of code to himself/herself 但是接下来的大部观点都不赞同。

比如作者认为不应该 assert on precodintions & postconditions,因为 assertion 是 for implementation details,这个思路其实不对。

关于 assertion,目前看过最好的阐述还是 Writing Solid Code,推荐去阅读这个。


Compile-time computations

这篇 post 展示了 constexpr functions 错误报告的常见手段:抛异常。

这个在之前的某个 cppcon talk 里有专门的介绍。

另外,文中更进一步,抽出专门的 constexpr validation functions,并通过 comma operator 串联。

C++ 14 支持多句之后,连 comma operatos 都可以免掉了。