/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-05-30 02:45:48 UTC
  • Revision ID: mbp@sourcefrog.net-20050530024548-120dad7e43de5fec
- rsync upload should be quieter

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
 
 
19
import sys, os
 
20
 
 
21
import bzrlib
 
22
from bzrlib.trace import mutter, note, log_error
 
23
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
24
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
25
from bzrlib.tree import RevisionTree, EmptyTree, Tree
 
26
from bzrlib.revision import Revision
 
27
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
28
     format_date
 
29
from bzrlib import merge
 
30
 
 
31
 
 
32
def _squish_command_name(cmd):
 
33
    return 'cmd_' + cmd.replace('-', '_')
 
34
 
 
35
 
 
36
def _unsquish_command_name(cmd):
 
37
    assert cmd.startswith("cmd_")
 
38
    return cmd[4:].replace('_','-')
 
39
 
 
40
def _parse_revision_str(revstr):
 
41
    """This handles a revision string -> revno. 
 
42
 
 
43
    There are several possibilities:
 
44
 
 
45
        '234'       -> 234
 
46
        '234:345'   -> [234, 345]
 
47
        ':234'      -> [None, 234]
 
48
        '234:'      -> [234, None]
 
49
 
 
50
    In the future we will also support:
 
51
        'uuid:blah-blah-blah'   -> ?
 
52
        'hash:blahblahblah'     -> ?
 
53
        potentially:
 
54
        'tag:mytag'             -> ?
 
55
    """
 
56
    if revstr.find(':') != -1:
 
57
        revs = revstr.split(':')
 
58
        if len(revs) > 2:
 
59
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
 
60
 
 
61
        if not revs[0]:
 
62
            revs[0] = None
 
63
        else:
 
64
            revs[0] = int(revs[0])
 
65
 
 
66
        if not revs[1]:
 
67
            revs[1] = None
 
68
        else:
 
69
            revs[1] = int(revs[1])
 
70
    else:
 
71
        revs = int(revstr)
 
72
    return revs
 
73
 
 
74
def get_all_cmds():
 
75
    """Return canonical name and class for all registered commands."""
 
76
    for k, v in globals().iteritems():
 
77
        if k.startswith("cmd_"):
 
78
            yield _unsquish_command_name(k), v
 
79
 
 
80
def get_cmd_class(cmd):
 
81
    """Return the canonical name and command class for a command.
 
82
    """
 
83
    cmd = str(cmd)                      # not unicode
 
84
 
 
85
    # first look up this command under the specified name
 
86
    try:
 
87
        return cmd, globals()[_squish_command_name(cmd)]
 
88
    except KeyError:
 
89
        pass
 
90
 
 
91
    # look for any command which claims this as an alias
 
92
    for cmdname, cmdclass in get_all_cmds():
 
93
        if cmd in cmdclass.aliases:
 
94
            return cmdname, cmdclass
 
95
 
 
96
    cmdclass = ExternalCommand.find_command(cmd)
 
97
    if cmdclass:
 
98
        return cmd, cmdclass
 
99
 
 
100
    raise BzrCommandError("unknown command %r" % cmd)
 
101
 
 
102
 
 
103
class Command(object):
 
104
    """Base class for commands.
 
105
 
 
106
    The docstring for an actual command should give a single-line
 
107
    summary, then a complete description of the command.  A grammar
 
108
    description will be inserted.
 
109
 
 
110
    takes_args
 
111
        List of argument forms, marked with whether they are optional,
 
112
        repeated, etc.
 
113
 
 
114
    takes_options
 
115
        List of options that may be given for this command.
 
116
 
 
117
    hidden
 
118
        If true, this command isn't advertised.
 
119
    """
 
120
    aliases = []
 
121
    
 
122
    takes_args = []
 
123
    takes_options = []
 
124
 
 
125
    hidden = False
 
126
    
 
127
    def __init__(self, options, arguments):
 
128
        """Construct and run the command.
 
129
 
 
130
        Sets self.status to the return value of run()."""
 
131
        assert isinstance(options, dict)
 
132
        assert isinstance(arguments, dict)
 
133
        cmdargs = options.copy()
 
134
        cmdargs.update(arguments)
 
135
        assert self.__doc__ != Command.__doc__, \
 
136
               ("No help message set for %r" % self)
 
137
        self.status = self.run(**cmdargs)
 
138
 
 
139
    
 
140
    def run(self):
 
141
        """Override this in sub-classes.
 
142
 
 
143
        This is invoked with the options and arguments bound to
 
144
        keyword parameters.
 
145
 
 
146
        Return 0 or None if the command was successful, or a shell
 
147
        error code if not.
 
148
        """
 
149
        return 0
 
150
 
 
151
 
 
152
class ExternalCommand(Command):
 
153
    """Class to wrap external commands.
 
154
 
 
155
    We cheat a little here, when get_cmd_class() calls us we actually give it back
 
156
    an object we construct that has the appropriate path, help, options etc for the
 
157
    specified command.
 
158
 
 
159
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
 
160
    method, which we override to call the Command.__init__ method. That then calls
 
161
    our run method which is pretty straight forward.
 
162
 
 
163
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
 
164
    back into command line options and arguments for the script.
 
165
    """
 
166
 
 
167
    def find_command(cls, cmd):
 
168
        import os.path
 
169
        bzrpath = os.environ.get('BZRPATH', '')
 
170
 
 
171
        for dir in bzrpath.split(':'):
 
172
            path = os.path.join(dir, cmd)
 
173
            if os.path.isfile(path):
 
174
                return ExternalCommand(path)
 
175
 
 
176
        return None
 
177
 
 
178
    find_command = classmethod(find_command)
 
179
 
 
180
    def __init__(self, path):
 
181
        self.path = path
 
182
 
 
183
        # TODO: If either of these fail, we should detect that and
 
184
        # assume that path is not really a bzr plugin after all.
 
185
 
 
186
        pipe = os.popen('%s --bzr-usage' % path, 'r')
 
187
        self.takes_options = pipe.readline().split()
 
188
        self.takes_args = pipe.readline().split()
 
189
        pipe.close()
 
190
 
 
191
        pipe = os.popen('%s --bzr-help' % path, 'r')
 
192
        self.__doc__ = pipe.read()
 
193
        pipe.close()
 
194
 
 
195
    def __call__(self, options, arguments):
 
196
        Command.__init__(self, options, arguments)
 
197
        return self
 
198
 
 
199
    def run(self, **kargs):
 
200
        opts = []
 
201
        args = []
 
202
 
 
203
        keys = kargs.keys()
 
204
        keys.sort()
 
205
        for name in keys:
 
206
            value = kargs[name]
 
207
            if OPTIONS.has_key(name):
 
208
                # it's an option
 
209
                opts.append('--%s' % name)
 
210
                if value is not None and value is not True:
 
211
                    opts.append(str(value))
 
212
            else:
 
213
                # it's an arg, or arg list
 
214
                if type(value) is not list:
 
215
                    value = [value]
 
216
                for v in value:
 
217
                    if v is not None:
 
218
                        args.append(str(v))
 
219
 
 
220
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
 
221
        return self.status
 
222
 
 
223
 
 
224
class cmd_status(Command):
 
225
    """Display status summary.
 
226
 
 
227
    This reports on versioned and unknown files, reporting them
 
228
    grouped by state.  Possible states are:
 
229
 
 
230
    added
 
231
        Versioned in the working copy but not in the previous revision.
 
232
 
 
233
    removed
 
234
        Versioned in the previous revision but removed or deleted
 
235
        in the working copy.
 
236
 
 
237
    renamed
 
238
        Path of this file changed from the previous revision;
 
239
        the text may also have changed.  This includes files whose
 
240
        parent directory was renamed.
 
241
 
 
242
    modified
 
243
        Text has changed since the previous revision.
 
244
 
 
245
    unchanged
 
246
        Nothing about this file has changed since the previous revision.
 
247
        Only shown with --all.
 
248
 
 
249
    unknown
 
250
        Not versioned and not matching an ignore pattern.
 
251
 
 
252
    To see ignored files use 'bzr ignored'.  For details in the
 
253
    changes to file texts, use 'bzr diff'.
 
254
 
 
255
    If no arguments are specified, the status of the entire working
 
256
    directory is shown.  Otherwise, only the status of the specified
 
257
    files or directories is reported.  If a directory is given, status
 
258
    is reported for everything inside that directory.
 
259
    """
 
260
    takes_args = ['file*']
 
261
    takes_options = ['all', 'show-ids']
 
262
    aliases = ['st', 'stat']
 
263
    
 
264
    def run(self, all=False, show_ids=False, file_list=None):
 
265
        if file_list:
 
266
            b = Branch(file_list[0])
 
267
            file_list = [b.relpath(x) for x in file_list]
 
268
            # special case: only one path was given and it's the root
 
269
            # of the branch
 
270
            if file_list == ['']:
 
271
                file_list = None
 
272
        else:
 
273
            b = Branch('.')
 
274
        import status
 
275
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
 
276
                           specific_files=file_list)
 
277
 
 
278
 
 
279
class cmd_cat_revision(Command):
 
280
    """Write out metadata for a revision."""
 
281
 
 
282
    hidden = True
 
283
    takes_args = ['revision_id']
 
284
    
 
285
    def run(self, revision_id):
 
286
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
287
 
 
288
 
 
289
class cmd_revno(Command):
 
290
    """Show current revision number.
 
291
 
 
292
    This is equal to the number of revisions on this branch."""
 
293
    def run(self):
 
294
        print Branch('.').revno()
 
295
 
 
296
    
 
297
class cmd_add(Command):
 
298
    """Add specified files or directories.
 
299
 
 
300
    In non-recursive mode, all the named items are added, regardless
 
301
    of whether they were previously ignored.  A warning is given if
 
302
    any of the named files are already versioned.
 
303
 
 
304
    In recursive mode (the default), files are treated the same way
 
305
    but the behaviour for directories is different.  Directories that
 
306
    are already versioned do not give a warning.  All directories,
 
307
    whether already versioned or not, are searched for files or
 
308
    subdirectories that are neither versioned or ignored, and these
 
309
    are added.  This search proceeds recursively into versioned
 
310
    directories.
 
311
 
 
312
    Therefore simply saying 'bzr add .' will version all files that
 
313
    are currently unknown.
 
314
 
 
315
    TODO: Perhaps adding a file whose directly is not versioned should
 
316
    recursively add that parent, rather than giving an error?
 
317
    """
 
318
    takes_args = ['file+']
 
319
    takes_options = ['verbose']
 
320
    
 
321
    def run(self, file_list, verbose=False):
 
322
        bzrlib.add.smart_add(file_list, verbose)
 
323
 
 
324
 
 
325
class cmd_relpath(Command):
 
326
    """Show path of a file relative to root"""
 
327
    takes_args = ['filename']
 
328
    hidden = True
 
329
    
 
330
    def run(self, filename):
 
331
        print Branch(filename).relpath(filename)
 
332
 
 
333
 
 
334
 
 
335
class cmd_inventory(Command):
 
336
    """Show inventory of the current working copy or a revision."""
 
337
    takes_options = ['revision', 'show-ids']
 
338
    
 
339
    def run(self, revision=None, show_ids=False):
 
340
        b = Branch('.')
 
341
        if revision == None:
 
342
            inv = b.read_working_inventory()
 
343
        else:
 
344
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
345
 
 
346
        for path, entry in inv.entries():
 
347
            if show_ids:
 
348
                print '%-50s %s' % (path, entry.file_id)
 
349
            else:
 
350
                print path
 
351
 
 
352
 
 
353
class cmd_move(Command):
 
354
    """Move files to a different directory.
 
355
 
 
356
    examples:
 
357
        bzr move *.txt doc
 
358
 
 
359
    The destination must be a versioned directory in the same branch.
 
360
    """
 
361
    takes_args = ['source$', 'dest']
 
362
    def run(self, source_list, dest):
 
363
        b = Branch('.')
 
364
 
 
365
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
366
 
 
367
 
 
368
class cmd_rename(Command):
 
369
    """Change the name of an entry.
 
370
 
 
371
    examples:
 
372
      bzr rename frob.c frobber.c
 
373
      bzr rename src/frob.c lib/frob.c
 
374
 
 
375
    It is an error if the destination name exists.
 
376
 
 
377
    See also the 'move' command, which moves files into a different
 
378
    directory without changing their name.
 
379
 
 
380
    TODO: Some way to rename multiple files without invoking bzr for each
 
381
    one?"""
 
382
    takes_args = ['from_name', 'to_name']
 
383
    
 
384
    def run(self, from_name, to_name):
 
385
        b = Branch('.')
 
386
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
387
 
 
388
 
 
389
 
 
390
class cmd_renames(Command):
 
391
    """Show list of renamed files.
 
392
 
 
393
    TODO: Option to show renames between two historical versions.
 
394
 
 
395
    TODO: Only show renames under dir, rather than in the whole branch.
 
396
    """
 
397
    takes_args = ['dir?']
 
398
 
 
399
    def run(self, dir='.'):
 
400
        b = Branch(dir)
 
401
        old_inv = b.basis_tree().inventory
 
402
        new_inv = b.read_working_inventory()
 
403
 
 
404
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
405
        renames.sort()
 
406
        for old_name, new_name in renames:
 
407
            print "%s => %s" % (old_name, new_name)        
 
408
 
 
409
 
 
410
class cmd_info(Command):
 
411
    """Show statistical information about a branch."""
 
412
    takes_args = ['branch?']
 
413
    
 
414
    def run(self, branch=None):
 
415
        import info
 
416
 
 
417
        from branch import find_branch
 
418
        b = find_branch(branch)
 
419
        info.show_info(b)
 
420
 
 
421
 
 
422
class cmd_remove(Command):
 
423
    """Make a file unversioned.
 
424
 
 
425
    This makes bzr stop tracking changes to a versioned file.  It does
 
426
    not delete the working copy.
 
427
    """
 
428
    takes_args = ['file+']
 
429
    takes_options = ['verbose']
 
430
    
 
431
    def run(self, file_list, verbose=False):
 
432
        b = Branch(file_list[0])
 
433
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
434
 
 
435
 
 
436
class cmd_file_id(Command):
 
437
    """Print file_id of a particular file or directory.
 
438
 
 
439
    The file_id is assigned when the file is first added and remains the
 
440
    same through all revisions where the file exists, even when it is
 
441
    moved or renamed.
 
442
    """
 
443
    hidden = True
 
444
    takes_args = ['filename']
 
445
    def run(self, filename):
 
446
        b = Branch(filename)
 
447
        i = b.inventory.path2id(b.relpath(filename))
 
448
        if i == None:
 
449
            bailout("%r is not a versioned file" % filename)
 
450
        else:
 
451
            print i
 
452
 
 
453
 
 
454
class cmd_file_path(Command):
 
455
    """Print path of file_ids to a file or directory.
 
456
 
 
457
    This prints one line for each directory down to the target,
 
458
    starting at the branch root."""
 
459
    hidden = True
 
460
    takes_args = ['filename']
 
461
    def run(self, filename):
 
462
        b = Branch(filename)
 
463
        inv = b.inventory
 
464
        fid = inv.path2id(b.relpath(filename))
 
465
        if fid == None:
 
466
            bailout("%r is not a versioned file" % filename)
 
467
        for fip in inv.get_idpath(fid):
 
468
            print fip
 
469
 
 
470
 
 
471
class cmd_revision_history(Command):
 
472
    """Display list of revision ids on this branch."""
 
473
    hidden = True
 
474
    def run(self):
 
475
        for patchid in Branch('.').revision_history():
 
476
            print patchid
 
477
 
 
478
 
 
479
class cmd_directories(Command):
 
480
    """Display list of versioned directories in this branch."""
 
481
    def run(self):
 
482
        for name, ie in Branch('.').read_working_inventory().directories():
 
483
            if name == '':
 
484
                print '.'
 
485
            else:
 
486
                print name
 
487
 
 
488
 
 
489
class cmd_init(Command):
 
490
    """Make a directory into a versioned branch.
 
491
 
 
492
    Use this to create an empty branch, or before importing an
 
493
    existing project.
 
494
 
 
495
    Recipe for importing a tree of files:
 
496
        cd ~/project
 
497
        bzr init
 
498
        bzr add -v .
 
499
        bzr status
 
500
        bzr commit -m 'imported project'
 
501
    """
 
502
    def run(self):
 
503
        Branch('.', init=True)
 
504
 
 
505
 
 
506
class cmd_diff(Command):
 
507
    """Show differences in working tree.
 
508
    
 
509
    If files are listed, only the changes in those files are listed.
 
510
    Otherwise, all changes for the tree are listed.
 
511
 
 
512
    TODO: Given two revision arguments, show the difference between them.
 
513
 
 
514
    TODO: Allow diff across branches.
 
515
 
 
516
    TODO: Option to use external diff command; could be GNU diff, wdiff,
 
517
          or a graphical diff.
 
518
 
 
519
    TODO: Python difflib is not exactly the same as unidiff; should
 
520
          either fix it up or prefer to use an external diff.
 
521
 
 
522
    TODO: If a directory is given, diff everything under that.
 
523
 
 
524
    TODO: Selected-file diff is inefficient and doesn't show you
 
525
          deleted files.
 
526
 
 
527
    TODO: This probably handles non-Unix newlines poorly.
 
528
    """
 
529
    
 
530
    takes_args = ['file*']
 
531
    takes_options = ['revision', 'diff-options']
 
532
    aliases = ['di']
 
533
 
 
534
    def run(self, revision=None, file_list=None, diff_options=None):
 
535
        from bzrlib.diff import show_diff
 
536
        from bzrlib import find_branch
 
537
 
 
538
        if file_list:
 
539
            b = find_branch(file_list[0])
 
540
            file_list = [b.relpath(f) for f in file_list]
 
541
            if file_list == ['']:
 
542
                # just pointing to top-of-tree
 
543
                file_list = None
 
544
        else:
 
545
            b = Branch('.')
 
546
    
 
547
        show_diff(b, revision, specific_files=file_list,
 
548
                  external_diff_options=diff_options)
 
549
 
 
550
 
 
551
        
 
552
 
 
553
 
 
554
class cmd_deleted(Command):
 
555
    """List files deleted in the working tree.
 
556
 
 
557
    TODO: Show files deleted since a previous revision, or between two revisions.
 
558
    """
 
559
    def run(self, show_ids=False):
 
560
        b = Branch('.')
 
561
        old = b.basis_tree()
 
562
        new = b.working_tree()
 
563
 
 
564
        ## TODO: Much more efficient way to do this: read in new
 
565
        ## directories with readdir, rather than stating each one.  Same
 
566
        ## level of effort but possibly much less IO.  (Or possibly not,
 
567
        ## if the directories are very large...)
 
568
 
 
569
        for path, ie in old.inventory.iter_entries():
 
570
            if not new.has_id(ie.file_id):
 
571
                if show_ids:
 
572
                    print '%-50s %s' % (path, ie.file_id)
 
573
                else:
 
574
                    print path
 
575
 
 
576
 
 
577
class cmd_modified(Command):
 
578
    """List files modified in working tree."""
 
579
    hidden = True
 
580
    def run(self):
 
581
        import statcache
 
582
        b = Branch('.')
 
583
        inv = b.read_working_inventory()
 
584
        sc = statcache.update_cache(b, inv)
 
585
        basis = b.basis_tree()
 
586
        basis_inv = basis.inventory
 
587
        
 
588
        # We used to do this through iter_entries(), but that's slow
 
589
        # when most of the files are unmodified, as is usually the
 
590
        # case.  So instead we iterate by inventory entry, and only
 
591
        # calculate paths as necessary.
 
592
 
 
593
        for file_id in basis_inv:
 
594
            cacheentry = sc.get(file_id)
 
595
            if not cacheentry:                 # deleted
 
596
                continue
 
597
            ie = basis_inv[file_id]
 
598
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
 
599
                path = inv.id2path(file_id)
 
600
                print path
 
601
 
 
602
 
 
603
 
 
604
class cmd_added(Command):
 
605
    """List files added in working tree."""
 
606
    hidden = True
 
607
    def run(self):
 
608
        b = Branch('.')
 
609
        wt = b.working_tree()
 
610
        basis_inv = b.basis_tree().inventory
 
611
        inv = wt.inventory
 
612
        for file_id in inv:
 
613
            if file_id in basis_inv:
 
614
                continue
 
615
            path = inv.id2path(file_id)
 
616
            if not os.access(b.abspath(path), os.F_OK):
 
617
                continue
 
618
            print path
 
619
                
 
620
        
 
621
 
 
622
class cmd_root(Command):
 
623
    """Show the tree root directory.
 
624
 
 
625
    The root is the nearest enclosing directory with a .bzr control
 
626
    directory."""
 
627
    takes_args = ['filename?']
 
628
    def run(self, filename=None):
 
629
        """Print the branch root."""
 
630
        from branch import find_branch
 
631
        b = find_branch(filename)
 
632
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
 
633
 
 
634
 
 
635
class cmd_log(Command):
 
636
    """Show log of this branch.
 
637
 
 
638
    To request a range of logs, you can use the command -r begin:end
 
639
    -r revision requests a specific revision, -r :end or -r begin: are
 
640
    also valid.
 
641
 
 
642
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
 
643
  
 
644
    """
 
645
 
 
646
    takes_args = ['filename?']
 
647
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
 
648
    
 
649
    def run(self, filename=None, timezone='original',
 
650
            verbose=False,
 
651
            show_ids=False,
 
652
            forward=False,
 
653
            revision=None):
 
654
        from bzrlib import show_log, find_branch
 
655
        import codecs
 
656
 
 
657
        direction = (forward and 'forward') or 'reverse'
 
658
        
 
659
        if filename:
 
660
            b = find_branch(filename)
 
661
            fp = b.relpath(filename)
 
662
            if fp:
 
663
                file_id = b.read_working_inventory().path2id(fp)
 
664
            else:
 
665
                file_id = None  # points to branch root
 
666
        else:
 
667
            b = find_branch('.')
 
668
            file_id = None
 
669
 
 
670
        if revision == None:
 
671
            revision = [None, None]
 
672
        elif isinstance(revision, int):
 
673
            revision = [revision, revision]
 
674
        else:
 
675
            # pair of revisions?
 
676
            pass
 
677
            
 
678
        assert len(revision) == 2
 
679
 
 
680
        mutter('encoding log as %r' % bzrlib.user_encoding)
 
681
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout)
 
682
 
 
683
        show_log(b, file_id,
 
684
                 show_timezone=timezone,
 
685
                 verbose=verbose,
 
686
                 show_ids=show_ids,
 
687
                 to_file=outf,
 
688
                 direction=direction,
 
689
                 start_revision=revision[0],
 
690
                 end_revision=revision[1])
 
691
 
 
692
 
 
693
 
 
694
class cmd_touching_revisions(Command):
 
695
    """Return revision-ids which affected a particular file.
 
696
 
 
697
    A more user-friendly interface is "bzr log FILE"."""
 
698
    hidden = True
 
699
    takes_args = ["filename"]
 
700
    def run(self, filename):
 
701
        b = Branch(filename)
 
702
        inv = b.read_working_inventory()
 
703
        file_id = inv.path2id(b.relpath(filename))
 
704
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
 
705
            print "%6d %s" % (revno, what)
 
706
 
 
707
 
 
708
class cmd_ls(Command):
 
709
    """List files in a tree.
 
710
 
 
711
    TODO: Take a revision or remote path and list that tree instead.
 
712
    """
 
713
    hidden = True
 
714
    def run(self, revision=None, verbose=False):
 
715
        b = Branch('.')
 
716
        if revision == None:
 
717
            tree = b.working_tree()
 
718
        else:
 
719
            tree = b.revision_tree(b.lookup_revision(revision))
 
720
 
 
721
        for fp, fc, kind, fid in tree.list_files():
 
722
            if verbose:
 
723
                if kind == 'directory':
 
724
                    kindch = '/'
 
725
                elif kind == 'file':
 
726
                    kindch = ''
 
727
                else:
 
728
                    kindch = '???'
 
729
 
 
730
                print '%-8s %s%s' % (fc, fp, kindch)
 
731
            else:
 
732
                print fp
 
733
 
 
734
 
 
735
 
 
736
class cmd_unknowns(Command):
 
737
    """List unknown files"""
 
738
    def run(self):
 
739
        for f in Branch('.').unknowns():
 
740
            print quotefn(f)
 
741
 
 
742
 
 
743
 
 
744
class cmd_ignore(Command):
 
745
    """Ignore a command or pattern
 
746
 
 
747
    To remove patterns from the ignore list, edit the .bzrignore file.
 
748
 
 
749
    If the pattern contains a slash, it is compared to the whole path
 
750
    from the branch root.  Otherwise, it is comapred to only the last
 
751
    component of the path.
 
752
 
 
753
    Ignore patterns are case-insensitive on case-insensitive systems.
 
754
 
 
755
    Note: wildcards must be quoted from the shell on Unix.
 
756
 
 
757
    examples:
 
758
        bzr ignore ./Makefile
 
759
        bzr ignore '*.class'
 
760
    """
 
761
    takes_args = ['name_pattern']
 
762
    
 
763
    def run(self, name_pattern):
 
764
        from bzrlib.atomicfile import AtomicFile
 
765
        import os.path
 
766
 
 
767
        b = Branch('.')
 
768
        ifn = b.abspath('.bzrignore')
 
769
 
 
770
        if os.path.exists(ifn):
 
771
            f = open(ifn, 'rt')
 
772
            try:
 
773
                igns = f.read().decode('utf-8')
 
774
            finally:
 
775
                f.close()
 
776
        else:
 
777
            igns = ''
 
778
 
 
779
        # TODO: If the file already uses crlf-style termination, maybe
 
780
        # we should use that for the newly added lines?
 
781
 
 
782
        if igns and igns[-1] != '\n':
 
783
            igns += '\n'
 
784
        igns += name_pattern + '\n'
 
785
 
 
786
        try:
 
787
            f = AtomicFile(ifn, 'wt')
 
788
            f.write(igns.encode('utf-8'))
 
789
            f.commit()
 
790
        finally:
 
791
            f.close()
 
792
 
 
793
        inv = b.working_tree().inventory
 
794
        if inv.path2id('.bzrignore'):
 
795
            mutter('.bzrignore is already versioned')
 
796
        else:
 
797
            mutter('need to make new .bzrignore file versioned')
 
798
            b.add(['.bzrignore'])
 
799
 
 
800
 
 
801
 
 
802
class cmd_ignored(Command):
 
803
    """List ignored files and the patterns that matched them.
 
804
 
 
805
    See also: bzr ignore"""
 
806
    def run(self):
 
807
        tree = Branch('.').working_tree()
 
808
        for path, file_class, kind, file_id in tree.list_files():
 
809
            if file_class != 'I':
 
810
                continue
 
811
            ## XXX: Slightly inefficient since this was already calculated
 
812
            pat = tree.is_ignored(path)
 
813
            print '%-50s %s' % (path, pat)
 
814
 
 
815
 
 
816
class cmd_lookup_revision(Command):
 
817
    """Lookup the revision-id from a revision-number
 
818
 
 
819
    example:
 
820
        bzr lookup-revision 33
 
821
    """
 
822
    hidden = True
 
823
    takes_args = ['revno']
 
824
    
 
825
    def run(self, revno):
 
826
        try:
 
827
            revno = int(revno)
 
828
        except ValueError:
 
829
            raise BzrCommandError("not a valid revision-number: %r" % revno)
 
830
 
 
831
        print Branch('.').lookup_revision(revno)
 
832
 
 
833
 
 
834
class cmd_export(Command):
 
835
    """Export past revision to destination directory.
 
836
 
 
837
    If no revision is specified this exports the last committed revision."""
 
838
    takes_args = ['dest']
 
839
    takes_options = ['revision']
 
840
    def run(self, dest, revision=None):
 
841
        b = Branch('.')
 
842
        if revision == None:
 
843
            rh = b.revision_history()[-1]
 
844
        else:
 
845
            rh = b.lookup_revision(int(revision))
 
846
        t = b.revision_tree(rh)
 
847
        t.export(dest)
 
848
 
 
849
 
 
850
class cmd_cat(Command):
 
851
    """Write a file's text from a previous revision."""
 
852
 
 
853
    takes_options = ['revision']
 
854
    takes_args = ['filename']
 
855
 
 
856
    def run(self, filename, revision=None):
 
857
        if revision == None:
 
858
            raise BzrCommandError("bzr cat requires a revision number")
 
859
        b = Branch('.')
 
860
        b.print_file(b.relpath(filename), int(revision))
 
861
 
 
862
 
 
863
class cmd_local_time_offset(Command):
 
864
    """Show the offset in seconds from GMT to local time."""
 
865
    hidden = True    
 
866
    def run(self):
 
867
        print bzrlib.osutils.local_time_offset()
 
868
 
 
869
 
 
870
 
 
871
class cmd_commit(Command):
 
872
    """Commit changes into a new revision.
 
873
 
 
874
    If selected files are specified, only changes to those files are
 
875
    committed.  If a directory is specified then its contents are also
 
876
    committed.
 
877
 
 
878
    A selected-file commit may fail in some cases where the committed
 
879
    tree would be invalid, such as trying to commit a file in a
 
880
    newly-added directory that is not itself committed.
 
881
 
 
882
    TODO: Run hooks on tree to-be-committed, and after commit.
 
883
 
 
884
    TODO: Strict commit that fails if there are unknown or deleted files.
 
885
    """
 
886
    takes_args = ['selected*']
 
887
    takes_options = ['message', 'file', 'verbose']
 
888
    aliases = ['ci', 'checkin']
 
889
 
 
890
    def run(self, message=None, file=None, verbose=True, selected_list=None):
 
891
        from bzrlib.commit import commit
 
892
 
 
893
        ## Warning: shadows builtin file()
 
894
        if not message and not file:
 
895
            raise BzrCommandError("please specify a commit message",
 
896
                                  ["use either --message or --file"])
 
897
        elif message and file:
 
898
            raise BzrCommandError("please specify either --message or --file")
 
899
        
 
900
        if file:
 
901
            import codecs
 
902
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
 
903
 
 
904
        b = Branch('.')
 
905
        commit(b, message, verbose=verbose, specific_files=selected_list)
 
906
 
 
907
 
 
908
class cmd_check(Command):
 
909
    """Validate consistency of branch history.
 
910
 
 
911
    This command checks various invariants about the branch storage to
 
912
    detect data corruption or bzr bugs.
 
913
    """
 
914
    takes_args = ['dir?']
 
915
    def run(self, dir='.'):
 
916
        import bzrlib.check
 
917
        bzrlib.check.check(Branch(dir))
 
918
 
 
919
 
 
920
 
 
921
class cmd_whoami(Command):
 
922
    """Show bzr user id."""
 
923
    takes_options = ['email']
 
924
    
 
925
    def run(self, email=False):
 
926
        if email:
 
927
            print bzrlib.osutils.user_email()
 
928
        else:
 
929
            print bzrlib.osutils.username()
 
930
 
 
931
 
 
932
class cmd_selftest(Command):
 
933
    """Run internal test suite"""
 
934
    hidden = True
 
935
    def run(self):
 
936
        failures, tests = 0, 0
 
937
 
 
938
        import doctest, bzrlib.store
 
939
        bzrlib.trace.verbose = False
 
940
 
 
941
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
942
            bzrlib.tree, bzrlib.commands, bzrlib.add:
 
943
            mf, mt = doctest.testmod(m)
 
944
            failures += mf
 
945
            tests += mt
 
946
            print '%-40s %3d tests' % (m.__name__, mt),
 
947
            if mf:
 
948
                print '%3d FAILED!' % mf
 
949
            else:
 
950
                print
 
951
 
 
952
        print '%-40s %3d tests' % ('total', tests),
 
953
        if failures:
 
954
            print '%3d FAILED!' % failures
 
955
            return 1
 
956
        else:
 
957
            print
 
958
            return 0
 
959
 
 
960
 
 
961
 
 
962
class cmd_version(Command):
 
963
    """Show version of bzr"""
 
964
    def run(self):
 
965
        show_version()
 
966
 
 
967
def show_version():
 
968
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
969
    print bzrlib.__copyright__
 
970
    print "http://bazaar-ng.org/"
 
971
    print
 
972
    print "bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and"
 
973
    print "you may use, modify and redistribute it under the terms of the GNU"
 
974
    print "General Public License version 2 or later."
 
975
 
 
976
 
 
977
class cmd_rocks(Command):
 
978
    """Statement of optimism."""
 
979
    hidden = True
 
980
    def run(self):
 
981
        print "it sure does!"
 
982
 
 
983
def parse_spec(spec):
 
984
    if '/@' in spec:
 
985
        parsed = spec.split('/@')
 
986
        assert len(parsed) == 2
 
987
        if parsed[1] == "":
 
988
            parsed[1] = -1
 
989
        else:
 
990
            parsed[1] = int(parsed[1])
 
991
            assert parsed[1] >=0
 
992
    else:
 
993
        parsed = [spec, None]
 
994
    return parsed
 
995
 
 
996
class cmd_merge(Command):
 
997
    """Perform a three-way merge of trees."""
 
998
    takes_args = ['other_spec', 'base_spec']
 
999
 
 
1000
    def run(self, other_spec, base_spec):
 
1001
        merge.merge(parse_spec(other_spec), parse_spec(base_spec))
 
1002
 
 
1003
class cmd_assert_fail(Command):
 
1004
    """Test reporting of assertion failures"""
 
1005
    hidden = True
 
1006
    def run(self):
 
1007
        assert False, "always fails"
 
1008
 
 
1009
 
 
1010
class cmd_help(Command):
 
1011
    """Show help on a command or other topic.
 
1012
 
 
1013
    For a list of all available commands, say 'bzr help commands'."""
 
1014
    takes_args = ['topic?']
 
1015
    aliases = ['?']
 
1016
    
 
1017
    def run(self, topic=None):
 
1018
        import help
 
1019
        help.help(topic)
 
1020
 
 
1021
 
 
1022
class cmd_update_stat_cache(Command):
 
1023
    """Update stat-cache mapping inodes to SHA-1 hashes.
 
1024
 
 
1025
    For testing only."""
 
1026
    hidden = True
 
1027
    def run(self):
 
1028
        import statcache
 
1029
        b = Branch('.')
 
1030
        statcache.update_cache(b.base, b.read_working_inventory())
 
1031
 
 
1032
 
 
1033
 
 
1034
# list of all available options; the rhs can be either None for an
 
1035
# option that takes no argument, or a constructor function that checks
 
1036
# the type.
 
1037
OPTIONS = {
 
1038
    'all':                    None,
 
1039
    'diff-options':           str,
 
1040
    'help':                   None,
 
1041
    'file':                   unicode,
 
1042
    'forward':                None,
 
1043
    'message':                unicode,
 
1044
    'profile':                None,
 
1045
    'revision':               _parse_revision_str,
 
1046
    'show-ids':               None,
 
1047
    'timezone':               str,
 
1048
    'verbose':                None,
 
1049
    'version':                None,
 
1050
    'email':                  None,
 
1051
    }
 
1052
 
 
1053
SHORT_OPTIONS = {
 
1054
    'F':                      'file', 
 
1055
    'h':                      'help',
 
1056
    'm':                      'message',
 
1057
    'r':                      'revision',
 
1058
    'v':                      'verbose',
 
1059
}
 
1060
 
 
1061
 
 
1062
def parse_args(argv):
 
1063
    """Parse command line.
 
1064
    
 
1065
    Arguments and options are parsed at this level before being passed
 
1066
    down to specific command handlers.  This routine knows, from a
 
1067
    lookup table, something about the available options, what optargs
 
1068
    they take, and which commands will accept them.
 
1069
 
 
1070
    >>> parse_args('--help'.split())
 
1071
    ([], {'help': True})
 
1072
    >>> parse_args('--version'.split())
 
1073
    ([], {'version': True})
 
1074
    >>> parse_args('status --all'.split())
 
1075
    (['status'], {'all': True})
 
1076
    >>> parse_args('commit --message=biter'.split())
 
1077
    (['commit'], {'message': u'biter'})
 
1078
    """
 
1079
    args = []
 
1080
    opts = {}
 
1081
 
 
1082
    # TODO: Maybe handle '--' to end options?
 
1083
 
 
1084
    while argv:
 
1085
        a = argv.pop(0)
 
1086
        if a[0] == '-':
 
1087
            # option names must not be unicode
 
1088
            a = str(a)
 
1089
            optarg = None
 
1090
            if a[1] == '-':
 
1091
                mutter("  got option %r" % a)
 
1092
                if '=' in a:
 
1093
                    optname, optarg = a[2:].split('=', 1)
 
1094
                else:
 
1095
                    optname = a[2:]
 
1096
                if optname not in OPTIONS:
 
1097
                    bailout('unknown long option %r' % a)
 
1098
            else:
 
1099
                shortopt = a[1:]
 
1100
                if shortopt not in SHORT_OPTIONS:
 
1101
                    bailout('unknown short option %r' % a)
 
1102
                optname = SHORT_OPTIONS[shortopt]
 
1103
            
 
1104
            if optname in opts:
 
1105
                # XXX: Do we ever want to support this, e.g. for -r?
 
1106
                bailout('repeated option %r' % a)
 
1107
                
 
1108
            optargfn = OPTIONS[optname]
 
1109
            if optargfn:
 
1110
                if optarg == None:
 
1111
                    if not argv:
 
1112
                        bailout('option %r needs an argument' % a)
 
1113
                    else:
 
1114
                        optarg = argv.pop(0)
 
1115
                opts[optname] = optargfn(optarg)
 
1116
            else:
 
1117
                if optarg != None:
 
1118
                    bailout('option %r takes no argument' % optname)
 
1119
                opts[optname] = True
 
1120
        else:
 
1121
            args.append(a)
 
1122
 
 
1123
    return args, opts
 
1124
 
 
1125
 
 
1126
 
 
1127
 
 
1128
def _match_argform(cmd, takes_args, args):
 
1129
    argdict = {}
 
1130
 
 
1131
    # step through args and takes_args, allowing appropriate 0-many matches
 
1132
    for ap in takes_args:
 
1133
        argname = ap[:-1]
 
1134
        if ap[-1] == '?':
 
1135
            if args:
 
1136
                argdict[argname] = args.pop(0)
 
1137
        elif ap[-1] == '*': # all remaining arguments
 
1138
            if args:
 
1139
                argdict[argname + '_list'] = args[:]
 
1140
                args = []
 
1141
            else:
 
1142
                argdict[argname + '_list'] = None
 
1143
        elif ap[-1] == '+':
 
1144
            if not args:
 
1145
                raise BzrCommandError("command %r needs one or more %s"
 
1146
                        % (cmd, argname.upper()))
 
1147
            else:
 
1148
                argdict[argname + '_list'] = args[:]
 
1149
                args = []
 
1150
        elif ap[-1] == '$': # all but one
 
1151
            if len(args) < 2:
 
1152
                raise BzrCommandError("command %r needs one or more %s"
 
1153
                        % (cmd, argname.upper()))
 
1154
            argdict[argname + '_list'] = args[:-1]
 
1155
            args[:-1] = []                
 
1156
        else:
 
1157
            # just a plain arg
 
1158
            argname = ap
 
1159
            if not args:
 
1160
                raise BzrCommandError("command %r requires argument %s"
 
1161
                        % (cmd, argname.upper()))
 
1162
            else:
 
1163
                argdict[argname] = args.pop(0)
 
1164
            
 
1165
    if args:
 
1166
        raise BzrCommandError("extra argument to command %s: %s"
 
1167
                              % (cmd, args[0]))
 
1168
 
 
1169
    return argdict
 
1170
 
 
1171
 
 
1172
 
 
1173
def run_bzr(argv):
 
1174
    """Execute a command.
 
1175
 
 
1176
    This is similar to main(), but without all the trappings for
 
1177
    logging and error handling.  
 
1178
    """
 
1179
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
1180
    
 
1181
    try:
 
1182
        args, opts = parse_args(argv[1:])
 
1183
        if 'help' in opts:
 
1184
            import help
 
1185
            if args:
 
1186
                help.help(args[0])
 
1187
            else:
 
1188
                help.help()
 
1189
            return 0
 
1190
        elif 'version' in opts:
 
1191
            show_version()
 
1192
            return 0
 
1193
        cmd = str(args.pop(0))
 
1194
    except IndexError:
 
1195
        import help
 
1196
        help.help()
 
1197
        return 1
 
1198
          
 
1199
 
 
1200
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
1201
 
 
1202
    # global option
 
1203
    if 'profile' in opts:
 
1204
        profile = True
 
1205
        del opts['profile']
 
1206
    else:
 
1207
        profile = False
 
1208
 
 
1209
    # check options are reasonable
 
1210
    allowed = cmd_class.takes_options
 
1211
    for oname in opts:
 
1212
        if oname not in allowed:
 
1213
            raise BzrCommandError("option '--%s' is not allowed for command %r"
 
1214
                                  % (oname, cmd))
 
1215
 
 
1216
    # mix arguments and options into one dictionary
 
1217
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
1218
    cmdopts = {}
 
1219
    for k, v in opts.items():
 
1220
        cmdopts[k.replace('-', '_')] = v
 
1221
 
 
1222
    if profile:
 
1223
        import hotshot, tempfile
 
1224
        pffileno, pfname = tempfile.mkstemp()
 
1225
        try:
 
1226
            prof = hotshot.Profile(pfname)
 
1227
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
1228
            prof.close()
 
1229
 
 
1230
            import hotshot.stats
 
1231
            stats = hotshot.stats.load(pfname)
 
1232
            #stats.strip_dirs()
 
1233
            stats.sort_stats('time')
 
1234
            ## XXX: Might like to write to stderr or the trace file instead but
 
1235
            ## print_stats seems hardcoded to stdout
 
1236
            stats.print_stats(20)
 
1237
            
 
1238
            return ret.status
 
1239
 
 
1240
        finally:
 
1241
            os.close(pffileno)
 
1242
            os.remove(pfname)
 
1243
    else:
 
1244
        return cmd_class(cmdopts, cmdargs).status 
 
1245
 
 
1246
 
 
1247
def _report_exception(summary, quiet=False):
 
1248
    import traceback
 
1249
    log_error('bzr: ' + summary)
 
1250
    bzrlib.trace.log_exception()
 
1251
 
 
1252
    if not quiet:
 
1253
        tb = sys.exc_info()[2]
 
1254
        exinfo = traceback.extract_tb(tb)
 
1255
        if exinfo:
 
1256
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
 
1257
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
 
1258
 
 
1259
 
 
1260
 
 
1261
def main(argv):
 
1262
    import errno
 
1263
    
 
1264
    bzrlib.open_tracefile(argv)
 
1265
 
 
1266
    try:
 
1267
        try:
 
1268
            try:
 
1269
                return run_bzr(argv)
 
1270
            finally:
 
1271
                # do this here inside the exception wrappers to catch EPIPE
 
1272
                sys.stdout.flush()
 
1273
        except BzrError, e:
 
1274
            quiet = isinstance(e, (BzrCommandError))
 
1275
            _report_exception('error: ' + e.args[0], quiet=quiet)
 
1276
            if len(e.args) > 1:
 
1277
                for h in e.args[1]:
 
1278
                    # some explanation or hints
 
1279
                    log_error('  ' + h)
 
1280
            return 1
 
1281
        except AssertionError, e:
 
1282
            msg = 'assertion failed'
 
1283
            if str(e):
 
1284
                msg += ': ' + str(e)
 
1285
            _report_exception(msg)
 
1286
            return 2
 
1287
        except KeyboardInterrupt, e:
 
1288
            _report_exception('interrupted', quiet=True)
 
1289
            return 2
 
1290
        except Exception, e:
 
1291
            quiet = False
 
1292
            if (isinstance(e, IOError) 
 
1293
                and hasattr(e, 'errno')
 
1294
                and e.errno == errno.EPIPE):
 
1295
                quiet = True
 
1296
                msg = 'broken pipe'
 
1297
            else:
 
1298
                msg = str(e).rstrip('\n')
 
1299
            _report_exception(msg, quiet)
 
1300
            return 2
 
1301
    finally:
 
1302
        bzrlib.trace.close_trace()
 
1303
 
 
1304
 
 
1305
if __name__ == '__main__':
 
1306
    sys.exit(main(sys.argv))