#!/usr/bin/env python3

import asyncio
from asyncio import Semaphore
from asyncio.subprocess import Process, PIPE
import os
from typing import List, Tuple


_N = 10


async def print_errors(repo: str, cmd: str, proc: Process):
    if proc.returncode != 0:
        print("[ERROR]", repo, cmd)
        async for line in proc.stderr:
            print(" " * 4 + line.decode("utf-8"))


async def run(repo: str, cmd: List[str]) -> Process:
    return await asyncio.create_subprocess_exec(
        *cmd, cwd=repo, stdout=PIPE, stderr=PIPE
    )


async def update(repo: str) -> Tuple[Process, Process]:
    fetch = await run(repo, ["git", "fetch", "-pa"])
    await fetch.wait()
    pull = await run(repo, ["git", "pull", "--rebase"])
    await pull.wait()
    return (fetch, pull)


async def update_with_semaphore(repo: str, sem: Semaphore, fuel: int = 3):
    async with sem:
        error = None
        for _ in range(fuel):
            (fetch, pull) = await update(repo)
            if fetch.returncode == 0 and pull.returncode == 0:
                print(f"{repo}: OK!")
                break
            else:
                error = (fetch, pull)
        else:
            if error:
                await print_errors(repo, "fetch", error[0])
                await print_errors(repo, "pull", error[1])


async def main(n: int):
    sem = Semaphore(n)
    repos = (
        repo
        for repo in os.listdir(".")
        if os.path.isdir(repo) and ".git" in os.listdir(repo)
    )
    tasks = [update_with_semaphore(repo, sem) for repo in repos]
    print(f"Updating {len(tasks)} repos...")
    await asyncio.gather(*tasks)


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        N = int(os.getenv("max_jobs", _N))
    except ValueError:
        N = _N
    try:
        loop.run_until_complete(main(N))
    finally:
        loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()