~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to testdata/mod

  • Committer: Aaron Bentley
  • Date: 2013-08-20 03:02:43 UTC
  • Revision ID: aaron@aaronbentley.com-20130820030243-r8v1xfbcnd8f10p4
Fix zap command for 2.6/7

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