1 unstable release

0.1.0 Feb 21, 2019

#1950 in Development tools

MIT/Apache

265KB
4.5K SLoC

quilt is a minimal version control system (VCS) based on the same ideas as pijul, as described in the series of blog posts here. This is not a real VCS, and you should not use it for anything important. (For starters, it is only capable of tracking a single file.) I wrote quilt to help me understand the ideas discussed in the blog posts, and I'm making it public in the hope that maybe it will help someone else also.

Installation

quilt is a command line program. It has only been tested on Linux, although it will probably also work on similar operating systems. quilt is written in rust; to install it, you will need a rust toolchain installed. Once you've done that, clone this repository and build with cargo:

$ git clone https://github.com/jneem/quilt.git
$ cd quilt
$ cargo build --release

Then you can find the ql binary in the target/release/ directory.

Usage

Creating a repository

To start your quilt journey, initialize a repository in the current directory:

$ ql init
Created empty quilt repository.

This will create a .quilt directory in the current directory, containing the file db. This db file contains all of quilt's internal data. It's plain text (in YAML format), so you can look if you're curious.

Creating and applying patches

Each quilt repository is capable of tracking only one file, and the filename defaults to quilt_file.txt. To create a patch, use the command ql patch create. For example, let's suppose that you've just created a repository, and then you edit the file quilt_file.txt. To create a patch reflecting your new changes: do

$ ql patch create --author "My Name" --description "Something something"
Created patch rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=

That long string in the output is the unique identifier of the patch you just created. It was obtained by hashing the contents of the patch (including a timestamp, so you're unlikely to see the same hash twice even if you have exactly the same contents).

One idiosyncracy of quilt is that it doesn't (by default) apply the patches as soon as you create them, as opposed to (for example) git commit, which creates a patch and also applies it to the current branch. If you want to both create and apply a patch at the same time, provide the argument --then-apply to ql patch create:

$ ql patch create --author "My Name" --description "Something something" --then-apply
Created and applied patch rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=

Alternatively, you can first create the patch and then apply it with the ql patch apply command:

$ ql patch create --author "My Name" --description "Something something"
Created patch rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=
$ ql patch apply rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=
Applied:
  vDLmQ2m8JnblI0wPq2bTSYusqNtHOLNo1iRt4nWdyLY=

If you want to unapply a patch, use ql patch apply --revert:

$ ql patch apply -R rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=
Unapplied:
  vDLmQ2m8JnblI0wPq2bTSYusqNtHOLNo1iRt4nWdyLY=

Outputting a file

Another of quilt's quirks is that it doesn't automatically update the working copy of your file to match changes in the internal repository. To output a file containing the repository's current contents, use the ql render command. By default, the repository's contents will be outputted to the quilt_file.txt file, but you can change that.

$ ql render # outputs the repository contents to quilt_file.txt
$ ql render --path other_file.txt # specify another output path

Putting it together

$ ql init
Created empty quilt repository.
$ echo "First line" > quilt_file.txt
$ ql patch create --author Me --description "I wuz here" --then-apply
Created and applied patch rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=

# Now the file stored in the repository consists of the single line
# "First line". The file quilt_file.txt also consists of the single line
# "First line", but that's because we put it there ourselves; quilt hasn't
# touched it.

$ echo "Second line" >> quilt_file.txt
$ ql patch create --author Me --description "Me again" --then-apply
Created and applied patch xGRnP1j1o9FdJPPJoD6OM4Pxj3qgyN2hKG_0qg54t38=

# Now the file stored in the repository has two lines, and so does the
# file quilt_file.txt.

$ ql patch apply --reverse
Unapplied:
  xGRnP1j1o9FdJPPJoD6OM4Pxj3qgyN2hKG_0qg54t38=

# The file stored in the repository has just one line. To reflect that change
# in the filesystem, we need to render:
$ ql render
Successfully wrote file 'quilt_file.txt'
$ cat quilt_file.txt
First line

Conflicts and resolution

The basic theory behind the way that quilt deals with conflicts is described in the series of blog posts here. The main idea is that instead of files, quilt stores "graggles", which are directed graphs of lines. A file is the special case of a graggle in which the directed graph of lines enforces a unique ordering. (More precisely, a graggle is a file if it has a unique topological sort.) Most of the time, you want your repository to represent a file; if it reprsents a graggle that isn't a file, we call it a conflict.

Here's a way to get a conflict:

$ ql init
Created empty quilt repository.
$ echo "First line" > quilt_file.txt
$ ql patch create --author Me --description "Starting out" --then-apply
Created and applied patch rLbZ6RjMol8_wV0tW2dnMapcaNVJB25A9uWFXixDU6c=
$ echo "Second line" >> quilt_file.txt
$ ql patch create --author Me --description "Working hard"
Created patch xGRnP1j1o9FdJPPJoD6OM4Pxj3qgyN2hKG_0qg54t38=

# Notice that we haven't applied the second patch, so the file in the repository
# only has the first patch applied. Now let's edit the file on disk so that
# it consists of "First line" followed by "Alternate second line":

$ echo "First line" > quilt_file.txt
$ echo "Alternate second line" >> quilt_file.txt
$ ql patch create --author Me --description "Working differently" --then-apply
Created and applied patch y-lgpjY30n5STzqtrMOEkvBM_WUWy0Yji91y9KTzptc=

# And finally, we apply the patch that added "Second line"
$ ql patch apply xGRnP1j1o9FdJPPJoD6OM4Pxj3qgyN2hKG_0qg54t38=
Applied:
  xGRnP1j1o9FdJPPJoD6OM4Pxj3qgyN2hKG_0qg54t38=

Now, the effect of that long command listing was to create a graggle containing the line "First line" followed by either "Second line" or "Alternate second line", but with no prescribed order between the two possible second lines. In particular, the result isn't a file, because the lines it contains aren't in a linear order. If you try to render the file, it won't work:

$ ql render
Error: Couldn't render a file, because the data isn't ordered

There are two important commands that you can use to resolve a conflict. The first is to inspect it, by rendering a graph:

$ ql graph

This will create a "dot" file, which can be rendered using graphviz:

$ dot -o out.pdf -Tpdf out.dot

And now you can look at out.pdf to see a visualization of your graggle that isn't a file.

Once you understand what's going on, you can resolve your conflict using quilt's built-in interactive graggle resolver:

$ ql resolve --author Me

This interactive utility will guide you through the process of turning the unordered graggle into a totally ordered file. In the example above, this amounts to deciding whether "Second line" should go before or after "Alternate second line". (At some point in the probably-distant future, I hope to create some comprehensive documentation for ql resolve's user interface. But for now, hopefully it's reasonably explorable. Anyway, you can see all of the currently-active key bindings in the top right.)

When ql resolve is done, it will produce a patch, which you can then apply to get rid of the conflict:

$ ql resolve --author Me
# do the interactive thing...
Created patch SfxSwnA2POPHzL4eNNHku7t4Lyl5xW7Ge9pRXr5hV60=
$ ql patch apply SfxSwnA2POPHzL4eNNHku7t4Lyl5xW7Ge9pRXr5hV60=
Applied:
  SfxSwnA2POPHzL4eNNHku7t4Lyl5xW7Ge9pRXr5hV60=
$ ql render
Successfully wrote file 'quilt_file.txt'

Dependencies

~7–16MB
~200K SLoC