Any Change Creates New Heads?
Giorgos Keramidas
keramida at ceid.upatras.gr
Wed Apr 29 04:22:46 CDT 2009
On Tue, 28 Apr 2009 18:21:06 -0700, Stephen Rasku <mercurial at srasku.net> wrote:
>On Tue, Apr 28, 2009 at 16:12, Douglas Philips <dgou at mac.com> wrote:
>> There is a bit of a wrinkle there. If you just pull/merge/commit
>> (fetch does this for you), then you are responsible for the results
>> of the merge. It is a matter of your workflow/policy as to how much
>> you should/need to examine/test the results. This is another one of
>> the EVIL things that CVS would hide from you, and so it can be a bit
>> of an adjustment.
>
> What do you mean CVS would hide it? Do you mean that if another
> developer changed a file you didn't edit that you wouldn't necessarily
> notice that.
Yes, in a way. With CVS you can check out a 'partial' tree. The upside
of this is that it is very convenient to be able to check out only the
part you are working with, especially if the full source tree is several
hundred MB.
The downside is that if you have a project that includes several parts
with inter-dependencies, like:
project/
project/lib/
project/lib/libfoo/
project/lib/libbar/
it is possible to check out only `project/lib/libfoo/' and make changes
to that library that work fine in isolation but break the build or even
introduce bugs in the behavior of the library. When multiple people
work with the same source tree it is also possible to have commits in a
chronological order that doesn't make sense, i.e.:
1. Committer A checks out `project/lib/libfoo'.
2. Committer B checks out everything under `project'.
3. Committer A makes a change to `libfoo' in this local tree, but
walks out for lunch before committing it to the main repository.
4. Committer B makes a local change to `project/lib/libbar', runs
the test suite for the entire project and commits his change.
5. Committer A walks back in. He runs the tests with his (now
stale) copy of the entire project. The tests work fine, so he
commits to `libfoo'.
At point 5 there are no `conflicting' changes to the repository, so A is
allowed to just go ahead and push his change. But his change has NEVER
been tested with the changes of committer B from step 4.
The repository now contains changes that were not tested as a full
`project'... ever. But this is neither committer A's fault nor the
fault of committer B, who are both doing their job just fine.
With a distributed VCS, what you would do is start with a `project'
repository at step 1 that includes the changes:
[1] --- [2] --- [3]
This is what both committer A and B would clone. Going through the same
steps, they would end up with the history shown after every step below:
1. Committer A clones the changes up to [3]:
[1] --- [2] --- [3]
2. Committer B clones the changes up to [3] too:
[1] --- [2] --- [3]
3. Committer A makes a local change to `libfoo' in his own personal
clone:
[1] --- [2] --- [3] --- [A1]
The change is _committed_ to his local clone, but it has not been
pushed to the main repository yet.
Then he walks out for lunch.
4. Committer B commits a local change to his clone, fixing something
in `libbar' as before:
[1] --- [2] --- [3] --- [B1]
Then he runs the test suite, and everything works fine. So he
goes ahead and pushes his change to the main repository.
5. Committer A walks back in. He tries to push his change, but
Mercurial warns him that the main repository has changed in the
meantime and that his push would "create remote heads".
6. Committer A pulls the changes of B and his local clone now is:
[1] --- [2] --- [3] --- [A1]
\
+---- [B1]
So he merges the changes of B from change B1, and tests the
*merged* state of the code:
[1] --- [2] --- [3] --- [A1] --- [M1]
\ /
+---- [B1] ---+
The merged code fails the tests because of the incompatible
changes committer A and B have made.
In this fictional example, CVS would *permit* something that creates a
broken state of the code in the main repository (without any warning
whatsoever). With Mercurial the brokenness is limited to the local
clone of committer A, who now has a chance to:
- Go back and see if his merge needs to be adapted to the new code
from committer B.
- Fix the breakage *before* it hits the main repository.
- (For advanced users only) Use a Mercurial extension to 'rebase'
his changes on top of changeset B1, to create a more 'linear'
history before pushing:
[1] --- [2] --- [3]
\
+---- [B1] --- [A1']
Note that the brokenness I mentioned in the CVS case can also be avoided
with CVS if your development guidelines include a rule that says:
Only commit from full checkouts and *always* run the test suite from
a fully updated workspace tree.
But this sort of loses the entire feature of being able to work with
partial checkouts (and might be _much_ slower than Mercurial when
updating the workspace very often, because the CVS repository has to be
consulted at every update over an arbitrarily slow network connection).
More information about the Mercurial
mailing list