A Django site.
July 26, 2008
» Advanced selectors, part III

After the previous posts describing selectors in detail, it's now time to enter the last selector frontier: multi repository selectors.

As you know plastic can manage multiple repositories. You can map each one of your projects inside a plastic repository, or go for more advanced practices like component oriented development.

Repositories can be totally independent from each other, but there're also situations in which they can be tightly related. For instance you can have shared libraries you’ve developed and you reuse between different projects. If this is the case, it can be useful to have one repository for each project, and one repository for the libraries.

But then, how can developers use the code from the libraries and the project at the same time?

Let's take a look at a very simple repository like the one on the figure. It has just a couple of files and an empty directory. Probably none of your projects looks so simple. Suppose this is the repository named “proj00”.



Then you have another repository containing your library code. It looks like the one at the following figure. This repository is named “lib_repos”.



Now we need to make the lib_repos repository available to the “proj00” developers.
Please note we’ve created an empty “lib” directory which will be used as mount point in “proj00” to plug “lib_repos”.

Take a look at the following selector:


repository "lib_repos" mount "/lib"
path "/"
branch "/main"
checkout "/main"
repository "proj00"
path "/"
branch "/main"
checkout "/main"

Remember how selector rules work:
from top to bottom, and you’ll see how we’re telling plastic: take everything from lib_repos at the main branch, but mount it at /lib. Later on it will need to resolve the /lib path, and it will be solved using the next repository rule (proj00).

If you run the following ls (with a format modifier to show the repository information) you’ll see the following.

>cm ls
br:/main#1@rep:proj00@local:8084 .
br:/main#0@rep:proj00@local:8084 file00.txt
br:/main#0@rep:proj00@local:8084 file01.txt
br:/main#1@rep:lib_repos@local:8084 lib


Note:
We used the following PLASTIC_LS_FORMAT environment variable:
LS_FORMAT="{3}@{8,-26} {4,-5} {5}"

Right, the lib directory is being loaded from the lib_repos repository.
Of course if you move inside the directory you’ll check that everything inside is from the same repos. Look at the following plastic screenshot showing the repository details of the files and directories.

Implementing a real working environment
The selector above showed how to go for main branch development on a multi-repository scenario. But you’ll probably need to implement a whole branching strategy. If so, then consider the following selector:

repository "lib_repos" mount "/lib"
path "/"
label “lib_00”
repository "proj00"
path "/"
branch "/main/task001" label “BL010”
checkout "/main/task001"


This resembles a branch per task pattern on a multi-rep scenario.

Please note we’re now mounting “lib_repos” as read-only because we’re just specifying a label and not a check out rule.

The way to use the mounted repository will vary depending on your project’s needs. It can happen than a different dev group completely manages “lib_repos”, then is ok to mount it read-only because developers at “proj00” will only use it as a “library”. Lib_repos will go through its own release cycle and the team at proj00 will only have to take care of changing the label of the mounted repository when a new release is available and approved for their project.

It can also happen that your team is responsible of both repositories. You’ve decided to split them because they’re clearly different components but there is only one release cycle for them.

Then it makes sense to follow a combined “branch and merge cycle” for the two repositories. If you’re working on task001 then it can happen you need to change code at both lib_repos and proj00. You’ll be probably using a selector like:


repository "lib_repos" mount "/lib"
path "/"
branch "/main/task001" label “BL010”
checkout "/main/task001"
repository "proj00"
path "/"
branch "/main/task001" label “BL010”
checkout "/main/task001"


Please note that:
There’re two branches /main/task001, one at each repos, and the same for labels, but you can use a naming convention (using the same name as I’m doing here) to enforce their relationship.

Going really advanced, configuring what you mount
So far we’ve seen typical mount scenarios. But, what if you need to mount inside the “/lib” directory at “proj00” something which is not at the root of “lib_repos”?

Then we’ll use the power of plastic branch inheritance to actually get the desired result.

Suppose we want to mount inside “lib” the content of the “/bin” directory. So, we want to use “/bin” as the root of the lib_repos repository. To make things easier we’ll assume there’s a “release” subdirectory inside “bin”. Check the following figure.



Let’s go to the lib_repos repository and create a branch named “/main/mount-point”.
Then let’s use regular rm and mv commands to correctly configure the “mount-point” branch as we need.


>cm co .
Checking out . ... Done

>cm rm doc src
Item doc has been removed.
Item src has been removed.

>cm co bin
Checking out bin ... Done

>cm mv bin\release .
bin\release has been moved to .

>cm ls
0 07/04/08 dir br:/main/mount-point#CO CO .
0 07/04/08 dir br:/main/mount-point#CO CO bin
0 07/04/08 dir br:/main#0 release

>cm ci bin
Checking in bin ... Done
Created changeset
cs:3@rep:lib_repos@repserver:CONRAD:8084

>cm rm bin
Item bin has been removed.

>cm ls
0 07/04/08 dir br:/main/mount-point#CO CO .
0 07/04/08 dir br:/main#0 release

>cm ci .
Checking in . ... Done
Created changeset
cs:4@rep:lib_repos@repserver:CONRAD:8084


Then we can set the following selector:


repository "lib_repos" mount "/lib"
path "/?"
branch "/main"
checkout "/main"
path "/" norecursive
branch "/main/mount-point"
repository "proj00"
path "/"
branch "/main"
checkout "/main"


Please note the following:

We’re using the /main/mount-point branch just as a way to “refactor” the directory structure, but all the contents will be loaded from the main branch of the “lib_repos” repository. Of course instead of the main branch we could be using a different one.

The purpose of the /main/mount-point branch is not being merged back into “/main”
but just hold a project reorganization. In fact we can even prevent it to be merged denying the merge permission.



Let’s set the following selector:


repository "lib_repos" mount "/lib"
path "/?"
branch "/main/task001" label “BL010”
checkout "/main/task001"
path "/" norecursive
branch "/main/mount-point"
repository "proj00"
path "/"
branch "/main/task001" label “BL010”
checkout "/main/task001"


And a little explanation:

Whenever you make changes to your code inside the “lib” directory, you’ll be placing the changes directly inside “/main/task001”, but using the directory reorganization from “/main/mount-point”.

When you merge back /main/task001 in lib_repos, you’ll be only getting the changes made on the task, and not the entire reorganization made inside “mount-point”.
That’s why plastic branch inheritance is so powerful and allows many different scenarios to be implemented.

Future work
We’re currently working on the design of new selector rules to allow different repositories to be mounted on different locations directly. Some people will find the “mount-point” branch solution helpful, but other will prefer to be able to do something equivalent just using selector rules.

We’re introducing new selector rules to be able to specify which one is the root item to be used in a mount point, for instance. This way you could specify that /bin is now the root at lib_repos.

Also we’re working on creating “workspace selector directories”, which are local directories not under source control but managed by the tool (created by the update process) and able to hold controlled code…

So, we’re open to suggestions… feel free to contact me by email if you have ideas about possible selector evolution.

» Selectors: welcome to the dark side, part II

Welcome back to the plastic dark side! Today we'll be moving deeper in branching concepts, and trying to explain how labels and branches work.

In the previous post I've introduced several important concepts like how selector rules are evaluated by plastic one by one, from top to bottom, and how the path specifier works.

Let's now go to the "label" selector rule modifier. But first, what's a label?

A label is simply an object inside a plastic repository. Just that. When you create a label from the command line or from the GUI, you're just creating a new object inside the repository.

Labels are useful once they're applied to revisions as you can see in the following version tree. In the tree you see that revision 20 has only one label (BL051) but revision 19 has a lot of them (which in this sample means it belongs to a number of baselines... it wasn't changed for a long time).



So to label a revision in plastic you first need to have a label, then apply the label to a revision. From the GUI the only option available is to label all the entire workspace content, which means apply the label to the revisions you have currently loaded. Why? Because users normally use labels to group together a set of revisions at specific moments like releasing a new version. And more often than not is the whole workspace what they want to label.

Let's take a look at the sample in the following figure.



You see we've a directory with a couple of entries, one of them labelled with label "BL001". So, what would be downloaded to our workspace if we set the following selector?


rep "default"
path "/"
label "BL001"



We're telling plastic:
download everything marked with BL001. So, what do you expect to be downloaded?

Nothing!

You'll get an error saying you can't load the root item.

Why? Well, remember how plastic resolves selectors: first it gets the root item and tries to find a revision for it using the selector rules. So it will try to find a revision for the root item labelled with BL001 and... yes, it can't... end of the story.

So, whether you label the root item too, or you provide a rule to load the root.


rep "default"
path "/"
label "BL001"
path "/"
branch "/main"



The previous selector solves the problem.
It will go down to the second rule to find a revision for the root item, so problem solved.

Now you understand why it is so dangerous to label separate revisions instead of the whole workspace tree, unless you know what you're doing.

Labels are very useful because they also introduce a lot of flexibility. For instance, in the following example, if you specify a selector like the previous one, you'll download the workspace a tree like the one at the bottom of the figure.



Enter the world of branches
So far we've played with revisions, changesets and labels. Let's start talking about branches.

In plastic a branch is just a revision container. When you create a new branch you're just creating a new empty object in a repository. You'll have to work with it to really put some content on it. Note this behaviour differs from systems like Subversion, CVS, SourceSafe, TeamSystem or Perforce, where branch creation actually means copying revisions to the new branch.

In plastic, you create a new branch, and it is totally empty.

How can we use it? Suppose we've just created a main branch /task001. Let's do it from the command line:


$ cm mkbr br:/task001


Note that from the GUI you normally create only child branches.

I'll discuss child branches in detail later on.

Ok, now you have task001, but, what can you do with it?

You're almost an expert in selectors, do you have any idea?

What if we set a selector like the following:


rep "default"
path "/"
branch "/task001"
co "/task001"


Yes, you're right...

The server will complain telling it can't load the root item.

So we've to find out a way to make it reach the task001 branch...

What about the following?


rep "default"
path "/"
branch "/task001"
path "/"
branch "/main"



Ok, it will work.

But we're loading the revisions from main... How can I actually create a revision on the new branch?


rep "default"
path "/"
branch "/task001"
co "/task001"
path "/"
branch "/main"
co "/task001"


Watch the selector above. Specially the second rule.

It is telling plastic: get the revisions from main, but if you've to checkout something... place the checkout at "/task001". So, as soon as you checkout a file or directory... it will go to task001, and you'll start using your newly created branch!

Please note if your second rule were something like path "/" branch "/main" co "/main" the revision wouldn't ever go to the task001... so the first rule would be useless.

So, now you've your first checkout on a separate branch... If you run a ls from the command line, or if you check the item's view on the GUI, you'll see the revision is at a separate branch.

Two important things here:
  • now you have a better understanding of selectors
  • now you understand how branching works in plastic: that's exactly the reason why only changed revisions go to branches while the rest is kept in the parent branch...

    Branches and labels combined
    With what we've learned so far... how would you implement a branch per task pattern? Easy?

    Yes, just create a new branch for each new task, and then set up a selector like the one above. And you're done.

    "Ok" - you may ask - "but what if I need to start working from an specific baseline?".

    Let's go.

    Suppose you want to work on task001, but your starting point must be a well-known stable baseline labelled BL059.

    How would you set up your selector?

    Remember: you need to take everything from BL059, unless you've something on your task branch, which will be retrieved first...

    Take a look at the following selector.


    rep "default"
    path "/"
    branch "/task001"
    co "/task001"
    path "/"
    label "BL059"
    co "/task001"


    Done, right?

    Then, what are child branches for??

    If you think they aren't needed... you've become a real selector hacker!! Congratulations! :-)

    Let's go again to the selector dealing with branches /main and /task001


    rep "default"
    path "/"
    branch "/task001"
    co "/task001"
    path "/"
    branch "/main"
    co "/task001"


    Here you're!

    After some weeks using them for branch per task at the beginning of the project (long, long ago), we came up with the following:

    what if we make /task001 inherit from /main somehow? It is what we're trying to do with the selector anyway...

    And then child branches were born!

    If you create task001 as a child of main you're saying if task001 has content, take the revisions from it, otherwise take them from main which is exactly the same as the previous selector.

    But child branches make your live easier. To create a child branch you can go to the GUI or type


    $ cm mkbr br:/main/task001


    Then the selector to use it would be:


    rep "default"
    path "/"
    br "/main/task001"
    co "/main/task001"


    Which actually saves you two lines!.

    And that's basically the reason why child branches were born: to save some lines writing selectors!! (Ok, please note that by the time when child branches were introduced, selectors were still written in XML format, this happened in the plastic prehistory, before any customer have even heard about plastic... and saving some lines was very important to the lazy developers!!).

    Child branches and labels

    "Ok" - you say - "but what if I want to use a baseline, which is the regular way of working anyway..."

    And you're right!

    To use a baseline you combine the label rule inside the branch rule, like the following selector


    rep "default"
    path "/"
    br "/main/task001" label "BL051;LAST"
    co "/main/task001"


    For shortness the label rule can be simplified:


    rep "default"
    path "/"
    br "/main/task001" label "BL051"
    co "/main/task001"


    The good thing here is that it is very simple to write multi-branch selectors


    rep "default"
    path "/"
    br "/main/release50/bug-fix490" label "stable040;BL050;LAST"
    co "/main/release50/bug-fix490"


    The branch per task rule

    And now admire one of the least known plastic selector rules:


    rep "default"
    path "/"
    branchpertask "/main/task001" baseline "BL009"



    Which is equivalent to:


    rep "default"
    path "/"
    branch "/main/task001" label "BL009"
    checkout "/main/task001"


    But even shorter!

    Wrapping up!

    Well, now you're familiar with all the core selector concepts, and you're ready to jump to our next big topic: multi-repository selectors!!