2
Various useful plugins for working with bzr.
1
# Copyright (C) 2005, 2006, 2007, 2011 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
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])
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
23
from bzrlib import help, urlutils
24
from bzrlib.plugins.bzrtools import shelf
27
from bzrlib.plugins import bzrtools
28
from command import BzrToolsCommand
48
29
from errors import CommandError
49
30
from patchsource import BzrPatchSource
50
from shelf import Shelf
51
from switch import cmd_switch
55
import bzrlib.builtins
57
32
import bzrlib.commands
33
from bzrlib.branch import Branch
58
34
from bzrlib.commands import get_cmd_object
59
35
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):
36
from bzrlib.option import Option, RegistryOption
39
class cmd_graph_ancestry(BzrToolsCommand):
99
40
"""Produce ancestry graphs using dot.
101
42
Output format is detected according to file extension. Some of the more
102
43
common output formats are html, png, gif, svg, ps. An extension of '.dot'
103
44
will cause a dot graph file to be produced. HTML output has mouseovers
130
71
If available, rsvg is used to antialias PNG and JPEG output, but this can
131
72
be disabled with --no-antialias.
133
takes_args = ['branch', 'file']
134
takes_options = [Option('no-collapse', help="Do not skip simple nodes"),
74
takes_args = ['file', 'merge_branch?']
75
takes_options = [Option('no-collapse', help="Do not skip simple nodes."),
135
76
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):
77
help="Do not use rsvg to produce antialiased output."),
78
Option('merge-branch', type=str,
79
help="Use this branch to calcuate a merge base."),
80
Option('cluster', help="Use clustered output."),
81
Option('max-distance',
82
help="Show no nodes farther than this.", type=int),
84
help='Source branch to use (default is current'
89
def run(self, file, merge_branch=None, no_collapse=False,
90
no_antialias=False, cluster=False, max_distance=100,
92
if max_distance == -1:
144
96
ranking = "cluster"
146
98
ranking = "forced"
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):
99
graph.write_ancestry_file(directory, file, not no_collapse,
100
not no_antialias, merge_branch, ranking,
101
max_distance=max_distance)
104
class cmd_fetch_ghosts(BzrToolsCommand):
151
105
"""Attempt to retrieve ghosts from another branch.
152
106
If the other branch is not supplied, the last-pulled branch is used.
154
108
aliases = ['fetch-missing']
155
109
takes_args = ['branch?']
156
takes_options = [Option('no-fix')]
110
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
157
111
def run(self, branch=None, no_fix=False):
158
112
from fetch_ghosts import fetch_ghosts
159
fetch_ghosts(branch, no_fix)
113
fetch_ghosts(branch, do_reconcile=not no_fix)
161
115
strip_help="""Strip the smallest prefix containing num leading slashes from \
162
116
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):
119
class cmd_patch(BzrToolsCommand):
166
120
"""Apply a named patch to the current tree.
168
122
takes_args = ['filename?']
169
takes_options = [Option('strip', type=int, help=strip_help)]
170
def run(self, filename=None, strip=-1, bzrdiff=0):
123
takes_options = [Option('strip', type=int, short_name='p',
125
Option('silent', help='Suppress chatter.')]
126
def run(self, filename=None, strip=None, silent=False):
171
127
from patch import patch
172
128
from bzrlib.workingtree import WorkingTree
173
129
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):
132
return patch(wt, filename, strip, silent)
135
class cmd_shelve1(BzrToolsCommand):
182
136
"""Temporarily set aside some changes from the current tree.
184
138
Shelve allows you to temporarily put changes you've made "on the shelf",
185
139
ie. out of the way, until a later time when you can bring them back from
186
the shelf with the 'unshelve' command.
140
the shelf with the 'unshelve1' command.
188
142
Shelve is intended to help separate several sets of text changes that have
189
143
been inappropriately mingled. If you just want to get rid of all changes
190
144
(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.
145
If you want to shelve all text changes at once, use shelve1 --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.
147
By default shelve1 asks you what you want to shelve, press '?' at the
148
prompt to get help. To shelve everything run shelve1 --all.
196
150
If filenames are specified, only the changes to those files will be
197
151
shelved, other files will be left untouched.
400
370
If --branch is specified, the branch will be deleted too, but only if the
401
371
the branch has no new commits (relative to its parent).
373
If bzr-pipeline is also installed, the --store option will store changes
374
in the branch before deleting the tree. To restore the changes, do::
376
bzr checkout --lightweight $BRANCH $CHECKOUT
377
bzr switch-pipe -d $CHECKOUT `bzr nick -d $CHECKOUT`
403
takes_options = [Option("branch", help="Remove associtated branch from"
379
takes_options = [Option("branch", help="Remove associated branch from"
381
RegistryOption('change_policy',
382
'How to handle changed files',
384
('bzrlib.plugins.bzrtools.zap',
385
'change_policy_registry'),
405
388
takes_args = ["checkout"]
406
def run(self, checkout, branch=False):
408
return zap(checkout, remove_branch=branch)
411
class cmd_cbranch(bzrlib.commands.Command):
389
def run(self, checkout, branch=False, change_policy=None):
391
change_policy_registry,
395
if change_policy is None:
396
change_policy = change_policy_registry.get()
397
if change_policy is StoreChanges:
399
import bzrlib.plugins.pipeline
401
raise BzrCommandError('--store requires bzr-pipeline.')
402
return zap(checkout, remove_branch=branch, policy=change_policy)
405
class cmd_cbranch(BzrToolsCommand):
413
407
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).
409
When you cbranch, bzr looks up a target location in locations.conf, and
410
creates the branch there.
412
In your locations.conf, add the following lines:
413
[/working_directory_root]
414
cbranch_target = /branch_root
415
cbranch_target:policy = appendpath
417
This will mean that if you run "bzr cbranch foo/bar foo/baz" in the
418
working directory root, the branch will be created in
419
"/branch_root/foo/baz"
421
NOTE: cbranch also supports "cbranch_root", but that behaviour is
430
takes_options = [Option("lightweight",
431
help="Create a lightweight checkout"), 'revision']
424
takes_options = [Option("lightweight",
425
help="Create a lightweight checkout."), 'revision',
426
Option('files-from', type=unicode,
427
help='Accelerate checkout using files from this'
430
help='Hard-link files from source/files-from tree'
432
432
takes_args = ["source", "target?"]
433
def run(self, source, target=None, lightweight=False, revision=None):
433
def run(self, source, target=None, lightweight=False, revision=None,
434
files_from=None, hardlink=False):
434
435
from cbranch import cbranch
435
return cbranch(source, target, lightweight=lightweight,
439
class cmd_branches(bzrlib.commands.Command):
436
return cbranch(source, target, lightweight=lightweight,
437
revision=revision, files_from=files_from,
441
class cmd_list_branches(BzrToolsCommand):
440
442
"""Scan a location for branches"""
445
from bzrlib import commands
446
return commands.plugin_cmds.get_info('list-branches').aliases
441
448
takes_args = ["location?"]
442
449
def run(self, location=None):
443
450
from branches import branches
444
451
return branches(location)
453
class cmd_trees(BzrToolsCommand):
454
"""Scan a location for trees"""
455
takes_args = ['location?']
456
def run(self, location='.'):
457
from bzrlib.workingtree import WorkingTree
458
from bzrlib.transport import get_transport
459
t = get_transport(location)
460
for tree in WorkingTree.find_trees(location):
461
self.outf.write('%s\n' % t.relpath(
462
tree.bzrdir.root_transport.base))
447
class cmd_multi_pull(bzrlib.commands.Command):
464
class cmd_multi_pull(BzrToolsCommand):
448
465
"""Pull all the branches under a location, e.g. a repository.
450
467
Both branches present in the directory and the branches of checkouts are
453
470
takes_args = ["location?"]
454
471
def run(self, location=None):
455
from bzrlib.branch import Branch
456
472
from bzrlib.transport import get_transport
457
473
from bzrtools import iter_branch_tree
458
474
if location is None:
460
476
t = get_transport(location)
477
possible_transports = []
461
478
if not t.listable():
462
479
print "Can't list this type of location."
480
497
print "Pulling %s from %s" % (relpath, parent)
482
pullable.pull(Branch.open(parent))
499
branch_t = get_transport(parent, possible_transports)
500
pullable.pull(Branch.open_from_transport(branch_t))
483
501
except Exception, e:
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
506
class cmd_import(BzrToolsCommand):
507
"""Import sources from a directory, tarball or zip file
509
This command will import a directory, tarball or zip file into a bzr
510
tree, replacing any versioned files already present. If a directory is
511
specified, it is used as the target. If the directory does not exist, or
512
is not versioned, it is created.
512
514
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.
516
If the tarball or zip has a single root directory, that directory is
517
stripped when extracting the tarball. This is not done for directories.
518
520
takes_args = ['source', 'tree?']
519
521
def run(self, source, tree=None):
520
522
from upstream_import import do_import
521
523
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):
526
class cmd_cdiff(BzrToolsCommand):
542
527
"""A color version of bzr's diff"""
543
528
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):
529
takes_options = list(get_cmd_object('diff').takes_options) + [
530
RegistryOption.from_kwargs('color',
531
'Color mode to use.',
532
title='Color Mode', value_switches=False, enum_switch=True,
533
never='Never colorize output.',
534
auto='Only colorize output if terminal supports it and STDOUT is a'
536
always='Always colorize output (default).'),
537
Option('check-style',
538
help='Warn if trailing whitespace or spurious changes have been'
541
def run(self, color='always', check_style=False, *args, **kwargs):
546
542
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())
543
colordiff(color, check_style, *args, **kwargs)
546
class cmd_conflict_diff(BzrToolsCommand):
548
"""Compare a conflicted file against BASE."""
550
encoding_type = 'exact'
551
takes_args = ['file*']
553
RegistryOption.from_kwargs('direction', 'Direction of comparison.',
554
value_switches=True, enum_switch=False,
555
other='Compare OTHER against common base.',
556
this='Compare THIS against common base.')]
558
def run(self, file_list, direction='other'):
559
from bzrlib.plugins.bzrtools.colordiff import DiffWriter
560
from conflict_diff import ConflictDiffer
561
dw = DiffWriter(self.outf, check_style=False, color='auto')
562
ConflictDiffer().run(dw, file_list, direction)
565
class cmd_rspush(BzrToolsCommand):
566
"""Upload this branch to another location using rsync.
568
If no location is specified, the last-used location will be used. To
569
prevent dirty trees from being uploaded, rspush will error out if there are
570
unknown files or local changes. It will also error out if the upstream
571
directory is non-empty and not an earlier version of the branch.
573
takes_args = ['location?']
574
takes_options = [Option('overwrite', help='Ignore differences between'
575
' branches and overwrite unconditionally.'),
576
Option('no-tree', help='Do not push the working tree,'
579
def run(self, location=None, overwrite=False, no_tree=False):
580
from bzrlib import workingtree
582
cur_branch = workingtree.WorkingTree.open_containing(".")[0]
583
bzrtools.rspush(cur_branch, location, overwrite=overwrite,
584
working_tree=not no_tree)
587
class cmd_link_tree(BzrToolsCommand):
588
"""Hardlink matching files to another tree.
590
Only files with identical content and execute bit will be linked.
592
takes_args = ['location']
594
def run(self, location):
595
from bzrlib import workingtree
596
from bzrlib.plugins.bzrtools.link_tree import link_tree
597
target_tree = workingtree.WorkingTree.open_containing(".")[0]
598
source_tree = workingtree.WorkingTree.open(location)
599
target_tree.lock_write()
601
source_tree.lock_read()
603
link_tree(target_tree, source_tree)
610
class cmd_create_mirror(BzrToolsCommand):
611
"""Create a mirror of another branch.
613
This is similar to `bzr branch`, but copies more settings, including the
614
submit branch and nickname.
616
It sets the public branch and parent of the target to the source location.
619
takes_args = ['source', 'target']
621
def run(self, source, target):
622
source_branch = Branch.open(source)
623
from bzrlib.plugins.bzrtools.mirror import create_mirror
624
create_mirror(source_branch, target, [])