2
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
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])
48
from errors import CommandError, NoPyBaz
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
49
29
from patchsource import BzrPatchSource
50
from shelf import Shelf
51
from switch import cmd_switch
55
33
import bzrlib.builtins
57
34
import bzrlib.commands
35
from bzrlib.branch import Branch
36
from bzrlib.bzrdir import BzrDir
58
37
from bzrlib.commands import get_cmd_object
59
38
from bzrlib.errors import BzrCommandError
60
from bzrlib.help import command_usage
61
39
import bzrlib.ignores
40
from bzrlib.trace import note
62
41
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):
43
from command import BzrToolsCommand
46
class cmd_clean_tree(BzrToolsCommand):
70
47
"""Remove unwanted files from working tree.
72
49
By default, only unknown files, not ignored files, are deleted. Versioned
82
59
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):
61
takes_options = [Option('ignored', help='Delete all ignored files.'),
62
Option('detritus', help='Delete conflict files, merge'
63
' backups, and failed selftest dirs.'),
65
help='Delete files unknown to bzr (default).'),
66
Option('dry-run', help='Show files to delete instead of'
68
Option('force', help='Do not prompt before deleting.')]
69
def run(self, unknown=False, ignored=False, detritus=False, dry_run=False,
92
71
from clean_tree import clean_tree
93
72
if not (unknown or ignored or detritus):
95
76
clean_tree('.', unknown=unknown, ignored=ignored, detritus=detritus,
99
class cmd_graph_ancestry(bzrlib.commands.Command):
77
dry_run=dry_run, no_prompt=force)
80
class cmd_graph_ancestry(BzrToolsCommand):
100
81
"""Produce ancestry graphs using dot.
102
83
Output format is detected according to file extension. Some of the more
131
112
If available, rsvg is used to antialias PNG and JPEG output, but this can
132
113
be disabled with --no-antialias.
134
takes_args = ['branch', 'file']
135
takes_options = [Option('no-collapse', help="Do not skip simple nodes"),
115
takes_args = ['file', 'merge_branch?']
116
takes_options = [Option('no-collapse', help="Do not skip simple nodes."),
136
117
Option('no-antialias',
137
help="Do not use rsvg to produce antialiased output"),
138
Option('merge-branch', type=str,
139
help="Use this branch to calcuate a merge base"),
140
Option('cluster', help="Use clustered output.")]
141
def run(self, branch, file, no_collapse=False, no_antialias=False,
142
merge_branch=None, cluster=False):
118
help="Do not use rsvg to produce antialiased output."),
119
Option('merge-branch', type=str,
120
help="Use this branch to calcuate a merge base."),
121
Option('cluster', help="Use clustered output."),
122
Option('max-distance',
123
help="Show no nodes farther than this.", type=int),
125
help='Source branch to use (default is current'
130
def run(self, file, merge_branch=None, no_collapse=False,
131
no_antialias=False, cluster=False, max_distance=100,
133
if max_distance == -1:
145
137
ranking = "cluster"
147
139
ranking = "forced"
148
graph.write_ancestry_file(branch, file, not no_collapse,
149
not no_antialias, merge_branch, ranking)
152
class cmd_fetch_ghosts(bzrlib.commands.Command):
140
graph.write_ancestry_file(directory, file, not no_collapse,
141
not no_antialias, merge_branch, ranking,
142
max_distance=max_distance)
145
class cmd_fetch_ghosts(BzrToolsCommand):
153
146
"""Attempt to retrieve ghosts from another branch.
154
147
If the other branch is not supplied, the last-pulled branch is used.
156
149
aliases = ['fetch-missing']
157
150
takes_args = ['branch?']
158
takes_options = [Option('no-fix')]
151
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
159
152
def run(self, branch=None, no_fix=False):
160
153
from fetch_ghosts import fetch_ghosts
161
154
fetch_ghosts(branch, no_fix)
163
156
strip_help="""Strip the smallest prefix containing num leading slashes from \
164
157
each file name found in the patch file."""
165
Option.OPTIONS['bzrdiff'] = Option('bzrdiff',type=None,
166
help="""Handle extra bzr tags""")
169
class cmd_patch(bzrlib.commands.Command):
160
class cmd_patch(BzrToolsCommand):
170
161
"""Apply a named patch to the current tree.
172
163
takes_args = ['filename?']
173
takes_options = [Option('strip', type=int, help=strip_help)]
174
def run(self, filename=None, strip=-1, bzrdiff=0):
164
takes_options = [Option('strip', type=int, help=strip_help),
165
Option('silent', help='Suppress chatter.')]
166
def run(self, filename=None, strip=None, silent=False):
175
167
from patch import patch
176
168
from bzrlib.workingtree import WorkingTree
177
169
wt = WorkingTree.open_containing('.')[0]
179
if bzrdiff: strip = 0
182
return patch(wt, filename, strip, legacy= not bzrdiff)
185
class cmd_shelve(bzrlib.commands.Command):
172
return patch(wt, filename, strip, silent)
175
class cmd_shelve1(BzrToolsCommand):
186
176
"""Temporarily set aside some changes from the current tree.
188
178
Shelve allows you to temporarily put changes you've made "on the shelf",
189
179
ie. out of the way, until a later time when you can bring them back from
190
the shelf with the 'unshelve' command.
180
the shelf with the 'unshelve1' command.
192
182
Shelve is intended to help separate several sets of text changes that have
193
183
been inappropriately mingled. If you just want to get rid of all changes
194
184
(text and otherwise) and you don't need to restore them later, use revert.
195
If you want to shelve all text changes at once, use shelve --all.
185
If you want to shelve all text changes at once, use shelve1 --all.
197
By default shelve asks you what you want to shelve, press '?' at the
198
prompt to get help. To shelve everything run shelve --all.
187
By default shelve1 asks you what you want to shelve, press '?' at the
188
prompt to get help. To shelve everything run shelve1 --all.
200
190
If filenames are specified, only the changes to those files will be
201
191
shelved, other files will be left untouched.
203
193
If a revision is specified, changes since that revision will be shelved.
205
195
You can put multiple items on the shelf. Normally each time you run
206
unshelve the most recently shelved changes will be reinstated. However,
196
unshelve1 the most recently shelved changes will be reinstated. However,
207
197
you can also unshelve changes in a different order by explicitly
208
specifiying which changes to unshelve. This works best when the changes
198
specifiying which changes to unshelve1. This works best when the changes
209
199
don't depend on each other.
211
201
While you have patches on the shelf you can view and manipulate them with
212
202
the 'shelf' command. Run 'bzr shelf -h' for more info.
215
206
takes_args = ['file*']
216
takes_options = ['message', 'revision',
217
Option('all', help='Shelve all changes without prompting'),
218
Option('no-color', help='Never display changes in color')]
207
takes_options = [Option('message',
208
help='A message to associate with the shelved changes.',
209
short_name='m', type=unicode),
211
Option('all', help='Shelve all changes without prompting.'),
212
Option('no-color', help='Never display changes in color.')]
220
214
def run(self, all=False, file_list=None, message=None, revision=None,
338
class cmd_unshelve(bzrlib.commands.Command):
337
class cmd_unshelve1(BzrToolsCommand):
339
338
"""Restore shelved changes.
341
340
By default the most recently shelved changes are restored. However if you
342
341
specify a patch by name those changes will be restored instead.
344
See 'shelve' for more information.
343
See 'shelve1' for more information.
345
aliases = ['unshelve']
346
346
takes_options = [
347
Option('all', help='Unshelve all changes without prompting'),
348
Option('force', help='Force unshelving even if errors occur'),
349
Option('no-color', help='Never display changes in color')
347
Option('all', help='Unshelve all changes without prompting.'),
348
Option('force', help='Force unshelving even if errors occur.'),
349
Option('no-color', help='Never display changes in color.')
351
351
takes_args = ['patch?']
352
352
def run(self, patch=None, all=False, force=False, no_color=False):
353
353
source = BzrPatchSource()
354
s = Shelf(source.base)
354
s = shelf.Shelf(source.base)
355
355
s.unshelve(source, patch, all, force, no_color)
359
class cmd_shell(bzrlib.commands.Command):
359
class cmd_shell(BzrToolsCommand):
360
360
"""Begin an interactive shell tailored for bzr.
361
361
Bzr commands can be used without typing bzr first, and will be run natively
362
362
when possible. Tab completion is tailored for bzr. The shell prompt shows
404
404
If --branch is specified, the branch will be deleted too, but only if the
405
405
the branch has no new commits (relative to its parent).
407
takes_options = [Option("branch", help="Remove associtated branch from"
407
takes_options = [Option("branch", help="Remove associated branch from"
409
Option('force', help='Delete tree even if contents are'
409
411
takes_args = ["checkout"]
410
def run(self, checkout, branch=False):
412
def run(self, checkout, branch=False, force=False):
411
413
from zap import zap
412
return zap(checkout, remove_branch=branch)
415
class cmd_cbranch(bzrlib.commands.Command):
414
return zap(checkout, remove_branch=branch, allow_modified=force)
417
class cmd_cbranch(BzrToolsCommand):
417
419
Create a new checkout, associated with a new repository branch.
419
When you cbranch, bzr looks up the repository associated with your current
420
directory in locations.conf. It creates a new branch in that repository
421
with the same name and relative path as the checkout you request.
423
The locations.conf parameter is "cbranch_root". So if you want
424
cbranch operations in /home/jrandom/bigproject to produce branches in
425
/home/jrandom/bigproject/repository, you'd add this:
427
[/home/jrandom/bigproject]
428
cbranch_root = /home/jrandom/bigproject/repository
430
Note that if "/home/jrandom/bigproject/repository" isn't a repository,
431
standalone branches will be produced. Standalone branches will also
432
be produced if the source branch is in 0.7 format (or earlier).
421
When you cbranch, bzr looks up a target location in locations.conf, and
422
creates the branch there.
424
In your locations.conf, add the following lines:
425
[/working_directory_root]
426
cbranch_target = /branch_root
427
cbranch_target:policy = appendpath
429
This will mean that if you run "bzr cbranch foo/bar foo/baz" in the
430
working directory root, the branch will be created in
431
"/branch_root/foo/baz"
433
NOTE: cbranch also supports "cbranch_root", but that behaviour is
434
takes_options = [Option("lightweight",
435
help="Create a lightweight checkout"), 'revision']
436
takes_options = [Option("lightweight",
437
help="Create a lightweight checkout."), 'revision',
438
Option('files-from', type=unicode,
439
help='Accelerate checkout using files from this'
442
help='Hard-link files from source/files-from tree'
436
444
takes_args = ["source", "target?"]
437
def run(self, source, target=None, lightweight=False, revision=None):
445
def run(self, source, target=None, lightweight=False, revision=None,
446
files_from=None, hardlink=False):
438
447
from cbranch import cbranch
439
return cbranch(source, target, lightweight=lightweight,
443
class cmd_branches(bzrlib.commands.Command):
448
return cbranch(source, target, lightweight=lightweight,
449
revision=revision, files_from=files_from,
453
class cmd_branches(BzrToolsCommand):
444
454
"""Scan a location for branches"""
445
455
takes_args = ["location?"]
446
456
def run(self, location=None):
447
457
from branches import branches
448
458
return branches(location)
460
class cmd_trees(BzrToolsCommand):
461
"""Scan a location for trees"""
462
takes_args = ['location?']
463
def run(self, location='.'):
464
from bzrlib.workingtree import WorkingTree
465
from bzrlib.transport import get_transport
466
t = get_transport(location)
467
for tree in WorkingTree.find_trees(location):
468
self.outf.write('%s\n' % t.relpath(
469
tree.bzrdir.root_transport.base))
451
class cmd_multi_pull(bzrlib.commands.Command):
471
class cmd_multi_pull(BzrToolsCommand):
452
472
"""Pull all the branches under a location, e.g. a repository.
454
474
Both branches present in the directory and the branches of checkouts are
457
477
takes_args = ["location?"]
458
478
def run(self, location=None):
459
from bzrlib.branch import Branch
460
479
from bzrlib.transport import get_transport
461
480
from bzrtools import iter_branch_tree
462
481
if location is None:
464
483
t = get_transport(location)
484
possible_transports = []
465
485
if not t.listable():
466
486
print "Can't list this type of location."
484
504
print "Pulling %s from %s" % (relpath, parent)
486
pullable.pull(Branch.open(parent))
506
branch_t = get_transport(parent, possible_transports)
507
pullable.pull(Branch.open_from_transport(branch_t))
487
508
except Exception, e:
491
class cmd_branch_mark(bzrlib.commands.Command):
493
Add, view or list branch markers <EXPERIMENTAL>
495
To add a mark, do 'bzr branch-mark MARK'.
496
To list marks, do 'bzr branch-mark' (this lists all marks for the branch's
498
To delete a mark, do 'bzr branch-mark --delete MARK'
500
These marks can be used to track a branch's status.
502
takes_args = ['mark?', 'branch?']
503
takes_options = [Option('delete', help='Delete this mark')]
504
def run(self, mark=None, branch=None, delete=False):
505
from branch_mark import branch_mark
506
branch_mark(mark, branch, delete)
509
class cmd_import(bzrlib.commands.Command):
510
"""Import sources from a tarball
512
This command will import a tarball into a bzr tree, replacing any versioned
513
files already present. If a directory is specified, it is used as the
514
target. If the directory does not exist, or is not versioned, it is
513
class cmd_import(BzrToolsCommand):
514
"""Import sources from a directory, tarball or zip file
516
This command will import a directory, tarball or zip file into a bzr
517
tree, replacing any versioned files already present. If a directory is
518
specified, it is used as the target. If the directory does not exist, or
519
is not versioned, it is created.
517
521
Tarballs may be gzip or bzip2 compressed. This is autodetected.
519
If the tarball has a single root directory, that directory is stripped
520
when extracting the tarball.
523
If the tarball or zip has a single root directory, that directory is
524
stripped when extracting the tarball. This is not done for directories.
523
527
takes_args = ['source', 'tree?']
524
528
def run(self, source, tree=None):
525
529
from upstream_import import do_import
526
530
do_import(source, tree)
529
class cmd_cdiff(bzrlib.commands.Command):
533
class cmd_cdiff(BzrToolsCommand):
530
534
"""A color version of bzr's diff"""
531
535
takes_args = property(lambda x: get_cmd_object('diff').takes_args)
532
takes_options = property(lambda x: get_cmd_object('diff').takes_options)
533
def run(*args, **kwargs):
536
takes_options = list(get_cmd_object('diff').takes_options) + [
537
Option('check-style',
538
help='Warn if trailing whitespace or spurious changes have been'
541
def run(self, check_style=False, *args, **kwargs):
534
542
from colordiff import colordiff
535
colordiff(*args, **kwargs)
538
class cmd_baz_import(bzrlib.commands.Command):
539
"""Import an Arch or Baz archive into a bzr repository.
541
This command should be used on local archives (or mirrors) only. It is
542
quite slow on remote archives.
544
reuse_history allows you to specify any previous imports you
545
have done of different archives, which this archive has branches
546
tagged from. This will dramatically reduce the time to convert
547
the archive as it will not have to convert the history already
548
converted in that other branch.
550
If you specify prefixes, only branches whose names start with that prefix
551
will be imported. Skipped branches will be listed, so you can import any
552
branches you missed by accident. Here's an example of doing a partial
553
import from thelove@canonical.com:
554
bzr baz-import thelove thelove@canonical.com --prefixes dists:talloc-except
556
takes_args = ['to_root_dir', 'from_archive', 'reuse_history*']
557
takes_options = ['verbose', Option('prefixes', type=str,
558
help="Prefixes of branches to import, colon-separated")]
560
def run(self, to_root_dir, from_archive, verbose=False,
561
reuse_history_list=[], prefixes=None):
562
from errors import NoPyBaz
565
baz_import.baz_import(to_root_dir, from_archive,
566
verbose, reuse_history_list, prefixes)
568
print "This command is disabled. Please install PyBaz."
571
class cmd_baz_import_branch(bzrlib.commands.Command):
572
"""Import an Arch or Baz branch into a bzr branch."""
573
takes_args = ['to_location', 'from_branch?', 'reuse_history*']
574
takes_options = ['verbose', Option('max-count', type=int)]
576
def run(self, to_location, from_branch=None, fast=False, max_count=None,
577
verbose=False, dry_run=False, reuse_history_list=[]):
578
from errors import NoPyBaz
581
baz_import.baz_import_branch(to_location, from_branch, fast,
582
max_count, verbose, dry_run,
585
print "This command is disabled. Please install PyBaz."
590
cmd_baz_import_branch,
610
commands.append(rspush.cmd_rspush)
612
if hasattr(bzrlib.commands, 'register_command'):
613
for command in commands:
614
bzrlib.commands.register_command(command)
618
from bzrlib.tests.TestUtil import TestLoader
620
from doctest import DocTestSuite, ELLIPSIS
621
from unittest import TestSuite
622
import tests.clean_tree
623
import upstream_import
625
import tests.blackbox
626
import tests.shelf_tests
628
result.addTest(DocTestSuite(bzrtools, optionflags=ELLIPSIS))
629
result.addTest(tests.clean_tree.test_suite())
632
result.addTest(DocTestSuite(baz_import))
635
result.addTest(tests.test_suite())
636
result.addTest(TestLoader().loadTestsFromModule(tests.shelf_tests))
637
result.addTest(tests.blackbox.test_suite())
638
result.addTest(upstream_import.test_suite())
639
result.addTest(zap.test_suite())
543
colordiff(check_style, *args, **kwargs)
546
class cmd_rspush(BzrToolsCommand):
547
"""Upload this branch to another location using rsync.
549
If no location is specified, the last-used location will be used. To
550
prevent dirty trees from being uploaded, rspush will error out if there are
551
unknown files or local changes. It will also error out if the upstream
552
directory is non-empty and not an earlier version of the branch.
554
takes_args = ['location?']
555
takes_options = [Option('overwrite', help='Ignore differences between'
556
' branches and overwrite unconditionally.'),
557
Option('no-tree', help='Do not push the working tree,'
560
def run(self, location=None, overwrite=False, no_tree=False):
561
from bzrlib import workingtree
563
cur_branch = workingtree.WorkingTree.open_containing(".")[0]
564
bzrtools.rspush(cur_branch, location, overwrite=overwrite,
565
working_tree=not no_tree)
568
class cmd_link_tree(BzrToolsCommand):
569
"""Hardlink matching files to another tree.
571
Only files with identical content and execute bit will be linked.
573
takes_args = ['location']
575
def run(self, location):
576
from bzrlib import workingtree
577
from bzrlib.plugins.bzrtools.link_tree import link_tree
578
target_tree = workingtree.WorkingTree.open_containing(".")[0]
579
source_tree = workingtree.WorkingTree.open(location)
580
target_tree.lock_write()
582
source_tree.lock_read()
584
link_tree(target_tree, source_tree)