diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c5402b9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,55 @@ +# Blue Oak Model License + +Version 1.0.0 + +## Purpose + +This license gives everyone as much permission to work with +this software as possible, while protecting contributors +from liability. + +## Acceptance + +In order to receive this license, you must agree to its +rules. The rules of this license are both obligations +under that agreement and conditions to your license. +You must not do anything with this software that triggers +a rule that you cannot or will not follow. + +## Copyright + +Each contributor licenses you to do everything with this +software that would otherwise infringe that contributor's +copyright in it. + +## Notices + +You must ensure that everyone who gets a copy of +any part of this software from you, with or without +changes, also gets the text of this license or a link to +. + +## Excuse + +If anyone notifies you in writing that you have not +complied with [Notices](#notices), you can keep your +license by taking all practical steps to comply within 30 +days after the notice. If you do not do so, your license +ends immediately. + +## Patent + +Each contributor licenses you to do everything with this +software that would otherwise infringe any patent claims +they can license or become able to license. + +## Reliability + +No contributor can revoke this license. + +## No Liability + +***As far as the law allows, this software comes as is, +without any warranty or condition, and no contributor +will be liable to anyone for any damages related to this +software or this license, under any kind of legal claim.*** diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ff9501 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# arc ![license] ![activity] + +[license]: https://badge.hanna.lol/license/BlueOak-1.0.0 +[activity]: https://badge.hanna.lol/activity/hanna/arc + +A delta-based version control system written in Rust. + +Unlike Git's snapshot-based model, Arc stores incremental deltas using +ZSTD-compressed MessagePack files. Changes are automatically tracked +without manual staging, and commits are immutable once created. + +Arc uses a **bookmark** system instead of branches, and bridges to Git +remotes for push, pull, clone, and sync operations via `libgit2`. + +## Features + +- Incremental delta storage (ZSTD + MessagePack) +- Automatic change tracking (no staging step) +- Bookmarks and immutable tags +- Named stashes +- Three-way merge and graft (cherrypick/rebase) +- Git bridge for remote operations (push, pull, clone, migrate, sync) +- Optional SSH commit signing +- Per-repo and global YAML configuration with aliases +- `.arcignore` / `.ignore` support + +## Building + +Arc builds exclusively through Nix: + +```sh +nix build +``` + +The flake uses `nixpkgs-unstable`, `flake-parts`, `fenix`, and `crane`. + +A dev shell is available for iterative work: + +```sh +nix develop +``` + +## Usage + +``` +arc init [path] Initialize a new repository +arc commit Commit current changes +arc status Show changes since last commit +arc diff [start..end] Show a diff of changes +arc log [start..end] Show commit history +arc show Show details of a ref or commit +arc history [start..end] Show per-line modification history + +arc switch Switch worktree to a bookmark or tag +arc merge Merge a bookmark or tag into the worktree +arc revert Revert a commit or range +arc reset [file...] Reset worktree to the last commit +arc graft --onto Cherrypick commits onto a bookmark + +arc mark add|rm|list|rename Manage bookmarks +arc tag add|rm|list Manage tags +arc stash create|use|push|pop|rm|list Manage named stashes + +arc push [-r remote] Push to a git remote +arc pull [-r remote] Pull from a git remote +arc clone [-b branch] [path] Clone a git remote as an arc repo +arc migrate Convert a git repo to an arc repo +arc sync [-p] Sync bookmarks and tags with remote + +arc remote add|rm|list Manage remotes +arc config set|get|show|unset [-g] Manage configuration +``` + +## Testing + +```sh +nix develop -c cargo test +nix develop -c cargo clippy +nix develop -c cargo fmt --check +``` + +## License + +[Blue Oak Model License 1.0.0](LICENSE.md) diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..25c7ce1 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,120 @@ +# Architecture + +Arc is a version control system with its own data model, storage format, and a +git bridge for interoperability. + +## Repository Layout + +An arc repository keeps all state in an `.arc/` directory at the worktree root: + +| Path | Format | Purpose | +|------|--------|---------| +| `HEAD` | YAML | Current state — one of three variants: **unborn** (no commits yet; has `bookmark`), **attached** (on a bookmark; has `bookmark` + `commit`), or **detached** (raw commit; has `commit`). | +| `config.yml` | YAML | Local repository configuration. | +| `commits/.zst` | Zstandard-compressed MessagePack | Commit objects. Each file contains a `CommitObject` that bundles a `Commit` and its `Delta`. | +| `bookmarks/.yml` | YAML | One file per bookmark. Contains a `RefTarget` with an optional `commit` field. | +| `tags/.yml` | YAML | Same format as bookmarks. | +| `stashes/state.yml` | YAML | Tracks the active stash. | +| `stashes/named/.yml` | YAML | Per-stash state files. | +| `remotes.yml` | YAML | Map of remote names to URLs. | +| `git/` | Bare git repo | Shadow repository used by the git bridge. | +| `git-map.yml` | YAML | Bidirectional mapping between arc commit IDs and git OIDs. | + +## Data Model (`src/model.rs`) + +`CommitId` and `DeltaId` are newtype wrappers around `String`, holding SHA-256 +hex hashes. + +**Commit** — `id`, `parents` (Vec), `delta` (DeltaId), `message`, +`author` (optional `Signature`), `timestamp` (i64 unix), +`ssh_signature` (optional PEM string). + +**Delta** — `id`, `base` (optional parent CommitId), `changes` (Vec of +`FileChange`). + +**FileChange** — `path` plus a `kind`: Add, Modify, Delete, or Rename. + +**FileContentDelta** — either `Full { bytes }` (complete snapshot) or +`Patch { format, data }` (incremental). + +**Head** — enum with variants Unborn, Attached, and Detached. + +## Storage (`src/store.rs`) + +`CommitObject` bundles a `Commit` and its `Delta` into a single unit that is +serialized as MessagePack, then compressed with Zstandard at level 3. Files are +written atomically (write to `.tmp`, then rename). IDs are computed by SHA-256 +hashing the MessagePack-serialized content-addressable data. + +## Tracking (`src/tracking.rs`) + +`FileTree` is a `BTreeMap>` mapping relative paths to file +content. + +- `scan_worktree` — recursively walks the working directory, respecting ignore + rules and skipping `.arc/` and `.git/`. +- `materialize_committed_tree` — rebuilds the full file tree by replaying the + linear delta chain from the root commit. +- `detect_changes` — compares the committed tree against the worktree to produce + a list of `FileChange` entries (adds, modifies, deletes). + +## Ignore System (`src/ignore.rs`) + +Reads `.arcignore` first, falling back to `.ignore`. Always ignores `.arc/` and +`.git/`. + +- `*` and `?` glob wildcards. +- `!` prefix for negation. +- Patterns without `/` match any path component; patterns with `/` match the + full relative path. +- Patterns ending with `/` match directories only. + +## Git Bridge (`src/bridge.rs`) + +Maintains a shadow bare git repository under `.arc/git/`. + +`GitMap` provides bidirectional mapping (`arc_to_git` / `git_to_arc`) persisted +in `.arc/git-map.yml`. + +- `arc_to_git` recursively converts arc commits to git commits, materializing + file trees as git tree objects. +- `git_to_arc` does the reverse, computing deltas from git tree diffs. +- SSH authentication via agent or key files (`~/.ssh/id_ed25519`, `id_rsa`, + `id_ecdsa`). + +## Merge (`src/merge.rs`) + +Full three-way merge with line-level merging for text files using Myers diff. +Conflicts are marked with `<<<<<<< ours` / `=======` / `>>>>>>> theirs`. +Binary files fall back to keeping the "ours" version on conflict. + +## Signing (`src/signing.rs`) + +Optional SSH key signing using the `ssh-key` crate. Signs with SHA-512 under +the `arc` namespace. Verification extracts the public key from the signature +itself. Supports `~` expansion in key paths. + +## Source Modules + +| Module | Responsibility | +|--------|----------------| +| `main.rs` | Entry point, macro definitions | +| `cli.rs` | Clap-based CLI parsing and command dispatch | +| `model.rs` | Core data types | +| `store.rs` | Commit/delta serialization and content-addressing | +| `tracking.rs` | Worktree scanning, change detection, commit logic | +| `repo.rs` | Repository init/open/discover, path validation | +| `config.rs` | YAML config loading, merging (local-first), effective config | +| `refs.rs` | Bookmark/tag CRUD, switch, worktree write/clean | +| `bridge.rs` | Git bridge (shadow repo, push, pull, clone, migrate, sync) | +| `diff.rs` | Unified diff rendering | +| `inspect.rs` | Log, show, history (blame), Myers diff | +| `merge.rs` | Three-way merge | +| `modify.rs` | Reset, revert, merge command, graft | +| `resolve.rs` | Target/range resolution (bookmarks, tags, prefixes, HEAD) | +| `ignore.rs` | Ignore file parsing and matching | +| `signing.rs` | SSH commit signing and verification | +| `stash.rs` | Named stash system | +| `remote.rs` | Remote management (remotes.yml) | +| `error.rs` | Error types | +| `ui.rs` | Colored output formatting | diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 0000000..c9df7a5 --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,180 @@ +# Command Reference + +## Global Flags + +- `-v` / `--verbose` — Increase verbosity (up to `-vvv`). +- `--version` — Show version. +- `--help` / `help` — Show help. + +## Core + +### `arc init [path]` + +Initialize a new arc repository. Creates the `.arc/` directory structure including `commits/`, `bookmarks/`, `tags/`, `stashes/`, `config.yml`, and `HEAD`. The default bookmark is `main`. + +### `arc commit ` + +Commit all current changes. No staging area is needed — changes are detected automatically by comparing the worktree to the last commit. Creates a ZSTD-compressed MessagePack commit object in `.arc/commits/`. If a signing key is configured (`user.key`), the commit is signed with SSH. + +### `arc status` + +Show added, modified, and deleted files since the last commit. + +### `arc diff [start..end]` + +Show a unified diff of changes. Without arguments, shows uncommitted changes. With a range, shows the diff between two commits. + +### `arc log [start..end]` + +Show commit history. Without arguments, shows all commits. Supports ranges like `start..end` (inclusive, either side optional). Each entry shows commit ID, timestamp, author, message, and a `[signed]` tag if applicable. + +### `arc show ` + +Show full details of a commit including author, date, parent(s), signature verification status, and the diff it introduced. Resolves bookmarks, tags, commit IDs, and commit prefixes. + +### `arc history [start..end]` + +Show per-line blame/annotation for a file, showing which commit last modified each line. Uses the Myers diff algorithm. + +## Branching (Bookmarks) + +### `arc mark add [commit]` + +Create a bookmark at the given commit. Defaults to HEAD if no commit is specified. + +### `arc mark rm ` + +Remove a bookmark. Cannot remove the active bookmark. + +### `arc mark list` + +List all bookmarks, marking the active one. + +### `arc mark rename ` + +Rename a bookmark. Updates HEAD if the active bookmark is renamed. + +## Tags + +### `arc tag add [commit]` + +Create an immutable tag at the given commit. Defaults to HEAD if no commit is specified. Tags cannot be overwritten. + +### `arc tag rm ` + +Remove a tag. + +### `arc tag list` + +List all tags with their commit IDs. + +## Navigation + +### `arc switch ` + +Switch the worktree to a bookmark or tag. Switching to a bookmark attaches HEAD. Switching to a tag or raw commit detaches HEAD. Requires a clean worktree. + +### `arc merge ` + +Three-way merge from a bookmark or tag into the current worktree. Creates a merge commit with two parents. Reports conflicts with `<<<<<<< ours / ======= / >>>>>>> theirs` markers. + +## Undo & Modification + +### `arc revert ` + +Create a new commit that reverses the changes from a commit or range. Uses three-way merge. + +### `arc reset [file...]` + +Reset the worktree to match the last commit. Without arguments, resets all files. With file arguments, resets only those files. + +### `arc graft --onto ` + +Cherry-pick/rebase commit(s) onto a target. Uses three-way merge. If the target is a bookmark, updates the bookmark pointer. + +## Stash + +### `arc stash create ` + +Create a named stash and set it as active. + +### `arc stash use ` + +Switch the active stash. + +### `arc stash push` + +Push dirty changes onto the active stash and reset the worktree. + +### `arc stash pop` + +Pop the most recent entry from the active stash and apply it to the worktree. Requires a clean worktree and matching HEAD. + +### `arc stash rm ` + +Remove a named stash. + +### `arc stash list` + +List all named stashes with entry counts. + +## Configuration + +### `arc config set [-g] ` + +Set a config value. Use `-g` for global config. Keys use `section.field` format (e.g. `user.name`, `default.bookmark`, `aliases.c`). + +### `arc config get [-g] ` + +Get a config value. Without `-g`, resolves local first, then falls back to global. + +### `arc config show [-g]` + +Show full configuration as YAML. Without `-g`, shows effective (merged) config. + +### `arc config unset [-g] ` + +Remove a config key. + +### Valid Config Fields + +- `user.name` — Author name. +- `user.email` — Author email. +- `user.key` — SSH key path for commit signing. +- `default.bookmark` — Default bookmark name (default: `main`). +- `default.remote` — Default remote name (default: `origin`). +- `aliases.` — Command aliases, expanded at the CLI level. + +## Git Bridge & Remotes + +### `arc remote add ` + +Add a remote. Stored in `.arc/remotes.yml`. + +### `arc remote rm ` + +Remove a remote. + +### `arc remote list` + +List all configured remotes. + +### `arc push [-r remote]` + +Push to a git remote. Converts arc commits to git commits via a shadow git repo (`.arc/git/`), with the mapping stored in `.arc/git-map.yml`. + +### `arc pull [-r remote]` + +Pull from a git remote. Converts git commits to arc commits. Fast-forward only. + +### `arc clone [-b branch] [path]` + +Clone a git remote and convert it to an arc repository. + +### `arc migrate` + +Convert an existing git repository to an arc repository. Imports all branches as bookmarks and all tags. Preserves commit history. + +### `arc sync [-p]` + +Sync all bookmarks and tags to the shadow git repo. With `-p`, also pushes to the default remote. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..12f4ffc --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,64 @@ +# Configuration + +Arc uses YAML configuration files. + +```yaml +user: + name: hanna + email: me@hanna.lol + key: ~/.ssh/id_ed25519 + +default: + bookmark: main + remote: origin + +aliases: + c: commit + p: push + l: pull +``` + +## File Locations + +- **Local config**: `.arc/config.yml` inside the repository. Created automatically on `arc init` with default bookmark/remote values. +- **Global config**: `$XDG_CONFIG_HOME/arc/config.yml`, or `~/.config/arc/config.yml` if `XDG_CONFIG_HOME` is not set. + +## Resolution Order + +Local config values take priority over global config values. For each field, the local value is checked first; if not set, the global value is used. Aliases from both configs are merged, with local aliases overriding global ones with the same name. + +## Sections + +### `user` + +| Key | Description | +|---|---| +| `user.name` | Author name for commits. | +| `user.email` | Author email for commits. Both name and email must be set for author info to appear on commits. | +| `user.key` | Path to an SSH private key for commit signing (e.g. `~/.ssh/id_ed25519`). Tilde expansion (`~`) is supported. When set, commits are automatically signed. | + +### `default` + +| Key | Description | +|---|---| +| `default.bookmark` | Default bookmark name (used on init). Defaults to `main`. | +| `default.remote` | Default remote name (used by push/pull/sync). Defaults to `origin`. | + +### `aliases` + +Maps short names to command names. For example, `aliases.c: commit` lets you run `arc c "message"` instead of `arc commit "message"`. Aliases are expanded at the CLI level before command dispatch. Any alias key is valid; the value should be a valid arc command name. + +## Commands + +``` +arc config set Set a local config value. +arc config set -g Set a global config value. +arc config get Get a value (local first, then global). +arc config get -g Get a value from global config only. +arc config show Show the effective (merged) configuration. +arc config show -g Show only the global configuration. +arc config unset Remove a key from local config. +arc config unset -g Remove a key from global config. +``` + +Keys use dot notation: `section.field` (e.g. `user.name`, `default.remote`, `aliases.st`). diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..6b53f3b --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,70 @@ +# Getting Started + +Arc is a delta-based version control system written in Rust. It builds exclusively through Nix. + +## Building + +Arc uses a Nix flake (nixpkgs-unstable, flake-parts, fenix, crane): + +```sh +nix build +``` + +Enter a dev shell with `nix develop` (provides rustc, cargo, clippy, rustfmt, git, pkg-config, cmake, perl). + +Run Arc with `nix run` or `result/bin/arc` after building. + +## Configuration + +```sh +arc config set user.name "yourname" +arc config set user.email "you@example.com" +``` + +Global config lives at `$XDG_CONFIG_HOME/arc/config.yml` (or `~/.config/arc/config.yml`). + +## Creating a Repository + +```sh +arc init [path] +``` + +This creates a `.arc/` directory containing: + +- `commits/` — commit storage +- `bookmarks/` — branches (default: `main`) +- `tags/` — tagged commits +- `stashes/` — stashed changes +- `config.yml` — repository config +- `HEAD` — current position + +The default remote is `origin`. + +## Tracking Changes + +Arc tracks changes automatically — there is no staging area. + +```sh +arc status # see what changed +arc commit # commit all changes +arc log # view history +arc diff # see diffs +``` + +## Ignoring Files + +Create a `.arcignore` or `.ignore` file with gitignore-like glob syntax. Prefix a pattern with `!` to negate it. + +## Working with Git Repositories + +Clone a git repo: + +```sh +arc clone [path] +``` + +Migrate an existing git repo (run inside the repo): + +```sh +arc migrate +``` diff --git a/docs/git-bridge.md b/docs/git-bridge.md new file mode 100644 index 0000000..cd205fa --- /dev/null +++ b/docs/git-bridge.md @@ -0,0 +1,65 @@ +# Git Bridge + +Arc uses an internal git bridge to interoperate with git remotes. Since Arc uses its own delta-based storage format (ZSTD-compressed MessagePack), it maintains a shadow bare git repository to translate between formats when communicating with git servers. + +## Shadow Repository + +- Located at `.arc/git/` — a bare git repository created automatically on first use. +- A bidirectional mapping between arc commit IDs and git OIDs is stored in `.arc/git-map.yml`. +- The map has two fields: `arc_to_git` and `git_to_arc`, both string-to-string mappings. + +## Commit Conversion + +**Arc → Git:** The bridge materializes the full file tree at each arc commit, writes it as a git tree object, and creates a git commit with the same message, author, and timestamp. Parents are recursively converted. Cached mappings are reused. + +**Git → Arc:** The bridge reads the git tree, computes deltas against the parent's tree using Arc's `detect_changes`, and creates arc `CommitObject` entries. Author info and timestamps are preserved from the git commit. + +## Authentication + +- SSH authentication is attempted first via the SSH agent, then by scanning `~/.ssh/` for key files (`id_ed25519`, `id_rsa`, `id_ecdsa`). +- Interactive/password authentication is not supported. +- Up to 4 authentication attempts are made before failing. + +## Commands + +### Push (`arc push [-r remote]`) + +- Converts all arc commits on the current bookmark to git commits. +- Pushes the current bookmark as a git branch to the remote. +- Only pushes the active bookmark's branch. +- Uses fast-forward only; diverged histories produce an error. +- Default remote is `origin` (configurable via `default.remote`). + +### Pull (`arc pull [-r remote]`) + +- Fetches from the remote into the shadow git repo. +- Converts new git commits to arc commits. +- Fast-forward only — if the local bookmark has diverged, an error is returned. +- Updates the bookmark pointer and worktree. + +### Clone (`arc clone [-b branch] [path]`) + +- Clones the git remote into a new arc repository. +- Imports all branches as bookmarks and tags. +- Checks out the specified branch (or the remote HEAD, or `main` by default). +- Writes the full worktree after import. + +### Migrate (`arc migrate`) + +- Converts an existing git repository (in the current directory) to an arc repository. +- Creates `.arc/` alongside the existing `.git/`. +- Imports all `refs/heads/*` as bookmarks and `refs/tags/*` as tags. +- Preserves remotes from the git config. +- Sets HEAD to the same branch as the git repo's HEAD. + +### Sync (`arc sync [-p]`) + +- Ensures the shadow git repo mirrors all arc bookmarks and tags. +- Converts any new arc commits to git commits and updates git refs. +- With `-p` (`--push`), also pushes all refs to the default remote. + +## Remotes + +- Stored in `.arc/remotes.yml` as a YAML map of name → URL. +- Managed with `arc remote add|rm|list`. +- When pushing to git, bookmarks become git branches and tags become git tags.