3
Various useful plugins for working with bzr.
7
from shelf import Shelf
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
30
from errors import CommandError
31
from patchsource import BzrPatchSource
10
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
12
40
from bzrlib.errors import BzrCommandError
13
from reweave_inventory import cmd_fix
14
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__),
17
Option.OPTIONS['ignored'] = Option('ignored',
18
help='delete all ignored files.')
19
Option.OPTIONS['detritus'] = Option('detritus',
20
help='delete conflict files merge backups, and failed selftest dirs.' +
21
'(*.THIS, *.BASE, *.OTHER, *~, *.tmp')
22
Option.OPTIONS['dry-run'] = Option('dry-run',
23
help='show files to delete instead of deleting them.')
25
class cmd_clean_tree(bzrlib.commands.Command):
26
"""Remove unwanted files from working tree.
27
Normally, ignored files are left alone.
29
takes_options = ['ignored', 'detritus', 'dry-run']
30
def run(self, ignored=False, detritus=False, dry_run=False):
31
from clean_tree import clean_tree
32
clean_tree('.', ignored=ignored, detritus=detritus, dry_run=dry_run)
34
Option.OPTIONS['merge-branch'] = Option('merge-branch',type=str)
36
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):
37
50
"""Produce ancestry graphs using dot.
39
52
Output format is detected according to file extension. Some of the more
40
53
common output formats are html, png, gif, svg, ps. An extension of '.dot'
41
54
will cause a dot graph file to be produced. HTML output has mouseovers
68
81
If available, rsvg is used to antialias PNG and JPEG output, but this can
69
82
be disabled with --no-antialias.
71
takes_args = ['branch', 'file']
72
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."),
73
86
Option('no-antialias',
74
help="Do not use rsvg to produce antialiased output"),
75
Option('merge-branch', type=str,
76
help="Use this branch to calcuate a merge base"),
77
Option('cluster', help="Use clustered output.")]
78
def run(self, branch, file, no_collapse=False, no_antialias=False,
79
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:
82
106
ranking = "cluster"
84
108
ranking = "forced"
85
graph.write_ancestry_file(branch, file, not no_collapse,
86
not no_antialias, merge_branch, ranking)
88
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):
89
115
"""Attempt to retrieve ghosts from another branch.
90
116
If the other branch is not supplied, the last-pulled branch is used.
92
118
aliases = ['fetch-missing']
93
119
takes_args = ['branch?']
94
takes_options = [Option('no-fix')]
120
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
95
121
def run(self, branch=None, no_fix=False):
96
122
from fetch_ghosts import fetch_ghosts
97
fetch_ghosts(branch, no_fix)
123
fetch_ghosts(branch, do_reconcile=not no_fix)
99
125
strip_help="""Strip the smallest prefix containing num leading slashes from \
100
126
each file name found in the patch file."""
101
Option.OPTIONS['strip'] = Option('strip', type=int, help=strip_help)
102
class cmd_patch(bzrlib.commands.Command):
129
class cmd_patch(BzrToolsCommand):
103
130
"""Apply a named patch to the current tree.
105
132
takes_args = ['filename?']
106
takes_options = ['strip']
107
def run(self, filename=None, strip=1):
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):
108
137
from patch import patch
109
from bzrlib.branch import Branch
110
b = Branch.open_containing('.')[0]
111
return patch(b, filename, strip)
114
class cmd_shelve(bzrlib.commands.Command):
115
"""Temporarily remove some text changes from the current tree.
116
Use 'unshelve' to restore these changes.
138
from bzrlib.workingtree import WorkingTree
139
wt = WorkingTree.open_containing('.')[0]
142
return patch(wt, filename, strip, silent)
145
class cmd_shelve1(BzrToolsCommand):
146
"""Temporarily set aside some changes from the current tree.
148
Shelve allows you to temporarily put changes you've made "on the shelf",
149
ie. out of the way, until a later time when you can bring them back from
150
the shelf with the 'unshelve1' command.
118
152
Shelve is intended to help separate several sets of text changes that have
119
153
been inappropriately mingled. If you just want to get rid of all changes
120
154
(text and otherwise) and you don't need to restore them later, use revert.
121
If you want to shelve all text changes at once, use shelve --all.
123
If filenames are specified, only changes to those files will be shelved.
124
If a revision is specified, all changes since that revision will may be
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.
160
If filenames are specified, only the changes to those files will be
161
shelved, other files will be left untouched.
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.
127
175
takes_args = ['file*']
128
takes_options = [Option('all',
129
help='Shelve all changes without prompting'),
130
'message', 'revision']
131
def run(self, all=False, file_list=None, message=None, revision=None):
132
if file_list is not None and len(file_list) > 0:
133
branchdir = file_list[0]
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.')]
183
def run(self, all=False, file_list=None, message=None, revision=None,
137
185
if revision is not None and revision:
138
186
if len(revision) == 1:
139
187
revision = revision[0]
141
raise BzrCommandError("shelve only accepts a single revision "
189
raise CommandError("shelve only accepts a single revision "
145
return s.shelve(all, message, revision, file_list)
148
class cmd_unshelve(bzrlib.commands.Command):
149
"""Restore previously-shelved changes to the current tree.
192
source = BzrPatchSource(revision, file_list)
193
s = shelf.Shelf(source.base)
194
s.shelve(source, all, message, no_color)
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."""
242
takes_args = ['subcommand', 'args*']
244
subcommands = [cmd_shelf_list, cmd_shelf_delete, cmd_shelf_switch,
245
cmd_shelf_show, cmd_shelf_upgrade]
247
def run(self, subcommand, args_list):
250
if args_list is None:
252
cmd = self._get_cmd_object(subcommand)
253
source = BzrPatchSource()
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.
156
class cmd_shell(bzrlib.commands.Command):
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):
321
source = BzrPatchSource()
322
s = shelf.Shelf(source.base)
323
s.unshelve(source, patch, all, force, no_color)
327
class cmd_shell(BzrToolsCommand):
157
328
"""Begin an interactive shell tailored for bzr.
158
329
Bzr commands can be used without typing bzr first, and will be run natively
159
330
when possible. Tab completion is tailored for bzr. The shell prompt shows
186
366
takes_args = ["branch?"]
187
367
def run(self, branch=None):
188
from branchhistory import branch_history
368
from branchhistory import branch_history
189
369
return branch_history(branch)
191
commands = [cmd_shelve, cmd_unshelve, cmd_clean_tree, cmd_graph_ancestry,
192
cmd_fetch_ghosts, cmd_patch, cmd_shell, cmd_fix, cmd_branch_history]
194
command_decorators = []
196
command_decorators = []
198
import bzrlib.builtins
199
if not hasattr(bzrlib.builtins, "cmd_push"):
200
commands.append(push.cmd_push)
202
command_decorators.append(push.cmd_push)
204
from errors import NoPyBaz
207
commands.append(baz_import.cmd_baz_import_branch)
208
commands.append(baz_import.cmd_baz_import)
211
class cmd_baz_import(bzrlib.commands.Command):
212
"""Disabled. (Requires PyBaz)"""
213
takes_args = ['to_root_dir?', 'from_archive?']
214
takes_options = ['verbose']
215
def run(self, to_root_dir=None, from_archive=None, verbose=False):
216
print "This command is disabled. Please install PyBaz."
217
commands.append(cmd_baz_import)
220
if hasattr(bzrlib.commands, 'register_command'):
221
for command in commands:
222
bzrlib.commands.register_command(command)
223
for command in command_decorators:
224
command._original_command = bzrlib.commands.register_command(
231
from doctest import DocTestSuite
232
from unittest import TestSuite, TestLoader
237
result.addTest(DocTestSuite(bzrtools))
238
result.addTest(clean_tree.test_suite())
239
result.addTest(DocTestSuite(baz_import))
240
result.addTest(tests.test_suite())
241
result.addTest(TestLoader().loadTestsFromModule(shelf_tests))
242
result.addTest(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, [])