3
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
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
23
from bzrlib import help, urlutils
27
from command import BzrToolsCommand
6
28
from errors import CommandError
7
29
from patchsource import BzrPatchSource
8
from shelf import Shelf
9
from switch import cmd_switch
13
from bzrlib import DEFAULT_IGNORE
14
33
import bzrlib.builtins
16
34
import bzrlib.commands
35
from bzrlib.branch import Branch
36
from bzrlib.bzrdir import BzrDir
17
37
from bzrlib.commands import get_cmd_object
18
38
from bzrlib.errors import BzrCommandError
19
from bzrlib.help import command_usage
20
from bzrlib.option import Option
21
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__),
25
DEFAULT_IGNORE.append('./.shelf')
26
DEFAULT_IGNORE.append('./.bzr-shelf*')
29
class cmd_clean_tree(bzrlib.commands.Command):
40
from bzrlib.trace import note
41
from bzrlib.option import Option, RegistryOption
43
from command import BzrToolsCommand
46
class cmd_clean_tree(BzrToolsCommand):
30
47
"""Remove unwanted files from working tree.
31
Normally, ignored files are left alone.
49
By default, only unknown files, not ignored files, are deleted. Versioned
50
files are never deleted.
52
Another class is 'detritus', which includes files emitted by bzr during
53
normal operations and selftests. (The value of these files decreases with
56
If no options are specified, unknown files are deleted. Otherwise, option
57
flags are respected, and may be combined.
59
To check what clean-tree will do, use --dry-run.
33
takes_options = [Option('ignored', help='delete all ignored files.'),
34
Option('detritus', help='delete conflict files merge'
35
' backups, and failed selftest dirs. (*.THIS, '
36
'*.BASE, *.OTHER, *~, *.tmp)'),
37
Option('unknown', help='delete files unknown to bzr. (default)'),
38
Option('dry-run', help='show files to delete instead of'
40
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,
41
71
from clean_tree import clean_tree
42
72
if not (unknown or ignored or detritus):
44
clean_tree('.', unknown=unknown, ignored=ignored, detritus=detritus, dry_run=dry_run)
46
class cmd_graph_ancestry(bzrlib.commands.Command):
76
clean_tree('.', unknown=unknown, ignored=ignored, detritus=detritus,
77
dry_run=dry_run, no_prompt=force)
80
class cmd_graph_ancestry(BzrToolsCommand):
47
81
"""Produce ancestry graphs using dot.
49
83
Output format is detected according to file extension. Some of the more
78
112
If available, rsvg is used to antialias PNG and JPEG output, but this can
79
113
be disabled with --no-antialias.
81
takes_args = ['branch', 'file']
82
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."),
83
117
Option('no-antialias',
84
help="Do not use rsvg to produce antialiased output"),
85
Option('merge-branch', type=str,
86
help="Use this branch to calcuate a merge base"),
87
Option('cluster', help="Use clustered output.")]
88
def run(self, branch, file, no_collapse=False, no_antialias=False,
89
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:
92
137
ranking = "cluster"
94
139
ranking = "forced"
95
graph.write_ancestry_file(branch, file, not no_collapse,
96
not no_antialias, merge_branch, ranking)
98
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):
99
146
"""Attempt to retrieve ghosts from another branch.
100
147
If the other branch is not supplied, the last-pulled branch is used.
102
149
aliases = ['fetch-missing']
103
150
takes_args = ['branch?']
104
takes_options = [Option('no-fix')]
151
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
105
152
def run(self, branch=None, no_fix=False):
106
153
from fetch_ghosts import fetch_ghosts
107
154
fetch_ghosts(branch, no_fix)
109
156
strip_help="""Strip the smallest prefix containing num leading slashes from \
110
157
each file name found in the patch file."""
111
Option.OPTIONS['bzrdiff'] = Option('bzrdiff',type=None,
112
help="""Handle extra bzr tags""")
113
class cmd_patch(bzrlib.commands.Command):
160
class cmd_patch(BzrToolsCommand):
114
161
"""Apply a named patch to the current tree.
116
163
takes_args = ['filename?']
117
takes_options = [Option('strip', type=int, help=strip_help)]
118
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):
119
167
from patch import patch
120
168
from bzrlib.workingtree import WorkingTree
121
169
wt = WorkingTree.open_containing('.')[0]
123
if bzrdiff: strip = 0
126
return patch(wt, filename, strip, legacy= not bzrdiff)
128
class cmd_shelve(bzrlib.commands.Command):
172
return patch(wt, filename, strip, silent)
175
class cmd_shelve1(BzrToolsCommand):
129
176
"""Temporarily set aside some changes from the current tree.
131
178
Shelve allows you to temporarily put changes you've made "on the shelf",
132
179
ie. out of the way, until a later time when you can bring them back from
133
the shelf with the 'unshelve' command.
180
the shelf with the 'unshelve1' command.
135
182
Shelve is intended to help separate several sets of text changes that have
136
183
been inappropriately mingled. If you just want to get rid of all changes
137
184
(text and otherwise) and you don't need to restore them later, use revert.
138
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.
140
By default shelve asks you what you want to shelve, press '?' at the
141
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.
143
190
If filenames are specified, only the changes to those files will be
144
191
shelved, other files will be left untouched.
277
class cmd_unshelve(bzrlib.commands.Command):
336
class cmd_unshelve1(BzrToolsCommand):
278
337
"""Restore shelved changes.
280
339
By default the most recently shelved changes are restored. However if you
281
340
specify a patch by name those changes will be restored instead.
283
See 'shelve' for more information.
342
See 'shelve1' for more information.
285
344
takes_options = [
286
Option('all', help='Unshelve all changes without prompting'),
287
Option('force', help='Force unshelving even if errors occur'),
345
Option('all', help='Unshelve all changes without prompting.'),
346
Option('force', help='Force unshelving even if errors occur.'),
347
Option('no-color', help='Never display changes in color.')
289
349
takes_args = ['patch?']
290
def run(self, patch=None, all=False, force=False):
350
def run(self, patch=None, all=False, force=False, no_color=False):
291
351
source = BzrPatchSource()
292
s = Shelf(source.base)
293
s.unshelve(source, patch, all, force)
352
s = shelf.Shelf(source.base)
353
s.unshelve(source, patch, all, force, no_color)
297
class cmd_shell(bzrlib.commands.Command):
357
class cmd_shell(BzrToolsCommand):
298
358
"""Begin an interactive shell tailored for bzr.
299
359
Bzr commands can be used without typing bzr first, and will be run natively
300
360
when possible. Tab completion is tailored for bzr. The shell prompt shows
341
402
If --branch is specified, the branch will be deleted too, but only if the
342
403
the branch has no new commits (relative to its parent).
344
takes_options = [Option("branch", help="Remove associtated branch from"
405
takes_options = [Option("branch", help="Remove associated branch from"
407
Option('force', help='Delete tree even if contents are'
346
409
takes_args = ["checkout"]
347
def run(self, checkout, branch=False):
410
def run(self, checkout, branch=False, force=False):
348
411
from zap import zap
349
return zap(checkout, remove_branch=branch)
352
class cmd_cbranch(bzrlib.commands.Command):
412
return zap(checkout, remove_branch=branch, allow_modified=force)
415
class cmd_cbranch(BzrToolsCommand):
354
417
Create a new checkout, associated with a new repository branch.
356
When you cbranch, bzr looks up the repository associated with your current
357
directory in branches.conf. It creates a new branch in that repository
358
with the same name and relative path as the checkout you request.
360
The branches.conf parameter is "cbranch_root". So if you want
361
cbranch operations in /home/jrandom/bigproject to produce branches in
362
/home/jrandom/bigproject/repository, you'd add this:
364
[/home/jrandom/bigproject]
365
cbranch_root = /home/jrandom/bigproject/repository
367
Note that if "/home/jrandom/bigproject/repository" isn't a repository,
368
standalone branches will be produced. Standalone branches will also
369
be produced if the source branch is in 0.7 format (or earlier).
419
When you cbranch, bzr looks up a target location in locations.conf, and
420
creates the branch there.
422
In your locations.conf, add the following lines:
423
[/working_directory_root]
424
cbranch_target = /branch_root
425
cbranch_target:policy = appendpath
427
This will mean that if you run "bzr cbranch foo/bar foo/baz" in the
428
working directory root, the branch will be created in
429
"/branch_root/foo/baz"
431
NOTE: cbranch also supports "cbranch_root", but that behaviour is
371
takes_options = [Option("lightweight",
372
help="Create a lightweight checkout")]
434
takes_options = [Option("lightweight",
435
help="Create a lightweight checkout."), 'revision',
436
Option('files-from', type=unicode,
437
help='Accelerate checkout using files from this'
440
help='Hard-link files from source/files-from tree'
373
442
takes_args = ["source", "target?"]
374
def run(self, source, target=None, lightweight=False):
443
def run(self, source, target=None, lightweight=False, revision=None,
444
files_from=None, hardlink=False):
375
445
from cbranch import cbranch
376
return cbranch(source, target, lightweight=lightweight)
379
class cmd_branches(bzrlib.commands.Command):
446
return cbranch(source, target, lightweight=lightweight,
447
revision=revision, files_from=files_from,
451
class cmd_branches(BzrToolsCommand):
380
452
"""Scan a location for branches"""
381
453
takes_args = ["location?"]
382
454
def run(self, location=None):
383
455
from branches import branches
384
456
return branches(location)
458
class cmd_trees(BzrToolsCommand):
459
"""Scan a location for trees"""
460
takes_args = ['location?']
461
def run(self, location='.'):
462
from bzrlib.workingtree import WorkingTree
463
from bzrlib.transport import get_transport
464
t = get_transport(location)
465
for tree in WorkingTree.find_trees(location):
466
self.outf.write('%s\n' % t.relpath(
467
tree.bzrdir.root_transport.base))
387
class cmd_multi_pull(bzrlib.commands.Command):
469
class cmd_multi_pull(BzrToolsCommand):
388
470
"""Pull all the branches under a location, e.g. a repository.
390
472
Both branches present in the directory and the branches of checkouts are
393
475
takes_args = ["location?"]
394
476
def run(self, location=None):
395
from bzrlib.branch import Branch
396
477
from bzrlib.transport import get_transport
397
478
from bzrtools import iter_branch_tree
398
479
if location is None:
400
481
t = get_transport(location)
482
possible_transports = []
401
483
if not t.listable():
402
484
print "Can't list this type of location."
420
502
print "Pulling %s from %s" % (relpath, parent)
422
pullable.pull(Branch.open(parent))
504
branch_t = get_transport(parent, possible_transports)
505
pullable.pull(Branch.open_from_transport(branch_t))
423
506
except Exception, e:
427
class cmd_branch_mark(bzrlib.commands.Command):
429
Add, view or list branch markers <EXPERIMENTAL>
431
To add a mark, do 'bzr branch-mark MARK'.
432
To list marks, do 'bzr branch-mark' (this lists all marks for the branch's
434
To delete a mark, do 'bzr branch-mark --delete MARK'
436
These marks can be used to track a branch's status.
438
takes_args = ['mark?', 'branch?']
439
takes_options = [Option('delete', help='Delete this mark')]
440
def run(self, mark=None, branch=None, delete=False):
441
from branch_mark import branch_mark
442
branch_mark(mark, branch, delete)
444
class cmd_import(bzrlib.commands.Command):
445
"""Import sources from a tarball
447
This command will import a tarball into a bzr tree, replacing any versioned
448
files already present. If a directory is specified, it is used as the
449
target. If the directory does not exist, or is not versioned, it is
511
class cmd_import(BzrToolsCommand):
512
"""Import sources from a directory, tarball or zip file
514
This command will import a directory, tarball or zip file into a bzr
515
tree, replacing any versioned files already present. If a directory is
516
specified, it is used as the target. If the directory does not exist, or
517
is not versioned, it is created.
452
519
Tarballs may be gzip or bzip2 compressed. This is autodetected.
454
If the tarball has a single root directory, that directory is stripped
455
when extracting the tarball.
521
If the tarball or zip has a single root directory, that directory is
522
stripped when extracting the tarball. This is not done for directories.
458
525
takes_args = ['source', 'tree?']
459
526
def run(self, source, tree=None):
460
527
from upstream_import import do_import
461
528
do_import(source, tree)
463
class cmd_shove(bzrlib.commands.Command):
464
"""Apply uncommitted changes to another tree
466
This is useful when you start to make changes in one tree, then realize
467
they should really be done in a different tree.
469
Shove is implemented using merge, so:
470
- All changes, including renames and adds, will be applied.
471
- No changes that have already been applied will be applied.
472
- If the target is significantly different from the source, conflicts may
476
takes_args = ['target', 'source?']
477
def run(self, target, source='.'):
478
from shove import do_shove
479
do_shove(source, target)
481
class cmd_cdiff(bzrlib.commands.Command):
531
class cmd_cdiff(BzrToolsCommand):
482
532
"""A color version of bzr's diff"""
483
533
takes_args = property(lambda x: get_cmd_object('diff').takes_args)
484
takes_options = property(lambda x: get_cmd_object('diff').takes_options)
485
def run(*args, **kwargs):
534
takes_options = list(get_cmd_object('diff').takes_options) + [
535
RegistryOption.from_kwargs('color',
536
'Color mode to use.',
537
title='Color Mode', value_switches=False, enum_switch=True,
538
never='Never colorize output.',
539
auto='Only colorize output if terminal supports it and STDOUT is a'
541
always='Always colorize output (default).'),
542
Option('check-style',
543
help='Warn if trailing whitespace or spurious changes have been'
546
def run(self, color='always', check_style=False, *args, **kwargs):
486
547
from colordiff import colordiff
487
colordiff(*args, **kwargs)
489
commands = [cmd_shelve, cmd_unshelve, cmd_shelf, cmd_clean_tree,
490
cmd_graph_ancestry, cmd_fetch_ghosts, cmd_patch, cmd_shell,
491
cmd_branch_history, cmd_zap, cmd_cbranch, cmd_branches,
492
cmd_multi_pull, cmd_switch, cmd_branch_mark, cmd_import, cmd_shove,
496
commands.append(rspush.cmd_rspush)
498
from errors import NoPyBaz
501
commands.append(baz_import.cmd_baz_import_branch)
502
commands.append(baz_import.cmd_baz_import)
505
class cmd_baz_import_branch(bzrlib.commands.Command):
506
"""Disabled. (Requires PyBaz)"""
507
takes_args = ['to_location?', 'from_branch?', 'reuse_history*']
508
takes_options = ['verbose', Option('max-count', type=int)]
509
def run(self, to_location=None, from_branch=None, fast=False,
510
max_count=None, verbose=False, dry_run=False,
511
reuse_history_list=[]):
512
print "This command is disabled. Please install PyBaz."
515
class cmd_baz_import(bzrlib.commands.Command):
516
"""Disabled. (Requires PyBaz)"""
517
takes_args = ['to_root_dir?', 'from_archive?', 'reuse_history*']
518
takes_options = ['verbose', Option('prefixes', type=str,
519
help="Prefixes of branches to import")]
520
def run(self, to_root_dir=None, from_archive=None, verbose=False,
521
reuse_history_list=[], prefixes=None):
522
print "This command is disabled. Please install PyBaz."
523
commands.extend((cmd_baz_import_branch, cmd_baz_import))
526
if hasattr(bzrlib.commands, 'register_command'):
527
for command in commands:
528
bzrlib.commands.register_command(command)
532
from bzrlib.tests.TestUtil import TestLoader
534
from doctest import DocTestSuite, ELLIPSIS
535
from unittest import TestSuite
536
import tests.clean_tree
537
import upstream_import
539
import tests.blackbox
540
import tests.shelf_tests
542
result.addTest(DocTestSuite(bzrtools, optionflags=ELLIPSIS))
543
result.addTest(tests.clean_tree.test_suite())
546
result.addTest(DocTestSuite(baz_import))
549
result.addTest(tests.test_suite())
550
result.addTest(TestLoader().loadTestsFromModule(tests.shelf_tests))
551
result.addTest(tests.blackbox.test_suite())
552
result.addTest(upstream_import.test_suite())
553
result.addTest(zap.test_suite())
548
colordiff(color, check_style, *args, **kwargs)
551
class cmd_rspush(BzrToolsCommand):
552
"""Upload this branch to another location using rsync.
554
If no location is specified, the last-used location will be used. To
555
prevent dirty trees from being uploaded, rspush will error out if there are
556
unknown files or local changes. It will also error out if the upstream
557
directory is non-empty and not an earlier version of the branch.
559
takes_args = ['location?']
560
takes_options = [Option('overwrite', help='Ignore differences between'
561
' branches and overwrite unconditionally.'),
562
Option('no-tree', help='Do not push the working tree,'
565
def run(self, location=None, overwrite=False, no_tree=False):
566
from bzrlib import workingtree
568
cur_branch = workingtree.WorkingTree.open_containing(".")[0]
569
bzrtools.rspush(cur_branch, location, overwrite=overwrite,
570
working_tree=not no_tree)
573
class cmd_link_tree(BzrToolsCommand):
574
"""Hardlink matching files to another tree.
576
Only files with identical content and execute bit will be linked.
578
takes_args = ['location']
580
def run(self, location):
581
from bzrlib import workingtree
582
from bzrlib.plugins.bzrtools.link_tree import link_tree
583
target_tree = workingtree.WorkingTree.open_containing(".")[0]
584
source_tree = workingtree.WorkingTree.open(location)
585
target_tree.lock_write()
587
source_tree.lock_read()
589
link_tree(target_tree, source_tree)