4
# Copyright (C) 2004, 2005 by Martin Pool
5
# Copyright (C) 2005 by Canonical Ltd
8
# This program is free software; you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation; either version 2 of the License, or
11
# (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program; if not, write to the Free Software
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
"""Bazaar-NG -- a free distributed version-control tool
24
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
26
Current limitation include:
28
* Metadata format is not stable yet -- you may need to
29
discard history in the future.
31
* No handling of subdirectories, symlinks or any non-text files.
33
* Insufficient error handling.
35
* Many commands unimplemented or partially implemented.
37
* Space-inefficient storage.
39
* No merge operators yet.
41
Interesting commands::
44
Show summary help screen
46
Show software version/licence/non-warranty.
48
Start versioning the current directory
52
Show revision history.
54
Show changes from last revision to working copy.
55
bzr commit -m 'MESSAGE'
56
Store current state as new revision.
57
bzr export REVNO DESTINATION
58
Export the branch state at a previous version.
60
Show summary of pending changes.
62
Make a file not versioned.
65
# not currently working:
67
# Run internal consistency checks.
69
# Show some information about this branch.
73
__copyright__ = "Copyright 2005 Canonical Development Ltd."
74
__author__ = "Martin Pool <mbp@canonical.com>"
75
__docformat__ = "restructuredtext en"
79
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
80
import traceback, socket, fnmatch, difflib
83
from pprint import pprint
86
from ElementTree import Element, ElementTree, SubElement
89
from bzrlib.store import ImmutableStore
90
from bzrlib.trace import mutter, note, log_error
91
from bzrlib.errors import bailout, BzrError
92
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
93
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
94
from bzrlib.revision import Revision
95
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
98
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
99
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
101
## standard representation
102
NONE_STRING = '(none)'
106
## TODO: Perhaps a different version of inventory commands that
107
## returns iterators...
109
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
111
## TODO: Some kind of locking on branches. Perhaps there should be a
112
## parameter to the branch object saying whether we want a read or
113
## write lock; release it from destructor. Perhaps don't even need a
114
## read lock to look at immutable objects?
116
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
117
## to compare output?
119
## TODO: Is ElementTree really all that much better for our purposes?
120
## Perhaps using the standard MiniDOM would be enough?
127
######################################################################
131
def cmd_status(all=False):
132
"""Display status summary.
134
For each file there is a single line giving its file state and name.
135
The name is that in the current revision unless it is deleted or
136
missing, in which case the old name is shown.
138
:todo: Don't show unchanged files unless ``--all`` is given?
140
Branch('.').show_status(show_all=all)
144
######################################################################
146
def cmd_get_revision(revision_id):
147
Branch('.').get_revision(revision_id).write_xml(sys.stdout)
150
def cmd_get_inventory(inventory_id):
151
"""Return inventory in XML by hash"""
152
Branch('.').get_inventory(inventory_hash).write_xml(sys.stdout)
155
def cmd_get_revision_inventory(revision_id):
156
"""Output inventory for a revision."""
158
b.get_revision_inventory(revision_id).write_xml(sys.stdout)
161
def cmd_get_file_text(text_id):
162
"""Get contents of a file by hash."""
163
sf = Branch('.').text_store[text_id]
164
pumpfile(sf, sys.stdout)
168
######################################################################
173
"""Show number of revisions on this branch"""
174
print Branch('.').revno()
177
def cmd_add(file_list, verbose=False):
178
"""Add specified files.
180
Fails if the files are already added.
182
Branch('.').add(file_list, verbose=verbose)
185
def cmd_inventory(revision=None):
186
"""Show inventory of the current working copy."""
187
## TODO: Also optionally show a previous inventory
188
## TODO: Format options
191
inv = b.read_working_inventory()
193
inv = b.get_revision_inventory(b.lookup_revision(revision))
195
for path, entry in inv.iter_entries():
196
print '%-50s %s' % (entry.file_id, path)
202
print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
203
print 'revision number:', b.revno()
204
print 'number of versioned files:', len(b.read_working_inventory())
207
def cmd_remove(file_list, verbose=False):
208
Branch('.').remove(file_list, verbose=verbose)
212
def cmd_file_id(filename):
213
i = Branch('.').read_working_inventory().path2id(filename)
215
bailout("%s is not a versioned file" % filename)
220
def cmd_find_filename(fileid):
221
n = find_filename(fileid)
223
bailout("%s is not a live file id" % fileid)
228
def cmd_revision_history():
229
for patchid in Branch('.').revision_history():
235
# TODO: Check we're not already in a working directory? At the
236
# moment you'll get an ugly error.
238
# TODO: What if we're in a subdirectory of a branch? Would like
239
# to allow that, but then the parent may need to understand that
240
# the children have disappeared, or should they be versioned in
243
# TODO: Take an argument/option for branch name.
244
Branch('.', init=True)
247
def cmd_diff(revision=None):
248
"""Show diff from basis to working copy.
250
:todo: Take one or two revision arguments, look up those trees,
253
:todo: Allow diff across branches.
255
:todo: Mangle filenames in diff to be more relevant.
257
:todo: Shouldn't be in the cmd function.
263
old_tree = b.basis_tree()
265
old_tree = b.revision_tree(b.lookup_revision(revision))
267
new_tree = b.working_tree()
268
old_inv = old_tree.inventory
269
new_inv = new_tree.inventory
271
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
275
DEVNULL = '/dev/null'
276
# Windows users, don't panic about this filename -- it is a
277
# special signal to GNU patch that the file should be created or
278
# deleted respectively.
280
# TODO: Generation of pseudo-diffs for added/deleted files could
281
# be usefully made into a much faster special case.
283
# TODO: Better to return them in sorted order I think.
285
for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
288
# Don't show this by default; maybe do it if an option is passed
289
# idlabel = ' {%s}' % fid
292
# FIXME: Something about the diff format makes patch unhappy
293
# with newly-added files.
295
def diffit(*a, **kw):
296
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
299
if file_state in ['.', '?', 'I']:
301
elif file_state == 'A':
302
print '*** added %s %r' % (kind, new_name)
305
new_tree.get_file(fid).readlines(),
307
tofile=new_label + new_name + idlabel)
308
elif file_state == 'D':
309
assert isinstance(old_name, types.StringTypes)
310
print '*** deleted %s %r' % (kind, old_name)
312
diffit(old_tree.get_file(fid).readlines(), [],
313
fromfile=old_label + old_name + idlabel,
315
elif file_state in ['M', 'R']:
316
if file_state == 'M':
317
assert kind == 'file'
318
assert old_name == new_name
319
print '*** modified %s %r' % (kind, new_name)
320
elif file_state == 'R':
321
print '*** renamed %s %r => %r' % (kind, old_name, new_name)
324
diffit(old_tree.get_file(fid).readlines(),
325
new_tree.get_file(fid).readlines(),
326
fromfile=old_label + old_name + idlabel,
327
tofile=new_label + new_name)
329
bailout("can't represent state %s {%s}" % (file_state, fid))
334
"""Show log of this branch.
336
:todo: Options for utc; to show ids; to limit range; etc.
338
Branch('.').write_log()
341
def cmd_ls(revision=None, verbose=False):
342
"""List files in a tree.
344
:todo: Take a revision or remote path and list that tree instead.
348
tree = b.working_tree()
350
tree = b.revision_tree(b.lookup_revision(revision))
352
for fp, fc, kind, fid in tree.list_files():
354
if kind == 'directory':
361
print '%-8s %s%s' % (fc, fp, kindch)
368
"""List unknown files"""
369
for f in Branch('.').unknowns():
373
def cmd_lookup_revision(revno):
377
bailout("usage: lookup-revision REVNO",
378
["REVNO is a non-negative revision number for this branch"])
380
print Branch('.').lookup_revision(revno) or NONE_STRING
384
def cmd_export(revno, dest):
385
"""Export past revision to destination directory."""
387
rh = b.lookup_revision(int(revno))
388
t = b.revision_tree(rh)
393
######################################################################
394
# internal/test commands
398
"""Print a newly-generated UUID."""
403
def cmd_commit(message, verbose=False):
404
Branch('.').commit(message, verbose=verbose)
408
"""Check consistency of the branch."""
412
def cmd_is(pred, *rest):
413
"""Test whether PREDICATE is true."""
415
cmd_handler = globals()['assert_' + pred.replace('-', '_')]
417
bailout("unknown predicate: %s" % quotefn(pred))
421
except BzrCheckError:
422
# by default we don't print the message so that this can
423
# be used from shell scripts without producing noise
428
print bzrlib.osutils.username()
431
def cmd_user_email():
432
print bzrlib.osutils.user_email()
435
def cmd_gen_revision_id():
437
print bzrlib.branch._gen_revision_id(time.time())
441
"""Run internal doctest suite"""
442
## -v, if present, is seen by doctest; the argument is just here
443
## so our parser doesn't complain
445
## TODO: --verbose option
447
import bzr, doctest, bzrlib.store
448
bzrlib.trace.verbose = False
450
doctest.testmod(bzrlib.store)
451
doctest.testmod(bzrlib.inventory)
452
doctest.testmod(bzrlib.branch)
453
doctest.testmod(bzrlib.osutils)
454
doctest.testmod(bzrlib.tree)
456
# more strenuous tests;
458
doctest.testmod(bzrlib.tests)
461
######################################################################
466
# TODO: Specific help for particular commands
471
print "bzr (bazaar-ng) %s" % __version__
473
print "http://bazaar-ng.org/"
476
"""bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and
477
you may use, modify and redistribute it under the terms of the GNU
478
General Public License version 2 or later."""
482
"""Statement of optimism."""
483
print "it sure does!"
487
######################################################################
491
# list of all available options; the rhs can be either None for an
492
# option that takes no argument, or a constructor function that checks
510
# List of options that apply to particular commands; commands not
514
'commit': ['message', 'verbose'],
515
'diff': ['revision'],
516
'inventory': ['revision'],
517
'ls': ['revision', 'verbose'],
520
'remove': ['verbose'],
529
'file-id': ['filename'],
530
'get-file-text': ['text_id'],
531
'get-inventory': ['inventory_id'],
532
'get-revision': ['revision_id'],
533
'get-revision-inventory': ['revision_id'],
535
'lookup-revision': ['revno'],
536
'export': ['revno', 'dest'],
542
def parse_args(argv):
543
"""Parse command line.
545
Arguments and options are parsed at this level before being passed
546
down to specific command handlers. This routine knows, from a
547
lookup table, something about the available options, what optargs
548
they take, and which commands will accept them.
550
>>> parse_args('bzr --help'.split())
552
>>> parse_args('bzr --version'.split())
553
([], {'version': True})
554
>>> parse_args('bzr status --all'.split())
555
(['status'], {'all': True})
560
# TODO: Maybe handle '--' to end options?
567
mutter(" got option %r" % a)
569
if optname not in OPTIONS:
570
bailout('unknown long option %r' % a)
573
if shortopt not in SHORT_OPTIONS:
574
bailout('unknown short option %r' % a)
575
optname = SHORT_OPTIONS[shortopt]
578
# XXX: Do we ever want to support this, e.g. for -r?
579
bailout('repeated option %r' % a)
580
optargfn = OPTIONS[optname]
583
bailout('option %r needs an argument' % a)
584
opts[optname] = optargfn(it.next())
585
mutter(" option argument %r" % opts[optname])
587
# takes no option argument
590
bailout('unknown short option %r' % a)
598
def _match_args(cmd, args):
599
"""Check non-option arguments match required pattern.
601
>>> _match_args('status', ['asdasdsadasd'])
602
Traceback (most recent call last):
604
BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
605
>>> _match_args('add', ['asdasdsadasd'])
606
{'file_list': ['asdasdsadasd']}
607
>>> _match_args('add', 'abc def gj'.split())
608
{'file_list': ['abc', 'def', 'gj']}
610
# match argument pattern
611
argform = cmd_args.get(cmd, [])
613
# TODO: Need a way to express 'cp SRC... DEST', where it matches
623
bailout("command %r needs one or more %s"
624
% (cmd, argname.upper()))
626
argdict[argname + '_list'] = args[:]
632
bailout("command %r requires argument %s"
633
% (cmd, argname.upper()))
635
argdict[argname] = args.pop(0)
638
bailout("extra arguments to command %s: %r"
646
"""Execute a command.
648
This is similar to main(), but without all the trappings for
649
logging and error handling.
652
args, opts = parse_args(argv)
654
# TODO: pass down other arguments in case they asked for
655
# help on a command name?
658
elif 'version' in opts:
663
log_error('usage: bzr COMMAND\n')
664
log_error(' try "bzr help"\n')
668
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
670
bailout("unknown command " + `cmd`)
672
# TODO: special --profile option to turn on the Python profiler
674
# check options are reasonable
675
allowed = cmd_options.get(cmd, [])
677
if oname not in allowed:
678
bailout("option %r is not allowed for command %r"
681
cmdargs = _match_args(cmd, args)
684
ret = cmd_handler(**cmdargs) or 0
689
## TODO: Handle command-line options; probably know what options are valid for
692
## TODO: If the arguments are wrong, give a usage message rather
693
## than just a backtrace.
696
t = bzrlib.trace._tracefile
697
t.write('-' * 60 + '\n')
698
t.write('bzr invoked at %s\n' % format_date(time.time()))
699
t.write(' by %s on %s\n' % (bzrlib.osutils.username(), socket.gethostname()))
700
t.write(' arguments: %r\n' % argv)
702
starttime = os.times()[4]
705
t.write(' platform: %s\n' % platform.platform())
706
t.write(' python: %s\n' % platform.python_version())
711
mutter("finished, %.3fu/%.3fs cpu, %.3fu/%.3fs cum"
713
mutter(" %.3f elapsed" % (times[4] - starttime))
717
log_error('bzr: error: ' + e.args[0] + '\n')
720
log_error(' ' + h + '\n')
723
log_error('bzr: exception: %s\n' % e)
724
log_error(' see .bzr.log for details\n')
725
traceback.print_exc(None, bzrlib.trace._tracefile)
726
traceback.print_exc(None, sys.stderr)
729
# TODO: Maybe nicer handling of IOError?
733
if __name__ == '__main__':
734
sys.exit(main(sys.argv))
736
##profile.run('main(sys.argv)')