Skip to content

2. 开始

你需要 Python 3.10 或更高版本,以及你喜欢的文本编辑器。我们不需要第三方包或虚拟环境,任何标准的 Python 解释器都可以满足需求。

我们将代码分为两个文件:

  • 一个可执行文件,名为 wyag
  • 一个 Python 库,名为 libwyag.py

每个软件项目开始时都会有很多样板代码,让我们尽快完成这部分。

首先创建一个(非常简短的)可执行文件。在文本编辑器中创建一个新文件,命名为 wyag,并复制以下几行:

python
#!/usr/bin/env python3

import libwyag
libwyag.main()

然后使其可执行:

shell
$ chmod +x wyag

完成了!

接下来是库文件。它必须命名为 libwyag.py,并与 wyag 可执行文件位于同一目录中。首先在文本编辑器中打开空的 libwyag.py 文件。

我们首先需要导入一些模块(可以逐一复制每个导入,或合并成一行):

  • Git 是一个命令行应用程序,因此我们需要解析命令行参数的工具。Python 提供了一个很棒的模块名为 argparse,可以为我们完成 99% 的工作。

    python
    import argparse
  • 我们还需要一些基本库中没有的容器类型,特别是 OrderedDict,它在 collections 中。

    python
    import collections
  • Git 使用的配置文件格式基本上是微软的 INI 格式。可以使用 configparser 模块读取和写入这些文件。

    python
    import configparser
  • 我们还会进行一些日期/时间的操作:

    python
    from datetime import datetime
  • 需要一次性读取 Unix 的用户/组数据库(grp 用于组,pwd 用于用户)。这是因为 Git 保存文件的所有者/组 ID,我们希望将其以文本形式美观地显示出来:

    python
    import grp, pwd
  • 为了支持 .gitignore,我们需要匹配如 *.txt 的文件名模式。文件名匹配功能在 fnmatch 中:

    python
    from fnmatch import fnmatch
  • Git 广泛使用 SHA-1 函数。在 Python 中,它位于 hashlib 中。

    python
    import hashlib
  • 只需要使用 math 中的一个函数:

    python
    from math import ceil
  • osos.path 提供了一些很好的文件系统抽象例程。

    python
    import os
  • 我们还需要使用一些正则表达式:

    python
    import re
  • 另外需要 sys 来访问实际的命令行参数(在 sys.argv 中):

    python
    import sys
  • Git 使用 zlib 进行所有内容的压缩。Python 中也有 这个功能

    python
    import zlib

导入完成。我们将频繁处理命令行参数。Python 提供了一个简单但功能强大的解析库 argparse。这是一个不错的库,但其接口可能并不是最直观的;如果需要,可以参考其 文档

python
argparser = argparse.ArgumentParser(description="最简单的内容跟踪器")

我们需要处理子命令(如 git 中的 initcommit 等)。在 argparse 的术语中,这些被称为“子解析器”。此时我们只需声明我们的 CLI 将使用子解析器,并且所有调用都必须包含一个——你不能只调用 git,而是要调用 git COMMAND

python
argsubparsers = argparser.add_subparsers(title="Commands", dest="command")
argsubparsers.required = True

dest="command" 参数表示所选择的子解析器的名称将作为字符串返回,存储在名为 command 的字段中。因此,我们只需读取这个字符串并相应地调用正确的函数。按照惯例,我将这些函数称为“桥接函数(bridges functions)”,并以 cmd_ 为前缀。桥接函数将解析的参数作为唯一参数,并负责处理和验证它们,然后执行实际命令。

python
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("无效命令。")