pvc <cmd>: patch version control command line utility.
NOTICE: currently re-writing, not functional.
pvc is a version control system similar to git or mercurial, but is ultra
simple: branches are simply a
base followed by a set of unix patches with
incrementing id. The only fundamental disadvantage (or advantage) of using pvc
instead of git is that pvc supports only fast-forward merges, which are the
cleanest and simplest to understand.
Basic Operation
The
pvc command works very similar to git. Use
pvc --help to get
a full list of available commands. Below are the major ones:
- init: initialize your repo. Be sure to edit .pvcignore with
line-separated lua patterns to ignore.
- commit "description": commit the current state, creating a
patch file in the current branch.
- status: get the current status of your local working tree.
- at branch@id: checkout a revision.
- branch "name": create a new branch.
- squash for local cleanup, rebase for including changes
from your base branch, graft for merging changes into
another branch.
The
.pvcignore file should contain a line-separated list of
#string.find
patterns that should be ignored. Items ending in
/ will apply to whole
directories. A common pvc ignore file might look like:
# directories
%.git/
%.out/
# extensions
%.so$
# binary files
%./path/to/some_binary
As the name implies, pvc is patch-based version control.
A branch is a @branch/ dir inside the local .pvc/ with a base and a series
of numbered patch files in rev/, e.g 01.p, 02.p, etc. A branch also has
a tip file with the current tip id.
Commits are performed by diffing from the previous snapshot, creating the patch
file.
Snapshots are created by applying patch files in either forward/reverse order to
an existing snapshot, with the "zero" snapshot being an empty directory.
Any other modification to a branch (e.g squash, rebase, etc) use a simple state
machine:
- a special .branch/ directory is created for the branch and any that depend
on it.
- an @.branch/ops (operations) file is created inside each with the exact
operations that need to be performed (in reverse order) to end up at the
intended state.
- pvc fix automatically attempts to fix all @.branches/ from their root to
their tip, starting at the base (or whichever is specified). Fixing simply
means applying the operations in ops, poping the final line after it is
complete.
For an example of the above, to rebase (change the base), the .branch's base
will have the new re-based id and the ops file will be a series of cherry picks
from the original branch's id 1 to it's tip. To perform a split, the ops file
just cherry cherry pick specific files, dropping a commit simply means
skipping a cherry picks, inserting a commit is cherry picking between two commits,
etc. Any merge conflicts stop the process, which the user needs to fix and commit
and then run pvc fix again. However, the user can fix any problems later and
work on another branch.
Finally, the entire .pvc directory is tracked by a hidden @.META branch
as a single series of patches, allowing the user to (soft) reset to a previous
repo state at any time.
Command pvc
Usage:
pvc <subcmd> --help
This is the
pvc command-line tool which can also be called directly from
Lua.
pvc uses rev (revision) semantics of 'branch@id' where "branch" is a
branch name (e.g "main") and id is an incrementing patch id, where later
ids are always later patches.
Special branch names include:
- 'local' or just '': the local work tree (live code).
- 'at': the current branch@id.
- 'at@id', '@id' or just 'id': the current branch at a different id.
Special id names include:
- 'tip': when used alone, the tip of the current branch.
'branch@tip' or just 'branch' mean the tip of the specified branch.
Subcmd init
Usage:
pvc init dir --branch=main
Initialize the current directory as a pvc directory.
This will create
.pvc/ and
.pvcpaths
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- branch ="main"
the initial branch name
Subcmd diff
Usage:
pvc diff rev1::rev2 paths...
Show the difference between rev1 and rev2.
With no args, this shows the difference between at::local, or
the changes from the current commit to the work_tree.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd apply
Usage:
pvc apply path/to/patch.p...
Apply a series of patches
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- exclude
paths to exclude
Subcmd commit
Usage:
pvc commit 'message' -- specific/paths to/commit
Commit local changes to the current branch, creating
a new id.
The message is prepended to anything in the file ./PATCH. The ./PATCH
file is deleted after the commit is complete.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd at
Usage:
pvc at [rev] --force
If
rev is not given, just returns current branch#id.
Otherwise, sets the active branch@id, causing the local directory to be
updated to be that snapshot.
If any local changes would be overwritten this will fail unless force=true.
at branch@id is called a "checkout" in other version
control systems.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- force
overwrite local changes
Subcmd upload
Usage:
pvc upload branchName path/to/dir
Backs up the branch to the directory, making a complete copy of the
rev/ and metadata files but not copying snapshots.
TODO: support upload to remote repo.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- force
if true delete any existing directories.
Subcmd pull
Usage:
pvc pull branchName path/to/dir
Restore the branch from path/to/dir.
TODO: support pulling from remote repo.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- force
if true delete an existing branch.
Subcmd tip
Usage:
pvc tip [branch]
Get the tip id of branch.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd branch
Usage:
pvc branch name [from]
Start new branch
name branching off of
from.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd show
Usage:
pvc show [branch#id] --before=10
Show the commits before/after
branch#id.
If branch#id is not given, print all branches.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- before
number of records before id to show
- after
number of records after id to show
- paths
show only paths.
Subcmd desc
Usage:
pvc desc branch#id=current [$to/new.cxt]
Get or set the description for a single branch id.
The new description can be passed via to/new.cxt or
after -- (like commit).
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd squash
Usage:
pvc squash [name#id]
Combine changes and descriptions from
branch id -> endId (inclusive)
into a single commit. You can then edit the description using
pvc desc branch#id.
This enables making lots of small commits and then
"squashing" them into a single commit once they are
in a good state.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- branch ="current"
the branch to squash
Subcmd rebase
Usage:
rebase [branch=current] --id=10
Change the base of
branch to
id.
Rebase is a fundamental operation. It allows you to make or accept changes
on your "main" branch while you work on development branches which are
behind the main. See #basic for more details.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- id
the id of base to change to
Subcmd grow
Usage:
grow --branch=current [from]
Copy changes in
from onto
branch.
In other version control systems this is called a
"fast forward merge"
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
- branch
the branch to mutate
Subcmd prune
Usage:
prune branch#id
- if #id: delete ids id -> tip (inclusive).
- else: delete branch
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd export
Usage:
export branch to/
Copy all patch files in the branch to
to/.
The resulting directory is commonly sent to
tar -zcvf branch.tar.gz path/ and then branch.tar.gz sent to a
maintainer to be merged.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Subcmd snap
Usage:
snap [branch#id=current]
Get the snapshot directory of branch#id.
The snapshot contains a copy of files at that commit.
Arguments:
- work_tree
working directory where code is
- pvc_repo
path to pvc repo, typically work_tree/.pvc/
Mod pvc.repo
pvc submodule containing most logic for managing the pvc repository.
Types: Repo
Functions
- fn rawIdPath(id)
Given an id return 01/23/45 (no extension)
Record Repo
A
.pvc/ repo with branches, snapshots and a current state.
Fields:
- w
the work_tree, aka the live user code
- r
repo dir, typically .../.pvc/
- atPath
.../.pvc/at
Methods
- fn:pvc(cmd)
- fn:index()
- fn:at() -> rev
Get rev (br@id) the repo is currently at.
- fn:atBranch() -> branch
- fn:bdir(br) -> .pvc/br/
Get the branch directory.
- fn:base(br) -> rev
Get the base of the branch
- fn:tip(br) -> int
Get the tip of the branch.
- fn:setTip(br, id)
- fn:rev(rev) -> rev
Converts a rev into (typically) branch@id form. Exceptions include:
- nil/local become 'local', meaning the local snapshot.
- /path/to/dir/ is kept and must be a snapshot path with an index.
See pvc docs for all special names and ids (local, at, tip, etc).
- fn:splitRev(rev) -> br, id
- fn:patch(rev) -> /d/br/rev/01/23.p
Get path to patch
- fn:snapPath(rev) -> /d/br/snap/123/
Get snapshot dir and index
- fn:snapshot0(br) -> snap.Snap
Create snapshot 0
- fn:newBranch(name, baseRev) -> ../branch/
Initialize a new branch.
This handles both initial base=nil, id=0 and from base.
- fn:init(br) -> self
Initialize a new repository with main br name (default='main').
- fn:tmp(name) -> .pvc/tmp/name
Return empty temporary folder of name.
Deletes the tmp dir if it already exists.
- fn:localsnap()
- fn:snapshot(rev) -> Snap, rev
Snapshot the br@id by applying patches.
Return the materialized snapshot and the rev.
- fn:diff(r1, r2) -> snap.Diff
Compare br1@id to br2@id
- fn:commit(desc, diff) -> rev2
Commit
- fn:setAt(rev, force)
Mod pvc.snap
pvc submodule containing logic for snapshots, aka complete directories of
files.
Types: Path Snap Diff
Functions
- fn loadIndex(index) -> {Path}
- fn loadDirAsIndex(dir) -> {Path}
Load a raw directory with .pvcignore etc as a list of Paths.
Note that this drops any information about the path origin id.
- fn trimPatch(patch, exclude, out) -> {lines}
Remove patch prefix (description) and exclude any paths in exclude set.
Record Path
Object in index files in form
(+-x)(mtime):(id)/p
Fields:
- c
created
- d
deleted
- m
modified
- x
executable
- mtime
modification time
- id
last id modified in
- p
the path value
Methods
- fn:cleaned() -> Path
- fn:updateId(id) -> Path
- fn from(T, str)
Create Path from string.
- fn:noMTime() -> Path
return Path without the mtime.
Record Snap
A snapshot with a set of indexed paths.
Fields:
Methods
Record Diff
Diff:of(dir1, dir2) returns what changed between two pvc dirs.
Fields:
- dir1
- dir2
- equal
- changed
- deleted
- created
Methods
- fn:reduced() -> Diff
Return a copy with equal={}.
- fn:hasDiff()
- fn:format(fmt, full)
- fn:patch() -> patchText
Mod pvc.unix
Functions
- fn diff(a,al, b,bl) -> string?
Get the unified diff using unix diff --unified=1,
properly handling file creation/deleting
the l variables are the "label" to use.
when the coresponding value is nil then the label is /dev/null
- fn merge(to, base, change) -> ok, err
incorporate all changes that went into going from base to change into to