20
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
19
"""Bazaar-NG -- a free distributed version-control tool
24
22
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
26
Current limitation include:
28
24
* Metadata format is not stable yet -- you may need to
29
25
discard history in the future.
31
* No handling of subdirectories, symlinks or any non-text files.
33
* Insufficient error handling.
35
27
* Many commands unimplemented or partially implemented.
37
29
* Space-inefficient storage.
39
31
* No merge operators yet.
41
Interesting commands::
44
Show summary help screen
46
Show software version/licence/non-warranty.
38
Show software version/licence/non-warranty.
48
Start versioning the current directory
40
Start versioning the current directory
52
Show revision history.
54
Show changes from last revision to working copy.
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.
55
51
bzr commit -m 'MESSAGE'
56
Store current state as new revision.
52
Store current state as new revision.
57
53
bzr export REVNO DESTINATION
58
Export the branch state at a previous version.
54
Export the branch state at a previous version.
60
Show summary of pending changes.
56
Show summary of pending changes.
62
Make a file not versioned.
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')
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
69
import sys, os, time, types, shutil, tempfile, fnmatch, difflib, os.path
82
70
from sets import Set
83
71
from pprint import pprint
85
73
from glob import glob
86
from ElementTree import Element, ElementTree, SubElement
74
from inspect import getdoc
89
77
from bzrlib.store import ImmutableStore
90
78
from bzrlib.trace import mutter, note, log_error
91
from bzrlib.errors import bailout, BzrError
79
from bzrlib.errors import bailout, BzrError, BzrCheckError
92
80
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
93
81
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
94
82
from bzrlib.revision import Revision
116
104
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
117
105
## 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
######################################################################
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
112
## TODO: rename command, needed soon: check destination doesn't exist
113
## either in working copy or tree; move working copy; update
114
## inventory; write out
116
## TODO: move command; check destination is a directory and will not
119
## TODO: command to show renames, one per line, as to->from
133
def get_cmd_handler(cmd):
136
cmd = cmd_aliases.get(cmd, cmd)
139
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
141
raise BzrError("unknown command %r" % cmd)
143
return cmd, cmd_handler
131
147
def cmd_status(all=False):
174
179
print Branch('.').revno()
177
183
def cmd_add(file_list, verbose=False):
178
"""Add specified files.
184
"""Add specified files or directories.
186
In non-recursive mode, all the named items are added, regardless
187
of whether they were previously ignored. A warning is given if
188
any of the named files are already versioned.
190
In recursive mode (the default), files are treated the same way
191
but the behaviour for directories is different. Directories that
192
are already versioned do not give a warning. All directories,
193
whether already versioned or not, are searched for files or
194
subdirectories that are neither versioned or ignored, and these
195
are added. This search proceeds recursively into versioned
198
Therefore simply saying 'bzr add .' will version all files that
199
are currently unknown.
201
TODO: Perhaps adding a file whose directly is not versioned should
202
recursively add that parent, rather than giving an error?
204
bzrlib.add.smart_add(file_list, verbose)
180
Fails if the files are already added.
182
Branch('.').add(file_list, verbose=verbose)
207
def cmd_relpath(filename):
208
"""Show path of file relative to root"""
209
print Branch(filename).relpath(filename)
185
213
def cmd_inventory(revision=None):
228
# TODO: Maybe a 'mv' command that has the combined move/rename
229
# special behaviour of Unix?
231
def cmd_move(source_list, dest):
234
b.move([b.relpath(s) for s in source_list], b.relpath(dest))
238
def cmd_rename(from_name, to_name):
239
"""Change the name of an entry.
241
usage: bzr rename FROM_NAME TO_NAME
244
bzr rename frob.c frobber.c
245
bzr rename src/frob.c lib/frob.c
247
It is an error if the destination name exists.
249
See also the 'move' command, which moves files into a different
250
directory without changing their name.
252
TODO: Some way to rename multiple files without invoking bzr for each
255
b.rename_one(b.relpath(from_name), b.relpath(to_name))
260
def cmd_renames(dir='.'):
261
"""Show list of renamed files.
263
usage: bzr renames [BRANCH]
265
TODO: Option to show renames between two historical versions.
267
TODO: Only show renames under dir, rather than in the whole branch.
270
old_inv = b.basis_tree().inventory
271
new_inv = b.read_working_inventory()
273
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
275
for old_name, new_name in renames:
276
print "%s => %s" % (old_name, new_name)
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())
281
"""info: Show statistical information for this branch
285
info.show_info(Branch('.'))
207
289
def cmd_remove(file_list, verbose=False):
208
Branch('.').remove(file_list, verbose=verbose)
290
b = Branch(file_list[0])
291
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
212
295
def cmd_file_id(filename):
213
i = Branch('.').read_working_inventory().path2id(filename)
215
bailout("%s is not a versioned file" % filename)
296
"""Print file_id of a particular file or directory.
298
usage: bzr file-id FILE
300
The file_id is assigned when the file is first added and remains the
301
same through all revisions where the file exists, even when it is
305
i = b.inventory.path2id(b.relpath(filename))
307
bailout("%r 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)
312
def cmd_file_id_path(filename):
313
"""Print path of file_ids to a file or directory.
315
usage: bzr file-id-path FILE
317
This prints one line for each directory down to the target,
318
starting at the branch root."""
321
fid = inv.path2id(b.relpath(filename))
323
bailout("%r is not a versioned file" % filename)
324
for fip in inv.get_idpath(fid):
228
328
def cmd_revision_history():
244
356
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.
359
def cmd_diff(revision=None, file_list=None):
360
"""bzr diff: Show differences in working tree.
362
usage: bzr diff [-r REV] [FILE...]
365
Show changes since REV, rather than predecessor.
367
If files are listed, only the changes in those files are listed.
368
Otherwise, all changes for the tree are listed.
370
TODO: Given two revision arguments, show the difference between them.
372
TODO: Allow diff across branches.
374
TODO: Option to use external diff command; could be GNU diff, wdiff,
377
TODO: Python difflib is not exactly the same as unidiff; should
378
either fix it up or prefer to use an external diff.
380
TODO: If a directory is given, diff everything under that.
382
TODO: Selected-file diff is inefficient and doesn't show you
385
TODO: This probably handles non-Unix newlines poorly.
388
## TODO: Shouldn't be in the cmd function.
292
428
# FIXME: Something about the diff format makes patch unhappy
293
429
# with newly-added files.
295
def diffit(*a, **kw):
296
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
431
def diffit(oldlines, newlines, **kw):
433
# FIXME: difflib is wrong if there is no trailing newline.
434
# The syntax used by patch seems to be "\ No newline at
435
# end of file" following the last diff line from that
436
# file. This is not trivial to insert into the
437
# unified_diff output and it might be better to just fix
438
# or replace that function.
440
# In the meantime we at least make sure the patch isn't
444
# Special workaround for Python2.3, where difflib fails if
445
# both sequences are empty.
446
if not oldlines and not newlines:
451
if oldlines and (oldlines[-1][-1] != '\n'):
454
if newlines and (newlines[-1][-1] != '\n'):
458
ud = difflib.unified_diff(oldlines, newlines, **kw)
459
sys.stdout.writelines(ud)
461
print "\\ No newline at end of file"
462
sys.stdout.write('\n')
299
464
if file_state in ['.', '?', 'I']:
498
def cmd_deleted(show_ids=False):
499
"""List files deleted in the working tree.
501
TODO: Show files deleted since a previous revision, or between two revisions.
505
new = b.working_tree()
507
## TODO: Much more efficient way to do this: read in new
508
## directories with readdir, rather than stating each one. Same
509
## level of effort but possibly much less IO. (Or possibly not,
510
## if the directories are very large...)
512
for path, ie in old.inventory.iter_entries():
513
if not new.has_id(ie.file_id):
515
print '%-50s %s' % (path, ie.file_id)
521
def cmd_parse_inventory():
524
cElementTree.ElementTree().parse(file('.bzr/inventory'))
528
def cmd_load_inventory():
529
"""Load inventory for timing purposes"""
530
Branch('.').basis_tree().inventory
533
def cmd_dump_inventory():
534
Branch('.').read_working_inventory().write_xml(sys.stdout)
537
def cmd_dump_new_inventory():
538
import bzrlib.newinventory
539
inv = Branch('.').basis_tree().inventory
540
bzrlib.newinventory.write_inventory(inv, sys.stdout)
543
def cmd_load_new_inventory():
544
import bzrlib.newinventory
545
bzrlib.newinventory.read_new_inventory(sys.stdin)
548
def cmd_dump_slacker_inventory():
549
import bzrlib.newinventory
550
inv = Branch('.').basis_tree().inventory
551
bzrlib.newinventory.write_slacker_inventory(inv, sys.stdout)
555
def cmd_dump_text_inventory():
556
import bzrlib.textinv
557
inv = Branch('.').basis_tree().inventory
558
bzrlib.textinv.write_text_inventory(inv, sys.stdout)
561
def cmd_load_text_inventory():
562
import bzrlib.textinv
563
inv = bzrlib.textinv.read_text_inventory(sys.stdin)
564
print 'loaded %d entries' % len(inv)
568
def cmd_root(filename=None):
569
"""Print the branch root."""
570
print bzrlib.branch.find_branch_root(filename)
573
def cmd_log(timezone='original', verbose=False):
334
574
"""Show log of this branch.
336
:todo: Options for utc; to show ids; to limit range; etc.
576
TODO: Options for utc; to show ids; to limit range; etc.
338
Branch('.').write_log()
578
Branch('.').write_log(show_timezone=timezone, verbose=verbose)
341
581
def cmd_ls(revision=None, verbose=False):
342
582
"""List files in a tree.
344
:todo: Take a revision or remote path and list that tree instead.
584
TODO: Take a revision or remote path and list that tree instead.
347
587
if revision == None:
398
655
"""Print a newly-generated UUID."""
403
def cmd_commit(message, verbose=False):
656
print bzrlib.osutils.uuid()
660
def cmd_local_time_offset():
661
print bzrlib.osutils.local_time_offset()
665
def cmd_commit(message=None, verbose=False):
666
"""Commit changes to a new revision.
669
Description of changes in this revision; free form text.
670
It is recommended that the first line be a single-sentence
673
Show status of changed files,
675
TODO: Commit only selected files.
677
TODO: Run hooks on tree to-be-committed, and after commit.
679
TODO: Strict commit that fails if there are unknown or deleted files.
683
bailout("please specify a commit message")
404
684
Branch('.').commit(message, verbose=verbose)
408
"""Check consistency of the branch."""
687
def cmd_check(dir='.'):
688
"""check: Consistency check of branch history.
690
usage: bzr check [-v] [BRANCH]
693
--verbose, -v Show progress of checking.
695
This command checks various invariants about the branch storage to
696
detect data corruption or bzr bugs.
699
bzrlib.check.check(Branch(dir, find_root=False))
412
702
def cmd_is(pred, *rest):
722
TODO: Command to show only the email-address part as parsed out.
428
724
print bzrlib.osutils.username()
431
def cmd_user_email():
432
print bzrlib.osutils.user_email()
435
727
def cmd_gen_revision_id():
437
728
print bzrlib.branch._gen_revision_id(time.time())
441
"""Run internal doctest suite"""
732
"""Run internal test suite"""
442
733
## -v, if present, is seen by doctest; the argument is just here
443
734
## so our parser doesn't complain
445
736
## TODO: --verbose option
738
failures, tests = 0, 0
447
import bzr, doctest, bzrlib.store
740
import doctest, bzrlib.store, bzrlib.tests
448
741
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)
743
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
744
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
745
mf, mt = doctest.testmod(m)
748
print '%-40s %3d tests' % (m.__name__, mt),
750
print '%3d FAILED!' % mf
754
print '%-40s %3d tests' % ('total', tests),
756
print '%3d FAILED!' % failures
763
cmd_doctest = cmd_selftest
461
766
######################################################################
466
# TODO: Specific help for particular commands
770
def cmd_help(topic=None):
773
elif topic == 'commands':
776
# otherwise, maybe the name of a command?
777
topic, cmdfn = get_cmd_handler(topic)
781
bailout("sorry, no detailed help yet for %r" % topic)
787
"""List all commands"""
789
for k in globals().keys():
790
if k.startswith('cmd_'):
791
accu.append(k[4:].replace('_','-'))
793
print "bzr commands: "
796
print "note: some of these commands are internal-use or obsolete"
797
# TODO: Some kind of marker for internal-use commands?
798
# TODO: Show aliases?
470
803
def cmd_version():
471
print "bzr (bazaar-ng) %s" % __version__
804
print "bzr (bazaar-ng) %s" % bzrlib.__version__
805
print bzrlib.__copyright__
473
806
print "http://bazaar-ng.org/"
511
846
# listed take none.
513
848
'add': ['verbose'],
514
850
'commit': ['message', 'verbose'],
851
'deleted': ['show-ids'],
515
852
'diff': ['revision'],
516
853
'inventory': ['revision'],
854
'log': ['timezone', 'verbose'],
517
855
'ls': ['revision', 'verbose'],
856
'remove': ['verbose'],
518
857
'status': ['all'],
520
'remove': ['verbose'],
526
862
'add': ['file+'],
866
'export': ['revno', 'dest'],
529
867
'file-id': ['filename'],
868
'file-id-path': ['filename'],
530
869
'get-file-text': ['text_id'],
531
870
'get-inventory': ['inventory_id'],
532
871
'get-revision': ['revision_id'],
533
872
'get-revision-inventory': ['revision_id'],
535
876
'lookup-revision': ['revno'],
536
'export': ['revno', 'dest'],
877
'move': ['source$', 'dest'],
878
'relpath': ['filename'],
537
879
'remove': ['file+'],
880
'rename': ['from_name', 'to_name'],
882
'root': ['filename?'],
547
892
lookup table, something about the available options, what optargs
548
893
they take, and which commands will accept them.
550
>>> parse_args('bzr --help'.split())
895
>>> parse_args('--help'.split())
551
896
([], {'help': True})
552
>>> parse_args('bzr --version'.split())
897
>>> parse_args('--version'.split())
553
898
([], {'version': True})
554
>>> parse_args('bzr status --all'.split())
899
>>> parse_args('status --all'.split())
555
900
(['status'], {'all': True})
901
>>> parse_args('commit --message=biter'.split())
902
(['commit'], {'message': u'biter'})
560
907
# TODO: Maybe handle '--' to end options?
912
# option names must not be unicode
567
916
mutter(" got option %r" % a)
918
optname, optarg = a[2:].split('=', 1)
569
921
if optname not in OPTIONS:
570
922
bailout('unknown long option %r' % a)
646
1013
"""Execute a command.
648
1015
This is similar to main(), but without all the trappings for
649
logging and error handling.
1016
logging and error handling.
1019
argv = [a.decode(bzrlib.user_encoding) for a in argv]
652
args, opts = parse_args(argv)
1022
args, opts = parse_args(argv[1:])
653
1023
if 'help' in opts:
654
1024
# TODO: pass down other arguments in case they asked for
655
1025
# help on a command name?
658
1031
elif 'version' in opts:
1034
cmd = str(args.pop(0))
662
1035
except IndexError:
663
log_error('usage: bzr COMMAND\n')
664
log_error(' try "bzr help"\n')
1036
log_error('usage: bzr COMMAND')
1037
log_error(' try "bzr help"')
668
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
670
bailout("unknown command " + `cmd`)
672
# TODO: special --profile option to turn on the Python profiler
1040
canonical_cmd, cmd_handler = get_cmd_handler(cmd)
1043
if 'profile' in opts:
674
1049
# check options are reasonable
675
allowed = cmd_options.get(cmd, [])
1050
allowed = cmd_options.get(canonical_cmd, [])
676
1051
for oname in opts:
677
1052
if oname not in allowed:
678
1053
bailout("option %r is not allowed for command %r"
681
cmdargs = _match_args(cmd, args)
684
ret = cmd_handler(**cmdargs) or 0
1056
# TODO: give an error if there are any mandatory options which are
1057
# not specified? Or maybe there shouldn't be any "mandatory
1058
# options" (it is an oxymoron)
1060
# mix arguments and options into one dictionary
1061
cmdargs = _match_args(canonical_cmd, args)
1062
for k, v in opts.items():
1063
cmdargs[k.replace('-', '_')] = v
1067
pffileno, pfname = tempfile.mkstemp()
1069
prof = hotshot.Profile(pfname)
1070
ret = prof.runcall(cmd_handler, **cmdargs) or 0
1073
import hotshot.stats
1074
stats = hotshot.stats.load(pfname)
1076
stats.sort_stats('time')
1077
## XXX: Might like to write to stderr or the trace file instead but
1078
## print_stats seems hardcoded to stdout
1079
stats.print_stats(20)
1087
return cmd_handler(**cmdargs) or 0
1091
def _report_exception(e, summary):
1093
log_error('bzr: ' + summary)
1094
bzrlib.trace.log_exception(e)
1095
tb = sys.exc_info()[2]
1096
exinfo = traceback.extract_tb(tb)
1098
sys.stderr.write(' at %s:%d in %s()\n' % exinfo[-1][:3])
1099
sys.stderr.write(' see ~/.bzr.log for debug information\n')
1102
def cmd_assert_fail():
1103
assert False, "always fails"
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.
1107
bzrlib.trace.create_tracefile(argv)
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?
1114
_report_exception(e, 'error: ' + e.args[0])
1117
# some explanation or hints
1120
except AssertionError, e:
1121
msg = 'assertion failed'
1123
msg += ': ' + str(e)
1124
_report_exception(e, msg)
1125
except Exception, e:
1126
_report_exception(e, 'exception: %s' % str(e).rstrip('\n'))
1129
bzrlib.trace.close_trace()
1131
## TODO: Trap AssertionError
1133
# TODO: Maybe nicer handling of IOError especially for broken pipe.
733
1137
if __name__ == '__main__':
734
1138
sys.exit(main(sys.argv))
736
##profile.run('main(sys.argv)')