|LICENSE||License as CC BY-SA 4.0 International||on February 3, 2019|
|README.md||Fix grammar and typos||on March 19, 2019|
Pijul is an in-development distributed version control system that implements repositories using a different model than Git's. This model enables cool features and avoids common problems which we are going to explore in this tutorial. (The good stuff appears from the "Maybe we don't need branches" section.) It is assumed that the reader uses Git in a daily base, i.e. knows not only how to commit, push and pull but also has had the need to cherry-pick commits and rebase branches at least once.
$ mkdir pijul-tutorial $ cd pijul-tutorial $ pijul init
Nothing new here.
Just like in Git after we create a file it must be explicitly added to the repository:
$ pijul add <files>
There's a difference though: in Pijul a file is just a UNIX file,
i.e. directories are also files so we don't need to create
files to add empty directories to our repos. Try this:
$ mkdir a-dir $ touch a-dir/a-file $ pijul add a-dir/ $ pijul status
The output will be:
On branch master Changes not yet recorded: (use "pijul record ..." to record a new patch) new file: a-dir Untracked files: (use "pijul add <file>..." to track them) a-dir/a-file
To add files recursively we must use the
Pijul can sign patches automatically, so let's create a signing key before we record our first patch:
$ pijul key gen --signing
The key pair will be located in
the moment the private key is created without a password so treat
it with care.
From the user perspective this is the equivalent to Git's commit operation but it is interactive by default:
$ pijul record added file a-dir Shall I record this change? (1/2) [ynkadi?] y added file a-dir/a-file Shall I record this change? (2/2) [ynkadi?] y What is your name <and email address>? Someone's name What is the name of this patch? Add a dir and a file Recorded patch 6fHCAzzT5UYCsSJi7cpNEqvZypMw1maoLgscWgi7m5JFsDjKcDNk7A84Cj93ZrKcmqHyPxXZebmvFarDA5tuX1jL
y means yes,
n means no,
k means undo and remake last
a means include this and all remaining patches,
means include neither this patch nor the remaining patches and
means ignore this file locally (i.e. it is added to
$ echo Hello > a-dir/a-file $ pijul record In file "a-dir/a-file" + Hello Shall I record this change? (1/1) [ynkad?] y What is the name of this patch? Add a greeting Recorded patch 9NrFXxyNATX5qgdq4tywLU1ZqTLMbjMCjrzS3obcV2kSdGKEHzC8j4i8VPBpCq8Qjs7WmCYt8eCTN6s1VSqjrBB4
We saw that when recording a patch we can chose to locally ignore
a file, but we can also create a
in the root of our repository and record it. All those files
accept the same patterns as a
Just like in Git if we want to ignore a file that was recorded in a previous patch we must remove that file from the repository.
$ pijul remove <files>
The files will be shown as untracked again whether they were
recorded with a previous patch or not, so this has the effect of
git reset <files> or
git rm --cached depending on the previous
state of these files.
$ pijul unrecord <patch>
To reference a patch we need its hash, which can be seen with
Unrecording and recording the same patch again will leave the repository in the same state.
There are cases where a patch depends on a previous one.
For example if a patch edits (and only edits) file A it will
depend on the patch that created that file. We can see these
pijul dependencies and they are managed
automatically, which means that if we unrecord a patch with
dependencies all its dependencies are unrecorded as well.
$ pijul revert
This is like
git checkout applied to files (instead of
To create a new branch we use the
pijul fork <branch-name>
command and to switch to another branch we use
pijul checkout <branch-name>.
To apply a patch from another branch we use the
pijul apply <patch-hash> command. Notice that this doesn't produce a
different patch with a different hash like
git cherry-pick does.
Finally to delete a branch we have the
Because in Git each commit is related to a parent (except for the first one), branches are useful to avoid mixing up unrelated work. We don't want our history to look like this:
* More work for feature 3 | * More work for feature 1 | * Work for feature 3 | * Work for feature 2 | * Work for feature 1
And if we need to push a fix for a bug ASAP we don't want to also push commits that still are a work in progress so we create branches for every new feature and work in them in isolation.
But in Pijul patches usually commute: in the same way that 3 + 4 + 8 produces exactly the same result than 4 + 3 + 8, if we apply patch B to our repo before we apply patch A and then C the result will be exactly the same that our coworkers will get if they apply patch A before patch C and then patch B. Now if patch C has a dependency called D (as we saw in Removing a patch) they cannot commute, but the entire graph commutes with other patches, i.e if I apply patch A before patch B and then patches CD I would get the same repository state than if I applied patch B before patches CD and then patch A. So Alice could have the same history as in the previous example while Bob could have
* More work for feature 1 | * Work for feature 2 | * More work for feature 3 | * Work for feature 3 | * Work for feature 1
And the repos would be equivalents; that is, the files would be the same. Why is that useful?
We can start working on a new feature without realizing that it is actually a new feature and that we need a new branch. We can create all the patches we need for that feature (e.g. the patches that implement it, the patches that fix the bugs introduced by it, and the patches that fix typos) in whatever order we want. Then we can unrecord these patches and record them again as just one patch without a rebase. (There's actually no rebase operation in Pijul.)
But this model really shines when we start to work with:
Currently we only have The Nest and pushing only works over SSH. We can reuse our current SSH key pair or create a new pair with
$ pijul key gen --ssh
This new key pair will be stored in the same directory used for the signing keys and we can add it to The Nest like we do with SSH keys in GitHub.
Now that we have an account on The Nest we can upload our
signing key with
pijul key upload.
Now let's push something:
$ pijul push <our-nest-user-name>@nest.pijul.com:<our-repo>
Unless we pass the
--all flag Pijul will ask us which patches we
want to push. So we can keep a patch locally, unrecord it, record
it again, decide that actually we don't need it and kill it
forever or push it a year later when we finally decide that the
world needs it. All without branches.
If we don't want to specify the remote every time we push we can
set it as default with the
Of course to pull changes we have the
pijul pull command.
Both commands have
--from-branch (source branch),
--to-branch (destination branch) and
--set-remote (create a
local name for the remote) options.
BTW if we can keep patches for ourselves can we pull only the patches we want? Yes, that's called "partial clone". It was introduced in version 0.11 and works like this:
$ pijul pull --path <patch-hash> <remote>
Of course it will bring the patch and all its dependencies.
As we have seen we neither need branches, cherry-picking nor rebasing because of the patch theory behind Pijul.
With Pijul we don't need forking either. The steps to contribute to a repo are:
pijul clone <repo-url>
pijul push <our-user-name>@nest.pijul.com:<repo-owner-user-name>/<repo-name> --to-branch :<discussion-number>
Then the repo owner could apply our patches to the master branch. You can also attach patches from your repos to a discussion when you create or participate in one.
A tag in Pijul is a patch that specifies that all the previous patches depend on each other to recreate the current state of the repo.
To create a tag we have the
pijul tag command which will ask for
a tag name.
After new patches are added to the repo we can recreate the state of any tag by creating a new branch:
pijul fork --patch <hash-of-the-tag> <name-of-the-new-branch>
Because tags are just patches we can look for their hashes with
Forget about bad merges, feature branches, rebasing and conflicts produced by merges after cherry-picking.
Pijul has an on-line manual but currently it is a
little bit outdated. The best way to learn more is by
pijul help. This will list all the subcommands and we
can read more about any of them by running
pijul help <subcommand>.
The subcommands are interactive by default but we can pass data to them directly from the command line to avoid being asked questions. All these options are explained in each subcommand's help.
As we said Pijul is an in-development tool: the UI could
change in the future and there are some missing
features. (Something like
bisect would be super
helpful.) But that's also an opportunity: the developers seem
quite open to receive feedback.