Table of Contents
The Stiftfile
A project's stiftfile sits at its root, in plain sight, as a qlfile or
a Makefile does. It is the only file stift needs: it holds what the
project asks for, and the commit chosen for each project of its build, so
it does the work of both a qlfile and a qlfile.lock. Commit it with the
project.
;;; -*- mode: lisp-data -*-
(:project "fakehacker.cc/dxtr/pyssel"
:require (("https://fakehacker.cc/dxtr/cl-gfx.git" :branch "main"
:commit "5e03cf5ae3ff322ec143280a69680ed4b13ce233")
("https://github.com/Shinmera/parachute.git" :branch "master"
:commit "9a6679e611925dfb59067393c5b7996f69501aa6" :test t)
("https://fakehacker.cc/dxtr/cl-ffi.git" :branch "main"
:commit "b7248691986fdeb2e7b4c0bcb7463ea3375089bb"
:indirect t)))
The first line is a mode line, so that Emacs opens the file as Lisp data.
Properties
The file holds one property list, data that describes the project, as a
defsystem form describes a system:
:project NAMEcomes first and names the project: the host and path of its repository (see Naming projects).:require (ENTRY ...)holds an entry for each project required.(URL :commit ID ...)requires the project atURL, at the commitID, a full commit ID: 40 hexadecimal digits, or 64 in a repository that uses SHA-256, in lower case. The commit or any commit that contains it meets the requirement. An entry's other keys::branch NAMEor:tag NAME, the ref the commit was read from, never both, whichupdatefollows;:test t, for a project only this project's own tests need, such as Parachute;:indirect t, on the entriestidywrites: projects that only the projects this one requires need (see Choosing Commits);:exclude ("DIR/" ...), directories of the project required, relative to its root, whose systems stift leaves out, such as a copy of another library it carries.
:replace (ENTRY ...)holds an entry(URL ...)for each project whose files it chooses, whatever the requirements say::commit ID, that commit;:with URL2 :commit ID, that commit of another repository, such as a fork;:local DIR, the files of a directory, relative to this project's;:branch NAMEor:tag NAMEbeside:commit, the refupdatefollows.
:require and :replace are left out when they would be empty. A
project may be required once and replaced once, and a project can neither
require nor replace itself.
Which entries count where
The stiftfile of each project required counts too: stift reads it at the commit required, and follows its requirements (see Choosing Commits). Three things count only in the main project, the one being built:
- requirements marked
:test, since nobody who uses a library needs its test framework; :replace, and:exclude, since a library cannot rearrange the builds of the projects that use it.
Reading and writing it
stift reads the file with a reader of its own, not with read, since it
reads other people's stiftfiles too. It accepts lists, strings, the keys
above, integers, t, nil and comments (; and #| |#), and nothing
else: no #., no quotes, no symbols. It evaluates nothing and interns
nothing. A mistake is reported with the file, the line and the column:
/home/me/hello/stiftfile:4:13: :colour is not a key stift knows.
stift get, remove, tidy and update keep the direct requirements
first and the indirect ones after them, each sorted by name, and the
replacements sorted by name, so that a new entry lands in a place of its
own, and two branches that each add one rarely touch the same line. They
edit the file entry by entry:
- An entry they do not change keeps its text.
- The comments on lines of their own above an entry, and those after it on its line, stay with it, even when it moves, as a requirement that becomes indirect does.
- An entry they write is laid out by stift rather than by the Lisp printer, so that SBCL and CCL write the same bytes: the URL on the first line, then the keys, as many to a line as fit in 72 characters.
- A property whose list becomes empty goes, and one that comes is written in its place among the others.
Naming projects
A project's name is the host and path of its repository, without the
scheme, the user, the port, a trailing / and a final .git; for a
repository on this machine, its path. Every URL of a repository gives the
same name:
| URL | Project |
|---|---|
https://fakehacker.cc/dxtr/cl-ffi.git |
fakehacker.cc/dxtr/cl-ffi |
git@fakehacker.cc:dxtr/cl-ffi |
fakehacker.cc/dxtr/cl-ffi |
ssh://git@fakehacker.cc:2222/dxtr/cl-ffi/ |
fakehacker.cc/dxtr/cl-ffi |
file:///srv/git/x.git |
/srv/git/x |
A stiftfile's :project gives the name in that form, which stift init
writes from a name or a URL. The name keeps no more than it needs, and,
unlike a short name such as cl-ffi, no other project can have it: with no
registry, two repositories of one short name, a fork and its original or
two unrelated libraries, would be taken for one project.
Paths keep their case. How a URL is fetched is git's business: a stiftfile
written with https:// URLs can be fetched over SSH through git's
url.<base>.insteadOf, so stiftfiles need not differ between people.
stift fetches from https://, ssh://, USER@HOST:PATH and file:///PATH
URLs. It refuses http:// and git://: a branch read over them could be
changed on the way, and the commit recorded from it trusted from then on.