3
Various useful plugins for working with bzr.
1
# Copyright (C) 2005, 2006, 2007 Aaron Bentley <aaron@aaronbentley.com>
2
# Copyright (C) 2005, 2006, 2011 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
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
23
from bzrlib import help, urlutils
27
from command import BzrToolsCommand
7
28
from errors import CommandError
8
29
from patchsource import BzrPatchSource
9
from shelf import Shelf
12
from bzrlib.option import Option
31
import bzrlib.commands
32
from bzrlib.branch import Branch
33
from bzrlib.commands import get_cmd_object
14
34
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):
35
from bzrlib.option import Option, RegistryOption
38
class cmd_graph_ancestry(BzrToolsCommand):
45
39
"""Produce ancestry graphs using dot.
47
41
Output format is detected according to file extension. Some of the more
48
42
common output formats are html, png, gif, svg, ps. An extension of '.dot'
49
43
will cause a dot graph file to be produced. HTML output has mouseovers
76
70
If available, rsvg is used to antialias PNG and JPEG output, but this can
77
71
be disabled with --no-antialias.
79
takes_args = ['branch', 'file']
80
takes_options = [Option('no-collapse', help="Do not skip simple nodes"),
73
takes_args = ['file', 'merge_branch?']
74
takes_options = [Option('no-collapse', help="Do not skip simple nodes."),
81
75
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):
76
help="Do not use rsvg to produce antialiased output."),
77
Option('merge-branch', type=str,
78
help="Use this branch to calcuate a merge base."),
79
Option('cluster', help="Use clustered output."),
80
Option('max-distance',
81
help="Show no nodes farther than this.", type=int),
83
help='Source branch to use (default is current'
88
def run(self, file, merge_branch=None, no_collapse=False,
89
no_antialias=False, cluster=False, max_distance=100,
91
if max_distance == -1:
90
95
ranking = "cluster"
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):
98
graph.write_ancestry_file(directory, file, not no_collapse,
99
not no_antialias, merge_branch, ranking,
100
max_distance=max_distance)
103
class cmd_fetch_ghosts(BzrToolsCommand):
97
104
"""Attempt to retrieve ghosts from another branch.
98
105
If the other branch is not supplied, the last-pulled branch is used.
100
107
aliases = ['fetch-missing']
101
108
takes_args = ['branch?']
102
takes_options = [Option('no-fix')]
109
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
103
110
def run(self, branch=None, no_fix=False):
104
111
from fetch_ghosts import fetch_ghosts
105
fetch_ghosts(branch, no_fix)
112
fetch_ghosts(branch, do_reconcile=not no_fix)
107
114
strip_help="""Strip the smallest prefix containing num leading slashes from \
108
115
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):
118
class cmd_patch(BzrToolsCommand):
113
119
"""Apply a named patch to the current tree.
115
121
takes_args = ['filename?']
116
takes_options = ['strip','bzrdiff']
117
def run(self, filename=None, strip=-1, bzrdiff=0):
122
takes_options = [Option('strip', type=int, short_name='p',
124
Option('silent', help='Suppress chatter.')]
125
def run(self, filename=None, strip=None, silent=False):
118
126
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):
127
from bzrlib.workingtree import WorkingTree
128
wt = WorkingTree.open_containing('.')[0]
131
return patch(wt, filename, strip, silent)
134
class cmd_shelve1(BzrToolsCommand):
128
135
"""Temporarily set aside some changes from the current tree.
130
137
Shelve allows you to temporarily put changes you've made "on the shelf",
131
138
ie. out of the way, until a later time when you can bring them back from
132
the shelf with the 'unshelve' command.
139
the shelf with the 'unshelve1' command.
134
141
Shelve is intended to help separate several sets of text changes that have
135
142
been inappropriately mingled. If you just want to get rid of all changes
136
143
(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.
144
If you want to shelve all text changes at once, use shelve1 --all.
146
By default shelve1 asks you what you want to shelve, press '?' at the
147
prompt to get help. To shelve everything run shelve1 --all.
145
149
If filenames are specified, only the changes to those files will be
146
150
shelved, other files will be left untouched.
148
152
If a revision is specified, changes since that revision will be shelved.
154
You can put multiple items on the shelf. Normally each time you run
155
unshelve1 the most recently shelved changes will be reinstated. However,
156
you can also unshelve changes in a different order by explicitly
157
specifiying which changes to unshelve1. This works best when the changes
158
don't depend on each other.
160
While you have patches on the shelf you can view and manipulate them with
161
the 'shelf1' command. Run 'bzr shelf1 -h' for more info.
151
164
takes_args = ['file*']
152
takes_options = ['message', 'revision',
153
Option('all', help='Shelve all changes without prompting')]
165
takes_options = [Option('message',
166
help='A message to associate with the shelved changes.',
167
short_name='m', type=unicode),
169
Option('all', help='Shelve all changes without prompting.'),
170
Option('no-color', help='Never display changes in color.')]
155
def run(self, all=False, file_list=None, message=None, revision=None):
172
def run(self, all=False, file_list=None, message=None, revision=None,
156
174
if revision is not None and revision:
157
175
if len(revision) == 1:
158
176
revision = revision[0]
163
181
source = BzrPatchSource(revision, file_list)
164
s = Shelf(source.base)
165
s.shelve(source, all, message)
182
s = shelf.Shelf(source.base)
183
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.
187
# The following classes are only used as subcommands for 'shelf1', they're
188
# not to be registered directly with bzr.
190
class cmd_shelf_list(bzrlib.commands.Command):
191
"""List the patches on the current shelf."""
192
aliases = ['list', 'ls']
197
class cmd_shelf_delete(bzrlib.commands.Command):
198
"""Delete the patch from the current shelf."""
199
aliases = ['delete', 'del']
200
takes_args = ['patch']
201
def run(self, patch):
202
self.shelf.delete(patch)
205
class cmd_shelf_switch(bzrlib.commands.Command):
206
"""Switch to the other shelf, create it if necessary."""
208
takes_args = ['othershelf']
209
def run(self, othershelf):
210
s = shelf.Shelf(self.shelf.base, othershelf)
214
class cmd_shelf_show(bzrlib.commands.Command):
215
"""Show the contents of the specified or topmost patch."""
216
aliases = ['show', 'cat', 'display']
217
takes_args = ['patch?']
218
def run(self, patch=None):
219
self.shelf.display(patch)
222
class cmd_shelf_upgrade(bzrlib.commands.Command):
223
"""Upgrade old format shelves."""
224
aliases = ['upgrade']
229
class cmd_shelf1(BzrToolsCommand):
230
"""Perform various operations on your shelved patches. See also shelve1."""
178
231
takes_args = ['subcommand', 'args*']
233
subcommands = [cmd_shelf_list, cmd_shelf_delete, cmd_shelf_switch,
234
cmd_shelf_show, cmd_shelf_upgrade]
180
236
def run(self, subcommand, args_list):
239
if args_list is None:
241
cmd = self._get_cmd_object(subcommand)
183
242
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.
243
s = shelf.Shelf(source.base)
246
if args_list is None:
248
return cmd.run_argv_aliases(args_list)
250
def _get_cmd_object(self, cmd_name):
251
for cmd_class in self.subcommands:
252
for alias in cmd_class.aliases:
253
if alias == cmd_name:
255
raise CommandError("Unknown shelf subcommand '%s'" % cmd_name)
258
text = ["%s\n\nSubcommands:\n" % self.__doc__]
260
for cmd_class in self.subcommands:
261
text.extend(self.sub_help(cmd_class) + ['\n'])
265
def sub_help(self, cmd_class):
267
cmd_obj = cmd_class()
270
usage = cmd_obj._usage()
271
usage = usage.replace('bzr shelf-', '')
272
text.append('%s%s\n' % (indent, usage))
274
text.append('%s%s\n' % (2 * indent, cmd_class.__doc__))
276
# Somewhat copied from bzrlib.help.help_on_command_options
278
for option_name, option in sorted(cmd_obj.options().items()):
279
if option_name == 'help':
281
option_help.append('%s--%s' % (3 * indent, option_name))
282
if option.type is not None:
283
option_help.append(' %s' % option.argname.upper())
284
if option.short_name():
285
option_help.append(', -%s' % option.short_name())
286
option_help.append('%s%s\n' % (2 * indent, option.help))
288
if len(option_help) > 0:
289
text.append('%soptions:\n' % (2 * indent))
290
text.extend(option_help)
295
class cmd_unshelve1(BzrToolsCommand):
296
"""Restore shelved changes.
298
By default the most recently shelved changes are restored. However if you
299
specify a patch by name those changes will be restored instead.
301
See 'shelve1' for more information.
219
303
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):
304
Option('all', help='Unshelve all changes without prompting.'),
305
Option('force', help='Force unshelving even if errors occur.'),
306
Option('no-color', help='Never display changes in color.')
308
takes_args = ['patch?']
309
def run(self, patch=None, all=False, force=False, no_color=False):
224
310
source = BzrPatchSource()
225
s = Shelf(source.base)
226
s.unshelve(source, all, force)
311
s = shelf.Shelf(source.base)
312
s.unshelve(source, patch, all, force, no_color)
230
class cmd_shell(bzrlib.commands.Command):
316
class cmd_shell(BzrToolsCommand):
231
317
"""Begin an interactive shell tailored for bzr.
232
318
Bzr commands can be used without typing bzr first, and will be run natively
233
319
when possible. Tab completion is tailored for bzr. The shell prompt shows
260
355
takes_args = ["branch?"]
261
356
def run(self, branch=None):
262
from branchhistory import branch_history
357
from branchhistory import branch_history
263
358
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())
361
class cmd_zap(BzrToolsCommand):
363
Remove a lightweight checkout, if it can be done safely.
365
This command will remove a lightweight checkout without losing data. That
366
means it only removes lightweight checkouts, and only if they have no
369
If --branch is specified, the branch will be deleted too, but only if the
370
the branch has no new commits (relative to its parent).
372
If bzr-pipeline is also installed, the --store option will store changes
373
in the branch before deleting the tree. To restore the changes, do::
375
bzr checkout --lightweight $BRANCH $CHECKOUT
376
bzr switch-pipe -d $CHECKOUT `bzr nick -d $CHECKOUT`
378
takes_options = [Option("branch", help="Remove associated branch from"
380
RegistryOption('change_policy',
381
'How to handle changed files',
383
('bzrlib.plugins.bzrtools.zap',
384
'change_policy_registry'),
387
takes_args = ["checkout"]
388
def run(self, checkout, branch=False, change_policy=None):
390
change_policy_registry,
394
if change_policy is None:
395
change_policy = change_policy_registry.get()
396
if change_policy is StoreChanges:
398
import bzrlib.plugins.pipeline
400
raise BzrCommandError('--store requires bzr-pipeline.')
401
return zap(checkout, remove_branch=branch, policy=change_policy)
404
class cmd_cbranch(BzrToolsCommand):
406
Create a new checkout, associated with a new repository branch.
408
When you cbranch, bzr looks up a target location in locations.conf, and
409
creates the branch there.
411
In your locations.conf, add the following lines:
412
[/working_directory_root]
413
cbranch_target = /branch_root
414
cbranch_target:policy = appendpath
416
This will mean that if you run "bzr cbranch foo/bar foo/baz" in the
417
working directory root, the branch will be created in
418
"/branch_root/foo/baz"
420
NOTE: cbranch also supports "cbranch_root", but that behaviour is
423
takes_options = [Option("lightweight",
424
help="Create a lightweight checkout."), 'revision',
425
Option('files-from', type=unicode,
426
help='Accelerate checkout using files from this'
429
help='Hard-link files from source/files-from tree'
431
takes_args = ["source", "target?"]
432
def run(self, source, target=None, lightweight=False, revision=None,
433
files_from=None, hardlink=False):
434
from cbranch import cbranch
435
return cbranch(source, target, lightweight=lightweight,
436
revision=revision, files_from=files_from,
440
class cmd_branches(BzrToolsCommand):
441
"""Scan a location for branches"""
442
takes_args = ["location?"]
443
def run(self, location=None):
444
from branches import branches
445
return branches(location)
447
class cmd_trees(BzrToolsCommand):
448
"""Scan a location for trees"""
449
takes_args = ['location?']
450
def run(self, location='.'):
451
from bzrlib.workingtree import WorkingTree
452
from bzrlib.transport import get_transport
453
t = get_transport(location)
454
for tree in WorkingTree.find_trees(location):
455
self.outf.write('%s\n' % t.relpath(
456
tree.bzrdir.root_transport.base))
458
class cmd_multi_pull(BzrToolsCommand):
459
"""Pull all the branches under a location, e.g. a repository.
461
Both branches present in the directory and the branches of checkouts are
464
takes_args = ["location?"]
465
def run(self, location=None):
466
from bzrlib.transport import get_transport
467
from bzrtools import iter_branch_tree
470
t = get_transport(location)
471
possible_transports = []
473
print "Can't list this type of location."
475
for branch, wt in iter_branch_tree(t):
480
parent = branch.get_parent()
487
if base.startswith(t.base):
488
relpath = base[len(t.base):].rstrip('/')
491
print "Pulling %s from %s" % (relpath, parent)
493
branch_t = get_transport(parent, possible_transports)
494
pullable.pull(Branch.open_from_transport(branch_t))
500
class cmd_import(BzrToolsCommand):
501
"""Import sources from a directory, tarball or zip file
503
This command will import a directory, tarball or zip file into a bzr
504
tree, replacing any versioned files already present. If a directory is
505
specified, it is used as the target. If the directory does not exist, or
506
is not versioned, it is created.
508
Tarballs may be gzip or bzip2 compressed. This is autodetected.
510
If the tarball or zip has a single root directory, that directory is
511
stripped when extracting the tarball. This is not done for directories.
514
takes_args = ['source', 'tree?']
515
def run(self, source, tree=None):
516
from upstream_import import do_import
517
do_import(source, tree)
520
class cmd_cdiff(BzrToolsCommand):
521
"""A color version of bzr's diff"""
522
takes_args = property(lambda x: get_cmd_object('diff').takes_args)
523
takes_options = list(get_cmd_object('diff').takes_options) + [
524
RegistryOption.from_kwargs('color',
525
'Color mode to use.',
526
title='Color Mode', value_switches=False, enum_switch=True,
527
never='Never colorize output.',
528
auto='Only colorize output if terminal supports it and STDOUT is a'
530
always='Always colorize output (default).'),
531
Option('check-style',
532
help='Warn if trailing whitespace or spurious changes have been'
535
def run(self, color='always', check_style=False, *args, **kwargs):
536
from colordiff import colordiff
537
colordiff(color, check_style, *args, **kwargs)
540
class cmd_conflict_diff(BzrToolsCommand):
542
"""Compare a conflicted file against BASE."""
544
encoding_type = 'exact'
545
takes_args = ['file*']
547
RegistryOption.from_kwargs('direction', 'Direction of comparison.',
548
value_switches=True, enum_switch=False,
549
other='Compare OTHER against common base.',
550
this='Compare THIS against common base.')]
552
def run(self, file_list, direction='other'):
553
from bzrlib.plugins.bzrtools.colordiff import DiffWriter
554
from conflict_diff import ConflictDiffer
555
dw = DiffWriter(self.outf, check_style=False, color='auto')
556
ConflictDiffer().run(dw, file_list, direction)
559
class cmd_rspush(BzrToolsCommand):
560
"""Upload this branch to another location using rsync.
562
If no location is specified, the last-used location will be used. To
563
prevent dirty trees from being uploaded, rspush will error out if there are
564
unknown files or local changes. It will also error out if the upstream
565
directory is non-empty and not an earlier version of the branch.
567
takes_args = ['location?']
568
takes_options = [Option('overwrite', help='Ignore differences between'
569
' branches and overwrite unconditionally.'),
570
Option('no-tree', help='Do not push the working tree,'
573
def run(self, location=None, overwrite=False, no_tree=False):
574
from bzrlib import workingtree
576
cur_branch = workingtree.WorkingTree.open_containing(".")[0]
577
bzrtools.rspush(cur_branch, location, overwrite=overwrite,
578
working_tree=not no_tree)
581
class cmd_link_tree(BzrToolsCommand):
582
"""Hardlink matching files to another tree.
584
Only files with identical content and execute bit will be linked.
586
takes_args = ['location']
588
def run(self, location):
589
from bzrlib import workingtree
590
from bzrlib.plugins.bzrtools.link_tree import link_tree
591
target_tree = workingtree.WorkingTree.open_containing(".")[0]
592
source_tree = workingtree.WorkingTree.open(location)
593
target_tree.lock_write()
595
source_tree.lock_read()
597
link_tree(target_tree, source_tree)
604
class cmd_create_mirror(BzrToolsCommand):
605
"""Create a mirror of another branch.
607
This is similar to `bzr branch`, but copies more settings, including the
608
submit branch and nickname.
610
It sets the public branch and parent of the target to the source location.
613
takes_args = ['source', 'target']
615
def run(self, source, target):
616
source_branch = Branch.open(source)
617
from bzrlib.plugins.bzrtools.mirror import create_mirror
618
create_mirror(source_branch, target, [])