Git提交历史自动生成开发日报:Python脚本实践 📅 发布时间:2026/8/30 4:03:08 👁 浏览次数: 每到下班前或者晚上准备收尾时最纠结的一件事就是“今天到底做了啥”。打开 IDE 看着改过的代码翻着终端里一条条 git 提交脑子里有点模糊可要写成一条清晰的工作进展又总觉得拼不完整。这篇博客想分享一个我最近一直在用的方法把“今天的进度”这件事从手工回忆变成可以量化的技术流程。核心思路很朴素——你的 git 提交历史就是最好的进度素材我们要做的只是把它变成能读、能归档、能直接贴到日报里的内容。文章会带着你从“为什么可以用 git 记录每日进度”开始理解提交信息规范和进度文档之间的关系然后完整写一个 Python 小工具自动读取当天提交记录、按类型统计、生成 Markdown 日报文件。整个过程适合刚接触 Git 的开发者也适合想规范团队日报流程的工程负责人参考。1. 背景与核心概念1.1 “今天的进度”到底是什么在软件研发场景里“今天的进度”通常包含三个层次的信息今天我完成了哪些功能、修复了哪些问题。这些改动发生在哪个模块、哪个需求下。目前处于什么状态明天需要继续做什么。很多团队要求成员提交日报本质上就是为了解决这三个问题。但日报的生成方式往往非常原始下班前打开聊天工具对照着记忆一条一条打字。这会出现两种情况要么今天的 commit 很多根本想不起来每一笔改动对应什么需求要么今天只改了几行代码但实际花了一下午排查问题最后日报里只写了一句“修复一个 Bug”完全体现不出工作价值。如果把 git 使用习惯规范好每天的提交信息本身就是一份天然的时间线。我们要做的事情就是对你的工作过程做一次数字化沉淀。1.2 用 Git 提交历史记录进度为什么可行Git 提交历史具备几个天然适合做进度记录的特点它有时间戳。每一次提交都带着精确的提交时间方便按天、按周筛选。它有作者信息。多人协作时可以通过 author 字段区分各自的进度。它有变更内容。每次提交对应的文件变更、代码差异都能查得到。它不可随意篡改至少修改成本很高比聊天记录、口头汇报更具备“凭证”意义。举个例子你在终端里执行下面的命令就能看到今天的提交记录git log --since2026-01-15 00:00:00 --until2026-01-15 23:59:59 --oneline输出结果大致如下a1b2c3d feat: 完成用户登录模块 d4e5f6a fix: 修复订单列表分页错误 g7h8i9j docs: 补充接口文档如果每天下班前把这段输出整理成日报你的进度记录就具备了客观依据。但手动执行命令、复制输出、再整理格式仍然很繁琐。这就引出下一部分如何规范提交信息让自动化成为可能。1.3 三种常见的“今日进度”记录方式方式优点缺点手动写日报表达自由可以写背景和思考依赖记忆耗时容易遗漏逐个翻 Git 提交客观准确信息碎片化需要二次整理提交信息 自动生成工具效率高规范统一需要前期提交规范配合本文要实现的就是第三种方案。先明确提交规范再通过脚本自动读取提交记录生成一份结构化的 Markdown 日报。你每天只需要执行一条命令就能得到“今天的进度”。2. 环境准备与版本说明在动手写工具之前先把环境梳理清楚。本文示例使用以下环境操作系统Windows 10/11、macOS、Linux 均可Git 命令和 Python 脚本没有平台依赖。Git 版本建议 2.20 以上。低版本也能运行但 log 命令的日期过滤功能建议使用新版本。Python 版本建议 3.8 或更高。本文脚本用到subprocess、datetime、pathlib等标准库不需要额外安装第三方依赖。终端工具Windows 下可以使用 PowerShell 或 CMDmacOS/Linux 下使用自带终端即可。如果你的项目目前还没有使用 Git需要先执行初始化并完成首次提交否则脚本无法读取历史记录git init git add . git commit -m chore: 初始化项目另外这里要提前说明一点不同团队的分支策略、代码托管平台可能不同但 git log 的核心参数和解析思路是通用的。本文示例以最常见的单分支开发场景为主如果你的团队使用 Git Flow 或者多分支并行开发可以在脚本基础上做适配。3. 准备提交信息规范要让脚本准确识别每一次提交属于什么类型前提是提交信息本身有规律。目前业界比较流行的是 Conventional Commits 规范也就是约定式提交。它的核心格式是type(scope): subject其中type表示提交类型。scope是可选的表示影响范围比如模块名、组件名。subject是对本次提交的简短描述。常见的 type 取值包括type含义使用场景feat新功能新增接口、新增页面、新增模块fix修复问题修复 Bug、修复崩溃、修复样式异常docs文档变更修改 README、补充注释、更新接口文档style格式调整调整缩进、补分号、修改空格不影响逻辑refactor重构调整代码结构不改变外部行为perf性能优化优化查询速度、减少内存占用chore构建或工具修改依赖、调整构建配置、升级依赖版本举个例子如果你今天完成了一个登录接口的开发提交信息可以写成feat(auth): 新增用户登录接口如果你修复了订单列表的分页问题可以写成fix(order): 修复分页参数丢失导致的列表显示异常这样的提交信息有两个好处一是人读的时候能快速理解改动意图二是机器读的时候可以通过 type 字段自动归类。接下来要写的脚本本质上就是解析这些结构化信息。4. 编写自动生成“今日进度”的脚本工具这一节是整篇文章的核心。我们会边拆解思路边写一个 Python 脚本。脚本最终通过运行git log获取当天的提交记录然后解析提交信息按类型统计并生成 Markdown 日报文件。4.1 项目结构为了演示清晰我们单独创建一个目录来放脚本daily-progress/ ├── git_daily_report.py └── output/git_daily_report.py是主脚本output目录用来存放生成的日报文件。你可以把脚本直接放进任何已有项目里不需要额外安装依赖。4.2 获取当天时间范围脚本的第一步是确定“今天”的起止时间。这里用 Python 的datetime标准库实现from datetime import datetime, timedelta def get_today_range(): 获取当天零点到第二天零点的时间范围 now datetime.now() start_of_day now.replace(hour0, minute0, second0, microsecond0) end_of_day start_of_day timedelta(days1) return start_of_day.strftime(%Y-%m-%d %H:%M:%S), end_of_day.strftime(%Y-%m-%d %H:%M:%S)这段代码做的事情很简单把当前时间的小时、分钟、秒都重置为 0得到当天零点再往后退一天得到第二天零点。这样得到的起止时间范围可以传给git log的--since和--until参数。为什么不直接用today datetime.now().date()因为git log的日期过滤既支持2026-01-15这种日期格式也支持完整的日期时间格式。使用零点和第二天零点可以精确覆盖当天的所有提交避免时分秒边界问题。4.3 执行 git log 命令接下来通过subprocess执行git log命令获取当天的提交记录。我们只需要提交信息的内容和提交人的名字不关心具体的文件差异所以给git log加上定制格式参数。import subprocess def get_git_log(since, until): 执行 git log 命令获取指定时间段内的提交记录 返回格式每条提交一行包含提交哈希、作者、提交时间、提交信息 cmd [ git, log, f--since{since}, f--until{until}, --prettyformat:%h|%an|%ad|%s, --dateformat:%Y-%m-%d %H:%M, ] try: result subprocess.run(cmd, capture_outputTrue, textTrue, encodingutf-8, checkTrue) return result.stdout.strip().split(\n) if result.stdout.strip() else [] except subprocess.CalledProcessError as e: print(f执行 git log 失败{e.stderr}) return []这里有几个值得注意的点--prettyformat:%h|%an|%ad|%s定义了输出格式每个字段用|分隔。%h是短哈希%an是作者名%ad是按--date格式化的时间%s是提交信息标题。使用capture_outputTrue和textTrue让子进程的输出以字符串形式返回方便后续处理。encodingutf-8是为了避免 Windows 控制台默认编码不是 UTF-8 导致的乱码问题。4.4 解析提交信息拿到原始提交记录后需要解析出每条提交的 type、scope 和 subject。这里用正则表达式提取import re PATTERN re.compile(r^(?Ptype\w)(?:\((?Pscope[\w-])\))?: (?Psubject.)$) def parse_commit_line(line): 解析单条提交记录 输入示例abc123|张三|2026-01-15 14:30|feat(auth): 新增用户登录接口 返回一个字典包含 hash, author, time, type, scope, subject parts line.split(|, 3) if len(parts) ! 4: return None commit_hash, author, commit_time, message parts match PATTERN.match(message) if match: commit_type match.group(type) scope match.group(scope) subject match.group(subject) else: commit_type other scope subject message return { hash: commit_hash, author: author, time: commit_time, type: commit_type, scope: scope, subject: subject, }这段代码的关键在于正则表达式和if/else分支。如果提交信息符合约定式提交规范就正常提取 type、scope 和 subject如果不符合就统一标记为other避免脚本崩溃。这样即使团队里有成员暂时没有严格按规范提交报告也能生成只是这些提交会归入“其他变更”一类。4.5 按类型统计并组织报告解析完每一条提交后需要按 type 分组统计。这一步能让日报从“流水账”变成“结构化汇总”def organize_commits(parsed_commits): 将解析后的提交列表按 type 分组并统计数量 grouped {} type_order [feat, fix, docs, style, refactor, perf, chore, other] for item in parsed_commits: commit_type item[type] if commit_type not in grouped: grouped[commit_type] [] grouped[commit_type].append(item) # 按预定义顺序排序未定义的 type 排到最后 sorted_grouped {t: grouped.get(t, []) for t in type_order} return sorted_grouped这里使用一个固定的type_order列表来控制分组展示的顺序让日报读起来更符合人们的阅读习惯先看新功能再看修复最后看杂项。4.6 生成 Markdown 日报接下来是把分组结果渲染成 Markdown 文件。日报的结构可以包含标题、日期、作者、完成项、问题修复、其他变更、总结。def generate_markdown(grouped, total_count): 根据分组数据生成 Markdown 格式的日报内容 lines [] lines.append(# 今日开发进度报告\n) lines.append(f- 日期{datetime.now().strftime(%Y-%m-%d)}) lines.append(f- 提交总数{total_count}\n) type_names { feat: ## 新功能, fix: ## Bug 修复, docs: ## 文档变更, style: ## 格式调整, refactor: ## 代码重构, perf: ## 性能优化, chore: ## 构建与工具, other: ## 其他变更, } has_content False for commit_type, commits in grouped.items(): if not commits: continue has_content True lines.append(type_names.get(commit_type, f## {commit_type})) for commit in commits: scope_text f{commit[scope]} if commit[scope] else lines.append(f- [{commit[hash]}] {commit[subject]}{scope_text} - {commit[time]}) if not has_content: lines.append(暂无提交记录。) return \n.join(lines) \n这里有一个细节scope_text被放在描述后面用中文括号包起来。原因是在 Markdown 列表里如果scope放在描述前面可能会让整行看起来不够整齐。放在后面作为补充信息阅读体验更好。4.7 写入文件最后一步把生成的内容写入output目录下的 Markdown 文件文件名带日期方便归档def write_report(content, output_diroutput): 将日报内容写入 output 目录文件名包含当天日期 from pathlib import Path output_path Path(output_dir) output_path.mkdir(exist_okTrue) today datetime.now().strftime(%Y-%m-%d) file_path output_path / fdaily-report-{today}.md with open(file_path, w, encodingutf-8) as f: f.write(content) print(f今日日报已生成{file_path.resolve()})使用Path.mkdir(exist_okTrue)可以确保目录存在不存在时自动创建。文件名中的日期部分让每天的日报文件天然独立不会相互覆盖。4.8 主函数与完整代码把上面几个函数串起来形成完整脚本import re import subprocess from datetime import datetime, timedelta from pathlib import Path PATTERN re.compile(r^(?Ptype\w)(?:\((?Pscope[\w-])\))?: (?Psubject.)$) def get_today_range(): now datetime.now() start_of_day now.replace(hour0, minute0, second0, microsecond0) end_of_day start_of_day timedelta(days1) return start_of_day.strftime(%Y-%m-%d %H:%M:%S), end_of_day.strftime(%Y-%m-%d %H:%M:%S) def get_git_log(since, until): cmd [ git, log, f--since{since}, f--until{until}, --prettyformat:%h|%an|%ad|%s, --dateformat:%Y-%m-%d %H:%M, ] try: result subprocess.run(cmd, capture_outputTrue, textTrue, encodingutf-8, checkTrue) return result.stdout.strip().split(\n) if result.stdout.strip() else [] except subprocess.CalledProcessError as e: print(f执行 git log 失败{e.stderr}) return [] def parse_commit_line(line): parts line.split(|, 3) if len(parts) ! 4: return None commit_hash, author, commit_time, message parts match PATTERN.match(message) if match: commit_type match.group(type) scope match.group(scope) subject match.group(subject) else: commit_type other scope subject message return { hash: commit_hash, author: author, time: commit_time, type: commit_type, scope: scope, subject: subject, } def organize_commits(parsed_commits): grouped {} type_order [feat, fix, docs, style, refactor, perf, chore, other] for item in parsed_commits: commit_type item[type] if commit_type not in grouped: grouped[commit_type] [] grouped[commit_type].append(item) sorted_grouped {t: grouped.get(t, []) for t in type_order} return sorted_grouped def generate_markdown(grouped, total_count): lines [] lines.append(# 今日开发进度报告\n) lines.append(f- 日期{datetime.now().strftime(%Y-%m-%d)}) lines.append(f- 提交总数{total_count}\n) type_names { feat: ## 新功能, fix: ## Bug 修复, docs: ## 文档变更, style: ## 格式调整, refactor: ## 代码重构, perf: ## 性能优化, chore: ## 构建与工具, other: ## 其他变更, } has_content False for commit_type, commits in grouped.items(): if not commits: continue has_content True lines.append(type_names.get(commit_type, f## {commit_type})) for commit in commits: scope_text f{commit[scope]} if commit[scope] else lines.append(f- [{commit[hash]}] {commit[subject]}{scope_text} - {commit[time]}) if not has_content: lines.append(暂无提交记录。) return \n.join(lines) \n def write_report(content, output_diroutput): output_path Path(output_dir) output_path.mkdir(exist_okTrue) today datetime.now().strftime(%Y-%m-%d) file_path output_path / fdaily-report-{today}.md with open(file_path, w, encodingutf-8) as f: f.write(content) print(f今日日报已生成{file_path.resolve()}) def main(): since, until get_today_range() raw_lines get_git_log(since, until) if not raw_lines: print(今天暂无提交记录。) return parsed_commits [] for line in raw_lines: item parse_commit_line(line) if item: parsed_commits.append(item) if not parsed_commits: print(没有解析到有效的提交记录。) return grouped organize_commits(parsed_commits) total_count len(parsed_commits) content generate_markdown(grouped, total_count) write_report(content) if __name__ __main__: main()4.9 运行与验证把脚本保存为git_daily_report.py在项目根目录执行python git_daily_report.py如果项目里已经有一定数量的提交你会在终端看到类似下面的提示今日日报已生成/path/to/project/output/daily-report-2026-01-15.md打开生成的 Markdown 文件内容大致如下# 今日开发进度报告 - 日期2026-01-15 - 提交总数5 ## 新功能 - [a1b2c3d] 新增用户登录接口auth - 2026-01-15 10:23 - [e5f6a7b] 完善个人中心页面user - 2026-01-15 15:40 ## Bug 修复 - [d4e5f6a] 修复订单列表分页参数丢失问题order - 2026-01-15 11:02 ## 文档变更 - [g7h8i9j] 补充接口文档docs - 2026-01-15 18:20 ## 构建与工具 - [j1k2l3m] 升级 lombok 依赖版本build - 2026-01-15 09:15 ## 其他变更 - [n4o5p6q] 临时提交 - 2026-01-15 16:00看到上面的输出你会发现这份日报的基本结构已经完整。你只需要在此基础上补充项目背景、风险说明和明天的计划就是一份信息密度很高的日报。5. 进阶让日报内容更丰富5.1 合并同 scope 的提交如果当天 feat 类型提交很多可以在 Markdown 生成阶段先按 scope 分组再在 scope 下罗列提交这样报告阅读起来更有层次。5.2 输出 JSON 格式有些团队会通过企业微信机器人、钉钉机器人或者自建平台来收集日报。脚本可以在生成 Markdown 的同时输出一份 JSONimport json def generate_json(grouped): result { date: datetime.now().strftime(%Y-%m-%d), commits: [] } for commit_type, commits in grouped.items(): for commit in commits: result[commits].append(commit) return json.dumps(result, ensure_asciiFalse, indent2)这样其他系统拿到 JSON 后可以自由决定展示方式比如通过机器人推送到群里。5.3 指定日期生成历史进度默认脚本只处理当天但有时候你需要补发前一天的日报。可以将主函数增加一个可选参数指定日期import argparse def get_range_by_date(date_str): target_date datetime.strptime(date_str, %Y-%m-%d) start_of_day target_date.replace(hour0, minute0, second0, microsecond0) end_of_day start_of_day timedelta(days1) return start_of_day.strftime(%Y-%m-%d %H:%M:%S), end_of_day.strftime(%Y-%m-%d %H:%M:%S)5.4 配合定时任务如果你想在下班前自动生成一份日报草稿可以在系统里配置定时任务。Windows 可以使用任务计划程序macOS/Linux 可以使用 cron。以 cron 为例每天 17 点 30 分执行脚本30 17 * * * cd /path/to/project /usr/bin/python3 git_daily_report.py需要注意这样生成的只是“草稿”仍然需要人工审核和补充说明不建议完全替代人工总结。6. 常见问题与排查思路6.1 执行脚本后提示“今天暂无提交记录”原因排查步骤解决方案今天确实没有提交在终端执行git log --oneline -3检查最近提交时间先完成一次有意义的提交再运行脚本时区不一致导致过滤范围错误执行git log --since今天零点看是否有记录检查系统时区与 Git 配置必要时手动指定时区脚本在非 Git 仓库中运行执行git rev-parse --git-dir检查是否为 Git 仓库在项目根目录运行脚本或先执行git init6.2 生成的日报里所有提交都归入“其他变更”原因分析这通常是因为提交信息不符合正则表达式格式或者提交人没有按约定式提交规范来写提交信息。排查步骤执行git log --format%s查看最近的提交信息。确认提交信息是否包含feat:、fix:等前缀。检查中英文冒号是否混用。规范中要求使用英文冒号加空格。解决方案调整提交习惯也可以让脚本对不符合规范的提交给出提示方便后续整改if not match: print(f[提示] 提交信息不规范{message})6.3 输出乱码在 Windows 的 PowerShell 或 CMD 中Python 输出中文可能乱码。常见原因是控制台编码问题。解决方案是在脚本最前面加上一行import sys sys.stdout.reconfigure(encodingutf-8)或者在设置脚本时显式指定环境编码。7. 最佳实践与工程建议到这里“今天的进度”这个工具已经能跑起来了。不过工具只是整个流程的一部分真正提升效率的还是规范和习惯。我这里给你几条对实际项目有帮助的建议。7.1 把提交信息规范固化成团队文档脚本能正确归类的前提是团队成员都按规范提交。建议在仓库根目录维护一份CONTRIBUTING.md把提交信息格式、type 取值、示例写清楚。也可以在 Git Hook 里做校验在prepare-commit-msg或commit-msg钩子里简单检查提交信息格式不合法就阻止提交。这样可以从源头保证数据质量。7.2 提交粒度要适中描述要具体有的人喜欢一小时提交一次有的人攒一天推一个 commit。从日报生成的角度看提交粒度太粗会导致一条提交信息里包含太多内容难以归类粒度太细又会生成大量低价值碎片提交让日报变得冗长。比较推荐的标准是完成一个逻辑闭环就提交一次。比如“新增登录接口”和“修复分页问题”是两次独立的工作就应该分成两条提交。7.3 日报不要完全自动化我始终觉得脚本生成的日报适合作为“素材底稿”而不应该直接发送。因为真正有价值的进度报告还需要包含你没写进 commit 里的内容比如今天调研了一个方案对比了两种实现最终选择了其中一种。今天大部分时间在排查线上问题最终定位到根因但还没修复。今天有一个任务被阻塞了需要产品经理确认需求。这部分信息 commit 里没有但可能恰恰是日报最有价值的内容。所以正确的使用方式是先运行脚本得到客观数据再人工补充背景、风险和明天计划。7.4 定期复盘比每天报告更重要“今天的进度”记录下来只是第一步更值得做的是每周或每两周回头看看这段时间的提交趋势。比如git log --author你的名字 --since2 weeks ago --prettyformat:%s如果发现大量提交都是fix说明最近项目质量稳定性有问题如果大量提交都是docs需要思考是不是文档流程拖慢了进度如果某个模块的提交特别集中要评估它是不是变成了技术债集中地。这些都是把 Git 数据用起来的延伸方向。8. 写在最后这篇文章从“今天的进度怎么写”这个问题出发带着你完成了三件事理解了为什么 Git 提交历史是可靠的进度来源建立了一套最小可用的提交信息规范写了一个能自动读取当天提交并生成 Markdown 日报的 Python 脚本。这套方案不复杂也不依赖第三方平台任何 Git 仓库都能跑起来。如果你刚接触 Git可以先从规范自己的提交信息开始让每一次git commit都变成可检索、可理解的工作记录。如果你已经有一定工程经验可以考虑把脚本推广到团队配合定时任务、代码托管平台 Webhook 自动推送日报草稿。下一次有人问你“今天做了什么”你不再需要翻聊天记录回忆直接用一条命令就能得到答案。你可以把脚本复制到自己项目里试跑一次再用真实 commit 数据验证效果。如果过程中遇到问题欢迎在评论区留言讨论。