Record text for Pijul 0.12 as starting point

tae
Mar 9, 2022, 8:48 PM
2FH6C6NN467AAQFV3TLOH6LLKW2XS7BBTMJNKQDSOLWV3KS5UYJQC

Dependencies

Change contents

  • file addition: README.md (----------)
    [2.1]
    # Pijul for Git users
    ## Introduction
    Pijul is an in-development distributed version control system that
    implements repositories using a different [model][model] than
    Git's. This model enables [cool features][why-pijul] and avoids
    [common problems][bad-merge] 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.
    ## Creating a repo
    ```
    $ mkdir pijul-tutorial
    $ cd pijul-tutorial
    $ pijul init
    ```
    Nothing new here.
    ## Adding files to the repository
    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 `.keep`
    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 `--recursive` flag.
    ## Signing keys
    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 `~/.local/share/pijul/config`. At
    the moment the private key is created without a password so treat
    it with care.
    ## Recording patches
    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
    ```
    Here `y` means yes, `n` means no, `k` means undo and remake last
    decision, `a` means include this and all remaining patches, `d`
    means include neither this patch nor the remaining patches and `i`
    means ignore this file locally (i.e. it is added to
    `.pijul/local/ignore`).
    Let's change `a-file`:
    ```
    $ 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
    ```
    ## Ignoring files
    We saw that when recording a patch we can chose to locally ignore
    a file, but we can also create a `.pijulignore` or `.ignore` file
    in the root of our repository and record it. All those files
    accept the same patterns as a `.gitignore` file.
    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.
    ## Removing files 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.
    ## Removing a patch
    ```
    $ pijul unrecord
    ```
    This command is interactive. Alternatively, one can use `pijul
    unrecord <patch>` to remove one or more patches, knowing their hash.
    Patch hashes can be obtained with `pijul log`.
    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
    dependencies with `pijul dependencies` and they are managed
    automatically. This is why `pijul unrecord <patch>` might
    sometimes refuse to work.
    ## Discarding changes
    ```
    $ pijul revert
    ```
    This is like `git checkout` applied to files (instead of
    branches).
    ## Branches
    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 `delete-branch` subcommand,
    but:
    ## Maybe we don't need branches
    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:
    ## Remotes
    At the moment, pushing works over SSH, the server only needs to have
    Pijul installed. [The Nest][nest] is a free service that hosts public
    repositories. 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][nest] like we do
    with SSH keys in GitHub.
    Now that we have an account on [The Nest][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 `--set-default` flag.
    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][partial-clone] 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.
    ## Contributing with a remote
    With Pijul we don't need forking either. The steps to contribute
    to a repo are:
    1. Clone it with `pijul clone <repo-url>`
    2. Make some patches!
    3. Go to the page of the repo in [The Nest][nest] and open a
    new discussion
    4. [The Nest][nest] will create a branch with the number of the
    discussion as a name
    5. Push the patches with `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.
    ## Tags
    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
    `pijul log`.
    ## In summary
    Forget about bad merges, feature branches, rebasing and conflicts
    produced by merges after cherry-picking.
    ## Learning more
    [Pijul has an on-line manual][manual] but currently it is a
    little bit outdated. The best way to learn more is by
    executing `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.
    For more information on the theory behind Pijul refer to Joe
    Neeman's [blog post on the matter][theory]. He also wrote a post
    [that explains how Pijul implements it][implementation].
    ## A work in progress
    As we said Pijul is an in-development tool: [the UI could
    change in the future][ui-changes] and there are some missing
    features. ([Something][bisect] like `bisect` would be super
    helpful.) But that's also an opportunity: the developers seem
    quite open to [receive feedback][discourse].
    [bad-merge]: https://tahoe-lafs.org/~zooko/badmerge/simple.html
    [bisect]: https://discourse.pijul.org/t/equivalent-of-checking-out-an-old-commit/176
    [discourse]: https://discourse.pijul.org
    [implementation]: https://jneem.github.io/pijul/
    [nest]: https://nest.pijul.com
    [manual]: https://pijul.org/manual/
    [model]: https://pijul.org/model/
    [partial-clone]: https://pijul.org/posts/2018-11-20-pijul-0.11/
    [theory]: https://jneem.github.io/merging/
    [ui-changes]: https://discourse.pijul.org/t/equivalent-of-checking-out-an-old-commit/176
    [why-pijul]: https://pijul.org/manual/why_pijul.html