GitHub bookmarks  Artifact [710302a0c9]

Artifact 710302a0c913d87997d0b4f8430f4bb0a1f6f91f9b75ca30a57813f8ca6ef6e6:

  • File upper-downer.v — part of check-in [f5da35d641] at 2025-06-30 14:57:06 on branch trunk — improved filtering and debug messages for fork sync (user: refaqtor size: 8979)

import net.http
import json
import os
import time

// Configuration
const local_base_dir = './repos' // Directory where repos will be cloned if -c is used
const license_keys = ['mit', 'gpl-3.0', 'apache-2.0', 'bsd-3-clause', 'lgpl-3.0', 'mpl-2.0', 'unlicense', 'isc', 'cc0-1.0']
const sync_poll_interval = 5 // Seconds between sync status checks
const sync_timeout = 300 // Max seconds to wait for sync completion

// Structs
struct License {
    key string
}

struct ParentRepo {
    full_name string
}

struct Repo {
    name        string
    clone_url   string
    language    string
    license     ?License
    fork        bool
    parent      ?ParentRepo
    description ?string
    updated_at  string
}

// Fetch repositories from GitHub API with pagination
fn fetch_repos(username string, token string) ![]Repo {
    mut repos := []Repo{}
    mut url := 'https://api.github.com/users/${username}/repos?per_page=100'
    for url != '' {
        mut req := http.new_request(.get, url, '')
        req.header.add(.authorization, 'Bearer ${token}')
        req.header.add(.accept, 'application/vnd.github.v3+json')
        resp := req.do()!
        if resp.status_code != 200 {
            return error('Failed to fetch repos: ${resp.status_code} ${resp.body}')
        }
        page_repos := json.decode([]Repo, resp.body)!
        repos << page_repos
        link_header := resp.header.get(.link) or { '' }
        next_url := get_next_url(link_header) or { '' }
        url = next_url
    }
    return repos
}

// Parse the Link header to get the next page URL
fn get_next_url(link_header string) ?string {
    parts := link_header.split(',')
    for part in parts {
        if part.contains('rel="next"') {
            start := part.index('<') or { continue }
            end := part.index('>') or { continue }
            if start < end {
                return part[start+1..end]
            }
        }
    }
    return none
}

// Sync a forked repository with its upstream parent
fn sync_fork(repo Repo, username string, token string) ! {
    if !repo.fork {
        return
    }
    parent := repo.parent or { return }
    parent_parts := parent.full_name.split('/')
    if parent_parts.len != 2 {
        return error('Invalid parent repository name')
    }
    branch := 'main' // Default branch; could fetch repo's default branch if needed
    payload := json.encode({'branch': branch})
    url := 'https://api.github.com/repos/${username}/${repo.name}/merge-upstream'
    mut req := http.new_request(.post, url, payload)
    req.header.add(.authorization, 'Bearer ${token}')
    req.header.add(.accept, 'application/vnd.github.v3+json')
    req.header.add(.content_type, 'application/json')
    resp := req.do()!
    if resp.status_code != 200 {
        return error('Failed to sync fork ${repo.name}: ${resp.status_code} ${resp.body}')
    }
}

// Check if sync is complete by polling updated_at
fn wait_for_sync(repo Repo, username string, token string, initial_updated_at string) ! {
    url := 'https://api.github.com/repos/${username}/${repo.name}'
    start_time := time.now()
    for {
        mut req := http.new_request(.get, url, '')
        req.header.add(.authorization, 'Bearer ${token}')
        req.header.add(.accept, 'application/vnd.github.v3+json')
        resp := req.do()!
        if resp.status_code != 200 {
            return error('Failed to check sync status for ${repo.name}: ${resp.status_code} ${resp.body}')
        }
        repo_data := json.decode(Repo, resp.body)!
        if repo_data.updated_at != initial_updated_at {
            return
        }
        now := time.now()
        if (now.unix() - start_time.unix()) > sync_timeout {
            return error('Timeout waiting for sync of ${repo.name}')
        }
        time.sleep(sync_poll_interval * time.second)
    }
}

fn main() {
    // Validate command-line arguments
    if os.args.len < 3 {
        eprintln('Usage: v run github_repo_processor.v <github_username> <github_token> [-c|--clone] [-f filter_terms...]')
        exit(1)
    }
    username := os.args[1]
    token := os.args[2]

    // Parse command-line switches
    mut clone_flag := false
    mut filter_terms := []string{}
    mut i := 3
    for i < os.args.len {
        arg := os.args[i]
        if arg == '-c' || arg == '--clone' {
            clone_flag = true
            i++
        } else if arg == '-f' {
            i++
            if i < os.args.len {
                unsafe { filter_terms = os.args[i..] } // Silence implicit clone warning
            }
            break
        } else {
            eprintln('Unknown option: ${arg}')
            exit(1)
        }
    }

    // Parse filter terms into license and language filters
    mut included_licenses := []string{}
    mut excluded_licenses := []string{}
    mut included_languages := []string{}
    mut excluded_languages := []string{}
    for term in filter_terms {
        if term.starts_with('!') {
            stripped := term[1..].to_lower()
            if stripped in license_keys {
                excluded_licenses << stripped
            } else {
                excluded_languages << stripped
            }
        } else {
            stripped := term.to_lower()
            if stripped in license_keys {
                included_licenses << stripped
            } else {
                included_languages << stripped
            }
        }
    }

    // Fetch all repositories
    repos := fetch_repos(username, token) or {
        eprintln('Error fetching repositories: ${err}')
        exit(1)
    }
    println('Found ${repos.len} repositories')

    // Process each repository
    mut filtered_repos := []Repo{}
    for repo in repos {
        license_key := if l := repo.license { l.key.to_lower() } else { '' }
        language := repo.language.to_lower()

        // Apply filters
        license_condition := (included_licenses.len == 0 || license_key in included_licenses) &&
                            !(license_key in excluded_licenses)
        language_condition := (included_languages.len == 0 || language in included_languages) &&
                             !(language in excluded_languages)

        if license_condition && language_condition {
            filtered_repos << repo
        }
    }

    if filtered_repos.len == 0 {
        println('No repositories match the specified filters')
        return
    }

    if clone_flag {
        // Clone or update the filtered repositories
        for repo in filtered_repos {
            if repo.fork {
                println('Syncing fork: ${repo.name}')
                initial_updated_at := repo.updated_at
                sync_fork(repo, username, token) or {
                    eprintln('Error syncing ${repo.name}: ${err}')
                    continue
                }
                wait_for_sync(repo, username, token, initial_updated_at) or {
                    eprintln('Error waiting for sync of ${repo.name}: ${err}')
                    continue
                }
            }
            repo_dir := os.join_path(local_base_dir, repo.name)
            if !os.exists(local_base_dir) {
                os.mkdir_all(local_base_dir)!
            }
            if os.exists(repo_dir) {
                println('Updating repository: ${repo.name}')
                os.execute_or_panic('git -C "${repo_dir}" pull')
            } else {
                println('Cloning repository: ${repo.name}')
                os.execute_or_panic('git clone ${repo.clone_url} "${repo_dir}"')
            }
        }
        println('All selected repositories cloned or updated successfully')
    } else {
        // Print repository info in Markdown table
        println('| Name | License | Language | Forked From | Description |')
        println('|------|---------|----------|-------------|-------------|')
        for repo in filtered_repos {
            license_key := if l := repo.license { l.key.to_lower() } else { '' }
            language := repo.language.to_lower()
            license_str := if license_key == '' { 'No license' } else { license_key }
            language_str := if language == '' { 'No language' } else { language }
            forked_from := if repo.fork { if p := repo.parent { p.full_name } else { 'N/A' } } else { 'N/A' }
            description := repo.description or { 'No description' }
            // Debug output to inspect fork and parent data
            if repo.fork {
                if p := repo.parent {
                    eprintln('DEBUG: ${repo.name} is a fork with parent ${p.full_name}')
                } else {
                    eprintln('DEBUG: ${repo.name} is a fork but parent is none')
                }
            } else {
                eprintln('DEBUG: ${repo.name} is not a fork')
            }
            println('| ${repo.name} | ${license_str} | ${language_str} | ${forked_from} | ${description} |')
        }
        println('\nRepository information printed successfully')
    }
}