Bazaar for Agile Teams

Bazaar makes it easy to collaborate with a team. Using what you've already learned about the basics of version control and how to work with branches, I'll show you how easy it is to coordinate with a team.


People have many different approaches to how they organize software development. Some people like to work alone while others like to work in teams of various sizes. Bazaar doesn't restrict how you use it to collaborate. It gives you all the tools you need to support any development process (or lack of process).

Since agile development methodologies are popular, I'm going to show you how you can use Bazaar to make collaboration on an agile project easy. This is not the only way to collaborate with Bazaar, but this part of the tutorial will give you a taste of how you can adopt Bazaar to support whatever approach to development you prefer.

As I mentioned in the previous section, all work in Bazaar happens in the context of a branch, so we're going to organize our agile team's work in branches.

For this project, we're going to have three kinds of branches. The first one is the trunk branch. This will be the base branch of our project. Some teams refer to this as the "mainline" or "master" branch. There's only one trunk branch.

Agile teams break up their work into iterations that normally last from one to four weeks. We're going to branch from the trunk to create an iteration branch. This branch will hold the team's shared work during an iteration. We'll have one iteration branch per iteration, so there will only be one active iteration branch at a time.

Agile teams model what they're building as user stories. The team will commit to a number of user stories in an iteration. For our process, we're going to create a branch per user story. This will allow developers to work on user stories independently. We can have as many user story branches as needed

As you can see from the diagram, we'll fork off an iteration branch at the start of the team's iteration. Work in the iteration branch will not affect the trunk until it's merged in at the end of the iteration.

A user story branch will be branched from the iteration branch as needed, and merged back in when complete.

Setting Up Our Workspaces

Let's get started by creating the trunk branch. In the previous section, Branching Zen, we took a rather haphazard approach to branching. Now that we understand how branching works, we're going to be a bit more mindful of our branch structure.

By default, the bzr init command will create a new branch and a repository to store that branch. When we fork that branch with bzr branch, the command will create a new branch and a new repository to hold the complete branch history.

Shared Repository
A repository that contains history for a number of related branches.

While this works, it makes branching a bit expensive because we have to copy the entire history every time we make a branch. Bazaar has an option to create a shared repository, which we can use to efficiently store multiple branches of the same project.

Creates a shared repository.
fmccann
~ fmccann$ bzr init-repo project
Shared repository with trees
Location:
  shared repository: project

Now that we have a shared repository, we'll create the trunk branch.

fmccann
~ fmccann$ cd project/
project fmccann$ bzr init trunk
Created a repository tree
Using shared repository: /home/fmccann/project/
project fmccann$ cd trunk/
trunk fmccann$ touch README.txt
trunk fmccann$ mkdir src
trunk fmccann$ mkdir test
trunk fmccann$ bzr add
adding README.txt
adding src
adding test
trunk fmccann$ bzr commit -m"initial import"
Committing to: /home/fmccann/project/trunk/
added README.txt
added src
added test
Committed revision 1.

The /home/fmccann/project directory is a shared repository. It contains the history for all our branches. Each subdirectory in /home/fmccann/project is a branch in the shared repository. When we call bzr init to create the trunk branch, Bazaar notices we're in a shared repository and uses it rather than creating a stand-alone repository.

Creating branches in a shared repository is very fast and takes less disk space than copying the entire history each time.

Now we've got an empty project structure in the trunk branch. We have a problem— right now this branch only exists on some developer's laptop. Just as we did in Branching Zen, we're going to use a server to collaborate.

trunk
trunk fmccann$ bzr init-repo bzr+ssh://dev.example.com/srv/project --no-trees
Shared repository
Location:
  shared repository: bzr+ssh://dev.example.com/srv/project/
trunk fmccann$ bzr push bzr+ssh://dev.example.com/srv/project/trunk
Created new branch.
Branches Without Trees
It's possible to make a branch without a working tree. This is common when making a mirror of a branch on a remote server since no one will be working on any files directly in the remote location. A branch without a working tree only holds history.

To create our server side repository, we call the same bzr init-repo command, giving Bazaar the url to the location on the server. Notice that we also used the --no-trees option. When Bazaar creates a new branch, it also creates a working tree— the files we work on.

Since no one is actually going to work directly on the remote server, we don't need a working tree in any of the branches. Not having working trees on the server makes the server side repository/branches take up less space, and applying changes is faster since we don't have to worry about updating the remote trees when we push new revisions.

After we have a shared repository on the server, we can publish our trunk branch with a simple bzr push to a subdirectory of the shared repository.

Let's get started with a branch for our first iteration.

trunk
trunk fmccann$ cd ..
project fmccann$ bzr branch trunk iteration-1
Branched 1 revision
project fmccann$ cd iteration-1
iteration-1 fmccann$ bzr push bzr+ssh://dev.example.com/srv/project/iteration-1
Created new branch.

It's simple to create a new iteration-1 branch based on trunk, then push that branch to the server so the team can share it. Notice that I branched the iteration-1 branch from my local copy of the trunk branch. This is much faster than working with the remote copy of trunk.

Charlie's going to be working on the agile team, so he's going to set up his own workspace with a shared repository and a local mirror of the iteration-1 branch.

charvey
~ charvey$ bzr init-repo project
Shared repository with trees
Location:
  shared repository: project
~ charvey$ cd project/
project charvey$ bzr branch bzr+ssh://dev.example.com/srv/project/iteration-1
Branched 1 revision.

Here's what all of the workspaces look like:

Bazaar does a good job of mapping to familiar file system metaphors. A branch is a directory. A shared repository is a parent directory that contains one or more branch directories.

Working on User Stories

We're going to build a simple calculator class. I know that is about as exciting as a "hello world" program, but the point here is to learn about using Bazaar with agile teams, not to implement HAL 9000. Besides, the HAL 9000 project didn't work out that well anyway.

For the first iteration of the project, we're going to tackle the following user stories:

  • User Story #1: Update the readme file.
  • User Story #2: Implement basic arithmetic operations (addition, subtraction, multiplication, division).
  • User Story #3: Add methods to get the value of the accumulator and clear the accumulator.
  • User Story #4: Implement square root.
Feature Branch
A branch that exists to track a single feature of a project.

To start, we're going to take user story #1 and Charlie will work on user story #2. We could all just start working directly in our iteration-1 branches, but we're going to work in feature branches. A feature branch is simply a branch that exists to track the work of a single feature. This is a good fit for working on user stories.

trunk
trunk fmccann$ cd ..
project fmccann$ bzr branch iteration-1 us-1
Branched 1 revision
project fmccann$ cd us-1/
project
project charvey$ bzr branch iteration-1 us-2
Branched 1 revision
project charvey$ cd us-2/

While Charlie gets to work writing some tests for the Calc class, we're going to update the readme file:

README.txt
Calc - A simple calculator class
us-1
us-1 fmccann$ bzr commit -m"Updated readme file"
Committing to: /home/fmccann/project/us-1/
modified README.txt
Committed revision 2.

Well, that was easy.

Merge vs Push/Pull
Push and pull are for operating on mirrored branches. Merging is used to combine the work from different branches.

Now that we have a user story completed, we're going to land it in the iteration-1 branch. You might think the correct thing to do is to use bzr push to send our revision to the iteration-1 branch, but this is not correct. We want to merge our changes into iteration-1.

We use bzr push and bzr pull to send revisions between branches that are mirrors of each other. These mirrors are conceptually the same branch, just in different locations. When we want to combine the work of different branches, we always use bzr merge.

The iteration-1 branch is not a mirror of the us-1 branch. We'd like the iteration-1 branch to contain the work from us-1 (and all subsequent user stories in the iteration). Since iteration-1 is the container, we're going to merge us-1 into iteration-1.

us-1
us-1 fmccann$ cd ../iteration-1/
iteration-1 fmccann$ bzr pull bzr+ssh://dev.example.com/srv/project/iteration-1
No revisions or tags to pull.
iteration-1 fmccann$ bzr merge ../us-1/
 M  README.txt
All changes applied successfully.
iteration-1 fmccann$ bzr commit -m"US-1: Update readme file"
Committing to: /home/fmccann/project/iteration-1/
modified README.txt
Committed revision 2.
iteration-1 fmccann$ bzr push bzr+ssh://dev.example.com/srv/project/iteration-1
Pushed up to revision 2.

Let's review the steps we took to land us-1 into the iteration-1 branch:

  1. Change to the iteration-1 branch.
  2. Use bzr pull to make sure our local mirror of the iteration-1 branch agrees with the server's copy.
  3. Use bzr merge to merge the changes from the us-1 branch into the iteration-1 branch.
  4. Call bzr commit to record the merge, adding a commit message describing the story.
  5. Use bzr push to make sure our server's copy of the iteration-1 branch agrees with our local copy.

We merge our changes to the iteration-1 branch as soon as our story is tested and working. We also make sure that the server's version of iteration-1 contains our work. We do this for a few reasons. One reason is we likely have a continuous integration system watching for changes in the server's copy of the iteration-1 branch to make sure we're not introducing regressions as we work. Another reason is we want the team to integrate work as soon as user stories are complete, so any future feature branches will build on the work that's already been completed.

Collaborating on User Stories

While we were busy with the readme file, Charlie made an empty Calc class with no methods in it, and some failing unit tests that test all basic arithmetic functions.

calc.rb
class Calc

end
calc_test.rb
require 'test/unit'
require_relative '../src/calc'

class TestCalc < Test::Unit::TestCase

  def setup
    @calc = Calc.new
  end

  def test_add
    assert_equal 5.2, @calc.add(5.2)
    assert_equal 6.2, @calc.add(1)
    assert_equal 4.2, @calc.add(-2)
    assert_equal -1.8, @calc.add(-6)
  end

  ...

  def test_div
    # Test divide by zero
    assert_raise RuntimeError do
      @calc.div(0)
    end

    # Test divide by 1
    @calc.add(64.0)
    assert_equal 64.0, @calc.div(1)

    # Test positive
    assert_equal 8.0, @calc.div(8)

    # Test negative
    assert_equal -4.0, @calc.div(-2)
  end

end
us-2
us-2 charvey$ bzr add
adding src/calc.rb
adding test/calc_test.rb
us-2 charvey$ bzr commit -m"Created empty Calc class and unit tests against all basic arithmetic functions"
Committing to: /home/charvey/project/us-2/
added src/calc.rb
added test/calc_test.rb
Committed revision 2.

This user story is larger than the others, and to implement it we need to lay the foundation of the Calc class. We decide that we should collaborate on this story.

So far, we've worked in isolation, segregating our work into local feature branches that no one else on the team can see. Sometimes working alone is exactly what we want, because we want to be able to commit changes without affecting anyone else. In this case, we want to collaborate with Charlie, so how do we do that?

One option is Charlie could land the us-2 branch, as-is, into the iteration-1 branch. This isn't a good idea for number of reasons. One is that the iteration-1 branch is just a container for complete user stories, not a location where we do work. Another problem is that Charlie has code committed in a state where his tests fail. This is a normal step in test driven development, but if he merges the code in this state into iteration-1, continuous integration will fail and he will break the build. The iteration-1 branch should always contain finshed, working code.

No one wants to break the build or expose team members to half-baked code. It's fine to have code not working at various points in your feature branch (you should be able to commit as often as you like), but that's because the work in that branch doesn't impact the entire team every time you commit.

There's a simple answer— both of us will work in the us-2 branch. We'll ask Charlie to get up-to-date with the user story we've landed in iteration-1 and to push a copy of his merged work to the server.

us-2
us-2 charvey$ cd ../iteration-1/
iteration-1 charvey$ bzr pull
Using saved parent location: bzr+ssh://dev.example.com/srv/project/iteration-1/
 M  README.txt
All changes applied successfully.
Now on revision 2.
iteration-1 charvey$ cd ../us-2/
us-2 charvey$ bzr merge ../iteration-1/
bzr merge ../iteration-1/
 M  README.txt
All changes applied successfully.
us-2 charvey$ bzr commit -m"get up-to-date with iteration-1"
Committing to: /home/charvey/project/us-2/
modified README.txt
Committed revision 3.
us-2 charvey$ bzr push bzr+ssh://dev.example.com/srv/project/us-2
Created new branch.

To update his feature branch with the latest revisions from iteration-1, Charlie pulls changes from the server to his iteration-1 mirror, then he merges those changes into the us-2 branch. Getting up-to-date with a branch like iteration-1, which contains the team's collective work, is a good idea because this will ensure that you're integrating your work with the rest of the team as soon as is possible. You could pull changes frequently from iteration-1 or you could just wait for a teammate to announce when they have landed completed stories.

Once he's up-to-date, Charlie pushes a copy of us-2 to the server so we can access it.

We decide that Charlie is going to add methods for multiplication and division, and we'll add methods for add and subtract. First we'll get a copy of the us-2 branch:

project
project fmccann$ bzr branch bzr+ssh://dev.example.com/srv/project/us-2
Branched 3 revisions.
project fmccann$ cd us-2/

Next we'll add our changes to make add and subtract work:

calc.rb
class Calc

end
calc.rb
require 'bigdecimal'

class Calc

  def initialize
    @acc = bd(0)
  end

  def add(val)
    @acc += bd(val)
    @acc.to_f
  end

  def sub(val)
    @acc -= bd(val)
    @acc.to_f
  end

  private

  def bd(val)
    BigDecimal.new(val.to_s)
  end

end

Then we'll push our changes so Charlie can see them:

us-2
us-2 fmccann$ bzr commit -m"Implemented add/subtract"
Committing to: /home/fmccann/project/us-2/
modified src/calc.rb
Committed revision 4.
us-2 fmccann$ bzr push bzr+ssh://dev.example.com/srv/project/us-2
Pushed up to revision 4.

Charlie will pull our changes and implement multiply and divide to finish the user story.

us-2
us-2 charvey$ bzr pull bzr+ssh://dev.example.com/srv/project/us-2
 M  src/calc.rb
All changes applied successfully.
Now on revision 4.
calc.rb
require 'bigdecimal'

class Calc

  def initialize
    @acc = bd(0)
  end

  def add(val)
    @acc += bd(val)
    @acc.to_f
  end

  def sub(val)
    @acc -= bd(val)
    @acc.to_f
  end

  private

  def bd(val)
    BigDecimal.new(val.to_s)
  end

end
calc.rb
require 'bigdecimal'

class Calc

  def initialize
    @acc = bd(0)
  end

  ...

  def mul(val)
    @acc *= bd(val)
    @acc.to_f
  end

  def div(val)
    raise "Division by zero" if val.to_f == 0.0
    @acc /= bd(val)
    @acc.to_f
  end

  private

  def bd(val)
    BigDecimal.new(val.to_s)
  end

end

Charlie will commit his changes after running the tests one more time to make sure everything is working.

us-2
us-2 charvey$ ruby test/calc_test.rb
4 tests, 16 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
us-2 charvey$ bzr commit -m"Implemented multiply and divide"
Committing to: /home/charvey/project/us-2/
modified src/calc.rb
Committed revision 5.

Now that user story #2 is complete and all the tests pass, Charlie will land the story into the iteration-1 branch.

us-2
us-2 charvey$ cd ../iteration-1/
iteration-1 charvey$ bzr pull
Using saved parent location: bzr+ssh://dev.example.com/srv/project/iteration-1/
No revisions or tags to pull.
iteration-1 charvey$ bzr merge ../us-2/
+N  src/calc.rb
+N  test/calc_test.rb
All changes applied successfully.
iteration-1 charvey$ bzr commit -m"US-2: Implement basic arithmetic functions"
Committing to: /home/charvey/project/iteration-1/
added src/calc.rb
added test/calc_test.rb
Committed revision 3.
iteration-1 charvey$ bzr push bzr+ssh://dev.example.com/srv/project/iteration-1
Pushed up to revision 3.

User story #2 is now added to our completed work for this iteration.

We use the same techniques to share our work with feature branches as we do with the team's shared iteration branch.

Bazaar makes branching trivial and sharing easy, so we can use as many branches for as many purposes as we'd like.

Simplifying Mirrors with Checkouts

We've arranged things so that our iteration-1 branch is merely a container for user story branches. All actual work is done in a user story branch, not in iteration-1. The only thing we ever do in iteration-1 is merge in user stories.

This may seem odd, but there's a reason why we're taking this approach. Remember that history in Bazaar is relative to the branch. When we take a look at the history of the iteration-1 branch, this is what we see:

iteration-1
iteration-1 fmccann$ bzr log
------------------------------------------------------------
revno: 3 [merge]
committer: Charlie Harvey <charlie@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 14:54:45 -0500
message:
  US-2: Implement basic arithmetic functions
------------------------------------------------------------
revno: 2 [merge]
committer: Fred McCann <fred@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 13:08:23 -0500
message:
  US-1: Update readme file
------------------------------------------------------------
revno: 1
committer: Fred McCann <fred@bzrinit.com>
branch nick: trunk
timestamp: Sat 2015-12-21 17:28:14 -0500
message:
  initial import
------------------------------------------------------------
Use --include-merged or -n0 to see merged revisions.

The only entries we see in the log are complete user stories. This is the correct level of detail when we talk about the iteration's history. All we really care about is what user stories were completed and when they were added. We don't care when we implemented add and subtract while building user story #2.

Of course, if we did want to see all the details of how user story 2 (revision 3) was implemented, we can take advantage of Bazaar's nested logs:

iteration-1
iteration-1 fmccann$ bzr log -r3 -n0
revno: 3 [merge]
committer: Charlie Harvey <charlie@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 14:54:45 -0500
message:
  US-2: Implement basic arithmetic functions
    ------------------------------------------------------------
    revno: 1.2.4
    committer: Charlie Harvey <charlie@bzrinit.com>
    branch nick: us-2
    timestamp: Sun 2015-12-22 14:44:30 -0500
    message:
      Implemented multiply and divide
    ------------------------------------------------------------
    revno: 1.2.3
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: us-2
    timestamp: Sun 2015-12-22 14:31:44 -0500
    message:
      Implemented add/subtract
    ------------------------------------------------------------
    revno: 1.2.2 [merge]
    committer: Charlie Harvey <charlie@bzrinit.com>
    branch nick: us-2
    timestamp: Sun 2015-12-22 14:08:00 -0500
    message:
      get up-to-date with iteration-1
    ------------------------------------------------------------
    revno: 1.2.1
    committer: Charlie Harvey <charlie@bzrinit.com>
    branch nick: us-2
    timestamp: Sun 2015-12-22 13:28:30 -0500
    message:
      Created empty Calc class and unit tests against all basic arithmetic functions

We can exploit these features in Bazaar to capture our project history in way that's very easy to understand months and years from now while still capturing the finest level of detail. It's easy to understand because Bazaar is flexible enough to capture our work in the same fashion that we think about and plan our work on an agile team.

There is another refinement we can make to take some of the friction out of maintaining a "container" branch like iteration-1. This is our current method for landing a completed user story into an iteration branch:

  1. Use bzr pull to make sure our local mirror of the iteration branch agrees with the server's copy.
  2. Use bzr merge to merge the changes from the user story branch into the iteration branch.
  3. Call bzr commit to record the merge, adding a commit message describing the story.
  4. Use bzr push to make sure our server's copy of the iteration branch agrees with our local copy.

The model we're using is pull/merge/commit/push. We pull before merging because we always want our local mirror to agree with the server. This is different from how we treat our user story branches. We expect user story branches to be different. Even when we were collaborating on user story #2, it's a normal condition for multiple developers to diverge as they work in local copies of a feature branch because we're adding new revisions as we work.

Also, we don't expect developers will push after every commit they make to a user story branch. Developers will work independently for as long as they need before sharing work. These delays in sharing new revisions is what causes user story branches to diverge.

Nobody works directly in a "container" branch, and we never expect anyone to ever diverge. Put simply, everyone expects to agree on what's in a container branch like our iteration-1 branch.

With that in mind, the pull/push steps are essentially clutter. We always want this branch to be in sync.

There's another problem with our push/pull steps, and that's what happens when two people are trying to merge into an iteration branch at the same time.

Consider this situation— we've finished work on user story #4 and Charlie has finished user story #3. We start merging our work into our mirror of iteration-1:

iteration-1
iteration-1 fmccann$ bzr pull bzr+ssh://dev.example.com/srv/project/iteration-1
No revisions or tags to pull.
iteration-1 fmccann$ bzr merge ../us-4
 M  src/calc.rb
 M  test/calc_test.rb
All changes applied successfully.
iteration-1 fmccann$ bzr commit -m"US-4: Implement square root"
Committing to: /home/fmccann/project/iteration-1/
modified src/calc.rb
modified test/calc_test.rb
Committed revision 4.

We've gotten as far as committing our merge into iteration-1 but we haven't pushed the revisions to the server yet. At the same time we were doing this, Charlie was merging his completed user story into his mirror of iteration-1:

iteration-1
iteration-1 charvey$ bzr pull bzr+ssh://dev.example.com/srv/project/iteration-1
No revisions or tags to pull.
iteration-1 charvey$ bzr merge ../us-3
 M  src/calc.rb
 M  test/calc_test.rb
All changes applied successfully.
iteration-1 charvey$ bzr commit -m"US-3: Implement methods to get value of accumulator and clear accumulator"
Committing to: /home/charvey/project/iteration-1/
modified src/calc.rb
modified test/calc_test.rb
Committed revision 4.
iteration-1 charvey$ bzr push bzr+ssh://dev.example.com/srv/project/iteration-1
All changes applied successfully.
Pushed up to revision 4.

Charlie beat us to the push! When we try to push our work to the server, this is what we get:

iteration-1
iteration-1 fmccann$ bzr push bzr+ssh://dev.example.com/srv/project/iteration-1
bzr: ERROR: These branches have diverged. See "bzr help diverged-branches" for more
information.

Our branches diverged— exactly what we never want or expect to happen with a mirrored "container" branch like iteration-1. This is because the state of iteration-1 on the server changed between the time we did the pull and the push, so when we pushed, it failed.

This isn't the end of the world, but it's messy because we've already committed a new revision to our local copy of iteration-1. We could just merge the remote state of revision-1 with ours, but then our iteration-1 log would look like this:

iteration-1
iteration-1 fmccann$ bzr merge bzr+ssh://dev.example.com/srv/project/iteration-1
 M  src/calc.rb
 M  test/calc_test.rb
Text conflict in test/calc_test.rb
1 conflicts encountered.
iteration-1 fmccann$ edit test/calc_test.rb
iteration-1 fmccann$ bzr resolve test/calc_test.rb
1 conflict resolved, 0 remaining
iteration-1 fmccann$ bzr commit -m"fix conflicts with us-4"
Committing to: /home/fmccann/project/iteration-1/
modified src/calc.rb
modified test/calc_test.rb
Committed revision 5.
iteration-1 fmccann$ bzr log -n2
------------------------------------------------------------
revno: 5 [merge]
committer: Fred McCann <fred@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 17:48:52 -0500
message:
  fix conflicts with us-4
    ------------------------------------------------------------
    revno: 3.3.1 [merge]
    committer: Charlie Harvey <charlie@bzrinit.com>
    branch nick: iteration-1
    timestamp: Sun 2015-12-22 17:33:59 -0500
    message:
      US-3: Implement methods to get value of accumulator and clear accumulator
------------------------------------------------------------
revno: 4 [merge]
committer: Fred McCann <fred@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 17:33:35 -0500
message:
  US-4: Implement square root
    ------------------------------------------------------------
    revno: 3.1.1
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: us-4
    timestamp: Sun 2015-12-22 17:22:53 -0500
    message:
      add a sqrt function to the Calc class
...

(I've skipped showing how we resolved the conflict)

Now we've got a meaningless merge that not only doesn't convey useful information, it's swallowed Charlie's us-3 merge. This is exactly why we don't do development directly in a container branch— it's messy.

We have other options. We could try to rollback our commit of the merge revision (more on this in Tips and Tricks). Another option would be to delete our copy of iteration-1 and start with a fresh copy based on the server's version.

You could also elect someone on the team to be the only person allowed to merge user stories into the iteration branch, but this isn't exactly agile. On an agile team there's no code ownership and everyone should be able to commit their work without having any single team member as a bottleneck.

What we really want is some guarantee that branches like iteration-1 are always in sync. Lucky for us, Bazaar supports just that. We can bind our local mirror of the iteration-1 branch to the remote branch. Let's go back in time and start over with Charlie merging in user story #4.

Binds this branch to another branch, turning this branch into a checkout of the other.
iteration-1
iteration-1 charvey$ bzr bind bzr+ssh://dev.example.com/srv/project/iteration-1
Checkout
A branch that is bound to another branch. A checkout acts as though its working tree is based on the branch it's bound to, not it's local branch. The local branch acts as a dependent cache of the other branch.

This binds Charlie's mirror of the iteration-1 branch to the remote copy of iteration-1. Bazaar refers to bound branches as "checkouts". A working tree in a bound branch behaves as though it's related to the upstream branch instead of the local branch. The local branch acts essentially as a cache to make certain operations happen quickly.

The working tree in a checkout works as though it's wired to the remote branch. This means that as soon as you commit a change, it's immediately sent to the remote branch— there's no need to push. It also means you never have to pull changes from the remote branch either. Since the working tree acts as though it's plugged into the remote branch, you simply call bzr update to update the working tree to the latest revision. (Refer to Bazaar from the Ground Up for a refresher on bzr update)

Now that Charlie's branch is a checkout, he'll merge in his work:

iteration-1
iteration-1 charvey$ bzr update
Tree is up-to-date at revision 3 of
branch bzr+ssh://dev.example.com/srv/project/iteration-1
iteration-1 charvey$ bzr merge ../us-3
 M  src/calc.rb
 M  test/calc_test.rb
All changes applied successfully.
iteration-1 charvey$ bzr commit -m"US-3: Implement methods to get value of accumulator and clear accumulator"
Committing to: bzr+ssh://dev.example.com/srv/project/iteration-1
modified src/calc.rb
modified test/calc_test.rb
Committed revision 4.
Turns a checkout into a normal branch.

This is simpler than pull/merge/commit/push. Charlie only has to make sure he's up-to-date, merge his change, and commit. As soon as he commits, the changes are sent to the server. If Charlie ever wanted to change his checkout back to an ordinary branch, he could call bzr unbind.

Now, let's take a look at our scenario. Just as before, we were attempting to merge user story #4 at the exact same time that Charlie was attempting to merge user story #3.

iteration-1
iteration-1 fmccann$ bzr bind bzr+ssh://dev.example.com/srv/project/iteration-1
iteration-1 fmccann$ bzr update
Tree is up-to-date at revision 3 of
branch bzr+ssh://dev.example.com/srv/project/iteration-1
iteration-1 fmccann$ bzr merge ../us-4/
 M  src/calc.rb
 M  test/calc_test.rb
All changes applied successfully.
iteration-1 fmccann$ bzr commit -m"US-4: Implement square root"
bzr: ERROR: Bound branch BzrBranch7(file:///home/fmccann/project/iteration-1/) is out
  of date with master branch
  BzrBranch7(bzr+ssh://dev.example.com/srv/project/iteration-1).
To commit to master branch, run update and then commit.
You can also pass --local to commit to continue working disconnected.

We have the same problem as before— the remote branch has changed between us calling bzr update and bzr commit. What's different than before is that we haven't committed anything yet. Bazaar won't let us commit anything to our checkout that would make our copy diverge from the server's copy of the branch.

This is a much easier situation to deal with. We can simply cancel the merge by calling bzr revert.

iteration-1
iteration-1 fmccann$ bzr revert
 M  src/calc.rb
 M  test/calc_test.rb

Now that our checkout of iteration-1 is clean, we can go back to the us-4 branch and merge in Charlie's changes (I won't show the details of resolving the conflict):

iteration-1
iteration-1 fmccann$ cd ../us-4/
us-4 fmccann$ bzr merge ../iteration-1/
 M  src/calc.rb
 M  test/calc_test.rb
Text conflict in test/calc_test.rb
1 conflicts encountered.
us-4 fmccann$ edit test/calc_test.rb
us-4 fmccann$ ruby test/calc_test.rb
7 tests, 21 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
us-4 fmccann$ bzr resolve test/calc_test.rb
1 conflict resolved, 0 remaining
us-4 fmccann$ bzr commit -m"fix conflict with us-3"
Committing to: /home/fmccann/project/us-4/
modified src/calc.rb
modified test/calc_test.rb
Committed revision 5.

Now that we have Charlie's us-3 revisions merged in, let's try merging us-4 into the iteration branch again:

us-4
us-4 fmccann$ cd ../iteration-1/
iteration-1 fmccann$ bzr merge ../us-4/
 M  src/calc.rb
 M  test/calc_test.rb
All changes applied successfully.
iteration-1 fmccann$ bzr commit -m"US-4: Implement square root"
Committing to: bzr+ssh://dev.example.com/srv/project/iteration-1
modified src/calc.rb
modified test/calc_test.rb
Committed revision 5.

Done! Since the checkout doesn't let us commit any changes that would make us diverge, we had no problems reverting our merge, fixing problems in the us-4 branch, then merging the fixed branch into iteration-1. We also didn't have to manually pull/push any revisions to the server.

Let's check the log now:

iteration-1
iteration-1 fmccann$ bzr log
------------------------------------------------------------
revno: 5 [merge]
committer: Fred McCann <fred@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 18:11:08 -0500
message:
  US-4: Implement square root
------------------------------------------------------------
revno: 4 [merge]
committer: Charlie Harvey <charlie@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 18:04:28 -0500
message:
  US-3: Implement methods to get value of accumulator and clear accumulator
------------------------------------------------------------
revno: 3 [merge]
committer: Charlie Harvey <charlie@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 14:54:45 -0500
message:
  US-2: Implement basic arithmetic functions
...
iteration-1 fmccann$ bzr log -r5 -n0
revno: 5 [merge]
committer: Fred McCann <fred@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 18:11:08 -0500
message:
  US-4: Implement square root
    ------------------------------------------------------------
    revno: 3.2.2 [merge]
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: us-4
    timestamp: Sun 2015-12-22 18:10:40 -0500
    message:
      fix conflict with us-3
    ------------------------------------------------------------
    revno: 3.2.1
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: us-4
    timestamp: Sun 2015-12-22 17:22:53 -0500
    message:
      add a sqrt function to the Calc class

Perfect. We've not only simplified the process of merging user story branches into the iteration branch with checkouts, we have a guarantee that everyone always agrees on the state of iteration branches.

Using checkouts, landing a user story brach is as easy as:

  1. Call bzr update to get up-to-date.
  2. Merge the user story branch with bzr merge.
  3. Commit the merge with bzr commit.

Finishing the Iteration

Now that we've implemented all of the user stories in our iteration, we're going to merge the iteration into the trunk. Just as the iteration branch is a container for user story branches, the trunk branch will act as a container for iterations.

Charlie is going to merge the iteration-1 branch into trunk. Since he doesn't have a copy of the trunk branch, he's going to make one based on what's on the server. He'd like this to be a checkout, just like his iteration-1 branch. Rather than using bzr branch to make a copy then calling bzr bind to turn it into a checkout, he can do both steps with the command bzr checkout:

Creates a branch that is tightly bound to another branch, known as a "checkout". This also creates a repository to store the branch if one does not already exist.
project
project charvey$ bzr checkout bzr+ssh://dev.example.com/srv/project/trunk
project charvey$ cd trunk/
trunk charvey$ bzr merge ../iteration-1/
+N  src/calc.rb
+N  test/calc_test.rb
 M  README.txt
All changes applied successfully.
trunk charvey$ bzr commit -m"Iteration 1"
Committing to: bzr+ssh://dev.example.com/srv/project/trunk
modified README.txt
added src/calc.rb
added test/calc_test.rb
Committed revision 2.

Remember, all history is relative. From the trunk branch's perspective, history is a collection of iterations:

trunk
trunk charvey$ bzr log
------------------------------------------------------------
revno: 2 [merge]
committer: Charlie Harvey <charlie@bzrinit.com>
branch nick: trunk
timestamp: Sun 2015-12-22 20:27:59 -0500
message:
  Iteration 1
------------------------------------------------------------
revno: 1
committer: Fred McCann <fred@bzrinit.com>
branch nick: trunk
timestamp: Sat 2015-12-21 17:28:14 -0500
message:
  initial import
------------------------------------------------------------
Use --include-merged or -n0 to see merged revisions.

... and we can examine the user stories committed in an iteration:

trunk
trunk charvey$ bzr log -r2 -n2
------------------------------------------------------------
revno: 2 [merge]
committer: Charlie Harvey <charlie@bzrinit.com>
branch nick: trunk
timestamp: Sun 2015-12-22 20:27:59 -0500
message:
  Iteration 1
    ------------------------------------------------------------
    revno: 1.2.4 [merge]
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: iteration-1
    timestamp: Sun 2015-12-22 18:11:08 -0500
    message:
      US-4: Implement square root
    ------------------------------------------------------------
    revno: 1.2.3 [merge]
    committer: Charlie Harvey <charlie@bzrinit.com>
    branch nick: iteration-1
    timestamp: Sun 2015-12-22 18:04:28 -0500
    message:
      US-3: Implement methods to get value of accumulator and clear accumulator
    ------------------------------------------------------------
    revno: 1.2.2 [merge]
    committer: Charlie Harvey <charlie@bzrinit.com>
    branch nick: iteration-1
    timestamp: Sun 2015-12-22 14:54:45 -0500
    message:
      US-2: Implement basic arithmetic functions
    ------------------------------------------------------------
    revno: 1.2.1 [merge]
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: iteration-1
    timestamp: Sun 2015-12-22 13:08:23 -0500
    message:
      US-1: Update readme file

... and we can dig into the details of any story:

trunk
trunk charvey$ bzr log -r1.2.4 -n0
------------------------------------------------------------
revno: 1.2.4 [merge]
committer: Fred McCann <fred@bzrinit.com>
branch nick: iteration-1
timestamp: Sun 2015-12-22 18:11:08 -0500
message:
  US-4: Implement square root
    ------------------------------------------------------------
    revno: 1.5.2 [merge]
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: us-4
    timestamp: Sun 2015-12-22 18:10:40 -0500
    message:
      fix conflict with us-3
    ------------------------------------------------------------
    revno: 1.5.1
    committer: Fred McCann <fred@bzrinit.com>
    branch nick: us-4
    timestamp: Sun 2015-12-22 17:22:53 -0500
    message:
      add a sqrt function to the Calc class

Review

What I've shown is not the official way of working with agile projects with Bazaar or even the only way. What you should take away from this part of the tutorial is that you can exploit Bazaar's high quality merge tracking to commemorate merging one branch into another, and use it's nested logs to ensure you only ever see the level of detail that makes sense when viewing project history.

You may find that simply merging user stories into the trunk without an intermediate iteration branch makes more sense. For simple projects, you might just work in trunk directly, forgoing user story branches. Maybe you need even more levels of nesting than presented here.

Bazaar is not opinionated about how you collaborate. Start with the process you have, and investigate how Bazaar can best record it. Bazaar makes branching simple, sharing branches easy, and it faithfully records whatever branch structure you want to use to match the conceptual model of your development methodology.

I didn't introduce a lot of new commands this time, but you now know how to:

  • Create a shared repository to hold multiple branches.
  • Create branches without working trees.
  • When to merge and when to push/pull.
  • How to work with checkouts and how to use them to simplify working with mirrors of branches.
  • How to arrange branches in your project and use nested history so your logs always show the appropriate level of detail.
Next: Development Pipeline