Skip to content

repositories

Ganesh Bakkera edited this page Aug 11, 2026 · 1 revision

Repositories & Synchronization

How URGithub discovers your repositories, keeps them in sync, and what happens when a repository cannot be pushed.

Discovery

URGithub discovers Git repositories within its configured repository area (repos in github\) and reconciles the local state against GitHub using gh repo list.

A repository generally needs to:

  • exist as a valid directory
  • contain a Git working tree
  • have valid Git metadata
  • have an appropriate remote
  • be accessible by the authenticated GitHub account

On the first run it clones your entire GitHub account — private repos and forks included (unless skip_forks is enabled).

flowchart TD
    A[repos in github folder] --> B{Valid git repo?}
    B -->|yes| C[Has remote?]
    B -->|no| D[ignored / flagged missing]
    C -->|yes| E[reconcile with gh repo list]
    C -->|no| F[flagged: no remote configured]
    E --> G{Exists on GitHub?}
    G -->|yes| H[tracked]
    G -->|no| I[deleted-repo policy / quarantine]
    E --> J{Exists locally but<br/>not on GitHub?}
    J -->|yes| K[new repo — tracked]
    J -->|no| L[clone_missing_repos<br/>clones it]
Loading

Inspect what was found with:

python urgithub.py --repos
python urgithub.py --verify

--verify checks registry entries, folders, Git state, and remote information.

Registry

Every managed repository has a registry entry in database\registry.json (status, SHAs, quarantine state).

  • --forget NAME — remove one repository from the registry (folder left on disk).
  • --prune — remove all stale registry entries (missing / quarantined / deleted).
  • --yes — skip confirmation prompts for --forget / --prune.

Keeping names in sync with GitHub

GitHub is the source of truth for names.

  • Renamed on GitHub only → URGithub detects it via gh api returning the new name and renames the local folder + registry entry automatically.
  • Renamed locally only → URGithub adopts it: on the next run it reads the folder's origin, matches it to the existing registry entry (whose path is gone), and reuses that entry — updating the path, no duplicate clone, no quarantine. Then just gh repo rename NewName --repo owner/OldName on GitHub to match.
  • Leftover ghost entries (from manual moves) can be dropped with --forget NAME or bulk-cleaned with --prune.

Deleted repositories — quarantine, never deletion

Repositories confirmed deleted on GitHub are not silently deleted. They are moved to deleted repos\ (a quarantined archive) and only with confirmation:

flowchart TD
    A[GitHub returns 404] --> B{3 consecutive scans?}
    B -->|no| C[wait / keep scanning]
    B -->|yes| D{7 days elapsed?}
    D -->|no| E[wait]
    D -->|yes| F{Remote confirms deletion?}
    F -->|no| G[do nothing]
    F -->|yes| H{User confirms?}
    H -->|no| G
    H -->|yes| I["Move to deleted repos folder"]
    I --> J[reported in report.html]
Loading

Configurable via deleted_repo_policy.* (defaults: 3 scans, 7 days, both confirmations required).

The sync engine

Synchronization is deliberately conservative — never reset, --force, rebase, or clean.

gitGraph
    commit id: "base"
    branch local
    checkout local
    commit id: "local change"
    checkout main
    commit id: "remote change"
    checkout local
    merge main
    commit id: "pushed"
Loading

The safe pull/commit/push sequence:

fetch → fast-forward-only pull → commit (per policy) → push

When a repository is not pushed

Situation Result
Secret files found (block_on_secrets) blocked: secrets — by filename pattern and file content
File larger than limits.max_file_mb blocked: oversize files
Local ahead and remote ahead blocked: divergence
Remote unreachable blocked: remote unreachable
No push permission on the repo blocked: no push permission
No origin remote blocked: no remote configured
Folder missing / not a git repo / quarantined blocked: missing
Dirty tree + auto_commit off skipped (never silently commits)
Fetch / git add / commit / push fails failed (journaled with the reason)

Every case is journaled and visible in report.html. Repos that pass are still pushed — one bad repo never blocks the others.

Divergence protection

If the local and remote histories diverged (both ahead of the common base), the repo is blocked instead of clobbered.

flowchart LR
    A{Local ahead?} --> C{Remote ahead?}
    A -->|no| B[clean / push]
    C -->|yes| D[BLOCKED: divergence<br/>you review, you decide]
    C -->|no| E[fast-forward safe]
Loading

You review, you decide. URGithub never force-pushes and never merges divergent history automatically.

The lock

A global PID-based lock (locks\run.lock) serializes runs:

  • A second run while another is active reports skipped — lock held.
  • A stale lock expires after 15 seconds.
sequenceDiagram
    participant R1 as Run 1
    participant R2 as Run 2
    participant L as Lock
    R1->>L: acquire
    R2->>L: try acquire → held
    L-->>R2: skipped — lock held
    R1->>L: release
Loading

See Security for what stops a sync, and Report for what each run records.

URGithub

URGithub Wiki
The safe, automatic Git repository manager

Version Python License Platform


Getting started

  • Home — overview, pipeline, quick start
  • Installation — requirements, setup wizard, first run

Concepts

Operation

Help


Quick reference

  • --setup · one-time registration wizard
  • --scan · discover repositories (never syncs)
  • --sync · full safe synchronization
  • --report · regenerate report.html
  • --schedule install · install Windows scheduling
  • --run manual · run the manual trigger

Repository ↗ · Issues ↗ · Releases ↗

v0.1.0 · MIT License · © 2026 Ganesh Bakkera

Clone this wiki locally