1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
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, \
 
 
29
from bzrlib import merge
 
 
32
def _squish_command_name(cmd):
 
 
33
    return 'cmd_' + cmd.replace('-', '_')
 
 
36
def _unsquish_command_name(cmd):
 
 
37
    assert cmd.startswith("cmd_")
 
 
38
    return cmd[4:].replace('_','-')
 
 
40
def _parse_revision_str(revstr):
 
 
41
    """This handles a revision string -> revno. 
 
 
43
    There are several possibilities:
 
 
46
        '234:345'   -> [234, 345]
 
 
50
    In the future we will also support:
 
 
51
        'uuid:blah-blah-blah'   -> ?
 
 
52
        'hash:blahblahblah'     -> ?
 
 
56
    if revstr.find(':') != -1:
 
 
57
        revs = revstr.split(':')
 
 
59
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
 
 
64
            revs[0] = int(revs[0])
 
 
69
            revs[1] = int(revs[1])
 
 
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
 
 
80
def get_cmd_class(cmd):
 
 
81
    """Return the canonical name and command class for a command.
 
 
83
    cmd = str(cmd)                      # not unicode
 
 
85
    # first look up this command under the specified name
 
 
87
        return cmd, globals()[_squish_command_name(cmd)]
 
 
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
 
 
96
    cmdclass = ExternalCommand.find_command(cmd)
 
 
100
    raise BzrCommandError("unknown command %r" % cmd)
 
 
103
class Command(object):
 
 
104
    """Base class for commands.
 
 
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.
 
 
111
        List of argument forms, marked with whether they are optional,
 
 
115
        List of options that may be given for this command.
 
 
118
        If true, this command isn't advertised.
 
 
127
    def __init__(self, options, arguments):
 
 
128
        """Construct and run the command.
 
 
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)
 
 
141
        """Override this in sub-classes.
 
 
143
        This is invoked with the options and arguments bound to
 
 
146
        Return 0 or None if the command was successful, or a shell
 
 
152
class ExternalCommand(Command):
 
 
153
    """Class to wrap external commands.
 
 
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
 
 
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.
 
 
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.
 
 
167
    def find_command(cls, cmd):
 
 
169
        bzrpath = os.environ.get('BZRPATH', '')
 
 
171
        for dir in bzrpath.split(':'):
 
 
172
            path = os.path.join(dir, cmd)
 
 
173
            if os.path.isfile(path):
 
 
174
                return ExternalCommand(path)
 
 
178
    find_command = classmethod(find_command)
 
 
180
    def __init__(self, path):
 
 
183
        # TODO: If either of these fail, we should detect that and
 
 
184
        # assume that path is not really a bzr plugin after all.
 
 
186
        pipe = os.popen('%s --bzr-usage' % path, 'r')
 
 
187
        self.takes_options = pipe.readline().split()
 
 
188
        self.takes_args = pipe.readline().split()
 
 
191
        pipe = os.popen('%s --bzr-help' % path, 'r')
 
 
192
        self.__doc__ = pipe.read()
 
 
195
    def __call__(self, options, arguments):
 
 
196
        Command.__init__(self, options, arguments)
 
 
199
    def run(self, **kargs):
 
 
207
            if OPTIONS.has_key(name):
 
 
209
                opts.append('--%s' % name)
 
 
210
                if value is not None and value is not True:
 
 
211
                    opts.append(str(value))
 
 
213
                # it's an arg, or arg list
 
 
214
                if type(value) is not list:
 
 
220
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
 
 
224
class cmd_status(Command):
 
 
225
    """Display status summary.
 
 
227
    This reports on versioned and unknown files, reporting them
 
 
228
    grouped by state.  Possible states are:
 
 
231
        Versioned in the working copy but not in the previous revision.
 
 
234
        Versioned in the previous revision but removed or deleted
 
 
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.
 
 
243
        Text has changed since the previous revision.
 
 
246
        Nothing about this file has changed since the previous revision.
 
 
247
        Only shown with --all.
 
 
250
        Not versioned and not matching an ignore pattern.
 
 
252
    To see ignored files use 'bzr ignored'.  For details in the
 
 
253
    changes to file texts, use 'bzr diff'.
 
 
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.
 
 
260
    takes_args = ['file*']
 
 
261
    takes_options = ['all', 'show-ids']
 
 
262
    aliases = ['st', 'stat']
 
 
264
    def run(self, all=False, show_ids=False, file_list=None):
 
 
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
 
 
270
            if file_list == ['']:
 
 
275
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
 
 
276
                           specific_files=file_list)
 
 
279
class cmd_cat_revision(Command):
 
 
280
    """Write out metadata for a revision."""
 
 
283
    takes_args = ['revision_id']
 
 
285
    def run(self, revision_id):
 
 
286
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
 
289
class cmd_revno(Command):
 
 
290
    """Show current revision number.
 
 
292
    This is equal to the number of revisions on this branch."""
 
 
294
        print Branch('.').revno()
 
 
297
class cmd_add(Command):
 
 
298
    """Add specified files or directories.
 
 
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.
 
 
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
 
 
312
    Therefore simply saying 'bzr add .' will version all files that
 
 
313
    are currently unknown.
 
 
315
    TODO: Perhaps adding a file whose directly is not versioned should
 
 
316
    recursively add that parent, rather than giving an error?
 
 
318
    takes_args = ['file+']
 
 
319
    takes_options = ['verbose']
 
 
321
    def run(self, file_list, verbose=False):
 
 
322
        bzrlib.add.smart_add(file_list, verbose)
 
 
325
class cmd_relpath(Command):
 
 
326
    """Show path of a file relative to root"""
 
 
327
    takes_args = ['filename']
 
 
330
    def run(self, filename):
 
 
331
        print Branch(filename).relpath(filename)
 
 
335
class cmd_inventory(Command):
 
 
336
    """Show inventory of the current working copy or a revision."""
 
 
337
    takes_options = ['revision', 'show-ids']
 
 
339
    def run(self, revision=None, show_ids=False):
 
 
342
            inv = b.read_working_inventory()
 
 
344
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
 
346
        for path, entry in inv.entries():
 
 
348
                print '%-50s %s' % (path, entry.file_id)
 
 
353
class cmd_move(Command):
 
 
354
    """Move files to a different directory.
 
 
359
    The destination must be a versioned directory in the same branch.
 
 
361
    takes_args = ['source$', 'dest']
 
 
362
    def run(self, source_list, dest):
 
 
365
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
 
368
class cmd_rename(Command):
 
 
369
    """Change the name of an entry.
 
 
372
      bzr rename frob.c frobber.c
 
 
373
      bzr rename src/frob.c lib/frob.c
 
 
375
    It is an error if the destination name exists.
 
 
377
    See also the 'move' command, which moves files into a different
 
 
378
    directory without changing their name.
 
 
380
    TODO: Some way to rename multiple files without invoking bzr for each
 
 
382
    takes_args = ['from_name', 'to_name']
 
 
384
    def run(self, from_name, to_name):
 
 
386
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
 
390
class cmd_renames(Command):
 
 
391
    """Show list of renamed files.
 
 
393
    TODO: Option to show renames between two historical versions.
 
 
395
    TODO: Only show renames under dir, rather than in the whole branch.
 
 
397
    takes_args = ['dir?']
 
 
399
    def run(self, dir='.'):
 
 
401
        old_inv = b.basis_tree().inventory
 
 
402
        new_inv = b.read_working_inventory()
 
 
404
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
 
406
        for old_name, new_name in renames:
 
 
407
            print "%s => %s" % (old_name, new_name)        
 
 
410
class cmd_info(Command):
 
 
411
    """Show statistical information about a branch."""
 
 
412
    takes_args = ['branch?']
 
 
414
    def run(self, branch=None):
 
 
417
        from branch import find_branch
 
 
418
        b = find_branch(branch)
 
 
422
class cmd_remove(Command):
 
 
423
    """Make a file unversioned.
 
 
425
    This makes bzr stop tracking changes to a versioned file.  It does
 
 
426
    not delete the working copy.
 
 
428
    takes_args = ['file+']
 
 
429
    takes_options = ['verbose']
 
 
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)
 
 
436
class cmd_file_id(Command):
 
 
437
    """Print file_id of a particular file or directory.
 
 
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
 
 
444
    takes_args = ['filename']
 
 
445
    def run(self, filename):
 
 
447
        i = b.inventory.path2id(b.relpath(filename))
 
 
449
            bailout("%r is not a versioned file" % filename)
 
 
454
class cmd_file_path(Command):
 
 
455
    """Print path of file_ids to a file or directory.
 
 
457
    This prints one line for each directory down to the target,
 
 
458
    starting at the branch root."""
 
 
460
    takes_args = ['filename']
 
 
461
    def run(self, filename):
 
 
464
        fid = inv.path2id(b.relpath(filename))
 
 
466
            bailout("%r is not a versioned file" % filename)
 
 
467
        for fip in inv.get_idpath(fid):
 
 
471
class cmd_revision_history(Command):
 
 
472
    """Display list of revision ids on this branch."""
 
 
475
        for patchid in Branch('.').revision_history():
 
 
479
class cmd_directories(Command):
 
 
480
    """Display list of versioned directories in this branch."""
 
 
482
        for name, ie in Branch('.').read_working_inventory().directories():
 
 
489
class cmd_init(Command):
 
 
490
    """Make a directory into a versioned branch.
 
 
492
    Use this to create an empty branch, or before importing an
 
 
495
    Recipe for importing a tree of files:
 
 
500
        bzr commit -m 'imported project'
 
 
503
        Branch('.', init=True)
 
 
506
class cmd_diff(Command):
 
 
507
    """Show differences in working tree.
 
 
509
    If files are listed, only the changes in those files are listed.
 
 
510
    Otherwise, all changes for the tree are listed.
 
 
512
    TODO: Given two revision arguments, show the difference between them.
 
 
514
    TODO: Allow diff across branches.
 
 
516
    TODO: Option to use external diff command; could be GNU diff, wdiff,
 
 
519
    TODO: Python difflib is not exactly the same as unidiff; should
 
 
520
          either fix it up or prefer to use an external diff.
 
 
522
    TODO: If a directory is given, diff everything under that.
 
 
524
    TODO: Selected-file diff is inefficient and doesn't show you
 
 
527
    TODO: This probably handles non-Unix newlines poorly.
 
 
530
    takes_args = ['file*']
 
 
531
    takes_options = ['revision', 'diff-options']
 
 
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
 
 
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
 
 
547
        show_diff(b, revision, specific_files=file_list,
 
 
548
                  external_diff_options=diff_options)
 
 
554
class cmd_deleted(Command):
 
 
555
    """List files deleted in the working tree.
 
 
557
    TODO: Show files deleted since a previous revision, or between two revisions.
 
 
559
    def run(self, show_ids=False):
 
 
562
        new = b.working_tree()
 
 
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...)
 
 
569
        for path, ie in old.inventory.iter_entries():
 
 
570
            if not new.has_id(ie.file_id):
 
 
572
                    print '%-50s %s' % (path, ie.file_id)
 
 
577
class cmd_modified(Command):
 
 
578
    """List files modified in working tree."""
 
 
583
        inv = b.read_working_inventory()
 
 
584
        sc = statcache.update_cache(b, inv)
 
 
585
        basis = b.basis_tree()
 
 
586
        basis_inv = basis.inventory
 
 
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.
 
 
593
        for file_id in basis_inv:
 
 
594
            cacheentry = sc.get(file_id)
 
 
595
            if not cacheentry:                 # deleted
 
 
597
            ie = basis_inv[file_id]
 
 
598
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
 
 
599
                path = inv.id2path(file_id)
 
 
604
class cmd_added(Command):
 
 
605
    """List files added in working tree."""
 
 
609
        wt = b.working_tree()
 
 
610
        basis_inv = b.basis_tree().inventory
 
 
613
            if file_id in basis_inv:
 
 
615
            path = inv.id2path(file_id)
 
 
616
            if not os.access(b.abspath(path), os.F_OK):
 
 
622
class cmd_root(Command):
 
 
623
    """Show the tree root directory.
 
 
625
    The root is the nearest enclosing directory with a .bzr control
 
 
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')
 
 
635
class cmd_log(Command):
 
 
636
    """Show log of this branch.
 
 
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
 
 
642
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
 
 
646
    takes_args = ['filename?']
 
 
647
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
 
 
649
    def run(self, filename=None, timezone='original',
 
 
654
        from bzrlib import show_log, find_branch
 
 
657
        direction = (forward and 'forward') or 'reverse'
 
 
660
            b = find_branch(filename)
 
 
661
            fp = b.relpath(filename)
 
 
663
                file_id = b.read_working_inventory().path2id(fp)
 
 
665
                file_id = None  # points to branch root
 
 
671
            revision = [None, None]
 
 
672
        elif isinstance(revision, int):
 
 
673
            revision = [revision, revision]
 
 
678
        assert len(revision) == 2
 
 
680
        mutter('encoding log as %r' % bzrlib.user_encoding)
 
 
681
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout)
 
 
684
                 show_timezone=timezone,
 
 
689
                 start_revision=revision[0],
 
 
690
                 end_revision=revision[1])
 
 
694
class cmd_touching_revisions(Command):
 
 
695
    """Return revision-ids which affected a particular file.
 
 
697
    A more user-friendly interface is "bzr log FILE"."""
 
 
699
    takes_args = ["filename"]
 
 
700
    def run(self, 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)
 
 
708
class cmd_ls(Command):
 
 
709
    """List files in a tree.
 
 
711
    TODO: Take a revision or remote path and list that tree instead.
 
 
714
    def run(self, revision=None, verbose=False):
 
 
717
            tree = b.working_tree()
 
 
719
            tree = b.revision_tree(b.lookup_revision(revision))
 
 
721
        for fp, fc, kind, fid in tree.list_files():
 
 
723
                if kind == 'directory':
 
 
730
                print '%-8s %s%s' % (fc, fp, kindch)
 
 
736
class cmd_unknowns(Command):
 
 
737
    """List unknown files"""
 
 
739
        for f in Branch('.').unknowns():
 
 
744
class cmd_ignore(Command):
 
 
745
    """Ignore a command or pattern
 
 
747
    To remove patterns from the ignore list, edit the .bzrignore file.
 
 
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.
 
 
753
    Ignore patterns are case-insensitive on case-insensitive systems.
 
 
755
    Note: wildcards must be quoted from the shell on Unix.
 
 
758
        bzr ignore ./Makefile
 
 
761
    takes_args = ['name_pattern']
 
 
763
    def run(self, name_pattern):
 
 
764
        from bzrlib.atomicfile import AtomicFile
 
 
768
        ifn = b.abspath('.bzrignore')
 
 
770
        if os.path.exists(ifn):
 
 
773
                igns = f.read().decode('utf-8')
 
 
779
        # TODO: If the file already uses crlf-style termination, maybe
 
 
780
        # we should use that for the newly added lines?
 
 
782
        if igns and igns[-1] != '\n':
 
 
784
        igns += name_pattern + '\n'
 
 
787
            f = AtomicFile(ifn, 'wt')
 
 
788
            f.write(igns.encode('utf-8'))
 
 
793
        inv = b.working_tree().inventory
 
 
794
        if inv.path2id('.bzrignore'):
 
 
795
            mutter('.bzrignore is already versioned')
 
 
797
            mutter('need to make new .bzrignore file versioned')
 
 
798
            b.add(['.bzrignore'])
 
 
802
class cmd_ignored(Command):
 
 
803
    """List ignored files and the patterns that matched them.
 
 
805
    See also: bzr ignore"""
 
 
807
        tree = Branch('.').working_tree()
 
 
808
        for path, file_class, kind, file_id in tree.list_files():
 
 
809
            if file_class != 'I':
 
 
811
            ## XXX: Slightly inefficient since this was already calculated
 
 
812
            pat = tree.is_ignored(path)
 
 
813
            print '%-50s %s' % (path, pat)
 
 
816
class cmd_lookup_revision(Command):
 
 
817
    """Lookup the revision-id from a revision-number
 
 
820
        bzr lookup-revision 33
 
 
823
    takes_args = ['revno']
 
 
825
    def run(self, revno):
 
 
829
            raise BzrCommandError("not a valid revision-number: %r" % revno)
 
 
831
        print Branch('.').lookup_revision(revno)
 
 
834
class cmd_export(Command):
 
 
835
    """Export past revision to destination directory.
 
 
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):
 
 
843
            rh = b.revision_history()[-1]
 
 
845
            rh = b.lookup_revision(int(revision))
 
 
846
        t = b.revision_tree(rh)
 
 
850
class cmd_cat(Command):
 
 
851
    """Write a file's text from a previous revision."""
 
 
853
    takes_options = ['revision']
 
 
854
    takes_args = ['filename']
 
 
856
    def run(self, filename, revision=None):
 
 
858
            raise BzrCommandError("bzr cat requires a revision number")
 
 
860
        b.print_file(b.relpath(filename), int(revision))
 
 
863
class cmd_local_time_offset(Command):
 
 
864
    """Show the offset in seconds from GMT to local time."""
 
 
867
        print bzrlib.osutils.local_time_offset()
 
 
871
class cmd_commit(Command):
 
 
872
    """Commit changes into a new revision.
 
 
874
    If selected files are specified, only changes to those files are
 
 
875
    committed.  If a directory is specified then its contents are also
 
 
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.
 
 
882
    TODO: Run hooks on tree to-be-committed, and after commit.
 
 
884
    TODO: Strict commit that fails if there are unknown or deleted files.
 
 
886
    takes_args = ['selected*']
 
 
887
    takes_options = ['message', 'file', 'verbose']
 
 
888
    aliases = ['ci', 'checkin']
 
 
890
    def run(self, message=None, file=None, verbose=True, selected_list=None):
 
 
891
        from bzrlib.commit import commit
 
 
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")
 
 
902
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
 
 
905
        commit(b, message, verbose=verbose, specific_files=selected_list)
 
 
908
class cmd_check(Command):
 
 
909
    """Validate consistency of branch history.
 
 
911
    This command checks various invariants about the branch storage to
 
 
912
    detect data corruption or bzr bugs.
 
 
914
    takes_args = ['dir?']
 
 
915
    def run(self, dir='.'):
 
 
917
        bzrlib.check.check(Branch(dir))
 
 
921
class cmd_whoami(Command):
 
 
922
    """Show bzr user id."""
 
 
923
    takes_options = ['email']
 
 
925
    def run(self, email=False):
 
 
927
            print bzrlib.osutils.user_email()
 
 
929
            print bzrlib.osutils.username()
 
 
932
class cmd_selftest(Command):
 
 
933
    """Run internal test suite"""
 
 
936
        failures, tests = 0, 0
 
 
938
        import doctest, bzrlib.store
 
 
939
        bzrlib.trace.verbose = False
 
 
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)
 
 
946
            print '%-40s %3d tests' % (m.__name__, mt),
 
 
948
                print '%3d FAILED!' % mf
 
 
952
        print '%-40s %3d tests' % ('total', tests),
 
 
954
            print '%3d FAILED!' % failures
 
 
962
class cmd_version(Command):
 
 
963
    """Show version of bzr"""
 
 
968
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
 
969
    print bzrlib.__copyright__
 
 
970
    print "http://bazaar-ng.org/"
 
 
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."
 
 
977
class cmd_rocks(Command):
 
 
978
    """Statement of optimism."""
 
 
981
        print "it sure does!"
 
 
983
def parse_spec(spec):
 
 
985
        parsed = spec.split('/@')
 
 
986
        assert len(parsed) == 2
 
 
990
            parsed[1] = int(parsed[1])
 
 
993
        parsed = [spec, None]
 
 
996
class cmd_merge(Command):
 
 
997
    """Perform a three-way merge of trees."""
 
 
998
    takes_args = ['other_spec', 'base_spec']
 
 
1000
    def run(self, other_spec, base_spec):
 
 
1001
        merge.merge(parse_spec(other_spec), parse_spec(base_spec))
 
 
1003
class cmd_assert_fail(Command):
 
 
1004
    """Test reporting of assertion failures"""
 
 
1007
        assert False, "always fails"
 
 
1010
class cmd_help(Command):
 
 
1011
    """Show help on a command or other topic.
 
 
1013
    For a list of all available commands, say 'bzr help commands'."""
 
 
1014
    takes_args = ['topic?']
 
 
1017
    def run(self, topic=None):
 
 
1022
class cmd_update_stat_cache(Command):
 
 
1023
    """Update stat-cache mapping inodes to SHA-1 hashes.
 
 
1025
    For testing only."""
 
 
1030
        statcache.update_cache(b.base, b.read_working_inventory())
 
 
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
 
 
1039
    'diff-options':           str,
 
 
1045
    'revision':               _parse_revision_str,
 
 
1062
def parse_args(argv):
 
 
1063
    """Parse command line.
 
 
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.
 
 
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'})
 
 
1082
    # TODO: Maybe handle '--' to end options?
 
 
1087
            # option names must not be unicode
 
 
1091
                mutter("  got option %r" % a)
 
 
1093
                    optname, optarg = a[2:].split('=', 1)
 
 
1096
                if optname not in OPTIONS:
 
 
1097
                    bailout('unknown long option %r' % a)
 
 
1100
                if shortopt not in SHORT_OPTIONS:
 
 
1101
                    bailout('unknown short option %r' % a)
 
 
1102
                optname = SHORT_OPTIONS[shortopt]
 
 
1105
                # XXX: Do we ever want to support this, e.g. for -r?
 
 
1106
                bailout('repeated option %r' % a)
 
 
1108
            optargfn = OPTIONS[optname]
 
 
1112
                        bailout('option %r needs an argument' % a)
 
 
1114
                        optarg = argv.pop(0)
 
 
1115
                opts[optname] = optargfn(optarg)
 
 
1118
                    bailout('option %r takes no argument' % optname)
 
 
1119
                opts[optname] = True
 
 
1128
def _match_argform(cmd, takes_args, args):
 
 
1131
    # step through args and takes_args, allowing appropriate 0-many matches
 
 
1132
    for ap in takes_args:
 
 
1136
                argdict[argname] = args.pop(0)
 
 
1137
        elif ap[-1] == '*': # all remaining arguments
 
 
1139
                argdict[argname + '_list'] = args[:]
 
 
1142
                argdict[argname + '_list'] = None
 
 
1145
                raise BzrCommandError("command %r needs one or more %s"
 
 
1146
                        % (cmd, argname.upper()))
 
 
1148
                argdict[argname + '_list'] = args[:]
 
 
1150
        elif ap[-1] == '$': # all but one
 
 
1152
                raise BzrCommandError("command %r needs one or more %s"
 
 
1153
                        % (cmd, argname.upper()))
 
 
1154
            argdict[argname + '_list'] = args[:-1]
 
 
1160
                raise BzrCommandError("command %r requires argument %s"
 
 
1161
                        % (cmd, argname.upper()))
 
 
1163
                argdict[argname] = args.pop(0)
 
 
1166
        raise BzrCommandError("extra argument to command %s: %s"
 
 
1174
    """Execute a command.
 
 
1176
    This is similar to main(), but without all the trappings for
 
 
1177
    logging and error handling.  
 
 
1179
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
 
1182
        args, opts = parse_args(argv[1:])
 
 
1190
        elif 'version' in opts:
 
 
1193
        cmd = str(args.pop(0))
 
 
1200
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
 
1203
    if 'profile' in opts:
 
 
1209
    # check options are reasonable
 
 
1210
    allowed = cmd_class.takes_options
 
 
1212
        if oname not in allowed:
 
 
1213
            raise BzrCommandError("option '--%s' is not allowed for command %r"
 
 
1216
    # mix arguments and options into one dictionary
 
 
1217
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
 
1219
    for k, v in opts.items():
 
 
1220
        cmdopts[k.replace('-', '_')] = v
 
 
1223
        import hotshot, tempfile
 
 
1224
        pffileno, pfname = tempfile.mkstemp()
 
 
1226
            prof = hotshot.Profile(pfname)
 
 
1227
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
 
1230
            import hotshot.stats
 
 
1231
            stats = hotshot.stats.load(pfname)
 
 
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)
 
 
1244
        return cmd_class(cmdopts, cmdargs).status 
 
 
1247
def _report_exception(summary, quiet=False):
 
 
1249
    log_error('bzr: ' + summary)
 
 
1250
    bzrlib.trace.log_exception()
 
 
1253
        tb = sys.exc_info()[2]
 
 
1254
        exinfo = traceback.extract_tb(tb)
 
 
1256
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
 
 
1257
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
 
 
1264
    bzrlib.open_tracefile(argv)
 
 
1269
                return run_bzr(argv)
 
 
1271
                # do this here inside the exception wrappers to catch EPIPE
 
 
1274
            quiet = isinstance(e, (BzrCommandError))
 
 
1275
            _report_exception('error: ' + e.args[0], quiet=quiet)
 
 
1278
                    # some explanation or hints
 
 
1281
        except AssertionError, e:
 
 
1282
            msg = 'assertion failed'
 
 
1284
                msg += ': ' + str(e)
 
 
1285
            _report_exception(msg)
 
 
1287
        except KeyboardInterrupt, e:
 
 
1288
            _report_exception('interrupted', quiet=True)
 
 
1290
        except Exception, e:
 
 
1292
            if (isinstance(e, IOError) 
 
 
1293
                and hasattr(e, 'errno')
 
 
1294
                and e.errno == errno.EPIPE):
 
 
1298
                msg = str(e).rstrip('\n')
 
 
1299
            _report_exception(msg, quiet)
 
 
1302
        bzrlib.trace.close_trace()
 
 
1305
if __name__ == '__main__':
 
 
1306
    sys.exit(main(sys.argv))