16
14
# along with this program; if not, write to the Free Software
17
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
"""Bazaar-NG -- a free distributed version-control tool
21
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
23
Current limitation include:
25
* Metadata format is not stable yet -- you may need to
26
discard history in the future.
28
* Insufficient error handling.
30
* Many commands unimplemented or partially implemented.
32
* Space-inefficient storage.
34
* No merge operators yet.
36
Interesting commands::
41
Show software version/licence/non-warranty.
43
Start versioning the current directory
47
Show revision history.
49
Show changes from last revision to working copy.
50
bzr commit -m 'MESSAGE'
51
Store current state as new revision.
52
bzr export REVNO DESTINATION
53
Export the branch state at a previous version.
55
Show summary of pending changes.
57
Make a file not versioned.
59
Show statistics about this branch.
65
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
66
import traceback, socket, fnmatch, difflib
69
from pprint import pprint
18
# TODO: probably should say which arguments are candidates for glob
19
# expansion on windows and do that at the command level.
21
# TODO: Help messages for options.
23
# TODO: Define arguments by objects, rather than just using names.
24
# Those objects can specify the expected type of the argument, which
25
# would help with validation and shell completion.
74
from bzrlib.store import ImmutableStore
75
from bzrlib.trace import mutter, note, log_error
76
from bzrlib.errors import bailout, BzrError
77
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
78
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
79
from bzrlib.revision import Revision
80
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
83
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
84
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
86
## standard representation
87
NONE_STRING = '(none)'
91
## TODO: Perhaps a different version of inventory commands that
92
## returns iterators...
94
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
96
## TODO: Some kind of locking on branches. Perhaps there should be a
97
## parameter to the branch object saying whether we want a read or
98
## write lock; release it from destructor. Perhaps don't even need a
99
## read lock to look at immutable objects?
101
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
102
## to compare output?
104
## TODO: Some kind of global code to generate the right Branch object
105
## to work on. Almost, but not quite all, commands need one, and it
106
## can be taken either from their parameters or their working
109
## TODO: rename command, needed soon: check destination doesn't exist
110
## either in working copy or tree; move working copy; update
111
## inventory; write out
113
## TODO: move command; check destination is a directory and will not
116
## TODO: command to show renames, one per line, as to->from
121
def cmd_status(all=False):
122
"""Display status summary.
124
For each file there is a single line giving its file state and name.
125
The name is that in the current revision unless it is deleted or
126
missing, in which case the old name is shown.
128
:todo: Don't show unchanged files unless ``--all`` is given?
130
Branch('.').show_status(show_all=all)
134
######################################################################
136
def cmd_get_revision(revision_id):
137
Branch('.').get_revision(revision_id).write_xml(sys.stdout)
140
def cmd_get_file_text(text_id):
141
"""Get contents of a file by hash."""
142
sf = Branch('.').text_store[text_id]
143
pumpfile(sf, sys.stdout)
147
######################################################################
152
"""Show number of revisions on this branch"""
153
print Branch('.').revno()
157
def cmd_add(file_list, verbose=False):
158
"""Add specified files or directories.
160
In non-recursive mode, all the named items are added, regardless
161
of whether they were previously ignored. A warning is given if
162
any of the named files are already versioned.
164
In recursive mode (the default), files are treated the same way
165
but the behaviour for directories is different. Directories that
166
are already versioned do not give a warning. All directories,
167
whether already versioned or not, are searched for files or
168
subdirectories that are neither versioned or ignored, and these
169
are added. This search proceeds recursively into versioned
172
Therefore simply saying 'bzr add .' will version all files that
173
are currently unknown.
175
bzrlib.add.smart_add(file_list, verbose)
178
def cmd_relpath(filename):
179
"""Show path of file relative to root"""
180
print Branch(filename).relpath(filename)
183
def cmd_inventory(revision=None):
184
"""Show inventory of the current working copy."""
185
## TODO: Also optionally show a previous inventory
186
## TODO: Format options
189
inv = b.read_working_inventory()
191
inv = b.get_revision_inventory(b.lookup_revision(revision))
193
for path, entry in inv.iter_entries():
194
print '%-50s %s' % (entry.file_id, path)
199
"""info: Show statistical information for this branch
203
info.show_info(Branch('.'))
207
def cmd_remove(file_list, verbose=False):
208
b = Branch(file_list[0])
209
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
213
def cmd_file_id(filename):
215
i = b.inventory.path2id(b.relpath(filename))
217
bailout("%s is not a versioned file" % filename)
222
def cmd_find_filename(fileid):
223
n = find_filename(fileid)
225
bailout("%s is not a live file id" % fileid)
230
def cmd_revision_history():
231
for patchid in Branch('.').revision_history():
237
# TODO: Check we're not already in a working directory? At the
238
# moment you'll get an ugly error.
240
# TODO: What if we're in a subdirectory of a branch? Would like
241
# to allow that, but then the parent may need to understand that
242
# the children have disappeared, or should they be versioned in
245
# TODO: Take an argument/option for branch name.
246
Branch('.', init=True)
249
def cmd_diff(revision=None):
250
"""bzr diff: Show differences in working tree.
252
usage: bzr diff [-r REV]
255
Show changes since REV, rather than predecessor.
257
TODO: Given two revision arguments, show the difference between them.
259
TODO: Allow diff across branches.
261
TODO: Option to use external diff command; could be GNU diff, wdiff,
264
TODO: Diff selected files.
267
## TODO: Shouldn't be in the cmd function.
272
old_tree = b.basis_tree()
274
old_tree = b.revision_tree(b.lookup_revision(revision))
276
new_tree = b.working_tree()
277
old_inv = old_tree.inventory
278
new_inv = new_tree.inventory
280
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
284
DEVNULL = '/dev/null'
285
# Windows users, don't panic about this filename -- it is a
286
# special signal to GNU patch that the file should be created or
287
# deleted respectively.
289
# TODO: Generation of pseudo-diffs for added/deleted files could
290
# be usefully made into a much faster special case.
292
# TODO: Better to return them in sorted order I think.
294
for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
297
# Don't show this by default; maybe do it if an option is passed
298
# idlabel = ' {%s}' % fid
301
# FIXME: Something about the diff format makes patch unhappy
302
# with newly-added files.
304
def diffit(*a, **kw):
305
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
308
if file_state in ['.', '?', 'I']:
310
elif file_state == 'A':
311
print '*** added %s %r' % (kind, new_name)
314
new_tree.get_file(fid).readlines(),
316
tofile=new_label + new_name + idlabel)
317
elif file_state == 'D':
318
assert isinstance(old_name, types.StringTypes)
319
print '*** deleted %s %r' % (kind, old_name)
321
diffit(old_tree.get_file(fid).readlines(), [],
322
fromfile=old_label + old_name + idlabel,
324
elif file_state in ['M', 'R']:
325
if file_state == 'M':
326
assert kind == 'file'
327
assert old_name == new_name
328
print '*** modified %s %r' % (kind, new_name)
329
elif file_state == 'R':
330
print '*** renamed %s %r => %r' % (kind, old_name, new_name)
333
diffit(old_tree.get_file(fid).readlines(),
334
new_tree.get_file(fid).readlines(),
335
fromfile=old_label + old_name + idlabel,
336
tofile=new_label + new_name)
338
bailout("can't represent state %s {%s}" % (file_state, fid))
342
def cmd_deleted(show_ids=False):
343
"""List files deleted in the working tree.
345
TODO: Show files deleted since a previous revision, or between two revisions.
349
new = b.working_tree()
351
for path, ie in old.inventory.iter_entries():
352
if not new.has_id(ie.file_id):
354
print '%-50s %s' % (path, ie.file_id)
361
def cmd_root(filename=None):
362
"""Print the branch root."""
363
print bzrlib.branch.find_branch_root(filename)
366
def cmd_log(timezone='original'):
367
"""Show log of this branch.
369
:todo: Options for utc; to show ids; to limit range; etc.
371
Branch('.').write_log(show_timezone=timezone)
374
def cmd_ls(revision=None, verbose=False):
375
"""List files in a tree.
377
:todo: Take a revision or remote path and list that tree instead.
381
tree = b.working_tree()
383
tree = b.revision_tree(b.lookup_revision(revision))
385
for fp, fc, kind, fid in tree.list_files():
387
if kind == 'directory':
394
print '%-8s %s%s' % (fc, fp, kindch)
401
"""List unknown files"""
402
for f in Branch('.').unknowns():
407
def cmd_ignored(verbose=True):
408
"""List ignored files and the patterns that matched them.
410
tree = Branch('.').working_tree()
411
for path, file_class, kind, id in tree.list_files():
412
if file_class != 'I':
414
## XXX: Slightly inefficient since this was already calculated
415
pat = tree.is_ignored(path)
416
print '%-50s %s' % (path, pat)
420
def cmd_lookup_revision(revno):
424
bailout("usage: lookup-revision REVNO",
425
["REVNO is a non-negative revision number for this branch"])
427
print Branch('.').lookup_revision(revno) or NONE_STRING
431
def cmd_export(revno, dest):
432
"""Export past revision to destination directory."""
434
rh = b.lookup_revision(int(revno))
435
t = b.revision_tree(rh)
440
######################################################################
441
# internal/test commands
445
"""Print a newly-generated UUID."""
446
print bzrlib.osutils.uuid()
450
def cmd_local_time_offset():
451
print bzrlib.osutils.local_time_offset()
455
def cmd_commit(message=None, verbose=False):
456
"""Commit changes to a new revision.
459
Description of changes in this revision; free form text.
460
It is recommended that the first line be a single-sentence
463
Show status of changed files,
465
TODO: Commit only selected files.
467
TODO: Run hooks on tree to-be-committed, and after commit.
469
TODO: Strict commit that fails if there are unknown or deleted files.
473
bailout("please specify a commit message")
474
Branch('.').commit(message, verbose=verbose)
477
def cmd_check(dir='.'):
478
"""check: Consistency check of branch history.
480
usage: bzr check [-v] [BRANCH]
483
--verbose, -v Show progress of checking.
485
This command checks various invariants about the branch storage to
486
detect data corruption or bzr bugs.
489
bzrlib.check.check(Branch(dir, find_root=False))
492
def cmd_is(pred, *rest):
493
"""Test whether PREDICATE is true."""
495
cmd_handler = globals()['assert_' + pred.replace('-', '_')]
497
bailout("unknown predicate: %s" % quotefn(pred))
501
except BzrCheckError:
502
# by default we don't print the message so that this can
503
# be used from shell scripts without producing noise
508
print bzrlib.osutils.username()
511
def cmd_user_email():
512
print bzrlib.osutils.user_email()
515
def cmd_gen_revision_id():
517
print bzrlib.branch._gen_revision_id(time.time())
520
def cmd_selftest(verbose=False):
521
"""Run internal test suite"""
522
## -v, if present, is seen by doctest; the argument is just here
523
## so our parser doesn't complain
525
## TODO: --verbose option
527
failures, tests = 0, 0
529
import doctest, bzrlib.store, bzrlib.tests
530
bzrlib.trace.verbose = False
532
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
533
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
534
mf, mt = doctest.testmod(m)
537
print '%-40s %3d tests' % (m.__name__, mt),
539
print '%3d FAILED!' % mf
543
print '%-40s %3d tests' % ('total', tests),
545
print '%3d FAILED!' % failures
552
cmd_doctest = cmd_selftest
555
######################################################################
559
def cmd_help(topic=None):
564
# otherwise, maybe the name of a command?
566
cmdfn = globals()['cmd_' + topic.replace('-', '_')]
568
bailout("no help for %r" % topic)
572
bailout("sorry, no detailed help yet for %r" % topic)
580
print "bzr (bazaar-ng) %s" % bzrlib.__version__
581
print bzrlib.__copyright__
582
print "http://bazaar-ng.org/"
585
"""bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and
586
you may use, modify and redistribute it under the terms of the GNU
587
General Public License version 2 or later."""
591
"""Statement of optimism."""
592
print "it sure does!"
596
######################################################################
33
from bzrlib.trace import mutter, note, log_error, warning
34
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
35
from bzrlib.branch import find_branch
36
from bzrlib import BZRDIR
42
def register_command(cmd):
43
"Utility function to help register a command"
46
if k.startswith("cmd_"):
47
k_unsquished = _unsquish_command_name(k)
50
if not plugin_cmds.has_key(k_unsquished):
51
plugin_cmds[k_unsquished] = cmd
52
mutter('registered plugin command %s', k_unsquished)
54
log_error('Two plugins defined the same command: %r' % k)
55
log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
58
def _squish_command_name(cmd):
59
return 'cmd_' + cmd.replace('-', '_')
62
def _unsquish_command_name(cmd):
63
assert cmd.startswith("cmd_")
64
return cmd[4:].replace('_','-')
67
def _parse_revision_str(revstr):
68
"""This handles a revision string -> revno.
70
This always returns a list. The list will have one element for
72
It supports integers directly, but everything else it
73
defers for passing to Branch.get_revision_info()
75
>>> _parse_revision_str('234')
77
>>> _parse_revision_str('234..567')
79
>>> _parse_revision_str('..')
81
>>> _parse_revision_str('..234')
83
>>> _parse_revision_str('234..')
85
>>> _parse_revision_str('234..456..789') # Maybe this should be an error
87
>>> _parse_revision_str('234....789') # Error?
89
>>> _parse_revision_str('revid:test@other.com-234234')
90
['revid:test@other.com-234234']
91
>>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
92
['revid:test@other.com-234234', 'revid:test@other.com-234235']
93
>>> _parse_revision_str('revid:test@other.com-234234..23')
94
['revid:test@other.com-234234', 23]
95
>>> _parse_revision_str('date:2005-04-12')
97
>>> _parse_revision_str('date:2005-04-12 12:24:33')
98
['date:2005-04-12 12:24:33']
99
>>> _parse_revision_str('date:2005-04-12T12:24:33')
100
['date:2005-04-12T12:24:33']
101
>>> _parse_revision_str('date:2005-04-12,12:24:33')
102
['date:2005-04-12,12:24:33']
103
>>> _parse_revision_str('-5..23')
105
>>> _parse_revision_str('-5')
107
>>> _parse_revision_str('123a')
109
>>> _parse_revision_str('abc')
113
old_format_re = re.compile('\d*:\d*')
114
m = old_format_re.match(revstr)
116
warning('Colon separator for revision numbers is deprecated.'
119
for rev in revstr.split(':'):
121
revs.append(int(rev))
126
for x in revstr.split('..'):
137
def get_merge_type(typestring):
138
"""Attempt to find the merge class/factory associated with a string."""
139
from merge import merge_types
141
return merge_types[typestring][0]
143
templ = '%s%%7s: %%s' % (' '*12)
144
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
145
type_list = '\n'.join(lines)
146
msg = "No known merge type %s. Supported types are:\n%s" %\
147
(typestring, type_list)
148
raise BzrCommandError(msg)
151
def get_merge_type(typestring):
152
"""Attempt to find the merge class/factory associated with a string."""
153
from merge import merge_types
155
return merge_types[typestring][0]
157
templ = '%s%%7s: %%s' % (' '*12)
158
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
159
type_list = '\n'.join(lines)
160
msg = "No known merge type %s. Supported types are:\n%s" %\
161
(typestring, type_list)
162
raise BzrCommandError(msg)
166
def _get_cmd_dict(plugins_override=True):
167
import bzrlib.builtins
170
builtins = bzrlib.builtins.__dict__
171
for name in builtins:
172
if name.startswith("cmd_"):
173
d[_unsquish_command_name(name)] = builtins[name]
174
# If we didn't load plugins, the plugin_cmds dict will be empty
176
d.update(plugin_cmds)
178
d2 = plugin_cmds.copy()
184
def get_all_cmds(plugins_override=True):
185
"""Return canonical name and class for all registered commands."""
186
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
190
def get_cmd_class(cmd, plugins_override=True):
191
"""Return the canonical name and command class for a command.
193
cmd = str(cmd) # not unicode
195
# first look up this command under the specified name
196
cmds = _get_cmd_dict(plugins_override=plugins_override)
197
mutter("all commands: %r", cmds.keys())
199
return cmd, cmds[cmd]
203
# look for any command which claims this as an alias
204
for cmdname, cmdclass in cmds.iteritems():
205
if cmd in cmdclass.aliases:
206
return cmdname, cmdclass
208
cmdclass = ExternalCommand.find_command(cmd)
212
raise BzrCommandError("unknown command %r" % cmd)
215
class Command(object):
216
"""Base class for commands.
218
The docstring for an actual command should give a single-line
219
summary, then a complete description of the command. A grammar
220
description will be inserted.
223
List of argument forms, marked with whether they are optional,
227
List of options that may be given for this command.
230
If true, this command isn't advertised.
239
def __init__(self, options, arguments):
240
"""Construct and run the command.
242
Sets self.status to the return value of run()."""
243
assert isinstance(options, dict)
244
assert isinstance(arguments, dict)
245
cmdargs = options.copy()
246
cmdargs.update(arguments)
247
if self.__doc__ == Command.__doc__:
248
from warnings import warn
249
warn("No help message set for %r" % self)
250
self.status = self.run(**cmdargs)
251
if self.status is None:
255
def run(self, *args, **kwargs):
256
"""Override this in sub-classes.
258
This is invoked with the options and arguments bound to
261
Return 0 or None if the command was successful, or a shell
264
raise NotImplementedError()
267
class ExternalCommand(Command):
268
"""Class to wrap external commands.
270
We cheat a little here, when get_cmd_class() calls us we actually
271
give it back an object we construct that has the appropriate path,
272
help, options etc for the specified command.
274
When run_bzr() tries to instantiate that 'class' it gets caught by
275
the __call__ method, which we override to call the Command.__init__
276
method. That then calls our run method which is pretty straight
279
The only wrinkle is that we have to map bzr's dictionary of options
280
and arguments back into command line options and arguments for the
284
def find_command(cls, cmd):
286
bzrpath = os.environ.get('BZRPATH', '')
288
for dir in bzrpath.split(os.pathsep):
289
path = os.path.join(dir, cmd)
290
if os.path.isfile(path):
291
return ExternalCommand(path)
295
find_command = classmethod(find_command)
297
def __init__(self, path):
300
pipe = os.popen('%s --bzr-usage' % path, 'r')
301
self.takes_options = pipe.readline().split()
303
for opt in self.takes_options:
304
if not opt in OPTIONS:
305
raise BzrError("Unknown option '%s' returned by external command %s"
308
# TODO: Is there any way to check takes_args is valid here?
309
self.takes_args = pipe.readline().split()
311
if pipe.close() is not None:
312
raise BzrError("Failed funning '%s --bzr-usage'" % path)
314
pipe = os.popen('%s --bzr-help' % path, 'r')
315
self.__doc__ = pipe.read()
316
if pipe.close() is not None:
317
raise BzrError("Failed funning '%s --bzr-help'" % path)
319
def __call__(self, options, arguments):
320
Command.__init__(self, options, arguments)
323
def run(self, **kargs):
330
optname = name.replace('_','-')
332
if OPTIONS.has_key(optname):
334
opts.append('--%s' % optname)
335
if value is not None and value is not True:
336
opts.append(str(value))
338
# it's an arg, or arg list
339
if type(value) is not list:
345
self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
350
def parse_spec(spec):
356
>>> parse_spec("../@")
358
>>> parse_spec("../f/@35")
360
>>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
361
['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
366
parsed = spec.split('/@')
367
assert len(parsed) == 2
372
parsed[1] = int(parsed[1])
374
pass # We can allow stuff like ./@revid:blahblahblah
378
parsed = [spec, None]
600
384
# list of all available options; the rhs can be either None for an