1
# Copyright (C) 2004, 2005 by Martin Pool
2
# Copyright (C) 2005 by Canonical Ltd
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
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
* No handling of subdirectories, symlinks or any non-text files.
30
* Insufficient error handling.
32
* Many commands unimplemented or partially implemented.
34
* Space-inefficient storage.
36
* No merge operators yet.
38
Interesting commands::
41
Show summary help screen
43
Show software version/licence/non-warranty.
45
Start versioning the current directory
49
Show revision history.
51
Show changes from last revision to working copy.
52
bzr commit -m 'MESSAGE'
53
Store current state as new revision.
54
bzr export REVNO DESTINATION
55
Export the branch state at a previous version.
57
Show summary of pending changes.
59
Make a file not versioned.
61
Show statistics about this branch.
67
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
68
import traceback, socket, fnmatch, difflib
71
from pprint import pprint
76
from bzrlib.store import ImmutableStore
77
from bzrlib.trace import mutter, note, log_error
78
from bzrlib.errors import bailout, BzrError
79
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
80
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
81
from bzrlib.revision import Revision
82
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
85
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
86
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
88
## standard representation
89
NONE_STRING = '(none)'
93
## TODO: Perhaps a different version of inventory commands that
94
## returns iterators...
96
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
98
## TODO: Some kind of locking on branches. Perhaps there should be a
99
## parameter to the branch object saying whether we want a read or
100
## write lock; release it from destructor. Perhaps don't even need a
101
## read lock to look at immutable objects?
103
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
104
## to compare output?
106
## TODO: Some kind of global code to generate the right Branch object
107
## to work on. Almost, but not quite all, commands need one, and it
108
## can be taken either from their parameters or their working
111
## TODO: rename command, needed soon: check destination doesn't exist
112
## either in working copy or tree; move working copy; update
113
## inventory; write out
115
## TODO: move command; check destination is a directory and will not
118
## TODO: command to show renames, one per line, as to->from
123
def cmd_status(all=False):
124
"""Display status summary.
126
For each file there is a single line giving its file state and name.
127
The name is that in the current revision unless it is deleted or
128
missing, in which case the old name is shown.
130
:todo: Don't show unchanged files unless ``--all`` is given?
132
Branch('.').show_status(show_all=all)
136
######################################################################
138
def cmd_get_revision(revision_id):
139
Branch('.').get_revision(revision_id).write_xml(sys.stdout)
142
def cmd_get_file_text(text_id):
143
"""Get contents of a file by hash."""
144
sf = Branch('.').text_store[text_id]
145
pumpfile(sf, sys.stdout)
149
######################################################################
154
"""Show number of revisions on this branch"""
155
print Branch('.').revno()
159
def cmd_add(file_list, verbose=False):
160
"""Add specified files or directories.
162
In non-recursive mode, all the named items are added, regardless
163
of whether they were previously ignored. A warning is given if
164
any of the named files are already versioned.
166
In recursive mode (the default), files are treated the same way
167
but the behaviour for directories is different. Directories that
168
are already versioned do not give a warning. All directories,
169
whether already versioned or not, are searched for files or
170
subdirectories that are neither versioned or ignored, and these
171
are added. This search proceeds recursively into versioned
174
Therefore simply saying 'bzr add .' will version all files that
175
are currently unknown.
178
bzrlib.add.smart_add(file_list, verbose)
182
b = Branch(file_list[0], find_root=True)
183
b.add([b.relpath(f) for f in file_list], verbose=verbose)
187
def cmd_relpath(filename):
188
print Branch(filename).relpath(filename)
191
def cmd_inventory(revision=None):
192
"""Show inventory of the current working copy."""
193
## TODO: Also optionally show a previous inventory
194
## TODO: Format options
197
inv = b.read_working_inventory()
199
inv = b.get_revision_inventory(b.lookup_revision(revision))
201
for path, entry in inv.iter_entries():
202
print '%-50s %s' % (entry.file_id, path)
208
info.show_info(Branch('.'))
212
def cmd_remove(file_list, verbose=False):
213
b = Branch(file_list[0])
214
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
218
def cmd_file_id(filename):
220
i = b.inventory.path2id(b.relpath(filename))
222
bailout("%s is not a versioned file" % filename)
227
def cmd_find_filename(fileid):
228
n = find_filename(fileid)
230
bailout("%s is not a live file id" % fileid)
235
def cmd_revision_history():
236
for patchid in Branch('.').revision_history():
242
# TODO: Check we're not already in a working directory? At the
243
# moment you'll get an ugly error.
245
# TODO: What if we're in a subdirectory of a branch? Would like
246
# to allow that, but then the parent may need to understand that
247
# the children have disappeared, or should they be versioned in
250
# TODO: Take an argument/option for branch name.
251
Branch('.', init=True)
254
def cmd_diff(revision=None):
255
"""Show diff from basis to working copy.
257
:todo: Take one or two revision arguments, look up those trees,
260
:todo: Allow diff across branches.
262
:todo: Mangle filenames in diff to be more relevant.
264
:todo: Shouldn't be in the cmd function.
266
TODO: Option to use external diff command; could be GNU diff,
267
wdiff, or a graphical diff.
273
old_tree = b.basis_tree()
275
old_tree = b.revision_tree(b.lookup_revision(revision))
277
new_tree = b.working_tree()
278
old_inv = old_tree.inventory
279
new_inv = new_tree.inventory
281
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
285
DEVNULL = '/dev/null'
286
# Windows users, don't panic about this filename -- it is a
287
# special signal to GNU patch that the file should be created or
288
# deleted respectively.
290
# TODO: Generation of pseudo-diffs for added/deleted files could
291
# be usefully made into a much faster special case.
293
# TODO: Better to return them in sorted order I think.
295
for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
298
# Don't show this by default; maybe do it if an option is passed
299
# idlabel = ' {%s}' % fid
302
# FIXME: Something about the diff format makes patch unhappy
303
# with newly-added files.
305
def diffit(*a, **kw):
306
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
309
if file_state in ['.', '?', 'I']:
311
elif file_state == 'A':
312
print '*** added %s %r' % (kind, new_name)
315
new_tree.get_file(fid).readlines(),
317
tofile=new_label + new_name + idlabel)
318
elif file_state == 'D':
319
assert isinstance(old_name, types.StringTypes)
320
print '*** deleted %s %r' % (kind, old_name)
322
diffit(old_tree.get_file(fid).readlines(), [],
323
fromfile=old_label + old_name + idlabel,
325
elif file_state in ['M', 'R']:
326
if file_state == 'M':
327
assert kind == 'file'
328
assert old_name == new_name
329
print '*** modified %s %r' % (kind, new_name)
330
elif file_state == 'R':
331
print '*** renamed %s %r => %r' % (kind, old_name, new_name)
334
diffit(old_tree.get_file(fid).readlines(),
335
new_tree.get_file(fid).readlines(),
336
fromfile=old_label + old_name + idlabel,
337
tofile=new_label + new_name)
339
bailout("can't represent state %s {%s}" % (file_state, fid))
343
def cmd_root(filename=None):
344
"""Print the branch root."""
345
print bzrlib.branch.find_branch_root(filename)
348
def cmd_log(timezone='original'):
349
"""Show log of this branch.
351
:todo: Options for utc; to show ids; to limit range; etc.
353
Branch('.').write_log(show_timezone=timezone)
356
def cmd_ls(revision=None, verbose=False):
357
"""List files in a tree.
359
:todo: Take a revision or remote path and list that tree instead.
363
tree = b.working_tree()
365
tree = b.revision_tree(b.lookup_revision(revision))
367
for fp, fc, kind, fid in tree.list_files():
369
if kind == 'directory':
376
print '%-8s %s%s' % (fc, fp, kindch)
383
"""List unknown files"""
384
for f in Branch('.').unknowns():
388
def cmd_lookup_revision(revno):
392
bailout("usage: lookup-revision REVNO",
393
["REVNO is a non-negative revision number for this branch"])
395
print Branch('.').lookup_revision(revno) or NONE_STRING
399
def cmd_export(revno, dest):
400
"""Export past revision to destination directory."""
402
rh = b.lookup_revision(int(revno))
403
t = b.revision_tree(rh)
408
######################################################################
409
# internal/test commands
413
"""Print a newly-generated UUID."""
414
print bzrlib.osutils.uuid()
418
def cmd_local_time_offset():
419
print bzrlib.osutils.local_time_offset()
423
def cmd_commit(message=None, verbose=False):
425
bailout("please specify a commit message")
426
Branch('.').commit(message, verbose=verbose)
430
"""Check consistency of the branch."""
434
def cmd_is(pred, *rest):
435
"""Test whether PREDICATE is true."""
437
cmd_handler = globals()['assert_' + pred.replace('-', '_')]
439
bailout("unknown predicate: %s" % quotefn(pred))
443
except BzrCheckError:
444
# by default we don't print the message so that this can
445
# be used from shell scripts without producing noise
450
print bzrlib.osutils.username()
453
def cmd_user_email():
454
print bzrlib.osutils.user_email()
457
def cmd_gen_revision_id():
459
print bzrlib.branch._gen_revision_id(time.time())
462
def cmd_selftest(verbose=False):
463
"""Run internal test suite"""
464
## -v, if present, is seen by doctest; the argument is just here
465
## so our parser doesn't complain
467
## TODO: --verbose option
469
failures, tests = 0, 0
471
import doctest, bzrlib.store, bzrlib.tests
472
bzrlib.trace.verbose = False
474
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
475
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
476
mf, mt = doctest.testmod(m)
479
print '%-40s %3d tests' % (m.__name__, mt),
481
print '%3d FAILED!' % mf
485
print '%-40s %3d tests' % ('total', tests),
487
print '%3d FAILED!' % failures
494
cmd_doctest = cmd_selftest
497
######################################################################
501
def cmd_help(topic=None):
506
# otherwise, maybe the name of a command?
508
cmdfn = globals()['cmd_' + topic.replace('-', '_')]
510
bailout("no help for %r" % topic)
514
bailout("sorry, no detailed help yet for %r" % topic)
522
print "bzr (bazaar-ng) %s" % bzrlib.__version__
523
print bzrlib.__copyright__
524
print "http://bazaar-ng.org/"
527
"""bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and
528
you may use, modify and redistribute it under the terms of the GNU
529
General Public License version 2 or later."""
533
"""Statement of optimism."""
534
print "it sure does!"
538
######################################################################
542
# list of all available options; the rhs can be either None for an
543
# option that takes no argument, or a constructor function that checks
562
# List of options that apply to particular commands; commands not
566
'commit': ['message', 'verbose'],
567
'diff': ['revision'],
568
'inventory': ['revision'],
569
'log': ['show-ids', 'timezone'],
570
'ls': ['revision', 'verbose'],
571
'remove': ['verbose'],
580
'export': ['revno', 'dest'],
581
'file-id': ['filename'],
582
'get-file-text': ['text_id'],
583
'get-inventory': ['inventory_id'],
584
'get-revision': ['revision_id'],
585
'get-revision-inventory': ['revision_id'],
589
'lookup-revision': ['revno'],
590
'relpath': ['filename'],
592
'root': ['filename?'],
597
def parse_args(argv):
598
"""Parse command line.
600
Arguments and options are parsed at this level before being passed
601
down to specific command handlers. This routine knows, from a
602
lookup table, something about the available options, what optargs
603
they take, and which commands will accept them.
605
>>> parse_args('--help'.split())
607
>>> parse_args('--version'.split())
608
([], {'version': True})
609
>>> parse_args('status --all'.split())
610
(['status'], {'all': True})
611
>>> parse_args('commit --message=biter'.split())
612
(['commit'], {'message': u'biter'})
617
# TODO: Maybe handle '--' to end options?
624
mutter(" got option %r" % a)
626
optname, optarg = a[2:].split('=', 1)
629
if optname not in OPTIONS:
630
bailout('unknown long option %r' % a)
633
if shortopt not in SHORT_OPTIONS:
634
bailout('unknown short option %r' % a)
635
optname = SHORT_OPTIONS[shortopt]
638
# XXX: Do we ever want to support this, e.g. for -r?
639
bailout('repeated option %r' % a)
641
optargfn = OPTIONS[optname]
645
bailout('option %r needs an argument' % a)
648
opts[optname] = optargfn(optarg)
649
mutter(" option argument %r" % opts[optname])
652
bailout('option %r takes no argument' % optname)
661
def _match_args(cmd, args):
662
"""Check non-option arguments match required pattern.
664
>>> _match_args('status', ['asdasdsadasd'])
665
Traceback (most recent call last):
667
BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
668
>>> _match_args('add', ['asdasdsadasd'])
669
{'file_list': ['asdasdsadasd']}
670
>>> _match_args('add', 'abc def gj'.split())
671
{'file_list': ['abc', 'def', 'gj']}
673
# match argument pattern
674
argform = cmd_args.get(cmd, [])
676
# TODO: Need a way to express 'cp SRC... DEST', where it matches
679
# step through args and argform, allowing appropriate 0-many matches
684
argdict[argname] = args.pop(0)
689
bailout("command %r needs one or more %s"
690
% (cmd, argname.upper()))
692
argdict[argname + '_list'] = args[:]
698
bailout("command %r requires argument %s"
699
% (cmd, argname.upper()))
701
argdict[argname] = args.pop(0)
704
bailout("extra arguments to command %s: %r"
712
"""Execute a command.
714
This is similar to main(), but without all the trappings for
715
logging and error handling.
718
args, opts = parse_args(argv[1:])
720
# TODO: pass down other arguments in case they asked for
721
# help on a command name?
724
elif 'version' in opts:
729
log_error('usage: bzr COMMAND\n')
730
log_error(' try "bzr help"\n')
734
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
736
bailout("unknown command " + `cmd`)
738
# TODO: special --profile option to turn on the Python profiler
740
# check options are reasonable
741
allowed = cmd_options.get(cmd, [])
743
if oname not in allowed:
744
bailout("option %r is not allowed for command %r"
747
cmdargs = _match_args(cmd, args)
750
ret = cmd_handler(**cmdargs) or 0
755
## TODO: Handle command-line options; probably know what options are valid for
758
## TODO: If the arguments are wrong, give a usage message rather
759
## than just a backtrace.
761
bzrlib.trace.create_tracefile(argv)
767
log_error('bzr: error: ' + e.args[0] + '\n')
770
log_error(' ' + h + '\n')
773
log_error('bzr: exception: %s\n' % e)
774
log_error(' see .bzr.log for details\n')
775
traceback.print_exc(None, bzrlib.trace._tracefile)
776
traceback.print_exc(None, sys.stderr)
779
# TODO: Maybe nicer handling of IOError?
783
if __name__ == '__main__':
784
sys.exit(main(sys.argv))
786
##profile.run('main(sys.argv)')