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

如何用Python实现Git checkout并检测子目录分支差异及变更信息?

嘿,我刚好做过类似的需求,整理了一个完整的Python脚本方案,能完美搞定你要的Git分支检出对比、子目录差异扫描,以及文件元信息提取的全部操作。下面直接上干货:

Python实现Git分支对比与子目录扫描方案

核心思路

  • 创建临时目录分别克隆仓库并检出两个目标分支,避免干扰本地工作区
  • 用git diff命令高效对比两个分支的文件差异(涵盖内容变更、文件新增/删除)
  • 通过git log提取每个差异文件的最后修改者、时间戳及邮箱信息
  • 按文件状态分类整理输出结构化结果

完整脚本

import os
import subprocess
import tempfile
from typing import Dict, List

def run_git_cmd(cmd: List[str], working_dir: str = None) -> str:
    """封装Git命令执行逻辑,处理输出和错误"""
    try:
        result = subprocess.run(
            cmd,
            cwd=working_dir,
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout.strip()
    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"Git命令执行失败: {' '.join(cmd)}\n错误详情: {e.stderr}") from e

def get_file_modification_info(repo_path: str, file_path: str) -> Dict[str, str]:
    """获取指定文件的最后修改元信息"""
    log_output = run_git_cmd(
        ["git", "log", "-1", "--format=%an|%ae|%at", "--", file_path],
        cwd=repo_path
    )
    author_name, author_email, timestamp = log_output.split("|")
    # 把时间戳转成可读格式
    readable_time = subprocess.run(
        ["date", "-d", f"@{timestamp}"],
        capture_output=True,
        text=True
    ).stdout.strip()
    return {
        "作者名称": author_name,
        "作者邮箱": author_email,
        "时间戳": timestamp,
        "修改时间": readable_time
    }

def compare_git_branches(repo_url: str, branch_a: str, branch_b: str) -> Dict:
    """对比两个分支的文件差异并返回结构化结果"""
    with tempfile.TemporaryDirectory() as temp_root:
        # 创建两个临时仓库目录
        repo_a_path = os.path.join(temp_root, "branch_a_repo")
        repo_b_path = os.path.join(temp_root, "branch_b_repo")

        # 克隆并检出分支A
        print(f"正在准备分支 {branch_a} 的副本...")
        run_git_cmd(["git", "clone", repo_url, repo_a_path])
        run_git_cmd(["git", "checkout", branch_a], cwd=repo_a_path)

        # 克隆并检出分支B
        print(f"正在准备分支 {branch_b} 的副本...")
        run_git_cmd(["git", "clone", repo_url, repo_b_path])
        run_git_cmd(["git", "checkout", branch_b], cwd=repo_b_path)

        # 对比两个分支的文件状态(仅追踪的文件)
        print("正在扫描文件差异...")
        diff_result = run_git_cmd(
            ["git", "diff", "--name-status", branch_a, branch_b],
            cwd=repo_a_path  # 在单个仓库内直接对比分支更高效
        )
        diff_lines = diff_result.split("\n") if diff_result else []

        # 初始化结果字典
        comparison_output = {
            "分支B新增文件": [],
            "分支B删除文件": [],
            "内容修改文件": [],
            "重命名文件": []
        }

        for line in diff_lines:
            if not line:
                continue
            status_code, file_info = line.split("\t", 1)
            # 根据Git diff状态码分类处理
            if status_code == "A":
                # 分支B新增的文件
                file_meta = get_file_modification_info(repo_b_path, file_info)
                comparison_output["分支B新增文件"].append({
                    "文件路径": file_info,
                    **file_meta
                })
            elif status_code == "D":
                # 分支B删除的文件(分支A存在)
                file_meta = get_file_modification_info(repo_a_path, file_info)
                comparison_output["分支B删除文件"].append({
                    "文件路径": file_info,
                    **file_meta
                })
            elif status_code == "M":
                # 内容修改的文件,同时获取两个分支的修改信息
                meta_a = get_file_modification_info(repo_a_path, file_info)
                meta_b = get_file_modification_info(repo_b_path, file_info)
                comparison_output["内容修改文件"].append({
                    "文件路径": file_info,
                    f"{branch_a} 修改信息": meta_a,
                    f"{branch_b} 修改信息": meta_b
                })
            elif status_code == "R":
                # 文件重命名,拆分新旧路径
                old_path, new_path = file_info.split("\t")
                comparison_output["重命名文件"].append({
                    "原路径": old_path,
                    "新路径": new_path,
                    "备注": "文件在分支B中被重命名"
                })

        return comparison_output

if __name__ == "__main__":
    # 替换成你的仓库地址和分支名
    TARGET_REPO = "https://github.com/your-username/your-repo.git"
    BRANCH_1 = "main"
    BRANCH_2 = "feature/update-module"

    try:
        result = compare_git_branches(TARGET_REPO, BRANCH_1, BRANCH_2)

        # 打印格式化结果
        print("\n=== Git分支对比结果 ===")
        for category, items in result.items():
            if items:
                print(f"\n--- {category} ---")
                for item in items:
                    for key, value in item.items():
                        if isinstance(value, dict):
                            print(f"{key}:")
                            for k, v in value.items():
                                print(f"  {k}: {v}")
                        else:
                            print(f"{key}: {value}")
                    print("-"*50)
    except Exception as e:
        print(f"执行过程出错: {str(e)}")

关键细节说明

  1. 临时目录自动清理:用tempfile.TemporaryDirectory创建临时仓库,脚本结束后会自动删除,不会留下垃圾文件
  2. 差异范围控制:如果只需要扫描特定子目录,只需在git diff命令末尾添加路径参数,比如-- "src/subdir"
  3. 错误处理:封装的run_git_cmd函数会捕获Git命令执行错误并抛出带详情的异常,方便调试
  4. 元信息准确性:直接从Git提交历史提取信息,比文件系统的修改时间更可靠(能反映代码仓库的提交记录)

扩展方向

  • 可以把结果导出为JSON/CSV文件,方便后续分析或导入其他工具
  • 若需要对比未追踪文件,可添加git ls-files --others --exclude-standard命令获取未追踪文件列表再做对比
  • 支持批量对比多个分支组合,只需循环调用compare_git_branches函数即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:28:27