0%

pygit2 - 获取历史记录信息并打印

  1. 预备知识
    1. 提交 - commit类
    2. 对比 - diff类
    3. 遍历提交
    4. 第一次提交的父级是谁?
  2. python脚本实现

预备知识

pygit2 - libgit2 bindings in Python — pygit2 1.9.0 documentation

提交 - commit类

repo.head.target得到当前分支的最后一次提交的hash

repo[repo.head.target]得到最后一次提交的提交信息,也可以通过这个方法获取任意一次提交的信息。

commit.author作者信息(https://www.pygit2.org/objects.html#pygit2.Signature

  • commit.author.name
  • commit.author.email
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
print(repo.head.target)  # 5b26c4c177c25f0c142c885ee23f2969b8457fe1
last = repo[repo.head.target]
print('author:', last.author) # <_pygit2.Signature object at 0x000002C2B662BF60>
print('commit_time:', last.commit_time) # 1645727372
print('commit_time_offset:', last.commit_time_offset) # 480
print('committer:', last.committer) # <_pygit2.Signature object at 0x000002C2B662BF60>
print('filemode:', last.filemode) # None
print('gpg_signature:', last.gpg_signature) # (None, None)
print('hex:', last.hex) # 5b26c4c177c25f0c142c885ee23f2969b8457fe1
print('id:', last.id) # 5b26c4c177c25f0c142c885ee23f2969b8457fe1
print('message:', last.message) # 2022年02月25日 02:29:29
print('message_encoding:', last.message_encoding) # None
print('message_trailers:', last.message_trailers) # {}
print('name:', last.name) # None
print('oid:', last.oid) # 5b26c4c177c25f0c142c885ee23f2969b8457fe1
print('parent_ids:', last.parent_ids) # [553aa5787427f29201392eec4165c68812e441cd]
print('parents:', last.parents) # [<pygit2.Object{commit:553aa5787427f29201392eec4165c68812e441cd}>]
print('peel:', last.peel) # <built-in method peel of _pygit2.Commit object at 0x000002C2B8199B40>
print('raw_message:', last.raw_message) # b'2022\xe5\xb9\xb402\xe6\x9c\x8825\xe6\x97\xa5 02:29:29'
print('raw_name:', last.raw_name) # None
print('read_raw:', last.read_raw) # <built-in method read_raw of _pygit2.Commit object at 0x000002C2B8199B40>
print('short_id:', last.short_id) # 5b26c4c
print('tree:', last.tree) # <pygit2.Object{tree:99299d26e0105889b96c413c46fa4c97aea89056}>
print('tree_id:', last.tree_id) # 99299d26e0105889b96c413c46fa4c97aea89056
print('type:', last.type) # 1
print('type_str:', last.type_str) # commit

对比 - diff类

https://www.pygit2.org/diff.html

对比两次提交

1
diff = repo.diff('HEAD~6', 'HEAD~7')

提交中的每一小块(patch),也就是每一个文件

1
patches = [p for p in diff]

文件中的每一个更改区域,第几行到第几行修改了多少这样

1
hunks = [h for h in patch]

文件的更改行数

1
2
patches = [p.line_stats for p in diff]  # ([更改的行的前后留白位置], [删除的行数], [新增的行数])
# [(5, 1, 1), (0, 5, 0), (0, 1, 2), (0, 0, 2), (0, 0, 95), (0, 0, 105)]

文件更改类型

1
2
patches = [p.delta.status for p in diff]  # 3 modify, 1 delete, 2 create
# [3, 1, 3, 2, 2, 2]

遍历提交

使用repo.walk

1
2
3
last = repo[repo.head.target]
for commit in repo.walk(last.id, git.GIT_SORT_TIME | git.GIT_SORT_REVERSE):
print(commit.message)

第一次提交的父级是谁?

failed to create commit: current tip is not the first parent error during commit in libgit2 - Stack Overflow

使用diff()函数时,需要填入两个分支的hash进行对比,那么对于第一次提交,其父级固定是4b825dc642cb6eb9a060e54bf8d69288fbee4904,所有git仓库创建时,其父级分支的hash都是这个。

python脚本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import time
from colorama import init
import os
import pygit2 as git

# 处理cmd颜色
init(autoreset=True)


def red(str: str):
return '\x1b[31m{:s}\x1b[0m'.format(str)


def green(str: str):
return '\x1b[32m{:s}\x1b[0m'.format(str)


def yellow(str: str):
return '\x1b[33m{:s}\x1b[0m'.format(str)


def blue(str: str):
return '\x1b[96m{:s}\x1b[0m'.format(str)


def gray(str: str):
return '\x1b[90m{:s}\x1b[0m'.format(str)


def get_config(repo: git.Repository,
key: str):
try:
val: str = repo.config.__getitem__(key)
return val
except:
print('获取配置[{:s}]失败,请通过以下命令配置:'.format(key))
print('git config --global {:s}=[...]'.format(key))
return None


def get_current_branch(repo: git.Repository):
for name in repo.branches.local:
branch = repo.lookup_branch(name)
if branch.is_head():
bName: str = branch.branch_name
return bName
return None


if __name__ == '__main__':
bFull = input('是否完整显示? [y/N]')
bFull = True if bFull == 'y' else False

# 读取仓库信息
repo_path = os.getcwd()
try:
repo = git.Repository(repo_path)
except:
print(red('[{:s}]不是一个git仓库').format(repo_path))
exit()

if repo.head_is_unborn:
print(gray('该仓库未发生过提交'))
exit()

last = repo[repo.head.target]
for commit in repo.walk(last.id, git.GIT_SORT_TIME | git.GIT_SORT_REVERSE):
hash = blue(str(commit.id)[0:10] + '...')
_time = yellow(time.strftime('%Y年%m月%d日 %H:%M:%S',
time.localtime(commit.commit_time)))
name = commit.author.name
email = commit.author.email

diff = repo.diff(commit.id, commit.parent_ids[0] if len(
commit.parent_ids) > 0 else '4b825dc642cb6eb9a060e54bf8d69288fbee4904')
new_lines = 0
rmv_lines = 0
files_changed = [[], [], []]
for patch in diff:
# ([更改的行的前后留白位置], [删除的行数], [新增的行数])
line_stat = patch.line_stats
new_lines += line_stat[2]
rmv_lines += line_stat[1]

line_modi = '{:s} {:s}'.format(green('+{:d}'.format(line_stat[2])), red(
'-{:d}'.format(line_stat[1]))) if patch.delta.is_binary == False else ''

if bFull: # 完整显示
if patch.delta.status == 2: # create
files_changed[0].append('* {type} {file} {line_modi}'.format(
file=gray(patch.delta.new_file.path),
type=green('create'),
line_modi=line_modi)
)
elif patch.delta.status == 1: # delete
files_changed[2].append('* {type} {file} {line_modi}'.format(
file=gray(patch.delta.new_file.path),
type=red('delete'),
line_modi=line_modi)
)
else: # modify and other
files_changed[1].append('* {type} {file} {line_modi}'.format(
file=gray(patch.delta.new_file.path),
type=yellow('modify'),
line_modi=line_modi)
)

files_changed = files_changed[0] + files_changed[1] + files_changed[2]

msg = commit.message.replace('\n', '')
print(
'{hash} {time} {author} {new_lines} {rmv_lines} {msg}'.format(
hash=hash, time=_time, msg=msg,
author=gray('{name}[{email}]'.format(name=name, email=email)),
new_lines=green('+{:d}'.format(new_lines)),
rmv_lines=red('-{:d}'.format(rmv_lines)),
)
)
for item in files_changed:
print(item)