19
import sys, os, time, os.path
22
22
from bzrlib.trace import mutter, note, log_error
23
23
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
24
from bzrlib.osutils import quotefn
25
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
24
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
25
from bzrlib.tree import RevisionTree, EmptyTree, Tree
26
from bzrlib.revision import Revision
27
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
29
from bzrlib import merge
29
32
def _squish_command_name(cmd):
34
37
assert cmd.startswith("cmd_")
35
38
return cmd[4:].replace('_','-')
37
def _parse_revision_str(revstr):
38
"""This handles a revision string -> revno.
40
There are several possibilities:
43
'234:345' -> [234, 345]
47
In the future we will also support:
48
'uuid:blah-blah-blah' -> ?
49
'hash:blahblahblah' -> ?
53
if revstr.find(':') != -1:
54
revs = revstr.split(':')
56
raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
61
revs[0] = int(revs[0])
66
revs[1] = int(revs[1])
72
"""Find all python files which are plugins, and load their commands
73
to add to the list of "all commands"
75
The environment variable BZRPATH is considered a delimited set of
76
paths to look through. Each entry is searched for *.py files.
77
If a directory is found, it is also searched, but they are
78
not searched recursively. This allows you to revctl the plugins.
80
Inside the plugin should be a series of cmd_* function, which inherit from
81
the bzrlib.commands.Command class.
83
bzrpath = os.environ.get('BZRPLUGINPATH', '')
88
_platform_extensions = {
94
if _platform_extensions.has_key(sys.platform):
95
platform_extension = _platform_extensions[sys.platform]
97
platform_extension = None
98
for d in bzrpath.split(os.pathsep):
99
plugin_names = {} # This should really be a set rather than a dict
100
for f in os.listdir(d):
101
if f.endswith('.py'):
103
elif f.endswith('.pyc') or f.endswith('.pyo'):
105
elif platform_extension and f.endswith(platform_extension):
106
f = f[:-len(platform_extension)]
107
if f.endswidth('module'):
108
f = f[:-len('module')]
111
if not plugin_names.has_key(f):
112
plugin_names[f] = True
114
plugin_names = plugin_names.keys()
117
sys.path.insert(0, d)
118
for name in plugin_names:
122
if sys.modules.has_key(name):
123
old_module = sys.modules[name]
124
del sys.modules[name]
125
plugin = __import__(name, locals())
126
for k in dir(plugin):
127
if k.startswith('cmd_'):
128
k_unsquished = _unsquish_command_name(k)
129
if not plugin_cmds.has_key(k_unsquished):
130
plugin_cmds[k_unsquished] = getattr(plugin, k)
132
log_error('Two plugins defined the same command: %r' % k)
133
log_error('Not loading the one in %r in dir %r' % (name, d))
136
sys.modules[name] = old_module
137
except ImportError, e:
138
log_error('Unable to load plugin: %r from %r\n%s' % (name, d, e))
143
def _get_cmd_dict(include_plugins=True):
41
"""Return canonical name and class for all registered commands."""
145
42
for k, v in globals().iteritems():
146
43
if k.startswith("cmd_"):
147
d[_unsquish_command_name(k)] = v
149
d.update(_find_plugins())
152
def get_all_cmds(include_plugins=True):
153
"""Return canonical name and class for all registered commands."""
154
for k, v in _get_cmd_dict(include_plugins=include_plugins).iteritems():
158
def get_cmd_class(cmd,include_plugins=True):
44
yield _unsquish_command_name(k), v
46
def get_cmd_class(cmd):
159
47
"""Return the canonical name and command class for a command.
161
49
cmd = str(cmd) # not unicode
163
51
# first look up this command under the specified name
164
cmds = _get_cmd_dict(include_plugins=include_plugins)
166
return cmd, cmds[cmd]
53
return cmd, globals()[_squish_command_name(cmd)]
170
57
# look for any command which claims this as an alias
171
for cmdname, cmdclass in cmds.iteritems():
58
for cmdname, cmdclass in get_all_cmds():
172
59
if cmd in cmdclass.aliases:
173
60
return cmdname, cmdclass
343
229
def run(self, all=False, show_ids=False, file_list=None):
345
b = Branch(file_list[0])
231
b = Branch(file_list[0], lock_mode='r')
346
232
file_list = [b.relpath(x) for x in file_list]
347
233
# special case: only one path was given and it's the root
349
235
if file_list == ['']:
238
b = Branch('.', lock_mode='r')
354
240
status.show_status(b, show_unchanged=all, show_ids=show_ids,
355
241
specific_files=file_list)
395
281
recursively add that parent, rather than giving an error?
397
283
takes_args = ['file+']
398
takes_options = ['verbose', 'no-recurse']
284
takes_options = ['verbose']
400
def run(self, file_list, verbose=False, no_recurse=False):
401
bzrlib.add.smart_add(file_list, verbose, not no_recurse)
286
def run(self, file_list, verbose=False):
287
bzrlib.add.smart_add(file_list, verbose)
404
290
class cmd_relpath(Command):
405
291
"""Show path of a file relative to root"""
406
292
takes_args = ['filename']
409
294
def run(self, filename):
410
295
print Branch(filename).relpath(filename)
414
299
class cmd_inventory(Command):
415
300
"""Show inventory of the current working copy or a revision."""
416
takes_options = ['revision', 'show-ids']
301
takes_options = ['revision']
418
def run(self, revision=None, show_ids=False):
303
def run(self, revision=None):
420
305
if revision == None:
421
306
inv = b.read_working_inventory()
423
308
inv = b.get_revision_inventory(b.lookup_revision(revision))
425
for path, entry in inv.entries():
427
print '%-50s %s' % (path, entry.file_id)
310
for path, entry in inv.iter_entries():
311
print '%-50s %s' % (entry.file_id, path)
432
314
class cmd_move(Command):
471
class cmd_pull(Command):
472
"""Pull any changes from another branch into the current one.
474
If the location is omitted, the last-used location will be used.
475
Both the revision history and the working directory will be
478
This command only works on branches that have not diverged. Branches are
479
considered diverged if both branches have had commits without first
480
pulling from the other.
482
If branches have diverged, you can use 'bzr merge' to pull the text changes
483
from one into the other.
485
takes_args = ['location?']
487
def run(self, location=None):
488
from bzrlib.merge import merge
494
stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
496
if errno == errno.ENOENT:
499
location = stored_loc
501
raise BzrCommandError("No pull location known or specified.")
502
from branch import find_branch, DivergedBranches
503
br_from = find_branch(location)
504
location = pull_loc(br_from)
505
old_revno = br_to.revno()
507
br_to.update_revisions(br_from)
508
except DivergedBranches:
509
raise BzrCommandError("These branches have diverged. Try merge.")
511
merge(('.', -1), ('.', old_revno), check_clean=False)
512
if location != stored_loc:
513
br_to.controlfile("x-pull", "wb").write(location + "\n")
517
class cmd_branch(Command):
518
"""Create a new copy of a branch.
520
If the TO_LOCATION is omitted, the last component of the
521
FROM_LOCATION will be used. In other words,
522
"branch ../foo/bar" will attempt to create ./bar.
524
takes_args = ['from_location', 'to_location?']
526
def run(self, from_location, to_location=None):
528
from bzrlib.merge import merge
530
if to_location is None:
531
to_location = os.path.basename(from_location)
532
# FIXME: If there's a trailing slash, keep removing them
533
# until we find the right bit
536
os.mkdir(to_location)
538
if e.errno == errno.EEXIST:
539
raise BzrCommandError('Target directory "%s" already exists.' %
541
if e.errno == errno.ENOENT:
542
raise BzrCommandError('Parent of "%s" does not exist.' %
546
br_to = Branch(to_location, init=True)
547
from branch import find_branch, DivergedBranches
549
br_from = find_branch(from_location)
551
if e.errno == errno.ENOENT:
552
raise BzrCommandError('Source location "%s" does not exist.' %
557
from_location = pull_loc(br_from)
558
br_to.update_revisions(br_from)
559
merge((to_location, -1), (to_location, 0), this_dir=to_location,
561
br_to.controlfile("x-pull", "wb").write(from_location + "\n")
564
def pull_loc(branch):
565
# TODO: Should perhaps just make attribute be 'base' in
566
# RemoteBranch and Branch?
567
if hasattr(branch, "baseurl"):
568
return branch.baseurl
574
351
class cmd_renames(Command):
575
352
"""Show list of renamed files.
714
490
takes_args = ['file*']
715
takes_options = ['revision', 'diff-options']
716
aliases = ['di', 'dif']
491
takes_options = ['revision']
718
def run(self, revision=None, file_list=None, diff_options=None):
494
def run(self, revision=None, file_list=None):
719
495
from bzrlib.diff import show_diff
720
from bzrlib import find_branch
723
b = find_branch(file_list[0])
724
file_list = [b.relpath(f) for f in file_list]
725
if file_list == ['']:
726
# just pointing to top-of-tree
731
show_diff(b, revision, specific_files=file_list,
732
external_diff_options=diff_options)
497
show_diff(Branch('.'), revision, specific_files=file_list)
819
584
class cmd_log(Command):
820
585
"""Show log of this branch.
822
To request a range of logs, you can use the command -r begin:end
823
-r revision requests a specific revision, -r :end or -r begin: are
587
TODO: Option to limit range.
826
TODO: Make --revision support uuid: and hash: [future tag:] notation.
589
TODO: Option to show in forward order.
830
591
takes_args = ['filename?']
831
takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
833
def run(self, filename=None, timezone='original',
592
takes_options = ['timezone', 'verbose', 'show-ids']
593
def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
838
594
from bzrlib import show_log, find_branch
841
direction = (forward and 'forward') or 'reverse'
844
b = find_branch(filename)
597
b = find_branch(filename, lock_mode='r')
845
598
fp = b.relpath(filename)
847
600
file_id = b.read_working_inventory().path2id(fp)
849
602
file_id = None # points to branch root
604
b = find_branch('.', lock_mode='r')
855
revision = [None, None]
856
elif isinstance(revision, int):
857
revision = [revision, revision]
862
assert len(revision) == 2
864
mutter('encoding log as %r' % bzrlib.user_encoding)
866
# use 'replace' so that we don't abort if trying to write out
867
# in e.g. the default C locale.
868
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
870
607
show_log(b, file_id,
871
608
show_timezone=timezone,
873
610
show_ids=show_ids,
876
start_revision=revision[0],
877
end_revision=revision[1])
1120
851
"""Run internal test suite"""
1123
from bzrlib.selftest import selftest
854
failures, tests = 0, 0
856
import doctest, bzrlib.store
857
bzrlib.trace.verbose = False
859
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
860
bzrlib.tree, bzrlib.commands, bzrlib.add:
861
mf, mt = doctest.testmod(m)
864
print '%-40s %3d tests' % (m.__name__, mt),
866
print '%3d FAILED!' % mf
870
print '%-40s %3d tests' % ('total', tests),
872
print '%3d FAILED!' % failures
1131
880
class cmd_version(Command):
1132
"""Show version of bzr."""
881
"""Show version of bzr"""
1136
885
def show_version():
1137
886
print "bzr (bazaar-ng) %s" % bzrlib.__version__
1138
# is bzrlib itself in a branch?
1139
bzrrev = bzrlib.get_bzr_revision()
1141
print " (bzr checkout, revision %d {%s})" % bzrrev
1142
887
print bzrlib.__copyright__
1143
888
print "http://bazaar-ng.org/"
1178
911
parsed = [spec, None]
1183
914
class cmd_merge(Command):
1184
"""Perform a three-way merge of trees.
1186
The SPEC parameters are working tree or revision specifiers. Working trees
1187
are specified using standard paths or urls. No component of a directory
1188
path may begin with '@'.
1190
Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
1192
Revisions are specified using a dirname/@revno pair, where dirname is the
1193
branch directory and revno is the revision within that branch. If no revno
1194
is specified, the latest revision is used.
1196
Revision examples: './@127', 'foo/@', '../@1'
1198
The OTHER_SPEC parameter is required. If the BASE_SPEC parameter is
1199
not supplied, the common ancestor of OTHER_SPEC the current branch is used
1202
merge refuses to run if there are any uncommitted changes, unless
1205
takes_args = ['other_spec', 'base_spec?']
1206
takes_options = ['force']
1208
def run(self, other_spec, base_spec=None, force=False):
1209
from bzrlib.merge import merge
1210
merge(parse_spec(other_spec), parse_spec(base_spec),
1211
check_clean=(not force))
1214
class cmd_revert(Command):
1215
"""Reverse all changes since the last commit.
1217
Only versioned files are affected.
1219
TODO: Store backups of any files that will be reverted, so
1220
that the revert can be undone.
1222
takes_options = ['revision']
1224
def run(self, revision=-1):
1225
from bzrlib.merge import merge
1226
merge(('.', revision), parse_spec('.'),
915
"""Perform a three-way merge of trees."""
916
takes_args = ['other_spec', 'base_spec']
918
def run(self, other_spec, base_spec):
919
merge.merge(parse_spec(other_spec), parse_spec(base_spec))
1231
921
class cmd_assert_fail(Command):
1232
922
"""Test reporting of assertion failures"""