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.
66
__copyright__ = "Copyright 2005 Canonical Development Ltd."
67
__author__ = "Martin Pool <mbp@canonical.com>"
68
__docformat__ = "restructuredtext en"
72
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
73
import traceback, socket, fnmatch, difflib
76
from pprint import pprint
81
from bzrlib.store import ImmutableStore
82
from bzrlib.trace import mutter, note, log_error
83
from bzrlib.errors import bailout, BzrError
84
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
85
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
86
from bzrlib.revision import Revision
87
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
90
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
91
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
93
## standard representation
94
NONE_STRING = '(none)'
98
## TODO: Perhaps a different version of inventory commands that
99
## returns iterators...
101
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
103
## TODO: Some kind of locking on branches. Perhaps there should be a
104
## parameter to the branch object saying whether we want a read or
105
## write lock; release it from destructor. Perhaps don't even need a
106
## read lock to look at immutable objects?
108
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
109
## to compare output?
111
## TODO: Some kind of global code to generate the right Branch object
112
## to work on. Almost, but not quite all, commands need one, and it
113
## can be taken either from their parameters or their working
116
## TODO: rename command, needed soon: check destination doesn't exist
117
## either in working copy or tree; move working copy; update
118
## inventory; write out
120
## TODO: move command; check destination is a directory and will not
123
## TODO: command to show renames, one per line, as to->from
128
def cmd_status(all=False):
129
"""Display status summary.
131
For each file there is a single line giving its file state and name.
132
The name is that in the current revision unless it is deleted or
133
missing, in which case the old name is shown.
135
:todo: Don't show unchanged files unless ``--all`` is given?
137
Branch('.').show_status(show_all=all)
141
######################################################################
143
def cmd_get_revision(revision_id):
144
Branch('.').get_revision(revision_id).write_xml(sys.stdout)
147
def cmd_get_file_text(text_id):
148
"""Get contents of a file by hash."""
149
sf = Branch('.').text_store[text_id]
150
pumpfile(sf, sys.stdout)
154
######################################################################
159
"""Show number of revisions on this branch"""
160
print Branch('.').revno()
164
def cmd_add(file_list, verbose=False):
165
"""Add specified files or directories.
167
In non-recursive mode, all the named items are added, regardless
168
of whether they were previously ignored. A warning is given if
169
any of the named files are already versioned.
171
In recursive mode (the default), files are treated the same way
172
but the behaviour for directories is different. Directories that
173
are already versioned do not give a warning. All directories,
174
whether already versioned or not, are searched for files or
175
subdirectories that are neither versioned or ignored, and these
176
are added. This search proceeds recursively into versioned
179
Therefore simply saying 'bzr add .' will version all files that
180
are currently unknown.
183
bzrlib.add.smart_add(file_list, verbose)
187
b = Branch(file_list[0], find_root=True)
188
b.add([b.relpath(f) for f in file_list], verbose=verbose)
192
def cmd_relpath(filename):
193
print Branch(filename).relpath(filename)
196
def cmd_inventory(revision=None):
197
"""Show inventory of the current working copy."""
198
## TODO: Also optionally show a previous inventory
199
## TODO: Format options
202
inv = b.read_working_inventory()
204
inv = b.get_revision_inventory(b.lookup_revision(revision))
206
for path, entry in inv.iter_entries():
207
print '%-50s %s' % (entry.file_id, path)
213
info.show_info(Branch('.'))
217
def cmd_remove(file_list, verbose=False):
218
b = Branch(file_list[0])
219
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
223
def cmd_file_id(filename):
225
i = b.inventory.path2id(b.relpath(filename))
227
bailout("%s is not a versioned file" % filename)
232
def cmd_find_filename(fileid):
233
n = find_filename(fileid)
235
bailout("%s is not a live file id" % fileid)
240
def cmd_revision_history():
241
for patchid in Branch('.').revision_history():
247
# TODO: Check we're not already in a working directory? At the
248
# moment you'll get an ugly error.
250
# TODO: What if we're in a subdirectory of a branch? Would like
251
# to allow that, but then the parent may need to understand that
252
# the children have disappeared, or should they be versioned in
255
# TODO: Take an argument/option for branch name.
256
Branch('.', init=True)
259
def cmd_diff(revision=None):
260
"""Show diff from basis to working copy.
262
:todo: Take one or two revision arguments, look up those trees,
265
:todo: Allow diff across branches.
267
:todo: Mangle filenames in diff to be more relevant.
269
:todo: Shouldn't be in the cmd function.
271
TODO: Option to use external diff command; could be GNU diff,
272
wdiff, or a graphical diff.
278
old_tree = b.basis_tree()
280
old_tree = b.revision_tree(b.lookup_revision(revision))
282
new_tree = b.working_tree()
283
old_inv = old_tree.inventory
284
new_inv = new_tree.inventory
286
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
290
DEVNULL = '/dev/null'
291
# Windows users, don't panic about this filename -- it is a
292
# special signal to GNU patch that the file should be created or
293
# deleted respectively.
295
# TODO: Generation of pseudo-diffs for added/deleted files could
296
# be usefully made into a much faster special case.
298
# TODO: Better to return them in sorted order I think.
300
for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
303
# Don't show this by default; maybe do it if an option is passed
304
# idlabel = ' {%s}' % fid
307
# FIXME: Something about the diff format makes patch unhappy
308
# with newly-added files.
310
def diffit(*a, **kw):
311
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
314
if file_state in ['.', '?', 'I']:
316
elif file_state == 'A':
317
print '*** added %s %r' % (kind, new_name)
320
new_tree.get_file(fid).readlines(),
322
tofile=new_label + new_name + idlabel)
323
elif file_state == 'D':
324
assert isinstance(old_name, types.StringTypes)
325
print '*** deleted %s %r' % (kind, old_name)
327
diffit(old_tree.get_file(fid).readlines(), [],
328
fromfile=old_label + old_name + idlabel,
330
elif file_state in ['M', 'R']:
331
if file_state == 'M':
332
assert kind == 'file'
333
assert old_name == new_name
334
print '*** modified %s %r' % (kind, new_name)
335
elif file_state == 'R':
336
print '*** renamed %s %r => %r' % (kind, old_name, new_name)
339
diffit(old_tree.get_file(fid).readlines(),
340
new_tree.get_file(fid).readlines(),
341
fromfile=old_label + old_name + idlabel,
342
tofile=new_label + new_name)
344
bailout("can't represent state %s {%s}" % (file_state, fid))
348
def cmd_root(filename=None):
349
"""Print the branch root."""
350
print bzrlib.branch.find_branch_root(filename)
353
def cmd_log(timezone='original'):
354
"""Show log of this branch.
356
:todo: Options for utc; to show ids; to limit range; etc.
358
Branch('.').write_log(show_timezone=timezone)
361
def cmd_ls(revision=None, verbose=False):
362
"""List files in a tree.
364
:todo: Take a revision or remote path and list that tree instead.
368
tree = b.working_tree()
370
tree = b.revision_tree(b.lookup_revision(revision))
372
for fp, fc, kind, fid in tree.list_files():
374
if kind == 'directory':
381
print '%-8s %s%s' % (fc, fp, kindch)
388
"""List unknown files"""
389
for f in Branch('.').unknowns():
393
def cmd_lookup_revision(revno):
397
bailout("usage: lookup-revision REVNO",
398
["REVNO is a non-negative revision number for this branch"])
400
print Branch('.').lookup_revision(revno) or NONE_STRING
404
def cmd_export(revno, dest):
405
"""Export past revision to destination directory."""
407
rh = b.lookup_revision(int(revno))
408
t = b.revision_tree(rh)
413
######################################################################
414
# internal/test commands
418
"""Print a newly-generated UUID."""
419
print bzrlib.osutils.uuid()
423
def cmd_local_time_offset():
424
print bzrlib.osutils.local_time_offset()
428
def cmd_commit(message=None, verbose=False):
430
bailout("please specify a commit message")
431
Branch('.').commit(message, verbose=verbose)
435
"""Check consistency of the branch."""
439
def cmd_is(pred, *rest):
440
"""Test whether PREDICATE is true."""
442
cmd_handler = globals()['assert_' + pred.replace('-', '_')]
444
bailout("unknown predicate: %s" % quotefn(pred))
448
except BzrCheckError:
449
# by default we don't print the message so that this can
450
# be used from shell scripts without producing noise
455
print bzrlib.osutils.username()
458
def cmd_user_email():
459
print bzrlib.osutils.user_email()
462
def cmd_gen_revision_id():
464
print bzrlib.branch._gen_revision_id(time.time())
467
def cmd_selftest(verbose=False):
468
"""Run internal test suite"""
469
## -v, if present, is seen by doctest; the argument is just here
470
## so our parser doesn't complain
472
## TODO: --verbose option
474
failures, tests = 0, 0
476
import doctest, bzrlib.store, bzrlib.tests
477
bzrlib.trace.verbose = False
479
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
480
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
481
mf, mt = doctest.testmod(m)
484
print '%-40s %3d tests' % (m.__name__, mt),
486
print '%3d FAILED!' % mf
490
print '%-40s %3d tests' % ('total', tests),
492
print '%3d FAILED!' % failures
499
cmd_doctest = cmd_selftest
502
######################################################################
506
def cmd_help(topic=None):
511
# otherwise, maybe the name of a command?
513
cmdfn = globals()['cmd_' + topic.replace('-', '_')]
515
bailout("no help for %r" % topic)
519
bailout("sorry, no detailed help yet for %r" % topic)
527
print "bzr (bazaar-ng) %s" % __version__
529
print "http://bazaar-ng.org/"
532
"""bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and
533
you may use, modify and redistribute it under the terms of the GNU
534
General Public License version 2 or later."""
538
"""Statement of optimism."""
539
print "it sure does!"
543
######################################################################
547
# list of all available options; the rhs can be either None for an
548
# option that takes no argument, or a constructor function that checks
567
# List of options that apply to particular commands; commands not
571
'commit': ['message', 'verbose'],
572
'diff': ['revision'],
573
'inventory': ['revision'],
574
'log': ['show-ids', 'timezone'],
575
'ls': ['revision', 'verbose'],
576
'remove': ['verbose'],
585
'export': ['revno', 'dest'],
586
'file-id': ['filename'],
587
'get-file-text': ['text_id'],
588
'get-inventory': ['inventory_id'],
589
'get-revision': ['revision_id'],
590
'get-revision-inventory': ['revision_id'],
594
'lookup-revision': ['revno'],
595
'relpath': ['filename'],
597
'root': ['filename?'],
602
def parse_args(argv):
603
"""Parse command line.
605
Arguments and options are parsed at this level before being passed
606
down to specific command handlers. This routine knows, from a
607
lookup table, something about the available options, what optargs
608
they take, and which commands will accept them.
610
>>> parse_args('--help'.split())
612
>>> parse_args('--version'.split())
613
([], {'version': True})
614
>>> parse_args('status --all'.split())
615
(['status'], {'all': True})
616
>>> parse_args('commit --message=biter'.split())
617
(['commit'], {'message': u'biter'})
622
# TODO: Maybe handle '--' to end options?
629
mutter(" got option %r" % a)
631
optname, optarg = a[2:].split('=', 1)
634
if optname not in OPTIONS:
635
bailout('unknown long option %r' % a)
638
if shortopt not in SHORT_OPTIONS:
639
bailout('unknown short option %r' % a)
640
optname = SHORT_OPTIONS[shortopt]
643
# XXX: Do we ever want to support this, e.g. for -r?
644
bailout('repeated option %r' % a)
646
optargfn = OPTIONS[optname]
650
bailout('option %r needs an argument' % a)
653
opts[optname] = optargfn(optarg)
654
mutter(" option argument %r" % opts[optname])
657
bailout('option %r takes no argument' % optname)
666
def _match_args(cmd, args):
667
"""Check non-option arguments match required pattern.
669
>>> _match_args('status', ['asdasdsadasd'])
670
Traceback (most recent call last):
672
BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
673
>>> _match_args('add', ['asdasdsadasd'])
674
{'file_list': ['asdasdsadasd']}
675
>>> _match_args('add', 'abc def gj'.split())
676
{'file_list': ['abc', 'def', 'gj']}
678
# match argument pattern
679
argform = cmd_args.get(cmd, [])
681
# TODO: Need a way to express 'cp SRC... DEST', where it matches
684
# step through args and argform, allowing appropriate 0-many matches
689
argdict[argname] = args.pop(0)
694
bailout("command %r needs one or more %s"
695
% (cmd, argname.upper()))
697
argdict[argname + '_list'] = args[:]
703
bailout("command %r requires argument %s"
704
% (cmd, argname.upper()))
706
argdict[argname] = args.pop(0)
709
bailout("extra arguments to command %s: %r"
717
"""Execute a command.
719
This is similar to main(), but without all the trappings for
720
logging and error handling.
723
args, opts = parse_args(argv[1:])
725
# TODO: pass down other arguments in case they asked for
726
# help on a command name?
729
elif 'version' in opts:
734
log_error('usage: bzr COMMAND\n')
735
log_error(' try "bzr help"\n')
739
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
741
bailout("unknown command " + `cmd`)
743
# TODO: special --profile option to turn on the Python profiler
745
# check options are reasonable
746
allowed = cmd_options.get(cmd, [])
748
if oname not in allowed:
749
bailout("option %r is not allowed for command %r"
752
cmdargs = _match_args(cmd, args)
755
ret = cmd_handler(**cmdargs) or 0
760
## TODO: Handle command-line options; probably know what options are valid for
763
## TODO: If the arguments are wrong, give a usage message rather
764
## than just a backtrace.
766
bzrlib.trace.create_tracefile(argv)
772
log_error('bzr: error: ' + e.args[0] + '\n')
775
log_error(' ' + h + '\n')
778
log_error('bzr: exception: %s\n' % e)
779
log_error(' see .bzr.log for details\n')
780
traceback.print_exc(None, bzrlib.trace._tracefile)
781
traceback.print_exc(None, sys.stderr)
784
# TODO: Maybe nicer handling of IOError?
788
if __name__ == '__main__':
789
sys.exit(main(sys.argv))
791
##profile.run('main(sys.argv)')