2
Various useful plugins for working with bzr.
11
version_info = tuple(int(n) for n in __version__.split('.'))
14
def check_bzrlib_version(desired):
15
"""Check that bzrlib is compatible.
17
If version is < bzrtools version, assume incompatible.
18
If version == bzrtools version, assume completely compatible
19
If version == bzrtools version + 1, assume compatible, with deprecations
20
Otherwise, assume incompatible.
22
desired_plus = (desired[0], desired[1]+1)
23
bzrlib_version = bzrlib.version_info[:2]
24
if bzrlib_version == desired:
27
from bzrlib.trace import warning
29
# get the message out any way we can
30
from warnings import warn as warning
31
if bzrlib_version < desired:
32
warning('Installed bzr version %s is too old to be used with bzrtools'
33
' %s.' % (bzrlib.__version__, __version__))
34
# Not using BzrNewError, because it may not exist.
35
raise Exception, 'Version mismatch', version_info
37
warning('Bzrtools is not up to date with installed bzr version %s.'
38
' \nThere should be a newer version available, e.g. %i.%i.'
39
% (bzrlib.__version__, bzrlib_version[0], bzrlib_version[1]))
40
if bzrlib_version != desired_plus:
41
raise Exception, 'Version mismatch'
44
check_bzrlib_version(version_info[:2])
48
from errors import CommandError
49
from patchsource import BzrPatchSource
50
from shelf import Shelf
51
from switch import cmd_switch
55
import bzrlib.builtins
57
import bzrlib.commands
58
from bzrlib.commands import get_cmd_object
59
from bzrlib.errors import BzrCommandError
60
from bzrlib.help import command_usage
62
from bzrlib.option import Option
63
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__),
66
bzrlib.ignores.add_runtime_ignores(['./.shelf'])
69
class cmd_clean_tree(bzrlib.commands.Command):
70
"""Remove unwanted files from working tree.
72
By default, only unknown files, not ignored files, are deleted. Versioned
73
files are never deleted.
75
Another class is 'detritus', which includes files emitted by bzr during
76
normal operations and selftests. (The value of these files decreases with
79
If no options are specified, unknown files are deleted. Otherwise, option
80
flags are respected, and may be combined.
82
To check what clean-tree will do, use --dry-run.
84
takes_options = [Option('ignored', help='delete all ignored files.'),
85
Option('detritus', help='delete conflict files, merge'
86
' backups, and failed selftest dirs.'),
88
help='delete files unknown to bzr. (default)'),
89
Option('dry-run', help='show files to delete instead of'
91
def run(self, unknown=False, ignored=False, detritus=False, dry_run=False):
92
from clean_tree import clean_tree
93
if not (unknown or ignored or detritus):
95
clean_tree('.', unknown=unknown, ignored=ignored, detritus=detritus,
98
class cmd_graph_ancestry(bzrlib.commands.Command):
99
"""Produce ancestry graphs using dot.
101
Output format is detected according to file extension. Some of the more
102
common output formats are html, png, gif, svg, ps. An extension of '.dot'
103
will cause a dot graph file to be produced. HTML output has mouseovers
104
that show the commit message.
106
Branches are labeled r?, where ? is the revno. If they have no revno,
107
with the last 5 characters of their revision identifier are used instead.
109
The value starting with d is "(maximum) distance from the null revision".
111
If --merge-branch is specified, the two branches are compared and a merge
115
white normal revision
118
orange COMMON history
119
blue COMMON non-history ancestor
120
green Merge base (COMMON ancestor farthest from the null revision)
121
dotted Ghost revision (missing from branch storage)
123
Ancestry is usually collapsed by skipping revisions with a single parent
124
and descendant. The number of skipped revisions is shown on the arrow.
125
This feature can be disabled with --no-collapse.
127
By default, revisions are ordered by distance from root, but they can be
128
clustered instead using --cluster.
130
If available, rsvg is used to antialias PNG and JPEG output, but this can
131
be disabled with --no-antialias.
133
takes_args = ['branch', 'file']
134
takes_options = [Option('no-collapse', help="Do not skip simple nodes"),
135
Option('no-antialias',
136
help="Do not use rsvg to produce antialiased output"),
137
Option('merge-branch', type=str,
138
help="Use this branch to calcuate a merge base"),
139
Option('cluster', help="Use clustered output.")]
140
def run(self, branch, file, no_collapse=False, no_antialias=False,
141
merge_branch=None, cluster=False):
147
graph.write_ancestry_file(branch, file, not no_collapse,
148
not no_antialias, merge_branch, ranking)
150
class cmd_fetch_ghosts(bzrlib.commands.Command):
151
"""Attempt to retrieve ghosts from another branch.
152
If the other branch is not supplied, the last-pulled branch is used.
154
aliases = ['fetch-missing']
155
takes_args = ['branch?']
156
takes_options = [Option('no-fix')]
157
def run(self, branch=None, no_fix=False):
158
from fetch_ghosts import fetch_ghosts
159
fetch_ghosts(branch, no_fix)
161
strip_help="""Strip the smallest prefix containing num leading slashes from \
162
each file name found in the patch file."""
163
Option.OPTIONS['bzrdiff'] = Option('bzrdiff',type=None,
164
help="""Handle extra bzr tags""")
165
class cmd_patch(bzrlib.commands.Command):
166
"""Apply a named patch to the current tree.
168
takes_args = ['filename?']
169
takes_options = [Option('strip', type=int, help=strip_help)]
170
def run(self, filename=None, strip=-1, bzrdiff=0):
171
from patch import patch
172
from bzrlib.workingtree import WorkingTree
173
wt = WorkingTree.open_containing('.')[0]
175
if bzrdiff: strip = 0
178
return patch(wt, filename, strip, legacy= not bzrdiff)
181
class cmd_shelve(bzrlib.commands.Command):
182
"""Temporarily set aside some changes from the current tree.
184
Shelve allows you to temporarily put changes you've made "on the shelf",
185
ie. out of the way, until a later time when you can bring them back from
186
the shelf with the 'unshelve' command.
188
Shelve is intended to help separate several sets of text changes that have
189
been inappropriately mingled. If you just want to get rid of all changes
190
(text and otherwise) and you don't need to restore them later, use revert.
191
If you want to shelve all text changes at once, use shelve --all.
193
By default shelve asks you what you want to shelve, press '?' at the
194
prompt to get help. To shelve everything run shelve --all.
196
If filenames are specified, only the changes to those files will be
197
shelved, other files will be left untouched.
199
If a revision is specified, changes since that revision will be shelved.
201
You can put multiple items on the shelf. Normally each time you run
202
unshelve the most recently shelved changes will be reinstated. However,
203
you can also unshelve changes in a different order by explicitly
204
specifiying which changes to unshelve. This works best when the changes
205
don't depend on each other.
207
While you have patches on the shelf you can view and manipulate them with
208
the 'shelf' command. Run 'bzr shelf -h' for more info.
211
takes_args = ['file*']
212
takes_options = ['message', 'revision',
213
Option('all', help='Shelve all changes without prompting'),
214
Option('no-color', help='Never display changes in color')]
216
def run(self, all=False, file_list=None, message=None, revision=None,
218
if revision is not None and revision:
219
if len(revision) == 1:
220
revision = revision[0]
222
raise CommandError("shelve only accepts a single revision "
225
source = BzrPatchSource(revision, file_list)
226
s = Shelf(source.base)
227
s.shelve(source, all, message, no_color)
231
# The following classes are only used as subcommands for 'shelf', they're
232
# not to be registered directly with bzr.
234
class cmd_shelf_list(bzrlib.commands.Command):
235
"""List the patches on the current shelf."""
236
aliases = ['list', 'ls']
241
class cmd_shelf_delete(bzrlib.commands.Command):
242
"""Delete the patch from the current shelf."""
243
aliases = ['delete', 'del']
244
takes_args = ['patch']
245
def run(self, patch):
246
self.shelf.delete(patch)
249
class cmd_shelf_switch(bzrlib.commands.Command):
250
"""Switch to the other shelf, create it if necessary."""
252
takes_args = ['othershelf']
253
def run(self, othershelf):
254
s = Shelf(self.shelf.base, othershelf)
258
class cmd_shelf_show(bzrlib.commands.Command):
259
"""Show the contents of the specified or topmost patch."""
260
aliases = ['show', 'cat', 'display']
261
takes_args = ['patch?']
262
def run(self, patch=None):
263
self.shelf.display(patch)
266
class cmd_shelf_upgrade(bzrlib.commands.Command):
267
"""Upgrade old format shelves."""
268
aliases = ['upgrade']
273
class cmd_shelf(bzrlib.commands.Command):
274
"""Perform various operations on your shelved patches. See also shelve."""
275
takes_args = ['subcommand', 'args*']
277
subcommands = [cmd_shelf_list, cmd_shelf_delete, cmd_shelf_switch,
278
cmd_shelf_show, cmd_shelf_upgrade]
280
def run(self, subcommand, args_list):
283
cmd = self._get_cmd_object(subcommand)
284
source = BzrPatchSource()
285
s = Shelf(source.base)
287
return cmd.run_argv_aliases(args_list)
289
def _get_cmd_object(self, cmd_name):
290
for cmd_class in self.subcommands:
291
for alias in cmd_class.aliases:
292
if alias == cmd_name:
294
raise CommandError("Unknown shelf subcommand '%s'" % cmd_name)
297
text = ["%s\n\nSubcommands:\n" % self.__doc__]
299
for cmd_class in self.subcommands:
300
text.extend(self.sub_help(cmd_class) + ['\n'])
304
def sub_help(self, cmd_class):
306
cmd_obj = cmd_class()
309
usage = command_usage(cmd_obj)
310
usage = usage.replace('bzr shelf-', '')
311
text.append('%s%s\n' % (indent, usage))
313
text.append('%s%s\n' % (2 * indent, cmd_class.__doc__))
315
# Somewhat copied from bzrlib.help.help_on_command_options
317
for option_name, option in sorted(cmd_obj.options().items()):
318
if option_name == 'help':
320
option_help.append('%s--%s' % (3 * indent, option_name))
321
if option.type is not None:
322
option_help.append(' %s' % option.argname.upper())
323
if option.short_name():
324
option_help.append(', -%s' % option.short_name())
325
option_help.append('%s%s\n' % (2 * indent, option.help))
327
if len(option_help) > 0:
328
text.append('%soptions:\n' % (2 * indent))
329
text.extend(option_help)
335
class cmd_unshelve(bzrlib.commands.Command):
336
"""Restore shelved changes.
338
By default the most recently shelved changes are restored. However if you
339
specify a patch by name those changes will be restored instead.
341
See 'shelve' for more information.
344
Option('all', help='Unshelve all changes without prompting'),
345
Option('force', help='Force unshelving even if errors occur'),
346
Option('no-color', help='Never display changes in color')
348
takes_args = ['patch?']
349
def run(self, patch=None, all=False, force=False, no_color=False):
350
source = BzrPatchSource()
351
s = Shelf(source.base)
352
s.unshelve(source, patch, all, force, no_color)
356
class cmd_shell(bzrlib.commands.Command):
357
"""Begin an interactive shell tailored for bzr.
358
Bzr commands can be used without typing bzr first, and will be run natively
359
when possible. Tab completion is tailored for bzr. The shell prompt shows
360
the branch nick, revno, and path.
362
If it encounters any moderately complicated shell command, it will punt to
367
bzr bzrtools:287/> status
370
bzr bzrtools:287/> status --[TAB][TAB]
371
--all --help --revision --show-ids
372
bzr bzrtools:287/> status --
376
return shell.run_shell()
378
class cmd_branch_history(bzrlib.commands.Command):
380
Display the development history of a branch.
382
Each different committer or branch nick is considered a different line of
383
development. Committers are treated as the same if they have the same
384
name, or if they have the same email address.
386
takes_args = ["branch?"]
387
def run(self, branch=None):
388
from branchhistory import branch_history
389
return branch_history(branch)
392
class cmd_zap(bzrlib.commands.Command):
394
Remove a lightweight checkout, if it can be done safely.
396
This command will remove a lightweight checkout without losing data. That
397
means it only removes lightweight checkouts, and only if they have no
400
If --branch is specified, the branch will be deleted too, but only if the
401
the branch has no new commits (relative to its parent).
403
takes_options = [Option("branch", help="Remove associtated branch from"
405
takes_args = ["checkout"]
406
def run(self, checkout, branch=False):
408
return zap(checkout, remove_branch=branch)
411
class cmd_cbranch(bzrlib.commands.Command):
413
Create a new checkout, associated with a new repository branch.
415
When you cbranch, bzr looks up the repository associated with your current
416
directory in branches.conf. It creates a new branch in that repository
417
with the same name and relative path as the checkout you request.
419
The branches.conf parameter is "cbranch_root". So if you want
420
cbranch operations in /home/jrandom/bigproject to produce branches in
421
/home/jrandom/bigproject/repository, you'd add this:
423
[/home/jrandom/bigproject]
424
cbranch_root = /home/jrandom/bigproject/repository
426
Note that if "/home/jrandom/bigproject/repository" isn't a repository,
427
standalone branches will be produced. Standalone branches will also
428
be produced if the source branch is in 0.7 format (or earlier).
430
takes_options = [Option("lightweight",
431
help="Create a lightweight checkout"), 'revision']
432
takes_args = ["source", "target?"]
433
def run(self, source, target=None, lightweight=False, revision=None):
434
from cbranch import cbranch
435
return cbranch(source, target, lightweight=lightweight,
439
class cmd_branches(bzrlib.commands.Command):
440
"""Scan a location for branches"""
441
takes_args = ["location?"]
442
def run(self, location=None):
443
from branches import branches
444
return branches(location)
447
class cmd_multi_pull(bzrlib.commands.Command):
448
"""Pull all the branches under a location, e.g. a repository.
450
Both branches present in the directory and the branches of checkouts are
453
takes_args = ["location?"]
454
def run(self, location=None):
455
from bzrlib.branch import Branch
456
from bzrlib.transport import get_transport
457
from bzrtools import iter_branch_tree
460
t = get_transport(location)
462
print "Can't list this type of location."
464
for branch, wt in iter_branch_tree(t):
469
parent = branch.get_parent()
476
if base.startswith(t.base):
477
relpath = base[len(t.base):].rstrip('/')
480
print "Pulling %s from %s" % (relpath, parent)
482
pullable.pull(Branch.open(parent))
487
class cmd_branch_mark(bzrlib.commands.Command):
489
Add, view or list branch markers <EXPERIMENTAL>
491
To add a mark, do 'bzr branch-mark MARK'.
492
To list marks, do 'bzr branch-mark' (this lists all marks for the branch's
494
To delete a mark, do 'bzr branch-mark --delete MARK'
496
These marks can be used to track a branch's status.
498
takes_args = ['mark?', 'branch?']
499
takes_options = [Option('delete', help='Delete this mark')]
500
def run(self, mark=None, branch=None, delete=False):
501
from branch_mark import branch_mark
502
branch_mark(mark, branch, delete)
504
class cmd_import(bzrlib.commands.Command):
505
"""Import sources from a tarball
507
This command will import a tarball into a bzr tree, replacing any versioned
508
files already present. If a directory is specified, it is used as the
509
target. If the directory does not exist, or is not versioned, it is
512
Tarballs may be gzip or bzip2 compressed. This is autodetected.
514
If the tarball has a single root directory, that directory is stripped
515
when extracting the tarball.
518
takes_args = ['source', 'tree?']
519
def run(self, source, tree=None):
520
from upstream_import import do_import
521
do_import(source, tree)
523
class cmd_shove(bzrlib.commands.Command):
524
"""Apply uncommitted changes to another tree
526
This is useful when you start to make changes in one tree, then realize
527
they should really be done in a different tree.
529
Shove is implemented using merge, so:
530
- All changes, including renames and adds, will be applied.
531
- No changes that have already been applied will be applied.
532
- If the target is significantly different from the source, conflicts may
536
takes_args = ['target', 'source?']
537
def run(self, target, source='.'):
538
from shove import do_shove
539
do_shove(source, target)
541
class cmd_cdiff(bzrlib.commands.Command):
542
"""A color version of bzr's diff"""
543
takes_args = property(lambda x: get_cmd_object('diff').takes_args)
544
takes_options = property(lambda x: get_cmd_object('diff').takes_options)
545
def run(*args, **kwargs):
546
from colordiff import colordiff
547
colordiff(*args, **kwargs)
550
class cmd_baz_import(bzrlib.commands.Command):
551
"""Import an Arch or Baz archive into a bzr repository.
553
This command should be used on local archives (or mirrors) only. It is
554
quite slow on remote archives.
556
reuse_history allows you to specify any previous imports you
557
have done of different archives, which this archive has branches
558
tagged from. This will dramatically reduce the time to convert
559
the archive as it will not have to convert the history already
560
converted in that other branch.
562
If you specify prefixes, only branches whose names start with that prefix
563
will be imported. Skipped branches will be listed, so you can import any
564
branches you missed by accident. Here's an example of doing a partial
565
import from thelove@canonical.com:
566
bzr baz-import thelove thelove@canonical.com --prefixes dists:talloc-except
568
takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
569
takes_options = ['verbose', Option('prefixes', type=str,
570
help="Prefixes of branches to import, colon-separated")]
572
def run(self, to_root_dir, from_archive, verbose=False,
573
reuse_history_list=[], prefixes=None):
574
from errors import NoPyBaz
577
baz_import.baz_import(to_root_dir, from_archive,
578
verbose, reuse_history_list, prefixes)
580
print "This command is disabled. Please install PyBaz."
583
class cmd_baz_import_branch(bzrlib.commands.Command):
584
"""Import an Arch or Baz branch into a bzr branch."""
585
takes_args = ['to_location', 'from_branch?', 'reuse_history*']
586
takes_options = ['verbose', Option('max-count', type=int)]
588
def run(self, to_location, from_branch=None, fast=False, max_count=None,
589
verbose=False, dry_run=False, reuse_history_list=[]):
590
from errors import NoPyBaz
593
baz_import.baz_import_branch(to_location, from_branch, fast,
594
max_count, verbose, dry_run,
597
print "This command is disabled. Please install PyBaz."
600
commands = [cmd_shelve, cmd_unshelve, cmd_shelf, cmd_clean_tree,
601
cmd_graph_ancestry, cmd_fetch_ghosts, cmd_patch, cmd_shell,
602
cmd_branch_history, cmd_zap, cmd_cbranch, cmd_branches,
603
cmd_multi_pull, cmd_switch, cmd_branch_mark, cmd_import, cmd_shove,
604
cmd_cdiff, cmd_baz_import_branch, cmd_baz_import]
606
commands.append(rspush.cmd_rspush)
608
if hasattr(bzrlib.commands, 'register_command'):
609
for command in commands:
610
bzrlib.commands.register_command(command)
614
from bzrlib.tests.TestUtil import TestLoader
616
from doctest import DocTestSuite, ELLIPSIS
617
from unittest import TestSuite
618
import tests.clean_tree
619
import upstream_import
621
import tests.blackbox
622
import tests.shelf_tests
624
result.addTest(DocTestSuite(bzrtools, optionflags=ELLIPSIS))
625
result.addTest(tests.clean_tree.test_suite())
628
result.addTest(DocTestSuite(baz_import))
631
result.addTest(tests.test_suite())
632
result.addTest(TestLoader().loadTestsFromModule(tests.shelf_tests))
633
result.addTest(tests.blackbox.test_suite())
634
result.addTest(upstream_import.test_suite())
635
result.addTest(zap.test_suite())