EdDev/github-pr-review-cycles

Analyze GitHub Pull Request review cycles

★ 0Forks 0PythonGitHub ↗Compare

README

GitHub PR Review Cycles

Python 3.7+ License

A Python tool that analyzes GitHub Pull Request review history and calculates the number of review cycles between a reviewer and the PR author.

What is a Review Cycle?

A review cycle represents one complete iteration of the code review process:

┌─────────────────────────────────────────────────────────┐
│  Review Cycle                                           │
├─────────────────────────────────────────────────────────┤
│  1. Reviewer submits review (CHANGES_REQUESTED/COMMENT) │
│  2. Author responds with commits (regular or force-push) │
│  3. Reviewer submits next review (cycle completes)      │
└─────────────────────────────────────────────────────────┘

Features

  • ✅ Standalone - No external Python dependencies, only uses standard library
  • ✅ GitHub CLI Integration - Uses gh CLI for authenticated GitHub API access
  • ✅ Flexible Output - Supports both Markdown (human-readable) and JSON (machine-readable)
  • ✅ Detailed Tracking - Tracks reviews, commits, and force-pushes with timestamps
  • ✅ Simple to Use - Just provide a PR URL and reviewer username

Prerequisites

Installing GitHub CLI

# macOS
brew install gh

# Linux (Debian/Ubuntu)
sudo apt install gh

# Windows
winget install --id GitHub.cli

# After installation, authenticate
gh auth login

Installation

Option 1: Direct Download

# Download the script
curl -O https://raw.githubusercontent.com/EdDev/github-pr-review-cycles/main/pr_review_cycles.py

# Make it executable
chmod +x pr_review_cycles.py

# Run it
./pr_review_cycles.py --help

Option 2: Clone Repository

git clone https://github.com/EdDev/github-pr-review-cycles.git
cd github-pr-review-cycles
python3 pr_review_cycles.py --help

Quick Start

Analyze review cycles for a specific reviewer on a PR:

python3 pr_review_cycles.py \
  --pr https://github.com/owner/repo/pull/123 \
  --reviewer username

Usage

Basic Syntax

python3 pr_review_cycles.py --pr <PR_URL> --reviewer <USERNAME> [options]

Arguments

Argument Required Description
--pr ✅ Full GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)
--reviewer ✅ GitHub username of the reviewer to track
--output ❌ Output format: markdown (default) or json
--verbose, -v ❌ Enable verbose logging
--version ❌ Show version and exit
--help, -h ❌ Show help message

Examples

Example 1: Markdown Output (Default)

python3 pr_review_cycles.py \
  --pr https://github.com/RedHatQE/openshift-virtualization-tests-design-docs/pull/37 \
  --reviewer EdDev

Output:

# Review Cycles for PR #37

**Repository:** RedHatQE/openshift-virtualization-tests-design-docs
**Reviewer:** EdDev
**Total Cycles:** 5

## Cycle Details

### Cycle 1
- **Review submitted:** 2026-03-03 15:23:13 UTC
- **Review state:** CHANGES_REQUESTED
- **Author committed:** 2026-03-09 10:23:30 UTC
- **Number of commits:** 2
- **Next review:** 2026-03-11 11:24:03 UTC
...

Example 2: JSON Output

Perfect for automation and data analysis:

python3 pr_review_cycles.py \
  --pr https://github.com/owner/repo/pull/123 \
  --reviewer EdDev \
  --output json 2>/dev/null

Output:

{
  "pr_number": 123,
  "repo": "owner/repo",
  "reviewer": "EdDev",
  "pr_author": "author",
  "total_cycles": 3,
  "cycles": [
    {
      "cycle": 1,
      "review_at": "2024-01-15T10:30:00+00:00",
      "review_state": "CHANGES_REQUESTED",
      "commit_at": "2024-01-16T14:20:00+00:00",
      "num_commits": 2,
      "next_review_at": "2024-01-17T09:15:00+00:00",
      "status": "Completed"
    }
  ]
}

Example 3: Extract Just the Cycle Count

python3 pr_review_cycles.py \
  --pr https://github.com/owner/repo/pull/123 \
  --reviewer EdDev \
  --output json 2>/dev/null | jq '.total_cycles'

Example 4: Verbose Logging

python3 pr_review_cycles.py \
  --pr https://github.com/owner/repo/pull/123 \
  --reviewer EdDev \
  --verbose

Advanced Usage

Analyze Multiple PRs

Create a script to analyze multiple PRs:

#!/bin/bash

PR_URLS=(
  "https://github.com/owner/repo/pull/100"
  "https://github.com/owner/repo/pull/101"
  "https://github.com/owner/repo/pull/102"
)

REVIEWER="EdDev"

echo "PR,Reviewer,Cycles"
for pr_url in "${PR_URLS[@]}"; do
  result=$(python3 pr_review_cycles.py --pr "$pr_url" --reviewer "$REVIEWER" --output json 2>/dev/null)
  pr_num=$(echo "$result" | jq -r '.pr_number')
  cycles=$(echo "$result" | jq -r '.total_cycles')
  echo "$pr_num,$REVIEWER,$cycles"
done

Generate CSV Report

python3 pr_review_cycles.py \
  --pr https://github.com/owner/repo/pull/123 \
  --reviewer EdDev \
  --output json 2>/dev/null | \
jq -r '["PR", "Reviewer", "Total Cycles"],
       ([.pr_number, .reviewer, .total_cycles]) | @csv'

How It Works

  1. Fetch Timeline - Uses GitHub GraphQL API (via gh CLI) to fetch:

    • Pull Request reviews by specified reviewer
    • Commits and force-pushes by PR author
  2. Process Events - Implements a state machine:

    INITIAL → AWAITING_CHANGES → AWAITING_REVIEW → (repeat) → FINAL
    
  3. Count Cycles - Tracks complete review-commit-review cycles

  4. Generate Output - Formats as Markdown or JSON

Troubleshooting

GitHub CLI Not Authenticated

Error: gh: Not Found (HTTP 404)

Solution: Authenticate with GitHub CLI:

gh auth login

Invalid PR URL

Error: Invalid GitHub PR URL format

Solution: Ensure you're using the complete PR URL:

# ✅ Correct
--pr https://github.com/owner/repo/pull/123

# ❌ Wrong
--pr https://github.com/owner/repo/issues/123  # Issue, not PR
--pr 123  # Just number

No Reviews Found

Output: Total Cycles: 0

Possible causes:

  • Reviewer username is case-sensitive - verify exact spelling
  • Reviewer never reviewed this PR
  • PR is very old and timeline data may be incomplete

Output Reference

Markdown Format

  • Summary header with repository, reviewer, and total cycles
  • Detailed breakdown of each cycle with timestamps
  • Review states (COMMENTED, CHANGES_REQUESTED, APPROVED)
  • Commit counts and types (commit vs force-push)

JSON Format

{
  "pr_number": int,           // PR number
  "repo": string,             // "owner/repo"
  "reviewer": string,         // Reviewer username
  "pr_author": string,        // PR author username
  "total_cycles": int,        // Total number of cycles
  "cycles": [
    {
      "cycle": int,           // Cycle number (1-indexed)
      "review_at": string,    // ISO 8601 timestamp
      "review_state": string, // COMMENTED | CHANGES_REQUESTED | APPROVED
      "commit_at": string,    // ISO 8601 timestamp or null
      "num_commits": int,     // Number of commits in this cycle
      "next_review_at": string, // ISO 8601 timestamp or null
      "status": string        // "Completed" | "Merged/Approved"
    }
  ]
}

Exit Codes

Code Meaning
0 Success
1 Error (invalid URL, API failure, etc.)
130 Interrupted by user (Ctrl+C)

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built with GitHub CLI
  • Uses GitHub GraphQL API for timeline data

Support


Made with ❤️ for better code reviews

Contributors

EdDev

Issues