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
28
from errors import CommandError
29
from patchsource import BzrPatchSource
31
import bzrlib.commands
32
from bzrlib.branch import Branch
33
from bzrlib.commands import get_cmd_object
34
from bzrlib.errors import BzrCommandError
35
from bzrlib.option import Option, RegistryOption
38
class cmd_graph_ancestry(BzrToolsCommand):
39
"""Produce ancestry graphs using dot.
41
Output format is detected according to file extension. Some of the more
42
common output formats are html, png, gif, svg, ps. An extension of '.dot'
43
will cause a dot graph file to be produced. HTML output has mouseovers
44
that show the commit message.
46
Branches are labeled r?, where ? is the revno. If they have no revno,
47
with the last 5 characters of their revision identifier are used instead.
49
The value starting with d is "(maximum) distance from the null revision".
51
If --merge-branch is specified, the two branches are compared and a merge
59
blue COMMON non-history ancestor
60
green Merge base (COMMON ancestor farthest from the null revision)
61
dotted Ghost revision (missing from branch storage)
63
Ancestry is usually collapsed by skipping revisions with a single parent
64
and descendant. The number of skipped revisions is shown on the arrow.
65
This feature can be disabled with --no-collapse.
67
By default, revisions are ordered by distance from root, but they can be
68
clustered instead using --cluster.
70
If available, rsvg is used to antialias PNG and JPEG output, but this can
71
be disabled with --no-antialias.
73
takes_args = ['file', 'merge_branch?']
74
takes_options = [Option('no-collapse', help="Do not skip simple nodes."),
75
Option('no-antialias',
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:
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):
104
"""Attempt to retrieve ghosts from another branch.
105
If the other branch is not supplied, the last-pulled branch is used.
107
aliases = ['fetch-missing']
108
takes_args = ['branch?']
109
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
110
def run(self, branch=None, no_fix=False):
111
from fetch_ghosts import fetch_ghosts
112
fetch_ghosts(branch, do_reconcile=not no_fix)
114
strip_help="""Strip the smallest prefix containing num leading slashes from \
115
each file name found in the patch file."""
118
class cmd_patch(BzrToolsCommand):
119
"""Apply a named patch to the current tree.
121
takes_args = ['filename?']
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):
126
from patch import patch
127
from bzrlib.workingtree import WorkingTree
128
wt = WorkingTree.open_containing('.')[0]
131
return patch(wt, filename, strip, silent)
134
class cmd_shelve1(BzrToolsCommand):
135
"""Temporarily set aside some changes from the current tree.
137
Shelve allows you to temporarily put changes you've made "on the shelf",
138
ie. out of the way, until a later time when you can bring them back from
139
the shelf with the 'unshelve1' command.
141
Shelve is intended to help separate several sets of text changes that have
142
been inappropriately mingled. If you just want to get rid of all changes
143
(text and otherwise) and you don't need to restore them later, use revert.
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.
149
If filenames are specified, only the changes to those files will be
150
shelved, other files will be left untouched.
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.
164
takes_args = ['file*']
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.')]
172
def run(self, all=False, file_list=None, message=None, revision=None,
174
if revision is not None and revision:
175
if len(revision) == 1:
176
revision = revision[0]
178
raise CommandError("shelve only accepts a single revision "
181
source = BzrPatchSource(revision, file_list)
182
s = shelf.Shelf(source.base)
183
s.shelve(source, all, message, no_color)
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."""
231
takes_args = ['subcommand', 'args*']
233
subcommands = [cmd_shelf_list, cmd_shelf_delete, cmd_shelf_switch,
234
cmd_shelf_show, cmd_shelf_upgrade]
236
def run(self, subcommand, args_list):
239
if args_list is None:
241
cmd = self._get_cmd_object(subcommand)
242
source = BzrPatchSource()
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.
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):
310
source = BzrPatchSource()
311
s = shelf.Shelf(source.base)
312
s.unshelve(source, patch, all, force, no_color)
316
class cmd_shell(BzrToolsCommand):
317
"""Begin an interactive shell tailored for bzr.
318
Bzr commands can be used without typing bzr first, and will be run natively
319
when possible. Tab completion is tailored for bzr. The shell prompt shows
320
the branch nick, revno, and path.
322
If it encounters any moderately complicated shell command, it will punt to
327
bzr bzrtools:287/> status
330
bzr bzrtools:287/> status --[TAB][TAB]
331
--all --help --revision --show-ids
332
bzr bzrtools:287/> status --
336
help='Branch in which to start the shell, '
337
'rather than the one containing the working directory.',
342
def run(self, directory=None):
344
return shell.run_shell(directory)
347
class cmd_branch_history(BzrToolsCommand):
349
Display the development history of a branch.
351
Each different committer or branch nick is considered a different line of
352
development. Committers are treated as the same if they have the same
353
name, or if they have the same email address.
355
takes_args = ["branch?"]
356
def run(self, branch=None):
357
from branchhistory import branch_history
358
return branch_history(branch)
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, [])