~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/commands.py

  • Committer: Robert Collins
  • Date: 2005-09-14 11:27:20 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050914112720-c66a21de86eafa6e
trim fai cribbage

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