~ecs/tm

14de1b93 — Eyal Sawady 3 months ago
Initial commit

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~ecs/tm
read/write
git@git.sr.ht:~ecs/tm

You can also use your local clone with git send-email.

tm

Time Machine, a (mostly) dependency-free simple version control system written in C99.

Note: WIP, expect major breakage.

Goals

  • Easy to convert between tm and git repos.
  • No other import/export facilities. If you want that, go through git.
  • Prioritize simplicity (in concept and in implementation) and portability over performance and features.
  • It should be possible to reimplement the tm plumbing from scratch in POSIX sh in a few weeks.

Dependencies

POSIX, zlib.

TODO: replace zlib?

Deliberate omissions

  • Tags: use refs
  • Remotes: send patches for collaboration, use ssh and tar to publish.
  • Signed commits: put the signature in the commit message
  • Hooks: write a wrapper script
  • Branches: use refs
  • Configuration files: deal
  • Symlinks: deal

If you don't like any of these decisions, you're free to fork or use something else.

License

AGPLv3.

(While tm is unlikely to come in contact with a network, there's no reason not to protect it from SaaS.)

Internals

Internals are similar to git, except where I thought I could get away with something simpler.

All text is UTF-8. All files are text files, except for objects, which are zlib-compressed text. All text files are newline-terminated.

As in git, objects are identified by 160-bit SHA-1 hashes. A "pointer" is the 40 byte hexadecimal representation of a hash, encoded in UTF-8.

.tm
| index
| objects
  | [aa-ff]
    | [hex SHA-1 hash]
| refs
  | HEAD
  ...

Index

The index file is a pointer to a tree object.

Objects

There are three types of objects: blobs, commits, and trees.

The first line of the object is the type of the object. Unlike in git, the size of the object is not stored in the object, and the object type is terminated with a newline instead of a NUL.

The SHA-1 hash is of the decompressed contents of the object, including the object type.

The path at which an object with hash $HASH whose first two characters are X and Y will be stored is .tm/objects/XY/$HASH.

Blobs

A blob is just a flat array of bytes. tm doesn't care about its contents.

Commits

A commit is a tagged tree. More specifically, a commit encapsulates the following information:

  • The state of the working directory at some point in time
  • The previous state(s) of the working directory. Usually, these are ordered chronologically, but there's nothing stopping you from doing weird stuff.
  • The people who made the changes between this state and the immediately previous states.
  • The person who added this commit to the repository. This is distinct from the person who made the changes. A patch sent over email may be authored by one person and commited by another.
  • A human-readable description of the changes in this commit.

The format of a commit is:

commit
tree deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
parent cafebabecafebabecafebabecafebabecafebabe
parent deafbeaddeafbeaddeafbeaddeafbeaddeafbead
author J. Random Hacker <jrh@example.org>
committer K. Random Hacker <krh@example.org>
date SECS

This line will become the subject of the patch

These lines will become the body of the patch.
This is another line. It serves no purpose except demonstrating that the
body can have multiple lines.

This commit tags the tree deadbeefdeadbeefdeadbeefdeadbeefdeadbeef and has two parents: cafebabecafebabecafebabecafebabecafebabe and deafbeaddeafbeaddeafbeaddeafbeaddeafbead.

Commits must have one tree, at least one parent, one committer, at least one author, one date, one subject line, and any number of body lines. These lines MUST occur in the order specified here.

SECS is the number of seconds since 1970-01-01 00:00 UTC at which the commit occurred.

The subject line MUST be less than 50 characters. The body MUST be hard-wrapped at 72 characters. There must be a blank line between the subject and the body.

Trees

A tree represents a directory. It contains a list of blobs or trees, each of which is associated with a name and a set of permissions.

The format of a tree is:

tree
rwxrwxr-x deadbeefdeadbeefdeadbeefdeadbeefdeadbeef docs
rw-rw-r-- 4242424242424242424242424242424242424242 README.md

This tree has two entries, docs and README.md. docs is world-readable, world-executable, and group-writable. README.md is world-readable, not executable, and group-writable.

Note that we can't tell from this whether docs and README.md are trees or blobs. We can get that information from the objects referenced. (Though in this case, it's obvious that docs is a tree and README.md is a blob.)

Refs

Refs are pointers to commits. Refs may be used anywhere an object is required, and are equivalent to specifying $(cat .tm/refs/$REF).

The HEAD ref will always exist, and points to the commit currently checked out. New commits are parented to HEAD.