~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to testdata/mod

  • Committer: Aaron Bentley
  • Date: 2006-05-31 03:42:18 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20060531034218-a3933f36350bb69f
Fix test suite

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