You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从多个GitHub仓库生成并维护单个CHANGELOG.md?

用Python自动化维护多GitHub仓库的统一CHANGELOG.md

完全可以用Python实现这个需求,下面是具体思路、示例脚本和优化建议:

核心实现思路

1. 仓库配置管理

用硬编码列表或配置文件(JSON/YAML)维护所有需要监控的仓库信息(owner、repo名称),方便后续扩展或调整,不用频繁修改脚本逻辑。

2. 拉取仓库变更数据

借助PyGitHub库调用GitHub API,获取每个仓库的发布版本(Releases)、标签(Tags)和提交记录。可以按需过滤:

  • 跳过草稿/预发布版本
  • 忽略合并提交
  • 只保留符合语义化规范的提交(如feat:/fix:前缀)

3. 统一变更格式

为所有仓库的变更定义统一的排版规则,比如:

  • 按仓库分组,用二级标题标识仓库名
  • 每个版本用三级标题标注版本号和发布日期
  • 提交记录用无序列表展示

4. 增量更新与去重

读取现有CHANGELOG.md,或用单独的状态文件跟踪已处理的版本/提交哈希,只新增未收录的内容,避免重复生成。

5. 自动化触发

将脚本结合GitHub Actions,设置定时任务或监听仓库事件(新tag/push),自动运行脚本并提交更新后的CHANGELOG。

简化示例脚本

首先安装依赖:

pip install PyGitHub

脚本代码:

from github import Github
import datetime
import os
import json

# 配置:从环境变量读取GitHub Token,避免硬编码
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
# 监控的仓库列表
REPOS = [
    {"owner": "your-username", "repo": "repo-1"},
    {"owner": "your-username", "repo": "repo-2"},
    # 添加更多仓库...
]
# 状态文件路径,用于跟踪已处理的版本
STATE_FILE = "changelog_state.json"

def load_state():
    """加载上次处理的状态"""
    if os.path.exists(STATE_FILE):
        with open(STATE_FILE, "r") as f:
            return json.load(f)
    return {}

def save_state(state):
    """保存最新处理状态"""
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)

def get_repo_changes(repo_obj, last_processed_tag):
    """获取仓库自上次处理后的新变更"""
    changes = []
    # 按发布时间倒序获取所有版本
    releases = sorted(repo_obj.get_releases(), key=lambda r: r.created_at, reverse=True)
    
    for release in releases:
        # 跳过草稿、预发布,以及已经处理过的版本
        if release.draft or release.prerelease or release.tag_name == last_processed_tag:
            continue
        
        # 获取版本对应的提交记录
        try:
            # 对比当前版本与上一个版本的差异
            prev_release = next((r for r in releases if r.created_at < release.created_at), None)
            if prev_release:
                compare = repo_obj.compare(prev_release.tag_name, release.tag_name)
                commits = compare.commits
            else:
                # 第一个版本,取所有提交到该tag的记录
                commits = repo_obj.get_commits(sha=release.tag_name)
            
            # 过滤提交,只保留有意义的信息
            commit_entries = []
            for commit in commits:
                msg_line = commit.commit.message.split("\n")[0]
                # 跳过合并提交
                if not msg_line.startswith("Merge"):
                    commit_entries.append(f"- {msg_line}")
            
            # 整理当前版本的变更条目
            change_entry = f"### {release.tag_name} ({release.created_at.strftime('%Y-%m-%d')})\n"
            change_entry += "\n".join(commit_entries) + "\n\n"
            changes.append({
                "repo": repo_obj.name,
                "entry": change_entry,
                "tag": release.tag_name,
                "date": release.created_at.isoformat()
            })
        except Exception as e:
            print(f"处理仓库 {repo_obj.name} 的版本 {release.tag_name} 时出错: {str(e)}")
            continue
    
    return changes

def update_changelog(all_changes):
    """更新统一的CHANGELOG.md"""
    changelog_path = "CHANGELOG.md"
    # 读取现有内容,保留头部信息
    if os.path.exists(changelog_path):
        with open(changelog_path, "r") as f:
            existing_content = f.read()
        # 拆分头部和现有变更内容(假设头部到第一个二级标题前)
        if "## " in existing_content:
            header, existing_entries = existing_content.split("## ", 1)
            existing_content = header + "## " + existing_entries
        else:
            existing_content = "# CHANGELOG\n\n所有监控仓库的变更记录:\n\n"
    else:
        existing_content = "# CHANGELOG\n\n所有监控仓库的变更记录:\n\n"
    
    # 按时间倒序排序新变更
    sorted_changes = sorted(all_changes, key=lambda x: datetime.datetime.fromisoformat(x["date"]), reverse=True)
    
    # 按仓库分组整理新内容
    repo_groups = {}
    for change in sorted_changes:
        repo_name = change["repo"]
        if repo_name not in repo_groups:
            repo_groups[repo_name] = []
        repo_groups[repo_name].append(change["entry"])
    
    # 生成新的变更内容,插入到现有内容的头部(最新变更在前)
    new_entries = ""
    for repo in sorted(repo_groups.keys()):
        new_entries += f"## {repo}\n\n"
        new_entries += "".join(repo_groups[repo])
    
    # 合并现有内容和新内容(避免重复)
    final_content = new_entries + existing_content
    
    # 写入文件
    with open(changelog_path, "w") as f:
        f.write(final_content)

def main():
    state = load_state()
    g = Github(GITHUB_TOKEN)
    all_changes = []
    updated_state = state.copy()
    
    for repo_config in REPOS:
        repo_full_name = f"{repo_config['owner']}/{repo_config['repo']}"
        try:
            repo = g.get_repo(repo_full_name)
            print(f"正在处理仓库: {repo_full_name}")
            last_tag = state.get(repo_full_name, "")
            repo_changes = get_repo_changes(repo, last_tag)
            if repo_changes:
                all_changes.extend(repo_changes)
                # 更新状态为最新处理的版本
                updated_state[repo_full_name] = repo_changes[0]["tag"]
        except Exception as e:
            print(f"访问仓库 {repo_full_name} 出错: {str(e)}")
            continue
    
    if all_changes:
        update_changelog(all_changes)
        save_state(updated_state)
        print("CHANGELOG已成功更新")
    else:
        print("没有新的变更需要更新")

if __name__ == "__main__":
    main()

优化建议

  1. 语义化提交过滤:可以在脚本中加入逻辑,只提取符合语义化规范的提交(如feat:、fix:、docs:),让CHANGELOG更简洁有用。
  2. 权限安全:GitHub Token只授予必要的repo权限,在GitHub Actions中用Secrets存储,不要硬编码在脚本里。
  3. GitHub Actions集成:创建如下.github/workflows/update-changelog.yml文件,实现自动触发:
name: 更新全局CHANGELOG
on:
  schedule:
    - cron: "0 0 * * *" # 每天凌晨运行一次
  workflow_dispatch: # 支持手动触发

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: 设置Python环境
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"
      - name: 安装依赖
        run: pip install PyGitHub
      - name: 运行更新脚本
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: python update_changelog.py
      - name: 提交并推送变更
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git add CHANGELOG.md changelog_state.json
          git diff --quiet || git commit -m "自动更新全局CHANGELOG"
          git push

内容的提问来源于stack exchange,提问作者Ranji Raj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 02:29:59