Skip to main content

gitfloe

gitfloe

License: MIT Python 3.12 GitHub stars merge-upstream sync CI passing

Keep your forks in sync with their upstreams by calling GitHub's merge-upstream API — the exact same thing the web "Sync fork" button and gh repo sync do. No git, no cloning. When a fork has a conflict, it hands off to a pluggable handler (default: email). After each run — and periodically — it also emails an overall digest of what happened.

What it does

One repo ("the manager") holds a list of your forks and, on a schedule, for each one:

  1. calls POST /repos/{owner}/{repo}/merge-upstream with {"branch": "<branch>"},
  2. reads the result:
    • 200 → merged (synced),
    • 204 → already up to date,
    • 409 → conflict → dispatch a handler (default: email),
    • 403 / 404 / other → error → dispatch a handler,
  3. records each result to state/events.jsonl and emails a digest.

It only ever writes to your forks (via the API), never to upstream.

Layout

assets/                          # logo (logo.svg, logo-light.svg, logo-lockup.svg, favicon.ico)
examples/                        # templates: repos.example.yaml, .env.example
gitfloe/
  cli.py                         # python -m gitfloe.cli
  core.py                        # calls merge-upstream; classifies result; dispatches
  config.py                      # load repos.yaml
  plugins.py                     # handler discovery + dispatch
  smtp.py                        # shared SMTP sender (env-driven)
  digest.py                      # run + interval digest
  handlers/
    email.py                     # default: email on conflict/error
    ai.py                        # example LLM handler (shows the hook)
tests/                           # pytest tests
.github/workflows/
  sync.yml                       # scheduled sync + per-run digest + state commit
  digest.yml                     # periodic aggregate digest
  ci.yml                         # CI + pytest
state/                           # committed back so history persists across runs

Adding your forks

Copy the template examples/repos.example.yaml to repos.yaml, then edit it:

default:
  branch: main
  handler: email
digest:
  on_run: true              # email a summary after every sync run
  on_interval: true         # + weekly digest via digest.yml
repos:
  - name: example-fork
    fork: you/example-fork
    upstream: someorg/example
    branch: main

upstream is for reference/labels (the API derives it from the fork's GitHub parent); branch is the branch merge-upstream merges into.

The handler hook

A handler is a Python module exposing HANDLER and handle(event):

# handlers/mine.py
HANDLER = "my-name"
def handle(event: dict) -> dict:
    # repo, fork, upstream, branch, kind, api_status, error, summary
    return {"handled": True, "summary": "did something"}

The event kind is synced / up_to_date / conflict / error (plus preview during dry-run). Return {"handled": False} to leave it for another handler / log only. External handlers: drop .py in a folder and point the run at it via GITFLOE_HANDLER_PATH (colon-separated); external ones override by name.

Running locally

uv sync                                                      # install into .venv (PyYAML only)
uv run gitfloe --config repos.yaml --dry-run                 # config/validation check (read-only)
uv run gitfloe --config repos.yaml                           # actually sync (needs GITFLOE_TOKEN)
uv run gitfloe --config repos.yaml --digest-interval         # send periodic digest only

Installing from PyPI

uv tool install gitfloe       # or: pip install gitfloe
gitfloe --config repos.yaml --dry-run

You can also trigger a single fork manually with the official CLI:

gh repo sync luoyueyuguang/vllm --branch main

Where the secrets live (the key point)

No secret is ever committed. Credentials come from the environment — GitHub Actions Secrets in CI, a local .env for development.

Secret Used by Purpose
GITFLOE_TOKEN core PAT/fine-grained token on your forks with Contents: write (the API needs it to push the merge).
GITFLOE_SMTP_HOST/PORT/USER/PASS smtp SMTP for the email handler + digest.
GITFLOE_MAIL_TO smtp Where attention + digest emails go.
GITFLOE_AI_ENDPOINT ai handler LLM endpoint (OpenAI-compatible).
GITFLOE_AI_KEY ai handler LLM bearer token.
GITFLOE_AI_MODEL ai handler Model id (default gpt-4o-mini).

LLM token — same rule as the sync token: put GITFLOE_AI_KEY (+ END/MODEL) in Secrets, injected by the workflow's env:. Local runs read them from .env. If unset, the ai handler degrades to a no-op.

Tip: for Gmail use an App Password + smtp.gmail.com:587.

State persistence

state/ is tracked so the digest can aggregate across workflow runs; both workflows commit it back. Peek at the event log: tail -f state/events.jsonl.

Metadata

Release files for gitfloe 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gitfloe 0.1.1
File Size Uploaded
gitfloe-0.1.1.tar.gz 29.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gitfloe 0.1.1
File Interpreter ABI Platform
gitfloe-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 44.1 kB

Release files / gitfloe-0.1.1.tar.gz

Download URL gitfloe-0.1.1.tar.gz
Size 29.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c81d6be08ef0500b01d7639558b38fa3da6d6678458b726d9360ba6f650a9150
BLAKE2b-256 checksum
How to use checksums
1b01d720266248d55aeeec3c095e4f3179eb23e8254e5b4a527f0c4a1b1a552d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / gitfloe-0.1.1-py3-none-any.whl

Download URL gitfloe-0.1.1-py3-none-any.whl
Size 14.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0178e27859bce9e553a7e7f0390c0cfaee496737ad25302a7d1ac50cc905a9d6
BLAKE2b-256 checksum
How to use checksums
6c7476c4dee46eef9976046f96fcf48a8fce418e874596e7794a44539787331e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page