Modules

Florin Mateoc fmateoc at gmail.com
Mon Feb 28 03:21:33 UTC 2005


Colin Putney wrote:

>
> On Feb 26, 2005, at 11:20 PM, Florin Mateoc wrote:
>
>>> We have two versions of a method, both with complete version 
>>> history. Because we have the version history, it doesn't really 
>>> matter that the two versions come from different packages, it's 
>>> exactly the same as merging two versions of the same package. So 
>>> instead of one version overriding the other, we do a merge. By 
>>> comparing the method histories we can decide if one version 
>>> supersedes the other. That would mean that it's an updated version 
>>> of the other, which means we can rely on the user's wisdom again. If 
>>> the user changed the method from one of the versions we have to the 
>>> other one, he must know what he's doing. Therefore we use which ever 
>>> version the user has already chosen.
>>
>>
>>
>> I am sorry, but this is simply not true. A developer may choose, in a 
>> newer version of a class, to ignore some unrelated development, and 
>> stick to an older protocol, by including some older versions for some 
>> of the methods. This is not a made up example, I have encountered the 
>> situation quite often. You can easily have, as a simplistic example, 
>> PackageA>ClassB>methodC(version1),methodD(version2) and 
>> PackageE>ClassB>methodC(version2),methodD(version1). The automatic 
>> resolution will do the wrong thing, and it won't even inform the user.
>
>
> Ok, let's get into this in excruciating detail, because it's not clear 
> to me why you think the above example cannot be resolved correctly. 
> Let's say I have the following program elements, drawn from your 
> example, with history.
>
> PackageA
>
> ClassB>>methodC.version1 (ancestors: version0)
> ClassB>>methodD.version2 (ancestors: version0, version1)
> ClassB>>MethodE.version1 (ancestors: version0)
>
> PackageZ
>
> ClassB>>methodC.version2 (ancestors: version0, version1)
> ClassB>>methodD.version1 (ancestors: version0)
> ClassB>>methodE.version2 (ancestors: version0)
>
> Ok, so let's see what happens if we load both packages into the same 
> image. PackageA and PackageZ both define methodC, and they have 
> different definitions. So we've got to decide which version, if any, 
> will be in the image. The version in PackageA is called version1, and 
> it was derived from version0. The version in PackageZ is called 
> version2, and lists version1 as its ancestor. Therefore, version2 was 
> created by modifying version1. So we'll choose version2, from PackageZ.
>
> MethodD has the reverse situation. PackageA's version is a descendent 
> of PackageZ's, so we'll choose version2 again, but this time from 
> PackageA.
>
> MethodE presents a conflict. Both versions descend from a common 
> ancestor, but neither descends from the other. So we pop up a conflict 
> resolution window, and let the user decide what methodE should look 
> like when both packages are present. This results in a new version, 
> called version3. When we're done, the image looks like this:
>
> ClassB>>methodC.version2 (ancestors: version0, version1)
> ClassB>>methodD.version2 (ancestors: version0, version1)
> ClassB>>methodE.version3 (ancestors: version0, version1, version2)
>
> Now, you are correct to point out that, say, methodC.version2 might 
> have been developed in PackageZ without PackageA loaded, and so might 
> not work as PackageA expects. Perhaps we should indeed log to the 
> Transcript when a merge automatically resolves overlapping packages. 
> There is no *guarentee* that version2 will work right. But our chances 
> of success are better if we follow the intention of the developer of 
> version2, which was to replace version1. Following the order that 
> packages are loaded is little different than choosing at random.


But there is no way to divine the intentions of a developer from the 
version history. When a developer intentionally chooses to include an 
older version, his or her intention is to have that older version 
loaded, and not the newer one, regardless of their ancestry. You cannot 
say that you follow any developer's intention by automatically choosing 
a newer version instead of an older one. There is such a thing as 
intentionally reverting to an older version.

In our particular case, the only intention known for the developer of 
PackageA is that methodC.version1 should work with methodD.version2 and 
methodE.version1. For the developer of PackageZ we know that 
methodC.version2 should work with methodD.version1 and methodE.version2. 
There is no manifest developer who ever had the intention to load 
methodC.version2 together with methodD.version2, let alone having tested 
this combination. There is a big chance that because of the automatic 
merge in such a case, none of the two packages will work anymore. Quite 
often, in the same repository you have multiple streams of development. 
A new fix for an old, production image, may mean backporting from the 
current, development image. Some of the new methods are appropriate, 
some not.
By letting the last package "win", not only do you not choose at random, 
but you get pretty close to guaranteeing that at least the last package 
will function. For me, at least in programming, where I like my universe 
deterministic, a "guarantee" for less is better than a hope for more.

>
>> Making independently developed packages work together means 
>> (intelligent) work, and if there's any overlap, the chances of 
>> solving the issues automatically are, I believe, very slim, and 
>> versioning does not help. Even if all the common methods in one of 
>> the packages are newer versions (and descendants) of the same methods 
>> in the other packages, it still doesn't mean that they are made to 
>> work with the older package, it may simply mean that the newer 
>> package is supposed to work with a newer version of the older 
>> package. I think the only situation where you can say that there is 
>> no conflict is when the common methods are all identical, and for 
>> this you don't need versions. This is why, to my mind, overrides have 
>> nothing to do with versioning, they are simply a different kind of 
>> extension.
>
>
> I agree that it takes intelligent work to make packages work together, 
> and I'm not suggesting that the computer can do that. I am suggesting 
> that, having done the work, we record the results so that we don't 
> have to do it again everytime we load those packages.
>
> [snip]
>
>>>> This is probably just the memory of a frustration with Envy: 
>>>> because it stores all these method editions (inluding every time 
>>>> you put a "halt" in a method), the noise level is pretty high, so I 
>>>> always wished that I could see at a glance, when looking at the 
>>>> list of editions for a method, which editions are "real". But even 
>>>> if we have explicit method versioning, so the noise is reduced, the 
>>>> most "real" ones are the ones associated with the holder's version, 
>>>> because there is an implicit minimal testing expectation for 
>>>> versions.For the method version I would expect something like a 
>>>> unit-test, for a class, the beginning of some functional testing. 
>>>> The expectation is even higher for the package, because it usually 
>>>> groups together classes working in tight coupling, so the testing 
>>>> done for a package version is more of a functional test, so now 
>>>> those methods "really" work. I guess it would be fun to disallow 
>>>> versioning if we detect that testing was not performed :) Seriously 
>>>> though, it might be interesting if we could link somehow versions 
>>>> to the tests performed.
>>>
>>>
>>>
>>> Ok, I see. You just want to define a group of program elements that 
>>> should be versioned together. With Monticello you do this 
>>> explicitly, so there's a lot less noise. Everything is a "real" 
>>> version, and they correspond to a bunch of other "real" versions 
>>> that were current at the same time.
>>
>>
>> Even if you do it explicitely, not all versioning happens at the same 
>> time.
>> I develop a method, it looks good, I test it a little (workspace, 
>> unit-test, whatever), I am happy with it and I want to keep it. I 
>> version it (separately, because this is what method-level granularity 
>> means).
>
>
> Interesting, because that's not what I mean by "method-level 
> granularity."
>
> In MC1 (and Store, as far as I can tell), only packages have ancestry. 
> The ancestry of a method has to be reconstructed by examing all the 
> versions of the package it appears in and noting how it changes. In 
> MC2 (and Envy, as far as I can tell), methods have individually 
> recorded ancestry. This is why I say that MC2 versions at method-level 
> granularity.
>
> But that doesn't mean that you have to version new methods in 
> isolation, whether explicitly or with every accept as in Envy. If you 
> do that, you loose the spacial context I mentioned in my last post. 
> That version is just noise, so why bother? It's not like the method is 
> going anywhere. You can save your image without versioning it, and 
> even if you manage to crash the VM you can always pull it out of the 
> change log.


Of course that you don't have to version the new method in isolation, 
but if you do want to, why not be able to do it? Who decides for me that 
something that I want to name and keep and reference later, share with 
others, is just noise, and I should not bother? In the course of 
developing something, the way the code progresses is meaningful, and you 
may want to maintain a history with these snapshots. This is a very 
cheap way of documenting what you are doing. Granted, the value of older 
versions decreases over time, but that is true for all versions, at 
every level, not just for methods. But I can tell you that every time I 
decided to purge something from arepository, to drop something from the 
history of a project, I came to regret it later.

>
> In MC1 you always version whole packages at a time. MC2 is more 
> flexible, in that you can specify other ways of separating the code 
> you are interested in from the rest of the image. But whatever your 
> method of segregation, you always version all of it at once. So in 
> this sense, Monticello versions at "project-level granularity," the 
> project being whatever you're working on, be it a package, change set 
> or whatever. It's important to do that so as to get the spacial 
> context needed to merge snapshots correctly.


Part of a "project" may mean reverting a method to an existing,  older 
version, reverting a class to an older version, reverting a package to 
an older version. This happens quite frequently when developing a patch. 
What does it mean for these components' history that you re-version them?

>
> Colin
>
>
>
>
>




More information about the Squeak-dev mailing list