~bzr-pqm/bzr/bzr.dev

1731.1.5 by Aaron Bentley
Restore test_patches_data
1
# Copyright (C) 2004 Aaron Bentley
2
# <aaron.bentley@utoronto.ca>
3
#
4
#    This program is free software; you can redistribute it and/or modify
5
#    it under the terms of the GNU General Public License as published by
6
#    the Free Software Foundation; either version 2 of the License, or
7
#    (at your option) any later version.
8
#
9
#    This program is distributed in the hope that it will be useful,
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
#    GNU General Public License for more details.
13
#
14
#    You should have received a copy of the GNU General Public License
15
#    along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1731.1.5 by Aaron Bentley
Restore test_patches_data
17
18
import sys
19
import arch
20
import arch.util
21
import arch.arch
22
23
import pylon.errors
24
from pylon.errors import *
25
from pylon import errors
26
from pylon import util
27
from pylon import arch_core
28
from pylon import arch_compound
29
from pylon import ancillary
30
from pylon import misc
31
from pylon import paths 
32
33
import abacmds
34
import cmdutil
35
import shutil
36
import os
37
import options
38
import time
39
import cmd
40
import readline
41
import re
42
import string
43
import terminal
44
import email
45
import smtplib
46
import textwrap
47
48
__docformat__ = "restructuredtext"
49
__doc__ = "Implementation of user (sub) commands"
50
commands = {}
51
52
def find_command(cmd):
53
    """
54
    Return an instance of a command type.  Return None if the type isn't
55
    registered.
56
57
    :param cmd: the name of the command to look for
58
    :type cmd: the type of the command
59
    """
60
    if commands.has_key(cmd):
61
        return commands[cmd]()
62
    else:
63
        return None
64
65
class BaseCommand:
66
    def __call__(self, cmdline):
67
        try:
68
            self.do_command(cmdline.split())
69
        except cmdutil.GetHelp, e:
70
            self.help()
71
        except Exception, e:
72
            print e
73
74
    def get_completer(index):
75
        return None
76
77
    def complete(self, args, text):
78
        """
79
        Returns a list of possible completions for the given text.
80
81
        :param args: The complete list of arguments
82
        :type args: List of str
83
        :param text: text to complete (may be shorter than args[-1])
84
        :type text: str
85
        :rtype: list of str
86
        """
87
        matches = []
88
        candidates = None
89
90
        if len(args) > 0: 
91
            realtext = args[-1]
92
        else:
93
            realtext = ""
94
95
        try:
96
            parser=self.get_parser()
97
            if realtext.startswith('-'):
98
                candidates = parser.iter_options()
99
            else:
100
                (options, parsed_args) = parser.parse_args(args)
101
102
                if len (parsed_args) > 0:
103
                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
104
                else:
105
                    candidates = self.get_completer("", 0)
106
        except:
107
            pass
108
        if candidates is None:
109
            return
110
        for candidate in candidates:
111
            candidate = str(candidate)
112
            if candidate.startswith(realtext):
113
                matches.append(candidate[len(realtext)- len(text):])
114
        return matches
115
116
117
class Help(BaseCommand):
118
    """
119
    Lists commands, prints help messages.
120
    """
121
    def __init__(self):
122
        self.description="Prints help mesages"
123
        self.parser = None
124
125
    def do_command(self, cmdargs):
126
        """
127
        Prints a help message.
128
        """
129
        options, args = self.get_parser().parse_args(cmdargs)
130
        if len(args) > 1:
131
            raise cmdutil.GetHelp
132
133
        if options.native or options.suggestions or options.external:
134
            native = options.native
135
            suggestions = options.suggestions
136
            external = options.external
137
        else:
138
            native = True
139
            suggestions = False
140
            external = True
141
        
142
        if len(args) == 0:
143
            self.list_commands(native, suggestions, external)
144
            return
145
        elif len(args) == 1:
146
            command_help(args[0])
147
            return
148
149
    def help(self):
150
        self.get_parser().print_help()
151
        print """
152
If no command is specified, commands are listed.  If a command is
153
specified, help for that command is listed.
154
        """
155
156
    def get_parser(self):
157
        """
158
        Returns the options parser to use for the "revision" command.
159
160
        :rtype: cmdutil.CmdOptionParser
161
        """
162
        if self.parser is not None:
163
            return self.parser
164
        parser=cmdutil.CmdOptionParser("fai help [command]")
165
        parser.add_option("-n", "--native", action="store_true", 
166
                         dest="native", help="Show native commands")
167
        parser.add_option("-e", "--external", action="store_true", 
168
                         dest="external", help="Show external commands")
169
        parser.add_option("-s", "--suggest", action="store_true", 
170
                         dest="suggestions", help="Show suggestions")
171
        self.parser = parser
172
        return parser 
173
      
174
    def list_commands(self, native=True, suggest=False, external=True):
175
        """
176
        Lists supported commands.
177
178
        :param native: list native, python-based commands
179
        :type native: bool
180
        :param external: list external aba-style commands
181
        :type external: bool
182
        """
183
        if native:
184
            print "Native Fai commands"
185
            keys=commands.keys()
186
            keys.sort()
187
            for k in keys:
188
                space=""
189
                for i in range(28-len(k)):
190
                    space+=" "
191
                print space+k+" : "+commands[k]().description
192
            print
193
        if suggest:
194
            print "Unavailable commands and suggested alternatives"
195
            key_list = suggestions.keys()
196
            key_list.sort()
197
            for key in key_list:
198
                print "%28s : %s" % (key, suggestions[key])
199
            print
200
        if external:
201
            fake_aba = abacmds.AbaCmds()
202
            if (fake_aba.abadir == ""):
203
                return
204
            print "External commands"
205
            fake_aba.list_commands()
206
            print
207
        if not suggest:
208
            print "Use help --suggest to list alternatives to tla and aba"\
209
                " commands."
210
        if options.tla_fallthrough and (native or external):
211
            print "Fai also supports tla commands."
212
213
def command_help(cmd):
214
    """
215
    Prints help for a command.
216
217
    :param cmd: The name of the command to print help for
218
    :type cmd: str
219
    """
220
    fake_aba = abacmds.AbaCmds()
221
    cmdobj = find_command(cmd)
222
    if cmdobj != None:
223
        cmdobj.help()
224
    elif suggestions.has_key(cmd):
225
        print "Not available\n" + suggestions[cmd]
226
    else:
227
        abacmd = fake_aba.is_command(cmd)
228
        if abacmd:
229
            abacmd.help()
230
        else:
231
            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
232
233
234
235
class Changes(BaseCommand):
236
    """
237
    the "changes" command: lists differences between trees/revisions:
238
    """
239
    
240
    def __init__(self):
241
        self.description="Lists what files have changed in the project tree"
242
243
    def get_completer(self, arg, index):
244
        if index > 1:
245
            return None
246
        try:
247
            tree = arch.tree_root()
248
        except:
249
            tree = None
250
        return cmdutil.iter_revision_completions(arg, tree)
251
    
252
    def parse_commandline(self, cmdline):
253
        """
254
        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
255
        
256
        :param cmdline: A list of arguments to parse
257
        :rtype: (options, Revision, Revision/WorkingTree)
258
        """
259
        parser=self.get_parser()
260
        (options, args) = parser.parse_args(cmdline)
261
        if len(args) > 2:
262
            raise cmdutil.GetHelp
263
264
        tree=arch.tree_root()
265
        if len(args) == 0:
266
            a_spec = ancillary.comp_revision(tree)
267
        else:
268
            a_spec = cmdutil.determine_revision_tree(tree, args[0])
269
        cmdutil.ensure_archive_registered(a_spec.archive)
270
        if len(args) == 2:
271
            b_spec = cmdutil.determine_revision_tree(tree, args[1])
272
            cmdutil.ensure_archive_registered(b_spec.archive)
273
        else:
274
            b_spec=tree
275
        return options, a_spec, b_spec
276
277
    def do_command(self, cmdargs):
278
        """
279
        Master function that perfoms the "changes" command.
280
        """
281
        try:
282
            options, a_spec, b_spec = self.parse_commandline(cmdargs);
283
        except cmdutil.CantDetermineRevision, e:
284
            print e
285
            return
286
        except arch.errors.TreeRootError, e:
287
            print e
288
            return
289
        if options.changeset:
290
            changeset=options.changeset
291
            tmpdir = None
292
        else:
293
            tmpdir=util.tmpdir()
294
            changeset=tmpdir+"/changeset"
295
        try:
296
            delta=arch.iter_delta(a_spec, b_spec, changeset)
297
            try:
298
                for line in delta:
299
                    if cmdutil.chattermatch(line, "changeset:"):
300
                        pass
301
                    else:
302
                        cmdutil.colorize(line, options.suppress_chatter)
303
            except arch.util.ExecProblem, e:
304
                if e.proc.error and e.proc.error.startswith(
305
                    "missing explicit id for file"):
306
                    raise MissingID(e)
307
                else:
308
                    raise
309
            status=delta.status
310
            if status > 1:
311
                return
312
            if (options.perform_diff):
313
                chan = arch_compound.ChangesetMunger(changeset)
314
                chan.read_indices()
315
                if options.diffopts is not None:
316
                    if isinstance(b_spec, arch.Revision):
317
                        b_dir = b_spec.library_find()
318
                    else:
319
                        b_dir = b_spec
320
                    a_dir = a_spec.library_find()
321
                    diffopts = options.diffopts.split()
322
                    cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
323
                else:
324
                    cmdutil.show_diffs(delta.changeset)
325
        finally:
326
            if tmpdir and (os.access(tmpdir, os.X_OK)):
327
                shutil.rmtree(tmpdir)
328
329
    def get_parser(self):
330
        """
331
        Returns the options parser to use for the "changes" command.
332
333
        :rtype: cmdutil.CmdOptionParser
334
        """
335
        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
336
                                       " [revision]")
337
        parser.add_option("-d", "--diff", action="store_true", 
338
                          dest="perform_diff", default=False, 
339
                          help="Show diffs in summary")
340
        parser.add_option("-c", "--changeset", dest="changeset", 
341
                          help="Store a changeset in the given directory", 
342
                          metavar="DIRECTORY")
343
        parser.add_option("-s", "--silent", action="store_true", 
344
                          dest="suppress_chatter", default=False, 
345
                          help="Suppress chatter messages")
346
        parser.add_option("--diffopts", dest="diffopts", 
347
                          help="Use the specified diff options", 
348
                          metavar="OPTIONS")
349
350
        return parser
351
352
    def help(self, parser=None):
353
        """
354
        Prints a help message.
355
356
        :param parser: If supplied, the parser to use for generating help.  If \
357
        not supplied, it is retrieved.
358
        :type parser: cmdutil.CmdOptionParser
359
        """
360
        if parser is None:
361
            parser=self.get_parser()
362
        parser.print_help()
363
        print """
364
Performs source-tree comparisons
365
366
If no revision is specified, the current project tree is compared to the
367
last-committed revision.  If one revision is specified, the current project
368
tree is compared to that revision.  If two revisions are specified, they are
369
compared to each other.
370
        """
371
        help_tree_spec() 
372
        return
373
374
375
class ApplyChanges(BaseCommand):
376
    """
377
    Apply differences between two revisions to a tree
378
    """
379
    
380
    def __init__(self):
381
        self.description="Applies changes to a project tree"
382
    
383
    def get_completer(self, arg, index):
384
        if index > 1:
385
            return None
386
        try:
387
            tree = arch.tree_root()
388
        except:
389
            tree = None
390
        return cmdutil.iter_revision_completions(arg, tree)
391
392
    def parse_commandline(self, cmdline, tree):
393
        """
394
        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
395
        
396
        :param cmdline: A list of arguments to parse
397
        :rtype: (options, Revision, Revision/WorkingTree)
398
        """
399
        parser=self.get_parser()
400
        (options, args) = parser.parse_args(cmdline)
401
        if len(args) != 2:
402
            raise cmdutil.GetHelp
403
404
        a_spec = cmdutil.determine_revision_tree(tree, args[0])
405
        cmdutil.ensure_archive_registered(a_spec.archive)
406
        b_spec = cmdutil.determine_revision_tree(tree, args[1])
407
        cmdutil.ensure_archive_registered(b_spec.archive)
408
        return options, a_spec, b_spec
409
410
    def do_command(self, cmdargs):
411
        """
412
        Master function that performs "apply-changes".
413
        """
414
        try:
415
            tree = arch.tree_root()
416
            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
417
        except cmdutil.CantDetermineRevision, e:
418
            print e
419
            return
420
        except arch.errors.TreeRootError, e:
421
            print e
422
            return
423
        delta=cmdutil.apply_delta(a_spec, b_spec, tree)
424
        for line in cmdutil.iter_apply_delta_filter(delta):
425
            cmdutil.colorize(line, options.suppress_chatter)
426
427
    def get_parser(self):
428
        """
429
        Returns the options parser to use for the "apply-changes" command.
430
431
        :rtype: cmdutil.CmdOptionParser
432
        """
433
        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
434
                                       " revision")
435
        parser.add_option("-d", "--diff", action="store_true", 
436
                          dest="perform_diff", default=False, 
437
                          help="Show diffs in summary")
438
        parser.add_option("-c", "--changeset", dest="changeset", 
439
                          help="Store a changeset in the given directory", 
440
                          metavar="DIRECTORY")
441
        parser.add_option("-s", "--silent", action="store_true", 
442
                          dest="suppress_chatter", default=False, 
443
                          help="Suppress chatter messages")
444
        return parser
445
446
    def help(self, parser=None):
447
        """
448
        Prints a help message.
449
450
        :param parser: If supplied, the parser to use for generating help.  If \
451
        not supplied, it is retrieved.
452
        :type parser: cmdutil.CmdOptionParser
453
        """
454
        if parser is None:
455
            parser=self.get_parser()
456
        parser.print_help()
457
        print """
458
Applies changes to a project tree
459
460
Compares two revisions and applies the difference between them to the current
461
tree.
462
        """
463
        help_tree_spec() 
464
        return
465
466
class Update(BaseCommand):
467
    """
468
    Updates a project tree to a given revision, preserving un-committed hanges. 
469
    """
470
    
471
    def __init__(self):
472
        self.description="Apply the latest changes to the current directory"
473
474
    def get_completer(self, arg, index):
475
        if index > 0:
476
            return None
477
        try:
478
            tree = arch.tree_root()
479
        except:
480
            tree = None
481
        return cmdutil.iter_revision_completions(arg, tree)
482
    
483
    def parse_commandline(self, cmdline, tree):
484
        """
485
        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
486
        
487
        :param cmdline: A list of arguments to parse
488
        :rtype: (options, Revision, Revision/WorkingTree)
489
        """
490
        parser=self.get_parser()
491
        (options, args) = parser.parse_args(cmdline)
492
        if len(args) > 2:
493
            raise cmdutil.GetHelp
494
495
        spec=None
496
        if len(args)>0:
497
            spec=args[0]
498
        revision=cmdutil.determine_revision_arch(tree, spec)
499
        cmdutil.ensure_archive_registered(revision.archive)
500
501
        mirror_source = cmdutil.get_mirror_source(revision.archive)
502
        if mirror_source != None:
503
            if cmdutil.prompt("Mirror update"):
504
                cmd=cmdutil.mirror_archive(mirror_source, 
505
                    revision.archive, arch.NameParser(revision).get_package_version())
506
                for line in arch.chatter_classifier(cmd):
507
                    cmdutil.colorize(line, options.suppress_chatter)
508
509
                revision=cmdutil.determine_revision_arch(tree, spec)
510
511
        return options, revision 
512
513
    def do_command(self, cmdargs):
514
        """
515
        Master function that perfoms the "update" command.
516
        """
517
        tree=arch.tree_root()
518
        try:
519
            options, to_revision = self.parse_commandline(cmdargs, tree);
520
        except cmdutil.CantDetermineRevision, e:
521
            print e
522
            return
523
        except arch.errors.TreeRootError, e:
524
            print e
525
            return
526
        from_revision = arch_compound.tree_latest(tree)
527
        if from_revision==to_revision:
528
            print "Tree is already up to date with:\n"+str(to_revision)+"."
529
            return
530
        cmdutil.ensure_archive_registered(from_revision.archive)
531
        cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
532
            options.patch_forward)
533
        for line in cmdutil.iter_apply_delta_filter(cmd):
534
            cmdutil.colorize(line)
535
        if to_revision.version != tree.tree_version:
536
            if cmdutil.prompt("Update version"):
537
                tree.tree_version = to_revision.version
538
539
    def get_parser(self):
540
        """
541
        Returns the options parser to use for the "update" command.
542
543
        :rtype: cmdutil.CmdOptionParser
544
        """
545
        parser=cmdutil.CmdOptionParser("fai update [options]"
546
                                       " [revision/version]")
547
        parser.add_option("-f", "--forward", action="store_true", 
548
                          dest="patch_forward", default=False, 
549
                          help="pass the --forward option to 'patch'")
550
        parser.add_option("-s", "--silent", action="store_true", 
551
                          dest="suppress_chatter", default=False, 
552
                          help="Suppress chatter messages")
553
        return parser
554
555
    def help(self, parser=None):
556
        """
557
        Prints a help message.
558
559
        :param parser: If supplied, the parser to use for generating help.  If \
560
        not supplied, it is retrieved.
561
        :type parser: cmdutil.CmdOptionParser
562
        """
563
        if parser is None:
564
            parser=self.get_parser()
565
        parser.print_help()
566
        print """
567
Updates a working tree to the current archive revision
568
569
If a revision or version is specified, that is used instead 
570
        """
571
        help_tree_spec() 
572
        return
573
574
575
class Commit(BaseCommand):
576
    """
577
    Create a revision based on the changes in the current tree.
578
    """
579
    
580
    def __init__(self):
581
        self.description="Write local changes to the archive"
582
583
    def get_completer(self, arg, index):
584
        if arg is None:
585
            arg = ""
586
        return iter_modified_file_completions(arch.tree_root(), arg)
587
#        return iter_source_file_completions(arch.tree_root(), arg)
588
    
589
    def parse_commandline(self, cmdline, tree):
590
        """
591
        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
592
        
593
        :param cmdline: A list of arguments to parse
594
        :rtype: (options, Revision, Revision/WorkingTree)
595
        """
596
        parser=self.get_parser()
597
        (options, args) = parser.parse_args(cmdline)
598
599
        if len(args) == 0:
600
            args = None
601
        if options.version is None:
602
            return options, tree.tree_version, args
603
604
        revision=cmdutil.determine_revision_arch(tree, options.version)
605
        return options, revision.get_version(), args
606
607
    def do_command(self, cmdargs):
608
        """
609
        Master function that perfoms the "commit" command.
610
        """
611
        tree=arch.tree_root()
612
        options, version, files = self.parse_commandline(cmdargs, tree)
613
        ancestor = None
614
        if options.__dict__.has_key("base") and options.base:
615
            base = cmdutil.determine_revision_tree(tree, options.base)
616
            ancestor = base
617
        else:
618
            base = ancillary.submit_revision(tree)
619
            ancestor = base
620
        if ancestor is None:
621
            ancestor = arch_compound.tree_latest(tree, version)
622
623
        writeversion=version
624
        archive=version.archive
625
        source=cmdutil.get_mirror_source(archive)
626
        allow_old=False
627
        writethrough="implicit"
628
629
        if source!=None:
630
            if writethrough=="explicit" and \
631
                cmdutil.prompt("Writethrough"):
632
                writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
633
            elif writethrough=="none":
634
                raise CommitToMirror(archive)
635
636
        elif archive.is_mirror:
637
            raise CommitToMirror(archive)
638
639
        try:
640
            last_revision=tree.iter_logs(version, True).next().revision
641
        except StopIteration, e:
642
            last_revision = None
643
            if ancestor is None:
644
                if cmdutil.prompt("Import from commit"):
645
                    return do_import(version)
646
                else:
647
                    raise NoVersionLogs(version)
648
        try:
649
            arch_last_revision = version.iter_revisions(True).next()
650
        except StopIteration, e:
651
            arch_last_revision = None
652
 
653
        if last_revision != arch_last_revision:
654
            print "Tree is not up to date with %s" % str(version)
655
            if not cmdutil.prompt("Out of date"):
656
                raise OutOfDate
657
            else:
658
                allow_old=True
659
660
        try:
661
            if not cmdutil.has_changed(ancestor):
662
                if not cmdutil.prompt("Empty commit"):
663
                    raise EmptyCommit
664
        except arch.util.ExecProblem, e:
665
            if e.proc.error and e.proc.error.startswith(
666
                "missing explicit id for file"):
667
                raise MissingID(e)
668
            else:
669
                raise
670
        log = tree.log_message(create=False, version=version)
671
        if log is None:
672
            try:
673
                if cmdutil.prompt("Create log"):
674
                    edit_log(tree, version)
675
676
            except cmdutil.NoEditorSpecified, e:
677
                raise CommandFailed(e)
678
            log = tree.log_message(create=False, version=version)
679
        if log is None: 
680
            raise NoLogMessage
681
        if log["Summary"] is None or len(log["Summary"].strip()) == 0:
682
            if not cmdutil.prompt("Omit log summary"):
683
                raise errors.NoLogSummary
684
        try:
685
            for line in tree.iter_commit(version, seal=options.seal_version,
686
                base=base, out_of_date_ok=allow_old, file_list=files):
687
                cmdutil.colorize(line, options.suppress_chatter)
688
689
        except arch.util.ExecProblem, e:
690
            if e.proc.error and e.proc.error.startswith(
691
                "These files violate naming conventions:"):
692
                raise LintFailure(e.proc.error)
693
            else:
694
                raise
695
696
    def get_parser(self):
697
        """
698
        Returns the options parser to use for the "commit" command.
699
700
        :rtype: cmdutil.CmdOptionParser
701
        """
702
703
        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
704
                                       " [file2...]")
705
        parser.add_option("--seal", action="store_true", 
706
                          dest="seal_version", default=False, 
707
                          help="seal this version")
708
        parser.add_option("-v", "--version", dest="version", 
709
                          help="Use the specified version", 
710
                          metavar="VERSION")
711
        parser.add_option("-s", "--silent", action="store_true", 
712
                          dest="suppress_chatter", default=False, 
713
                          help="Suppress chatter messages")
714
        if cmdutil.supports_switch("commit", "--base"):
715
            parser.add_option("--base", dest="base", help="", 
716
                              metavar="REVISION")
717
        return parser
718
719
    def help(self, parser=None):
720
        """
721
        Prints a help message.
722
723
        :param parser: If supplied, the parser to use for generating help.  If \
724
        not supplied, it is retrieved.
725
        :type parser: cmdutil.CmdOptionParser
726
        """
727
        if parser is None:
728
            parser=self.get_parser()
729
        parser.print_help()
730
        print """
731
Updates a working tree to the current archive revision
732
733
If a version is specified, that is used instead 
734
        """
735
#        help_tree_spec() 
736
        return
737
738
739
740
class CatLog(BaseCommand):
741
    """
742
    Print the log of a given file (from current tree)
743
    """
744
    def __init__(self):
745
        self.description="Prints the patch log for a revision"
746
747
    def get_completer(self, arg, index):
748
        if index > 0:
749
            return None
750
        try:
751
            tree = arch.tree_root()
752
        except:
753
            tree = None
754
        return cmdutil.iter_revision_completions(arg, tree)
755
756
    def do_command(self, cmdargs):
757
        """
758
        Master function that perfoms the "cat-log" command.
759
        """
760
        parser=self.get_parser()
761
        (options, args) = parser.parse_args(cmdargs)
762
        try:
763
            tree = arch.tree_root()
764
        except arch.errors.TreeRootError, e:
765
            tree = None
766
        spec=None
767
        if len(args) > 0:
768
            spec=args[0]
769
        if len(args) > 1:
770
            raise cmdutil.GetHelp()
771
        try:
772
            if tree:
773
                revision = cmdutil.determine_revision_tree(tree, spec)
774
            else:
775
                revision = cmdutil.determine_revision_arch(tree, spec)
776
        except cmdutil.CantDetermineRevision, e:
777
            raise CommandFailedWrapper(e)
778
        log = None
779
        
780
        use_tree = (options.source == "tree" or \
781
            (options.source == "any" and tree))
782
        use_arch = (options.source == "archive" or options.source == "any")
783
        
784
        log = None
785
        if use_tree:
786
            for log in tree.iter_logs(revision.get_version()):
787
                if log.revision == revision:
788
                    break
789
                else:
790
                    log = None
791
        if log is None and use_arch:
792
            cmdutil.ensure_revision_exists(revision)
793
            log = arch.Patchlog(revision)
794
        if log is not None:
795
            for item in log.items():
796
                print "%s: %s" % item
797
            print log.description
798
799
    def get_parser(self):
800
        """
801
        Returns the options parser to use for the "cat-log" command.
802
803
        :rtype: cmdutil.CmdOptionParser
804
        """
805
        parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
806
        parser.add_option("--archive", action="store_const", dest="source",
807
                          const="archive", default="any",
808
                          help="Always get the log from the archive")
809
        parser.add_option("--tree", action="store_const", dest="source",
810
                          const="tree", help="Always get the log from the tree")
811
        return parser 
812
813
    def help(self, parser=None):
814
        """
815
        Prints a help message.
816
817
        :param parser: If supplied, the parser to use for generating help.  If \
818
        not supplied, it is retrieved.
819
        :type parser: cmdutil.CmdOptionParser
820
        """
821
        if parser==None:
822
            parser=self.get_parser()
823
        parser.print_help()
824
        print """
825
Prints the log for the specified revision
826
        """
827
        help_tree_spec()
828
        return
829
830
class Revert(BaseCommand):
831
    """ Reverts a tree (or aspects of it) to a revision
832
    """
833
    def __init__(self):
834
        self.description="Reverts a tree (or aspects of it) to a revision "
835
836
    def get_completer(self, arg, index):
837
        if index > 0:
838
            return None
839
        try:
840
            tree = arch.tree_root()
841
        except:
842
            tree = None
843
        return iter_modified_file_completions(tree, arg)
844
845
    def do_command(self, cmdargs):
846
        """
847
        Master function that perfoms the "revert" command.
848
        """
849
        parser=self.get_parser()
850
        (options, args) = parser.parse_args(cmdargs)
851
        try:
852
            tree = arch.tree_root()
853
        except arch.errors.TreeRootError, e:
854
            raise CommandFailed(e)
855
        spec=None
856
        if options.revision is not None:
857
            spec=options.revision
858
        try:
859
            if spec is not None:
860
                revision = cmdutil.determine_revision_tree(tree, spec)
861
            else:
862
                revision = ancillary.comp_revision(tree)
863
        except cmdutil.CantDetermineRevision, e:
864
            raise CommandFailedWrapper(e)
865
        munger = None
866
867
        if options.file_contents or options.file_perms or options.deletions\
868
            or options.additions or options.renames or options.hunk_prompt:
869
            munger = arch_compound.MungeOpts()
870
            munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
871
                                   options.hunk_prompt)
872
873
        if len(args) > 0 or options.logs or options.pattern_files or \
874
            options.control:
875
            if munger is None:
876
                munger = cmdutil.arch_compound.MungeOpts(True)
877
                munger.all_types(True)
878
        if len(args) > 0:
879
            t_cwd = arch_compound.tree_cwd(tree)
880
            for name in args:
881
                if len(t_cwd) > 0:
882
                    t_cwd += "/"
883
                name = "./" + t_cwd + name
884
                munger.add_keep_file(name);
885
886
        if options.file_perms:
887
            munger.file_perms = True
888
        if options.file_contents:
889
            munger.file_contents = True
890
        if options.deletions:
891
            munger.deletions = True
892
        if options.additions:
893
            munger.additions = True
894
        if options.renames:
895
            munger.renames = True
896
        if options.logs:
897
            munger.add_keep_pattern('^\./\{arch\}/[^=].*')
898
        if options.control:
899
            munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
900
                                    "/\.arch-inventory$")
901
        if options.pattern_files:
902
            munger.add_keep_pattern(options.pattern_files)
903
                
904
        for line in arch_compound.revert(tree, revision, munger, 
905
                                   not options.no_output):
906
            cmdutil.colorize(line)
907
908
909
    def get_parser(self):
910
        """
911
        Returns the options parser to use for the "cat-log" command.
912
913
        :rtype: cmdutil.CmdOptionParser
914
        """
915
        parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
916
        parser.add_option("", "--contents", action="store_true", 
917
                          dest="file_contents", 
918
                          help="Revert file content changes")
919
        parser.add_option("", "--permissions", action="store_true", 
920
                          dest="file_perms", 
921
                          help="Revert file permissions changes")
922
        parser.add_option("", "--deletions", action="store_true", 
923
                          dest="deletions", 
924
                          help="Restore deleted files")
925
        parser.add_option("", "--additions", action="store_true", 
926
                          dest="additions", 
927
                          help="Remove added files")
928
        parser.add_option("", "--renames", action="store_true", 
929
                          dest="renames", 
930
                          help="Revert file names")
931
        parser.add_option("--hunks", action="store_true", 
932
                          dest="hunk_prompt", default=False,
933
                          help="Prompt which hunks to revert")
934
        parser.add_option("--pattern-files", dest="pattern_files", 
935
                          help="Revert files that match this pattern", 
936
                          metavar="REGEX")
937
        parser.add_option("--logs", action="store_true", 
938
                          dest="logs", default=False,
939
                          help="Revert only logs")
940
        parser.add_option("--control-files", action="store_true", 
941
                          dest="control", default=False,
942
                          help="Revert logs and other control files")
943
        parser.add_option("-n", "--no-output", action="store_true", 
944
                          dest="no_output", 
945
                          help="Don't keep an undo changeset")
946
        parser.add_option("--revision", dest="revision", 
947
                          help="Revert to the specified revision", 
948
                          metavar="REVISION")
949
        return parser 
950
951
    def help(self, parser=None):
952
        """
953
        Prints a help message.
954
955
        :param parser: If supplied, the parser to use for generating help.  If \
956
        not supplied, it is retrieved.
957
        :type parser: cmdutil.CmdOptionParser
958
        """
959
        if parser==None:
960
            parser=self.get_parser()
961
        parser.print_help()
962
        print """
963
Reverts changes in the current working tree.  If no flags are specified, all
964
types of changes are reverted.  Otherwise, only selected types of changes are
965
reverted.  
966
967
If a revision is specified on the commandline, differences between the current
968
tree and that revision are reverted.  If a version is specified, the current
969
tree is used to determine the revision.
970
971
If files are specified, only those files listed will have any changes applied.
972
To specify a renamed file, you can use either the old or new name. (or both!)
973
974
Unless "-n" is specified, reversions can be undone with "redo".
975
        """
976
        return
977
978
class Revision(BaseCommand):
979
    """
980
    Print a revision name based on a revision specifier
981
    """
982
    def __init__(self):
983
        self.description="Prints the name of a revision"
984
985
    def get_completer(self, arg, index):
986
        if index > 0:
987
            return None
988
        try:
989
            tree = arch.tree_root()
990
        except:
991
            tree = None
992
        return cmdutil.iter_revision_completions(arg, tree)
993
994
    def do_command(self, cmdargs):
995
        """
996
        Master function that perfoms the "revision" command.
997
        """
998
        parser=self.get_parser()
999
        (options, args) = parser.parse_args(cmdargs)
1000
1001
        try:
1002
            tree = arch.tree_root()
1003
        except arch.errors.TreeRootError:
1004
            tree = None
1005
1006
        spec=None
1007
        if len(args) > 0:
1008
            spec=args[0]
1009
        if len(args) > 1:
1010
            raise cmdutil.GetHelp
1011
        try:
1012
            if tree:
1013
                revision = cmdutil.determine_revision_tree(tree, spec)
1014
            else:
1015
                revision = cmdutil.determine_revision_arch(tree, spec)
1016
        except cmdutil.CantDetermineRevision, e:
1017
            print str(e)
1018
            return
1019
        print options.display(revision)
1020
1021
    def get_parser(self):
1022
        """
1023
        Returns the options parser to use for the "revision" command.
1024
1025
        :rtype: cmdutil.CmdOptionParser
1026
        """
1027
        parser=cmdutil.CmdOptionParser("fai revision [revision]")
1028
        parser.add_option("", "--location", action="store_const", 
1029
                         const=paths.determine_path, dest="display", 
1030
                         help="Show location instead of name", default=str)
1031
        parser.add_option("--import", action="store_const", 
1032
                         const=paths.determine_import_path, dest="display",  
1033
                         help="Show location of import file")
1034
        parser.add_option("--log", action="store_const", 
1035
                         const=paths.determine_log_path, dest="display", 
1036
                         help="Show location of log file")
1037
        parser.add_option("--patch", action="store_const", 
1038
                         dest="display", const=paths.determine_patch_path,
1039
                         help="Show location of patchfile")
1040
        parser.add_option("--continuation", action="store_const", 
1041
                         const=paths.determine_continuation_path, 
1042
                         dest="display",
1043
                         help="Show location of continuation file")
1044
        parser.add_option("--cacherev", action="store_const", 
1045
                         const=paths.determine_cacherev_path, dest="display",
1046
                         help="Show location of cacherev file")
1047
        return parser 
1048
1049
    def help(self, parser=None):
1050
        """
1051
        Prints a help message.
1052
1053
        :param parser: If supplied, the parser to use for generating help.  If \
1054
        not supplied, it is retrieved.
1055
        :type parser: cmdutil.CmdOptionParser
1056
        """
1057
        if parser==None:
1058
            parser=self.get_parser()
1059
        parser.print_help()
1060
        print """
1061
Expands aliases and prints the name of the specified revision.  Instead of
1062
the name, several options can be used to print locations.  If more than one is
1063
specified, the last one is used.
1064
        """
1065
        help_tree_spec()
1066
        return
1067
1068
class Revisions(BaseCommand):
1069
    """
1070
    Print a revision name based on a revision specifier
1071
    """
1072
    def __init__(self):
1073
        self.description="Lists revisions"
1074
        self.cl_revisions = []
1075
    
1076
    def do_command(self, cmdargs):
1077
        """
1078
        Master function that perfoms the "revision" command.
1079
        """
1080
        (options, args) = self.get_parser().parse_args(cmdargs)
1081
        if len(args) > 1:
1082
            raise cmdutil.GetHelp
1083
        try:
1084
            self.tree = arch.tree_root()
1085
        except arch.errors.TreeRootError:
1086
            self.tree = None
1087
        if options.type == "default":
1088
            options.type = "archive"
1089
        try:
1090
            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
1091
                                             options.reverse, options.modified,
1092
                                             options.shallow)
1093
        except cmdutil.CantDetermineRevision, e:
1094
            raise CommandFailedWrapper(e)
1095
        except cmdutil.CantDetermineVersion, e:
1096
            raise CommandFailedWrapper(e)
1097
        if options.skip is not None:
1098
            iter = cmdutil.iter_skip(iter, int(options.skip))
1099
1100
        try:
1101
            for revision in iter:
1102
                log = None
1103
                if isinstance(revision, arch.Patchlog):
1104
                    log = revision
1105
                    revision=revision.revision
1106
                out = options.display(revision)
1107
                if out is not None:
1108
                    print out
1109
                if log is None and (options.summary or options.creator or 
1110
                                    options.date or options.merges):
1111
                    log = revision.patchlog
1112
                if options.creator:
1113
                    print "    %s" % log.creator
1114
                if options.date:
1115
                    print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
1116
                if options.summary:
1117
                    print "    %s" % log.summary
1118
                if options.merges:
1119
                    showed_title = False
1120
                    for revision in log.merged_patches:
1121
                        if not showed_title:
1122
                            print "    Merged:"
1123
                            showed_title = True
1124
                        print "    %s" % revision
1125
            if len(self.cl_revisions) > 0:
1126
                print pylon.changelog_for_merge(self.cl_revisions)
1127
        except pylon.errors.TreeRootNone:
1128
            raise CommandFailedWrapper(
1129
                Exception("This option can only be used in a project tree."))
1130
1131
    def changelog_append(self, revision):
1132
        if isinstance(revision, arch.Revision):
1133
            revision=arch.Patchlog(revision)
1134
        self.cl_revisions.append(revision)
1135
   
1136
    def get_parser(self):
1137
        """
1138
        Returns the options parser to use for the "revision" command.
1139
1140
        :rtype: cmdutil.CmdOptionParser
1141
        """
1142
        parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
1143
        select = cmdutil.OptionGroup(parser, "Selection options",
1144
                          "Control which revisions are listed.  These options"
1145
                          " are mutually exclusive.  If more than one is"
1146
                          " specified, the last is used.")
1147
1148
        cmdutil.add_revision_iter_options(select)
1149
        parser.add_option("", "--skip", dest="skip", 
1150
                          help="Skip revisions.  Positive numbers skip from "
1151
                          "beginning, negative skip from end.",
1152
                          metavar="NUMBER")
1153
1154
        parser.add_option_group(select)
1155
1156
        format = cmdutil.OptionGroup(parser, "Revision format options",
1157
                          "These control the appearance of listed revisions")
1158
        format.add_option("", "--location", action="store_const", 
1159
                         const=paths.determine_path, dest="display", 
1160
                         help="Show location instead of name", default=str)
1161
        format.add_option("--import", action="store_const", 
1162
                         const=paths.determine_import_path, dest="display",  
1163
                         help="Show location of import file")
1164
        format.add_option("--log", action="store_const", 
1165
                         const=paths.determine_log_path, dest="display", 
1166
                         help="Show location of log file")
1167
        format.add_option("--patch", action="store_const", 
1168
                         dest="display", const=paths.determine_patch_path,
1169
                         help="Show location of patchfile")
1170
        format.add_option("--continuation", action="store_const", 
1171
                         const=paths.determine_continuation_path, 
1172
                         dest="display",
1173
                         help="Show location of continuation file")
1174
        format.add_option("--cacherev", action="store_const", 
1175
                         const=paths.determine_cacherev_path, dest="display",
1176
                         help="Show location of cacherev file")
1177
        format.add_option("--changelog", action="store_const", 
1178
                         const=self.changelog_append, dest="display",
1179
                         help="Show location of cacherev file")
1180
        parser.add_option_group(format)
1181
        display = cmdutil.OptionGroup(parser, "Display format options",
1182
                          "These control the display of data")
1183
        display.add_option("-r", "--reverse", action="store_true", 
1184
                          dest="reverse", help="Sort from newest to oldest")
1185
        display.add_option("-s", "--summary", action="store_true", 
1186
                          dest="summary", help="Show patchlog summary")
1187
        display.add_option("-D", "--date", action="store_true", 
1188
                          dest="date", help="Show patchlog date")
1189
        display.add_option("-c", "--creator", action="store_true", 
1190
                          dest="creator", help="Show the id that committed the"
1191
                          " revision")
1192
        display.add_option("-m", "--merges", action="store_true", 
1193
                          dest="merges", help="Show the revisions that were"
1194
                          " merged")
1195
        parser.add_option_group(display)
1196
        return parser 
1197
    def help(self, parser=None):
1198
        """Attempt to explain the revisions command
1199
        
1200
        :param parser: If supplied, used to determine options
1201
        """
1202
        if parser==None:
1203
            parser=self.get_parser()
1204
        parser.print_help()
1205
        print """List revisions.
1206
        """
1207
        help_tree_spec()
1208
1209
1210
class Get(BaseCommand):
1211
    """
1212
    Retrieve a revision from the archive
1213
    """
1214
    def __init__(self):
1215
        self.description="Retrieve a revision from the archive"
1216
        self.parser=self.get_parser()
1217
1218
1219
    def get_completer(self, arg, index):
1220
        if index > 0:
1221
            return None
1222
        try:
1223
            tree = arch.tree_root()
1224
        except:
1225
            tree = None
1226
        return cmdutil.iter_revision_completions(arg, tree)
1227
1228
1229
    def do_command(self, cmdargs):
1230
        """
1231
        Master function that perfoms the "get" command.
1232
        """
1233
        (options, args) = self.parser.parse_args(cmdargs)
1234
        if len(args) < 1:
1235
            return self.help()            
1236
        try:
1237
            tree = arch.tree_root()
1238
        except arch.errors.TreeRootError:
1239
            tree = None
1240
        
1241
        arch_loc = None
1242
        try:
1243
            revision, arch_loc = paths.full_path_decode(args[0])
1244
        except Exception, e:
1245
            revision = cmdutil.determine_revision_arch(tree, args[0], 
1246
                check_existence=False, allow_package=True)
1247
        if len(args) > 1:
1248
            directory = args[1]
1249
        else:
1250
            directory = str(revision.nonarch)
1251
        if os.path.exists(directory):
1252
            raise DirectoryExists(directory)
1253
        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
1254
        try:
1255
            cmdutil.ensure_revision_exists(revision)
1256
        except cmdutil.NoSuchRevision, e:
1257
            raise CommandFailedWrapper(e)
1258
1259
        link = cmdutil.prompt ("get link")
1260
        for line in cmdutil.iter_get(revision, directory, link,
1261
                                     options.no_pristine,
1262
                                     options.no_greedy_add):
1263
            cmdutil.colorize(line)
1264
1265
    def get_parser(self):
1266
        """
1267
        Returns the options parser to use for the "get" command.
1268
1269
        :rtype: cmdutil.CmdOptionParser
1270
        """
1271
        parser=cmdutil.CmdOptionParser("fai get revision [dir]")
1272
        parser.add_option("--no-pristine", action="store_true", 
1273
                         dest="no_pristine", 
1274
                         help="Do not make pristine copy for reference")
1275
        parser.add_option("--no-greedy-add", action="store_true", 
1276
                         dest="no_greedy_add", 
1277
                         help="Never add to greedy libraries")
1278
1279
        return parser 
1280
1281
    def help(self, parser=None):
1282
        """
1283
        Prints a help message.
1284
1285
        :param parser: If supplied, the parser to use for generating help.  If \
1286
        not supplied, it is retrieved.
1287
        :type parser: cmdutil.CmdOptionParser
1288
        """
1289
        if parser==None:
1290
            parser=self.get_parser()
1291
        parser.print_help()
1292
        print """
1293
Expands aliases and constructs a project tree for a revision.  If the optional
1294
"dir" argument is provided, the project tree will be stored in this directory.
1295
        """
1296
        help_tree_spec()
1297
        return
1298
1299
class PromptCmd(cmd.Cmd):
1300
    def __init__(self):
1301
        cmd.Cmd.__init__(self)
1302
        self.prompt = "Fai> "
1303
        try:
1304
            self.tree = arch.tree_root()
1305
        except:
1306
            self.tree = None
1307
        self.set_title()
1308
        self.set_prompt()
1309
        self.fake_aba = abacmds.AbaCmds()
1310
        self.identchars += '-'
1311
        self.history_file = os.path.expanduser("~/.fai-history")
1312
        readline.set_completer_delims(string.whitespace)
1313
        if os.access(self.history_file, os.R_OK) and \
1314
            os.path.isfile(self.history_file):
1315
            readline.read_history_file(self.history_file)
1316
        self.cwd = os.getcwd()
1317
1318
    def write_history(self):
1319
        readline.write_history_file(self.history_file)
1320
1321
    def do_quit(self, args):
1322
        self.write_history()
1323
        sys.exit(0)
1324
1325
    def do_exit(self, args):
1326
        self.do_quit(args)
1327
1328
    def do_EOF(self, args):
1329
        print
1330
        self.do_quit(args)
1331
1332
    def postcmd(self, line, bar):
1333
        self.set_title()
1334
        self.set_prompt()
1335
1336
    def set_prompt(self):
1337
        if self.tree is not None:
1338
            try:
1339
                prompt = pylon.alias_or_version(self.tree.tree_version, 
1340
                                                self.tree, 
1341
                                                full=False)
1342
                if prompt is not None:
1343
                    prompt = " " + prompt
1344
            except:
1345
                prompt = ""
1346
        else:
1347
            prompt = ""
1348
        self.prompt = "Fai%s> " % prompt
1349
1350
    def set_title(self, command=None):
1351
        try:
1352
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
1353
                                             full=False)
1354
        except:
1355
            version = "[no version]"
1356
        if command is None:
1357
            command = ""
1358
        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
1359
1360
    def do_cd(self, line):
1361
        if line == "":
1362
            line = "~"
1363
        line = os.path.expanduser(line)
1364
        if os.path.isabs(line):
1365
            newcwd = line
1366
        else:
1367
            newcwd = self.cwd+'/'+line
1368
        newcwd = os.path.normpath(newcwd)
1369
        try:
1370
            os.chdir(newcwd)
1371
            self.cwd = newcwd
1372
        except Exception, e:
1373
            print e
1374
        try:
1375
            self.tree = arch.tree_root()
1376
        except:
1377
            self.tree = None
1378
1379
    def do_help(self, line):
1380
        Help()(line)
1381
1382
    def default(self, line):
1383
        args = line.split()
1384
        if find_command(args[0]):
1385
            try:
1386
                find_command(args[0]).do_command(args[1:])
1387
            except cmdutil.BadCommandOption, e:
1388
                print e
1389
            except cmdutil.GetHelp, e:
1390
                find_command(args[0]).help()
1391
            except CommandFailed, e:
1392
                print e
1393
            except arch.errors.ArchiveNotRegistered, e:
1394
                print e
1395
            except KeyboardInterrupt, e:
1396
                print "Interrupted"
1397
            except arch.util.ExecProblem, e:
1398
                print e.proc.error.rstrip('\n')
1399
            except cmdutil.CantDetermineVersion, e:
1400
                print e
1401
            except cmdutil.CantDetermineRevision, e:
1402
                print e
1403
            except Exception, e:
1404
                print "Unhandled error:\n%s" % errors.exception_str(e)
1405
1406
        elif suggestions.has_key(args[0]):
1407
            print suggestions[args[0]]
1408
1409
        elif self.fake_aba.is_command(args[0]):
1410
            tree = None
1411
            try:
1412
                tree = arch.tree_root()
1413
            except arch.errors.TreeRootError:
1414
                pass
1415
            cmd = self.fake_aba.is_command(args[0])
1416
            try:
1417
                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
1418
            except KeyboardInterrupt, e:
1419
                print "Interrupted"
1420
1421
        elif options.tla_fallthrough and args[0] != "rm" and \
1422
            cmdutil.is_tla_command(args[0]):
1423
            try:
1424
                tree = None
1425
                try:
1426
                    tree = arch.tree_root()
1427
                except arch.errors.TreeRootError:
1428
                    pass
1429
                args = cmdutil.expand_prefix_alias(args, tree)
1430
                arch.util.exec_safe('tla', args, stderr=sys.stderr,
1431
                expected=(0, 1))
1432
            except arch.util.ExecProblem, e:
1433
                pass
1434
            except KeyboardInterrupt, e:
1435
                print "Interrupted"
1436
        else:
1437
            try:
1438
                try:
1439
                    tree = arch.tree_root()
1440
                except arch.errors.TreeRootError:
1441
                    tree = None
1442
                args=line.split()
1443
                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
1444
            except KeyboardInterrupt, e:
1445
                print "Interrupted"
1446
1447
    def completenames(self, text, line, begidx, endidx):
1448
        completions = []
1449
        iter = iter_command_names(self.fake_aba)
1450
        try:
1451
            if len(line) > 0:
1452
                arg = line.split()[-1]
1453
            else:
1454
                arg = ""
1455
            iter = cmdutil.iter_munged_completions(iter, arg, text)
1456
        except Exception, e:
1457
            print e
1458
        return list(iter)
1459
1460
    def completedefault(self, text, line, begidx, endidx):
1461
        """Perform completion for native commands.
1462
        
1463
        :param text: The text to complete
1464
        :type text: str
1465
        :param line: The entire line to complete
1466
        :type line: str
1467
        :param begidx: The start of the text in the line
1468
        :type begidx: int
1469
        :param endidx: The end of the text in the line
1470
        :type endidx: int
1471
        """
1472
        try:
1473
            (cmd, args, foo) = self.parseline(line)
1474
            command_obj=find_command(cmd)
1475
            if command_obj is not None:
1476
                return command_obj.complete(args.split(), text)
1477
            elif not self.fake_aba.is_command(cmd) and \
1478
                cmdutil.is_tla_command(cmd):
1479
                iter = cmdutil.iter_supported_switches(cmd)
1480
                if len(args) > 0:
1481
                    arg = args.split()[-1]
1482
                else:
1483
                    arg = ""
1484
                if arg.startswith("-"):
1485
                    return list(cmdutil.iter_munged_completions(iter, arg, 
1486
                                                                text))
1487
                else:
1488
                    return list(cmdutil.iter_munged_completions(
1489
                        cmdutil.iter_file_completions(arg), arg, text))
1490
1491
1492
            elif cmd == "cd":
1493
                if len(args) > 0:
1494
                    arg = args.split()[-1]
1495
                else:
1496
                    arg = ""
1497
                iter = cmdutil.iter_dir_completions(arg)
1498
                iter = cmdutil.iter_munged_completions(iter, arg, text)
1499
                return list(iter)
1500
            elif len(args)>0:
1501
                arg = args.split()[-1]
1502
                iter = cmdutil.iter_file_completions(arg)
1503
                return list(cmdutil.iter_munged_completions(iter, arg, text))
1504
            else:
1505
                return self.completenames(text, line, begidx, endidx)
1506
        except Exception, e:
1507
            print e
1508
1509
1510
def iter_command_names(fake_aba):
1511
    for entry in cmdutil.iter_combine([commands.iterkeys(), 
1512
                                     fake_aba.get_commands(), 
1513
                                     cmdutil.iter_tla_commands(False)]):
1514
        if not suggestions.has_key(str(entry)):
1515
            yield entry
1516
1517
1518
def iter_source_file_completions(tree, arg):
1519
    treepath = arch_compound.tree_cwd(tree)
1520
    if len(treepath) > 0:
1521
        dirs = [treepath]
1522
    else:
1523
        dirs = None
1524
    for file in tree.iter_inventory(dirs, source=True, both=True):
1525
        file = file_completion_match(file, treepath, arg)
1526
        if file is not None:
1527
            yield file
1528
1529
1530
def iter_untagged(tree, dirs):
1531
    for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False, 
1532
                                                categories=arch_core.non_root,
1533
                                                control_files=True):
1534
        yield file.name 
1535
1536
1537
def iter_untagged_completions(tree, arg):
1538
    """Generate an iterator for all visible untagged files that match arg.
1539
1540
    :param tree: The tree to look for untagged files in
1541
    :type tree: `arch.WorkingTree`
1542
    :param arg: The argument to match
1543
    :type arg: str
1544
    :return: An iterator of all matching untagged files
1545
    :rtype: iterator of str
1546
    """
1547
    treepath = arch_compound.tree_cwd(tree)
1548
    if len(treepath) > 0:
1549
        dirs = [treepath]
1550
    else:
1551
        dirs = None
1552
1553
    for file in iter_untagged(tree, dirs):
1554
        file = file_completion_match(file, treepath, arg)
1555
        if file is not None:
1556
            yield file
1557
1558
1559
def file_completion_match(file, treepath, arg):
1560
    """Determines whether a file within an arch tree matches the argument.
1561
1562
    :param file: The rooted filename
1563
    :type file: str
1564
    :param treepath: The path to the cwd within the tree
1565
    :type treepath: str
1566
    :param arg: The prefix to match
1567
    :return: The completion name, or None if not a match
1568
    :rtype: str
1569
    """
1570
    if not file.startswith(treepath):
1571
        return None
1572
    if treepath != "":
1573
        file = file[len(treepath)+1:]
1574
1575
    if not file.startswith(arg):
1576
        return None 
1577
    if os.path.isdir(file):
1578
        file += '/'
1579
    return file
1580
1581
def iter_modified_file_completions(tree, arg):
1582
    """Returns a list of modified files that match the specified prefix.
1583
1584
    :param tree: The current tree
1585
    :type tree: `arch.WorkingTree`
1586
    :param arg: The prefix to match
1587
    :type arg: str
1588
    """
1589
    treepath = arch_compound.tree_cwd(tree)
1590
    tmpdir = util.tmpdir()
1591
    changeset = tmpdir+"/changeset"
1592
    completions = []
1593
    revision = cmdutil.determine_revision_tree(tree)
1594
    for line in arch.iter_delta(revision, tree, changeset):
1595
        if isinstance(line, arch.FileModification):
1596
            file = file_completion_match(line.name[1:], treepath, arg)
1597
            if file is not None:
1598
                completions.append(file)
1599
    shutil.rmtree(tmpdir)
1600
    return completions
1601
1602
class Shell(BaseCommand):
1603
    def __init__(self):
1604
        self.description = "Runs Fai as a shell"
1605
1606
    def do_command(self, cmdargs):
1607
        if len(cmdargs)!=0:
1608
            raise cmdutil.GetHelp
1609
        prompt = PromptCmd()
1610
        try:
1611
            prompt.cmdloop()
1612
        finally:
1613
            prompt.write_history()
1614
1615
class AddID(BaseCommand):
1616
    """
1617
    Adds an inventory id for the given file
1618
    """
1619
    def __init__(self):
1620
        self.description="Add an inventory id for a given file"
1621
1622
    def get_completer(self, arg, index):
1623
        tree = arch.tree_root()
1624
        return iter_untagged_completions(tree, arg)
1625
1626
    def do_command(self, cmdargs):
1627
        """
1628
        Master function that perfoms the "revision" command.
1629
        """
1630
        parser=self.get_parser()
1631
        (options, args) = parser.parse_args(cmdargs)
1632
1633
        try:
1634
            tree = arch.tree_root()
1635
        except arch.errors.TreeRootError, e:
1636
            raise pylon.errors.CommandFailedWrapper(e)
1637
            
1638
1639
        if (len(args) == 0) == (options.untagged == False):
1640
            raise cmdutil.GetHelp
1641
1642
       #if options.id and len(args) != 1:
1643
       #    print "If --id is specified, only one file can be named."
1644
       #    return
1645
        
1646
        method = tree.tagging_method
1647
        
1648
        if options.id_type == "tagline":
1649
            if method != "tagline":
1650
                if not cmdutil.prompt("Tagline in other tree"):
1651
                    if method == "explicit" or method == "implicit":
1652
                        options.id_type == method
1653
                    else:
1654
                        print "add-id not supported for \"%s\" tagging method"\
1655
                            % method 
1656
                        return
1657
        
1658
        elif options.id_type == "implicit":
1659
            if method != "implicit":
1660
                if not cmdutil.prompt("Implicit in other tree"):
1661
                    if method == "explicit" or method == "tagline":
1662
                        options.id_type == method
1663
                    else:
1664
                        print "add-id not supported for \"%s\" tagging method"\
1665
                            % method 
1666
                        return
1667
        elif options.id_type == "explicit":
1668
            if method != "tagline" and method != explicit:
1669
                if not prompt("Explicit in other tree"):
1670
                    print "add-id not supported for \"%s\" tagging method" % \
1671
                        method
1672
                    return
1673
        
1674
        if options.id_type == "auto":
1675
            if method != "tagline" and method != "explicit" \
1676
                and method !="implicit":
1677
                print "add-id not supported for \"%s\" tagging method" % method
1678
                return
1679
            else:
1680
                options.id_type = method
1681
        if options.untagged:
1682
            args = None
1683
        self.add_ids(tree, options.id_type, args)
1684
1685
    def add_ids(self, tree, id_type, files=()):
1686
        """Add inventory ids to files.
1687
        
1688
        :param tree: the tree the files are in
1689
        :type tree: `arch.WorkingTree`
1690
        :param id_type: the type of id to add: "explicit" or "tagline"
1691
        :type id_type: str
1692
        :param files: The list of files to add.  If None do all untagged.
1693
        :type files: tuple of str
1694
        """
1695
1696
        untagged = (files is None)
1697
        if untagged:
1698
            files = list(iter_untagged(tree, None))
1699
        previous_files = []
1700
        while len(files) > 0:
1701
            previous_files.extend(files)
1702
            if id_type == "explicit":
1703
                cmdutil.add_id(files)
1704
            elif id_type == "tagline" or id_type == "implicit":
1705
                for file in files:
1706
                    try:
1707
                        implicit = (id_type == "implicit")
1708
                        cmdutil.add_tagline_or_explicit_id(file, False,
1709
                                                           implicit)
1710
                    except cmdutil.AlreadyTagged:
1711
                        print "\"%s\" already has a tagline." % file
1712
                    except cmdutil.NoCommentSyntax:
1713
                        pass
1714
            #do inventory after tagging until no untagged files are encountered
1715
            if untagged:
1716
                files = []
1717
                for file in iter_untagged(tree, None):
1718
                    if not file in previous_files:
1719
                        files.append(file)
1720
1721
            else:
1722
                break
1723
1724
    def get_parser(self):
1725
        """
1726
        Returns the options parser to use for the "revision" command.
1727
1728
        :rtype: cmdutil.CmdOptionParser
1729
        """
1730
        parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
1731
# ddaa suggests removing this to promote GUIDs.  Let's see who squalks.
1732
#        parser.add_option("-i", "--id", dest="id", 
1733
#                         help="Specify id for a single file", default=None)
1734
        parser.add_option("--tltl", action="store_true", 
1735
                         dest="lord_style",  help="Use Tom Lord's style of id.")
1736
        parser.add_option("--explicit", action="store_const", 
1737
                         const="explicit", dest="id_type", 
1738
                         help="Use an explicit id", default="auto")
1739
        parser.add_option("--tagline", action="store_const", 
1740
                         const="tagline", dest="id_type", 
1741
                         help="Use a tagline id")
1742
        parser.add_option("--implicit", action="store_const", 
1743
                         const="implicit", dest="id_type", 
1744
                         help="Use an implicit id (deprecated)")
1745
        parser.add_option("--untagged", action="store_true", 
1746
                         dest="untagged", default=False, 
1747
                         help="tag all untagged files")
1748
        return parser 
1749
1750
    def help(self, parser=None):
1751
        """
1752
        Prints a help message.
1753
1754
        :param parser: If supplied, the parser to use for generating help.  If \
1755
        not supplied, it is retrieved.
1756
        :type parser: cmdutil.CmdOptionParser
1757
        """
1758
        if parser==None:
1759
            parser=self.get_parser()
1760
        parser.print_help()
1761
        print """
1762
Adds an inventory to the specified file(s) and directories.  If --untagged is
1763
specified, adds inventory to all untagged files and directories.
1764
        """
1765
        return
1766
1767
1768
class Merge(BaseCommand):
1769
    """
1770
    Merges changes from other versions into the current tree
1771
    """
1772
    def __init__(self):
1773
        self.description="Merges changes from other versions"
1774
        try:
1775
            self.tree = arch.tree_root()
1776
        except:
1777
            self.tree = None
1778
1779
1780
    def get_completer(self, arg, index):
1781
        if self.tree is None:
1782
            raise arch.errors.TreeRootError
1783
        return cmdutil.merge_completions(self.tree, arg, index)
1784
1785
    def do_command(self, cmdargs):
1786
        """
1787
        Master function that perfoms the "merge" command.
1788
        """
1789
        parser=self.get_parser()
1790
        (options, args) = parser.parse_args(cmdargs)
1791
        if options.diff3:
1792
            action="star-merge"
1793
        else:
1794
            action = options.action
1795
        
1796
        if self.tree is None:
1797
            raise arch.errors.TreeRootError(os.getcwd())
1798
        if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
1799
            raise UncommittedChanges(self.tree)
1800
1801
        if len(args) > 0:
1802
            revisions = []
1803
            for arg in args:
1804
                revisions.append(cmdutil.determine_revision_arch(self.tree, 
1805
                                                                 arg))
1806
            source = "from commandline"
1807
        else:
1808
            revisions = ancillary.iter_partner_revisions(self.tree, 
1809
                                                         self.tree.tree_version)
1810
            source = "from partner version"
1811
        revisions = misc.rewind_iterator(revisions)
1812
        try:
1813
            revisions.next()
1814
            revisions.rewind()
1815
        except StopIteration, e:
1816
            revision = cmdutil.tag_cur(self.tree)
1817
            if revision is None:
1818
                raise CantDetermineRevision("", "No version specified, no "
1819
                                            "partner-versions, and no tag"
1820
                                            " source")
1821
            revisions = [revision]
1822
            source = "from tag source"
1823
        for revision in revisions:
1824
            cmdutil.ensure_archive_registered(revision.archive)
1825
            cmdutil.colorize(arch.Chatter("* Merging %s [%s]" % 
1826
                             (revision, source)))
1827
            if action=="native-merge" or action=="update":
1828
                if self.native_merge(revision, action) == 0:
1829
                    continue
1830
            elif action=="star-merge":
1831
                try: 
1832
                    self.star_merge(revision, options.diff3)
1833
                except errors.MergeProblem, e:
1834
                    break
1835
            if cmdutil.has_changed(self.tree.tree_version):
1836
                break
1837
1838
    def star_merge(self, revision, diff3):
1839
        """Perform a star-merge on the current tree.
1840
        
1841
        :param revision: The revision to use for the merge
1842
        :type revision: `arch.Revision`
1843
        :param diff3: If true, do a diff3 merge
1844
        :type diff3: bool
1845
        """
1846
        try:
1847
            for line in self.tree.iter_star_merge(revision, diff3=diff3):
1848
                cmdutil.colorize(line)
1849
        except arch.util.ExecProblem, e:
1850
            if e.proc.status is not None and e.proc.status == 1:
1851
                if e.proc.error:
1852
                    print e.proc.error
1853
                raise MergeProblem
1854
            else:
1855
                raise
1856
1857
    def native_merge(self, other_revision, action):
1858
        """Perform a native-merge on the current tree.
1859
        
1860
        :param other_revision: The revision to use for the merge
1861
        :type other_revision: `arch.Revision`
1862
        :return: 0 if the merge was skipped, 1 if it was applied
1863
        """
1864
        other_tree = arch_compound.find_or_make_local_revision(other_revision)
1865
        try:
1866
            if action == "native-merge":
1867
                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
1868
                                                         other_revision)
1869
            elif action == "update":
1870
                ancestor = arch_compound.tree_latest(self.tree, 
1871
                                                     other_revision.version)
1872
        except CantDetermineRevision, e:
1873
            raise CommandFailedWrapper(e)
1874
        cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
1875
        if (ancestor == other_revision):
1876
            cmdutil.colorize(arch.Chatter("* Skipping redundant merge" 
1877
                                          % ancestor))
1878
            return 0
1879
        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
1880
        for line in cmdutil.iter_apply_delta_filter(delta):
1881
            cmdutil.colorize(line)
1882
        return 1
1883
1884
1885
1886
    def get_parser(self):
1887
        """
1888
        Returns the options parser to use for the "merge" command.
1889
1890
        :rtype: cmdutil.CmdOptionParser
1891
        """
1892
        parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
1893
        parser.add_option("-s", "--star-merge", action="store_const",
1894
                          dest="action", help="Use star-merge",
1895
                          const="star-merge", default="native-merge")
1896
        parser.add_option("--update", action="store_const",
1897
                          dest="action", help="Use update picker",
1898
                          const="update")
1899
        parser.add_option("--diff3", action="store_true", 
1900
                         dest="diff3",  
1901
                         help="Use diff3 for merge (implies star-merge)")
1902
        return parser 
1903
1904
    def help(self, parser=None):
1905
        """
1906
        Prints a help message.
1907
1908
        :param parser: If supplied, the parser to use for generating help.  If \
1909
        not supplied, it is retrieved.
1910
        :type parser: cmdutil.CmdOptionParser
1911
        """
1912
        if parser==None:
1913
            parser=self.get_parser()
1914
        parser.print_help()
1915
        print """
1916
Performs a merge operation using the specified version.
1917
        """
1918
        return
1919
1920
class ELog(BaseCommand):
1921
    """
1922
    Produces a raw patchlog and invokes the user's editor
1923
    """
1924
    def __init__(self):
1925
        self.description="Edit a patchlog to commit"
1926
        try:
1927
            self.tree = arch.tree_root()
1928
        except:
1929
            self.tree = None
1930
1931
1932
    def do_command(self, cmdargs):
1933
        """
1934
        Master function that perfoms the "elog" command.
1935
        """
1936
        parser=self.get_parser()
1937
        (options, args) = parser.parse_args(cmdargs)
1938
        if self.tree is None:
1939
            raise arch.errors.TreeRootError
1940
1941
        try:
1942
            edit_log(self.tree, self.tree.tree_version)
1943
        except pylon.errors.NoEditorSpecified, e:
1944
            raise pylon.errors.CommandFailedWrapper(e)
1945
1946
    def get_parser(self):
1947
        """
1948
        Returns the options parser to use for the "merge" command.
1949
1950
        :rtype: cmdutil.CmdOptionParser
1951
        """
1952
        parser=cmdutil.CmdOptionParser("fai elog")
1953
        return parser 
1954
1955
1956
    def help(self, parser=None):
1957
        """
1958
        Invokes $EDITOR to produce a log for committing.
1959
1960
        :param parser: If supplied, the parser to use for generating help.  If \
1961
        not supplied, it is retrieved.
1962
        :type parser: cmdutil.CmdOptionParser
1963
        """
1964
        if parser==None:
1965
            parser=self.get_parser()
1966
        parser.print_help()
1967
        print """
1968
Invokes $EDITOR to produce a log for committing.
1969
        """
1970
        return
1971
1972
def edit_log(tree, version):
1973
    """Makes and edits the log for a tree.  Does all kinds of fancy things
1974
    like log templates and merge summaries and log-for-merge
1975
    
1976
    :param tree: The tree to edit the log for
1977
    :type tree: `arch.WorkingTree`
1978
    """
1979
    #ensure we have an editor before preparing the log
1980
    cmdutil.find_editor()
1981
    log = tree.log_message(create=False, version=version)
1982
    log_is_new = False
1983
    if log is None or cmdutil.prompt("Overwrite log"):
1984
        if log is not None:
1985
           os.remove(log.name)
1986
        log = tree.log_message(create=True, version=version)
1987
        log_is_new = True
1988
        tmplog = log.name
1989
        template = pylon.log_template_path(tree)
1990
        if template:
1991
            shutil.copyfile(template, tmplog)
1992
        comp_version = ancillary.comp_revision(tree).version
1993
        new_merges = cmdutil.iter_new_merges(tree, comp_version)
1994
        new_merges = cmdutil.direct_merges(new_merges)
1995
        log["Summary"] = pylon.merge_summary(new_merges, 
1996
                                         version)
1997
        if len(new_merges) > 0:   
1998
            if cmdutil.prompt("Log for merge"):
1999
                if cmdutil.prompt("changelog for merge"):
2000
                    mergestuff = "Patches applied:\n"
2001
                    mergestuff += pylon.changelog_for_merge(new_merges)
2002
                else:
2003
                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
2004
                log.description += mergestuff
2005
        log.save()
2006
    try:
2007
        cmdutil.invoke_editor(log.name)
2008
    except:
2009
        if log_is_new:
2010
            os.remove(log.name)
2011
        raise
2012
2013
2014
class MirrorArchive(BaseCommand):
2015
    """
2016
    Updates a mirror from an archive
2017
    """
2018
    def __init__(self):
2019
        self.description="Update a mirror from an archive"
2020
2021
    def do_command(self, cmdargs):
2022
        """
2023
        Master function that perfoms the "revision" command.
2024
        """
2025
2026
        parser=self.get_parser()
2027
        (options, args) = parser.parse_args(cmdargs)
2028
        if len(args) > 1:
2029
            raise GetHelp
2030
        try:
2031
            tree = arch.tree_root()
2032
        except:
2033
            tree = None
2034
2035
        if len(args) == 0:
2036
            if tree is not None:
2037
                name = tree.tree_version()
2038
        else:
2039
            name = cmdutil.expand_alias(args[0], tree)
2040
            name = arch.NameParser(name)
2041
2042
        to_arch = name.get_archive()
2043
        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
2044
        limit = name.get_nonarch()
2045
2046
        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
2047
        for line in arch.chatter_classifier(iter):
2048
            cmdutil.colorize(line)
2049
2050
    def get_parser(self):
2051
        """
2052
        Returns the options parser to use for the "revision" command.
2053
2054
        :rtype: cmdutil.CmdOptionParser
2055
        """
2056
        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
2057
        return parser 
2058
2059
    def help(self, parser=None):
2060
        """
2061
        Prints a help message.
2062
2063
        :param parser: If supplied, the parser to use for generating help.  If \
2064
        not supplied, it is retrieved.
2065
        :type parser: cmdutil.CmdOptionParser
2066
        """
2067
        if parser==None:
2068
            parser=self.get_parser()
2069
        parser.print_help()
2070
        print """
2071
Updates a mirror from an archive.  If a branch, package, or version is
2072
supplied, only changes under it are mirrored.
2073
        """
2074
        return
2075
2076
def help_tree_spec():
2077
    print """Specifying revisions (default: tree)
2078
Revisions may be specified by alias, revision, version or patchlevel.
2079
Revisions or versions may be fully qualified.  Unqualified revisions, versions, 
2080
or patchlevels use the archive of the current project tree.  Versions will
2081
use the latest patchlevel in the tree.  Patchlevels will use the current tree-
2082
version.
2083
2084
Use "alias" to list available (user and automatic) aliases."""
2085
2086
auto_alias = [
2087
"acur", 
2088
"The latest revision in the archive of the tree-version.  You can specify \
2089
a different version like so: acur:foo--bar--0 (aliases can be used)",
2090
"tcur",
2091
"""(tree current) The latest revision in the tree of the tree-version. \
2092
You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
2093
used).""",
2094
"tprev" , 
2095
"""(tree previous) The previous revision in the tree of the tree-version.  To \
2096
specify an older revision, use a number, e.g. "tprev:4" """,
2097
"tanc" , 
2098
"""(tree ancestor) The ancestor revision of the tree To specify an older \
2099
revision, use a number, e.g. "tanc:4".""",
2100
"tdate" , 
2101
"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
2102
"tmod" , 
2103
""" (tree modified) The latest revision to modify a given file, e.g. \
2104
"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
2105
"ttag" , 
2106
"""(tree tag) The revision that was tagged into the current tree revision, \
2107
according to the tree""",
2108
"tagcur", 
2109
"""(tag current) The latest revision of the version that the current tree \
2110
was tagged from.""",
2111
"mergeanc" , 
2112
"""The common ancestor of the current tree and the specified revision. \
2113
Defaults to the first partner-version's latest revision or to tagcur.""",
2114
]
2115
2116
2117
def is_auto_alias(name):
2118
    """Determine whether a name is an auto alias name
2119
2120
    :param name: the name to check
2121
    :type name: str
2122
    :return: True if the name is an auto alias, false if not
2123
    :rtype: bool
2124
    """
2125
    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
2126
2127
2128
def display_def(iter, wrap = 80):
2129
    """Display a list of definitions
2130
2131
    :param iter: iter of name, definition pairs
2132
    :type iter: iter of (str, str)
2133
    :param wrap: The width for text wrapping
2134
    :type wrap: int
2135
    """
2136
    vals = list(iter)
2137
    maxlen = 0
2138
    for (key, value) in vals:
2139
        if len(key) > maxlen:
2140
            maxlen = len(key)
2141
    for (key, value) in vals:
2142
        tw=textwrap.TextWrapper(width=wrap, 
2143
                                initial_indent=key.rjust(maxlen)+" : ",
2144
                                subsequent_indent="".rjust(maxlen+3))
2145
        print tw.fill(value)
2146
2147
2148
def help_aliases(tree):
2149
    print """Auto-generated aliases"""
2150
    display_def(pylon.util.iter_pairs(auto_alias))
2151
    print "User aliases"
2152
    display_def(ancillary.iter_all_alias(tree))
2153
2154
class Inventory(BaseCommand):
2155
    """List the status of files in the tree"""
2156
    def __init__(self):
2157
        self.description=self.__doc__
2158
2159
    def do_command(self, cmdargs):
2160
        """
2161
        Master function that perfoms the "revision" command.
2162
        """
2163
2164
        parser=self.get_parser()
2165
        (options, args) = parser.parse_args(cmdargs)
2166
        tree = arch.tree_root()
2167
        categories = []
2168
2169
        if (options.source):
2170
            categories.append(arch_core.SourceFile)
2171
        if (options.precious):
2172
            categories.append(arch_core.PreciousFile)
2173
        if (options.backup):
2174
            categories.append(arch_core.BackupFile)
2175
        if (options.junk):
2176
            categories.append(arch_core.JunkFile)
2177
2178
        if len(categories) == 1:
2179
            show_leading = False
2180
        else:
2181
            show_leading = True
2182
2183
        if len(categories) == 0:
2184
            categories = None
2185
2186
        if options.untagged:
2187
            categories = arch_core.non_root
2188
            show_leading = False
2189
            tagged = False
2190
        else:
2191
            tagged = None
2192
        
2193
        for file in arch_core.iter_inventory_filter(tree, None, 
2194
            control_files=options.control_files, 
2195
            categories = categories, tagged=tagged):
2196
            print arch_core.file_line(file, 
2197
                                      category = show_leading, 
2198
                                      untagged = show_leading,
2199
                                      id = options.ids)
2200
2201
    def get_parser(self):
2202
        """
2203
        Returns the options parser to use for the "revision" command.
2204
2205
        :rtype: cmdutil.CmdOptionParser
2206
        """
2207
        parser=cmdutil.CmdOptionParser("fai inventory [options]")
2208
        parser.add_option("--ids", action="store_true", dest="ids", 
2209
                          help="Show file ids")
2210
        parser.add_option("--control", action="store_true", 
2211
                          dest="control_files", help="include control files")
2212
        parser.add_option("--source", action="store_true", dest="source",
2213
                          help="List source files")
2214
        parser.add_option("--backup", action="store_true", dest="backup",
2215
                          help="List backup files")
2216
        parser.add_option("--precious", action="store_true", dest="precious",
2217
                          help="List precious files")
2218
        parser.add_option("--junk", action="store_true", dest="junk",
2219
                          help="List junk files")
2220
        parser.add_option("--unrecognized", action="store_true", 
2221
                          dest="unrecognized", help="List unrecognized files")
2222
        parser.add_option("--untagged", action="store_true", 
2223
                          dest="untagged", help="List only untagged files")
2224
        return parser 
2225
2226
    def help(self, parser=None):
2227
        """
2228
        Prints a help message.
2229
2230
        :param parser: If supplied, the parser to use for generating help.  If \
2231
        not supplied, it is retrieved.
2232
        :type parser: cmdutil.CmdOptionParser
2233
        """
2234
        if parser==None:
2235
            parser=self.get_parser()
2236
        parser.print_help()
2237
        print """
2238
Lists the status of files in the archive:
2239
S source
2240
P precious
2241
B backup
2242
J junk
2243
U unrecognized
2244
T tree root
2245
? untagged-source
2246
Leading letter are not displayed if only one kind of file is shown
2247
        """
2248
        return
2249
2250
2251
class Alias(BaseCommand):
2252
    """List or adjust aliases"""
2253
    def __init__(self):
2254
        self.description=self.__doc__
2255
2256
    def get_completer(self, arg, index):
2257
        if index > 2:
2258
            return ()
2259
        try:
2260
            self.tree = arch.tree_root()
2261
        except:
2262
            self.tree = None
2263
2264
        if index == 0:
2265
            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
2266
        elif index == 1:
2267
            return cmdutil.iter_revision_completions(arg, self.tree)
2268
2269
2270
    def do_command(self, cmdargs):
2271
        """
2272
        Master function that perfoms the "revision" command.
2273
        """
2274
2275
        parser=self.get_parser()
2276
        (options, args) = parser.parse_args(cmdargs)
2277
        try:
2278
            self.tree =  arch.tree_root()
2279
        except:
2280
            self.tree = None
2281
2282
2283
        try:
2284
            options.action(args, options)
2285
        except cmdutil.ForbiddenAliasSyntax, e:
2286
            raise CommandFailedWrapper(e)
2287
2288
    def no_prefix(self, alias):
2289
        if alias.startswith("^"):
2290
            alias = alias[1:]
2291
        return alias
2292
        
2293
    def arg_dispatch(self, args, options):
2294
        """Add, modify, or list aliases, depending on number of arguments
2295
2296
        :param args: The list of commandline arguments
2297
        :type args: list of str
2298
        :param options: The commandline options
2299
        """
2300
        if len(args) == 0:
2301
            help_aliases(self.tree)
2302
            return
2303
        else:
2304
            alias = self.no_prefix(args[0])
2305
            if len(args) == 1:
2306
                self.print_alias(alias)
2307
            elif (len(args)) == 2:
2308
                self.add(alias, args[1], options)
2309
            else:
2310
                raise cmdutil.GetHelp
2311
2312
    def print_alias(self, alias):
2313
        answer = None
2314
        if is_auto_alias(alias):
2315
            raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
2316
                "  Use \"revision\" to expand auto aliases." % alias)
2317
        for pair in ancillary.iter_all_alias(self.tree):
2318
            if pair[0] == alias:
2319
                answer = pair[1]
2320
        if answer is not None:
2321
            print answer
2322
        else:
2323
            print "The alias %s is not assigned." % alias
2324
2325
    def add(self, alias, expansion, options):
2326
        """Add or modify aliases
2327
2328
        :param alias: The alias name to create/modify
2329
        :type alias: str
2330
        :param expansion: The expansion to assign to the alias name
2331
        :type expansion: str
2332
        :param options: The commandline options
2333
        """
2334
        if is_auto_alias(alias):
2335
            raise IsAutoAlias(alias)
2336
        newlist = ""
2337
        written = False
2338
        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
2339
            self.tree))
2340
        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
2341
2342
        for pair in self.get_iterator(options):
2343
            if pair[0] != alias:
2344
                newlist+="%s=%s\n" % (pair[0], pair[1])
2345
            elif not written:
2346
                newlist+=new_line
2347
                written = True
2348
        if not written:
2349
            newlist+=new_line
2350
        self.write_aliases(newlist, options)
2351
            
2352
    def delete(self, args, options):
2353
        """Delete the specified alias
2354
2355
        :param args: The list of arguments
2356
        :type args: list of str
2357
        :param options: The commandline options
2358
        """
2359
        deleted = False
2360
        if len(args) != 1:
2361
            raise cmdutil.GetHelp
2362
        alias = self.no_prefix(args[0])
2363
        if is_auto_alias(alias):
2364
            raise IsAutoAlias(alias)
2365
        newlist = ""
2366
        for pair in self.get_iterator(options):
2367
            if pair[0] != alias:
2368
                newlist+="%s=%s\n" % (pair[0], pair[1])
2369
            else:
2370
                deleted = True
2371
        if not deleted:
2372
            raise errors.NoSuchAlias(alias)
2373
        self.write_aliases(newlist, options)
2374
2375
    def get_alias_file(self, options):
2376
        """Return the name of the alias file to use
2377
2378
        :param options: The commandline options
2379
        """
2380
        if options.tree:
2381
            if self.tree is None:
2382
                self.tree == arch.tree_root()
2383
            return str(self.tree)+"/{arch}/+aliases"
2384
        else:
2385
            return "~/.aba/aliases"
2386
2387
    def get_iterator(self, options):
2388
        """Return the alias iterator to use
2389
2390
        :param options: The commandline options
2391
        """
2392
        return ancillary.iter_alias(self.get_alias_file(options))
2393
2394
    def write_aliases(self, newlist, options):
2395
        """Safely rewrite the alias file
2396
        :param newlist: The new list of aliases
2397
        :type newlist: str
2398
        :param options: The commandline options
2399
        """
2400
        filename = os.path.expanduser(self.get_alias_file(options))
2401
        file = util.NewFileVersion(filename)
2402
        file.write(newlist)
2403
        file.commit()
2404
2405
2406
    def get_parser(self):
2407
        """
2408
        Returns the options parser to use for the "alias" command.
2409
2410
        :rtype: cmdutil.CmdOptionParser
2411
        """
2412
        parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
2413
        parser.add_option("-d", "--delete", action="store_const", dest="action",
2414
                          const=self.delete, default=self.arg_dispatch, 
2415
                          help="Delete an alias")
2416
        parser.add_option("--tree", action="store_true", dest="tree", 
2417
                          help="Create a per-tree alias", default=False)
2418
        return parser 
2419
2420
    def help(self, parser=None):
2421
        """
2422
        Prints a help message.
2423
2424
        :param parser: If supplied, the parser to use for generating help.  If \
2425
        not supplied, it is retrieved.
2426
        :type parser: cmdutil.CmdOptionParser
2427
        """
2428
        if parser==None:
2429
            parser=self.get_parser()
2430
        parser.print_help()
2431
        print """
2432
Lists current aliases or modifies the list of aliases.
2433
2434
If no arguments are supplied, aliases will be listed.  If two arguments are
2435
supplied, the specified alias will be created or modified.  If -d or --delete
2436
is supplied, the specified alias will be deleted.
2437
2438
You can create aliases that refer to any fully-qualified part of the
2439
Arch namespace, e.g. 
2440
archive, 
2441
archive/category, 
2442
archive/category--branch, 
2443
archive/category--branch--version (my favourite)
2444
archive/category--branch--version--patchlevel
2445
2446
Aliases can be used automatically by native commands.  To use them
2447
with external or tla commands, prefix them with ^ (you can do this
2448
with native commands, too).
2449
"""
2450
2451
2452
class RequestMerge(BaseCommand):
2453
    """Submit a merge request to Bug Goo"""
2454
    def __init__(self):
2455
        self.description=self.__doc__
2456
2457
    def do_command(self, cmdargs):
2458
        """Submit a merge request
2459
2460
        :param cmdargs: The commandline arguments
2461
        :type cmdargs: list of str
2462
        """
2463
        parser = self.get_parser()
2464
        (options, args) = parser.parse_args(cmdargs)
2465
        try:
2466
            cmdutil.find_editor()
2467
        except pylon.errors.NoEditorSpecified, e:
2468
            raise pylon.errors.CommandFailedWrapper(e)
2469
        try:
2470
            self.tree=arch.tree_root()
2471
        except:
2472
            self.tree=None
2473
        base, revisions = self.revision_specs(args)
2474
        message = self.make_headers(base, revisions)
2475
        message += self.make_summary(revisions)
2476
        path = self.edit_message(message)
2477
        message = self.tidy_message(path)
2478
        if cmdutil.prompt("Send merge"):
2479
            self.send_message(message)
2480
            print "Merge request sent"
2481
2482
    def make_headers(self, base, revisions):
2483
        """Produce email and Bug Goo header strings
2484
2485
        :param base: The base revision to apply merges to
2486
        :type base: `arch.Revision`
2487
        :param revisions: The revisions to replay into the base
2488
        :type revisions: list of `arch.Patchlog`
2489
        :return: The headers
2490
        :rtype: str
2491
        """
2492
        headers = "To: gnu-arch-users@gnu.org\n"
2493
        headers += "From: %s\n" % options.fromaddr
2494
        if len(revisions) == 1:
2495
            headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
2496
        else:
2497
            headers += "Subject: [MERGE REQUEST]\n"
2498
        headers += "\n"
2499
        headers += "Base-Revision: %s\n" % base
2500
        for revision in revisions:
2501
            headers += "Revision: %s\n" % revision.revision
2502
        headers += "Bug: \n\n"
2503
        return headers
2504
2505
    def make_summary(self, logs):
2506
        """Generate a summary of merges
2507
2508
        :param logs: the patchlogs that were directly added by the merges
2509
        :type logs: list of `arch.Patchlog`
2510
        :return: the summary
2511
        :rtype: str
2512
        """ 
2513
        summary = ""
2514
        for log in logs:
2515
            summary+=str(log.revision)+"\n"
2516
            summary+=log.summary+"\n"
2517
            if log.description.strip():
2518
                summary+=log.description.strip('\n')+"\n\n"
2519
        return summary
2520
2521
    def revision_specs(self, args):
2522
        """Determine the base and merge revisions from tree and arguments.
2523
2524
        :param args: The parsed arguments
2525
        :type args: list of str
2526
        :return: The base revision and merge revisions 
2527
        :rtype: `arch.Revision`, list of `arch.Patchlog`
2528
        """
2529
        if len(args) > 0:
2530
            target_revision = cmdutil.determine_revision_arch(self.tree, 
2531
                                                              args[0])
2532
        else:
2533
            target_revision = arch_compound.tree_latest(self.tree)
2534
        if len(args) > 1:
2535
            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
2536
                       self.tree, f)) for f in args[1:] ]
2537
        else:
2538
            if self.tree is None:
2539
                raise CantDetermineRevision("", "Not in a project tree")
2540
            merge_iter = cmdutil.iter_new_merges(self.tree, 
2541
                                                 target_revision.version, 
2542
                                                 False)
2543
            merges = [f for f in cmdutil.direct_merges(merge_iter)]
2544
        return (target_revision, merges)
2545
2546
    def edit_message(self, message):
2547
        """Edit an email message in the user's standard editor
2548
2549
        :param message: The message to edit
2550
        :type message: str
2551
        :return: the path of the edited message
2552
        :rtype: str
2553
        """
2554
        if self.tree is None:
2555
            path = os.get_cwd()
2556
        else:
2557
            path = self.tree
2558
        path += "/,merge-request"
2559
        file = open(path, 'w')
2560
        file.write(message)
2561
        file.flush()
2562
        cmdutil.invoke_editor(path)
2563
        return path
2564
2565
    def tidy_message(self, path):
2566
        """Validate and clean up message.
2567
2568
        :param path: The path to the message to clean up
2569
        :type path: str
2570
        :return: The parsed message
2571
        :rtype: `email.Message`
2572
        """
2573
        mail = email.message_from_file(open(path))
2574
        if mail["Subject"].strip() == "[MERGE REQUEST]":
2575
            raise BlandSubject
2576
        
2577
        request = email.message_from_string(mail.get_payload())
2578
        if request.has_key("Bug"):
2579
            if request["Bug"].strip()=="":
2580
                del request["Bug"]
2581
        mail.set_payload(request.as_string())
2582
        return mail
2583
2584
    def send_message(self, message):
2585
        """Send a message, using its headers to address it.
2586
2587
        :param message: The message to send
2588
        :type message: `email.Message`"""
2589
        server = smtplib.SMTP("localhost")
2590
        server.sendmail(message['From'], message['To'], message.as_string())
2591
        server.quit()
2592
2593
    def help(self, parser=None):
2594
        """Print a usage message
2595
2596
        :param parser: The options parser to use
2597
        :type parser: `cmdutil.CmdOptionParser`
2598
        """
2599
        if parser is None:
2600
            parser = self.get_parser()
2601
        parser.print_help()
2602
        print """
2603
Sends a merge request formatted for Bug Goo.  Intended use: get the tree
2604
you'd like to merge into.  Apply the merges you want.  Invoke request-merge.
2605
The merge request will open in your $EDITOR.
2606
2607
When no TARGET is specified, it uses the current tree revision.  When
2608
no MERGE is specified, it uses the direct merges (as in "revisions
2609
--direct-merges").  But you can specify just the TARGET, or all the MERGE
2610
revisions.
2611
"""
2612
2613
    def get_parser(self):
2614
        """Produce a commandline parser for this command.
2615
2616
        :rtype: `cmdutil.CmdOptionParser`
2617
        """
2618
        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
2619
        return parser
2620
2621
commands = { 
2622
'changes' : Changes,
2623
'help' : Help,
2624
'update': Update,
2625
'apply-changes':ApplyChanges,
2626
'cat-log': CatLog,
2627
'commit': Commit,
2628
'revision': Revision,
2629
'revisions': Revisions,
2630
'get': Get,
2631
'revert': Revert,
2632
'shell': Shell,
2633
'add-id': AddID,
2634
'merge': Merge,
2635
'elog': ELog,
2636
'mirror-archive': MirrorArchive,
2637
'ninventory': Inventory,
2638
'alias' : Alias,
2639
'request-merge': RequestMerge,
2640
}
2641
2642
def my_import(mod_name):
2643
    module = __import__(mod_name)
2644
    components = mod_name.split('.')
2645
    for comp in components[1:]:
2646
        module = getattr(module, comp)
2647
    return module
2648
2649
def plugin(mod_name):
2650
    module = my_import(mod_name)
2651
    module.add_command(commands)
2652
2653
for file in os.listdir(sys.path[0]+"/command"):
2654
    if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
2655
        plugin("command."+file[:-3])
2656
2657
suggestions = {
2658
'apply-delta' : "Try \"apply-changes\".",
2659
'delta' : "To compare two revisions, use \"changes\".",
2660
'diff-rev' : "To compare two revisions, use \"changes\".",
2661
'undo' : "To undo local changes, use \"revert\".",
2662
'undelete' : "To undo only deletions, use \"revert --deletions\"",
2663
'missing-from' : "Try \"revisions --missing-from\".",
2664
'missing' : "Try \"revisions --missing\".",
2665
'missing-merge' : "Try \"revisions --partner-missing\".",
2666
'new-merges' : "Try \"revisions --new-merges\".",
2667
'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
2668
'logs' : "Try \"revisions --logs\"",
2669
'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
2670
'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
2671
'change-version' : "Try \"update REVISION\"",
2672
'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
2673
'rev-depends' : "Use revisions --dependencies",
2674
'auto-get' : "Plain get will do archive lookups",
2675
'tagline' : "Use add-id.  It uses taglines in tagline trees",
2676
'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
2677
'library-revisions' : "Use revisions --library",
2678
'file-revert' : "Use revert FILE",
2679
'join-branch' : "Use replay --logs-only"
2680
}
2681
# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7