CLI tool for mirroring GitHub repos to Tangled.org
entangle _docs DESIGN.md
8.0 kB
Markdown
at main

entangle #

Language: Rust

Output: CLI tool

Target: Linux, MacOS, Windows

Goal: Easy setup for mirroring GitHub repos to Tangled.org in one command

Manual process this is replacing #

From the Tangled.org docs, the thing I have to do by hand at the moment:

You can configure your local repository to push to both Tangled and, say, GitHub. You may already have the following setup:

$ git remote -v
origin  git@github.com:username/my-project.git (fetch)
origin  git@github.com:username/my-project.git (push)

Now add Tangled as an additional push URL to the same remote:

git remote set-url --add --push origin git@tangled.org:user.tngl.sh/my-project

You also need to re-add the original URL as a push destination (Git will now use the original URL to fetch only):

git remote set-url --add --push origin git@github.com:username/my-project.git

Verify your configuration:

$ git remote -v
origin  git@github.com:username/my-project.git (fetch)
origin  git@tangled.org:user.tngl.sh/my-project (push)
origin  git@github.com:username/my-project.git (push)

Notice that there’s one fetch URL (the primary remote) and two push URLs.

The goal is to never do that manually again.

Commands to implement #

  • entangle init: interactive if empty, or entangle init repo-name-as-positional-argument optional-alias-as-positional-argument
  • entangle setup: interactive setup of GitHub and Tangled usernames and default origin preference (defaults to GitHub)
  • entangle set [gh-user | github-user || tngl-user | tangled-user || origin {gh | github || tngl | tangled}] to manually establish config preferences
  • entangle shove finishing-up helper: convenience alias for git push origin --all && git push origin --tags. One-time "push the whole thing" helper for the first sync after entangle init; since origin already has two push URLs configured, one command hits both forges automatically.

Using entangle setup #

  • Setup creates a JSON config file at .config/entangle/config.json or appropriate equivalent (use dirs crate to handle directories)
  • Interactive setup: prompt for GitHub username, Tangled username, and origin preference (GitHub or Tangled, default GitHub), in that order
  • Individual features set with entangle set: gh-user | github-user username, tngl-user | tangled-user username, origin can be gh | github or tngl | tangled.
  • entangle set modifies the global config

Config file format: #

{
     "github_username": "...",
     "tangled_username": "...",
     "origin_preference": "github" // or "tangled"
}

origin_preference: which remote is the fetch remote.

Example of entangle set #
# Pretend you're me
entangle set gh-user cyrusae # this checks GitHub username validity
entangle set tngl-user atdot.fyi # this checks ATProto username validity
entangle set origin github # this is already the default option

Using entangle init #

When a user runs entangle init, what should happen:

  1. Check if a valid config exists; if not, stop and refer the user to entangle setup
  2. Without arguments: prompt for repo name and then for optional mirror name
  3. With arguments: entangle init arg1 arg2 where arg1 is the repo name and arg2 is the optional alias for its mirror (i.e., by default, arg1 is the GitHub repo name and arg2 if present becomes the Tangled repo name)
  4. Check whether name(s) given are valid repo names
  5. Build the prospective GitHub and Tangled URLs
    • GitHub: git@github.com:{github_username}/{repo}.git
    • Tangled: git@tangled.org:{tangled_username}/{repo} (note: no .git; Tangled username is a full ATProto handle, e.g. atdot.fyi)
  6. Check if intended remotes are valid — two-stage:
    1. Local regex first: validate the constructed URL strings before touching the network (fast fail on obviously malformed input)
    2. gix SSH ls-refs: attempt a real connection to each remote (equivalent to git ls-remote). Fail the default-origin remote first. Distinguish three error cases:
      • Not found: repo doesn't exist at that URL → stop, warn user to check repo name / whether the repo has been initialized on that forge
      • Auth failure: SSH handshake failed → stop, warn user their SSH key may not be configured for that forge (different problem from a typo)
      • Network error (timeout, no route to host): → warn and offer an override prompt ("Couldn't reach remote. Accept anyway? [y/N]") so the tool remains usable offline
    • Note: private GitHub repos are handled naturally by this approach — if the user's SSH key has access, ls-refs succeeds regardless of repo visibility. No special-casing needed.
  7. Check if the folder is already a git repository
    1. If not, git init and print informative message ("Folder is not a git repository, initializing...")
    2. Check for .gitignore and README.md; suggest user to add them on their next commit if either or both are missing
  8. Check if remotes are already configured
    1. If both GitHub and Tangled remotes exist according to git remote -v, stop early
    2. If origin doesn't match the generated origin URL based on repo-name, prompt:
      An origin remote already exists: git@gitlab.com:someone/something.git
      Replace it with git@github.com:{user}/{repo}.git? [Y/n]
      
      • Yes: replace and continue normally
      • No: prompt Add push URLs to existing origin anyway? [Y/n]
        • Yes: continue (origin fetch URL stays as-is; push URLs will be added to whatever is there — this is unusual but not blocked)
        • No: abort
  9. Add the non-default remote as push origin
  10. Re-add the default remote as push origin (order matters, default last)
  11. Finish and return to user verification of the set remotes

Ending prompt should include a suggestion to entangle shove for a first-time mirror to sync all repo contents (including all branches).

Coding and preferences #

  • Use the gix crate for interacting with git. Avoid shelling out to git except for tests.
  • Modular code: if I have to scroll twice, the file might be doing too many things at once.
  • Add documentation comments.
  • Comment prolifically in general.
  • Unit tests and integration tests: use gix for unit tests, directly use git for integration tests. See the testing doc for details.

Test cases and sanitization #

See the testing doc for details.

Decisions to make during development #

  • Consistent style guide for progress messages
  • Consistent style guide for error messages
  • Formatting of terminal output
  • Crates for terminal output (decided):
    • clap — argument parsing
    • dialoguer — interactive prompts (setup, Y/n confirmations, "already set to X, change?" pattern); handles Ctrl+C gracefully
    • indicatif — spinners/progress for network calls (ls-refs can take a moment)
    • owo-colors — terminal colors
    • serde + serde_json — config file serialization
    • dirs — platform-aware config directory (already noted above)
    • gix — all git operations (already noted above)
  • Comprehensive test suite — how to maximize coverage?

Post-MVP considerations/additions #

  • Add -o | --overwrite flag to skip the overwriting warnings
    • Decision point: How aggressively does --overwrite let entangle act? Decide this based on failure cases established during testing.
  • Add -q | --quiet flag to do things silently instead of verbosely (I prefer verbose as a default), and/or a config-level preference on verbosity (default "verbosity_preference": "verbose")
    • Decision point: What output still happens if quiet is on? Decide this based on the final draft of the verbose messages.
  • entangle help or -h/--help shows help; entangle command --help shows per-command help; entangle lists valid commands
  • entangle version or -v/--version shows version
  • entangle one-or-more illegal-arguments from-a-user fails helpfully