2. 开始
你需要 Python 3.10 或更高版本,以及你喜欢的文本编辑器。我们不需要第三方包或虚拟环境,任何标准的 Python 解释器都可以满足需求。
我们将代码分为两个文件:
- 一个可执行文件,名为
wyag
; - 一个 Python 库,名为
libwyag.py
;
每个软件项目开始时都会有很多样板代码,让我们尽快完成这部分。
首先创建一个(非常简短的)可执行文件。在文本编辑器中创建一个新文件,命名为 wyag
,并复制以下几行:
#!/usr/bin/env python3
import libwyag
libwyag.main()
然后使其可执行:
$ chmod +x wyag
完成了!
接下来是库文件。它必须命名为 libwyag.py
,并与 wyag
可执行文件位于同一目录中。首先在文本编辑器中打开空的 libwyag.py
文件。
我们首先需要导入一些模块(可以逐一复制每个导入,或合并成一行):
Git 是一个命令行应用程序,因此我们需要解析命令行参数的工具。Python 提供了一个很棒的模块名为 argparse,可以为我们完成 99% 的工作。
pythonimport argparse
我们还需要一些基本库中没有的容器类型,特别是
OrderedDict
,它在 collections 中。pythonimport collections
Git 使用的配置文件格式基本上是微软的 INI 格式。可以使用 configparser 模块读取和写入这些文件。
pythonimport configparser
我们还会进行一些日期/时间的操作:
pythonfrom datetime import datetime
需要一次性读取 Unix 的用户/组数据库(
grp
用于组,pwd
用于用户)。这是因为 Git 保存文件的所有者/组 ID,我们希望将其以文本形式美观地显示出来:pythonimport grp, pwd
为了支持
.gitignore
,我们需要匹配如 *.txt 的文件名模式。文件名匹配功能在fnmatch
中:pythonfrom fnmatch import fnmatch
Git 广泛使用 SHA-1 函数。在 Python 中,它位于 hashlib 中。
pythonimport hashlib
只需要使用 math 中的一个函数:
pythonfrom math import ceil
os 和 os.path 提供了一些很好的文件系统抽象例程。
pythonimport os
我们还需要使用一些正则表达式:
pythonimport re
另外需要 sys 来访问实际的命令行参数(在
sys.argv
中):pythonimport sys
Git 使用 zlib 进行所有内容的压缩。Python 中也有 这个功能:
pythonimport zlib
导入完成。我们将频繁处理命令行参数。Python 提供了一个简单但功能强大的解析库 argparse
。这是一个不错的库,但其接口可能并不是最直观的;如果需要,可以参考其 文档。
argparser = argparse.ArgumentParser(description="最简单的内容跟踪器")
我们需要处理子命令(如 git 中的 init
、commit
等)。在 argparse 的术语中,这些被称为“子解析器”。此时我们只需声明我们的 CLI 将使用子解析器,并且所有调用都必须包含一个——你不能只调用 git
,而是要调用 git COMMAND
。
argsubparsers = argparser.add_subparsers(title="Commands", dest="command")
argsubparsers.required = True
dest="command"
参数表示所选择的子解析器的名称将作为字符串返回,存储在名为 command
的字段中。因此,我们只需读取这个字符串并相应地调用正确的函数。按照惯例,我将这些函数称为“桥接函数(bridges functions)”,并以 cmd_
为前缀。桥接函数将解析的参数作为唯一参数,并负责处理和验证它们,然后执行实际命令。
def main(argv=sys.argv[1:]):
args = argparser.parse_args(argv)
match args.command:
case "add" : cmd_add(args)
case "cat-file" : cmd_cat_file(args)
case "check-ignore" : cmd_check_ignore(args)
case "checkout" : cmd_checkout(args)
case "commit" : cmd_commit(args)
case "hash-object" : cmd_hash_object(args)
case "init" : cmd_init(args)
case "log" : cmd_log(args)
case "ls-files" : cmd_ls_files(args)
case "ls-tree" : cmd_ls_tree(args)
case "rev-parse" : cmd_rev_parse(args)
case "rm" : cmd_rm(args)
case "show-ref" : cmd_show_ref(args)
case "status" : cmd_status(args)
case "tag" : cmd_tag(args)
case _ : print("无效命令。")