跳转到主要内容

使用SQLite分析Git历史的工具

项目描述

git历史记录

PyPI Changelog Tests License

使用SQLite分析Git历史的工具

关于此项目的背景信息,请参阅 git历史记录:使用Git和SQLite收集的数据分析工具

安装

使用pip安装此工具

$ pip install git-history

演示

git-history-demos.datasette.io托管使用此工具创建的三个示例数据库

演示是通过DatasetteGoogle Cloud Run上由此GitHub Actions工作流程部署的。

使用方法

此工具可用于针对包含JSON、CSV/TSV或其他格式文件以及多个版本在Git历史中跟踪的Git仓库。阅读Git scraping: track changes over time by scraping to a Git repository了解如何创建此类仓库。

file命令分析仓库中单个文件的历史,并生成一个SQLite数据库表,表示该文件随时间的变化版本。

假设该文件包含多个对象,例如,抓取停电地图的结果或记录满的CSV文件。

假设您有一个名为incidents.json的文件,它是一个对象的JSON数组,该文件在仓库中有多个版本。该文件的每个版本可能看起来像这样

[
    {
        "IncidentID": "abc123",
        "Location": "Corner of 4th and Vermont",
        "Type": "fire"
    },
    {
        "IncidentID": "cde448",
        "Location": "555 West Example Drive",
        "Type": "medical"
    }
]

将工作目录切换到相关GitHub仓库,并运行以下命令

git-history file incidents.db incidents.json

这将创建一个名为incidents.db的新SQLite数据库,包含三个表

  • commits表包含每行提交,有一个hash列、commit_at日期以及一个到namespace的外键。
  • item表包含每个版本中filename.json文件的每行记录,有一个额外的_commit列,是该表到commit表的外键。
  • namespaces表包含一行。这允许您使用下面描述的--namespace选项为不同的文件构建多个表。

此示例的数据库模式如下所示

CREATE TABLE [namespaces] (
   [id] INTEGER PRIMARY KEY,
   [name] TEXT
);
CREATE UNIQUE INDEX [idx_namespaces_name]
    ON [namespaces] ([name]);
CREATE TABLE [commits] (
   [id] INTEGER PRIMARY KEY,
   [namespace] INTEGER REFERENCES [namespaces]([id]),
   [hash] TEXT,
   [commit_at] TEXT
);
CREATE UNIQUE INDEX [idx_commits_namespace_hash]
    ON [commits] ([namespace], [hash]);
CREATE TABLE [item] (
   [IncidentID] TEXT,
   [Location] TEXT,
   [Type] TEXT
);

如果您有10个历史版本的incidents.json文件,并且每个文件包含30个事件,您将在item表中得到10 * 30 = 300行。

使用ID跟踪单个项目的历史

如果您的对象具有唯一标识符或多个列的组合形成一个唯一标识符,您可以使用--id选项来消除重复并跟踪每个项目随时间的变化。

这为应用此工具提供了一种更有趣的方法。

如果有一个名为IncidentID的唯一标识符列,您可以运行以下命令

git-history file incidents.db incidents.json --id IncidentID

这里使用的数据库模式与未使用--id选项时使用的模式非常不同。

如果您已导入历史记录,则该命令将跳过它已经看到的任何提交,并仅处理新的提交。这意味着尽管初始导入可能很慢,但后续导入应该会运行得更快。

该命令将创建六个表 - commitsitemitem_versioncolumnsitem_changednamespaces

以下是完整的模式

CREATE TABLE [namespaces] (
   [id] INTEGER PRIMARY KEY,
   [name] TEXT
);
CREATE UNIQUE INDEX [idx_namespaces_name]
    ON [namespaces] ([name]);
CREATE TABLE [commits] (
   [id] INTEGER PRIMARY KEY,
   [namespace] INTEGER REFERENCES [namespaces]([id]),
   [hash] TEXT,
   [commit_at] TEXT
);
CREATE UNIQUE INDEX [idx_commits_namespace_hash]
    ON [commits] ([namespace], [hash]);
CREATE TABLE [item] (
   [_id] INTEGER PRIMARY KEY,
   [_item_id] TEXT
, [IncidentID] TEXT, [Location] TEXT, [Type] TEXT, [_commit] INTEGER);
CREATE UNIQUE INDEX [idx_item__item_id]
    ON [item] ([_item_id]);
CREATE TABLE [item_version] (
   [_id] INTEGER PRIMARY KEY,
   [_item] INTEGER REFERENCES [item]([_id]),
   [_version] INTEGER,
   [_commit] INTEGER REFERENCES [commits]([id]),
   [IncidentID] TEXT,
   [Location] TEXT,
   [Type] TEXT,
   [_item_full_hash] TEXT
);
CREATE TABLE [columns] (
   [id] INTEGER PRIMARY KEY,
   [namespace] INTEGER REFERENCES [namespaces]([id]),
   [name] TEXT
);
CREATE UNIQUE INDEX [idx_columns_namespace_name]
    ON [columns] ([namespace], [name]);
CREATE TABLE [item_changed] (
   [item_version] INTEGER REFERENCES [item_version]([_id]),
   [column] INTEGER REFERENCES [columns]([id]),
   PRIMARY KEY ([item_version], [column])
);
CREATE VIEW item_version_detail AS select
  commits.commit_at as _commit_at,
  commits.hash as _commit_hash,
  item_version.*,
  (
    select json_group_array(name) from columns
    where id in (
      select column from item_changed
      where item_version = item_version._id
    )
) as _changed_columns
from item_version
  join commits on commits.id = item_version._commit;
CREATE INDEX [idx_item_version__item]
    ON [item_version] ([_item]);

item表

item表将包含每行的最新版本,通过ID消除重复,并包含以下附加列

  • _id - 一个数字整数主键,用作从item_version表的外键。
  • _item_id - 使用命令的--id选项指定的列值的哈希。在处理新版本时用于消除重复。
  • _commit - 到commit表的外键,表示修改此项目的最新提交。

item_version表

item_version表将包含每个捕获的不同版本的行,以及以下列

  • _id - 项目版本记录的数字ID。
  • _item - 到item表的外键。
  • _version - 数字版本号,从1开始,对每个捕获的版本递增。
  • _commit - 到commit表的外键。
  • _item_full_hash - 项目的此版本的哈希。此工具内部使用以识别提交之间已更改的项目。

该表中的其他列代表自上一版本以来发生变化的原数据中的列。如果值没有变化,它将用 null 表示。

如果一个值之前已设置但已改回 null,则在 item_version 行中它仍将表示为 null。您可以使用以下描述的 item_changed 多对多表来识别这些。

您可以使用 --full-versions 选项在每个版本存储项目的完整副本,而不是仅存储已更改的列。

item_version_detail 视图

此 SQL 视图将 item_versioncommits 连接,以添加三个额外的列:带有提交日期的 _commit_at 和带有 Git 提交哈希的 _commit_hash

item_changed

此多对多表指示 item_version 中确切地更改了哪些列。

  • item_versionitem_version 表中一行的外键。
  • columncolumns 表中一行的外键。

此表将有最多的行数,因此它仅存储两个整数以节省空间。

columns

columns 表存储列名。它由 item_changed 引用。

  • id - 整数 ID。
  • name - 列的名称。
  • namespace - 外键到 namespaces,如果多个文件历史记录共享相同的数据库。

保留的列名

请注意,_id_item_full_hash_item_item_id_version_commit_item_id_commit_at_commit_hash_changed_columnsrowid 被认为是此工具的保留列名。

如果您的数据包含这些中的任何一项,它们将被重命名,以添加尾随下划线,例如 _id__item__version_,以避免与保留列冲突。

如果您有一个名为 _commit_ 的列,它也将被重命名,添加额外的尾随下划线,因此 _commit_ 变为 _commit___commit__ 变为 _commit___

附加选项

  • --repo DIRECTORY - 如果不是当前工作目录,则为 Git 仓库的路径。
  • --branch TEXT - 要分析的 Git 分支 - 默认为 main
  • --id TEXT - 如上所述:传递一个或多个唯一标识记录的列,以便可以随时间计算该记录的变化。
  • --full-versions - 而不是在 item_version 表记录中仅记录已更改的列,记录每个版本的完整副本。
  • --ignore TEXT - 要忽略的一列或多列 - 它们将不包括在结果数据库中。
  • --csv - 将数据视为 CSV 或 TSV 而不是 JSON,并尝试猜测正确的方言。
  • --dialect - 使用特定的 CSV 方言。选项为 excelexcel-tabunix - 有关详细信息,请参阅 Python CSV 文档
  • --skip TEXT - 要跳过的一个或多个完整 Git 提交哈希。如果您的修订历史中的一些数据损坏,防止此工具工作,则可以使用此选项。
  • --start-at TEXT - 跳过指定提交哈希之前的提交。
  • --start-after TEXT - 跳过包括指定提交哈希在内的提交,然后从下一个提交开始处理。
  • --convert TEXT - 用于转换的自定义 Python 代码,如下所述。
  • --import TEXT - 为 --convert 导入的附加 Python 模块。
  • --ignore-duplicate-ids - 如果一个文件的单个版本中具有相同的 ID 多次,则工具将带错误退出。使用此选项忽略此并选择两个重复项中的第一个。
  • --namespace TEXT - 如果您希望将多个不同文件的历史记录包含在同一数据库中,请使用此选项。默认值为 item,但您可以将其设置为其他值,这将生成名为 yournamespaceyournamespace_version 的表格。
  • --wal - 在创建的数据库文件上启用 WAL 模式。如果您计划在 git-history 创建数据库的同时对数据库运行查询,请使用此选项。
  • --silent - 不显示进度条。

CSV 和 TSV 数据

如果您的存储库中的数据是 CSV 或 TSV 文件,您可以通过添加 --csv 选项来处理它。这将尝试检测文件使用的分隔符,因此相同的选项适用于逗号分隔和制表符分隔的值。

git-history file trees.db trees.csv --id TreeID

您还可以使用 --dialect 选项指定 CSV 语法。

使用 --convert 进行自定义转换

如果您的数据不是 CSV/TSV 或平面 JSON 数组,您可以使用 --convert 选项对其进行重塑。

此工具需要的格式是字典数组,如上面所示的 incidents.json 示例。

如果您的数据不适合此形状,您可以提供一段 Python 代码,将每个存储文件的磁盘内容转换为 Python 字典列表。

例如,如果您的存储文件每个都像这样

{
    "incidents": [
        {
            "id": "552",
            "name": "Hawthorne Fire",
            "engines": 3
        },
        {
            "id": "556",
            "name": "Merlin Fire",
            "engines": 1
        }
    ]
}

您可以使用以下 Python 片段将它们转换为所需的格式

json.loads(content)["incidents"]

(默认情况下,将 json 模块暴露给您的自定义函数。)

然后您将像这样运行工具

git-history file database.db incidents.json \
  --id id \
  --convert 'json.loads(content)["incidents"]'

content 变量始终是一个表示仓库历史中特定时刻文件内容的 bytes 对象。

您可以使用 --import 导入额外的模块。以下示例显示了如何使用 --import 读取使用 ; 作为分隔符的 CSV 文件

git-history file trees.db ../sf-tree-history/Street_Tree_List.csv \
  --repo ../sf-tree-history \
  --import csv \
  --import io \
  --convert '
    fp = io.StringIO(content.decode("utf-8"))
    return list(csv.DictReader(fp, delimiter=";"))
    ' \
  --id TreeID

您可以使用 --import xml.etree.ElementTree 导入嵌套模块,例如 ElementTree,然后在函数体中引用它们为 xml.etree.ElementTree。例如,如果您的跟踪数据在看起来像这样的 items.xml 文件中

<items>
  <item id="1" name="One" />
  <item id="2" name="Two" />
  <item id="3" name="Three" />
</item>

您可以使用以下 --convert 脚本来加载它

git-history file items.xml --convert '
tree = xml.etree.ElementTree.fromstring(content)
return [el.attrib for el in tree.iter("item")]
' --import xml.etree.ElementTree --id id

如果您的 Python 代码跨越多行,则需要包含一个 return 语句。

您还可以在 --convert 代码中使用 Python 生成器,例如

git-history file stats.db package-stats/stats.json \
    --repo package-stats \
    --convert '
    data = json.loads(content)
    for key, counts in data.items():
        for date, count in counts.items():
            yield {
                "package": key,
                "date": date,
                "count": count
            }
    ' --id package --id date

此转换函数期望数据看起来像这样

{
    "airtable-export": {
        "2021-05-18": 66,
        "2021-05-19": 60,
        "2021-05-20": 87
    }
}

开发

要为此工具做出贡献,首先检出代码。然后创建一个新的虚拟环境

cd git-history
python -m venv venv
source venv/bin/activate

或者如果您正在使用 pipenv

pipenv shell

现在安装依赖关系并测试依赖关系

pip install -e '.[test]'

要运行测试

pytest

要更新本 README 文件中的模式示例

cog -r README.md

项目详情


下载文件

下载适用于您的平台文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源分布

git-history-0.6.1.tar.gz (22.3 kB 查看散列)

上传时间

构建分布

git_history-0.6.1-py3-none-any.whl (17.6 kB 查看散列)

上传于 Python 3

由以下机构支持