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
# Show some information about this branch.
71
__copyright__ = "Copyright 2005 Canonical Development Ltd."
72
__author__ = "Martin Pool <mbp@canonical.com>"
73
__docformat__ = "restructuredtext en"
77
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
78
import traceback, socket, fnmatch, difflib
69
import sys, os, time, types, shutil, tempfile, fnmatch, difflib, os.path
80
70
from sets import Set
81
71
from pprint import pprint
83
73
from glob import glob
74
from inspect import getdoc
86
77
from bzrlib.store import ImmutableStore
87
78
from bzrlib.trace import mutter, note, log_error
88
from bzrlib.errors import bailout, BzrError
79
from bzrlib.errors import bailout, BzrError, BzrCheckError
89
80
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
90
81
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
91
82
from bzrlib.revision import Revision
165
170
print Branch('.').revno()
168
174
def cmd_add(file_list, verbose=False):
169
"""Add specified files.
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?
195
bzrlib.add.smart_add(file_list, verbose)
171
Fails if the files are already added.
173
Branch('.').add(file_list, verbose=verbose)
198
def cmd_relpath(filename):
199
"""Show path of file relative to root"""
200
print Branch(filename).relpath(filename)
176
204
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)
193
print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
195
def plural(n, base='', pl=None):
203
count_version_dirs = 0
205
count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
206
for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
208
count_status[fs] += 1
209
if fs not in ['I', '?'] and st_tup[4] == 'directory':
210
count_version_dirs += 1
213
print 'in the working tree:'
214
for name, fs in (('unchanged', '.'),
215
('modified', 'M'), ('added', 'A'), ('removed', 'D'),
216
('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
218
print ' %5d %s' % (count_status[fs], name)
219
print ' %5d versioned subdirector%s' % (count_version_dirs,
220
plural(count_version_dirs, 'y', 'ies'))
223
print 'branch history:'
224
history = b.revision_history()
226
print ' %5d revision%s' % (revno, plural(revno))
229
committers.add(b.get_revision(rev).committer)
230
print ' %5d committer%s' % (len(committers), plural(len(committers)))
232
firstrev = b.get_revision(history[0])
233
age = int((time.time() - firstrev.timestamp) / 3600 / 24)
234
print ' %5d day%s old' % (age, plural(age))
235
print ' first revision: %s' % format_date(firstrev.timestamp,
238
lastrev = b.get_revision(history[-1])
239
print ' latest revision: %s' % format_date(lastrev.timestamp,
272
"""info: Show statistical information for this branch
276
info.show_info(Branch('.'))
245
280
def cmd_remove(file_list, verbose=False):
246
Branch('.').remove(file_list, verbose=verbose)
281
b = Branch(file_list[0])
282
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
250
286
def cmd_file_id(filename):
251
i = Branch('.').read_working_inventory().path2id(filename)
253
bailout("%s is not a versioned file" % 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)
258
def cmd_find_filename(fileid):
259
n = find_filename(fileid)
261
bailout("%s is not a live file id" % fileid)
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):
266
319
def cmd_revision_history():
282
347
Branch('.', init=True)
285
def cmd_diff(revision=None):
286
"""Show diff from basis to working copy.
288
:todo: Take one or two revision arguments, look up those trees,
291
:todo: Allow diff across branches.
293
:todo: Mangle filenames in diff to be more relevant.
295
:todo: Shouldn't be in the cmd function.
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.
330
419
# FIXME: Something about the diff format makes patch unhappy
331
420
# with newly-added files.
333
def diffit(*a, **kw):
334
sys.stdout.writelines(difflib.unified_diff(*a, **kw))
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')
337
455
if file_state in ['.', '?', 'I']:
371
def cmd_log(timezone='original'):
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):
372
565
"""Show log of this branch.
374
:todo: Options for utc; to show ids; to limit range; etc.
567
TODO: Options for utc; to show ids; to limit range; etc.
376
Branch('.').write_log(show_timezone=timezone)
569
Branch('.').write_log(show_timezone=timezone, verbose=verbose)
379
572
def cmd_ls(revision=None, verbose=False):
380
573
"""List files in a tree.
382
:todo: Take a revision or remote path and list that tree instead.
575
TODO: Take a revision or remote path and list that tree instead.
385
578
if revision == None:
446
def cmd_commit(message, verbose=False):
656
def cmd_commit(message=None, verbose=False):
657
"""Commit changes to a new revision.
660
Description of changes in this revision; free form text.
661
It is recommended that the first line be a single-sentence
664
Show status of changed files,
666
TODO: Commit only selected files.
668
TODO: Run hooks on tree to-be-committed, and after commit.
670
TODO: Strict commit that fails if there are unknown or deleted files.
674
bailout("please specify a commit message")
447
675
Branch('.').commit(message, verbose=verbose)
451
"""Check consistency of the branch."""
678
def cmd_check(dir='.'):
679
"""check: Consistency check of branch history.
681
usage: bzr check [-v] [BRANCH]
684
--verbose, -v Show progress of checking.
686
This command checks various invariants about the branch storage to
687
detect data corruption or bzr bugs.
690
bzrlib.check.check(Branch(dir, find_root=False))
455
693
def cmd_is(pred, *rest):
471
print bzrlib.osutils.username()
474
def cmd_user_email():
475
print bzrlib.osutils.user_email()
708
def cmd_whoami(email=False):
714
--email Show only the email address.
718
print bzrlib.osutils.user_email()
720
print bzrlib.osutils.username()
478
723
def cmd_gen_revision_id():
480
724
print bzrlib.branch._gen_revision_id(time.time())
484
"""Run internal doctest suite"""
728
"""Run internal test suite"""
485
729
## -v, if present, is seen by doctest; the argument is just here
486
730
## so our parser doesn't complain
488
732
## TODO: --verbose option
734
failures, tests = 0, 0
490
import bzr, doctest, bzrlib.store
736
import doctest, bzrlib.store, bzrlib.tests
491
737
bzrlib.trace.verbose = False
493
doctest.testmod(bzrlib.store)
494
doctest.testmod(bzrlib.inventory)
495
doctest.testmod(bzrlib.branch)
496
doctest.testmod(bzrlib.osutils)
497
doctest.testmod(bzrlib.tree)
499
# more strenuous tests;
501
doctest.testmod(bzrlib.tests)
739
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
740
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
741
mf, mt = doctest.testmod(m)
744
print '%-40s %3d tests' % (m.__name__, mt),
746
print '%3d FAILED!' % mf
750
print '%-40s %3d tests' % ('total', tests),
752
print '%3d FAILED!' % failures
759
cmd_doctest = cmd_selftest
504
762
######################################################################
509
# TODO: Specific help for particular commands
766
def cmd_help(topic=None):
769
elif topic == 'commands':
772
# otherwise, maybe the name of a command?
773
topic, cmdfn = get_cmd_handler(topic)
777
bailout("sorry, no detailed help yet for %r" % topic)
783
"""List all commands"""
785
for k in globals().keys():
786
if k.startswith('cmd_'):
787
accu.append(k[4:].replace('_','-'))
789
print "bzr commands: "
792
print "note: some of these commands are internal-use or obsolete"
793
# TODO: Some kind of marker for internal-use commands?
794
# TODO: Show aliases?
513
799
def cmd_version():
514
print "bzr (bazaar-ng) %s" % __version__
800
print "bzr (bazaar-ng) %s" % bzrlib.__version__
801
print bzrlib.__copyright__
516
802
print "http://bazaar-ng.org/"
555
843
# listed take none.
557
845
'add': ['verbose'],
558
847
'commit': ['message', 'verbose'],
848
'deleted': ['show-ids'],
559
849
'diff': ['revision'],
560
850
'inventory': ['revision'],
561
'log': ['show-ids', 'timezone'],
851
'log': ['timezone', 'verbose'],
562
852
'ls': ['revision', 'verbose'],
563
853
'remove': ['verbose'],
564
854
'status': ['all'],
570
860
'add': ['file+'],
864
'export': ['revno', 'dest'],
573
865
'file-id': ['filename'],
866
'file-id-path': ['filename'],
574
867
'get-file-text': ['text_id'],
575
868
'get-inventory': ['inventory_id'],
576
869
'get-revision': ['revision_id'],
577
870
'get-revision-inventory': ['revision_id'],
579
874
'lookup-revision': ['revno'],
580
'export': ['revno', 'dest'],
875
'move': ['source$', 'dest'],
876
'relpath': ['filename'],
581
877
'remove': ['file+'],
878
'rename': ['from_name', 'to_name'],
880
'root': ['filename?'],
699
1011
"""Execute a command.
701
1013
This is similar to main(), but without all the trappings for
702
logging and error handling.
1014
logging and error handling.
1017
argv = [a.decode(bzrlib.user_encoding) for a in argv]
705
1020
args, opts = parse_args(argv[1:])
706
1021
if 'help' in opts:
707
1022
# TODO: pass down other arguments in case they asked for
708
1023
# help on a command name?
711
1029
elif 'version' in opts:
1032
cmd = str(args.pop(0))
715
1033
except IndexError:
716
log_error('usage: bzr COMMAND\n')
717
log_error(' try "bzr help"\n')
1034
log_error('usage: bzr COMMAND')
1035
log_error(' try "bzr help"')
721
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
723
bailout("unknown command " + `cmd`)
725
# TODO: special --profile option to turn on the Python profiler
1038
canonical_cmd, cmd_handler = get_cmd_handler(cmd)
1041
if 'profile' in opts:
727
1047
# check options are reasonable
728
allowed = cmd_options.get(cmd, [])
1048
allowed = cmd_options.get(canonical_cmd, [])
729
1049
for oname in opts:
730
1050
if oname not in allowed:
731
1051
bailout("option %r is not allowed for command %r"
734
cmdargs = _match_args(cmd, args)
737
ret = cmd_handler(**cmdargs) or 0
1054
# TODO: give an error if there are any mandatory options which are
1055
# not specified? Or maybe there shouldn't be any "mandatory
1056
# options" (it is an oxymoron)
1058
# mix arguments and options into one dictionary
1059
cmdargs = _match_args(canonical_cmd, args)
1060
for k, v in opts.items():
1061
cmdargs[k.replace('-', '_')] = v
1065
pffileno, pfname = tempfile.mkstemp()
1067
prof = hotshot.Profile(pfname)
1068
ret = prof.runcall(cmd_handler, **cmdargs) or 0
1071
import hotshot.stats
1072
stats = hotshot.stats.load(pfname)
1074
stats.sort_stats('time')
1075
## XXX: Might like to write to stderr or the trace file instead but
1076
## print_stats seems hardcoded to stdout
1077
stats.print_stats(20)
1085
return cmd_handler(**cmdargs) or 0
1089
def _report_exception(e, summary):
1091
log_error('bzr: ' + summary)
1092
bzrlib.trace.log_exception(e)
1093
tb = sys.exc_info()[2]
1094
exinfo = traceback.extract_tb(tb)
1096
sys.stderr.write(' at %s:%d in %s()\n' % exinfo[-1][:3])
1097
sys.stderr.write(' see ~/.bzr.log for debug information\n')
1100
def cmd_assert_fail():
1101
assert False, "always fails"
742
## TODO: Handle command-line options; probably know what options are valid for
745
## TODO: If the arguments are wrong, give a usage message rather
746
## than just a backtrace.
1105
bzrlib.trace.create_tracefile(argv)
749
# TODO: Lift into separate function in trace.py
750
# TODO: Also show contents of /etc/lsb-release, if it can be parsed.
751
# Perhaps that should eventually go into the platform library?
752
# TODO: If the file doesn't exist, add a note describing it.
753
t = bzrlib.trace._tracefile
754
t.write('-' * 60 + '\n')
755
t.write('bzr invoked at %s\n' % format_date(time.time()))
756
t.write(' by %s on %s\n' % (bzrlib.osutils.username(), socket.getfqdn()))
757
t.write(' arguments: %r\n' % argv)
759
starttime = os.times()[4]
762
t.write(' platform: %s\n' % platform.platform())
763
t.write(' python: %s\n' % platform.python_version())
768
mutter("finished, %.3fu/%.3fs cpu, %.3fu/%.3fs cum"
770
mutter(" %.3f elapsed" % (times[4] - starttime))
774
log_error('bzr: error: ' + e.args[0] + '\n')
777
log_error(' ' + h + '\n')
780
log_error('bzr: exception: %s\n' % e)
781
log_error(' see .bzr.log for details\n')
782
traceback.print_exc(None, bzrlib.trace._tracefile)
783
traceback.print_exc(None, sys.stderr)
786
# TODO: Maybe nicer handling of IOError?
1112
_report_exception(e, 'error: ' + e.args[0])
1115
# some explanation or hints
1118
except AssertionError, e:
1119
msg = 'assertion failed'
1121
msg += ': ' + str(e)
1122
_report_exception(e, msg)
1123
except Exception, e:
1124
_report_exception(e, 'exception: %s' % str(e).rstrip('\n'))
1127
bzrlib.trace.close_trace()
1129
## TODO: Trap AssertionError
1131
# TODO: Maybe nicer handling of IOError especially for broken pipe.
790
1135
if __name__ == '__main__':
791
1136
sys.exit(main(sys.argv))
793
##profile.run('main(sys.argv)')