Go 语言解析 git config
最近做的一个 go 语言的项目需要频繁读写 git config 文件,一些看似现成的解决方案并不能满足需要:
- 不考虑调用外部命令
git config
,因为在 Windows 平台性能差。 - 不考虑
libgit2
,因为会给静态编译和发布带来麻烦。
在 github 上找到一个能够解析 git config 文件的项目 goconfig,该项目的代码直接从 Git 项目的 git/config.c 移植过来,可以确保兼容性。但是这个项目只是一个半成品,因为:
- 不支持多值配置项。Git 的很多设置实际上是多值配置项,例如:
push.pushOption
、remote.<name>.fetch
等。 - 不支持配置文件嵌套,即不支持通过
include.path
指令包含其他配置文件,而这在我们要开发的应用中至关重要。 - 不支持配置文件继承(多级配置)。Git 在读取一个配置项时,会依次读取系统级配置(
/etc/gitconfig
)、用户全局配置($HOME/.gitconfig
)、仓库级配置文件(.git/config
)、嵌套的配置文件(include.path
指向的配置文件)。从优先级上看,仓库级配置文件高于全局配置,更高于系统级配置。 - 不支持写配置文件。
于是派生了一个项目到 jiangxin/goconfig,实现了上述特性。
增加多值配置特性
实际上 Git 配置项,无论单值(如 user.name
),还是多值(如 remote.<name>.fetch
),都应该一视同仁当做多值来处理,这样配置文件嵌套、配置文件继承的处理就非常简单了。即:
- 每一个配置项(如
user.name
)的值是一个字符串数组。 - 如果用户将某个配置项视为单值设置,只取数组的最后一项,作为该配置的唯一值。
- 如果用户将某个配置项视为多值设置,数组所有内容都是该配置项内容。
为此将 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 记录到配置文件中。
参见提交:
- 784eaa8 (Mark values with scope, such as system, global, self, 2019-03-09)
- feb3e92 (String() of GitConfig is ready for saving file, 2019-03-11)
- b826f49 (Save GitConfig to file using Save(), 2019-03-11)
一个简单的 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 并帮助改进