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