Hell Version Control
A lightweight, content-addressed version control system for solo developers.
Snapshots are stored as SHA-256 objects, compressed with a custom LZ codec and
delta encoding, and kept in a plain-text layout under .hvc/.
Installation
One-line install on Linux. Downloads the latest release binary.
$ curl -fsSL https://hvc.example.com/install.sh | sh
Build from source
Download the source archive and compile with Cargo.
$ curl -fsSL https://hvc.example.com/hvc-source.zip -o hvc-source.zip
$ unzip hvc-source.zip && cd hvc-source
$ cargo build --release
Initialize a repository
Run init at the root of the project you want to version.
$ hvc init
This creates a .hvc/ directory containing:
.hvc/
├── active # branch name or detached hash
├── staged # index
├── ignore # ignore patterns
├── history # reflog of HEAD moves
├── branches/ # one file per branch
└── data/ # loose objects and packfiles
Quick workflow
save stages every change and commits in one step. undo moves
HEAD back one commit, keeping your files on disk untouched.
$ hvc save "tweak renderer"
Saved 3 change(s) [a1b2c3d]
$ hvc undo
Undid last save. Files on disk remain untouched.
save respects .hvc/ignore and always skips .hvc/ itself.
If nothing changed, it exits without creating a commit.
Staged workflow
For precise control, stage files with add, then commit.
Stage
$ hvc add src/main.rs src/lib.rs
added 2 file(s)
$ hvc add "src/**/*.rs" # glob pathspecs
$ hvc add . # everything under cwd
Unstage
$ hvc unstage src/lib.rs
unstaged 1 file(s)
Commit
The string after -m is a free-form commit message.
$ hvc commit -m "your message"
[a1b2c3d] your message
Status
Compares HEAD, the index, and the working directory. Prints clean when
nothing differs.
- staged: differences between HEAD and the index.
- modified: tracked files whose content or mode differs from the index.
- deleted: tracked files missing from disk.
- untracked: files on disk that are neither tracked nor ignored.
$ hvc status
staged:
new src/tests.rs
modified:
src/main.rs
untracked:
notes.md
Log
Walk history from HEAD, following the first parent.
$ hvc log
commit 3f8a2c1e...
date 1733241600
tweak renderer
| Flag | Effect |
|---|---|
--oneline | Short hash plus first line of message. |
-p | Print a textual patch against the first parent. |
Diff
Compares the index against the working directory. Use --staged to see
what commit would record.
$ hvc diff
modified: src/main.rs
fn main() {
- println!("hi");
+ println!("hello");
}
$ hvc diff --staged
Uses Hirschberg LCS: O(n·m) time, O(min(n,m)) memory. Binary files print
(binary).
Undo
Moves HEAD to HEAD~1 and rebuilds the index. Working files are not touched.
$ hvc undo
Undid last save. Files on disk remain untouched.
After undo, status will show the previous commit's content as modified,
since the working tree still holds the newer files.
Rollback
Moves HEAD to an arbitrary revision. Default is soft (index only); --hard
also overwrites working files.
$ hvc rollback a1b2c3d # soft reset
HEAD is now at a1b2c3d
$ hvc rollback a1b2c3d --hard # discard working changes
$ hvc rollback --last # same as HEAD~1
--hard overwrites tracked files and deletes
tracked files not in the target tree. Untracked files are left alone.
Restore
Bring files back from the index or from any commit.
$ hvc restore -- src/main.rs # from index
$ hvc restore HEAD~2 -- src/main.rs # from a commit
$ hvc restore a1b2c3d -- README.md
The -- separator is required. Before it: revision. After it: path.
Branch
List branches or create one at the current HEAD.
$ hvc branch
* main
experiment
$ hvc branch experiment
created branch experiment
Branch names map 1:1 to files under .hvc/branches/. Current branch is
recorded in .hvc/active.
Switch
Change the active branch, or check out a raw commit (detached HEAD).
$ hvc switch experiment
switched to experiment
$ hvc switch a1b2c3d
switched to a1b2c3d
rollback --hard first.
Merge
Three-way merge using the best common ancestor. Fast-forwards when possible.
$ hvc merge experiment
fast-forward to 9e4f1c0
Conflict handling
Conflicting files are written with markers and listed in .hvc/IN_PROGRESS:
<<<<<<< current
local version
=======
other version
>>>>>>> target
Resolve, then:
$ hvc merge --continue # finalize
$ hvc merge --abort # discard
--continue refuses to proceed while any conflicted file still contains
<<<<<<< .
Garbage collection
Marks reachable objects, deletes the rest, and packs loose objects into
.pack + .idx pairs.
$ hvc gc
packing 87 loose object(s)...
packed 87 object(s)
gc: removed 12 unreachable object(s)
Packing uses a greedy delta search over recent objects of the same type, preferring deltas smaller than half the target size.
Help
Print the built-in command summary. Running hvc with no arguments does the same.
$ hvc help
Command reference
| Command | Description |
|---|---|
init | Create a new .hvc/ repository. |
save [msg] | Auto-stage all changes and commit. |
add <paths…> | Stage files, directories, or glob pathspecs. |
unstage <paths…> | Remove entries from the index. |
commit -m <msg> | Commit the current index. |
undo | Move HEAD to the previous commit, keeping files. |
rollback <rev> [--hard] | Reset HEAD (and optionally the working tree). |
status | Show staged / modified / deleted / untracked. |
log [--oneline] [-p] | Walk commit history from HEAD. |
diff [--staged] | Changes vs. index, or index vs. HEAD. |
restore [rev] -- <paths…> | Restore files from index or a commit. |
branch | List branches (current marked with *). |
branch <name> | Create a branch at HEAD. |
switch <target> | Switch to a branch or detached commit. |
merge <branch> | Merge into the current branch. |
merge --continue | Finalize an in-progress merge. |
merge --abort | Abort an in-progress merge. |
gc | Prune unreachable objects and pack loose ones. |
help | Show command summary. |
Revision syntax
| Syntax | Meaning |
|---|---|
HEAD / @ | Current commit. |
main | Tip of the named branch. |
<hash> | Full SHA-256 or any unique prefix (≥ 4 chars). |
rev~n | Walk n first parents back (default 1). |
rev^ / rev^n | First parent / n-th parent. |
$ hvc log --oneline HEAD~3
$ hvc restore main~1 -- src/lib.rs
Architecture
Three object types, two storage formats, and a handful of text files for refs and locks.
Object model
Every object is type_byte || payload, hashed with SHA-256.
| Type | Byte | Payload |
|---|---|---|
| blob | b | Raw content (or symlink target). |
| tree | t | Lines of mode hash path. |
| commit | c | Header block, blank line, message. |
Storage layout
- Loose objects at
data/aa/bb/cccc…. - Packfiles at
data/pack/*.packwith matching.idx. - Index is
staged: one entry per line,mode hash path. - Reflog is
history: one line per HEAD move.
Compression
Loose objects use an LZ77 codec (32 KiB window, 64-probe hash chain), prefixed with
0xFF. If compression doesn't win, the raw payload is stored.
Packfile format
flags (1) | type (1) | hash (32) | [base_off u32, len u32, delta] if DELTA_FLAG
| [len u32, encoded_data] otherwise
The companion .idx is a sorted hash table with a 256-entry fanout prefix.
Delta encoding
Copy (0x00–0x7F + u32 offset) and insert
(0x80–0xFE + bytes) commands, terminated by 0xFF. Only used
if smaller than half the target.
Locking
All writes take a path.lock file with the holder's PID. Stale after 60s
if the PID is gone. Writes go through write_atomic: temp file, fsync,
rename.
Ignore patterns
.hvc/ignore supports # comments, ! negation,
trailing / for dirs, leading / for anchoring. Matches any
path suffix, so *.log catches build/output.log.
License
HVC is free software, released under the GNU General Public License v3.0.
You may redistribute and modify it under the terms of the GPL. It is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.
Full text: GNU General Public License v3.0.
Developer
Designed and maintained by Winexero.