In this chapter, we will look under the hood, and explore a few interesting ways in which you can interact with Bazaar programmatically. This chapter assumes that you have a working knowledge of the Python programming language.
We will start with a quick introduction of the basics—how the main objects of version control are represented in Bazaar, and how to use them. This will enable you to manipulate Bazaar programmatically and provide the essential knowledge to write plugins.
Next, we will explain the details of writing plugins. Plugins are very powerful, and are the standard way to hook into Bazaar's architecture and extend Bazaar in various ways, such as adding new commands, modifying existing commands, or even completely replacing existing commands. Plugins can also be used to implement hooks that can be triggered by various steps in version control operations.
The following topics will be covered in this chapter:
The core functionality of Bazaar is implemented within the bzrlib
Python package. A detailed study of Bazaar's architecture is beyond the scope of this book. Instead, we will take a pragmatic approach and show you, through examples, how to access the main objects of version control in Bazaar and other important tips.
The main goal of this chapter is to teach you enough to be able to create your own plugins and make simple modifications to Bazaar's behavior to better suit your needs.
If you would like to know more about Bazaar's internals, this overview should be a good starting point:
http://doc.bazaar.canonical.com/developers/overview.html
When using bzrlib
within bzr
, for example in plugins, the library is already initialized. To ensure that bzrlib
functions correctly when using it outside of bzr
, for example in your custom scripts, it must be initialized as follows:
>>> import bzrlib >>> bzrlib.initialize()
Additionally, if you want to use a functionality that is implemented in plugins, for example working with branches on Launchpad, then you must load the plugins manually, as follows:
>>> bzrlib.plugin.load_plugins()
This should be a fast operation, as plugins normally use lazy initialization so that their main implementation is only loaded when really used.
This example loads all the plugins at the default plugin path locations in the same way as they are loaded when using the bzr
command. Optionally, you can pass to the function a list of paths to limit the plugin discovery process to the specified locations.
When implementing plugins or trying to do simple operations in Bazaar, it can be difficult to find the right modules to access the right objects, to get the information you need. The aim of this section is to show a couple of examples for accessing various objects of Bazaar's version control model.
The main classes and methods that will be demonstrated are as follows:
bzrlib.branch.Branch
bzrlib.config.BranchConfig
bzrlib.revision.Revision
bzrlib.revisiontree.RevisionTree
bzrlib.log.LongLogFormatter
We will demonstrate various methods for accessing Bazaar's objects using the branch:
lp:~bzrbook/bzrbook-examples/bzr-summary
You can follow the same steps as in the examples by preparing a local branch, as follows:
$ bzr branch lp:~bzrbook/bzrbook-examples/bzr-summary /tmp/summary -r20 $ cd /tmp/summary
We used the specific revision 20 to match with the operations in the examples.
A branch is one of the most important objects in Bazaar. The class to work with branches is named Branch
in the bzrlib.branch
module. You can open a local branch by specifying its path in the filesystem as follows:
>>> from bzrlib.branch import Branch >>> branch = Branch.open('.')
In this example, we specified ".
" as the path, meaning the current directory.
You can open remote branches in the same way, however, if the protocol is implemented in a plugin such as lp:
for branches on Launchpad, then you must load the required plugins before using this method.
A Branch
object has several interesting methods and attributes, such as the following:
repository
: This is the Bazaar repository associated with the branch, as a CHKInventoryRepository
objectrevno()
:This returns the last revision number, as an integerlast_revision()
:This returns the last revision ID, as a stringcontrol_url
: This is the path to the .bzr/branch
directory of the branch, as a stringget_config()
:This returns the branch configuration data, as a BranchConfig
objectThe class to work with branch configuration data is named BranchConfig
in the bzrlib.config
module. An easy way to access the configuration of a branch is by opening the branch and then using the get_config()
method on it. For example:
>>> from bzrlib.branch import Branch >>> branch = Branch.open('.') >>> config = branch.get_config()
This is especially useful for accessing the key-value properties in the .bzr/branch/branch.conf
file, as follows:
>>> config.get_user_option('parent_location') u'bzr+ssh://bazaar.launchpad.net/~bzrbook/bzrbook-examples/bzr-summary/'
If the specified configuration variable does not exist, the method returns None.
The class to work with the revision history is named Revision
in the
bzrlib.revision
module. An easy way to access the revisions is by opening a branch and then using the get_revision()
method on its associated repository. For example:
>>> from bzrlib.branch import Branch >>> branch = Branch.open('.') >>> rev_id = branch.last_revision() >>> revision = branch.repository.get_revision(rev_id)
A Revision
object has several interesting methods to access the revision information. For example:
The class to work with the content of files and the shape of the tree of revisions is named RevisionTree
in the bzrlib.revisiontree
module. An easy way to get a RevisionTree
object is from a branch and a revision ID. For example:
>>> from bzrlib.branch import Branch >>> branch = Branch.open('.') >>> rev_id = branch.last_revision() >>> tree = branch.repository.revision_tree(rev_id)
An easy way to list files in the tree is by using the iter_entries_by_dir
method. For example:
>>> tree.lock_read() <InventoryRevisionTree instance at 1019ac7d0, rev_id='janos@axiom-20130105162648-iwv0yb9o5etwyvzh'> >>> iter = tree.iter_entries_by_dir() >>> print iter.next() (u'', CHKInventoryDirectory('tree_root-20121223122411-46c0o678h271d3jk-1', u'', parent_id=None, revision='janos@axiom-20121223160417-9uz9ynbehy0il02t')) >>> print iter.next() (u'README', InventoryFile('readme-20121223154742-2ymrdwwoa9j1wva0-1', u'README', parent_id='tree_root-20121223122411-46c0o678h271d3jk-1', sha1='dd28845af2cdeb1b56bd67b34c4823533405d654', len=764, revision=janos@axiom-20121230121603-50u69s9ch8o7eg41)) >>> print iter.next() (u'__init__.py', InventoryFile('__init__.py-20121230094916-m7i3mv0mikwdipb9-1', u'__init__.py', parent_id='tree_root-20121223122411-46c0o678h271d3jk-1', sha1='285dce023ba62f899b592543b6627cc5a27c9341', len=761, revision=janos@axiom-20130105162648-iwv0yb9o5etwyvzh)) >>> tree.unlock()
In order to iterate over the entries in the tree, we must first lock the tree object. Each iteration returns a tuple of two elements:
CHKInventoryDirectory
object in case of a directory, and an InventoryFile
object in case of a fileThe first entry is the root directory of the
project, thus its relative path is an empty string, and it is a CHKInventoryDirectory
object. The ordering of entries is the same as in the output of the bzr ls
command:
$ bzr ls --show-ids README readme-20121223154742-2ymrdwwoa9j1wva0-1 __init__.py __init__.py-20121230094916-m7i3mv0mikwdipb9-1 cmd_summary.py cmd_summary.py-20121230115024-py0vs3wj3oqux5z2-1 setup.py setup.py-20121230120402-zfu8im6iax17fl7r-1 tests/ tests-20121230123000-bh7lacxmlglvq30b-1
The inventory object contains very important information, such as the file ID, which can be used to access file content.
By using a RevisionTree
object and the file ID, you can access the contents of files using the get_file(file_id)
method. For example:
>>> print tree.get_file('readme-20130108195909-jmwgut5e1y6x608x-1').readlines()
The get_file
method returns a file-like object. In this example, we used the readline()
method to print the list of lines in the file, omitting the actual output for brevity.
The classes handling the formatting
of the revision information are derived from the LogFormatter
class in the bzrlib.log
module. For each log format that you can use on the command line, there exists a different implementation of the LogFormatter
class, for example, the default long format is handled by LongLogFormatter
.
Formatting revision information using a log formatter involves the following steps:
Revision
object of the revision you want to format.LogRevision
object by using the Revision
object and the revision number.LogRevision
object.For example, you can format the last revision and print to the standard output by using the long log formatter, as follows:
>>> from bzrlib.branch import Branch >>> branch = Branch.open('.') >>> from bzrlib.log import LongLogFormatter, LogRevision >>> revno, rev_id = branch.last_revision_info() >>> revision = branch.repository.get_revision(rev_id) >>> log_revision = LogRevision(rev=revision, revno=revno) >>> from sys import stdout >>> formatter = LongLogFormatter(stdout) >>> formatter.log_revision(log_revision) ------------------------------------------------------------ revno: 20 committer: Janos Gyerik <janos@axiom> branch nick: summary timestamp: Sat 2013-01-05 17:26:48 +0100 message:
The preceding command made the plugin docstring
multiline. The output is identical to the output of the command bzr log -r20 --long
.
You will find more examples and practical tips at http://doc.bazaar.canonical.com/developers/integration.html.
Throughout the chapter, we will make references to the location BZRLIB
. By that we will mean always the base path of the bzrlib
Python package, as it was created during the installation of Bazaar. You can find this directory in the output of bzr version
. For example:
$ bzr version Bazaar (bzr) 2.5.0 Python interpreter: /usr/bin/python2.6 2.6.1 Python standard library: /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6 Platform: Darwin-10.8.0-i386-64bit bzrlib: /Library/Python/2.6/site-packages/bzrlib Bazaar configuration: /Users/janos/.bazaar Bazaar log file: /Users/janos/.bzr.log
In this example, the path of BZRLIB
is /Library/Python/2.6/site-packages/bzrlib
, from the line that contains "bzrlib:"
3.22.71.28