2019-03-17

Go 语言解析 git config

最近做的一个 go 语言的项目需要频繁读写 git config 文件,一些看似现成的解决方案并不能满足需要:

  1. 不考虑调用外部命令 git config,因为在 Windows 平台性能差。
  2. 不考虑 libgit2,因为会给静态编译和发布带来麻烦。

在 github 上找到一个能够解析 git config 文件的项目 goconfig,该项目的代码直接从 Git 项目的 git/config.c 移植过来,可以确保兼容性。但是这个项目只是一个半成品,因为:

  1. 不支持多值配置项。Git 的很多设置实际上是多值配置项,例如:push.pushOptionremote.<name>.fetch 等。
  2. 不支持配置文件嵌套,即不支持通过 include.path 指令包含其他配置文件,而这在我们要开发的应用中至关重要。
  3. 不支持配置文件继承(多级配置)。Git 在读取一个配置项时,会依次读取系统级配置(/etc/gitconfig)、用户全局配置($HOME/.gitconfig)、仓库级配置文件(.git/config)、嵌套的配置文件(include.path 指向的配置文件)。从优先级上看,仓库级配置文件高于全局配置,更高于系统级配置。
  4. 不支持写配置文件。

于是派生了一个项目到 jiangxin/goconfig,实现了上述特性。

增加多值配置特性

实际上 Git 配置项,无论单值(如 user.name),还是多值(如 remote.<name>.fetch),都应该一视同仁当做多值来处理,这样配置文件嵌套、配置文件继承的处理就非常简单了。即:

  1. 每一个配置项(如 user.name)的值是一个字符串数组。
  2. 如果用户将某个配置项视为单值设置,只取数组的最后一项,作为该配置的唯一值。
  3. 如果用户将某个配置项视为多值设置,数组所有内容都是该配置项内容。

为此将 goconfig.go 的返回值由 map[string]string 替换为支持多值配置的自定义类型

type GitConfig map[string]GitConfigKeys
type GitConfigKeys map[string][]string

其中 GitConfig 的索引对应 config 配置文件的小节(section)名称,GitConfigKeys 的索引对应于配置项在小节内的 key。

GitConfig 最核心的方法是 GetAll,其他方法 Get, GetBool, GetInt 等都是基于 GetAll 方法。

参见提交 9e83c31 (Add GitConfig to read boolean, int, multi-values, 2019-02-28)

增加配置文件嵌套特性

配置文件嵌套,涉及到迭代和配置文件的合并。核心方法是 GitConfig 的 Merge 方法。

参见提交 83a00ae (Parse include config files from include.path, 2019-03-05)

增加配置文件继承

配置文件继承和配置文件嵌套非常相似,都是 GitConfig 结构体的 Merge,只不过多了一些文件 IO,以及缓存机制。

参见提交 a033f72 (Add Load() function to read git config from disk, 2019-03-04)

增加配置文件写操作

实际上第一个版本的 GitConfig 结构体定义为 map[string][]string,就可以实现多值配置。为了支持将 GitConfig 结构体回写为文件,对其做了提交修补(git commit –amend)操作。 GitConfig 的 map 索引变成了小节(section)名称。

为了支持写操作所做的第二个改变是为每一个值增加了一个范围(scope),这样在保存 GitConfig 到文件的时候,知道哪些配置来自于系统级(ScopeSystem)、全局级(ScopeGlobal)、 文件嵌套(ScopeGlobal),只将配置文件中的 ScopeSelf 记录到配置文件中。

参见提交:

一个简单的 git-config 实现

作为一个 lib,goconfig 项目的根目录是名为 goconfig 的包,为了演示如何用 goconfig 实现一个完整的 git config 命令的功能,在 cmd/gocongig 目录下写了一个 main 包。

可以用如下命令编译安装这一 goconfig 示例:

go get github.com/jiangxin/goconfig/cmd/goconfig

示例代码参见:cmd/goconfig/main.go

欢迎使用 goconfig 并帮助改进

blog comments powered by Disqus