Merging parallel updates in two Monticello packages
Last updated at 6:17 pm UTC on 14 January 2006
Do not use this. I'm just muddling through Tom Koenig
Monticello assumes that code belongs to at most one package. Within a package, Monticello nicely supports distributed, parallel development. Two versions of a package can be merged back together at any time because Monticello knows their common ancestor. For example, package P has been enhanced by two separate developers creating versions v and w. Now we want to put combine/merge the two versions. We load, say, v as our working copy and ask Montecello to merge w into it. Monticello will find the common ancestor, call this c, and produce a new common version, call this n. The problem frame is:
P(c) From a common base, separate development occurs
/\ giving the "most current" versions v and w
/ \ Any number of versions between c and v
/ \ and between c and w can be created.
P(v) P(w) —-above is the problem, below is the solution—-
\ / Merge using Monticello to give version n,
P(n) Development based on P(n) continues.
The challenge in implementing the solution is making the right decisions about conflicts by inspecting the code.
For practical reasons, sometimes developers clone some part of an original package (call it O) and puts the code into another package (call it T). The cloned code, call it x, implements some set of functionality (think of it as a sub-package.) The code in x is not actually copied in the image. Rather the class and method categories of x are changed so that x is now considered part of T. Typically both packages continue to undergo their separate develoment paths, each treating x as if it was part of itself. In any given image, only one version the classes and methods of x will exist. Which ones they are will depend on whether O or T is loaded last.(See footnotes.)
In such circumstance, eventually some developer will want to merge the two update streams of x back together and apply the results to T. However, the original cloning was not a Monticello transform. Monticello doesn't "know" that x in T has a shared ancestory from O. What's more the functionally represented by x may have changed in both packages. We haven't made x a true package so we have no boundaries around it. This is exactly the problem I have in wanting to update the OB code in the Traits package with a more recent version from the OmniBrowser package.
How can we merge parallel updates done in separate packages? This is more complicated that the above problem frame but the it is based on that frame with a couple of embellishments. The extended problem frame is:
O(0)========x=>T(1) Parts of O, x, were reclassified as being part of T.
| . | Development based on O(0)and T(1)continued
| . T(3) giving the "most current" versions 3 and 4.
O(4) . | —-above is the problem, below is the solution—-
|\ O(5)<=y==T(3) Parts of T, y, are reclassified as being part of O.
| \ / |
| O(6) | O(4) and O(5) are merged to produce O(6).
| | |
| 0(6)=====z=>T(7) Parts of O, z, are reclassified as being part of T.
| | Development based on O(0)and T(7)continues.
To implement this solution we must:
The challenge is that the discovery of how x became y became z must be done by human inspection not by automated compares. Only the discovery of x is automated. (Let's hope we have good Sunit tests; in the case of Omnibrowser and Traits, we do. )
- Discover x, the classes and methods that went from having O's class and method categories to having T's class and method categories in the original cloning. I think x can be discovered by: a) loading O(0), b) loading T(1), and c) comparing the working version of O to O(0)to see the changes. The deleted core classes and extension methods are x.
- Discover y, the equivalent classes and methods after development in in the target package. We can begin to discover y: by a) loading T(3). If the core classes and methods haven't been drastically, changed we will be able to figure out what code now implements the equivalent functionality by looking at the changes.
- Discover z, the equivalent classes and methods in the after development in both packages. We can begin to discover z by: a) reclassifying the classes and methods in y back to O creating O(5), b) merging O(3) and O(5) creating O(6). (In this process O(0)acts as the implied ancestor for the O(3) and O(5) merge.) If the core classes and extention methods haven't drastically changed, we will be able to figure out what code now implements the equivalent functionality by looking at the changes.
- Finally, save the merged implementation back to the target package. We do this by: a) changing the class and method categories for elements in z from O to T and b) saving T(7) which we use for future developmnet. (T(3) will be the ancestor for T(7).)
Here is second description of the merge process, showing what happens in the image. In the following, O(w) refers to the working version of OmniBrowser and O(w+x) indicates that the classes and methods in question are classified as part of OB.
Install O(0) O(w+x)
Install T(1) O(w-x) T(w+x) This recategorizes x as a side effect.
Diff O(w) vs O(0) O(w-x) T(w+x) This gives us -x.
Install T(3) O(w-x) T(w+y) Now we must discover y.
Recategorize y O(w+y) T(w-y)
Merge with O(4) O(w+z) T(w-z) Now we must discover z.
Recategorize z O(w-z) T(w+z)
Save T(w)as T(7) O(w-z) T(w+z) This gives us what we want.
Here is third description of what I did to upgrade the Omnibrowser code in Traits to have the latest changes from both Packages. Before starting, I had to figure out the most current version of OB and Traits, which was trivial, and the version of OB and Traits when the cloneing of the OBBroswer occurred. I did this by loading Traits 2.1 into Squeak 3.7 and opening the browser. I found xsss, so I then loaded Omnibrowser sss, then Traits sss, I saved Omnibrowser tlk and compared it to Omnibrowser sss. So
I ran all the Sunit tests for Omnibrowser and Traits to see if the code still worked.
- Having discovered this it was a simple matter to ask Monticello to compare the working verison of OB to I saved the list of elements as sss(x).
- To find y, I then loaded Traints in theimage. Comparing the diffences showed...... So I created a list of core classes and extension methods that needed to be changed (y).I saved the list of elements as sss(z).
- To find z, I recategorized the classes and methods in y back to OB . I then ask Monticello to merge OB .....into the working verison of OB. By inspecting the chagnes I recovered z. I saved the list of elements as sss(z).
- Finally, I changed all of the z core classes class categories and z extension methods method categories back to the Trait categoreis. I then saved Traits 2.2.ob
- Cloning is not quite the right word. The code is not copied, its class and methods categories are changed. In any given image, only one version of each class and each extension method of x will exist. Which one exists will depend on which package is loaded last. (In reality we have a sort of race condition.) I use cloning because two copies do exist in the two different package snapshots.
- Rather than contining to clone the code and do dual maintenance, we can of course factor the common code out into a separate package and make it a prequisite for both packages. Technically, this would only mean modifying step 4 to generate three packages: O(6), T(7) and say P(8), the new subpackage. Organizationally it requires closer coordination.