17
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
22
"""Bazaar-NG -- a free distributed version-control tool
22
24
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
26
Current limitation include:
24
28
* Metadata format is not stable yet -- you may need to
25
29
discard history in the future.
31
* No handling of subdirectories, symlinks or any non-text files.
33
* Insufficient error handling.
27
35
* Many commands unimplemented or partially implemented.
29
37
* Space-inefficient storage.
31
39
* No merge operators yet.
41
Interesting commands::
44
Show summary help screen
38
Show software version/licence/non-warranty.
46
Show software version/licence/non-warranty.
40
Start versioning the current directory
48
Start versioning the current directory
44
Show revision history.
47
bzr move FROM... DESTDIR
48
Move one or more files to a different directory.
50
Show changes from last revision to working copy.
52
Show revision history.
54
Show changes from last revision to working copy.
51
55
bzr commit -m 'MESSAGE'
52
Store current state as new revision.
56
Store current state as new revision.
53
57
bzr export REVNO DESTINATION
54
Export the branch state at a previous version.
58
Export the branch state at a previous version.
56
Show summary of pending changes.
60
Show summary of pending changes.
58
Make a file not versioned.
60
Show statistics about this branch.
62
Verify history is stored safely.
63
(for more type 'bzr help commands')
62
Make a file not versioned.
69
import sys, os, time, types, shutil, tempfile, fnmatch, difflib, os.path
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
70
82
from sets import Set
71
83
from pprint import pprint
73
85
from glob import glob
74
from inspect import getdoc
86
from ElementTree import Element, ElementTree, SubElement
77
89
from bzrlib.store import ImmutableStore
78
90
from bzrlib.trace import mutter, note, log_error
79
from bzrlib.errors import bailout, BzrError, BzrCheckError
91
from bzrlib.errors import bailout, BzrError
80
92
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
81
93
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
82
94
from bzrlib.revision import Revision
104
116
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
105
117
## to compare output?
107
## TODO: Some kind of global code to generate the right Branch object
108
## to work on. Almost, but not quite all, commands need one, and it
109
## can be taken either from their parameters or their working
124
def get_cmd_handler(cmd):
127
cmd = cmd_aliases.get(cmd, cmd)
130
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
132
raise BzrError("unknown command %r" % cmd)
134
return cmd, cmd_handler
119
## TODO: Is ElementTree really all that much better for our purposes?
120
## Perhaps using the standard MiniDOM would be enough?
127
######################################################################
138
131
def cmd_status(all=False):
170
174
print Branch('.').revno()
174
177
def cmd_add(file_list, verbose=False):
175
"""Add specified files or directories.
177
In non-recursive mode, all the named items are added, regardless
178
of whether they were previously ignored. A warning is given if
179
any of the named files are already versioned.
181
In recursive mode (the default), files are treated the same way
182
but the behaviour for directories is different. Directories that
183
are already versioned do not give a warning. All directories,
184
whether already versioned or not, are searched for files or
185
subdirectories that are neither versioned or ignored, and these
186
are added. This search proceeds recursively into versioned
189
Therefore simply saying 'bzr add .' will version all files that
190
are currently unknown.
192
TODO: Perhaps adding a file whose directly is not versioned should
193
recursively add that parent, rather than giving an error?
178
"""Add specified files.
180
Fails if the files are already added.
195
bzrlib.add.smart_add(file_list, verbose)
198
def cmd_relpath(filename):
199
"""Show path of file relative to root"""
200
print Branch(filename).relpath(filename)
182
Branch('.').add(file_list, verbose=verbose)
204
185
def cmd_inventory(revision=None):
219
# TODO: Maybe a 'mv' command that has the combined move/rename
220
# special behaviour of Unix?
222
def cmd_move(source_list, dest):
225
b.move([b.relpath(s) for s in source_list], b.relpath(dest))
229
def cmd_rename(from_name, to_name):
230
"""Change the name of an entry.
232
usage: bzr rename FROM_NAME TO_NAME
235
bzr rename frob.c frobber.c
236
bzr rename src/frob.c lib/frob.c
238
It is an error if the destination name exists.
240
See also the 'move' command, which moves files into a different
241
directory without changing their name.
243
TODO: Some way to rename multiple files without invoking bzr for each
246
b.rename_one(b.relpath(from_name), b.relpath(to_name))
251
def cmd_renames(dir='.'):
252
"""Show list of renamed files.
254
usage: bzr renames [BRANCH]
256
TODO: Option to show renames between two historical versions.
258
TODO: Only show renames under dir, rather than in the whole branch.
261
old_inv = b.basis_tree().inventory
262
new_inv = b.read_working_inventory()
264
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
266
for old_name, new_name in renames:
267
print "%s => %s" % (old_name, new_name)
272
"""info: Show statistical information for this branch
276
info.show_info(Branch('.'))
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())
280
207
def cmd_remove(file_list, verbose=False):
281
b = Branch(file_list[0])
282
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
208
Branch('.').remove(file_list, verbose=verbose)
286
212
def cmd_file_id(filename):
287
"""Print file_id of a particular file or directory.
289
usage: bzr file-id FILE
291
The file_id is assigned when the file is first added and remains the
292
same through all revisions where the file exists, even when it is
296
i = b.inventory.path2id(b.relpath(filename))
298
bailout("%r is not a versioned file" % filename)
213
i = Branch('.').read_working_inventory().path2id(filename)
215
bailout("%s is not a versioned file" % filename)
303
def cmd_file_id_path(filename):
304
"""Print path of file_ids to a file or directory.
306
usage: bzr file-id-path FILE
308
This prints one line for each directory down to the target,
309
starting at the branch root."""
312
fid = inv.path2id(b.relpath(filename))
314
bailout("%r is not a versioned file" % filename)
315
for fip in inv.get_idpath(fid):
220
def cmd_find_filename(fileid):
221
n = find_filename(fileid)
223
bailout("%s is not a live file id" % fileid)
319
228
def cmd_revision_history():
347
244
Branch('.', init=True)
350
def cmd_diff(revision=None, file_list=None):
351
"""bzr diff: Show differences in working tree.
353
usage: bzr diff [-r REV] [FILE...]
356
Show changes since REV, rather than predecessor.
358
If files are listed, only the changes in those files are listed.
359
Otherwise, all changes for the tree are listed.
361
TODO: Given two revision arguments, show the difference between them.
363
TODO: Allow diff across branches.
365
TODO: Option to use external diff command; could be GNU diff, wdiff,
368
TODO: Python difflib is not exactly the same as unidiff; should
369
either fix it up or prefer to use an external diff.
371
TODO: If a directory is given, diff everything under that.
373
TODO: Selected-file diff is inefficient and doesn't show you
376
TODO: This probably handles non-Unix newlines poorly.
379
## TODO: Shouldn't be in the cmd function.
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.
419
292
# FIXME: Something about the diff format makes patch unhappy
420
293
# with newly-added files.
422
def diffit(oldlines, newlines, **kw):
424
# FIXME: difflib is wrong if there is no trailing newline.
425
# The syntax used by patch seems to be "\ No newline at
426
# end of file" following the last diff line from that
427
# file. This is not trivial to insert into the
428
# unified_diff output and it might be better to just fix
429
# or replace that function.
431
# In the meantime we at least make sure the patch isn't
435
# Special workaround for Python2.3, where difflib fails if
436
# both sequences are empty.
437
if not oldlines and not newlines:
442
if oldlines and (oldlines[-1][-1] != '\n'):
445
if newlines and (newlines[-1][-1] != '\n'):
449
ud = difflib.unified_diff(oldlines, newlines, **kw)
450
sys.stdout.writelines(ud)
452
print "\\ No newline at end of file"
453
sys.stdout.write('\n')
295
def diffit(*a, **kw):
296
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
455
299
if file_state in ['.', '?', 'I']:
489
def cmd_deleted(show_ids=False):
490
"""List files deleted in the working tree.
492
TODO: Show files deleted since a previous revision, or between two revisions.
496
new = b.working_tree()
498
## TODO: Much more efficient way to do this: read in new
499
## directories with readdir, rather than stating each one. Same
500
## level of effort but possibly much less IO. (Or possibly not,
501
## if the directories are very large...)
503
for path, ie in old.inventory.iter_entries():
504
if not new.has_id(ie.file_id):
506
print '%-50s %s' % (path, ie.file_id)
512
def cmd_parse_inventory():
515
cElementTree.ElementTree().parse(file('.bzr/inventory'))
519
def cmd_load_inventory():
520
"""Load inventory for timing purposes"""
521
Branch('.').basis_tree().inventory
524
def cmd_dump_inventory():
525
Branch('.').read_working_inventory().write_xml(sys.stdout)
528
def cmd_dump_new_inventory():
529
import bzrlib.newinventory
530
inv = Branch('.').basis_tree().inventory
531
bzrlib.newinventory.write_inventory(inv, sys.stdout)
534
def cmd_load_new_inventory():
535
import bzrlib.newinventory
536
bzrlib.newinventory.read_new_inventory(sys.stdin)
539
def cmd_dump_slacker_inventory():
540
import bzrlib.newinventory
541
inv = Branch('.').basis_tree().inventory
542
bzrlib.newinventory.write_slacker_inventory(inv, sys.stdout)
546
def cmd_dump_text_inventory():
547
import bzrlib.textinv
548
inv = Branch('.').basis_tree().inventory
549
bzrlib.textinv.write_text_inventory(inv, sys.stdout)
552
def cmd_load_text_inventory():
553
import bzrlib.textinv
554
inv = bzrlib.textinv.read_text_inventory(sys.stdin)
555
print 'loaded %d entries' % len(inv)
559
def cmd_root(filename=None):
560
"""Print the branch root."""
561
print bzrlib.branch.find_branch_root(filename)
564
def cmd_log(timezone='original', verbose=False):
565
334
"""Show log of this branch.
567
TODO: Options for utc; to show ids; to limit range; etc.
336
:todo: Options for utc; to show ids; to limit range; etc.
569
Branch('.').write_log(show_timezone=timezone, verbose=verbose)
338
Branch('.').write_log()
572
341
def cmd_ls(revision=None, verbose=False):
573
342
"""List files in a tree.
575
TODO: Take a revision or remote path and list that tree instead.
344
:todo: Take a revision or remote path and list that tree instead.
578
347
if revision == None:
605
def cmd_ignore(name_pattern):
606
"""Ignore a command or pattern"""
610
# XXX: This will fail if it's a hardlink; should use an AtomicFile class.
611
f = open(b.abspath('.bzrignore'), 'at')
612
f.write(name_pattern + '\n')
615
inv = b.working_tree().inventory
616
if inv.path2id('.bzrignore'):
617
mutter('.bzrignore is already versioned')
619
mutter('need to make new .bzrignore file versioned')
620
b.add(['.bzrignore'])
625
"""List ignored files and the patterns that matched them.
627
tree = Branch('.').working_tree()
628
for path, file_class, kind, file_id in tree.list_files():
629
if file_class != 'I':
631
## XXX: Slightly inefficient since this was already calculated
632
pat = tree.is_ignored(path)
633
print '%-50s %s' % (path, pat)
636
373
def cmd_lookup_revision(revno):
638
375
revno = int(revno)
665
398
"""Print a newly-generated UUID."""
666
print bzrlib.osutils.uuid()
670
def cmd_local_time_offset():
671
print bzrlib.osutils.local_time_offset()
675
def cmd_commit(message=None, verbose=False):
676
"""Commit changes to a new revision.
679
Description of changes in this revision; free form text.
680
It is recommended that the first line be a single-sentence
683
Show status of changed files,
685
TODO: Commit only selected files.
687
TODO: Run hooks on tree to-be-committed, and after commit.
689
TODO: Strict commit that fails if there are unknown or deleted files.
693
bailout("please specify a commit message")
403
def cmd_commit(message, verbose=False):
694
404
Branch('.').commit(message, verbose=verbose)
697
def cmd_check(dir='.'):
698
"""check: Consistency check of branch history.
700
usage: bzr check [-v] [BRANCH]
703
--verbose, -v Show progress of checking.
705
This command checks various invariants about the branch storage to
706
detect data corruption or bzr bugs.
709
bzrlib.check.check(Branch(dir, find_root=False))
408
"""Check consistency of the branch."""
712
412
def cmd_is(pred, *rest):
727
def cmd_whoami(email=False):
733
--email Show only the email address.
737
print bzrlib.osutils.user_email()
739
print bzrlib.osutils.username()
428
print bzrlib.osutils.username()
431
def cmd_user_email():
432
print bzrlib.osutils.user_email()
742
435
def cmd_gen_revision_id():
743
437
print bzrlib.branch._gen_revision_id(time.time())
747
"""Run internal test suite"""
441
"""Run internal doctest suite"""
748
442
## -v, if present, is seen by doctest; the argument is just here
749
443
## so our parser doesn't complain
751
445
## TODO: --verbose option
753
failures, tests = 0, 0
755
import doctest, bzrlib.store, bzrlib.tests
447
import bzr, doctest, bzrlib.store
756
448
bzrlib.trace.verbose = False
758
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
759
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
760
mf, mt = doctest.testmod(m)
763
print '%-40s %3d tests' % (m.__name__, mt),
765
print '%3d FAILED!' % mf
769
print '%-40s %3d tests' % ('total', tests),
771
print '%3d FAILED!' % failures
778
cmd_doctest = cmd_selftest
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)
781
461
######################################################################
785
def cmd_help(topic=None):
788
elif topic == 'commands':
791
# otherwise, maybe the name of a command?
792
topic, cmdfn = get_cmd_handler(topic)
796
bailout("sorry, no detailed help yet for %r" % topic)
802
"""List all commands"""
804
for k in globals().keys():
805
if k.startswith('cmd_'):
806
accu.append(k[4:].replace('_','-'))
808
print "bzr commands: "
811
print "note: some of these commands are internal-use or obsolete"
812
# TODO: Some kind of marker for internal-use commands?
813
# TODO: Show aliases?
466
# TODO: Specific help for particular commands
818
470
def cmd_version():
819
print "bzr (bazaar-ng) %s" % bzrlib.__version__
820
print bzrlib.__copyright__
471
print "bzr (bazaar-ng) %s" % __version__
821
473
print "http://bazaar-ng.org/"
862
511
# listed take none.
864
513
'add': ['verbose'],
866
514
'commit': ['message', 'verbose'],
867
'deleted': ['show-ids'],
868
515
'diff': ['revision'],
869
516
'inventory': ['revision'],
870
'log': ['timezone', 'verbose'],
871
517
'ls': ['revision', 'verbose'],
872
520
'remove': ['verbose'],
879
526
'add': ['file+'],
883
'export': ['revno', 'dest'],
884
529
'file-id': ['filename'],
885
'file-id-path': ['filename'],
886
530
'get-file-text': ['text_id'],
887
531
'get-inventory': ['inventory_id'],
888
532
'get-revision': ['revision_id'],
889
533
'get-revision-inventory': ['revision_id'],
891
'ignore': ['name_pattern'],
894
535
'lookup-revision': ['revno'],
895
'move': ['source$', 'dest'],
896
'relpath': ['filename'],
536
'export': ['revno', 'dest'],
897
537
'remove': ['file+'],
898
'rename': ['from_name', 'to_name'],
900
'root': ['filename?'],
910
547
lookup table, something about the available options, what optargs
911
548
they take, and which commands will accept them.
913
>>> parse_args('--help'.split())
550
>>> parse_args('bzr --help'.split())
914
551
([], {'help': True})
915
>>> parse_args('--version'.split())
552
>>> parse_args('bzr --version'.split())
916
553
([], {'version': True})
917
>>> parse_args('status --all'.split())
554
>>> parse_args('bzr status --all'.split())
918
555
(['status'], {'all': True})
919
>>> parse_args('commit --message=biter'.split())
920
(['commit'], {'message': u'biter'})
925
560
# TODO: Maybe handle '--' to end options?
930
# option names must not be unicode
934
567
mutter(" got option %r" % a)
936
optname, optarg = a[2:].split('=', 1)
939
569
if optname not in OPTIONS:
940
570
bailout('unknown long option %r' % a)
1031
646
"""Execute a command.
1033
648
This is similar to main(), but without all the trappings for
1034
logging and error handling.
649
logging and error handling.
1037
argv = [a.decode(bzrlib.user_encoding) for a in argv]
1040
args, opts = parse_args(argv[1:])
652
args, opts = parse_args(argv)
1041
653
if 'help' in opts:
1042
654
# TODO: pass down other arguments in case they asked for
1043
655
# help on a command name?
1049
658
elif 'version' in opts:
1052
cmd = str(args.pop(0))
1053
662
except IndexError:
1054
log_error('usage: bzr COMMAND')
1055
log_error(' try "bzr help"')
663
log_error('usage: bzr COMMAND\n')
664
log_error(' try "bzr help"\n')
1058
canonical_cmd, cmd_handler = get_cmd_handler(cmd)
1061
if 'profile' in opts:
668
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
670
bailout("unknown command " + `cmd`)
672
# TODO: special --profile option to turn on the Python profiler
1067
674
# check options are reasonable
1068
allowed = cmd_options.get(canonical_cmd, [])
675
allowed = cmd_options.get(cmd, [])
1069
676
for oname in opts:
1070
677
if oname not in allowed:
1071
678
bailout("option %r is not allowed for command %r"
1074
# TODO: give an error if there are any mandatory options which are
1075
# not specified? Or maybe there shouldn't be any "mandatory
1076
# options" (it is an oxymoron)
1078
# mix arguments and options into one dictionary
1079
cmdargs = _match_args(canonical_cmd, args)
1080
for k, v in opts.items():
1081
cmdargs[k.replace('-', '_')] = v
1085
pffileno, pfname = tempfile.mkstemp()
1087
prof = hotshot.Profile(pfname)
1088
ret = prof.runcall(cmd_handler, **cmdargs) or 0
1091
import hotshot.stats
1092
stats = hotshot.stats.load(pfname)
1094
stats.sort_stats('time')
1095
## XXX: Might like to write to stderr or the trace file instead but
1096
## print_stats seems hardcoded to stdout
1097
stats.print_stats(20)
1105
return cmd_handler(**cmdargs) or 0
1109
def _report_exception(e, summary):
1111
log_error('bzr: ' + summary)
1112
bzrlib.trace.log_exception(e)
1113
tb = sys.exc_info()[2]
1114
exinfo = traceback.extract_tb(tb)
1116
sys.stderr.write(' at %s:%d in %s()\n' % exinfo[-1][:3])
1117
sys.stderr.write(' see ~/.bzr.log for debug information\n')
1120
def cmd_assert_fail():
1121
assert False, "always fails"
681
cmdargs = _match_args(cmd, args)
684
ret = cmd_handler(**cmdargs) or 0
1125
bzrlib.trace.create_tracefile(argv)
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.
1132
_report_exception(e, 'error: ' + e.args[0])
1135
# some explanation or hints
1138
except AssertionError, e:
1139
msg = 'assertion failed'
1141
msg += ': ' + str(e)
1142
_report_exception(e, msg)
1143
except Exception, e:
1144
_report_exception(e, 'exception: %s' % str(e).rstrip('\n'))
1147
bzrlib.trace.close_trace()
1149
## TODO: Trap AssertionError
1151
# TODO: Maybe nicer handling of IOError especially for broken pipe.
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?
1155
733
if __name__ == '__main__':
1156
734
sys.exit(main(sys.argv))
736
##profile.run('main(sys.argv)')