Previous: Making Changes, Up: Tutorial



2.10 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 preceeded Abe's work, which entirely preceeded 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 2e24d...
  2. Abe changed revision 2e24d... into revision 70dec...
  3. Beth derived revision 70dec... into revision 80ef9...

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 80ef9... which is currently in her working copy:

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

Beth finishes and examines her changes:

     $ monotone diff
     #
     # patch "src/banana.c"
     #  from [7381d6b3adfddaf16dc0fdb05e0f2d1873e3132a]
     #    to [5e6622cf5c8805bcbd50921ce7db86dad40f2ec6]
     #
     --- src/banana.c
     +++ src/banana.c
     @ -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()
     +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:

     $ monotone commit --message='interrupt implementation of src/banana.c'
     monotone: beginning commit
     monotone: manifest de81e46acb24b2950effb18572d5166f83af3881
     monotone: revision 8b41b5399a564494993063287a737d26ede3dee4
     monotone: branch jp.co.juicebot.jb7
     monotone: committed revision 8b41b5399a564494993063287a737d26ede3dee4

And she syncs with Jim:

     $ monotone sync jim-laptop.juicebot.co.jp jp.co.juicebot.jb7

Unfortunately, before Beth managed to sync with Jim, Abe had woken up and implemented a similar interrupt-based apple juice dispenser, but his working copy is 70dec..., 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:

     $ monotone 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:

     $ monotone sync jim-laptop.juicebot.co.jp jp.co.juicebot.jb7

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:

     $ monotone heads
     monotone: branch 'jp.co.juicebot.jb7' is currently unmerged:
     39969614e5a14316c7ffefc588771f491c709152 abe@juicebot.co.jp 2004-10-26T02:53:16
     8b41b5399a564494993063287a737d26ede3dee4 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:

     $ monotone merge
     monotone: merging with revision 1 / 2
     monotone: [source] 39969614e5a14316c7ffefc588771f491c709152
     monotone: [source] 8b41b5399a564494993063287a737d26ede3dee4
     monotone: common ancestor 70decb4b31a8227a629c0e364495286c5c75f979 found
     monotone: trying 3-way merge
     monotone: [merged] da499b9d9465a0e003a4c6b2909102ef98bf4e6d

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.

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

     $ monotone update
     monotone: selected update target da499b9d9465a0e003a4c6b2909102ef98bf4e6d
     monotone: updating src/apple.c to f088e24beb43ab1468d7243e36ce214a559bdc96
     monotone: updating src/banana.c to 5e6622cf5c8805bcbd50921ce7db86dad40f2ec6
     monotone: updated to base revision da499b9d9465a0e003a4c6b2909102ef98bf4e6d

The update command selected an update target — in this case the newly merged head — and performed an in-memory merge between Jim's working copy and the chosen target. The result was then written to Jim's working copy. If Jim's working copy 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, the merge can fail (due to difficulty in a manual merge step) and your committed state is still safe. It is therefore recommended that you commit your work first, before merging.