A composable pijul user interface? (brainstorm)

See the discussion here: https://nest.pijul.com/pijul_org/pijul/discussions/60

For the record, I will now work on pijul command line interface, with the main objective to make it easier to use and more consistent. I have started a repository on the nest to build an empty pijul that would only parse the command line arguments. This can be seen as an attempt to specify the cli before actually implementing it in the upstream.

You are welcome to help in any way. I just read your brainstorm and I think there are some really good idea in it.

I think we should try to specify completely the cli before going any further.

3 Likes

A few things I’ve noted.

Add is obsolete

Needing to pijul add new files is error prone (I for one tend to forget it easily), and not needed often if a good ignore file is in place. record (or whatever its equivalent ends up being named) should offer by default to add any new non-ignored files. To make this acceptable, the dialog when recording the addition of a file needs to offer the following:

  • Add the file
  • Delete the file
  • Add the file to a local ignore list
  • Add the file to the shared ignore list.

With the last two options, the user should be able to edit the ignore pattern before validating.

Push / pull distinction is less important than sync / merge

The distinction between push and pull is not a conceptual distinction, just a change in direction. On the other hand, the distinction between merging two branches (or rather two patchset), and transferring patches between branches needs to be made more overt than it is already. I think the two commands should be:

  • pijul merge [branch] <patchset> offers patches from <patchset> to add to <branch> (presumably defaulting to the current branch)
  • pijul sync <from-repostate> <to-branch> makes the content of <to-branch> (which may be remote) be equal to that of <from-repostate>, presumably requiring an option like --force in case where patches are present in <to-branch> but not in <from-repostate>.

Vocabulary is needed

Pijul needs a good vocabulary to refer to its concepts, a naming scheme and operations on them. Native english-speakers are probably needed here to make the right choices.

  • a patch is […]
  • a patchset is a set of patches. They can be unionned, intersected, filtered by name / tags / author / … and queried for dependencies.
  • a repostate is a set of patches, closed under dependencies. . Their contents can be queried for files: <repostate>/dir/file is the state of that file when the patches in repostate are applied. The difference between two repostates is a patchset. A patchset can be applied to a repostate if all of its dependencies are present there.
  • branches are local labels for repostates; they can be updated and dereferenced. They are referred to by an URI of the form proto://Repo-URL#branch
  • repositories contain several branches. They have an URI.

We may or may not want the notion of remote from git; likewise, we may or may not want a set of draft patches, corresponding to the index in git (i.e., they are given special identifiers preventing them from being referred to in patchsets).

5 Likes

Speaking of push/pull, I’d like to introduce sync/merge/pull/push/clone from/to subfolders of repositories in Pijul 0.9. In order to be efficient, this will require a change in the patch format (which is needed for other reasons, as noted by @lthms when he tried to fix tags), and the promise that we will never want to move stuff between files (conflicts between two moves would not be computationally easy to detect, and would probably be extremely confusing to users).

@flobec, speaking of patchsets and repostates, I think we only want sets of patches closed under dependencies. Your proposed notion of patchsets is simple and minimal, and is the object we transfer over the network, but is likely to be confusing to new users, as they’re not closed under dependencies. This means that a new user would have to understand what a dependency is before understanding that concept, and we might want to avoid that requirement (do we?).

2 Likes

I mostly agree that patchset will not appear too often. They may be
needed if we implement an equivalent of darcs send. They can probably
be given a second-class name like partial repostate to reflect their
second-class citizenship.

0.9 is alrdeay big enough, no? I’d rather test a lot your waited patches and wait for 0.10 to add this feature. What do you think?

I’ve just started to tinker with pijul. Tried it out (but not yet with a real project), read the documentation and skimmed over the paper (my knowledge of category theory is limited, but it’s enough to get the gist I think).

Before I’m to familiar, I’ll try to write down my impressions about the vocabulary and how it matches my mental model.

My mental model is: I can make changes (patches) to a project (repository). A repostate is just starting with nothing and applying a bunch of changes. It’s intuitively clear that changes can or cannot be independent of other changes. Also, the difference between states is also just a set of changes.

I think the biggest stumbling block is to understand what a patch is. I’m not a native speaker, but the metaphor is unclear to me. Imo it makes sense to “patch software”: There is a hole (security vulnerability) and we patch it up (issuing an update that fixes that). But I’m not patching up a project until it’s complete and patches do not really depend on each other. And what is “recording a patch” supposed to mean?
I know that the documentation explains patches as changes on several occasions. But that alone could be a sign that it could be a good idea to call them “changes”, “edits” or something in that direction.

Then, repostate. I find it a little bit clunky, but it’s fairly descriptive. What I don’t understand are branches in that context. In my mental model, a branch is just a point in the development of the project, the application of all changes that lead to that point. So people can branch of from another path of development etc. So, the branchness of a branch is a structural property.
But the defining property of a branch seems to be that is labeled. For me, when a pijul branch is a branch, then repostate must be a branch too.

Little nitpick: I associate the command “record” with “start a recording” while pijul is always recording and the command is ending it. Maybe “report” would be clearer? Idk.

Another nitpick: “blame”. It’s more of a personal thing but for me the snark of the git blame command symbolises everything that is wrong with git. From the hostile community that it came from to the hostile user interface it has.

Just to be clear, my understanding of pijul could be wrong and this is just my impression. Also, naming things is hard :smiley:

2 Likes

You’re absolutely right about patch being standard but misleading terminology. Contribution is a better word to carry the meaning of “unit of work to be shared”. What do others think; should we switch?

For branches versus repostates, it’s a variable versus value thing: at any point in time, the value of a branch is a repostate. If I do, say, a pull operation, then the value of this branch is going to be another repostate. I can’t think of a better word to carry that idea than branch right now, and the word is quite standard, so I think we’re better off keeping even though, as you said, branching is not fundamental to how they’re used. The alternative would be to call them “feeds” or “channels” to carry the idea that you want to watch their contents in order to follow the development of the project.

1 Like

I like contribution very much. It’s more meaningful and has quite a positive connotation.

Maybe just “tag” or “marker”? But of course branch is standard lingo. I just have the feeling that the name “branch” conveys that it’s more than it’s actually is.

pijul contribute would be kind of long to type. Aside from that, in my post here https://nest.pijul.com/pijul_org/pijul/discussions/60 I was discussing factoring away false semantic distinctions to be made in the UI and using very literal, boring, and straightforward naming. This improves composability, for one.

For recording changes to the repo I might do:

pijul add changes

That means we can do:

pijul remove changes

To unrecord changes.

pijul add files

To add files to repo.

pijul list files

To list files in repo.

pijul list changes

List recorded changes in the repo (aka pijul log)

…and so on.

2 Likes

Hi guys!

Just like @madmalik I’m new to Pijul but I already love it. Reading the docs I thought that some command names are inconsistent with the docs itself though so I’m happy to find this thread.

For example pijul status says:

use “pijul add …” to track them

Why not call the command track then? If the idea is to remove confusing metaphors and being clear with the command function then I think commands name should look just as they are described in the docs. Also if it is called track then the inverse operation would be called untrack instead of the confusing remove.

Another example: pijul revert is described in the docs as “Reset the working directory to the state of the pristine”. Why not call it reset then?

That lets revert free to being used to “revert a patch” and maybe pijul patch should be “the only command for creating patches in Pijul” which currently is record. (The current behavior of pijul patch could be pijul dump :thinking:)

That being said I really like @pointfree’s idea of a compassable TUI. He gave more details in the linked issue and I just wanted to point out that in the case of pijul branch [new|delete|switch|list] I would replace new with create. A little bit longer, but it is imperative, right?

About @flobec idea on add being obsolete I have my doubts. I use a lot git clean -df here, but maybe it is a necessary command because Git itself is messy.

1 Like

I agree with you @tae, the distinction between declarative and imperative commands should be clear. Since I’ve had some time and distance from this it’s become clearer to me what’s actually needed in the ui.

The row is for source. The column is for destination. Source and destination can be: label, remote, file, or directory.

labels of hunks & filepaths (patches)

Some of these seem unnecessary but are actually useful when combined with branches, etc.

track untrack tracked untracked
track record adds revert removes apply patch to cwd clean changes not in patch
untrack record removes revert & unrecord, ignore unrecord clean changes not in patch/cwd
tracked record adds revert adds (clean) blame, log, ls diff/status: adds
untracked record removes revert files missing in patch diff/status: removes show nothing

labels of patches (branches)

track untrack tracked untracked
track sync (re)name to pull, merge put
untrack (re)name from delete-branch unpull unpull --not-in-remote
tracked push, fork unpush checkout push (dryrun)
untracked get unpush --not-in-local pull (dryrun) in-repo?

So we have hunks, (file)paths, and labels (branches). Because there are only the commands “track”, “untrack”, “tracked”, and “untracked” we could probably just make it positional and not need to type out the words “track” “untrack”. The commands “tracked” and “untracked” could be default (for read-only querying) while “track” and “untrack” arguments would be prefixed with a flag “-w”

Let’s see how this looks:

Keep in mind the arguments are arbitrary so these examples are highly redundant. -w writes something. No -w means read, query, or show something.

sync:

synchronize branch1 and branch2 so they have the same patches.

pijul -w <branch1> -w <branch2>

pull:

pull patches from remote branch to local branch.

pijul -w <branch_local> <branch_remote/other>
pijul -w <branch_remote/other> # if current branch is the destination

push:

push patches from local branch to remote branch.

pijul <branch_local> -w <branch_remote/other>
pijul -w <branch_remote/other> # if current branch is the source

unpull:

Rollback patches in <branch_local> that are not in <branch_remote> but are in <branch_local>.

pijul -w <branch_local> <branch_remote/other>

unpush:

Rollback patches in <branch_remote> that are in <branch_remote> but not in <branch_local>.

pijul <branch_local> -w <branch_remote/other>
pijul -w <branch_remote/other> # if current branch is src

rename-branch:

pijul -w <existing_branch_name> -w <non_existent_branch_name>

delete-branch:

pijul -w -w <branch_to_delete> # empty branch name implies the master, previous, or parent branch

fork:

pijul <existing_branch_name> -w <non_existent_branch_name>
pijul -w <non_existent_branch_name> # if forking from current branch

checkout:

pijul <existing_branch_name>

put:

Here we use a filepath argument for the current directory, “.”

pijul . -w <branch_remote>

get:

pijul -w . <branch_remote>

init (if not already inside a repo):

pijul -w .

add current directory to current branch of current repo:

pijul -w .

…mix and match filepath, patch hash, branch label, and hunk arguments. I got bored writing examples. Maybe I’ll do more later. Let me know if I’m being overly verbose.

I don’t think it will be too difficult to map this to the commands in the cells of the above tables with clap. We just need a prefix or something to identify arguments as label, local filepath, patch hash, url, etc.

As an aside, with this UI it could be rather straightforward to do a pijul-backed FUSE or 9P filesystem, but I haven’t thought much about that yet.

EDIT: tldr from irc:

3:55:47 PM <pointfree> The idea is to use pijul like the unix cp command but with awareness of whether the source and destination are hunks, filepaths, patches, or branches or some combination of those.
3:58:09 PM <pointfree> The arguments by default work like a dry run (they are queries). Prefixing an argument with -w will make it writeback.
4:00:53 PM <pointfree> So pijul branch1 branch2 shows a diff between branch 1 and 2. pijul -w branch1 -w branch2 will synchronize the two branches (apply the diff to branch 1 and 2).
4:02:42 PM <pointfree> I mapped this all to existing pijul commands in tables so it shouldn’t require any backend rework to implement.

EDIT2: I noticed all of these commands are effectively symmetrical. Maybe we could get by with just a single -w flag?

My fear is, this cli for pijul, however clever, will not be very intuitive for new comers. It hides all the important verbs, so wouldn’t it be harder for someone new to understand the tool and the example he or she might read?

“Everything is very similar, that’s confusing” is the first feedback I expect.

What do you think?

@lthms That’s exactly what I thought

My perspective is that having carefully picked verbs that are clear in their meaning, and different from each other, will help the command line interface be intuitive and easy to use. I would like to be able to type in a command, and if I type the command incorrectly, have a helpful error message based on what I was perhaps trying to do, and also have a command that I can type into a search engine and get meaningful help.

I believe this is something that darcs worked on, and it is one of the things that attracted me to that project. I do not believe that the same verbs must be used as darcs uses, nor the same as git, nor any other tool. If different verbs are chosen, my preference would be for them to be more clear in their meaning than the original, if possible. Having the verbs used by pijul be different from each other (different concepts or meanings), I think would also clarify what each is used for, so there would be less times that a command was attempted to be used for something it does not do.

1 Like

It’s most natural for nouns to be explicit because they are things, and it is most natural for verbs to implied by what is done with those things: moving them around. The post was just a vcs rosetta stone not anything that would need to be memorized because copying (synchronizing) something to something else is rather intuitive.

pijul [-w] [branch | filepath] [branch | filepath]

This ui amounts to using pijul like the unix cp command for “files” and “directories”. What it adds over cp is “branches” as a new kind of noun that can capture changes from nouns, namely file(s)/directorie(s)/branch(es) and -w for write vs query (dryrun).

The vocabulary necessary for using dvcs repos should be much less than it is. Without a lot of familiar false dichotomies I’d expect new users from other dvcs to be at least briefly surprised at how much they don’t need for doing all the same things in a declarative ui. I wouldn’t expect that response from total newcomers to dvcs.

Maybe I’ll implement this as an alternate or experimental ui that others and myself can test drive.

That would be quite neat, personally I’m having trouble understanding your proposal in practical terms, so it would definitely help. I think it will be very quick to implement: I suggest you simply write a python wrapper to the pijul cli using subprocess (or better, delegator by kenneth reitz, which has a nicer api).

2 Likes

Implementing alternative clients for pijul would definitely be a good thing.

One thing to do on pijul side to help such an attempt is to improve the dichotomy between libpijul and pijul crates. Right now, I sometimes feel it is a bit arbitrary and we could probably improve that a bit.

1 Like

Good idea @yory. I wrote a quick python wrapper: https://nest.pijul.com/pointfree/pj

I would appreciate anyone taking it for a spin.

I should add support for defaulting to the current branch, directory, and repo when the src argument is omitted. Also, support for multiple src arguments (i.e. multiple files, directories, branches Spontaneous (non-)branching vs. tree-style branches ) would be great but we’re not there yet.

I mentioned this wrapper on irc a while ago but I’m posting it here now that with 0.10.1 everything seems to be working very smoothly without any of the conflicts I had during 0.9. Yay!

4 Likes