I will try to explain, step by step, how the key component inside the plastic server works, because almost all the other concepts in plastic have something to do with it.
First things first: what's a selector? A selector is just a piece of text, associated to a workspace, whose only mission is to tell the server what has to be downloaded into a workspace and how checkouts have to be performed. It actually "selects" (that's why it is called a selector) a set of revisions from the repository to be downloaded into the workspace.
Maybe you've been using plastic for months and you've never heard about selectors... if so, how is this possible? Well, the GUI folks at codice did a pretty good job trying to make them as transparent as possible. So in fact, even if you're using the standard version, each time you perform a "switch to branch" or "swith to label" operation, you're setting a selector.
If you right click the name of your workspace, a pop up menu like the one in the following figure will show up, and then if you click on "set selector" you'll see your current selector.
A typical selector (the default one when you create your first plastic workspace) looks like:
repository "default"
path "/"
branch "/main"
checkout "/main"
Which means, rule by rule:
Does it sound confusing? Ok, let's try to check how it works step by step. But first let's think a little bit about two concepts I've just introduced: what's an item? and... what's a revision?

An item represents a file or a directory, but it is very important to note that it doesn't actually resemble a real element on a file system. It is a bit difficult to understand at the beginning. An item tells plastic "something" exists, but it doesn't tell plastic any other information.
The real information associated to an item goes inside the revisions. Each item has one or more revisions, and revisions, as you know, are stored inside branches.
As programmers we can think of items as "classes" and "revisions" as their instances. Is it clearer now?
And now you might think: ok, and items store the names of the files and directories... wrong! It is a bit more complicated than that, and it gives a lot of flexibility.
Names of files and directories are stored as data of directory revisions. I mean, what's the data of a given file? Ok, this one is clear, the file's content, right? Then, what's the data of a given directory? The files and directories it contains? So whenever you create an item, its name isn't associated to the item itself, but introduced as data inside the directory containing the file or directory. That's exactly why you've to check out the parent directory to add a file or a directory, as you've probably realized while you were using plastic.
There's something very important here, which introduces an important indirection and is critical to introduce selectors: directory's data contain the names of the items it contains, not the names of the revisions. So, a directory knows which items it contains, not the exact revisions, which will be decided at "runtime" by the specific selector rules.
Let's explore a simple sample now. Whenever you create a new plastic repository, the following objects will be created by default: a root item (an special item which is the root directory of your new repository, so you can actually add contents inside it), a main branch (the branch "/main", which you're probably familiar with) and a revision of the root item inside the main branch. This revision is the one which let's you start working with your newly created repository.
If you set a selector like the one described above, and you update it, the content of the new repository will be downloaded.
On selector resolution, plastic will always try to find the root item for each specified repository. All repositories have a root item, if not, something very wrong happened to the repository (it's broken!) and the system will abort the operation. In the previous selector, the repository is "default".
Once the root item is located it will try to locate a rule that matches, by path, with the current path. The current path is the root item, "/" so the first rule matches.
Try to set the following selector to work with your newly created repository and check what happens:
repository "default"
path "/dumb"
branch "/main"
checkout "/main"
Then plastic will complain with the following message:
Can't load the root item. Probably, the workspace selector contains errors
Why? Well, it tries to load a revision for the root item, then it finds none of the rules match the "/" path because they need the path to be, at least "/dumb", and then the root directory can't be loaded.
If we use the right selector (with a rule path "/") plastic will be able to locate a rule, which is telling: "load the last revision at branch main". There's only one revision at this moment at branch main, which is the root revision (the first, the last, and the only one existing for the item right now), so it will be loaded.
Then plastic will get the data of the root revision. It is empty, so the selector is solved.
What if we now add a new file inside the workspace?. To do so (the GUI handles it automatically, but from the command line you've to manually check out the root dir) the root directory will be checked out, a new revision will be created in check out status, and then a new item for the new file (let's say file01.txt) will be created. A entry for file01.txt will be created on the checked out revision of the root directory, pointing to the newly created item. Then a revision (in check out status) will be created for file01.txt. If you now check in both the directory and the file, you'll get something like the following figure.

There are two interesting conclusions you must understand after reaching this point: first, how is the information stored in directory's revisions, and second: how a directory points to its content by item, and not by revision.
Let's play a little bit with plastic, and create a couple of new revisions for file01.txt. We do so by checking out the file, adding more content, check in it, check out again, more content and check it back in.
We get something like the following figure.

Let's now play a little bit with the selector.
Set the following selector and check what happens:
repository "default"
path "/"
branch "/main" revno "0"
checkout "/main"
Once your workspace is updated you'll check that... it's empty!!
Where file01.txt has gone? Look carefully at your selector again. You're telling it: get the revision "0" at branch main for all the items which path matches the "/". Then you could think, "ok, but then I need the first revision of file01.txt!!". And this isn't true, because the first thing the selector does is solve the root item, once it is located, it will load its revision 0, and if you look at the figure carefully, you'll notice the first revision of the root item on main is... empty! There's no content to be downloaded, and that's why your workspace is empty now.
Then, how can you load the revision 0 of file01.txt. There are several options, let's check the first one.
repository "default"
path "/file01.txt"
br "/main" revno "0"
path "/"
branch "/main"
You're actually specifying how to load file01.txt.
Plastic evaluates the rules from top to bottom, it will first try to use the first rule, if it finds a match, then it won't go for the second for this item. So, how does it work with this example?
Let's explore a second (and a bit more complex) possibility:
repository "default"
path "/?"
br "/main" revno "0"
path "/" norecursive
branch "/main"
Study the previous selector in detail.
Path rules understand two different wildcards right now: * and ?. * stands for 0 or more and ? for 1 or more. So, we're telling plastic, for each path starting with / and containing at least one more character, go to the first rule, otherwise go to the second.
The second rule, which is the one loading the root item now, uses a new keyword: norecursive which means: use this rule only for paths exactly matching the rule, not files or directories "inside" the path in the rule.
Are you familiar with changesets? A changeset resembles and atomic commit and is created each time you check in one or several files or directories together. A unique number is generated and assigned to all the revisions checked in together. This number is the changeset.
Selector rules can be also used to work with changesets. Suppose you want to load the revisions at changeset "0" which is the one created by default with the initial root directory revision, you'd set a selector like:
repository "default"
path "/"
branch "/main" changeset "0"
Specifying different numbers you'll be able to jump to different changesets.
Well, that's enough for today, in the next chapter I'll be talking about labels, and how they work with selectors. If you have any questions, feel free to ask...
2 comments:
Pablo,
Great post. I've been using Plastic for a while now, but always use the default selectors created by the GUI. If I understand it right, you can also do a selector that reads for multiple repositories. Is that correct? If so, can you show some more examples and best practices of that? Also, you talked in a previous post about the smartbranch rule. I'd be interested in more information about that. Thanks.
-Keith
Hi Keith,
Yes, you can create selectors with rules to read more than one repository. If you take a look at the post about the distributed system you'll see I'm currently mounting up to 6 repos right now.
You can find more information about it at our whitepaper on Component Oriented development, check the support resources. I'll be discussing it in more detail in the coming days.
About smart branches... :-D Yes, this is a major step ahead that is already implemented in plastic 2.0, but not yet unveiled because of the lack of GUI support (still). I'll be posting about it in the coming days too...
Post a Comment