# 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).
```mermaid
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
not on GitHub?}
J -->|yes| K[new repo — tracked]
J -->|no| L[clone_missing_repos
clones it]
```
Inspect what was found with:
```bash
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:
```mermaid
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]
```
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`.
```mermaid
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"
```
The safe pull/commit/push sequence:
```text
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.
```mermaid
flowchart LR
A{Local ahead?} --> C{Remote ahead?}
A -->|no| B[clean / push]
C -->|yes| D[BLOCKED: divergence
you review, you decide]
C -->|no| E[fast-forward safe]
```
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**.
```mermaid
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
```
See [Security](security.md) for what stops a sync, and [Report](report.md) for what each run records.