3
Various useful plugins for working with bzr.
1
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron@aaronbentley.com>
2
# Copyright (C) 2005, 2006 Canonical Limited.
3
# Copyright (C) 2006 Michael Ellerman.
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
from bzrlib.lazy_import import lazy_import
24
lazy_import(globals(), """
25
from bzrlib import help, urlutils
29
from command import BzrToolsCommand
7
30
from errors import CommandError
8
31
from patchsource import BzrPatchSource
9
from shelf import Shelf
12
from bzrlib.option import Option
35
import bzrlib.builtins
36
import bzrlib.commands
37
from bzrlib.branch import Branch
38
from bzrlib.bzrdir import BzrDir
39
from bzrlib.commands import get_cmd_object
14
40
from bzrlib.errors import BzrCommandError
15
from reweave_inventory import cmd_fix
16
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__),
18
from bzrlib import DEFAULT_IGNORE
21
DEFAULT_IGNORE.append('./.shelf')
22
DEFAULT_IGNORE.append('./.bzr-shelf*')
25
Option.OPTIONS['ignored'] = Option('ignored',
26
help='delete all ignored files.')
27
Option.OPTIONS['detritus'] = Option('detritus',
28
help='delete conflict files merge backups, and failed selftest dirs.' +
29
'(*.THIS, *.BASE, *.OTHER, *~, *.tmp')
30
Option.OPTIONS['dry-run'] = Option('dry-run',
31
help='show files to delete instead of deleting them.')
33
class cmd_clean_tree(bzrlib.commands.Command):
34
"""Remove unwanted files from working tree.
35
Normally, ignored files are left alone.
37
takes_options = ['ignored', 'detritus', 'dry-run']
38
def run(self, ignored=False, detritus=False, dry_run=False):
39
from clean_tree import clean_tree
40
clean_tree('.', ignored=ignored, detritus=detritus, dry_run=dry_run)
42
Option.OPTIONS['merge-branch'] = Option('merge-branch',type=str)
44
class cmd_graph_ancestry(bzrlib.commands.Command):
42
from bzrlib.trace import note
43
from bzrlib.option import Option, RegistryOption
44
from bzrlib.workingtree import WorkingTree
46
from command import BzrToolsCommand
49
class cmd_graph_ancestry(BzrToolsCommand):
45
50
"""Produce ancestry graphs using dot.
47
52
Output format is detected according to file extension. Some of the more
48
53
common output formats are html, png, gif, svg, ps. An extension of '.dot'
49
54
will cause a dot graph file to be produced. HTML output has mouseovers
76
81
If available, rsvg is used to antialias PNG and JPEG output, but this can
77
82
be disabled with --no-antialias.
79
takes_args = ['branch', 'file']
80
takes_options = [Option('no-collapse', help="Do not skip simple nodes"),
84
takes_args = ['file', 'merge_branch?']
85
takes_options = [Option('no-collapse', help="Do not skip simple nodes."),
81
86
Option('no-antialias',
82
help="Do not use rsvg to produce antialiased output"),
83
Option('merge-branch', type=str,
84
help="Use this branch to calcuate a merge base"),
85
Option('cluster', help="Use clustered output.")]
86
def run(self, branch, file, no_collapse=False, no_antialias=False,
87
merge_branch=None, cluster=False):
87
help="Do not use rsvg to produce antialiased output."),
88
Option('merge-branch', type=str,
89
help="Use this branch to calcuate a merge base."),
90
Option('cluster', help="Use clustered output."),
91
Option('max-distance',
92
help="Show no nodes farther than this.", type=int),
94
help='Source branch to use (default is current'
99
def run(self, file, merge_branch=None, no_collapse=False,
100
no_antialias=False, cluster=False, max_distance=100,
102
if max_distance == -1:
90
106
ranking = "cluster"
92
108
ranking = "forced"
93
graph.write_ancestry_file(branch, file, not no_collapse,
94
not no_antialias, merge_branch, ranking)
96
class cmd_fetch_ghosts(bzrlib.commands.Command):
109
graph.write_ancestry_file(directory, file, not no_collapse,
110
not no_antialias, merge_branch, ranking,
111
max_distance=max_distance)
114
class cmd_fetch_ghosts(BzrToolsCommand):
97
115
"""Attempt to retrieve ghosts from another branch.
98
116
If the other branch is not supplied, the last-pulled branch is used.
100
118
aliases = ['fetch-missing']
101
119
takes_args = ['branch?']
102
takes_options = [Option('no-fix')]
120
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
103
121
def run(self, branch=None, no_fix=False):
104
122
from fetch_ghosts import fetch_ghosts
105
fetch_ghosts(branch, no_fix)
123
fetch_ghosts(branch, do_reconcile=not no_fix)
107
125
strip_help="""Strip the smallest prefix containing num leading slashes from \
108
126
each file name found in the patch file."""
109
Option.OPTIONS['strip'] = Option('strip', type=int, help=strip_help)
110
Option.OPTIONS['bzrdiff'] = Option('bzrdiff',type=None,
111
help="""Handle extra bzr tags""")
112
class cmd_patch(bzrlib.commands.Command):
129
class cmd_patch(BzrToolsCommand):
113
130
"""Apply a named patch to the current tree.
115
132
takes_args = ['filename?']
116
takes_options = ['strip','bzrdiff']
117
def run(self, filename=None, strip=-1, bzrdiff=0):
133
takes_options = [Option('strip', type=int, short_name='p',
135
Option('silent', help='Suppress chatter.')]
136
def run(self, filename=None, strip=None, silent=False):
118
137
from patch import patch
119
from bzrlib.branch import Branch
120
b = Branch.open_containing('.')[0]
122
if bzrdiff: strip = 0
125
return patch(b, filename, strip, legacy= not bzrdiff)
127
class cmd_shelve(bzrlib.commands.Command):
138
from bzrlib.workingtree import WorkingTree
139
wt = WorkingTree.open_containing('.')[0]
142
return patch(wt, filename, strip, silent)
145
class cmd_shelve1(BzrToolsCommand):
128
146
"""Temporarily set aside some changes from the current tree.
130
148
Shelve allows you to temporarily put changes you've made "on the shelf",
131
149
ie. out of the way, until a later time when you can bring them back from
132
the shelf with the 'unshelve' command.
150
the shelf with the 'unshelve1' command.
134
152
Shelve is intended to help separate several sets of text changes that have
135
153
been inappropriately mingled. If you just want to get rid of all changes
136
154
(text and otherwise) and you don't need to restore them later, use revert.
137
If you want to shelve all text changes at once, use shelve --all.
139
By default shelve asks you what you want to shelve, press '?' at the
140
prompt to get help. To shelve everything run shelve --all.
142
You can put multiple items on the shelf, each time you run unshelve the
143
most recently shelved changes will be reinstated.
155
If you want to shelve all text changes at once, use shelve1 --all.
157
By default shelve1 asks you what you want to shelve, press '?' at the
158
prompt to get help. To shelve everything run shelve1 --all.
145
160
If filenames are specified, only the changes to those files will be
146
161
shelved, other files will be left untouched.
148
163
If a revision is specified, changes since that revision will be shelved.
165
You can put multiple items on the shelf. Normally each time you run
166
unshelve1 the most recently shelved changes will be reinstated. However,
167
you can also unshelve changes in a different order by explicitly
168
specifiying which changes to unshelve1. This works best when the changes
169
don't depend on each other.
171
While you have patches on the shelf you can view and manipulate them with
172
the 'shelf1' command. Run 'bzr shelf1 -h' for more info.
151
175
takes_args = ['file*']
152
takes_options = ['message', 'revision',
153
Option('all', help='Shelve all changes without prompting')]
176
takes_options = [Option('message',
177
help='A message to associate with the shelved changes.',
178
short_name='m', type=unicode),
180
Option('all', help='Shelve all changes without prompting.'),
181
Option('no-color', help='Never display changes in color.')]
155
def run(self, all=False, file_list=None, message=None, revision=None):
183
def run(self, all=False, file_list=None, message=None, revision=None,
156
185
if revision is not None and revision:
157
186
if len(revision) == 1:
158
187
revision = revision[0]
163
192
source = BzrPatchSource(revision, file_list)
164
s = Shelf(source.base)
165
s.shelve(source, all, message)
193
s = shelf.Shelf(source.base)
194
s.shelve(source, all, message, no_color)
168
class cmd_shelf(bzrlib.commands.Command):
169
"""Perform various operations on your shelved patches. See also shelve.
172
list (ls) List the patches on the current shelf.
173
delete (del) <patch> Delete a patch from the current shelf.
174
switch <shelf> Switch to the named shelf, create it if necessary.
175
show <patch> Show the contents of the specified patch.
176
upgrade Upgrade old format shelves.
198
# The following classes are only used as subcommands for 'shelf1', they're
199
# not to be registered directly with bzr.
201
class cmd_shelf_list(bzrlib.commands.Command):
202
"""List the patches on the current shelf."""
203
aliases = ['list', 'ls']
208
class cmd_shelf_delete(bzrlib.commands.Command):
209
"""Delete the patch from the current shelf."""
210
aliases = ['delete', 'del']
211
takes_args = ['patch']
212
def run(self, patch):
213
self.shelf.delete(patch)
216
class cmd_shelf_switch(bzrlib.commands.Command):
217
"""Switch to the other shelf, create it if necessary."""
219
takes_args = ['othershelf']
220
def run(self, othershelf):
221
s = shelf.Shelf(self.shelf.base, othershelf)
225
class cmd_shelf_show(bzrlib.commands.Command):
226
"""Show the contents of the specified or topmost patch."""
227
aliases = ['show', 'cat', 'display']
228
takes_args = ['patch?']
229
def run(self, patch=None):
230
self.shelf.display(patch)
233
class cmd_shelf_upgrade(bzrlib.commands.Command):
234
"""Upgrade old format shelves."""
235
aliases = ['upgrade']
240
class cmd_shelf1(BzrToolsCommand):
241
"""Perform various operations on your shelved patches. See also shelve1."""
178
242
takes_args = ['subcommand', 'args*']
244
subcommands = [cmd_shelf_list, cmd_shelf_delete, cmd_shelf_switch,
245
cmd_shelf_show, cmd_shelf_upgrade]
180
247
def run(self, subcommand, args_list):
250
if args_list is None:
252
cmd = self._get_cmd_object(subcommand)
183
253
source = BzrPatchSource()
184
s = Shelf(source.base)
186
if subcommand == 'ls' or subcommand == 'list':
187
self.__check_no_args(args_list, "shelf list takes no arguments!")
189
elif subcommand == 'delete' or subcommand == 'del':
190
self.__check_one_arg(args_list, "shelf delete takes one argument!")
191
s.delete(args_list[0])
192
elif subcommand == 'switch':
193
self.__check_one_arg(args_list, "shelf switch takes one argument!")
194
s = Shelf(source.base, args_list[0])
196
elif subcommand == 'show':
197
self.__check_one_arg(args_list, "shelf show takes one argument!")
198
s.display(args_list[0])
199
elif subcommand == 'upgrade':
200
self.__check_no_args(args_list, "shelf upgrade takes no arguments!")
203
print subcommand, args_list
204
print >>sys.stderr, "Unknown shelf subcommand '%s'" % subcommand
206
def __check_one_arg(self, args, msg):
207
if args is None or len(args) != 1:
208
raise CommandError(msg)
210
def __check_no_args(self, args, msg):
212
raise CommandError(msg)
215
class cmd_unshelve(bzrlib.commands.Command):
216
"""Restore the most recently shelved changes to the current tree.
217
See 'shelve' for more information.
254
s = shelf.Shelf(source.base)
257
if args_list is None:
259
return cmd.run_argv_aliases(args_list)
261
def _get_cmd_object(self, cmd_name):
262
for cmd_class in self.subcommands:
263
for alias in cmd_class.aliases:
264
if alias == cmd_name:
266
raise CommandError("Unknown shelf subcommand '%s'" % cmd_name)
269
text = ["%s\n\nSubcommands:\n" % self.__doc__]
271
for cmd_class in self.subcommands:
272
text.extend(self.sub_help(cmd_class) + ['\n'])
276
def sub_help(self, cmd_class):
278
cmd_obj = cmd_class()
281
usage = cmd_obj._usage()
282
usage = usage.replace('bzr shelf-', '')
283
text.append('%s%s\n' % (indent, usage))
285
text.append('%s%s\n' % (2 * indent, cmd_class.__doc__))
287
# Somewhat copied from bzrlib.help.help_on_command_options
289
for option_name, option in sorted(cmd_obj.options().items()):
290
if option_name == 'help':
292
option_help.append('%s--%s' % (3 * indent, option_name))
293
if option.type is not None:
294
option_help.append(' %s' % option.argname.upper())
295
if option.short_name():
296
option_help.append(', -%s' % option.short_name())
297
option_help.append('%s%s\n' % (2 * indent, option.help))
299
if len(option_help) > 0:
300
text.append('%soptions:\n' % (2 * indent))
301
text.extend(option_help)
306
class cmd_unshelve1(BzrToolsCommand):
307
"""Restore shelved changes.
309
By default the most recently shelved changes are restored. However if you
310
specify a patch by name those changes will be restored instead.
312
See 'shelve1' for more information.
219
314
takes_options = [
220
Option('all', help='Unshelve all changes without prompting'),
221
Option('force', help='Force unshelving even if errors occur'),
223
def run(self, all=False, force=False):
315
Option('all', help='Unshelve all changes without prompting.'),
316
Option('force', help='Force unshelving even if errors occur.'),
317
Option('no-color', help='Never display changes in color.')
319
takes_args = ['patch?']
320
def run(self, patch=None, all=False, force=False, no_color=False):
224
321
source = BzrPatchSource()
225
s = Shelf(source.base)
226
s.unshelve(source, all, force)
322
s = shelf.Shelf(source.base)
323
s.unshelve(source, patch, all, force, no_color)
230
class cmd_shell(bzrlib.commands.Command):
327
class cmd_shell(BzrToolsCommand):
231
328
"""Begin an interactive shell tailored for bzr.
232
329
Bzr commands can be used without typing bzr first, and will be run natively
233
330
when possible. Tab completion is tailored for bzr. The shell prompt shows
260
366
takes_args = ["branch?"]
261
367
def run(self, branch=None):
262
from branchhistory import branch_history
368
from branchhistory import branch_history
263
369
return branch_history(branch)
265
commands = [cmd_shelve, cmd_unshelve, cmd_shelf, cmd_clean_tree,
266
cmd_graph_ancestry, cmd_fetch_ghosts, cmd_patch, cmd_shell,
267
cmd_fix, cmd_branch_history]
269
command_decorators = []
271
command_decorators = []
273
import bzrlib.builtins
274
if not hasattr(bzrlib.builtins, "cmd_push"):
275
commands.append(push.cmd_push)
277
command_decorators.append(push.cmd_push)
279
from errors import NoPyBaz
282
commands.append(baz_import.cmd_baz_import_branch)
283
commands.append(baz_import.cmd_baz_import)
286
class cmd_baz_import_branch(bzrlib.commands.Command):
287
"""Disabled. (Requires PyBaz)"""
288
takes_args = ['to_location?', 'from_branch?', 'reuse_history*']
289
takes_options = ['verbose', Option('max-count', type=int)]
290
def run(self, to_location=None, from_branch=None, fast=False,
291
max_count=None, verbose=False, dry_run=False,
292
reuse_history_list=[]):
293
print "This command is disabled. Please install PyBaz."
296
class cmd_baz_import(bzrlib.commands.Command):
297
"""Disabled. (Requires PyBaz)"""
298
takes_args = ['to_root_dir?', 'from_archive?', 'reuse_history*']
299
takes_options = ['verbose', Option('prefixes', type=str,
300
help="Prefixes of branches to import")]
301
def run(self, to_root_dir=None, from_archive=None, verbose=False,
302
reuse_history_list=[], prefixes=None):
303
print "This command is disabled. Please install PyBaz."
304
commands.extend((cmd_baz_import_branch, cmd_baz_import))
307
if hasattr(bzrlib.commands, 'register_command'):
308
for command in commands:
309
bzrlib.commands.register_command(command)
310
for command in command_decorators:
311
command._original_command = bzrlib.commands.register_command(
317
from bzrlib.tests.TestUtil import TestLoader
319
from doctest import DocTestSuite, ELLIPSIS
320
from unittest import TestSuite
322
import tests.blackbox
323
import tests.shelf_tests
325
result.addTest(DocTestSuite(bzrtools, optionflags=ELLIPSIS))
326
result.addTest(clean_tree.test_suite())
327
result.addTest(DocTestSuite(baz_import))
328
result.addTest(tests.test_suite())
329
result.addTest(TestLoader().loadTestsFromModule(tests.shelf_tests))
330
result.addTest(tests.blackbox.test_suite())
372
class cmd_zap(BzrToolsCommand):
374
Remove a lightweight checkout, if it can be done safely.
376
This command will remove a lightweight checkout without losing data. That
377
means it only removes lightweight checkouts, and only if they have no
380
If --branch is specified, the branch will be deleted too, but only if the
381
the branch has no new commits (relative to its parent).
383
takes_options = [Option("branch", help="Remove associated branch from"
385
Option('force', help='Delete tree even if contents are'
387
takes_args = ["checkout"]
388
def run(self, checkout, branch=False, force=False):
390
return zap(checkout, remove_branch=branch, allow_modified=force)
393
class cmd_cbranch(BzrToolsCommand):
395
Create a new checkout, associated with a new repository branch.
397
When you cbranch, bzr looks up a target location in locations.conf, and
398
creates the branch there.
400
In your locations.conf, add the following lines:
401
[/working_directory_root]
402
cbranch_target = /branch_root
403
cbranch_target:policy = appendpath
405
This will mean that if you run "bzr cbranch foo/bar foo/baz" in the
406
working directory root, the branch will be created in
407
"/branch_root/foo/baz"
409
NOTE: cbranch also supports "cbranch_root", but that behaviour is
412
takes_options = [Option("lightweight",
413
help="Create a lightweight checkout."), 'revision',
414
Option('files-from', type=unicode,
415
help='Accelerate checkout using files from this'
418
help='Hard-link files from source/files-from tree'
420
takes_args = ["source", "target?"]
421
def run(self, source, target=None, lightweight=False, revision=None,
422
files_from=None, hardlink=False):
423
from cbranch import cbranch
424
return cbranch(source, target, lightweight=lightweight,
425
revision=revision, files_from=files_from,
429
class cmd_branches(BzrToolsCommand):
430
"""Scan a location for branches"""
431
takes_args = ["location?"]
432
def run(self, location=None):
433
from branches import branches
434
return branches(location)
436
class cmd_trees(BzrToolsCommand):
437
"""Scan a location for trees"""
438
takes_args = ['location?']
439
def run(self, location='.'):
440
from bzrlib.workingtree import WorkingTree
441
from bzrlib.transport import get_transport
442
t = get_transport(location)
443
for tree in WorkingTree.find_trees(location):
444
self.outf.write('%s\n' % t.relpath(
445
tree.bzrdir.root_transport.base))
447
class cmd_multi_pull(BzrToolsCommand):
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.transport import get_transport
456
from bzrtools import iter_branch_tree
459
t = get_transport(location)
460
possible_transports = []
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
branch_t = get_transport(parent, possible_transports)
483
pullable.pull(Branch.open_from_transport(branch_t))
489
class cmd_import(BzrToolsCommand):
490
"""Import sources from a directory, tarball or zip file
492
This command will import a directory, tarball or zip file into a bzr
493
tree, replacing any versioned files already present. If a directory is
494
specified, it is used as the target. If the directory does not exist, or
495
is not versioned, it is created.
497
Tarballs may be gzip or bzip2 compressed. This is autodetected.
499
If the tarball or zip has a single root directory, that directory is
500
stripped when extracting the tarball. This is not done for directories.
503
takes_args = ['source', 'tree?']
504
def run(self, source, tree=None):
505
from upstream_import import do_import
506
do_import(source, tree)
509
class cmd_cdiff(BzrToolsCommand):
510
"""A color version of bzr's diff"""
511
takes_args = property(lambda x: get_cmd_object('diff').takes_args)
512
takes_options = list(get_cmd_object('diff').takes_options) + [
513
RegistryOption.from_kwargs('color',
514
'Color mode to use.',
515
title='Color Mode', value_switches=False, enum_switch=True,
516
never='Never colorize output.',
517
auto='Only colorize output if terminal supports it and STDOUT is a'
519
always='Always colorize output (default).'),
520
Option('check-style',
521
help='Warn if trailing whitespace or spurious changes have been'
524
def run(self, color='always', check_style=False, *args, **kwargs):
525
from colordiff import colordiff
526
colordiff(color, check_style, *args, **kwargs)
529
class cmd_conflict_diff(BzrToolsCommand):
531
"""Compare a conflicted file against BASE."""
533
encoding_type = 'exact'
534
takes_args = ['file*']
536
RegistryOption.from_kwargs('direction', 'Direction of comparison.',
537
value_switches=True, enum_switch=False,
538
other='Compare OTHER against common base.',
539
this='Compare THIS against common base.')]
541
def run(self, file_list, direction='other'):
542
from bzrlib.plugins.bzrtools.colordiff import DiffWriter
543
from conflict_diff import ConflictDiffer
544
dw = DiffWriter(self.outf, check_style=False, color='auto')
545
ConflictDiffer().run(dw, file_list, direction)
548
class cmd_rspush(BzrToolsCommand):
549
"""Upload this branch to another location using rsync.
551
If no location is specified, the last-used location will be used. To
552
prevent dirty trees from being uploaded, rspush will error out if there are
553
unknown files or local changes. It will also error out if the upstream
554
directory is non-empty and not an earlier version of the branch.
556
takes_args = ['location?']
557
takes_options = [Option('overwrite', help='Ignore differences between'
558
' branches and overwrite unconditionally.'),
559
Option('no-tree', help='Do not push the working tree,'
562
def run(self, location=None, overwrite=False, no_tree=False):
563
from bzrlib import workingtree
565
cur_branch = workingtree.WorkingTree.open_containing(".")[0]
566
bzrtools.rspush(cur_branch, location, overwrite=overwrite,
567
working_tree=not no_tree)
570
class cmd_link_tree(BzrToolsCommand):
571
"""Hardlink matching files to another tree.
573
Only files with identical content and execute bit will be linked.
575
takes_args = ['location']
577
def run(self, location):
578
from bzrlib import workingtree
579
from bzrlib.plugins.bzrtools.link_tree import link_tree
580
target_tree = workingtree.WorkingTree.open_containing(".")[0]
581
source_tree = workingtree.WorkingTree.open(location)
582
target_tree.lock_write()
584
source_tree.lock_read()
586
link_tree(target_tree, source_tree)
593
class cmd_create_mirror(BzrToolsCommand):
594
"""Create a mirror of another branch.
596
This is similar to `bzr branch`, but copies more settings, including the
597
submit branch and nickname.
599
It sets the public branch and parent of the target to the source location.
602
takes_args = ['source', 'target']
604
def run(self, source, target):
605
source_branch = Branch.open(source)
606
from bzrlib.plugins.bzrtools.mirror import create_mirror
607
create_mirror(source_branch, target, [])