/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-04-22 01:40:26 UTC
  • Revision ID: mbp@sourcefrog.net-20050422014025-0b925fcb1060c6e2
Prepare for 0.0.4 release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005 by Martin Pool
 
2
# Copyright (C) 2005 by Canonical Ltd
 
3
 
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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
 
18
 
 
19
"""Bazaar-NG -- a free distributed version-control tool
 
20
http://bazaar-ng.org/
 
21
 
 
22
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
 
23
 
 
24
* Metadata format is not stable yet -- you may need to
 
25
  discard history in the future.
 
26
 
 
27
* Many commands unimplemented or partially implemented.
 
28
 
 
29
* Space-inefficient storage.
 
30
 
 
31
* No merge operators yet.
 
32
 
 
33
Interesting commands:
 
34
 
 
35
  bzr help [COMMAND]
 
36
      Show help screen
 
37
  bzr version
 
38
      Show software version/licence/non-warranty.
 
39
  bzr init
 
40
      Start versioning the current directory
 
41
  bzr add FILE...
 
42
      Make files versioned.
 
43
  bzr log
 
44
      Show revision history.
 
45
  bzr diff [FILE...]
 
46
      Show changes from last revision to working copy.
 
47
  bzr commit -m 'MESSAGE'
 
48
      Store current state as new revision.
 
49
  bzr export REVNO DESTINATION
 
50
      Export the branch state at a previous version.
 
51
  bzr status
 
52
      Show summary of pending changes.
 
53
  bzr remove FILE...
 
54
      Make a file not versioned.
 
55
  bzr info
 
56
      Show statistics about this branch.
 
57
  bzr check
 
58
      Verify history is stored safely. 
 
59
  (for more type 'bzr help commands')
 
60
"""
 
61
 
 
62
 
 
63
 
 
64
 
 
65
import sys, os, time, types, shutil, tempfile, fnmatch, difflib, os.path
 
66
from sets import Set
 
67
from pprint import pprint
 
68
from stat import *
 
69
from glob import glob
 
70
from inspect import getdoc
 
71
 
 
72
import bzrlib
 
73
from bzrlib.store import ImmutableStore
 
74
from bzrlib.trace import mutter, note, log_error
 
75
from bzrlib.errors import bailout, BzrError, BzrCheckError
 
76
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
77
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
78
from bzrlib.revision import Revision
 
79
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
80
     format_date
 
81
 
 
82
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
 
83
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
 
84
 
 
85
## standard representation
 
86
NONE_STRING = '(none)'
 
87
EMPTY = 'empty'
 
88
 
 
89
 
 
90
## TODO: Perhaps a different version of inventory commands that
 
91
## returns iterators...
 
92
 
 
93
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
 
94
 
 
95
## TODO: Some kind of locking on branches.  Perhaps there should be a
 
96
## parameter to the branch object saying whether we want a read or
 
97
## write lock; release it from destructor.  Perhaps don't even need a
 
98
## read lock to look at immutable objects?
 
99
 
 
100
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
 
101
## to compare output?
 
102
 
 
103
## TODO: Some kind of global code to generate the right Branch object
 
104
## to work on.  Almost, but not quite all, commands need one, and it
 
105
## can be taken either from their parameters or their working
 
106
## directory.
 
107
 
 
108
## TODO: rename command, needed soon: check destination doesn't exist
 
109
## either in working copy or tree; move working copy; update
 
110
## inventory; write out
 
111
 
 
112
## TODO: move command; check destination is a directory and will not
 
113
## clash; move it.
 
114
 
 
115
## TODO: command to show renames, one per line, as to->from
 
116
 
 
117
 
 
118
 
 
119
cmd_aliases = {
 
120
    '?':         'help',
 
121
    'ci':        'commit',
 
122
    'checkin':   'commit',
 
123
    'di':        'diff',
 
124
    'st':        'status',
 
125
    'stat':      'status',
 
126
    }
 
127
 
 
128
 
 
129
def get_cmd_handler(cmd):
 
130
    cmd = str(cmd)
 
131
    
 
132
    cmd = cmd_aliases.get(cmd, cmd)
 
133
    
 
134
    try:
 
135
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
 
136
    except KeyError:
 
137
        raise BzrError("unknown command %r" % cmd)
 
138
 
 
139
    return cmd, cmd_handler
 
140
 
 
141
 
 
142
 
 
143
def cmd_status(all=False):
 
144
    """Display status summary.
 
145
 
 
146
    For each file there is a single line giving its file state and name.
 
147
    The name is that in the current revision unless it is deleted or
 
148
    missing, in which case the old name is shown.
 
149
    """
 
150
    #import bzrlib.status
 
151
    #bzrlib.status.tree_status(Branch('.'))
 
152
    Branch('.').show_status(show_all=all)
 
153
 
 
154
 
 
155
 
 
156
######################################################################
 
157
# examining history
 
158
def cmd_get_revision(revision_id):
 
159
    Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
160
 
 
161
 
 
162
def cmd_get_file_text(text_id):
 
163
    """Get contents of a file by hash."""
 
164
    sf = Branch('.').text_store[text_id]
 
165
    pumpfile(sf, sys.stdout)
 
166
 
 
167
 
 
168
 
 
169
######################################################################
 
170
# commands
 
171
    
 
172
 
 
173
def cmd_revno():
 
174
    """Show number of revisions on this branch"""
 
175
    print Branch('.').revno()
 
176
    
 
177
 
 
178
    
 
179
def cmd_add(file_list, verbose=False):
 
180
    """Add specified files or directories.
 
181
 
 
182
    In non-recursive mode, all the named items are added, regardless
 
183
    of whether they were previously ignored.  A warning is given if
 
184
    any of the named files are already versioned.
 
185
 
 
186
    In recursive mode (the default), files are treated the same way
 
187
    but the behaviour for directories is different.  Directories that
 
188
    are already versioned do not give a warning.  All directories,
 
189
    whether already versioned or not, are searched for files or
 
190
    subdirectories that are neither versioned or ignored, and these
 
191
    are added.  This search proceeds recursively into versioned
 
192
    directories.
 
193
 
 
194
    Therefore simply saying 'bzr add .' will version all files that
 
195
    are currently unknown.
 
196
 
 
197
    TODO: Perhaps adding a file whose directly is not versioned should
 
198
    recursively add that parent, rather than giving an error?
 
199
    """
 
200
    bzrlib.add.smart_add(file_list, verbose)
 
201
    
 
202
 
 
203
def cmd_relpath(filename):
 
204
    """Show path of file relative to root"""
 
205
    print Branch(filename).relpath(filename)
 
206
 
 
207
 
 
208
 
 
209
def cmd_inventory(revision=None):
 
210
    """Show inventory of the current working copy."""
 
211
    ## TODO: Also optionally show a previous inventory
 
212
    ## TODO: Format options
 
213
    b = Branch('.')
 
214
    if revision == None:
 
215
        inv = b.read_working_inventory()
 
216
    else:
 
217
        inv = b.get_revision_inventory(b.lookup_revision(revision))
 
218
        
 
219
    for path, entry in inv.iter_entries():
 
220
        print '%-50s %s' % (entry.file_id, path)
 
221
 
 
222
 
 
223
 
 
224
# TODO: Maybe a 'mv' command that has the combined move/rename
 
225
# special behaviour of Unix?
 
226
 
 
227
def cmd_move(source_list, dest):
 
228
    b = Branch('.')
 
229
 
 
230
    b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
231
 
 
232
 
 
233
 
 
234
def cmd_rename(from_name, to_name):
 
235
    """Change the name of an entry.
 
236
 
 
237
    usage: bzr rename FROM_NAME TO_NAME
 
238
 
 
239
    examples:
 
240
      bzr rename frob.c frobber.c
 
241
      bzr rename src/frob.c lib/frob.c
 
242
 
 
243
    It is an error if the destination name exists.
 
244
 
 
245
    See also the 'move' command, which moves files into a different
 
246
    directory without changing their name.
 
247
 
 
248
    TODO: Some way to rename multiple files without invoking bzr for each
 
249
    one?"""
 
250
    b = Branch('.')
 
251
    b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
252
    
 
253
 
 
254
 
 
255
 
 
256
def cmd_renames(dir='.'):
 
257
    """Show list of renamed files.
 
258
 
 
259
    usage: bzr renames [BRANCH]
 
260
 
 
261
    TODO: Option to show renames between two historical versions.
 
262
 
 
263
    TODO: Only show renames under dir, rather than in the whole branch.
 
264
    """
 
265
    b = Branch(dir)
 
266
    old_inv = b.basis_tree().inventory
 
267
    new_inv = b.read_working_inventory()
 
268
    
 
269
    renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
270
    renames.sort()
 
271
    for old_name, new_name in renames:
 
272
        print "%s => %s" % (old_name, new_name)        
 
273
 
 
274
 
 
275
 
 
276
def cmd_info():
 
277
    """info: Show statistical information for this branch
 
278
 
 
279
    usage: bzr info"""
 
280
    import info
 
281
    info.show_info(Branch('.'))        
 
282
    
 
283
 
 
284
 
 
285
def cmd_remove(file_list, verbose=False):
 
286
    b = Branch(file_list[0])
 
287
    b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
288
 
 
289
 
 
290
 
 
291
def cmd_file_id(filename):
 
292
    """Print file_id of a particular file or directory.
 
293
 
 
294
    usage: bzr file-id FILE
 
295
 
 
296
    The file_id is assigned when the file is first added and remains the
 
297
    same through all revisions where the file exists, even when it is
 
298
    moved or renamed.
 
299
    """
 
300
    b = Branch(filename)
 
301
    i = b.inventory.path2id(b.relpath(filename))
 
302
    if i == None:
 
303
        bailout("%r is not a versioned file" % filename)
 
304
    else:
 
305
        print i
 
306
 
 
307
 
 
308
def cmd_file_id_path(filename):
 
309
    """Print path of file_ids to a file or directory.
 
310
 
 
311
    usage: bzr file-id-path FILE
 
312
 
 
313
    This prints one line for each directory down to the target,
 
314
    starting at the branch root."""
 
315
    b = Branch(filename)
 
316
    inv = b.inventory
 
317
    fid = inv.path2id(b.relpath(filename))
 
318
    if fid == None:
 
319
        bailout("%r is not a versioned file" % filename)
 
320
    for fip in inv.get_idpath(fid):
 
321
        print fip
 
322
 
 
323
 
 
324
def cmd_revision_history():
 
325
    for patchid in Branch('.').revision_history():
 
326
        print patchid
 
327
 
 
328
 
 
329
def cmd_directories():
 
330
    for name, ie in Branch('.').read_working_inventory().directories():
 
331
        if name == '':
 
332
            print '.'
 
333
        else:
 
334
            print name
 
335
 
 
336
 
 
337
def cmd_missing():
 
338
    for name, ie in Branch('.').working_tree().missing():
 
339
        print name
 
340
 
 
341
 
 
342
def cmd_init():
 
343
    # TODO: Check we're not already in a working directory?  At the
 
344
    # moment you'll get an ugly error.
 
345
    
 
346
    # TODO: What if we're in a subdirectory of a branch?  Would like
 
347
    # to allow that, but then the parent may need to understand that
 
348
    # the children have disappeared, or should they be versioned in
 
349
    # both?
 
350
 
 
351
    # TODO: Take an argument/option for branch name.
 
352
    Branch('.', init=True)
 
353
 
 
354
 
 
355
def cmd_diff(revision=None, file_list=None):
 
356
    """bzr diff: Show differences in working tree.
 
357
    
 
358
    usage: bzr diff [-r REV] [FILE...]
 
359
 
 
360
    --revision REV
 
361
         Show changes since REV, rather than predecessor.
 
362
 
 
363
    If files are listed, only the changes in those files are listed.
 
364
    Otherwise, all changes for the tree are listed.
 
365
 
 
366
    TODO: Given two revision arguments, show the difference between them.
 
367
 
 
368
    TODO: Allow diff across branches.
 
369
 
 
370
    TODO: Option to use external diff command; could be GNU diff, wdiff,
 
371
          or a graphical diff.
 
372
 
 
373
    TODO: Python difflib is not exactly the same as unidiff; should
 
374
          either fix it up or prefer to use an external diff.
 
375
 
 
376
    TODO: If a directory is given, diff everything under that.
 
377
 
 
378
    TODO: Selected-file diff is inefficient and doesn't show you
 
379
          deleted files.
 
380
 
 
381
    TODO: This probably handles non-Unix newlines poorly.
 
382
"""
 
383
 
 
384
    ## TODO: Shouldn't be in the cmd function.
 
385
 
 
386
    b = Branch('.')
 
387
 
 
388
    if revision == None:
 
389
        old_tree = b.basis_tree()
 
390
    else:
 
391
        old_tree = b.revision_tree(b.lookup_revision(revision))
 
392
        
 
393
    new_tree = b.working_tree()
 
394
 
 
395
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
396
    old_label = ''
 
397
    new_label = ''
 
398
 
 
399
    DEVNULL = '/dev/null'
 
400
    # Windows users, don't panic about this filename -- it is a
 
401
    # special signal to GNU patch that the file should be created or
 
402
    # deleted respectively.
 
403
 
 
404
    # TODO: Generation of pseudo-diffs for added/deleted files could
 
405
    # be usefully made into a much faster special case.
 
406
 
 
407
    # TODO: Better to return them in sorted order I think.
 
408
 
 
409
    if file_list:
 
410
        file_list = [b.relpath(f) for f in file_list]
 
411
 
 
412
    # FIXME: If given a file list, compare only those files rather
 
413
    # than comparing everything and then throwing stuff away.
 
414
    
 
415
    for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
 
416
 
 
417
        if file_list and (new_name not in file_list):
 
418
            continue
 
419
        
 
420
        # Don't show this by default; maybe do it if an option is passed
 
421
        # idlabel = '      {%s}' % fid
 
422
        idlabel = ''
 
423
 
 
424
        # FIXME: Something about the diff format makes patch unhappy
 
425
        # with newly-added files.
 
426
 
 
427
        def diffit(oldlines, newlines, **kw):
 
428
            
 
429
            # FIXME: difflib is wrong if there is no trailing newline.
 
430
            # The syntax used by patch seems to be "\ No newline at
 
431
            # end of file" following the last diff line from that
 
432
            # file.  This is not trivial to insert into the
 
433
            # unified_diff output and it might be better to just fix
 
434
            # or replace that function.
 
435
 
 
436
            # In the meantime we at least make sure the patch isn't
 
437
            # mangled.
 
438
            
 
439
 
 
440
            # Special workaround for Python2.3, where difflib fails if
 
441
            # both sequences are empty.
 
442
            if not oldlines and not newlines:
 
443
                return
 
444
 
 
445
            nonl = False
 
446
 
 
447
            if oldlines and (oldlines[-1][-1] != '\n'):
 
448
                oldlines[-1] += '\n'
 
449
                nonl = True
 
450
            if newlines and (newlines[-1][-1] != '\n'):
 
451
                newlines[-1] += '\n'
 
452
                nonl = True
 
453
 
 
454
            ud = difflib.unified_diff(oldlines, newlines, **kw)
 
455
            sys.stdout.writelines(ud)
 
456
            if nonl:
 
457
                print "\\ No newline at end of file"
 
458
            sys.stdout.write('\n')
 
459
        
 
460
        if file_state in ['.', '?', 'I']:
 
461
            continue
 
462
        elif file_state == 'A':
 
463
            print '*** added %s %r' % (kind, new_name)
 
464
            if kind == 'file':
 
465
                diffit([],
 
466
                       new_tree.get_file(fid).readlines(),
 
467
                       fromfile=DEVNULL,
 
468
                       tofile=new_label + new_name + idlabel)
 
469
        elif file_state == 'D':
 
470
            assert isinstance(old_name, types.StringTypes)
 
471
            print '*** deleted %s %r' % (kind, old_name)
 
472
            if kind == 'file':
 
473
                diffit(old_tree.get_file(fid).readlines(), [],
 
474
                       fromfile=old_label + old_name + idlabel,
 
475
                       tofile=DEVNULL)
 
476
        elif file_state in ['M', 'R']:
 
477
            if file_state == 'M':
 
478
                assert kind == 'file'
 
479
                assert old_name == new_name
 
480
                print '*** modified %s %r' % (kind, new_name)
 
481
            elif file_state == 'R':
 
482
                print '*** renamed %s %r => %r' % (kind, old_name, new_name)
 
483
 
 
484
            if kind == 'file':
 
485
                diffit(old_tree.get_file(fid).readlines(),
 
486
                       new_tree.get_file(fid).readlines(),
 
487
                       fromfile=old_label + old_name + idlabel,
 
488
                       tofile=new_label + new_name)
 
489
        else:
 
490
            bailout("can't represent state %s {%s}" % (file_state, fid))
 
491
 
 
492
 
 
493
 
 
494
def cmd_deleted(show_ids=False):
 
495
    """List files deleted in the working tree.
 
496
 
 
497
    TODO: Show files deleted since a previous revision, or between two revisions.
 
498
    """
 
499
    b = Branch('.')
 
500
    old = b.basis_tree()
 
501
    new = b.working_tree()
 
502
 
 
503
    ## TODO: Much more efficient way to do this: read in new
 
504
    ## directories with readdir, rather than stating each one.  Same
 
505
    ## level of effort but possibly much less IO.  (Or possibly not,
 
506
    ## if the directories are very large...)
 
507
 
 
508
    for path, ie in old.inventory.iter_entries():
 
509
        if not new.has_id(ie.file_id):
 
510
            if show_ids:
 
511
                print '%-50s %s' % (path, ie.file_id)
 
512
            else:
 
513
                print path
 
514
 
 
515
 
 
516
 
 
517
def cmd_parse_inventory():
 
518
    import cElementTree
 
519
    
 
520
    cElementTree.ElementTree().parse(file('.bzr/inventory'))
 
521
 
 
522
 
 
523
 
 
524
def cmd_load_inventory():
 
525
    """Load inventory for timing purposes"""
 
526
    Branch('.').basis_tree().inventory
 
527
 
 
528
 
 
529
def cmd_dump_inventory():
 
530
    Branch('.').read_working_inventory().write_xml(sys.stdout)
 
531
 
 
532
 
 
533
def cmd_dump_new_inventory():
 
534
    import bzrlib.newinventory
 
535
    inv = Branch('.').basis_tree().inventory
 
536
    bzrlib.newinventory.write_inventory(inv, sys.stdout)
 
537
 
 
538
 
 
539
def cmd_load_new_inventory():
 
540
    import bzrlib.newinventory
 
541
    bzrlib.newinventory.read_new_inventory(sys.stdin)
 
542
                
 
543
    
 
544
def cmd_dump_slacker_inventory():
 
545
    import bzrlib.newinventory
 
546
    inv = Branch('.').basis_tree().inventory
 
547
    bzrlib.newinventory.write_slacker_inventory(inv, sys.stdout)
 
548
 
 
549
 
 
550
 
 
551
def cmd_dump_text_inventory():
 
552
    import bzrlib.textinv
 
553
    inv = Branch('.').basis_tree().inventory
 
554
    bzrlib.textinv.write_text_inventory(inv, sys.stdout)
 
555
 
 
556
 
 
557
def cmd_load_text_inventory():
 
558
    import bzrlib.textinv
 
559
    inv = bzrlib.textinv.read_text_inventory(sys.stdin)
 
560
    print 'loaded %d entries' % len(inv)
 
561
    
 
562
    
 
563
 
 
564
def cmd_root(filename=None):
 
565
    """Print the branch root."""
 
566
    print bzrlib.branch.find_branch_root(filename)
 
567
    
 
568
 
 
569
def cmd_log(timezone='original', verbose=False):
 
570
    """Show log of this branch.
 
571
 
 
572
    TODO: Options for utc; to show ids; to limit range; etc.
 
573
    """
 
574
    Branch('.').write_log(show_timezone=timezone, verbose=verbose)
 
575
 
 
576
 
 
577
def cmd_ls(revision=None, verbose=False):
 
578
    """List files in a tree.
 
579
 
 
580
    TODO: Take a revision or remote path and list that tree instead.
 
581
    """
 
582
    b = Branch('.')
 
583
    if revision == None:
 
584
        tree = b.working_tree()
 
585
    else:
 
586
        tree = b.revision_tree(b.lookup_revision(revision))
 
587
        
 
588
    for fp, fc, kind, fid in tree.list_files():
 
589
        if verbose:
 
590
            if kind == 'directory':
 
591
                kindch = '/'
 
592
            elif kind == 'file':
 
593
                kindch = ''
 
594
            else:
 
595
                kindch = '???'
 
596
                
 
597
            print '%-8s %s%s' % (fc, fp, kindch)
 
598
        else:
 
599
            print fp
 
600
    
 
601
    
 
602
 
 
603
def cmd_unknowns():
 
604
    """List unknown files"""
 
605
    for f in Branch('.').unknowns():
 
606
        print quotefn(f)
 
607
 
 
608
 
 
609
 
 
610
def cmd_ignored():
 
611
    """List ignored files and the patterns that matched them.
 
612
      """
 
613
    tree = Branch('.').working_tree()
 
614
    for path, file_class, kind, file_id in tree.list_files():
 
615
        if file_class != 'I':
 
616
            continue
 
617
        ## XXX: Slightly inefficient since this was already calculated
 
618
        pat = tree.is_ignored(path)
 
619
        print '%-50s %s' % (path, pat)
 
620
 
 
621
 
 
622
def cmd_lookup_revision(revno):
 
623
    try:
 
624
        revno = int(revno)
 
625
    except ValueError:
 
626
        bailout("usage: lookup-revision REVNO",
 
627
                ["REVNO is a non-negative revision number for this branch"])
 
628
 
 
629
    print Branch('.').lookup_revision(revno) or NONE_STRING
 
630
 
 
631
 
 
632
 
 
633
def cmd_export(revno, dest):
 
634
    """Export past revision to destination directory."""
 
635
    b = Branch('.')
 
636
    rh = b.lookup_revision(int(revno))
 
637
    t = b.revision_tree(rh)
 
638
    t.export(dest)
 
639
 
 
640
def cmd_cat(revision, filename):
 
641
    """Print file to stdout."""
 
642
    b = Branch('.')
 
643
    b.print_file(b.relpath(filename), int(revision))
 
644
 
 
645
 
 
646
######################################################################
 
647
# internal/test commands
 
648
 
 
649
 
 
650
def cmd_uuid():
 
651
    """Print a newly-generated UUID."""
 
652
    print bzrlib.osutils.uuid()
 
653
 
 
654
 
 
655
 
 
656
def cmd_local_time_offset():
 
657
    print bzrlib.osutils.local_time_offset()
 
658
 
 
659
 
 
660
 
 
661
def cmd_commit(message=None, verbose=False):
 
662
    """Commit changes to a new revision.
 
663
 
 
664
    --message MESSAGE
 
665
        Description of changes in this revision; free form text.
 
666
        It is recommended that the first line be a single-sentence
 
667
        summary.
 
668
    --verbose
 
669
        Show status of changed files,
 
670
 
 
671
    TODO: Commit only selected files.
 
672
 
 
673
    TODO: Run hooks on tree to-be-committed, and after commit.
 
674
 
 
675
    TODO: Strict commit that fails if there are unknown or deleted files.
 
676
    """
 
677
 
 
678
    if not message:
 
679
        bailout("please specify a commit message")
 
680
    Branch('.').commit(message, verbose=verbose)
 
681
 
 
682
 
 
683
def cmd_check(dir='.'):
 
684
    """check: Consistency check of branch history.
 
685
 
 
686
    usage: bzr check [-v] [BRANCH]
 
687
 
 
688
    options:
 
689
      --verbose, -v         Show progress of checking.
 
690
 
 
691
    This command checks various invariants about the branch storage to
 
692
    detect data corruption or bzr bugs.
 
693
    """
 
694
    import bzrlib.check
 
695
    bzrlib.check.check(Branch(dir, find_root=False))
 
696
 
 
697
 
 
698
def cmd_is(pred, *rest):
 
699
    """Test whether PREDICATE is true."""
 
700
    try:
 
701
        cmd_handler = globals()['assert_' + pred.replace('-', '_')]
 
702
    except KeyError:
 
703
        bailout("unknown predicate: %s" % quotefn(pred))
 
704
        
 
705
    try:
 
706
        cmd_handler(*rest)
 
707
    except BzrCheckError:
 
708
        # by default we don't print the message so that this can
 
709
        # be used from shell scripts without producing noise
 
710
        sys.exit(1)
 
711
 
 
712
 
 
713
def cmd_whoami():
 
714
    """Show bzr user id.
 
715
 
 
716
    usage: bzr whoami
 
717
 
 
718
    TODO: Command to show only the email-address part as parsed out.
 
719
    """
 
720
    print bzrlib.osutils.username()
 
721
 
 
722
 
 
723
def cmd_gen_revision_id():
 
724
    print bzrlib.branch._gen_revision_id(time.time())
 
725
 
 
726
 
 
727
def cmd_selftest():
 
728
    """Run internal test suite"""
 
729
    ## -v, if present, is seen by doctest; the argument is just here
 
730
    ## so our parser doesn't complain
 
731
 
 
732
    ## TODO: --verbose option
 
733
 
 
734
    failures, tests = 0, 0
 
735
    
 
736
    import doctest, bzrlib.store, bzrlib.tests
 
737
    bzrlib.trace.verbose = False
 
738
 
 
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)
 
742
        failures += mf
 
743
        tests += mt
 
744
        print '%-40s %3d tests' % (m.__name__, mt),
 
745
        if mf:
 
746
            print '%3d FAILED!' % mf
 
747
        else:
 
748
            print
 
749
 
 
750
    print '%-40s %3d tests' % ('total', tests),
 
751
    if failures:
 
752
        print '%3d FAILED!' % failures
 
753
    else:
 
754
        print
 
755
 
 
756
 
 
757
 
 
758
# deprecated
 
759
cmd_doctest = cmd_selftest
 
760
 
 
761
 
 
762
######################################################################
 
763
# help
 
764
 
 
765
 
 
766
def cmd_help(topic=None):
 
767
    if topic == None:
 
768
        print __doc__
 
769
    elif topic == 'commands':
 
770
        help_commands()
 
771
    else:
 
772
        # otherwise, maybe the name of a command?
 
773
        topic, cmdfn = get_cmd_handler(topic)
 
774
 
 
775
        doc = getdoc(cmdfn)
 
776
        if doc == None:
 
777
            bailout("sorry, no detailed help yet for %r" % topic)
 
778
 
 
779
        print doc
 
780
 
 
781
 
 
782
def help_commands():
 
783
    """List all commands"""
 
784
    accu = []
 
785
    for k in globals().keys():
 
786
        if k.startswith('cmd_'):
 
787
            accu.append(k[4:].replace('_','-'))
 
788
    accu.sort()
 
789
    print "bzr commands: "
 
790
    for x in accu:
 
791
        print "   " + x
 
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?
 
795
        
 
796
 
 
797
 
 
798
 
 
799
def cmd_version():
 
800
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
801
    print bzrlib.__copyright__
 
802
    print "http://bazaar-ng.org/"
 
803
    print
 
804
    print \
 
805
"""bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and
 
806
you may use, modify and redistribute it under the terms of the GNU 
 
807
General Public License version 2 or later."""
 
808
 
 
809
 
 
810
def cmd_rocks():
 
811
    """Statement of optimism."""
 
812
    print "it sure does!"
 
813
 
 
814
 
 
815
 
 
816
######################################################################
 
817
# main routine
 
818
 
 
819
 
 
820
# list of all available options; the rhs can be either None for an
 
821
# option that takes no argument, or a constructor function that checks
 
822
# the type.
 
823
OPTIONS = {
 
824
    'all':                    None,
 
825
    'help':                   None,
 
826
    'message':                unicode,
 
827
    'profile':                None,
 
828
    'revision':               int,
 
829
    'show-ids':               None,
 
830
    'timezone':               str,
 
831
    'verbose':                None,
 
832
    'version':                None,
 
833
    }
 
834
 
 
835
SHORT_OPTIONS = {
 
836
    'm':                      'message',
 
837
    'r':                      'revision',
 
838
    'v':                      'verbose',
 
839
}
 
840
 
 
841
# List of options that apply to particular commands; commands not
 
842
# listed take none.
 
843
cmd_options = {
 
844
    'add':                    ['verbose'],
 
845
    'cat':                    ['revision'],
 
846
    'commit':                 ['message', 'verbose'],
 
847
    'deleted':                ['show-ids'],
 
848
    'diff':                   ['revision'],
 
849
    'inventory':              ['revision'],
 
850
    'log':                    ['timezone', 'verbose'],
 
851
    'ls':                     ['revision', 'verbose'],
 
852
    'remove':                 ['verbose'],
 
853
    'status':                 ['all'],
 
854
    }
 
855
 
 
856
 
 
857
cmd_args = {
 
858
    'add':                    ['file+'],
 
859
    'cat':                    ['filename'],
 
860
    'commit':                 [],
 
861
    'diff':                   ['file*'],
 
862
    'export':                 ['revno', 'dest'],
 
863
    'file-id':                ['filename'],
 
864
    'file-id-path':           ['filename'],
 
865
    'get-file-text':          ['text_id'],
 
866
    'get-inventory':          ['inventory_id'],
 
867
    'get-revision':           ['revision_id'],
 
868
    'get-revision-inventory': ['revision_id'],
 
869
    'help':                   ['topic?'],
 
870
    'init':                   [],
 
871
    'log':                    [],
 
872
    'lookup-revision':        ['revno'],
 
873
    'move':                   ['source$', 'dest'],
 
874
    'relpath':                ['filename'],
 
875
    'remove':                 ['file+'],
 
876
    'rename':                 ['from_name', 'to_name'],
 
877
    'renames':                ['dir?'],
 
878
    'root':                   ['filename?'],
 
879
    'status':                 [],
 
880
    }
 
881
 
 
882
 
 
883
def parse_args(argv):
 
884
    """Parse command line.
 
885
    
 
886
    Arguments and options are parsed at this level before being passed
 
887
    down to specific command handlers.  This routine knows, from a
 
888
    lookup table, something about the available options, what optargs
 
889
    they take, and which commands will accept them.
 
890
 
 
891
    >>> parse_args('--help'.split())
 
892
    ([], {'help': True})
 
893
    >>> parse_args('--version'.split())
 
894
    ([], {'version': True})
 
895
    >>> parse_args('status --all'.split())
 
896
    (['status'], {'all': True})
 
897
    >>> parse_args('commit --message=biter'.split())
 
898
    (['commit'], {'message': u'biter'})
 
899
    """
 
900
    args = []
 
901
    opts = {}
 
902
 
 
903
    # TODO: Maybe handle '--' to end options?
 
904
 
 
905
    while argv:
 
906
        a = argv.pop(0)
 
907
        if a[0] == '-':
 
908
            # option names must not be unicode
 
909
            a = str(a)
 
910
            optarg = None
 
911
            if a[1] == '-':
 
912
                mutter("  got option %r" % a)
 
913
                if '=' in a:
 
914
                    optname, optarg = a[2:].split('=', 1)
 
915
                else:
 
916
                    optname = a[2:]
 
917
                if optname not in OPTIONS:
 
918
                    bailout('unknown long option %r' % a)
 
919
            else:
 
920
                shortopt = a[1:]
 
921
                if shortopt not in SHORT_OPTIONS:
 
922
                    bailout('unknown short option %r' % a)
 
923
                optname = SHORT_OPTIONS[shortopt]
 
924
            
 
925
            if optname in opts:
 
926
                # XXX: Do we ever want to support this, e.g. for -r?
 
927
                bailout('repeated option %r' % a)
 
928
                
 
929
            optargfn = OPTIONS[optname]
 
930
            if optargfn:
 
931
                if optarg == None:
 
932
                    if not argv:
 
933
                        bailout('option %r needs an argument' % a)
 
934
                    else:
 
935
                        optarg = argv.pop(0)
 
936
                opts[optname] = optargfn(optarg)
 
937
            else:
 
938
                if optarg != None:
 
939
                    bailout('option %r takes no argument' % optname)
 
940
                opts[optname] = True
 
941
        else:
 
942
            args.append(a)
 
943
 
 
944
    return args, opts
 
945
 
 
946
 
 
947
 
 
948
def _match_args(cmd, args):
 
949
    """Check non-option arguments match required pattern.
 
950
 
 
951
    >>> _match_args('status', ['asdasdsadasd'])
 
952
    Traceback (most recent call last):
 
953
    ...
 
954
    BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
 
955
    >>> _match_args('add', ['asdasdsadasd'])
 
956
    {'file_list': ['asdasdsadasd']}
 
957
    >>> _match_args('add', 'abc def gj'.split())
 
958
    {'file_list': ['abc', 'def', 'gj']}
 
959
    """
 
960
    # match argument pattern
 
961
    argform = cmd_args.get(cmd, [])
 
962
    argdict = {}
 
963
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
 
964
    # all but one.
 
965
 
 
966
    # step through args and argform, allowing appropriate 0-many matches
 
967
    for ap in argform:
 
968
        argname = ap[:-1]
 
969
        if ap[-1] == '?':
 
970
            if args:
 
971
                argdict[argname] = args.pop(0)
 
972
        elif ap[-1] == '*': # all remaining arguments
 
973
            if args:
 
974
                argdict[argname + '_list'] = args[:]
 
975
                args = []
 
976
            else:
 
977
                argdict[argname + '_list'] = None
 
978
        elif ap[-1] == '+':
 
979
            if not args:
 
980
                bailout("command %r needs one or more %s"
 
981
                        % (cmd, argname.upper()))
 
982
            else:
 
983
                argdict[argname + '_list'] = args[:]
 
984
                args = []
 
985
        elif ap[-1] == '$': # all but one
 
986
            if len(args) < 2:
 
987
                bailout("command %r needs one or more %s"
 
988
                        % (cmd, argname.upper()))
 
989
            argdict[argname + '_list'] = args[:-1]
 
990
            args[:-1] = []                
 
991
        else:
 
992
            # just a plain arg
 
993
            argname = ap
 
994
            if not args:
 
995
                bailout("command %r requires argument %s"
 
996
                        % (cmd, argname.upper()))
 
997
            else:
 
998
                argdict[argname] = args.pop(0)
 
999
            
 
1000
    if args:
 
1001
        bailout("extra arguments to command %s: %r"
 
1002
                % (cmd, args))
 
1003
 
 
1004
    return argdict
 
1005
 
 
1006
 
 
1007
 
 
1008
def run_bzr(argv):
 
1009
    """Execute a command.
 
1010
 
 
1011
    This is similar to main(), but without all the trappings for
 
1012
    logging and error handling.  
 
1013
    """
 
1014
 
 
1015
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
1016
    
 
1017
    try:
 
1018
        args, opts = parse_args(argv[1:])
 
1019
        if 'help' in opts:
 
1020
            # TODO: pass down other arguments in case they asked for
 
1021
            # help on a command name?
 
1022
            if args:
 
1023
                cmd_help(args[0])
 
1024
            else:
 
1025
                cmd_help()
 
1026
            return 0
 
1027
        elif 'version' in opts:
 
1028
            cmd_version()
 
1029
            return 0
 
1030
        cmd = str(args.pop(0))
 
1031
    except IndexError:
 
1032
        log_error('usage: bzr COMMAND')
 
1033
        log_error('  try "bzr help"')
 
1034
        return 1
 
1035
 
 
1036
    canonical_cmd, cmd_handler = get_cmd_handler(cmd)
 
1037
 
 
1038
    # global option
 
1039
    if 'profile' in opts:
 
1040
        profile = True
 
1041
        del opts['profile']
 
1042
    else:
 
1043
        profile = False
 
1044
 
 
1045
    # check options are reasonable
 
1046
    allowed = cmd_options.get(canonical_cmd, [])
 
1047
    for oname in opts:
 
1048
        if oname not in allowed:
 
1049
            bailout("option %r is not allowed for command %r"
 
1050
                    % (oname, cmd))
 
1051
 
 
1052
    # TODO: give an error if there are any mandatory options which are
 
1053
    # not specified?  Or maybe there shouldn't be any "mandatory
 
1054
    # options" (it is an oxymoron)
 
1055
 
 
1056
    # mix arguments and options into one dictionary
 
1057
    cmdargs = _match_args(canonical_cmd, args)
 
1058
    for k, v in opts.items():
 
1059
        cmdargs[k.replace('-', '_')] = v
 
1060
 
 
1061
    if profile:
 
1062
        import hotshot
 
1063
        pffileno, pfname = tempfile.mkstemp()
 
1064
        try:
 
1065
            prof = hotshot.Profile(pfname)
 
1066
            ret = prof.runcall(cmd_handler, **cmdargs) or 0
 
1067
            prof.close()
 
1068
 
 
1069
            import hotshot.stats
 
1070
            stats = hotshot.stats.load(pfname)
 
1071
            #stats.strip_dirs()
 
1072
            stats.sort_stats('time')
 
1073
            ## XXX: Might like to write to stderr or the trace file instead but
 
1074
            ## print_stats seems hardcoded to stdout
 
1075
            stats.print_stats(20)
 
1076
            
 
1077
            return ret
 
1078
 
 
1079
        finally:
 
1080
            os.close(pffileno)
 
1081
            os.remove(pfname)
 
1082
    else:
 
1083
        return cmd_handler(**cmdargs) or 0
 
1084
 
 
1085
 
 
1086
 
 
1087
def _report_exception(e, summary):
 
1088
    import traceback
 
1089
    log_error('bzr: ' + summary)
 
1090
    bzrlib.trace.log_exception(e)
 
1091
    tb = sys.exc_info()[2]
 
1092
    exinfo = traceback.extract_tb(tb)
 
1093
    if exinfo:
 
1094
        sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
 
1095
    sys.stderr.write('  see ~/.bzr.log for debug information\n')
 
1096
 
 
1097
 
 
1098
def cmd_assert_fail():
 
1099
    assert False, "always fails"
 
1100
 
 
1101
 
 
1102
def main(argv):
 
1103
    bzrlib.trace.create_tracefile(argv)
 
1104
 
 
1105
    try:
 
1106
        try:
 
1107
            ret = run_bzr(argv)
 
1108
            return ret
 
1109
        except BzrError, e:
 
1110
            _report_exception(e, 'error: ' + e.args[0])
 
1111
            if len(e.args) > 1:
 
1112
                for h in e.args[1]:
 
1113
                    # some explanation or hints
 
1114
                    log_error('  ' + h)
 
1115
            return 1
 
1116
        except AssertionError, e:
 
1117
            msg = 'assertion failed'
 
1118
            if str(e):
 
1119
                msg += ': ' + str(e)
 
1120
            _report_exception(e, msg)
 
1121
        except Exception, e:
 
1122
            _report_exception(e, 'exception: %s' % str(e).rstrip('\n'))
 
1123
            return 1
 
1124
    finally:
 
1125
        bzrlib.trace.close_trace()
 
1126
 
 
1127
    ## TODO: Trap AssertionError
 
1128
 
 
1129
    # TODO: Maybe nicer handling of IOError especially for broken pipe.
 
1130
 
 
1131
 
 
1132
 
 
1133
if __name__ == '__main__':
 
1134
    sys.exit(main(sys.argv))