Last active
September 9, 2025 08:35
-
-
Save hannesoid/5e1a887efbf3a6c29b4755a6793853aa to your computer and use it in GitHub Desktop.
GitHub PR Tree Visualizer - Shows pull request dependencies in a hierarchical tree view with activity indicators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # GitHub PR Tree Visualizer | |
| # | |
| # This script creates a hierarchical tree view of open GitHub pull requests, | |
| # showing their dependency relationships based on base branches. It provides | |
| # visual indicators for PR activity and highlights your current working branch. | |
| # | |
| # Features: | |
| # - Tree structure showing PR dependencies (which PRs are based on which branches) | |
| # - Current branch highlighting with bold green colors | |
| # - Activity indicators: π¨ for PRs updated in last 12 hours | |
| # - Dimmed styling for stale PRs (not updated in 2+ weeks) | |
| # - Clean format: PR# "Title" branch-name URL | |
| # - Color-coded for easy scanning | |
| # | |
| # Requirements: | |
| # - GitHub CLI (gh) installed and authenticated | |
| # - jq for JSON parsing | |
| # - Git repository with open pull requests | |
| # | |
| # Example output: | |
| # main | |
| # βββ #123 "Add user authentication" feature/auth https://github.com/user/repo/pull/123 | |
| # βββ #124 "Fix login bug" π¨ bugfix/login-issue https://github.com/user/repo/pull/124 | |
| # β βββ #125 "Update tests" test/login-fixes https://github.com/user/repo/pull/125 | |
| # βββ #126 "Database migration" feature/db-migration https://github.com/user/repo/pull/126 | |
| # β βββ #127 "Performance improvements" enhancement/db-perf https://github.com/user/repo/pull/127 | |
| # | |
| # develop | |
| # βββ #128 "Feature branch sync" feature/sync https://github.com/user/repo/pull/128 | |
| # Get current branch | |
| current_branch=$(git branch --show-current 2>/dev/null || echo "") | |
| # Fetch PR data with update times | |
| pr_data=$(gh pr list --state open --json number,title,baseRefName,headRefName,url,updatedAt --limit 100) | |
| # Colors | |
| CYAN='\033[96m' | |
| YELLOW='\033[33m' | |
| DARK_GRAY='\033[90m' | |
| GREEN='\033[92m' | |
| RED='\033[91m' | |
| BOLD='\033[1m' | |
| DIM='\033[2m' | |
| RESET='\033[0m' | |
| # Function to create clickable URL | |
| make_clickable() { | |
| local url="$1" | |
| printf '\e]8;;%s\e\\%s\e]8;;\e\\' "$url" "$url" | |
| } | |
| # Function to get PR age styling based on last update | |
| get_pr_styling() { | |
| local pr_num="$1" | |
| local updated_at | |
| # Extract updatedAt for this PR from the JSON data | |
| updated_at=$(echo "$pr_data" | jq -r ".[] | select(.number == $pr_num) | .updatedAt") | |
| if [[ -z "$updated_at" || "$updated_at" == "null" ]]; then | |
| # Default styling if we can't get the date | |
| echo "normal" | |
| return | |
| fi | |
| # Convert ISO date to epoch seconds for comparison | |
| if command -v gdate >/dev/null 2>&1; then | |
| # Use GNU date if available (brew install coreutils) | |
| updated_epoch=$(gdate -d "$updated_at" +%s 2>/dev/null || echo "0") | |
| current_epoch=$(gdate +%s) | |
| else | |
| # Use BSD date (macOS default) | |
| updated_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "${updated_at%.*}Z" +%s 2>/dev/null || echo "0") | |
| current_epoch=$(date +%s) | |
| fi | |
| if [[ "$updated_epoch" -eq 0 ]]; then | |
| echo "normal" | |
| return | |
| fi | |
| # Calculate age in hours and days | |
| age_seconds=$((current_epoch - updated_epoch)) | |
| age_hours=$((age_seconds / 3600)) | |
| age_days=$((age_seconds / 86400)) | |
| if [[ $age_hours -le 12 ]]; then | |
| echo "very_recent" # Updated in last 12 hours | |
| elif [[ $age_days -ge 14 ]]; then | |
| echo "old" # Not updated for 2+ weeks | |
| else | |
| echo "normal" # Normal styling | |
| fi | |
| } | |
| # Function to print PR line with current branch highlighting | |
| print_pr_line() { | |
| local prefix="$1" | |
| local pr_num="$2" | |
| local branch="$3" | |
| local title="$4" | |
| local url="$5" | |
| # Get styling based on PR age | |
| local pr_styling=$(get_pr_styling "$pr_num") | |
| # Set colors based on age and current branch | |
| local pr_color="${CYAN}" | |
| local title_color="${YELLOW}" | |
| local branch_color="" | |
| local age_indicator="" | |
| if [[ "$current_branch" == "$branch" ]]; then | |
| # Current branch always gets bold green highlighting | |
| pr_color="${BOLD}${GREEN}" | |
| branch_color="${BOLD}" | |
| elif [[ "$pr_styling" == "very_recent" ]]; then | |
| # Very recent PRs (last 12 hours) get hammer indicator | |
| pr_color="${CYAN}" | |
| title_color="${YELLOW}" | |
| age_indicator=" π¨" | |
| elif [[ "$pr_styling" == "old" ]]; then | |
| # Old PRs get dimmed styling | |
| pr_color="${DIM}${CYAN}" | |
| title_color="${DIM}${YELLOW}" | |
| branch_color="${DIM}" | |
| fi | |
| echo -e "${prefix} ${pr_color}#${pr_num}${RESET} ${title_color}\"${title}\"${RESET}${age_indicator} ${branch_color}${branch}${RESET} ${DARK_GRAY}${url}${RESET}" | |
| } | |
| # Function to find root branches (branches that don't have PRs targeting them) | |
| get_root_branches() { | |
| local all_bases=($(echo "$pr_data" | jq -r '.[].baseRefName' | sort -u)) | |
| local all_heads=($(echo "$pr_data" | jq -r '.[].headRefName' | sort -u)) | |
| # Root branches are bases that are not also head branches | |
| local root_branches=() | |
| for base in "${all_bases[@]}"; do | |
| local is_head=false | |
| for head in "${all_heads[@]}"; do | |
| if [[ "$base" == "$head" ]]; then | |
| is_head=true | |
| break | |
| fi | |
| done | |
| if [[ "$is_head" == "false" ]]; then | |
| root_branches+=("$base") | |
| fi | |
| done | |
| printf '%s\n' "${root_branches[@]}" | |
| } | |
| # Function to build tree structure from PR data | |
| build_pr_tree() { | |
| local root_branches=($(get_root_branches)) | |
| for root in "${root_branches[@]}"; do | |
| echo "$root" | |
| print_branch_prs "$root" "" | |
| echo "" | |
| done | |
| } | |
| # Function to print PRs for a branch and their dependencies | |
| print_branch_prs() { | |
| local branch="$1" | |
| local indent="$2" | |
| local branch_prs=($(echo "$pr_data" | jq -r ".[] | select(.baseRefName == \"$branch\") | .number")) | |
| for i in "${!branch_prs[@]}"; do | |
| local pr_num="${branch_prs[$i]}" | |
| local pr_info=$(echo "$pr_data" | jq -r ".[] | select(.number == $pr_num)") | |
| local title=$(echo "$pr_info" | jq -r '.title') | |
| local head_branch=$(echo "$pr_info" | jq -r '.headRefName') | |
| local url=$(echo "$pr_info" | jq -r '.url') | |
| # Determine prefix based on position | |
| local prefix="${indent}βββ" | |
| if [[ $i -eq $((${#branch_prs[@]} - 1)) ]]; then | |
| prefix="${indent}βββ" | |
| fi | |
| print_pr_line "$prefix" "$pr_num" "$head_branch" "$title" "$url" | |
| # Recursively show PRs that are based on this PR's head branch | |
| print_branch_prs "$head_branch" "${indent}β " | |
| done | |
| } | |
| build_pr_tree |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment