~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to testdata/orig

  • Committer: Aaron Bentley
  • Date: 2008-01-11 03:01:54 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20080111030154-apm50v0b0tu93prh
Support branch6 formats in rspush

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