Next: , Previous: Making Changes, Up: Tutorial


2.11 Dealing with a Fork

Careful readers will note that, in the previous section, the JuiceBot company's work was perfectly serialized:

  1. Jim did some work
  2. Abe synced with Jim
  3. Abe did some work
  4. Abe synced with Jim
  5. Beth synced with Jim
  6. Beth did some work
  7. Beth synced with Jim

The result of this ordering is that Jim's work entirely preceded Abe's work, which entirely preceded Beth's work. Moreover, each worker was fully informed of the “up-stream” worker's actions, and produced purely derivative, “down-stream” work:

  1. Jim made revision 493bd...
  2. Abe changed revision 493bd... into revision 42eae...
  3. Beth derived revision 42eae... into revision 85573...

This is a simple, but sadly unrealistic, ordering of events. In real companies or work groups, people often work in parallel, diverging from commonly known revisions and merging their work together, sometime after each unit of work is complete.

Monotone supports this diverge/merge style of operation naturally; any time two revisions diverge from a common parent revision, we say that the revision graph has a fork in it. Forks can happen at any time, and require no coordination between workers. In fact any interleaving of the previous events would work equally well; with one exception: if forks were produced, someone would eventually have to run the merge command, and possibly resolve any conflicts in the fork.

To illustrate this, we return to our workers Beth and Abe. Suppose Jim sends out an email saying that the current polling juice dispensers use too much CPU time, and must be rewritten to use the JuiceBot's interrupt system. Beth wakes up first and begins working immediately, basing her work off the revision 85573... which is currently in her workspace:

$ vi src/banana.c
<Beth changes her banana-juice dispenser to use interrupts>

Beth finishes and examines her changes:

$ mtn diff
#
# old_revision [85573a54105cd3220db10aa6a0713643cdf5ce6f]
#
# patch "src/banana.c"
#  from [d7e28a01cf6fc0f9ac04c6901dcafd77c2d32fb8]
#    to [dd979c3c880e6a7221fcecd7148bd4afcfb3e964]
#
============================================================
--- src/banana.c        d7e28a01cf6fc0f9ac04c6901dcafd77c2d32fb8
+++ src/banana.c        dd979c3c880e6a7221fcecd7148bd4afcfb3e964
@ -1,10 +1,15 @
 #include "jb.h"

+static void
+shut_off_banana()
+{
+  spoutctl(BANANA_SPOUT, SET_INTR, 0);
+  spoutctl(BANANA_SPOUT, FLOW_JUICE, 0);
+}
+
 void
 dispense_banana_juice()
 {
+  spoutctl(BANANA_SPOUT, SET_INTR, &shut_off_banana);
   spoutctl(BANANA_SPOUT, FLOW_JUICE, 1);
-  while (spoutctl(BANANA_SPOUT, POLL_JUICE, 1) == 0)
-    usleep (1000);
-  spoutctl(BANANA_SPOUT, FLOW_JUICE, 0);
 }

She commits her work:

$ mtn commit --message="interrupt implementation of src/banana.c"
mtn: beginning commit on branch 'jp.co.juicebot.jb7'
mtn: committed revision 90abe0f1bc354a73d42d3bff1b02946559682bd9

And she syncs with Jim:

$ mtn sync

Unfortunately, before Beth managed to sync with Jim, Abe had woken up and implemented a similar interrupt-based apple juice dispenser, but his workspace is 42eae..., which is still “upstream” of Beth's.

$ vi apple.c
<Abe changes his apple-juice dispenser to use interrupts>

Thus when Abe commits, he unknowingly creates a fork:

$ mtn commit --message="interrupt implementation of src/apple.c"

Abe does not see the fork yet; Abe has not actually seen any of Beth's work yet, because he has not synchronized with Jim. Since he has new work to contribute, however, he now syncs:

$ mtn sync

Now Jim and Abe will be aware of the fork. Jim sees it when he sits down at his desk and asks monotone for the current set of heads of the branch:

$ mtn heads
mtn: branch 'jp.co.juicebot.jb7' is currently unmerged:
90abe0f1bc354a73d42d3bff1b02946559682bd9 abe@juicebot.co.jp 2004-10-26T02:53:16
951da88860a4cf7419d66ed9094d8bf24df5fb8b beth@juicebot.co.jp 2004-10-26T02:53:15

Clearly there are two heads to the branch: it contains an un-merged fork. Beth will not yet know about the fork, but in this case it doesn't matter: anyone can merge the fork, and since there are no conflicts Jim does so himself:

$ mtn merge
mtn: 2 heads on branch 'jp.co.juicebot.jb7'
mtn: merge 1 / 1:
mtn: calculating best pair of heads to merge next
mtn: [left]  90abe0f1bc354a73d42d3bff1b02946559682bd9
mtn: [right] 951da88860a4cf7419d66ed9094d8bf24df5fb8b
mtn: [merged] 3aca69c7749bde9bd07fe4c92bb868bd69b2e421
mtn: note: your workspaces have not been updated

The output of this command shows Jim that two heads were found, combined via a 3-way merge with their ancestor, and saved to a new revision. This happened automatically, because the changes between the common ancestor and heads did not conflict. If there had been a conflict, monotone would have invoked an external merging tool to help resolve it, or Jim could have used the conflicts set of commands to resolve it (see Conflicts).

After merging, the branch has a single head again, and Jim updates his workspace.

$ mtn update
mtn: updating along branch 'jp.co.juicebot.jb7'
mtn: selected update target 3aca69c7749bde9bd07fe4c92bb868bd69b2e421
mtn: [left]  d60c18ec5e0cf1163b276f0bfdd908c1dfd53b4a
mtn: [right] 3aca69c7749bde9bd07fe4c92bb868bd69b2e421
mtn: updating src/apple.c
mtn: updating src/banana.c
mtn: updated to base revision 3aca69c7749bde9bd07fe4c92bb868bd69b2e421

The update command selected an update target — in this case the newly merged head — and performed an in-memory merge between Jim's workspace and the chosen target. The result was then written to Jim's workspace. If Jim's workspace had any uncommitted changes in it, they would have been merged with the update in exactly the same manner as the merge of multiple committed heads.

Monotone makes very little distinction between a “pre-commit” merge (an update) and a “post-commit” merge. Both sorts of merge use the exact same algorithm. The major difference concerns the recoverability of the pre-merge state: if you commit your work first, and merge after committing, then even if the merge somehow fails (due to difficulty in a manual merge step, for instance), your committed state is still safe. If you update, on the other hand, you are requesting that monotone directly modify your workspace, and while monotone will try hard not to break anything, this process is inherently more open to error. It is therefore recommended that you commit your work first, before merging.

If you have previously used another version control system, this may at first seem surprising; there are some systems where you are required to update, and risk the above problems, before you can commit. Monotone, however, was designed with this problem in mind, and thus always allows you to commit before merging. A good rule of thumb is to only use update in workspaces with no local modifications, or when you actually want to work against a different base revision (perhaps because finishing your change turns out to require some fixes made in another revision, or because you discover that you have accidentally started working against a revision that contains unrelated bugs, and need to back out to a working revision for testing).

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status