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
23
from bzrlib.lazy_import import lazy_import
24
lazy_import(globals(), """
25
from bzrlib import help, urlutils
29
from command import BzrToolsCommand
6
30
from errors import CommandError
7
31
from patchsource import BzrPatchSource
8
from shelf import Shelf
9
from switch import cmd_switch
13
from bzrlib import DEFAULT_IGNORE
14
35
import bzrlib.builtins
16
36
import bzrlib.commands
37
from bzrlib.branch import Branch
38
from bzrlib.bzrdir import BzrDir
17
39
from bzrlib.commands import get_cmd_object
18
40
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):
30
"""Remove unwanted files from working tree.
32
By default, only unknown files, not ignored files, are deleted. Versioned
33
files are never deleted.
35
Another class is 'detritus', which includes files emitted by bzr during
36
normal operations and selftests. (The value of these files decreases with
39
If no options are specified, unknown files are deleted. Otherwise, option
40
flags are respected, and may be combined.
42
To check what clean-tree will do, use --dry-run.
44
takes_options = [Option('ignored', help='delete all ignored files.'),
45
Option('detritus', help='delete conflict files, merge'
46
' backups, and failed selftest dirs.'),
48
help='delete files unknown to bzr. (default)'),
49
Option('dry-run', help='show files to delete instead of'
51
def run(self, unknown=False, ignored=False, detritus=False, dry_run=False):
52
from clean_tree import clean_tree
53
if not (unknown or ignored or detritus):
55
clean_tree('.', unknown=unknown, ignored=ignored, detritus=detritus,
58
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):
59
50
"""Produce ancestry graphs using dot.
61
52
Output format is detected according to file extension. Some of the more
62
53
common output formats are html, png, gif, svg, ps. An extension of '.dot'
63
54
will cause a dot graph file to be produced. HTML output has mouseovers
90
81
If available, rsvg is used to antialias PNG and JPEG output, but this can
91
82
be disabled with --no-antialias.
93
takes_args = ['branch', 'file']
94
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."),
95
86
Option('no-antialias',
96
help="Do not use rsvg to produce antialiased output"),
97
Option('merge-branch', type=str,
98
help="Use this branch to calcuate a merge base"),
99
Option('cluster', help="Use clustered output.")]
100
def run(self, branch, file, no_collapse=False, no_antialias=False,
101
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:
104
106
ranking = "cluster"
106
108
ranking = "forced"
107
graph.write_ancestry_file(branch, file, not no_collapse,
108
not no_antialias, merge_branch, ranking)
110
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):
111
115
"""Attempt to retrieve ghosts from another branch.
112
116
If the other branch is not supplied, the last-pulled branch is used.
114
118
aliases = ['fetch-missing']
115
119
takes_args = ['branch?']
116
takes_options = [Option('no-fix')]
120
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
117
121
def run(self, branch=None, no_fix=False):
118
122
from fetch_ghosts import fetch_ghosts
119
fetch_ghosts(branch, no_fix)
123
fetch_ghosts(branch, do_reconcile=not no_fix)
121
125
strip_help="""Strip the smallest prefix containing num leading slashes from \
122
126
each file name found in the patch file."""
123
Option.OPTIONS['bzrdiff'] = Option('bzrdiff',type=None,
124
help="""Handle extra bzr tags""")
125
class cmd_patch(bzrlib.commands.Command):
129
class cmd_patch(BzrToolsCommand):
126
130
"""Apply a named patch to the current tree.
128
132
takes_args = ['filename?']
129
takes_options = [Option('strip', type=int, help=strip_help)]
130
def run(self, filename=None, strip=-1, bzrdiff=0):
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):
131
137
from patch import patch
132
138
from bzrlib.workingtree import WorkingTree
133
139
wt = WorkingTree.open_containing('.')[0]
135
if bzrdiff: strip = 0
138
return patch(wt, filename, strip, legacy= not bzrdiff)
140
class cmd_shelve(bzrlib.commands.Command):
142
return patch(wt, filename, strip, silent)
145
class cmd_shelve1(BzrToolsCommand):
141
146
"""Temporarily set aside some changes from the current tree.
143
148
Shelve allows you to temporarily put changes you've made "on the shelf",
144
149
ie. out of the way, until a later time when you can bring them back from
145
the shelf with the 'unshelve' command.
150
the shelf with the 'unshelve1' command.
147
152
Shelve is intended to help separate several sets of text changes that have
148
153
been inappropriately mingled. If you just want to get rid of all changes
149
154
(text and otherwise) and you don't need to restore them later, use revert.
150
If you want to shelve all text changes at once, use shelve --all.
155
If you want to shelve all text changes at once, use shelve1 --all.
152
By default shelve asks you what you want to shelve, press '?' at the
153
prompt to get help. To shelve everything run shelve --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.
155
160
If filenames are specified, only the changes to those files will be
156
161
shelved, other files will be left untouched.
353
380
If --branch is specified, the branch will be deleted too, but only if the
354
381
the branch has no new commits (relative to its parent).
356
takes_options = [Option("branch", help="Remove associtated branch from"
383
takes_options = [Option("branch", help="Remove associated branch from"
385
Option('force', help='Delete tree even if contents are'
358
387
takes_args = ["checkout"]
359
def run(self, checkout, branch=False):
388
def run(self, checkout, branch=False, force=False):
360
389
from zap import zap
361
return zap(checkout, remove_branch=branch)
364
class cmd_cbranch(bzrlib.commands.Command):
390
return zap(checkout, remove_branch=branch, allow_modified=force)
393
class cmd_cbranch(BzrToolsCommand):
366
395
Create a new checkout, associated with a new repository branch.
368
When you cbranch, bzr looks up the repository associated with your current
369
directory in branches.conf. It creates a new branch in that repository
370
with the same name and relative path as the checkout you request.
372
The branches.conf parameter is "cbranch_root". So if you want
373
cbranch operations in /home/jrandom/bigproject to produce branches in
374
/home/jrandom/bigproject/repository, you'd add this:
376
[/home/jrandom/bigproject]
377
cbranch_root = /home/jrandom/bigproject/repository
379
Note that if "/home/jrandom/bigproject/repository" isn't a repository,
380
standalone branches will be produced. Standalone branches will also
381
be produced if the source branch is in 0.7 format (or earlier).
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
383
takes_options = [Option("lightweight",
384
help="Create a lightweight checkout"), 'revision']
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'
385
420
takes_args = ["source", "target?"]
386
def run(self, source, target=None, lightweight=False, revision=None):
421
def run(self, source, target=None, lightweight=False, revision=None,
422
files_from=None, hardlink=False):
387
423
from cbranch import cbranch
388
return cbranch(source, target, lightweight=lightweight,
392
class cmd_branches(bzrlib.commands.Command):
424
return cbranch(source, target, lightweight=lightweight,
425
revision=revision, files_from=files_from,
429
class cmd_branches(BzrToolsCommand):
393
430
"""Scan a location for branches"""
394
431
takes_args = ["location?"]
395
432
def run(self, location=None):
396
433
from branches import branches
397
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))
400
class cmd_multi_pull(bzrlib.commands.Command):
447
class cmd_multi_pull(BzrToolsCommand):
401
448
"""Pull all the branches under a location, e.g. a repository.
403
450
Both branches present in the directory and the branches of checkouts are
406
453
takes_args = ["location?"]
407
454
def run(self, location=None):
408
from bzrlib.branch import Branch
409
455
from bzrlib.transport import get_transport
410
456
from bzrtools import iter_branch_tree
411
457
if location is None:
413
459
t = get_transport(location)
460
possible_transports = []
414
461
if not t.listable():
415
462
print "Can't list this type of location."
433
480
print "Pulling %s from %s" % (relpath, parent)
435
pullable.pull(Branch.open(parent))
482
branch_t = get_transport(parent, possible_transports)
483
pullable.pull(Branch.open_from_transport(branch_t))
436
484
except Exception, e:
440
class cmd_branch_mark(bzrlib.commands.Command):
442
Add, view or list branch markers <EXPERIMENTAL>
444
To add a mark, do 'bzr branch-mark MARK'.
445
To list marks, do 'bzr branch-mark' (this lists all marks for the branch's
447
To delete a mark, do 'bzr branch-mark --delete MARK'
449
These marks can be used to track a branch's status.
451
takes_args = ['mark?', 'branch?']
452
takes_options = [Option('delete', help='Delete this mark')]
453
def run(self, mark=None, branch=None, delete=False):
454
from branch_mark import branch_mark
455
branch_mark(mark, branch, delete)
457
class cmd_import(bzrlib.commands.Command):
458
"""Import sources from a tarball
460
This command will import a tarball into a bzr tree, replacing any versioned
461
files already present. If a directory is specified, it is used as the
462
target. If the directory does not exist, or is not versioned, it is
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.
465
497
Tarballs may be gzip or bzip2 compressed. This is autodetected.
467
If the tarball has a single root directory, that directory is stripped
468
when extracting the tarball.
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.
471
503
takes_args = ['source', 'tree?']
472
504
def run(self, source, tree=None):
473
505
from upstream_import import do_import
474
506
do_import(source, tree)
476
class cmd_shove(bzrlib.commands.Command):
477
"""Apply uncommitted changes to another tree
479
This is useful when you start to make changes in one tree, then realize
480
they should really be done in a different tree.
482
Shove is implemented using merge, so:
483
- All changes, including renames and adds, will be applied.
484
- No changes that have already been applied will be applied.
485
- If the target is significantly different from the source, conflicts may
489
takes_args = ['target', 'source?']
490
def run(self, target, source='.'):
491
from shove import do_shove
492
do_shove(source, target)
494
class cmd_cdiff(bzrlib.commands.Command):
509
class cmd_cdiff(BzrToolsCommand):
495
510
"""A color version of bzr's diff"""
496
511
takes_args = property(lambda x: get_cmd_object('diff').takes_args)
497
takes_options = property(lambda x: get_cmd_object('diff').takes_options)
498
def run(*args, **kwargs):
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):
499
525
from colordiff import colordiff
500
colordiff(*args, **kwargs)
502
commands = [cmd_shelve, cmd_unshelve, cmd_shelf, cmd_clean_tree,
503
cmd_graph_ancestry, cmd_fetch_ghosts, cmd_patch, cmd_shell,
504
cmd_branch_history, cmd_zap, cmd_cbranch, cmd_branches,
505
cmd_multi_pull, cmd_switch, cmd_branch_mark, cmd_import, cmd_shove,
509
commands.append(rspush.cmd_rspush)
511
from errors import NoPyBaz
514
commands.append(baz_import.cmd_baz_import_branch)
515
commands.append(baz_import.cmd_baz_import)
518
class cmd_baz_import_branch(bzrlib.commands.Command):
519
"""Disabled. (Requires PyBaz)"""
520
takes_args = ['to_location?', 'from_branch?', 'reuse_history*']
521
takes_options = ['verbose', Option('max-count', type=int)]
522
def run(self, to_location=None, from_branch=None, fast=False,
523
max_count=None, verbose=False, dry_run=False,
524
reuse_history_list=[]):
525
print "This command is disabled. Please install PyBaz."
528
class cmd_baz_import(bzrlib.commands.Command):
529
"""Disabled. (Requires PyBaz)"""
530
takes_args = ['to_root_dir?', 'from_archive?', 'reuse_history*']
531
takes_options = ['verbose', Option('prefixes', type=str,
532
help="Prefixes of branches to import")]
533
def run(self, to_root_dir=None, from_archive=None, verbose=False,
534
reuse_history_list=[], prefixes=None):
535
print "This command is disabled. Please install PyBaz."
536
commands.extend((cmd_baz_import_branch, cmd_baz_import))
539
if hasattr(bzrlib.commands, 'register_command'):
540
for command in commands:
541
bzrlib.commands.register_command(command)
545
from bzrlib.tests.TestUtil import TestLoader
547
from doctest import DocTestSuite, ELLIPSIS
548
from unittest import TestSuite
549
import tests.clean_tree
550
import upstream_import
552
import tests.blackbox
553
import tests.shelf_tests
555
result.addTest(DocTestSuite(bzrtools, optionflags=ELLIPSIS))
556
result.addTest(tests.clean_tree.test_suite())
559
result.addTest(DocTestSuite(baz_import))
562
result.addTest(tests.test_suite())
563
result.addTest(TestLoader().loadTestsFromModule(tests.shelf_tests))
564
result.addTest(tests.blackbox.test_suite())
565
result.addTest(upstream_import.test_suite())
566
result.addTest(zap.test_suite())
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, [])