1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
|
=======================
Integrating with Breezy
=======================
This document provides some general observations on integrating with
Breezy and some recipes for typical tasks. It is intended to be useful to
someone developing either a plugin or some other piece of software that
integrates with brz. If you want to know about a topic that's not covered
here, just ask us.
Starting with breezy
====================
Within brz
----------
When using breezy within the ``brz`` program (for instance as a brz
plugin), breezy's global state is already available for use.
From outside brz
----------------
To use breezy outside of ``brz`` some global state needs to be setup.
breezy needs ways to handle user input, passwords, a place to emit
progress bars, logging setup appropriately for your program. The easiest
way to set all this up in the same fashion ``brz`` does is to call
``breezy.initialize``.
This returns a context manager within which breezy functions will work
correctly. See the pydoc for ``breezy.initialize`` for more information.
(You can get away without entering the context manager, because the setup
work happens directly from ``initialize``.)
Running brz commands
====================
To run command-line commands in-process::
from breezy.commands import get_command
cmd = get_command('version')
cmd.run([])
This will send output through the current UIFactory; you can redirect this
elsewhere through the parameters to `breezy.initialize`.
Load the ``bzr`` and/or ``git`` module(s)
=========================================
The code below have the prerequisite that you have imported either of
the ``bzr`` or ``git`` module, or both::
import breezy.bzr
import breezy.git
Depending on what the code is intended to do, the corresponding module or
modules need to be imported (``bzr`` for Bazaar, and ``git`` for git), or some
of the code will not work, and will cause errors like
``breezy.errors.NotBranchError``.
Manipulating the Working Tree
=============================
Most objects in Breezy are in files, named after the class they contain.
To manipulate the Working Tree we need a valid WorkingTree object, which
is loaded from the workingtree.py file, eg::
from breezy import workingtree
wt = workingtree.WorkingTree.open('/home/jebw/brztest')
This gives us a WorkingTree object, which has various methods spread over
itself, and its parent classes MutableTree and Tree - it's worth having a
look through these three files (workingtree.py, mutabletree.py and tree.py)
to see which methods are available.
Compare trees
-------------
There are two methods for comparing trees: ``changes_from`` and
``iter_changes``. ``iter_changes`` is more regular and precise, but it is
somewhat harder to work with. See the API documentation for more details.
``changes_from`` creates a Delta object showing changes::
from breezy import delta
changes = wt.changes_from(wt.basis_tree())
This gives us a Delta object, which has several lists of files for each type of
change, eg changes.added is a list of added files, changes.removed is list
of removed files, changes.modified is a list of modified files. The contents
of the lists aren't just filenames, but include other information as well.
To grab just the filename we want the first value, eg::
print("list of newly added files")
for filename in changes.added:
print("%s has been added" % filename[0])
The exception to this is changes.renamed, where the list returned for each
renamed files contains both the old and new names -- one or both may interest
you, depending on what you're doing.
For example::
print("list of renamed files")
for filename in changes.renamed:
print("%s has been renamed to %s" % (filename[0], filename[1]))
Adding Files
------------
If you want to add files the same way ``brz add`` does, you can use
MutableTree.smart_add. By default, this is recursive. Paths can either be
absolute or relative to the workingtree::
wt.smart_add(['dir1/filea.txt', 'fileb.txt',
'/home/jebw/brztesttree/filec.txt'])
For more precise control over which files to add, use MutableTree.add::
wt.add(['dir1/filea.txt', 'fileb.txt', '/home/jebw/brztesttree/filec.txt'])
Removing Files
--------------
You can remove multiple files at once. The file paths need to be relative
to the workingtree::
wt.remove(['filea.txt', 'fileb.txt', 'dir1'])
By default, the files are not deleted, just removed from the inventory.
To delete them from the filesystem as well::
wt.remove(['filea.txt', 'fileb.txt', 'dir1'], keep_files=False)
Renaming a File
---------------
You can rename one file to a different name using WorkingTree.rename_one.
You just provide the old and new names, eg::
wt.rename_one('oldfile.txt','newfile.txt')
Moving Files
------------
You can move multiple files from one directory into another using
WorkingTree.move::
wt.move(['olddir/file.txt'], 'newdir')
More complicated renames/moves can be done with transform.TreeTransform,
which is outside the scope of this document.
Committing Changes
------------------
To commit _all_ the changes to our working tree we can just call the
WorkingTree's commit method, giving it a commit message, eg::
wt.commit('this is my commit message')
To commit only certain files, we need to provide a list of filenames which we
want committing, eg::
wt.commit(message='this is my commit message', specific_files=['fileA.txt',
'dir2/fileB.txt', 'fileD.txt'])
Generating a Log for a File
===========================
Generating a log is, in itself, simple. Grab a branch (see below) and pass
it to show_log together with a log formatter, eg::
from breezy import log
from breezy import branch
b = branch.Branch.open('/path/to/bazaar/branch')
lf = log.LongLogFormatter(to_file=sys.stdout)
log.show_log(b, lf)
Three log formatters are included with breezy: LongLogFormatter,
ShortLogFormatter and LineLogFormatter. These provide long, short and
single-line log output formats. It's also possible to write your own in
very little code.
Annotating a File
=================
To annotate a file, we want to walk every line of a file, retrieving the
revision which last modified/created that line and then retrieving the
information for that revision.
First we get an annotation iterator for the file we are interested in::
tree, relpath = workingtree.WorkingTree.open_containing('/path/to/file.txt')
fileid = tree.path2id(relpath)
annotation = list(tree.annotate_iter(fileid))
To avoid repeatedly retrieving the same revisions we grab all revisions
associated with the file at once and build up a map of id to revision
information. We also build an map of revision numbers, again indexed
by the revision id::
revision_ids = set(revision_id for revision_id, text in annotation)
revisions = tree.branch.repository.get_revisions(revision_ids)
revision_map = dict(izip(revision_ids, revisions))
revno_map = tree.branch.get_revision_id_to_revno_map()
Finally, we use our annotation iterator to walk the lines of the file,
displaying the information from our revision maps as we go::
for revision_id, text in annotation :
rev = revision_map[revision_id]
revno = revno_map[revision_id]
revno_string = '.'.join(str(i) for i in revno)
print("%s, %s: %s" % (revno_string, rev.committer, text))
Working with branches
=====================
To work with a branch you need a branch object, created from your branch::
from breezy import branch
b = branch.Branch.open('/home/jebw/brztest')
Branching from an existing branch
---------------------------------
To branch you create a branch object representing the branch you are
branching from, and supply a path/url to the new branch location.
The following code clones a branch::
from breezy import branch
b = branch.Branch.open('https://github.com/breezy-team/breezy')
nb = b.controldir.sprout('/tmp/newBrzBranch').open_branch()
This provides no feedback, since Breezy automatically uses the 'silent' UI.
Pushing and pulling branches
----------------------------
To push a branch you need to open the source and destination branches, then
just call push with the other branch as a parameter::
from breezy import branch
b1 = branch.Branch.open('file:///home/user/mybranch')
b2 = branch.Branch.open('https://example.com/path/to/branch')
b1.push(b2)
Pulling is much the same::
b1.pull(b2)
If you have a working tree, as well as a branch, you should use
WorkingTree.pull, not Branch.pull.
This won't handle conflicts automatically though, so any conflicts will be
left in the working tree for the user to resolve.
Checkout from an existing branch
================================
This performs a Lightweight checkout from an existing Branch::
from breezy import bzrdir
accelerator_tree, source = bzrdir.BzrDir.open_tree_or_branch('http:URL')
source.create_checkout('/tmp/newBrzCheckout', None, True, accelerator_tree)
To make a heavyweight checkout, change the last line to::
source.create_checkout('/tmp/newBrzCheckout', None, False, accelerator_tree
History Operations
==================
Finding the last revision number or id
--------------------------------------
To get the last revision number and id of a branch use::
revision_number, revision_id = branch.last_revision_info()
If all you care about is the revision_id there is also the
method::
revision_id = branch.last_revision()
Getting the list of revision ids that make up a branch
------------------------------------------------------
IMPORTANT: This should be avoided wherever possible, as it scales with the
length of history::
revisions = branch.revision_history()
now revisions[0] is the revision id of the first commit, and revs[-1] is the
revision id of the most recent. Note that if all you want is the last
revision then you should use branch.last_revision() as described above, as
it is vastly more efficient.
Getting a Revision object from a revision id
--------------------------------------------
The Revision object has attributes like "message" to get the information
about the revision::
repo = branch.repository
revision = repo.get_revision(rev_id)
Accessing the files from a revision
-----------------------------------
To get the file contents and tree shape for a specific revision you need
a RevisionTree. These are supplied by the repository for a specific
revision id::
revtree = repo.revision_tree(rev_id)
RevisionTrees, like all trees, can be compared as described in "Comparing
Trees" above.
The most common way to list files in a tree is ``Tree.iter_entries()``.
The simplest way to get file content is ``Tree.get_file()``. The best way
to retrieve file content for large numbers of files `Tree.iter_files_bytes()``
|