~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to testdata/orig

  • Committer: aaron.bentley at utoronto
  • Date: 2005-08-25 01:51:22 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20050825015122-169b68edd17fb871
Made files for shelf optional

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
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