ed54e28f — Eyal Sawady 7 months ago
HACKING.md: initial commit
5079df99 — Eyal Sawady 7 months ago
README.md: update commit spec
1c31152d — Eyal Sawady 7 months ago
usage(): add cat


browse  log 



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


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

Note: WIP, expect major breakage.


  • 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.


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.



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


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.

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


The index file is a pointer to a tree object.


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.


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


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:

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, any number of parents, one committer, 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.


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:

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 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.