1
 
*** added file 'bzrlib/patches.py'
 
5
 
+# Copyright (C) 2004, 2005 Aaron Bentley
 
6
 
+# <aaron.bentley@utoronto.ca>
 
8
 
+#    This program is free software; you can redistribute it and/or modify
 
9
 
+#    it under the terms of the GNU General Public License as published by
 
10
 
+#    the Free Software Foundation; either version 2 of the License, or
 
11
 
+#    (at your option) any later version.
 
13
 
+#    This program is distributed in the hope that it will be useful,
 
14
 
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
 
+#    GNU General Public License for more details.
 
18
 
+#    You should have received a copy of the GNU General Public License
 
19
 
+#    along with this program; if not, write to the Free Software
 
20
 
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
23
 
+class PatchSyntax(Exception):
 
24
 
+    def __init__(self, msg):
 
25
 
+        Exception.__init__(self, msg)
 
28
 
+class MalformedPatchHeader(PatchSyntax):
 
29
 
+    def __init__(self, desc, line):
 
32
 
+        msg = "Malformed patch header.  %s\n%s" % (self.desc, self.line)
 
33
 
+        PatchSyntax.__init__(self, msg)
 
35
 
+class MalformedHunkHeader(PatchSyntax):
 
36
 
+    def __init__(self, desc, line):
 
39
 
+        msg = "Malformed hunk header.  %s\n%s" % (self.desc, self.line)
 
40
 
+        PatchSyntax.__init__(self, msg)
 
42
 
+class MalformedLine(PatchSyntax):
 
43
 
+    def __init__(self, desc, line):
 
46
 
+        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
 
47
 
+        PatchSyntax.__init__(self, msg)
 
49
 
+def get_patch_names(iter_lines):
 
51
 
+        line = iter_lines.next()
 
52
 
+        if not line.startswith("--- "):
 
53
 
+            raise MalformedPatchHeader("No orig name", line)
 
55
 
+            orig_name = line[4:].rstrip("\n")
 
56
 
+    except StopIteration:
 
57
 
+        raise MalformedPatchHeader("No orig line", "")
 
59
 
+        line = iter_lines.next()
 
60
 
+        if not line.startswith("+++ "):
 
61
 
+            raise PatchSyntax("No mod name")
 
63
 
+            mod_name = line[4:].rstrip("\n")
 
64
 
+    except StopIteration:
 
65
 
+        raise MalformedPatchHeader("No mod line", "")
 
66
 
+    return (orig_name, mod_name)
 
68
 
+def parse_range(textrange):
 
69
 
+    """Parse a patch range, handling the "1" special-case
 
71
 
+    :param textrange: The text to parse
 
72
 
+    :type textrange: str
 
73
 
+    :return: the position and range, as a tuple
 
76
 
+    tmp = textrange.split(',')
 
87
 
+def hunk_from_header(line):
 
88
 
+    if not line.startswith("@@") or not line.endswith("@@\n") \
 
89
 
+        or not len(line) > 4:
 
90
 
+        raise MalformedHunkHeader("Does not start and end with @@.", line)
 
92
 
+        (orig, mod) = line[3:-4].split(" ")
 
93
 
+    except Exception, e:
 
94
 
+        raise MalformedHunkHeader(str(e), line)
 
95
 
+    if not orig.startswith('-') or not mod.startswith('+'):
 
96
 
+        raise MalformedHunkHeader("Positions don't start with + or -.", line)
 
98
 
+        (orig_pos, orig_range) = parse_range(orig[1:])
 
99
 
+        (mod_pos, mod_range) = parse_range(mod[1:])
 
100
 
+    except Exception, e:
 
101
 
+        raise MalformedHunkHeader(str(e), line)
 
102
 
+    if mod_range < 0 or orig_range < 0:
 
103
 
+        raise MalformedHunkHeader("Hunk range is negative", line)
 
104
 
+    return Hunk(orig_pos, orig_range, mod_pos, mod_range)
 
108
 
+    def __init__(self, contents):
 
109
 
+        self.contents = contents
 
111
 
+    def get_str(self, leadchar):
 
112
 
+        if self.contents == "\n" and leadchar == " " and False:
 
114
 
+        return leadchar + self.contents
 
116
 
+class ContextLine(HunkLine):
 
117
 
+    def __init__(self, contents):
 
118
 
+        HunkLine.__init__(self, contents)
 
121
 
+        return self.get_str(" ")
 
124
 
+class InsertLine(HunkLine):
 
125
 
+    def __init__(self, contents):
 
126
 
+        HunkLine.__init__(self, contents)
 
129
 
+        return self.get_str("+")
 
132
 
+class RemoveLine(HunkLine):
 
133
 
+    def __init__(self, contents):
 
134
 
+        HunkLine.__init__(self, contents)
 
137
 
+        return self.get_str("-")
 
139
 
+__pychecker__="no-returnvalues"
 
140
 
+def parse_line(line):
 
141
 
+    if line.startswith("\n"):
 
142
 
+        return ContextLine(line)
 
143
 
+    elif line.startswith(" "):
 
144
 
+        return ContextLine(line[1:])
 
145
 
+    elif line.startswith("+"):
 
146
 
+        return InsertLine(line[1:])
 
147
 
+    elif line.startswith("-"):
 
148
 
+        return RemoveLine(line[1:])
 
150
 
+        raise MalformedLine("Unknown line type", line)
 
155
 
+    def __init__(self, orig_pos, orig_range, mod_pos, mod_range):
 
156
 
+        self.orig_pos = orig_pos
 
157
 
+        self.orig_range = orig_range
 
158
 
+        self.mod_pos = mod_pos
 
159
 
+        self.mod_range = mod_range
 
162
 
+    def get_header(self):
 
163
 
+        return "@@ -%s +%s @@\n" % (self.range_str(self.orig_pos, 
 
165
 
+                                    self.range_str(self.mod_pos, 
 
168
 
+    def range_str(self, pos, range):
 
169
 
+        """Return a file range, special-casing for 1-line files.
 
171
 
+        :param pos: The position in the file
 
173
 
+        :range: The range in the file
 
175
 
+        :return: a string in the format 1,4 except when range == pos == 1
 
180
 
+            return "%i,%i" % (pos, range)
 
183
 
+        lines = [self.get_header()]
 
184
 
+        for line in self.lines:
 
185
 
+            lines.append(str(line))
 
186
 
+        return "".join(lines)
 
188
 
+    def shift_to_mod(self, pos):
 
189
 
+        if pos < self.orig_pos-1:
 
191
 
+        elif pos > self.orig_pos+self.orig_range:
 
192
 
+            return self.mod_range - self.orig_range
 
194
 
+            return self.shift_to_mod_lines(pos)
 
196
 
+    def shift_to_mod_lines(self, pos):
 
197
 
+        assert (pos >= self.orig_pos-1 and pos <= self.orig_pos+self.orig_range)
 
198
 
+        position = self.orig_pos-1
 
200
 
+        for line in self.lines:
 
201
 
+            if isinstance(line, InsertLine):
 
203
 
+            elif isinstance(line, RemoveLine):
 
204
 
+                if position == pos:
 
208
 
+            elif isinstance(line, ContextLine):
 
214
 
+def iter_hunks(iter_lines):
 
216
 
+    for line in iter_lines:
 
217
 
+        if line.startswith("@@"):
 
218
 
+            if hunk is not None:
 
220
 
+            hunk = hunk_from_header(line)
 
222
 
+            hunk.lines.append(parse_line(line))
 
224
 
+    if hunk is not None:
 
228
 
+    def __init__(self, oldname, newname):
 
229
 
+        self.oldname = oldname
 
230
 
+        self.newname = newname
 
234
 
+        ret =  "--- %s\n+++ %s\n" % (self.oldname, self.newname) 
 
235
 
+        ret += "".join([str(h) for h in self.hunks])
 
238
 
+    def stats_str(self):
 
239
 
+        """Return a string of patch statistics"""
 
242
 
+        for hunk in self.hunks:
 
243
 
+            for line in hunk.lines:
 
244
 
+                if isinstance(line, InsertLine):
 
246
 
+                elif isinstance(line, RemoveLine):
 
248
 
+        return "%i inserts, %i removes in %i hunks" % \
 
249
 
+            (inserts, removes, len(self.hunks))
 
251
 
+    def pos_in_mod(self, position):
 
253
 
+        for hunk in self.hunks:
 
254
 
+            shift = hunk.shift_to_mod(position)
 
260
 
+    def iter_inserted(self):
 
261
 
+        """Iteraties through inserted lines
 
263
 
+        :return: Pair of line number, line
 
264
 
+        :rtype: iterator of (int, InsertLine)
 
266
 
+        for hunk in self.hunks:
 
267
 
+            pos = hunk.mod_pos - 1;
 
268
 
+            for line in hunk.lines:
 
269
 
+                if isinstance(line, InsertLine):
 
272
 
+                if isinstance(line, ContextLine):
 
275
 
+def parse_patch(iter_lines):
 
276
 
+    (orig_name, mod_name) = get_patch_names(iter_lines)
 
277
 
+    patch = Patch(orig_name, mod_name)
 
278
 
+    for hunk in iter_hunks(iter_lines):
 
279
 
+        patch.hunks.append(hunk)
 
284
 
+    """A line associated with the log that produced it"""
 
285
 
+    def __init__(self, text, log=None):
 
289
 
+class CantGetRevisionData(Exception):
 
290
 
+    def __init__(self, revision):
 
291
 
+        Exception.__init__(self, "Can't get data for revision %s" % revision)
 
293
 
+def annotate_file2(file_lines, anno_iter):
 
294
 
+    for result in iter_annotate_file(file_lines, anno_iter):
 
299
 
+def iter_annotate_file(file_lines, anno_iter):
 
300
 
+    lines = [AnnotateLine(f) for f in file_lines]
 
303
 
+        for result in anno_iter:
 
304
 
+            if isinstance(result, progress.Progress):
 
307
 
+            log, iter_inserted, patch = result
 
308
 
+            for (num, line) in iter_inserted:
 
311
 
+                for cur_patch in patches:
 
312
 
+                    num = cur_patch.pos_in_mod(num)
 
316
 
+                if num >= len(lines):
 
318
 
+                if num is not None and lines[num].log is None:
 
319
 
+                    lines[num].log = log
 
320
 
+            patches=[patch]+patches
 
321
 
+    except CantGetRevisionData:
 
326
 
+def difference_index(atext, btext):
 
327
 
+    """Find the indext of the first character that differs betweeen two texts
 
329
 
+    :param atext: The first text
 
331
 
+    :param btext: The second text
 
333
 
+    :return: The index, or None if there are no differences within the range
 
334
 
+    :rtype: int or NoneType
 
336
 
+    length = len(atext)
 
337
 
+    if len(btext) < length:
 
338
 
+        length = len(btext)
 
339
 
+    for i in range(length):
 
340
 
+        if atext[i] != btext[i]:
 
347
 
+    class PatchesTester(unittest.TestCase):
 
348
 
+        def testValidPatchHeader(self):
 
349
 
+            """Parse a valid patch header"""
 
350
 
+            lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
 
351
 
+            (orig, mod) = get_patch_names(lines.__iter__())
 
352
 
+            assert(orig == "orig/commands.py")
 
353
 
+            assert(mod == "mod/dommands.py")
 
355
 
+        def testInvalidPatchHeader(self):
 
356
 
+            """Parse an invalid patch header"""
 
357
 
+            lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
 
358
 
+            self.assertRaises(MalformedPatchHeader, get_patch_names,
 
361
 
+        def testValidHunkHeader(self):
 
362
 
+            """Parse a valid hunk header"""
 
363
 
+            header = "@@ -34,11 +50,6 @@\n"
 
364
 
+            hunk = hunk_from_header(header);
 
365
 
+            assert (hunk.orig_pos == 34)
 
366
 
+            assert (hunk.orig_range == 11)
 
367
 
+            assert (hunk.mod_pos == 50)
 
368
 
+            assert (hunk.mod_range == 6)
 
369
 
+            assert (str(hunk) == header)
 
371
 
+        def testValidHunkHeader2(self):
 
372
 
+            """Parse a tricky, valid hunk header"""
 
373
 
+            header = "@@ -1 +0,0 @@\n"
 
374
 
+            hunk = hunk_from_header(header);
 
375
 
+            assert (hunk.orig_pos == 1)
 
376
 
+            assert (hunk.orig_range == 1)
 
377
 
+            assert (hunk.mod_pos == 0)
 
378
 
+            assert (hunk.mod_range == 0)
 
379
 
+            assert (str(hunk) == header)
 
381
 
+        def makeMalformed(self, header):
 
382
 
+            self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
 
384
 
+        def testInvalidHeader(self):
 
385
 
+            """Parse an invalid hunk header"""
 
386
 
+            self.makeMalformed(" -34,11 +50,6 \n")
 
387
 
+            self.makeMalformed("@@ +50,6 -34,11 @@\n")
 
388
 
+            self.makeMalformed("@@ -34,11 +50,6 @@")
 
389
 
+            self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
 
390
 
+            self.makeMalformed("@@-34,11 +50,6@@\n")
 
391
 
+            self.makeMalformed("@@ 34,11 50,6 @@\n")
 
392
 
+            self.makeMalformed("@@ -34,11 @@\n")
 
393
 
+            self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
 
394
 
+            self.makeMalformed("@@ -34,11 +50,-6 @@\n")
 
396
 
+        def lineThing(self,text, type):
 
397
 
+            line = parse_line(text)
 
398
 
+            assert(isinstance(line, type))
 
399
 
+            assert(str(line)==text)
 
401
 
+        def makeMalformedLine(self, text):
 
402
 
+            self.assertRaises(MalformedLine, parse_line, text)
 
404
 
+        def testValidLine(self):
 
405
 
+            """Parse a valid hunk line"""
 
406
 
+            self.lineThing(" hello\n", ContextLine)
 
407
 
+            self.lineThing("+hello\n", InsertLine)
 
408
 
+            self.lineThing("-hello\n", RemoveLine)
 
410
 
+        def testMalformedLine(self):
 
411
 
+            """Parse invalid valid hunk lines"""
 
412
 
+            self.makeMalformedLine("hello\n")
 
414
 
+        def compare_parsed(self, patchtext):
 
415
 
+            lines = patchtext.splitlines(True)
 
416
 
+            patch = parse_patch(lines.__iter__())
 
418
 
+            i = difference_index(patchtext, pstr)
 
420
 
+                print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
 
421
 
+            assert (patchtext == str(patch))
 
424
 
+            """Test parsing a whole patch"""
 
425
 
+            patchtext = """--- orig/commands.py
 
427
 
+@@ -1337,7 +1337,8 @@
 
429
 
+     def set_title(self, command=None):
 
431
 
+-            version = self.tree.tree_version.nonarch
 
432
 
++            version = pylon.alias_or_version(self.tree.tree_version, self.tree,
 
435
 
+             version = "[no version]"
 
436
 
+         if command is None:
 
437
 
+@@ -1983,7 +1984,11 @@
 
439
 
+         if len(new_merges) > 0:
 
440
 
+             if cmdutil.prompt("Log for merge"):
 
441
 
+-                mergestuff = cmdutil.log_for_merge(tree, comp_version)
 
442
 
++                if cmdutil.prompt("changelog for merge"):
 
443
 
++                    mergestuff = "Patches applied:\\n"
 
444
 
++                    mergestuff += pylon.changelog_for_merge(new_merges)
 
446
 
++                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
 
447
 
+                 log.description += mergestuff
 
451
 
+            self.compare_parsed(patchtext)
 
453
 
+        def testInit(self):
 
454
 
+            """Handle patches missing half the position, range tuple"""
 
456
 
+"""--- orig/__init__.py
 
459
 
+ __docformat__ = "restructuredtext en"
 
460
 
++__doc__ = An alternate Arch commandline interface"""
 
461
 
+            self.compare_parsed(patchtext)
 
465
 
+        def testLineLookup(self):
 
466
 
+            """Make sure we can accurately look up mod line from orig"""
 
467
 
+            patch = parse_patch(open("testdata/diff"))
 
468
 
+            orig = list(open("testdata/orig"))
 
469
 
+            mod = list(open("testdata/mod"))
 
471
 
+            for i in range(len(orig)):
 
472
 
+                mod_pos = patch.pos_in_mod(i)
 
473
 
+                if mod_pos is None:
 
474
 
+                    removals.append(orig[i])
 
476
 
+                assert(mod[mod_pos]==orig[i])
 
477
 
+            rem_iter = removals.__iter__()
 
478
 
+            for hunk in patch.hunks:
 
479
 
+                for line in hunk.lines:
 
480
 
+                    if isinstance(line, RemoveLine):
 
481
 
+                        next = rem_iter.next()
 
482
 
+                        if line.contents != next:
 
483
 
+                            sys.stdout.write(" orig:%spatch:%s" % (next,
 
485
 
+                        assert(line.contents == next)
 
486
 
+            self.assertRaises(StopIteration, rem_iter.next)
 
488
 
+        def testFirstLineRenumber(self):
 
489
 
+            """Make sure we handle lines at the beginning of the hunk"""
 
490
 
+            patch = parse_patch(open("testdata/insert_top.patch"))
 
491
 
+            assert (patch.pos_in_mod(0)==1)
 
494
 
+    patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
 
495
 
+    runner = unittest.TextTestRunner(verbosity=0)
 
496
 
+    return runner.run(patchesTestSuite)
 
499
 
+if __name__ == "__main__":
 
501
 
+# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683
 
503
 
*** added directory 'testdata'
 
504
 
*** added file 'testdata/diff'
 
508
 
+--- orig/commands.py
 
515
 
++import pylon.errors
 
516
 
++from pylon.errors import *
 
517
 
++from pylon import errors
 
518
 
++from pylon import util
 
519
 
++from pylon import arch_core
 
520
 
++from pylon import arch_compound
 
521
 
++from pylon import ancillary
 
522
 
++from pylon import misc
 
523
 
++from pylon import paths 
 
537
 
+-from errors import *
 
546
 
+ __docformat__ = "restructuredtext"
 
547
 
+ __doc__ = "Implementation of user (sub) commands"
 
550
 
+         tree=arch.tree_root()
 
552
 
+-            a_spec = cmdutil.comp_revision(tree)
 
553
 
++            a_spec = ancillary.comp_revision(tree)
 
555
 
+             a_spec = cmdutil.determine_revision_tree(tree, args[0])
 
556
 
+         cmdutil.ensure_archive_registered(a_spec.archive)
 
558
 
+             changeset=options.changeset
 
561
 
+-            tmpdir=cmdutil.tmpdir()
 
562
 
++            tmpdir=util.tmpdir()
 
563
 
+             changeset=tmpdir+"/changeset"
 
565
 
+             delta=arch.iter_delta(a_spec, b_spec, changeset)
 
566
 
+@@ -304,14 +310,14 @@
 
569
 
+             if (options.perform_diff):
 
570
 
+-                chan = cmdutil.ChangesetMunger(changeset)
 
571
 
++                chan = arch_compound.ChangesetMunger(changeset)
 
572
 
+                 chan.read_indices()
 
573
 
+-                if isinstance(b_spec, arch.Revision):
 
574
 
+-                    b_dir = b_spec.library_find()
 
577
 
+-                a_dir = a_spec.library_find()
 
578
 
+                 if options.diffopts is not None:
 
579
 
++                    if isinstance(b_spec, arch.Revision):
 
580
 
++                        b_dir = b_spec.library_find()
 
583
 
++                    a_dir = a_spec.library_find()
 
584
 
+                     diffopts = options.diffopts.split()
 
585
 
+                     cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
 
588
 
+         except arch.errors.TreeRootError, e:
 
591
 
+-        from_revision=cmdutil.tree_latest(tree)
 
592
 
++        from_revision = arch_compound.tree_latest(tree)
 
593
 
+         if from_revision==to_revision:
 
594
 
+             print "Tree is already up to date with:\n"+str(to_revision)+"."
 
600
 
++        if options.version is None:
 
601
 
++            return options, tree.tree_version, args
 
603
 
+         revision=cmdutil.determine_revision_arch(tree, options.version)
 
604
 
+         return options, revision.get_version(), args
 
606
 
+@@ -601,11 +610,16 @@
 
608
 
+         tree=arch.tree_root()
 
609
 
+         options, version, files = self.parse_commandline(cmdargs, tree)
 
611
 
+         if options.__dict__.has_key("base") and options.base:
 
612
 
+             base = cmdutil.determine_revision_tree(tree, options.base)
 
615
 
+-            base = cmdutil.submit_revision(tree)
 
617
 
++            base = ancillary.submit_revision(tree)
 
619
 
++        if ancestor is None:
 
620
 
++            ancestor = arch_compound.tree_latest(tree, version)
 
622
 
+         writeversion=version
 
623
 
+         archive=version.archive
 
624
 
+         source=cmdutil.get_mirror_source(archive)
 
625
 
+@@ -625,18 +639,26 @@
 
627
 
+             last_revision=tree.iter_logs(version, True).next().revision
 
628
 
+         except StopIteration, e:
 
629
 
+-            if cmdutil.prompt("Import from commit"):
 
630
 
+-                return do_import(version)
 
632
 
+-                raise NoVersionLogs(version)
 
633
 
+-        if last_revision!=version.iter_revisions(True).next():
 
634
 
++            last_revision = None
 
635
 
++            if ancestor is None:
 
636
 
++                if cmdutil.prompt("Import from commit"):
 
637
 
++                    return do_import(version)
 
639
 
++                    raise NoVersionLogs(version)
 
641
 
++            arch_last_revision = version.iter_revisions(True).next()
 
642
 
++        except StopIteration, e:
 
643
 
++            arch_last_revision = None
 
645
 
++        if last_revision != arch_last_revision:
 
646
 
++            print "Tree is not up to date with %s" % str(version)
 
647
 
+             if not cmdutil.prompt("Out of date"):
 
653
 
+-            if not cmdutil.has_changed(version):
 
654
 
++            if not cmdutil.has_changed(ancestor):
 
655
 
+                 if not cmdutil.prompt("Empty commit"):
 
657
 
+         except arch.util.ExecProblem, e:
 
658
 
+@@ -645,15 +667,15 @@
 
662
 
+-        log = tree.log_message(create=False)
 
663
 
++        log = tree.log_message(create=False, version=version)
 
666
 
+                 if cmdutil.prompt("Create log"):
 
668
 
++                    edit_log(tree, version)
 
670
 
+             except cmdutil.NoEditorSpecified, e:
 
671
 
+                 raise CommandFailed(e)
 
672
 
+-            log = tree.log_message(create=False)
 
673
 
++            log = tree.log_message(create=False, version=version)
 
676
 
+         if log["Summary"] is None or len(log["Summary"].strip()) == 0:
 
677
 
+@@ -837,23 +859,24 @@
 
678
 
+             if spec is not None:
 
679
 
+                 revision = cmdutil.determine_revision_tree(tree, spec)
 
681
 
+-                revision = cmdutil.comp_revision(tree)
 
682
 
++                revision = ancillary.comp_revision(tree)
 
683
 
+         except cmdutil.CantDetermineRevision, e:
 
684
 
+             raise CommandFailedWrapper(e)
 
687
 
+         if options.file_contents or options.file_perms or options.deletions\
 
688
 
+             or options.additions or options.renames or options.hunk_prompt:
 
689
 
+-            munger = cmdutil.MungeOpts()
 
690
 
+-            munger.hunk_prompt = options.hunk_prompt
 
691
 
++            munger = arch_compound.MungeOpts()
 
692
 
++            munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
 
693
 
++                                   options.hunk_prompt)
 
695
 
+         if len(args) > 0 or options.logs or options.pattern_files or \
 
698
 
+-                munger = cmdutil.MungeOpts(True)
 
699
 
++                munger = cmdutil.arch_compound.MungeOpts(True)
 
700
 
+                 munger.all_types(True)
 
702
 
+-            t_cwd = cmdutil.tree_cwd(tree)
 
703
 
++            t_cwd = arch_compound.tree_cwd(tree)
 
708
 
+         if options.pattern_files:
 
709
 
+             munger.add_keep_pattern(options.pattern_files)
 
711
 
+-        for line in cmdutil.revert(tree, revision, munger, 
 
712
 
++        for line in arch_compound.revert(tree, revision, munger, 
 
713
 
+                                    not options.no_output):
 
714
 
+             cmdutil.colorize(line)
 
716
 
+@@ -1042,18 +1065,13 @@
 
720
 
+-def require_version_exists(version, spec):
 
721
 
+-    if not version.exists():
 
722
 
+-        raise cmdutil.CantDetermineVersion(spec, 
 
723
 
+-                                           "The version %s does not exist." \
 
726
 
+ class Revisions(BaseCommand):
 
728
 
+     Print a revision name based on a revision specifier
 
730
 
+     def __init__(self):
 
731
 
+         self.description="Lists revisions"
 
732
 
++        self.cl_revisions = []
 
734
 
+     def do_command(self, cmdargs):
 
736
 
+@@ -1066,224 +1084,68 @@
 
737
 
+             self.tree = arch.tree_root()
 
738
 
+         except arch.errors.TreeRootError:
 
740
 
++        if options.type == "default":
 
741
 
++            options.type = "archive"
 
743
 
+-            iter = self.get_iterator(options.type, args, options.reverse, 
 
745
 
++            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
 
746
 
++                                             options.reverse, options.modified,
 
748
 
+         except cmdutil.CantDetermineRevision, e:
 
749
 
+             raise CommandFailedWrapper(e)
 
751
 
++        except cmdutil.CantDetermineVersion, e:
 
752
 
++            raise CommandFailedWrapper(e)
 
753
 
+         if options.skip is not None:
 
754
 
+             iter = cmdutil.iter_skip(iter, int(options.skip))
 
756
 
+-        for revision in iter:
 
758
 
+-            if isinstance(revision, arch.Patchlog):
 
760
 
+-                revision=revision.revision
 
761
 
+-            print options.display(revision)
 
762
 
+-            if log is None and (options.summary or options.creator or 
 
763
 
+-                                options.date or options.merges):
 
764
 
+-                log = revision.patchlog
 
765
 
+-            if options.creator:
 
766
 
+-                print "    %s" % log.creator
 
768
 
+-                print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
 
769
 
+-            if options.summary:
 
770
 
+-                print "    %s" % log.summary
 
771
 
+-            if options.merges:
 
772
 
+-                showed_title = False
 
773
 
+-                for revision in log.merged_patches:
 
774
 
+-                    if not showed_title:
 
776
 
+-                        showed_title = True
 
777
 
+-                    print "    %s" % revision
 
779
 
+-    def get_iterator(self, type, args, reverse, modified):
 
784
 
+-        if modified is not None:
 
785
 
+-            iter = cmdutil.modified_iter(modified, self.tree)
 
789
 
+-                return cmdutil.iter_reverse(iter)
 
790
 
+-        elif type == "archive":
 
792
 
+-                if self.tree is None:
 
793
 
+-                    raise cmdutil.CantDetermineRevision("", 
 
794
 
+-                                                        "Not in a project tree")
 
795
 
+-                version = cmdutil.determine_version_tree(spec, self.tree)
 
797
 
+-                version = cmdutil.determine_version_arch(spec, self.tree)
 
798
 
+-                cmdutil.ensure_archive_registered(version.archive)
 
799
 
+-                require_version_exists(version, spec)
 
800
 
+-            return version.iter_revisions(reverse)
 
801
 
+-        elif type == "cacherevs":
 
803
 
+-                if self.tree is None:
 
804
 
+-                    raise cmdutil.CantDetermineRevision("", 
 
805
 
+-                                                        "Not in a project tree")
 
806
 
+-                version = cmdutil.determine_version_tree(spec, self.tree)
 
808
 
+-                version = cmdutil.determine_version_arch(spec, self.tree)
 
809
 
+-                cmdutil.ensure_archive_registered(version.archive)
 
810
 
+-                require_version_exists(version, spec)
 
811
 
+-            return cmdutil.iter_cacherevs(version, reverse)
 
812
 
+-        elif type == "library":
 
814
 
+-                if self.tree is None:
 
815
 
+-                    raise cmdutil.CantDetermineRevision("", 
 
816
 
+-                                                        "Not in a project tree")
 
817
 
+-                version = cmdutil.determine_version_tree(spec, self.tree)
 
819
 
+-                version = cmdutil.determine_version_arch(spec, self.tree)
 
820
 
+-            return version.iter_library_revisions(reverse)
 
821
 
+-        elif type == "logs":
 
822
 
+-            if self.tree is None:
 
823
 
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
824
 
+-            return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
 
825
 
+-                                  self.tree), reverse)
 
826
 
+-        elif type == "missing" or type == "skip-present":
 
827
 
+-            if self.tree is None:
 
828
 
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
829
 
+-            skip = (type == "skip-present")
 
830
 
+-            version = cmdutil.determine_version_tree(spec, self.tree)
 
831
 
+-            cmdutil.ensure_archive_registered(version.archive)
 
832
 
+-            require_version_exists(version, spec)
 
833
 
+-            return cmdutil.iter_missing(self.tree, version, reverse,
 
834
 
+-                                        skip_present=skip)
 
836
 
+-        elif type == "present":
 
837
 
+-            if self.tree is None:
 
838
 
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
839
 
+-            version = cmdutil.determine_version_tree(spec, self.tree)
 
840
 
+-            cmdutil.ensure_archive_registered(version.archive)
 
841
 
+-            require_version_exists(version, spec)
 
842
 
+-            return cmdutil.iter_present(self.tree, version, reverse)
 
844
 
+-        elif type == "new-merges" or type == "direct-merges":
 
845
 
+-            if self.tree is None:
 
846
 
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
847
 
+-            version = cmdutil.determine_version_tree(spec, self.tree)
 
848
 
+-            cmdutil.ensure_archive_registered(version.archive)
 
849
 
+-            require_version_exists(version, spec)
 
850
 
+-            iter = cmdutil.iter_new_merges(self.tree, version, reverse)
 
851
 
+-            if type == "new-merges":
 
853
 
+-            elif type == "direct-merges":
 
854
 
+-                return cmdutil.direct_merges(iter)
 
856
 
+-        elif type == "missing-from":
 
857
 
+-            if self.tree is None:
 
858
 
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
859
 
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
 
860
 
+-            libtree = cmdutil.find_or_make_local_revision(revision)
 
861
 
+-            return cmdutil.iter_missing(libtree, self.tree.tree_version,
 
864
 
+-        elif type == "partner-missing":
 
865
 
+-            return cmdutil.iter_partner_missing(self.tree, reverse)
 
867
 
+-        elif type == "ancestry":
 
868
 
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
 
869
 
+-            iter = cmdutil._iter_ancestry(self.tree, revision)
 
873
 
+-                return cmdutil.iter_reverse(iter)
 
875
 
+-        elif type == "dependencies" or type == "non-dependencies":
 
876
 
+-            nondeps = (type == "non-dependencies")
 
877
 
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
 
878
 
+-            anc_iter = cmdutil._iter_ancestry(self.tree, revision)
 
879
 
+-            iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
 
881
 
+-                return iter_depends
 
883
 
+-                return cmdutil.iter_reverse(iter_depends)
 
884
 
+-        elif type == "micro":
 
885
 
+-            return cmdutil.iter_micro(self.tree)
 
889
 
++            for revision in iter:
 
891
 
++                if isinstance(revision, arch.Patchlog):
 
893
 
++                    revision=revision.revision
 
894
 
++                out = options.display(revision)
 
895
 
++                if out is not None:
 
897
 
++                if log is None and (options.summary or options.creator or 
 
898
 
++                                    options.date or options.merges):
 
899
 
++                    log = revision.patchlog
 
900
 
++                if options.creator:
 
901
 
++                    print "    %s" % log.creator
 
903
 
++                    print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
 
904
 
++                if options.summary:
 
905
 
++                    print "    %s" % log.summary
 
906
 
++                if options.merges:
 
907
 
++                    showed_title = False
 
908
 
++                    for revision in log.merged_patches:
 
909
 
++                        if not showed_title:
 
911
 
++                            showed_title = True
 
912
 
++                        print "    %s" % revision
 
913
 
++            if len(self.cl_revisions) > 0:
 
914
 
++                print pylon.changelog_for_merge(self.cl_revisions)
 
915
 
++        except pylon.errors.TreeRootNone:
 
916
 
++            raise CommandFailedWrapper(
 
917
 
++                Exception("This option can only be used in a project tree."))
 
919
 
++    def changelog_append(self, revision):
 
920
 
++        if isinstance(revision, arch.Revision):
 
921
 
++            revision=arch.Patchlog(revision)
 
922
 
++        self.cl_revisions.append(revision)
 
924
 
+     def get_parser(self):
 
926
 
+         Returns the options parser to use for the "revision" command.
 
928
 
+         :rtype: cmdutil.CmdOptionParser
 
930
 
+-        parser=cmdutil.CmdOptionParser("fai revisions [revision]")
 
931
 
++        parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
 
932
 
+         select = cmdutil.OptionGroup(parser, "Selection options",
 
933
 
+                           "Control which revisions are listed.  These options"
 
934
 
+                           " are mutually exclusive.  If more than one is"
 
935
 
+                           " specified, the last is used.")
 
936
 
+-        select.add_option("", "--archive", action="store_const", 
 
937
 
+-                          const="archive", dest="type", default="archive",
 
938
 
+-                          help="List all revisions in the archive")
 
939
 
+-        select.add_option("", "--cacherevs", action="store_const", 
 
940
 
+-                          const="cacherevs", dest="type",
 
941
 
+-                          help="List all revisions stored in the archive as "
 
942
 
+-                          "complete copies")
 
943
 
+-        select.add_option("", "--logs", action="store_const", 
 
944
 
+-                          const="logs", dest="type",
 
945
 
+-                          help="List revisions that have a patchlog in the "
 
947
 
+-        select.add_option("", "--missing", action="store_const", 
 
948
 
+-                          const="missing", dest="type",
 
949
 
+-                          help="List revisions from the specified version that"
 
950
 
+-                          " have no patchlog in the tree")
 
951
 
+-        select.add_option("", "--skip-present", action="store_const", 
 
952
 
+-                          const="skip-present", dest="type",
 
953
 
+-                          help="List revisions from the specified version that"
 
954
 
+-                          " have no patchlogs at all in the tree")
 
955
 
+-        select.add_option("", "--present", action="store_const", 
 
956
 
+-                          const="present", dest="type",
 
957
 
+-                          help="List revisions from the specified version that"
 
958
 
+-                          " have no patchlog in the tree, but can't be merged")
 
959
 
+-        select.add_option("", "--missing-from", action="store_const", 
 
960
 
+-                          const="missing-from", dest="type",
 
961
 
+-                          help="List revisions from the specified revision "
 
962
 
+-                          "that have no patchlog for the tree version")
 
963
 
+-        select.add_option("", "--partner-missing", action="store_const", 
 
964
 
+-                          const="partner-missing", dest="type",
 
965
 
+-                          help="List revisions in partner versions that are"
 
967
 
+-        select.add_option("", "--new-merges", action="store_const", 
 
968
 
+-                          const="new-merges", dest="type",
 
969
 
+-                          help="List revisions that have had patchlogs added"
 
970
 
+-                          " to the tree since the last commit")
 
971
 
+-        select.add_option("", "--direct-merges", action="store_const", 
 
972
 
+-                          const="direct-merges", dest="type",
 
973
 
+-                          help="List revisions that have been directly added"
 
974
 
+-                          " to tree since the last commit ")
 
975
 
+-        select.add_option("", "--library", action="store_const", 
 
976
 
+-                          const="library", dest="type",
 
977
 
+-                          help="List revisions in the revision library")
 
978
 
+-        select.add_option("", "--ancestry", action="store_const", 
 
979
 
+-                          const="ancestry", dest="type",
 
980
 
+-                          help="List revisions that are ancestors of the "
 
981
 
+-                          "current tree version")
 
983
 
+-        select.add_option("", "--dependencies", action="store_const", 
 
984
 
+-                          const="dependencies", dest="type",
 
985
 
+-                          help="List revisions that the given revision "
 
988
 
+-        select.add_option("", "--non-dependencies", action="store_const", 
 
989
 
+-                          const="non-dependencies", dest="type",
 
990
 
+-                          help="List revisions that the given revision "
 
991
 
+-                          "does not depend on")
 
993
 
+-        select.add_option("--micro", action="store_const", 
 
994
 
+-                          const="micro", dest="type",
 
995
 
+-                          help="List partner revisions aimed for this "
 
998
 
+-        select.add_option("", "--modified", dest="modified", 
 
999
 
+-                          help="List tree ancestor revisions that modified a "
 
1000
 
+-                          "given file", metavar="FILE[:LINE]")
 
1002
 
++        cmdutil.add_revision_iter_options(select)
 
1003
 
+         parser.add_option("", "--skip", dest="skip", 
 
1004
 
+                           help="Skip revisions.  Positive numbers skip from "
 
1005
 
+                           "beginning, negative skip from end.",
 
1006
 
+@@ -1312,6 +1174,9 @@
 
1007
 
+         format.add_option("--cacherev", action="store_const", 
 
1008
 
+                          const=paths.determine_cacherev_path, dest="display",
 
1009
 
+                          help="Show location of cacherev file")
 
1010
 
++        format.add_option("--changelog", action="store_const", 
 
1011
 
++                         const=self.changelog_append, dest="display",
 
1012
 
++                         help="Show location of cacherev file")
 
1013
 
+         parser.add_option_group(format)
 
1014
 
+         display = cmdutil.OptionGroup(parser, "Display format options",
 
1015
 
+                           "These control the display of data")
 
1016
 
+@@ -1448,6 +1313,7 @@
 
1017
 
+         if os.access(self.history_file, os.R_OK) and \
 
1018
 
+             os.path.isfile(self.history_file):
 
1019
 
+             readline.read_history_file(self.history_file)
 
1020
 
++        self.cwd = os.getcwd()
 
1022
 
+     def write_history(self):
 
1023
 
+         readline.write_history_file(self.history_file)
 
1024
 
+@@ -1470,16 +1336,21 @@
 
1025
 
+     def set_prompt(self):
 
1026
 
+         if self.tree is not None:
 
1028
 
+-                version = " "+self.tree.tree_version.nonarch
 
1029
 
++                prompt = pylon.alias_or_version(self.tree.tree_version, 
 
1032
 
++                if prompt is not None:
 
1033
 
++                    prompt = " " + prompt
 
1039
 
+-        self.prompt = "Fai%s> " % version
 
1041
 
++        self.prompt = "Fai%s> " % prompt
 
1043
 
+     def set_title(self, command=None):
 
1045
 
+-            version = self.tree.tree_version.nonarch
 
1046
 
++            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
 
1049
 
+             version = "[no version]"
 
1050
 
+         if command is None:
 
1051
 
+@@ -1489,8 +1360,15 @@
 
1052
 
+     def do_cd(self, line):
 
1055
 
++        line = os.path.expanduser(line)
 
1056
 
++        if os.path.isabs(line):
 
1059
 
++            newcwd = self.cwd+'/'+line
 
1060
 
++        newcwd = os.path.normpath(newcwd)
 
1062
 
+-            os.chdir(os.path.expanduser(line))
 
1064
 
++            self.cwd = newcwd
 
1065
 
+         except Exception, e:
 
1068
 
+@@ -1523,7 +1401,7 @@
 
1069
 
+             except cmdutil.CantDetermineRevision, e:
 
1071
 
+             except Exception, e:
 
1072
 
+-                print "Unhandled error:\n%s" % cmdutil.exception_str(e)
 
1073
 
++                print "Unhandled error:\n%s" % errors.exception_str(e)
 
1075
 
+         elif suggestions.has_key(args[0]):
 
1076
 
+             print suggestions[args[0]]
 
1077
 
+@@ -1574,7 +1452,7 @@
 
1078
 
+                 arg = line.split()[-1]
 
1081
 
+-            iter = iter_munged_completions(iter, arg, text)
 
1082
 
++            iter = cmdutil.iter_munged_completions(iter, arg, text)
 
1083
 
+         except Exception, e:
 
1086
 
+@@ -1604,10 +1482,11 @@
 
1089
 
+                 if arg.startswith("-"):
 
1090
 
+-                    return list(iter_munged_completions(iter, arg, text))
 
1091
 
++                    return list(cmdutil.iter_munged_completions(iter, arg, 
 
1094
 
+-                    return list(iter_munged_completions(
 
1095
 
+-                        iter_file_completions(arg), arg, text))
 
1096
 
++                    return list(cmdutil.iter_munged_completions(
 
1097
 
++                        cmdutil.iter_file_completions(arg), arg, text))
 
1101
 
+@@ -1615,13 +1494,13 @@
 
1102
 
+                     arg = args.split()[-1]
 
1105
 
+-                iter = iter_dir_completions(arg)
 
1106
 
+-                iter = iter_munged_completions(iter, arg, text)
 
1107
 
++                iter = cmdutil.iter_dir_completions(arg)
 
1108
 
++                iter = cmdutil.iter_munged_completions(iter, arg, text)
 
1111
 
+                 arg = args.split()[-1]
 
1112
 
+-                return list(iter_munged_completions(iter_file_completions(arg),
 
1114
 
++                iter = cmdutil.iter_file_completions(arg)
 
1115
 
++                return list(cmdutil.iter_munged_completions(iter, arg, text))
 
1117
 
+                 return self.completenames(text, line, begidx, endidx)
 
1118
 
+         except Exception, e:
 
1119
 
+@@ -1636,44 +1515,8 @@
 
1123
 
+-def iter_file_completions(arg, only_dirs = False):
 
1124
 
+-    """Generate an iterator that iterates through filename completions.
 
1126
 
+-    :param arg: The filename fragment to match
 
1128
 
+-    :param only_dirs: If true, match only directories
 
1129
 
+-    :type only_dirs: bool
 
1131
 
+-    cwd = os.getcwd()
 
1133
 
+-        extras = [".", ".."]
 
1136
 
+-    (dir, file) = os.path.split(arg)
 
1138
 
+-        listingdir = os.path.expanduser(dir)
 
1141
 
+-    for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
 
1143
 
+-            userfile = dir+'/'+file
 
1146
 
+-        if userfile.startswith(arg):
 
1147
 
+-            if os.path.isdir(listingdir+'/'+file):
 
1150
 
+-            elif not only_dirs:
 
1153
 
+-def iter_munged_completions(iter, arg, text):
 
1154
 
+-    for completion in iter:
 
1155
 
+-        completion = str(completion)
 
1156
 
+-        if completion.startswith(arg):
 
1157
 
+-            yield completion[len(arg)-len(text):]
 
1159
 
+ def iter_source_file_completions(tree, arg):
 
1160
 
+-    treepath = cmdutil.tree_cwd(tree)
 
1161
 
++    treepath = arch_compound.tree_cwd(tree)
 
1162
 
+     if len(treepath) > 0:
 
1165
 
+@@ -1701,7 +1544,7 @@
 
1166
 
+     :return: An iterator of all matching untagged files
 
1167
 
+     :rtype: iterator of str
 
1169
 
+-    treepath = cmdutil.tree_cwd(tree)
 
1170
 
++    treepath = arch_compound.tree_cwd(tree)
 
1171
 
+     if len(treepath) > 0:
 
1174
 
+@@ -1743,8 +1586,8 @@
 
1175
 
+     :param arg: The prefix to match
 
1178
 
+-    treepath = cmdutil.tree_cwd(tree)
 
1179
 
+-    tmpdir = cmdutil.tmpdir()
 
1180
 
++    treepath = arch_compound.tree_cwd(tree)
 
1181
 
++    tmpdir = util.tmpdir()
 
1182
 
+     changeset = tmpdir+"/changeset"
 
1184
 
+     revision = cmdutil.determine_revision_tree(tree)
 
1185
 
+@@ -1756,14 +1599,6 @@
 
1186
 
+     shutil.rmtree(tmpdir)
 
1187
 
+     return completions
 
1189
 
+-def iter_dir_completions(arg):
 
1190
 
+-    """Generate an iterator that iterates through directory name completions.
 
1192
 
+-    :param arg: The directory name fragment to match
 
1195
 
+-    return iter_file_completions(arg, True)
 
1197
 
+ class Shell(BaseCommand):
 
1198
 
+     def __init__(self):
 
1199
 
+         self.description = "Runs Fai as a shell"
 
1200
 
+@@ -1795,7 +1630,11 @@
 
1201
 
+         parser=self.get_parser()
 
1202
 
+         (options, args) = parser.parse_args(cmdargs)
 
1204
 
+-        tree = arch.tree_root()
 
1206
 
++            tree = arch.tree_root()
 
1207
 
++        except arch.errors.TreeRootError, e:
 
1208
 
++            raise pylon.errors.CommandFailedWrapper(e)
 
1211
 
+         if (len(args) == 0) == (options.untagged == False):
 
1212
 
+             raise cmdutil.GetHelp
 
1213
 
+@@ -1809,13 +1648,22 @@
 
1214
 
+         if options.id_type == "tagline":
 
1215
 
+             if method != "tagline":
 
1216
 
+                 if not cmdutil.prompt("Tagline in other tree"):
 
1217
 
+-                    if method == "explicit":
 
1218
 
+-                        options.id_type == explicit
 
1219
 
++                    if method == "explicit" or method == "implicit":
 
1220
 
++                        options.id_type == method
 
1222
 
+                         print "add-id not supported for \"%s\" tagging method"\
 
1226
 
++        elif options.id_type == "implicit":
 
1227
 
++            if method != "implicit":
 
1228
 
++                if not cmdutil.prompt("Implicit in other tree"):
 
1229
 
++                    if method == "explicit" or method == "tagline":
 
1230
 
++                        options.id_type == method
 
1232
 
++                        print "add-id not supported for \"%s\" tagging method"\
 
1235
 
+         elif options.id_type == "explicit":
 
1236
 
+             if method != "tagline" and method != explicit:
 
1237
 
+                 if not prompt("Explicit in other tree"):
 
1238
 
+@@ -1824,7 +1672,8 @@
 
1241
 
+         if options.id_type == "auto":
 
1242
 
+-            if method != "tagline" and method != "explicit":
 
1243
 
++            if method != "tagline" and method != "explicit" \
 
1244
 
++                and method !="implicit":
 
1245
 
+                 print "add-id not supported for \"%s\" tagging method" % method
 
1248
 
+@@ -1852,10 +1701,12 @@
 
1249
 
+             previous_files.extend(files)
 
1250
 
+             if id_type == "explicit":
 
1251
 
+                 cmdutil.add_id(files)
 
1252
 
+-            elif id_type == "tagline":
 
1253
 
++            elif id_type == "tagline" or id_type == "implicit":
 
1254
 
+                 for file in files:
 
1256
 
+-                        cmdutil.add_tagline_or_explicit_id(file)
 
1257
 
++                        implicit = (id_type == "implicit")
 
1258
 
++                        cmdutil.add_tagline_or_explicit_id(file, False,
 
1260
 
+                     except cmdutil.AlreadyTagged:
 
1261
 
+                         print "\"%s\" already has a tagline." % file
 
1262
 
+                     except cmdutil.NoCommentSyntax:
 
1263
 
+@@ -1888,6 +1739,9 @@
 
1264
 
+         parser.add_option("--tagline", action="store_const", 
 
1265
 
+                          const="tagline", dest="id_type", 
 
1266
 
+                          help="Use a tagline id")
 
1267
 
++        parser.add_option("--implicit", action="store_const", 
 
1268
 
++                         const="implicit", dest="id_type", 
 
1269
 
++                         help="Use an implicit id (deprecated)")
 
1270
 
+         parser.add_option("--untagged", action="store_true", 
 
1271
 
+                          dest="untagged", default=False, 
 
1272
 
+                          help="tag all untagged files")
 
1273
 
+@@ -1926,27 +1780,7 @@
 
1274
 
+     def get_completer(self, arg, index):
 
1275
 
+         if self.tree is None:
 
1276
 
+             raise arch.errors.TreeRootError
 
1277
 
+-        completions = list(ancillary.iter_partners(self.tree, 
 
1278
 
+-                                                   self.tree.tree_version))
 
1279
 
+-        if len(completions) == 0:
 
1280
 
+-            completions = list(self.tree.iter_log_versions())
 
1284
 
+-            for completion in completions:
 
1285
 
+-                alias = ancillary.compact_alias(str(completion), self.tree)
 
1287
 
+-                    aliases.extend(alias)
 
1289
 
+-            for completion in completions:
 
1290
 
+-                if completion.archive == self.tree.tree_version.archive:
 
1291
 
+-                    aliases.append(completion.nonarch)
 
1293
 
+-        except Exception, e:
 
1296
 
+-        completions.extend(aliases)
 
1297
 
+-        return completions
 
1298
 
++        return cmdutil.merge_completions(self.tree, arg, index)
 
1300
 
+     def do_command(self, cmdargs):
 
1302
 
+@@ -1961,7 +1795,7 @@
 
1304
 
+         if self.tree is None:
 
1305
 
+             raise arch.errors.TreeRootError(os.getcwd())
 
1306
 
+-        if cmdutil.has_changed(self.tree.tree_version):
 
1307
 
++        if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
 
1308
 
+             raise UncommittedChanges(self.tree)
 
1311
 
+@@ -2027,14 +1861,14 @@
 
1312
 
+         :type other_revision: `arch.Revision`
 
1313
 
+         :return: 0 if the merge was skipped, 1 if it was applied
 
1315
 
+-        other_tree = cmdutil.find_or_make_local_revision(other_revision)
 
1316
 
++        other_tree = arch_compound.find_or_make_local_revision(other_revision)
 
1318
 
+             if action == "native-merge":
 
1319
 
+-                ancestor = cmdutil.merge_ancestor2(self.tree, other_tree, 
 
1321
 
++                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
 
1323
 
+             elif action == "update":
 
1324
 
+-                ancestor = cmdutil.tree_latest(self.tree, 
 
1325
 
+-                                               other_revision.version)
 
1326
 
++                ancestor = arch_compound.tree_latest(self.tree, 
 
1327
 
++                                                     other_revision.version)
 
1328
 
+         except CantDetermineRevision, e:
 
1329
 
+             raise CommandFailedWrapper(e)
 
1330
 
+         cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
 
1331
 
+@@ -2104,7 +1938,10 @@
 
1332
 
+         if self.tree is None:
 
1333
 
+             raise arch.errors.TreeRootError
 
1335
 
+-        edit_log(self.tree)
 
1337
 
++            edit_log(self.tree, self.tree.tree_version)
 
1338
 
++        except pylon.errors.NoEditorSpecified, e:
 
1339
 
++            raise pylon.errors.CommandFailedWrapper(e)
 
1341
 
+     def get_parser(self):
 
1343
 
+@@ -2132,7 +1969,7 @@
 
1347
 
+-def edit_log(tree):
 
1348
 
++def edit_log(tree, version):
 
1349
 
+     """Makes and edits the log for a tree.  Does all kinds of fancy things
 
1350
 
+     like log templates and merge summaries and log-for-merge
 
1352
 
+@@ -2141,28 +1978,29 @@
 
1354
 
+     #ensure we have an editor before preparing the log
 
1355
 
+     cmdutil.find_editor()
 
1356
 
+-    log = tree.log_message(create=False)
 
1357
 
++    log = tree.log_message(create=False, version=version)
 
1358
 
+     log_is_new = False
 
1359
 
+     if log is None or cmdutil.prompt("Overwrite log"):
 
1360
 
+         if log is not None:
 
1361
 
+            os.remove(log.name)
 
1362
 
+-        log = tree.log_message(create=True)
 
1363
 
++        log = tree.log_message(create=True, version=version)
 
1366
 
+-        template = tree+"/{arch}/=log-template"
 
1367
 
+-        if not os.path.exists(template):
 
1368
 
+-            template = os.path.expanduser("~/.arch-params/=log-template")
 
1369
 
+-            if not os.path.exists(template):
 
1371
 
++        template = pylon.log_template_path(tree)
 
1373
 
+             shutil.copyfile(template, tmplog)
 
1375
 
+-        new_merges = list(cmdutil.iter_new_merges(tree, 
 
1376
 
+-                                                  tree.tree_version))
 
1377
 
+-        log["Summary"] = merge_summary(new_merges, tree.tree_version)
 
1378
 
++        comp_version = ancillary.comp_revision(tree).version
 
1379
 
++        new_merges = cmdutil.iter_new_merges(tree, comp_version)
 
1380
 
++        new_merges = cmdutil.direct_merges(new_merges)
 
1381
 
++        log["Summary"] = pylon.merge_summary(new_merges, 
 
1383
 
+         if len(new_merges) > 0:   
 
1384
 
+             if cmdutil.prompt("Log for merge"):
 
1385
 
+-                mergestuff = cmdutil.log_for_merge(tree)
 
1386
 
++                if cmdutil.prompt("changelog for merge"):
 
1387
 
++                    mergestuff = "Patches applied:\n"
 
1388
 
++                    mergestuff += pylon.changelog_for_merge(new_merges)
 
1390
 
++                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
 
1391
 
+                 log.description += mergestuff
 
1394
 
+@@ -2172,29 +2010,6 @@
 
1395
 
+             os.remove(log.name)
 
1398
 
+-def merge_summary(new_merges, tree_version):
 
1399
 
+-    if len(new_merges) == 0:
 
1401
 
+-    if len(new_merges) == 1:
 
1402
 
+-        summary = new_merges[0].summary
 
1404
 
+-        summary = "Merge"
 
1407
 
+-    for merge in new_merges:
 
1408
 
+-        if arch.my_id() != merge.creator:
 
1409
 
+-            name = re.sub("<.*>", "", merge.creator).rstrip(" ");
 
1410
 
+-            if not name in credits:
 
1411
 
+-                credits.append(name)
 
1413
 
+-            version = merge.revision.version
 
1414
 
+-            if version.archive == tree_version.archive:
 
1415
 
+-                if not version.nonarch in credits:
 
1416
 
+-                    credits.append(version.nonarch)
 
1417
 
+-            elif not str(version) in credits:
 
1418
 
+-                credits.append(str(version))
 
1420
 
+-    return ("%s (%s)") % (summary, ", ".join(credits))
 
1422
 
+ class MirrorArchive(BaseCommand):
 
1424
 
+@@ -2268,31 +2083,73 @@
 
1426
 
+ Use "alias" to list available (user and automatic) aliases."""
 
1430
 
++"The latest revision in the archive of the tree-version.  You can specify \
 
1431
 
++a different version like so: acur:foo--bar--0 (aliases can be used)",
 
1433
 
++"""(tree current) The latest revision in the tree of the tree-version. \
 
1434
 
++You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
 
1437
 
++"""(tree previous) The previous revision in the tree of the tree-version.  To \
 
1438
 
++specify an older revision, use a number, e.g. "tprev:4" """,
 
1440
 
++"""(tree ancestor) The ancestor revision of the tree To specify an older \
 
1441
 
++revision, use a number, e.g. "tanc:4".""",
 
1443
 
++"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
 
1445
 
++""" (tree modified) The latest revision to modify a given file, e.g. \
 
1446
 
++"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
 
1448
 
++"""(tree tag) The revision that was tagged into the current tree revision, \
 
1449
 
++according to the tree""",
 
1451
 
++"""(tag current) The latest revision of the version that the current tree \
 
1452
 
++was tagged from.""",
 
1454
 
++"""The common ancestor of the current tree and the specified revision. \
 
1455
 
++Defaults to the first partner-version's latest revision or to tagcur.""",
 
1459
 
++def is_auto_alias(name):
 
1460
 
++    """Determine whether a name is an auto alias name
 
1462
 
++    :param name: the name to check
 
1464
 
++    :return: True if the name is an auto alias, false if not
 
1467
 
++    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
 
1470
 
++def display_def(iter, wrap = 80):
 
1471
 
++    """Display a list of definitions
 
1473
 
++    :param iter: iter of name, definition pairs
 
1474
 
++    :type iter: iter of (str, str)
 
1475
 
++    :param wrap: The width for text wrapping
 
1478
 
++    vals = list(iter)
 
1480
 
++    for (key, value) in vals:
 
1481
 
++        if len(key) > maxlen:
 
1482
 
++            maxlen = len(key)
 
1483
 
++    for (key, value) in vals:
 
1484
 
++        tw=textwrap.TextWrapper(width=wrap, 
 
1485
 
++                                initial_indent=key.rjust(maxlen)+" : ",
 
1486
 
++                                subsequent_indent="".rjust(maxlen+3))
 
1487
 
++        print tw.fill(value)
 
1490
 
+ def help_aliases(tree):
 
1491
 
+-    print """Auto-generated aliases
 
1492
 
+- acur : The latest revision in the archive of the tree-version.  You can specfy
 
1493
 
+-        a different version like so: acur:foo--bar--0 (aliases can be used)
 
1494
 
+- tcur : (tree current) The latest revision in the tree of the tree-version.
 
1495
 
+-        You can specify a different version like so: tcur:foo--bar--0 (aliases
 
1497
 
+-tprev : (tree previous) The previous revision in the tree of the tree-version.
 
1498
 
+-        To specify an older revision, use a number, e.g. "tprev:4"
 
1499
 
+- tanc : (tree ancestor) The ancestor revision of the tree
 
1500
 
+-        To specify an older revision, use a number, e.g. "tanc:4"
 
1501
 
+-tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
 
1502
 
+- tmod : (tree modified) The latest revision to modify a given file 
 
1503
 
+-        (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
 
1504
 
+- ttag : (tree tag) The revision that was tagged into the current tree revision,
 
1505
 
+-        according to the tree.
 
1506
 
+-tagcur: (tag current) The latest revision of the version that the current tree
 
1508
 
+-mergeanc : The common ancestor of the current tree and the specified revision.
 
1509
 
+-        Defaults to the first partner-version's latest revision or to tagcur.
 
1511
 
++    print """Auto-generated aliases"""
 
1512
 
++    display_def(pylon.util.iter_pairs(auto_alias))
 
1513
 
+     print "User aliases"
 
1514
 
+-    for parts in ancillary.iter_all_alias(tree):
 
1515
 
+-        print parts[0].rjust(10)+" : "+parts[1]
 
1517
 
++    display_def(ancillary.iter_all_alias(tree))
 
1519
 
+ class Inventory(BaseCommand):
 
1520
 
+     """List the status of files in the tree"""
 
1521
 
+@@ -2428,6 +2285,11 @@
 
1522
 
+         except cmdutil.ForbiddenAliasSyntax, e:
 
1523
 
+             raise CommandFailedWrapper(e)
 
1525
 
++    def no_prefix(self, alias):
 
1526
 
++        if alias.startswith("^"):
 
1527
 
++            alias = alias[1:]
 
1530
 
+     def arg_dispatch(self, args, options):
 
1531
 
+         """Add, modify, or list aliases, depending on number of arguments
 
1533
 
+@@ -2438,15 +2300,20 @@
 
1534
 
+         if len(args) == 0:
 
1535
 
+             help_aliases(self.tree)
 
1537
 
+-        elif len(args) == 1:
 
1538
 
+-            self.print_alias(args[0])
 
1539
 
+-        elif (len(args)) == 2:
 
1540
 
+-            self.add(args[0], args[1], options)
 
1542
 
+-            raise cmdutil.GetHelp
 
1543
 
++            alias = self.no_prefix(args[0])
 
1544
 
++            if len(args) == 1:
 
1545
 
++                self.print_alias(alias)
 
1546
 
++            elif (len(args)) == 2:
 
1547
 
++                self.add(alias, args[1], options)
 
1549
 
++                raise cmdutil.GetHelp
 
1551
 
+     def print_alias(self, alias):
 
1553
 
++        if is_auto_alias(alias):
 
1554
 
++            raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
 
1555
 
++                "  Use \"revision\" to expand auto aliases." % alias)
 
1556
 
+         for pair in ancillary.iter_all_alias(self.tree):
 
1557
 
+             if pair[0] == alias:
 
1559
 
+@@ -2464,6 +2331,8 @@
 
1560
 
+         :type expansion: str
 
1561
 
+         :param options: The commandline options
 
1563
 
++        if is_auto_alias(alias):
 
1564
 
++            raise IsAutoAlias(alias)
 
1567
 
+         new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
 
1568
 
+@@ -2490,14 +2359,17 @@
 
1570
 
+         if len(args) != 1:
 
1571
 
+             raise cmdutil.GetHelp
 
1572
 
++        alias = self.no_prefix(args[0])
 
1573
 
++        if is_auto_alias(alias):
 
1574
 
++            raise IsAutoAlias(alias)
 
1576
 
+         for pair in self.get_iterator(options):
 
1577
 
+-            if pair[0] != args[0]:
 
1578
 
++            if pair[0] != alias:
 
1579
 
+                 newlist+="%s=%s\n" % (pair[0], pair[1])
 
1583
 
+-            raise errors.NoSuchAlias(args[0])
 
1584
 
++            raise errors.NoSuchAlias(alias)
 
1585
 
+         self.write_aliases(newlist, options)
 
1587
 
+     def get_alias_file(self, options):
 
1588
 
+@@ -2526,7 +2398,7 @@
 
1589
 
+         :param options: The commandline options
 
1591
 
+         filename = os.path.expanduser(self.get_alias_file(options))
 
1592
 
+-        file = cmdutil.NewFileVersion(filename)
 
1593
 
++        file = util.NewFileVersion(filename)
 
1594
 
+         file.write(newlist)
 
1597
 
+@@ -2588,10 +2460,13 @@
 
1598
 
+         :param cmdargs: The commandline arguments
 
1599
 
+         :type cmdargs: list of str
 
1601
 
+-        cmdutil.find_editor()
 
1602
 
+         parser = self.get_parser()
 
1603
 
+         (options, args) = parser.parse_args(cmdargs)
 
1605
 
++            cmdutil.find_editor()
 
1606
 
++        except pylon.errors.NoEditorSpecified, e:
 
1607
 
++            raise pylon.errors.CommandFailedWrapper(e)
 
1609
 
+             self.tree=arch.tree_root()
 
1612
 
+@@ -2655,7 +2530,7 @@
 
1613
 
+             target_revision = cmdutil.determine_revision_arch(self.tree, 
 
1616
 
+-            target_revision = cmdutil.tree_latest(self.tree)
 
1617
 
++            target_revision = arch_compound.tree_latest(self.tree)
 
1619
 
+             merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
 
1620
 
+                        self.tree, f)) for f in args[1:] ]
 
1621
 
+@@ -2711,7 +2586,7 @@
 
1623
 
+         :param message: The message to send
 
1624
 
+         :type message: `email.Message`"""
 
1625
 
+-        server = smtplib.SMTP()
 
1626
 
++        server = smtplib.SMTP("localhost")
 
1627
 
+         server.sendmail(message['From'], message['To'], message.as_string())
 
1630
 
+@@ -2763,6 +2638,22 @@
 
1632
 
+ 'request-merge': RequestMerge,
 
1635
 
++def my_import(mod_name):
 
1636
 
++    module = __import__(mod_name)
 
1637
 
++    components = mod_name.split('.')
 
1638
 
++    for comp in components[1:]:
 
1639
 
++        module = getattr(module, comp)
 
1642
 
++def plugin(mod_name):
 
1643
 
++    module = my_import(mod_name)
 
1644
 
++    module.add_command(commands)
 
1646
 
++for file in os.listdir(sys.path[0]+"/command"):
 
1647
 
++    if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
 
1648
 
++        plugin("command."+file[:-3])
 
1651
 
+ 'apply-delta' : "Try \"apply-changes\".",
 
1652
 
+ 'delta' : "To compare two revisions, use \"changes\".",
 
1653
 
+@@ -2784,6 +2675,7 @@
 
1654
 
+ 'tagline' : "Use add-id.  It uses taglines in tagline trees",
 
1655
 
+ 'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
 
1656
 
+ 'library-revisions' : "Use revisions --library",
 
1657
 
+-'file-revert' : "Use revert FILE"
 
1658
 
++'file-revert' : "Use revert FILE",
 
1659
 
++'join-branch' : "Use replay --logs-only"
 
1661
 
+ # arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
 
1663
 
*** added file 'testdata/insert_top.patch'
 
1665
 
+++ testdata/insert_top.patch 
 
1667
 
+--- orig/pylon/patches.py
 
1668
 
++++ mod/pylon/patches.py
 
1673
 
+ class PatchSyntax(Exception):
 
1675
 
*** added file 'testdata/mod'
 
1679
 
+# Copyright (C) 2004 Aaron Bentley
 
1680
 
+# <aaron.bentley@utoronto.ca>
 
1682
 
+#    This program is free software; you can redistribute it and/or modify
 
1683
 
+#    it under the terms of the GNU General Public License as published by
 
1684
 
+#    the Free Software Foundation; either version 2 of the License, or
 
1685
 
+#    (at your option) any later version.
 
1687
 
+#    This program is distributed in the hope that it will be useful,
 
1688
 
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
1689
 
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
1690
 
+#    GNU General Public License for more details.
 
1692
 
+#    You should have received a copy of the GNU General Public License
 
1693
 
+#    along with this program; if not, write to the Free Software
 
1694
 
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
1701
 
+import pylon.errors
 
1702
 
+from pylon.errors import *
 
1703
 
+from pylon import errors
 
1704
 
+from pylon import util
 
1705
 
+from pylon import arch_core
 
1706
 
+from pylon import arch_compound
 
1707
 
+from pylon import ancillary
 
1708
 
+from pylon import misc
 
1709
 
+from pylon import paths 
 
1726
 
+__docformat__ = "restructuredtext"
 
1727
 
+__doc__ = "Implementation of user (sub) commands"
 
1730
 
+def find_command(cmd):
 
1732
 
+    Return an instance of a command type.  Return None if the type isn't
 
1735
 
+    :param cmd: the name of the command to look for
 
1736
 
+    :type cmd: the type of the command
 
1738
 
+    if commands.has_key(cmd):
 
1739
 
+        return commands[cmd]()
 
1744
 
+    def __call__(self, cmdline):
 
1746
 
+            self.do_command(cmdline.split())
 
1747
 
+        except cmdutil.GetHelp, e:
 
1749
 
+        except Exception, e:
 
1752
 
+    def get_completer(index):
 
1755
 
+    def complete(self, args, text):
 
1757
 
+        Returns a list of possible completions for the given text.
 
1759
 
+        :param args: The complete list of arguments
 
1760
 
+        :type args: List of str
 
1761
 
+        :param text: text to complete (may be shorter than args[-1])
 
1763
 
+        :rtype: list of str
 
1769
 
+            realtext = args[-1]
 
1774
 
+            parser=self.get_parser()
 
1775
 
+            if realtext.startswith('-'):
 
1776
 
+                candidates = parser.iter_options()
 
1778
 
+                (options, parsed_args) = parser.parse_args(args)
 
1780
 
+                if len (parsed_args) > 0:
 
1781
 
+                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
 
1783
 
+                    candidates = self.get_completer("", 0)
 
1786
 
+        if candidates is None:
 
1788
 
+        for candidate in candidates:
 
1789
 
+            candidate = str(candidate)
 
1790
 
+            if candidate.startswith(realtext):
 
1791
 
+                matches.append(candidate[len(realtext)- len(text):])
 
1795
 
+class Help(BaseCommand):
 
1797
 
+    Lists commands, prints help messages.
 
1799
 
+    def __init__(self):
 
1800
 
+        self.description="Prints help mesages"
 
1801
 
+        self.parser = None
 
1803
 
+    def do_command(self, cmdargs):
 
1805
 
+        Prints a help message.
 
1807
 
+        options, args = self.get_parser().parse_args(cmdargs)
 
1809
 
+            raise cmdutil.GetHelp
 
1811
 
+        if options.native or options.suggestions or options.external:
 
1812
 
+            native = options.native
 
1813
 
+            suggestions = options.suggestions
 
1814
 
+            external = options.external
 
1817
 
+            suggestions = False
 
1820
 
+        if len(args) == 0:
 
1821
 
+            self.list_commands(native, suggestions, external)
 
1823
 
+        elif len(args) == 1:
 
1824
 
+            command_help(args[0])
 
1828
 
+        self.get_parser().print_help()
 
1830
 
+If no command is specified, commands are listed.  If a command is
 
1831
 
+specified, help for that command is listed.
 
1834
 
+    def get_parser(self):
 
1836
 
+        Returns the options parser to use for the "revision" command.
 
1838
 
+        :rtype: cmdutil.CmdOptionParser
 
1840
 
+        if self.parser is not None:
 
1841
 
+            return self.parser
 
1842
 
+        parser=cmdutil.CmdOptionParser("fai help [command]")
 
1843
 
+        parser.add_option("-n", "--native", action="store_true", 
 
1844
 
+                         dest="native", help="Show native commands")
 
1845
 
+        parser.add_option("-e", "--external", action="store_true", 
 
1846
 
+                         dest="external", help="Show external commands")
 
1847
 
+        parser.add_option("-s", "--suggest", action="store_true", 
 
1848
 
+                         dest="suggestions", help="Show suggestions")
 
1849
 
+        self.parser = parser
 
1852
 
+    def list_commands(self, native=True, suggest=False, external=True):
 
1854
 
+        Lists supported commands.
 
1856
 
+        :param native: list native, python-based commands
 
1857
 
+        :type native: bool
 
1858
 
+        :param external: list external aba-style commands
 
1859
 
+        :type external: bool
 
1862
 
+            print "Native Fai commands"
 
1863
 
+            keys=commands.keys()
 
1867
 
+                for i in range(28-len(k)):
 
1869
 
+                print space+k+" : "+commands[k]().description
 
1872
 
+            print "Unavailable commands and suggested alternatives"
 
1873
 
+            key_list = suggestions.keys()
 
1875
 
+            for key in key_list:
 
1876
 
+                print "%28s : %s" % (key, suggestions[key])
 
1879
 
+            fake_aba = abacmds.AbaCmds()
 
1880
 
+            if (fake_aba.abadir == ""):
 
1882
 
+            print "External commands"
 
1883
 
+            fake_aba.list_commands()
 
1886
 
+            print "Use help --suggest to list alternatives to tla and aba"\
 
1888
 
+        if options.tla_fallthrough and (native or external):
 
1889
 
+            print "Fai also supports tla commands."
 
1891
 
+def command_help(cmd):
 
1893
 
+    Prints help for a command.
 
1895
 
+    :param cmd: The name of the command to print help for
 
1898
 
+    fake_aba = abacmds.AbaCmds()
 
1899
 
+    cmdobj = find_command(cmd)
 
1900
 
+    if cmdobj != None:
 
1902
 
+    elif suggestions.has_key(cmd):
 
1903
 
+        print "Not available\n" + suggestions[cmd]
 
1905
 
+        abacmd = fake_aba.is_command(cmd)
 
1909
 
+            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
 
1913
 
+class Changes(BaseCommand):
 
1915
 
+    the "changes" command: lists differences between trees/revisions:
 
1918
 
+    def __init__(self):
 
1919
 
+        self.description="Lists what files have changed in the project tree"
 
1921
 
+    def get_completer(self, arg, index):
 
1925
 
+            tree = arch.tree_root()
 
1928
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
1930
 
+    def parse_commandline(self, cmdline):
 
1932
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
 
1934
 
+        :param cmdline: A list of arguments to parse
 
1935
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
1937
 
+        parser=self.get_parser()
 
1938
 
+        (options, args) = parser.parse_args(cmdline)
 
1940
 
+            raise cmdutil.GetHelp
 
1942
 
+        tree=arch.tree_root()
 
1943
 
+        if len(args) == 0:
 
1944
 
+            a_spec = ancillary.comp_revision(tree)
 
1946
 
+            a_spec = cmdutil.determine_revision_tree(tree, args[0])
 
1947
 
+        cmdutil.ensure_archive_registered(a_spec.archive)
 
1948
 
+        if len(args) == 2:
 
1949
 
+            b_spec = cmdutil.determine_revision_tree(tree, args[1])
 
1950
 
+            cmdutil.ensure_archive_registered(b_spec.archive)
 
1953
 
+        return options, a_spec, b_spec
 
1955
 
+    def do_command(self, cmdargs):
 
1957
 
+        Master function that perfoms the "changes" command.
 
1960
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs);
 
1961
 
+        except cmdutil.CantDetermineRevision, e:
 
1964
 
+        except arch.errors.TreeRootError, e:
 
1967
 
+        if options.changeset:
 
1968
 
+            changeset=options.changeset
 
1971
 
+            tmpdir=util.tmpdir()
 
1972
 
+            changeset=tmpdir+"/changeset"
 
1974
 
+            delta=arch.iter_delta(a_spec, b_spec, changeset)
 
1976
 
+                for line in delta:
 
1977
 
+                    if cmdutil.chattermatch(line, "changeset:"):
 
1980
 
+                        cmdutil.colorize(line, options.suppress_chatter)
 
1981
 
+            except arch.util.ExecProblem, e:
 
1982
 
+                if e.proc.error and e.proc.error.startswith(
 
1983
 
+                    "missing explicit id for file"):
 
1984
 
+                    raise MissingID(e)
 
1987
 
+            status=delta.status
 
1990
 
+            if (options.perform_diff):
 
1991
 
+                chan = arch_compound.ChangesetMunger(changeset)
 
1992
 
+                chan.read_indices()
 
1993
 
+                if options.diffopts is not None:
 
1994
 
+                    if isinstance(b_spec, arch.Revision):
 
1995
 
+                        b_dir = b_spec.library_find()
 
1998
 
+                    a_dir = a_spec.library_find()
 
1999
 
+                    diffopts = options.diffopts.split()
 
2000
 
+                    cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
 
2002
 
+                    cmdutil.show_diffs(delta.changeset)
 
2004
 
+            if tmpdir and (os.access(tmpdir, os.X_OK)):
 
2005
 
+                shutil.rmtree(tmpdir)
 
2007
 
+    def get_parser(self):
 
2009
 
+        Returns the options parser to use for the "changes" command.
 
2011
 
+        :rtype: cmdutil.CmdOptionParser
 
2013
 
+        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
 
2015
 
+        parser.add_option("-d", "--diff", action="store_true", 
 
2016
 
+                          dest="perform_diff", default=False, 
 
2017
 
+                          help="Show diffs in summary")
 
2018
 
+        parser.add_option("-c", "--changeset", dest="changeset", 
 
2019
 
+                          help="Store a changeset in the given directory", 
 
2020
 
+                          metavar="DIRECTORY")
 
2021
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
2022
 
+                          dest="suppress_chatter", default=False, 
 
2023
 
+                          help="Suppress chatter messages")
 
2024
 
+        parser.add_option("--diffopts", dest="diffopts", 
 
2025
 
+                          help="Use the specified diff options", 
 
2026
 
+                          metavar="OPTIONS")
 
2030
 
+    def help(self, parser=None):
 
2032
 
+        Prints a help message.
 
2034
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2035
 
+        not supplied, it is retrieved.
 
2036
 
+        :type parser: cmdutil.CmdOptionParser
 
2038
 
+        if parser is None:
 
2039
 
+            parser=self.get_parser()
 
2040
 
+        parser.print_help()
 
2042
 
+Performs source-tree comparisons
 
2044
 
+If no revision is specified, the current project tree is compared to the
 
2045
 
+last-committed revision.  If one revision is specified, the current project
 
2046
 
+tree is compared to that revision.  If two revisions are specified, they are
 
2047
 
+compared to each other.
 
2053
 
+class ApplyChanges(BaseCommand):
 
2055
 
+    Apply differences between two revisions to a tree
 
2058
 
+    def __init__(self):
 
2059
 
+        self.description="Applies changes to a project tree"
 
2061
 
+    def get_completer(self, arg, index):
 
2065
 
+            tree = arch.tree_root()
 
2068
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
2070
 
+    def parse_commandline(self, cmdline, tree):
 
2072
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
 
2074
 
+        :param cmdline: A list of arguments to parse
 
2075
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
2077
 
+        parser=self.get_parser()
 
2078
 
+        (options, args) = parser.parse_args(cmdline)
 
2079
 
+        if len(args) != 2:
 
2080
 
+            raise cmdutil.GetHelp
 
2082
 
+        a_spec = cmdutil.determine_revision_tree(tree, args[0])
 
2083
 
+        cmdutil.ensure_archive_registered(a_spec.archive)
 
2084
 
+        b_spec = cmdutil.determine_revision_tree(tree, args[1])
 
2085
 
+        cmdutil.ensure_archive_registered(b_spec.archive)
 
2086
 
+        return options, a_spec, b_spec
 
2088
 
+    def do_command(self, cmdargs):
 
2090
 
+        Master function that performs "apply-changes".
 
2093
 
+            tree = arch.tree_root()
 
2094
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
 
2095
 
+        except cmdutil.CantDetermineRevision, e:
 
2098
 
+        except arch.errors.TreeRootError, e:
 
2101
 
+        delta=cmdutil.apply_delta(a_spec, b_spec, tree)
 
2102
 
+        for line in cmdutil.iter_apply_delta_filter(delta):
 
2103
 
+            cmdutil.colorize(line, options.suppress_chatter)
 
2105
 
+    def get_parser(self):
 
2107
 
+        Returns the options parser to use for the "apply-changes" command.
 
2109
 
+        :rtype: cmdutil.CmdOptionParser
 
2111
 
+        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
 
2113
 
+        parser.add_option("-d", "--diff", action="store_true", 
 
2114
 
+                          dest="perform_diff", default=False, 
 
2115
 
+                          help="Show diffs in summary")
 
2116
 
+        parser.add_option("-c", "--changeset", dest="changeset", 
 
2117
 
+                          help="Store a changeset in the given directory", 
 
2118
 
+                          metavar="DIRECTORY")
 
2119
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
2120
 
+                          dest="suppress_chatter", default=False, 
 
2121
 
+                          help="Suppress chatter messages")
 
2124
 
+    def help(self, parser=None):
 
2126
 
+        Prints a help message.
 
2128
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2129
 
+        not supplied, it is retrieved.
 
2130
 
+        :type parser: cmdutil.CmdOptionParser
 
2132
 
+        if parser is None:
 
2133
 
+            parser=self.get_parser()
 
2134
 
+        parser.print_help()
 
2136
 
+Applies changes to a project tree
 
2138
 
+Compares two revisions and applies the difference between them to the current
 
2144
 
+class Update(BaseCommand):
 
2146
 
+    Updates a project tree to a given revision, preserving un-committed hanges. 
 
2149
 
+    def __init__(self):
 
2150
 
+        self.description="Apply the latest changes to the current directory"
 
2152
 
+    def get_completer(self, arg, index):
 
2156
 
+            tree = arch.tree_root()
 
2159
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
2161
 
+    def parse_commandline(self, cmdline, tree):
 
2163
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
 
2165
 
+        :param cmdline: A list of arguments to parse
 
2166
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
2168
 
+        parser=self.get_parser()
 
2169
 
+        (options, args) = parser.parse_args(cmdline)
 
2171
 
+            raise cmdutil.GetHelp
 
2176
 
+        revision=cmdutil.determine_revision_arch(tree, spec)
 
2177
 
+        cmdutil.ensure_archive_registered(revision.archive)
 
2179
 
+        mirror_source = cmdutil.get_mirror_source(revision.archive)
 
2180
 
+        if mirror_source != None:
 
2181
 
+            if cmdutil.prompt("Mirror update"):
 
2182
 
+                cmd=cmdutil.mirror_archive(mirror_source, 
 
2183
 
+                    revision.archive, arch.NameParser(revision).get_package_version())
 
2184
 
+                for line in arch.chatter_classifier(cmd):
 
2185
 
+                    cmdutil.colorize(line, options.suppress_chatter)
 
2187
 
+                revision=cmdutil.determine_revision_arch(tree, spec)
 
2189
 
+        return options, revision 
 
2191
 
+    def do_command(self, cmdargs):
 
2193
 
+        Master function that perfoms the "update" command.
 
2195
 
+        tree=arch.tree_root()
 
2197
 
+            options, to_revision = self.parse_commandline(cmdargs, tree);
 
2198
 
+        except cmdutil.CantDetermineRevision, e:
 
2201
 
+        except arch.errors.TreeRootError, e:
 
2204
 
+        from_revision = arch_compound.tree_latest(tree)
 
2205
 
+        if from_revision==to_revision:
 
2206
 
+            print "Tree is already up to date with:\n"+str(to_revision)+"."
 
2208
 
+        cmdutil.ensure_archive_registered(from_revision.archive)
 
2209
 
+        cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
 
2210
 
+            options.patch_forward)
 
2211
 
+        for line in cmdutil.iter_apply_delta_filter(cmd):
 
2212
 
+            cmdutil.colorize(line)
 
2213
 
+        if to_revision.version != tree.tree_version:
 
2214
 
+            if cmdutil.prompt("Update version"):
 
2215
 
+                tree.tree_version = to_revision.version
 
2217
 
+    def get_parser(self):
 
2219
 
+        Returns the options parser to use for the "update" command.
 
2221
 
+        :rtype: cmdutil.CmdOptionParser
 
2223
 
+        parser=cmdutil.CmdOptionParser("fai update [options]"
 
2224
 
+                                       " [revision/version]")
 
2225
 
+        parser.add_option("-f", "--forward", action="store_true", 
 
2226
 
+                          dest="patch_forward", default=False, 
 
2227
 
+                          help="pass the --forward option to 'patch'")
 
2228
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
2229
 
+                          dest="suppress_chatter", default=False, 
 
2230
 
+                          help="Suppress chatter messages")
 
2233
 
+    def help(self, parser=None):
 
2235
 
+        Prints a help message.
 
2237
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2238
 
+        not supplied, it is retrieved.
 
2239
 
+        :type parser: cmdutil.CmdOptionParser
 
2241
 
+        if parser is None:
 
2242
 
+            parser=self.get_parser()
 
2243
 
+        parser.print_help()
 
2245
 
+Updates a working tree to the current archive revision
 
2247
 
+If a revision or version is specified, that is used instead 
 
2253
 
+class Commit(BaseCommand):
 
2255
 
+    Create a revision based on the changes in the current tree.
 
2258
 
+    def __init__(self):
 
2259
 
+        self.description="Write local changes to the archive"
 
2261
 
+    def get_completer(self, arg, index):
 
2264
 
+        return iter_modified_file_completions(arch.tree_root(), arg)
 
2265
 
+#        return iter_source_file_completions(arch.tree_root(), arg)
 
2267
 
+    def parse_commandline(self, cmdline, tree):
 
2269
 
+        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
 
2271
 
+        :param cmdline: A list of arguments to parse
 
2272
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
2274
 
+        parser=self.get_parser()
 
2275
 
+        (options, args) = parser.parse_args(cmdline)
 
2277
 
+        if len(args) == 0:
 
2279
 
+        if options.version is None:
 
2280
 
+            return options, tree.tree_version, args
 
2282
 
+        revision=cmdutil.determine_revision_arch(tree, options.version)
 
2283
 
+        return options, revision.get_version(), args
 
2285
 
+    def do_command(self, cmdargs):
 
2287
 
+        Master function that perfoms the "commit" command.
 
2289
 
+        tree=arch.tree_root()
 
2290
 
+        options, version, files = self.parse_commandline(cmdargs, tree)
 
2292
 
+        if options.__dict__.has_key("base") and options.base:
 
2293
 
+            base = cmdutil.determine_revision_tree(tree, options.base)
 
2296
 
+            base = ancillary.submit_revision(tree)
 
2298
 
+        if ancestor is None:
 
2299
 
+            ancestor = arch_compound.tree_latest(tree, version)
 
2301
 
+        writeversion=version
 
2302
 
+        archive=version.archive
 
2303
 
+        source=cmdutil.get_mirror_source(archive)
 
2305
 
+        writethrough="implicit"
 
2308
 
+            if writethrough=="explicit" and \
 
2309
 
+                cmdutil.prompt("Writethrough"):
 
2310
 
+                writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
 
2311
 
+            elif writethrough=="none":
 
2312
 
+                raise CommitToMirror(archive)
 
2314
 
+        elif archive.is_mirror:
 
2315
 
+            raise CommitToMirror(archive)
 
2318
 
+            last_revision=tree.iter_logs(version, True).next().revision
 
2319
 
+        except StopIteration, e:
 
2320
 
+            last_revision = None
 
2321
 
+            if ancestor is None:
 
2322
 
+                if cmdutil.prompt("Import from commit"):
 
2323
 
+                    return do_import(version)
 
2325
 
+                    raise NoVersionLogs(version)
 
2327
 
+            arch_last_revision = version.iter_revisions(True).next()
 
2328
 
+        except StopIteration, e:
 
2329
 
+            arch_last_revision = None
 
2331
 
+        if last_revision != arch_last_revision:
 
2332
 
+            print "Tree is not up to date with %s" % str(version)
 
2333
 
+            if not cmdutil.prompt("Out of date"):
 
2339
 
+            if not cmdutil.has_changed(ancestor):
 
2340
 
+                if not cmdutil.prompt("Empty commit"):
 
2342
 
+        except arch.util.ExecProblem, e:
 
2343
 
+            if e.proc.error and e.proc.error.startswith(
 
2344
 
+                "missing explicit id for file"):
 
2345
 
+                raise MissingID(e)
 
2348
 
+        log = tree.log_message(create=False, version=version)
 
2351
 
+                if cmdutil.prompt("Create log"):
 
2352
 
+                    edit_log(tree, version)
 
2354
 
+            except cmdutil.NoEditorSpecified, e:
 
2355
 
+                raise CommandFailed(e)
 
2356
 
+            log = tree.log_message(create=False, version=version)
 
2358
 
+            raise NoLogMessage
 
2359
 
+        if log["Summary"] is None or len(log["Summary"].strip()) == 0:
 
2360
 
+            if not cmdutil.prompt("Omit log summary"):
 
2361
 
+                raise errors.NoLogSummary
 
2363
 
+            for line in tree.iter_commit(version, seal=options.seal_version,
 
2364
 
+                base=base, out_of_date_ok=allow_old, file_list=files):
 
2365
 
+                cmdutil.colorize(line, options.suppress_chatter)
 
2367
 
+        except arch.util.ExecProblem, e:
 
2368
 
+            if e.proc.error and e.proc.error.startswith(
 
2369
 
+                "These files violate naming conventions:"):
 
2370
 
+                raise LintFailure(e.proc.error)
 
2374
 
+    def get_parser(self):
 
2376
 
+        Returns the options parser to use for the "commit" command.
 
2378
 
+        :rtype: cmdutil.CmdOptionParser
 
2381
 
+        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
 
2383
 
+        parser.add_option("--seal", action="store_true", 
 
2384
 
+                          dest="seal_version", default=False, 
 
2385
 
+                          help="seal this version")
 
2386
 
+        parser.add_option("-v", "--version", dest="version", 
 
2387
 
+                          help="Use the specified version", 
 
2388
 
+                          metavar="VERSION")
 
2389
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
2390
 
+                          dest="suppress_chatter", default=False, 
 
2391
 
+                          help="Suppress chatter messages")
 
2392
 
+        if cmdutil.supports_switch("commit", "--base"):
 
2393
 
+            parser.add_option("--base", dest="base", help="", 
 
2394
 
+                              metavar="REVISION")
 
2397
 
+    def help(self, parser=None):
 
2399
 
+        Prints a help message.
 
2401
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2402
 
+        not supplied, it is retrieved.
 
2403
 
+        :type parser: cmdutil.CmdOptionParser
 
2405
 
+        if parser is None:
 
2406
 
+            parser=self.get_parser()
 
2407
 
+        parser.print_help()
 
2409
 
+Updates a working tree to the current archive revision
 
2411
 
+If a version is specified, that is used instead 
 
2418
 
+class CatLog(BaseCommand):
 
2420
 
+    Print the log of a given file (from current tree)
 
2422
 
+    def __init__(self):
 
2423
 
+        self.description="Prints the patch log for a revision"
 
2425
 
+    def get_completer(self, arg, index):
 
2429
 
+            tree = arch.tree_root()
 
2432
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
2434
 
+    def do_command(self, cmdargs):
 
2436
 
+        Master function that perfoms the "cat-log" command.
 
2438
 
+        parser=self.get_parser()
 
2439
 
+        (options, args) = parser.parse_args(cmdargs)
 
2441
 
+            tree = arch.tree_root()
 
2442
 
+        except arch.errors.TreeRootError, e:
 
2448
 
+            raise cmdutil.GetHelp()
 
2451
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
 
2453
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
 
2454
 
+        except cmdutil.CantDetermineRevision, e:
 
2455
 
+            raise CommandFailedWrapper(e)
 
2458
 
+        use_tree = (options.source == "tree" or \
 
2459
 
+            (options.source == "any" and tree))
 
2460
 
+        use_arch = (options.source == "archive" or options.source == "any")
 
2464
 
+            for log in tree.iter_logs(revision.get_version()):
 
2465
 
+                if log.revision == revision:
 
2469
 
+        if log is None and use_arch:
 
2470
 
+            cmdutil.ensure_revision_exists(revision)
 
2471
 
+            log = arch.Patchlog(revision)
 
2472
 
+        if log is not None:
 
2473
 
+            for item in log.items():
 
2474
 
+                print "%s: %s" % item
 
2475
 
+            print log.description
 
2477
 
+    def get_parser(self):
 
2479
 
+        Returns the options parser to use for the "cat-log" command.
 
2481
 
+        :rtype: cmdutil.CmdOptionParser
 
2483
 
+        parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
 
2484
 
+        parser.add_option("--archive", action="store_const", dest="source",
 
2485
 
+                          const="archive", default="any",
 
2486
 
+                          help="Always get the log from the archive")
 
2487
 
+        parser.add_option("--tree", action="store_const", dest="source",
 
2488
 
+                          const="tree", help="Always get the log from the tree")
 
2491
 
+    def help(self, parser=None):
 
2493
 
+        Prints a help message.
 
2495
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2496
 
+        not supplied, it is retrieved.
 
2497
 
+        :type parser: cmdutil.CmdOptionParser
 
2500
 
+            parser=self.get_parser()
 
2501
 
+        parser.print_help()
 
2503
 
+Prints the log for the specified revision
 
2508
 
+class Revert(BaseCommand):
 
2509
 
+    """ Reverts a tree (or aspects of it) to a revision
 
2511
 
+    def __init__(self):
 
2512
 
+        self.description="Reverts a tree (or aspects of it) to a revision "
 
2514
 
+    def get_completer(self, arg, index):
 
2518
 
+            tree = arch.tree_root()
 
2521
 
+        return iter_modified_file_completions(tree, arg)
 
2523
 
+    def do_command(self, cmdargs):
 
2525
 
+        Master function that perfoms the "revert" command.
 
2527
 
+        parser=self.get_parser()
 
2528
 
+        (options, args) = parser.parse_args(cmdargs)
 
2530
 
+            tree = arch.tree_root()
 
2531
 
+        except arch.errors.TreeRootError, e:
 
2532
 
+            raise CommandFailed(e)
 
2534
 
+        if options.revision is not None:
 
2535
 
+            spec=options.revision
 
2537
 
+            if spec is not None:
 
2538
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
 
2540
 
+                revision = ancillary.comp_revision(tree)
 
2541
 
+        except cmdutil.CantDetermineRevision, e:
 
2542
 
+            raise CommandFailedWrapper(e)
 
2545
 
+        if options.file_contents or options.file_perms or options.deletions\
 
2546
 
+            or options.additions or options.renames or options.hunk_prompt:
 
2547
 
+            munger = arch_compound.MungeOpts()
 
2548
 
+            munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
 
2549
 
+                                   options.hunk_prompt)
 
2551
 
+        if len(args) > 0 or options.logs or options.pattern_files or \
 
2553
 
+            if munger is None:
 
2554
 
+                munger = cmdutil.arch_compound.MungeOpts(True)
 
2555
 
+                munger.all_types(True)
 
2557
 
+            t_cwd = arch_compound.tree_cwd(tree)
 
2559
 
+                if len(t_cwd) > 0:
 
2561
 
+                name = "./" + t_cwd + name
 
2562
 
+                munger.add_keep_file(name);
 
2564
 
+        if options.file_perms:
 
2565
 
+            munger.file_perms = True
 
2566
 
+        if options.file_contents:
 
2567
 
+            munger.file_contents = True
 
2568
 
+        if options.deletions:
 
2569
 
+            munger.deletions = True
 
2570
 
+        if options.additions:
 
2571
 
+            munger.additions = True
 
2572
 
+        if options.renames:
 
2573
 
+            munger.renames = True
 
2575
 
+            munger.add_keep_pattern('^\./\{arch\}/[^=].*')
 
2576
 
+        if options.control:
 
2577
 
+            munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
 
2578
 
+                                    "/\.arch-inventory$")
 
2579
 
+        if options.pattern_files:
 
2580
 
+            munger.add_keep_pattern(options.pattern_files)
 
2582
 
+        for line in arch_compound.revert(tree, revision, munger, 
 
2583
 
+                                   not options.no_output):
 
2584
 
+            cmdutil.colorize(line)
 
2587
 
+    def get_parser(self):
 
2589
 
+        Returns the options parser to use for the "cat-log" command.
 
2591
 
+        :rtype: cmdutil.CmdOptionParser
 
2593
 
+        parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
 
2594
 
+        parser.add_option("", "--contents", action="store_true", 
 
2595
 
+                          dest="file_contents", 
 
2596
 
+                          help="Revert file content changes")
 
2597
 
+        parser.add_option("", "--permissions", action="store_true", 
 
2598
 
+                          dest="file_perms", 
 
2599
 
+                          help="Revert file permissions changes")
 
2600
 
+        parser.add_option("", "--deletions", action="store_true", 
 
2602
 
+                          help="Restore deleted files")
 
2603
 
+        parser.add_option("", "--additions", action="store_true", 
 
2605
 
+                          help="Remove added files")
 
2606
 
+        parser.add_option("", "--renames", action="store_true", 
 
2608
 
+                          help="Revert file names")
 
2609
 
+        parser.add_option("--hunks", action="store_true", 
 
2610
 
+                          dest="hunk_prompt", default=False,
 
2611
 
+                          help="Prompt which hunks to revert")
 
2612
 
+        parser.add_option("--pattern-files", dest="pattern_files", 
 
2613
 
+                          help="Revert files that match this pattern", 
 
2615
 
+        parser.add_option("--logs", action="store_true", 
 
2616
 
+                          dest="logs", default=False,
 
2617
 
+                          help="Revert only logs")
 
2618
 
+        parser.add_option("--control-files", action="store_true", 
 
2619
 
+                          dest="control", default=False,
 
2620
 
+                          help="Revert logs and other control files")
 
2621
 
+        parser.add_option("-n", "--no-output", action="store_true", 
 
2623
 
+                          help="Don't keep an undo changeset")
 
2624
 
+        parser.add_option("--revision", dest="revision", 
 
2625
 
+                          help="Revert to the specified revision", 
 
2626
 
+                          metavar="REVISION")
 
2629
 
+    def help(self, parser=None):
 
2631
 
+        Prints a help message.
 
2633
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2634
 
+        not supplied, it is retrieved.
 
2635
 
+        :type parser: cmdutil.CmdOptionParser
 
2638
 
+            parser=self.get_parser()
 
2639
 
+        parser.print_help()
 
2641
 
+Reverts changes in the current working tree.  If no flags are specified, all
 
2642
 
+types of changes are reverted.  Otherwise, only selected types of changes are
 
2645
 
+If a revision is specified on the commandline, differences between the current
 
2646
 
+tree and that revision are reverted.  If a version is specified, the current
 
2647
 
+tree is used to determine the revision.
 
2649
 
+If files are specified, only those files listed will have any changes applied.
 
2650
 
+To specify a renamed file, you can use either the old or new name. (or both!)
 
2652
 
+Unless "-n" is specified, reversions can be undone with "redo".
 
2656
 
+class Revision(BaseCommand):
 
2658
 
+    Print a revision name based on a revision specifier
 
2660
 
+    def __init__(self):
 
2661
 
+        self.description="Prints the name of a revision"
 
2663
 
+    def get_completer(self, arg, index):
 
2667
 
+            tree = arch.tree_root()
 
2670
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
2672
 
+    def do_command(self, cmdargs):
 
2674
 
+        Master function that perfoms the "revision" command.
 
2676
 
+        parser=self.get_parser()
 
2677
 
+        (options, args) = parser.parse_args(cmdargs)
 
2680
 
+            tree = arch.tree_root()
 
2681
 
+        except arch.errors.TreeRootError:
 
2688
 
+            raise cmdutil.GetHelp
 
2691
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
 
2693
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
 
2694
 
+        except cmdutil.CantDetermineRevision, e:
 
2697
 
+        print options.display(revision)
 
2699
 
+    def get_parser(self):
 
2701
 
+        Returns the options parser to use for the "revision" command.
 
2703
 
+        :rtype: cmdutil.CmdOptionParser
 
2705
 
+        parser=cmdutil.CmdOptionParser("fai revision [revision]")
 
2706
 
+        parser.add_option("", "--location", action="store_const", 
 
2707
 
+                         const=paths.determine_path, dest="display", 
 
2708
 
+                         help="Show location instead of name", default=str)
 
2709
 
+        parser.add_option("--import", action="store_const", 
 
2710
 
+                         const=paths.determine_import_path, dest="display",  
 
2711
 
+                         help="Show location of import file")
 
2712
 
+        parser.add_option("--log", action="store_const", 
 
2713
 
+                         const=paths.determine_log_path, dest="display", 
 
2714
 
+                         help="Show location of log file")
 
2715
 
+        parser.add_option("--patch", action="store_const", 
 
2716
 
+                         dest="display", const=paths.determine_patch_path,
 
2717
 
+                         help="Show location of patchfile")
 
2718
 
+        parser.add_option("--continuation", action="store_const", 
 
2719
 
+                         const=paths.determine_continuation_path, 
 
2721
 
+                         help="Show location of continuation file")
 
2722
 
+        parser.add_option("--cacherev", action="store_const", 
 
2723
 
+                         const=paths.determine_cacherev_path, dest="display",
 
2724
 
+                         help="Show location of cacherev file")
 
2727
 
+    def help(self, parser=None):
 
2729
 
+        Prints a help message.
 
2731
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2732
 
+        not supplied, it is retrieved.
 
2733
 
+        :type parser: cmdutil.CmdOptionParser
 
2736
 
+            parser=self.get_parser()
 
2737
 
+        parser.print_help()
 
2739
 
+Expands aliases and prints the name of the specified revision.  Instead of
 
2740
 
+the name, several options can be used to print locations.  If more than one is
 
2741
 
+specified, the last one is used.
 
2746
 
+class Revisions(BaseCommand):
 
2748
 
+    Print a revision name based on a revision specifier
 
2750
 
+    def __init__(self):
 
2751
 
+        self.description="Lists revisions"
 
2752
 
+        self.cl_revisions = []
 
2754
 
+    def do_command(self, cmdargs):
 
2756
 
+        Master function that perfoms the "revision" command.
 
2758
 
+        (options, args) = self.get_parser().parse_args(cmdargs)
 
2760
 
+            raise cmdutil.GetHelp
 
2762
 
+            self.tree = arch.tree_root()
 
2763
 
+        except arch.errors.TreeRootError:
 
2765
 
+        if options.type == "default":
 
2766
 
+            options.type = "archive"
 
2768
 
+            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
 
2769
 
+                                             options.reverse, options.modified,
 
2771
 
+        except cmdutil.CantDetermineRevision, e:
 
2772
 
+            raise CommandFailedWrapper(e)
 
2773
 
+        except cmdutil.CantDetermineVersion, e:
 
2774
 
+            raise CommandFailedWrapper(e)
 
2775
 
+        if options.skip is not None:
 
2776
 
+            iter = cmdutil.iter_skip(iter, int(options.skip))
 
2779
 
+            for revision in iter:
 
2781
 
+                if isinstance(revision, arch.Patchlog):
 
2783
 
+                    revision=revision.revision
 
2784
 
+                out = options.display(revision)
 
2785
 
+                if out is not None:
 
2787
 
+                if log is None and (options.summary or options.creator or 
 
2788
 
+                                    options.date or options.merges):
 
2789
 
+                    log = revision.patchlog
 
2790
 
+                if options.creator:
 
2791
 
+                    print "    %s" % log.creator
 
2793
 
+                    print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
 
2794
 
+                if options.summary:
 
2795
 
+                    print "    %s" % log.summary
 
2796
 
+                if options.merges:
 
2797
 
+                    showed_title = False
 
2798
 
+                    for revision in log.merged_patches:
 
2799
 
+                        if not showed_title:
 
2801
 
+                            showed_title = True
 
2802
 
+                        print "    %s" % revision
 
2803
 
+            if len(self.cl_revisions) > 0:
 
2804
 
+                print pylon.changelog_for_merge(self.cl_revisions)
 
2805
 
+        except pylon.errors.TreeRootNone:
 
2806
 
+            raise CommandFailedWrapper(
 
2807
 
+                Exception("This option can only be used in a project tree."))
 
2809
 
+    def changelog_append(self, revision):
 
2810
 
+        if isinstance(revision, arch.Revision):
 
2811
 
+            revision=arch.Patchlog(revision)
 
2812
 
+        self.cl_revisions.append(revision)
 
2814
 
+    def get_parser(self):
 
2816
 
+        Returns the options parser to use for the "revision" command.
 
2818
 
+        :rtype: cmdutil.CmdOptionParser
 
2820
 
+        parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
 
2821
 
+        select = cmdutil.OptionGroup(parser, "Selection options",
 
2822
 
+                          "Control which revisions are listed.  These options"
 
2823
 
+                          " are mutually exclusive.  If more than one is"
 
2824
 
+                          " specified, the last is used.")
 
2826
 
+        cmdutil.add_revision_iter_options(select)
 
2827
 
+        parser.add_option("", "--skip", dest="skip", 
 
2828
 
+                          help="Skip revisions.  Positive numbers skip from "
 
2829
 
+                          "beginning, negative skip from end.",
 
2832
 
+        parser.add_option_group(select)
 
2834
 
+        format = cmdutil.OptionGroup(parser, "Revision format options",
 
2835
 
+                          "These control the appearance of listed revisions")
 
2836
 
+        format.add_option("", "--location", action="store_const", 
 
2837
 
+                         const=paths.determine_path, dest="display", 
 
2838
 
+                         help="Show location instead of name", default=str)
 
2839
 
+        format.add_option("--import", action="store_const", 
 
2840
 
+                         const=paths.determine_import_path, dest="display",  
 
2841
 
+                         help="Show location of import file")
 
2842
 
+        format.add_option("--log", action="store_const", 
 
2843
 
+                         const=paths.determine_log_path, dest="display", 
 
2844
 
+                         help="Show location of log file")
 
2845
 
+        format.add_option("--patch", action="store_const", 
 
2846
 
+                         dest="display", const=paths.determine_patch_path,
 
2847
 
+                         help="Show location of patchfile")
 
2848
 
+        format.add_option("--continuation", action="store_const", 
 
2849
 
+                         const=paths.determine_continuation_path, 
 
2851
 
+                         help="Show location of continuation file")
 
2852
 
+        format.add_option("--cacherev", action="store_const", 
 
2853
 
+                         const=paths.determine_cacherev_path, dest="display",
 
2854
 
+                         help="Show location of cacherev file")
 
2855
 
+        format.add_option("--changelog", action="store_const", 
 
2856
 
+                         const=self.changelog_append, dest="display",
 
2857
 
+                         help="Show location of cacherev file")
 
2858
 
+        parser.add_option_group(format)
 
2859
 
+        display = cmdutil.OptionGroup(parser, "Display format options",
 
2860
 
+                          "These control the display of data")
 
2861
 
+        display.add_option("-r", "--reverse", action="store_true", 
 
2862
 
+                          dest="reverse", help="Sort from newest to oldest")
 
2863
 
+        display.add_option("-s", "--summary", action="store_true", 
 
2864
 
+                          dest="summary", help="Show patchlog summary")
 
2865
 
+        display.add_option("-D", "--date", action="store_true", 
 
2866
 
+                          dest="date", help="Show patchlog date")
 
2867
 
+        display.add_option("-c", "--creator", action="store_true", 
 
2868
 
+                          dest="creator", help="Show the id that committed the"
 
2870
 
+        display.add_option("-m", "--merges", action="store_true", 
 
2871
 
+                          dest="merges", help="Show the revisions that were"
 
2873
 
+        parser.add_option_group(display)
 
2875
 
+    def help(self, parser=None):
 
2876
 
+        """Attempt to explain the revisions command
 
2878
 
+        :param parser: If supplied, used to determine options
 
2881
 
+            parser=self.get_parser()
 
2882
 
+        parser.print_help()
 
2883
 
+        print """List revisions.
 
2888
 
+class Get(BaseCommand):
 
2890
 
+    Retrieve a revision from the archive
 
2892
 
+    def __init__(self):
 
2893
 
+        self.description="Retrieve a revision from the archive"
 
2894
 
+        self.parser=self.get_parser()
 
2897
 
+    def get_completer(self, arg, index):
 
2901
 
+            tree = arch.tree_root()
 
2904
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
2907
 
+    def do_command(self, cmdargs):
 
2909
 
+        Master function that perfoms the "get" command.
 
2911
 
+        (options, args) = self.parser.parse_args(cmdargs)
 
2913
 
+            return self.help()            
 
2915
 
+            tree = arch.tree_root()
 
2916
 
+        except arch.errors.TreeRootError:
 
2921
 
+            revision, arch_loc = paths.full_path_decode(args[0])
 
2922
 
+        except Exception, e:
 
2923
 
+            revision = cmdutil.determine_revision_arch(tree, args[0], 
 
2924
 
+                check_existence=False, allow_package=True)
 
2926
 
+            directory = args[1]
 
2928
 
+            directory = str(revision.nonarch)
 
2929
 
+        if os.path.exists(directory):
 
2930
 
+            raise DirectoryExists(directory)
 
2931
 
+        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
 
2933
 
+            cmdutil.ensure_revision_exists(revision)
 
2934
 
+        except cmdutil.NoSuchRevision, e:
 
2935
 
+            raise CommandFailedWrapper(e)
 
2937
 
+        link = cmdutil.prompt ("get link")
 
2938
 
+        for line in cmdutil.iter_get(revision, directory, link,
 
2939
 
+                                     options.no_pristine,
 
2940
 
+                                     options.no_greedy_add):
 
2941
 
+            cmdutil.colorize(line)
 
2943
 
+    def get_parser(self):
 
2945
 
+        Returns the options parser to use for the "get" command.
 
2947
 
+        :rtype: cmdutil.CmdOptionParser
 
2949
 
+        parser=cmdutil.CmdOptionParser("fai get revision [dir]")
 
2950
 
+        parser.add_option("--no-pristine", action="store_true", 
 
2951
 
+                         dest="no_pristine", 
 
2952
 
+                         help="Do not make pristine copy for reference")
 
2953
 
+        parser.add_option("--no-greedy-add", action="store_true", 
 
2954
 
+                         dest="no_greedy_add", 
 
2955
 
+                         help="Never add to greedy libraries")
 
2959
 
+    def help(self, parser=None):
 
2961
 
+        Prints a help message.
 
2963
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
2964
 
+        not supplied, it is retrieved.
 
2965
 
+        :type parser: cmdutil.CmdOptionParser
 
2968
 
+            parser=self.get_parser()
 
2969
 
+        parser.print_help()
 
2971
 
+Expands aliases and constructs a project tree for a revision.  If the optional
 
2972
 
+"dir" argument is provided, the project tree will be stored in this directory.
 
2977
 
+class PromptCmd(cmd.Cmd):
 
2978
 
+    def __init__(self):
 
2979
 
+        cmd.Cmd.__init__(self)
 
2980
 
+        self.prompt = "Fai> "
 
2982
 
+            self.tree = arch.tree_root()
 
2987
 
+        self.fake_aba = abacmds.AbaCmds()
 
2988
 
+        self.identchars += '-'
 
2989
 
+        self.history_file = os.path.expanduser("~/.fai-history")
 
2990
 
+        readline.set_completer_delims(string.whitespace)
 
2991
 
+        if os.access(self.history_file, os.R_OK) and \
 
2992
 
+            os.path.isfile(self.history_file):
 
2993
 
+            readline.read_history_file(self.history_file)
 
2994
 
+        self.cwd = os.getcwd()
 
2996
 
+    def write_history(self):
 
2997
 
+        readline.write_history_file(self.history_file)
 
2999
 
+    def do_quit(self, args):
 
3000
 
+        self.write_history()
 
3003
 
+    def do_exit(self, args):
 
3004
 
+        self.do_quit(args)
 
3006
 
+    def do_EOF(self, args):
 
3008
 
+        self.do_quit(args)
 
3010
 
+    def postcmd(self, line, bar):
 
3014
 
+    def set_prompt(self):
 
3015
 
+        if self.tree is not None:
 
3017
 
+                prompt = pylon.alias_or_version(self.tree.tree_version, 
 
3020
 
+                if prompt is not None:
 
3021
 
+                    prompt = " " + prompt
 
3026
 
+        self.prompt = "Fai%s> " % prompt
 
3028
 
+    def set_title(self, command=None):
 
3030
 
+            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
 
3033
 
+            version = "[no version]"
 
3034
 
+        if command is None:
 
3036
 
+        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
 
3038
 
+    def do_cd(self, line):
 
3041
 
+        line = os.path.expanduser(line)
 
3042
 
+        if os.path.isabs(line):
 
3045
 
+            newcwd = self.cwd+'/'+line
 
3046
 
+        newcwd = os.path.normpath(newcwd)
 
3050
 
+        except Exception, e:
 
3053
 
+            self.tree = arch.tree_root()
 
3057
 
+    def do_help(self, line):
 
3060
 
+    def default(self, line):
 
3061
 
+        args = line.split()
 
3062
 
+        if find_command(args[0]):
 
3064
 
+                find_command(args[0]).do_command(args[1:])
 
3065
 
+            except cmdutil.BadCommandOption, e:
 
3067
 
+            except cmdutil.GetHelp, e:
 
3068
 
+                find_command(args[0]).help()
 
3069
 
+            except CommandFailed, e:
 
3071
 
+            except arch.errors.ArchiveNotRegistered, e:
 
3073
 
+            except KeyboardInterrupt, e:
 
3074
 
+                print "Interrupted"
 
3075
 
+            except arch.util.ExecProblem, e:
 
3076
 
+                print e.proc.error.rstrip('\n')
 
3077
 
+            except cmdutil.CantDetermineVersion, e:
 
3079
 
+            except cmdutil.CantDetermineRevision, e:
 
3081
 
+            except Exception, e:
 
3082
 
+                print "Unhandled error:\n%s" % errors.exception_str(e)
 
3084
 
+        elif suggestions.has_key(args[0]):
 
3085
 
+            print suggestions[args[0]]
 
3087
 
+        elif self.fake_aba.is_command(args[0]):
 
3090
 
+                tree = arch.tree_root()
 
3091
 
+            except arch.errors.TreeRootError:
 
3093
 
+            cmd = self.fake_aba.is_command(args[0])
 
3095
 
+                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
 
3096
 
+            except KeyboardInterrupt, e:
 
3097
 
+                print "Interrupted"
 
3099
 
+        elif options.tla_fallthrough and args[0] != "rm" and \
 
3100
 
+            cmdutil.is_tla_command(args[0]):
 
3104
 
+                    tree = arch.tree_root()
 
3105
 
+                except arch.errors.TreeRootError:
 
3107
 
+                args = cmdutil.expand_prefix_alias(args, tree)
 
3108
 
+                arch.util.exec_safe('tla', args, stderr=sys.stderr,
 
3110
 
+            except arch.util.ExecProblem, e:
 
3112
 
+            except KeyboardInterrupt, e:
 
3113
 
+                print "Interrupted"
 
3117
 
+                    tree = arch.tree_root()
 
3118
 
+                except arch.errors.TreeRootError:
 
3121
 
+                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
 
3122
 
+            except KeyboardInterrupt, e:
 
3123
 
+                print "Interrupted"
 
3125
 
+    def completenames(self, text, line, begidx, endidx):
 
3127
 
+        iter = iter_command_names(self.fake_aba)
 
3130
 
+                arg = line.split()[-1]
 
3133
 
+            iter = cmdutil.iter_munged_completions(iter, arg, text)
 
3134
 
+        except Exception, e:
 
3138
 
+    def completedefault(self, text, line, begidx, endidx):
 
3139
 
+        """Perform completion for native commands.
 
3141
 
+        :param text: The text to complete
 
3143
 
+        :param line: The entire line to complete
 
3145
 
+        :param begidx: The start of the text in the line
 
3147
 
+        :param endidx: The end of the text in the line
 
3151
 
+            (cmd, args, foo) = self.parseline(line)
 
3152
 
+            command_obj=find_command(cmd)
 
3153
 
+            if command_obj is not None:
 
3154
 
+                return command_obj.complete(args.split(), text)
 
3155
 
+            elif not self.fake_aba.is_command(cmd) and \
 
3156
 
+                cmdutil.is_tla_command(cmd):
 
3157
 
+                iter = cmdutil.iter_supported_switches(cmd)
 
3159
 
+                    arg = args.split()[-1]
 
3162
 
+                if arg.startswith("-"):
 
3163
 
+                    return list(cmdutil.iter_munged_completions(iter, arg, 
 
3166
 
+                    return list(cmdutil.iter_munged_completions(
 
3167
 
+                        cmdutil.iter_file_completions(arg), arg, text))
 
3172
 
+                    arg = args.split()[-1]
 
3175
 
+                iter = cmdutil.iter_dir_completions(arg)
 
3176
 
+                iter = cmdutil.iter_munged_completions(iter, arg, text)
 
3179
 
+                arg = args.split()[-1]
 
3180
 
+                iter = cmdutil.iter_file_completions(arg)
 
3181
 
+                return list(cmdutil.iter_munged_completions(iter, arg, text))
 
3183
 
+                return self.completenames(text, line, begidx, endidx)
 
3184
 
+        except Exception, e:
 
3188
 
+def iter_command_names(fake_aba):
 
3189
 
+    for entry in cmdutil.iter_combine([commands.iterkeys(), 
 
3190
 
+                                     fake_aba.get_commands(), 
 
3191
 
+                                     cmdutil.iter_tla_commands(False)]):
 
3192
 
+        if not suggestions.has_key(str(entry)):
 
3196
 
+def iter_source_file_completions(tree, arg):
 
3197
 
+    treepath = arch_compound.tree_cwd(tree)
 
3198
 
+    if len(treepath) > 0:
 
3202
 
+    for file in tree.iter_inventory(dirs, source=True, both=True):
 
3203
 
+        file = file_completion_match(file, treepath, arg)
 
3204
 
+        if file is not None:
 
3208
 
+def iter_untagged(tree, dirs):
 
3209
 
+    for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False, 
 
3210
 
+                                                categories=arch_core.non_root,
 
3211
 
+                                                control_files=True):
 
3215
 
+def iter_untagged_completions(tree, arg):
 
3216
 
+    """Generate an iterator for all visible untagged files that match arg.
 
3218
 
+    :param tree: The tree to look for untagged files in
 
3219
 
+    :type tree: `arch.WorkingTree`
 
3220
 
+    :param arg: The argument to match
 
3222
 
+    :return: An iterator of all matching untagged files
 
3223
 
+    :rtype: iterator of str
 
3225
 
+    treepath = arch_compound.tree_cwd(tree)
 
3226
 
+    if len(treepath) > 0:
 
3231
 
+    for file in iter_untagged(tree, dirs):
 
3232
 
+        file = file_completion_match(file, treepath, arg)
 
3233
 
+        if file is not None:
 
3237
 
+def file_completion_match(file, treepath, arg):
 
3238
 
+    """Determines whether a file within an arch tree matches the argument.
 
3240
 
+    :param file: The rooted filename
 
3242
 
+    :param treepath: The path to the cwd within the tree
 
3243
 
+    :type treepath: str
 
3244
 
+    :param arg: The prefix to match
 
3245
 
+    :return: The completion name, or None if not a match
 
3248
 
+    if not file.startswith(treepath):
 
3250
 
+    if treepath != "":
 
3251
 
+        file = file[len(treepath)+1:]
 
3253
 
+    if not file.startswith(arg):
 
3255
 
+    if os.path.isdir(file):
 
3259
 
+def iter_modified_file_completions(tree, arg):
 
3260
 
+    """Returns a list of modified files that match the specified prefix.
 
3262
 
+    :param tree: The current tree
 
3263
 
+    :type tree: `arch.WorkingTree`
 
3264
 
+    :param arg: The prefix to match
 
3267
 
+    treepath = arch_compound.tree_cwd(tree)
 
3268
 
+    tmpdir = util.tmpdir()
 
3269
 
+    changeset = tmpdir+"/changeset"
 
3271
 
+    revision = cmdutil.determine_revision_tree(tree)
 
3272
 
+    for line in arch.iter_delta(revision, tree, changeset):
 
3273
 
+        if isinstance(line, arch.FileModification):
 
3274
 
+            file = file_completion_match(line.name[1:], treepath, arg)
 
3275
 
+            if file is not None:
 
3276
 
+                completions.append(file)
 
3277
 
+    shutil.rmtree(tmpdir)
 
3278
 
+    return completions
 
3280
 
+class Shell(BaseCommand):
 
3281
 
+    def __init__(self):
 
3282
 
+        self.description = "Runs Fai as a shell"
 
3284
 
+    def do_command(self, cmdargs):
 
3285
 
+        if len(cmdargs)!=0:
 
3286
 
+            raise cmdutil.GetHelp
 
3287
 
+        prompt = PromptCmd()
 
3291
 
+            prompt.write_history()
 
3293
 
+class AddID(BaseCommand):
 
3295
 
+    Adds an inventory id for the given file
 
3297
 
+    def __init__(self):
 
3298
 
+        self.description="Add an inventory id for a given file"
 
3300
 
+    def get_completer(self, arg, index):
 
3301
 
+        tree = arch.tree_root()
 
3302
 
+        return iter_untagged_completions(tree, arg)
 
3304
 
+    def do_command(self, cmdargs):
 
3306
 
+        Master function that perfoms the "revision" command.
 
3308
 
+        parser=self.get_parser()
 
3309
 
+        (options, args) = parser.parse_args(cmdargs)
 
3312
 
+            tree = arch.tree_root()
 
3313
 
+        except arch.errors.TreeRootError, e:
 
3314
 
+            raise pylon.errors.CommandFailedWrapper(e)
 
3317
 
+        if (len(args) == 0) == (options.untagged == False):
 
3318
 
+            raise cmdutil.GetHelp
 
3320
 
+       #if options.id and len(args) != 1:
 
3321
 
+       #    print "If --id is specified, only one file can be named."
 
3324
 
+        method = tree.tagging_method
 
3326
 
+        if options.id_type == "tagline":
 
3327
 
+            if method != "tagline":
 
3328
 
+                if not cmdutil.prompt("Tagline in other tree"):
 
3329
 
+                    if method == "explicit" or method == "implicit":
 
3330
 
+                        options.id_type == method
 
3332
 
+                        print "add-id not supported for \"%s\" tagging method"\
 
3336
 
+        elif options.id_type == "implicit":
 
3337
 
+            if method != "implicit":
 
3338
 
+                if not cmdutil.prompt("Implicit in other tree"):
 
3339
 
+                    if method == "explicit" or method == "tagline":
 
3340
 
+                        options.id_type == method
 
3342
 
+                        print "add-id not supported for \"%s\" tagging method"\
 
3345
 
+        elif options.id_type == "explicit":
 
3346
 
+            if method != "tagline" and method != explicit:
 
3347
 
+                if not prompt("Explicit in other tree"):
 
3348
 
+                    print "add-id not supported for \"%s\" tagging method" % \
 
3352
 
+        if options.id_type == "auto":
 
3353
 
+            if method != "tagline" and method != "explicit" \
 
3354
 
+                and method !="implicit":
 
3355
 
+                print "add-id not supported for \"%s\" tagging method" % method
 
3358
 
+                options.id_type = method
 
3359
 
+        if options.untagged:
 
3361
 
+        self.add_ids(tree, options.id_type, args)
 
3363
 
+    def add_ids(self, tree, id_type, files=()):
 
3364
 
+        """Add inventory ids to files.
 
3366
 
+        :param tree: the tree the files are in
 
3367
 
+        :type tree: `arch.WorkingTree`
 
3368
 
+        :param id_type: the type of id to add: "explicit" or "tagline"
 
3369
 
+        :type id_type: str
 
3370
 
+        :param files: The list of files to add.  If None do all untagged.
 
3371
 
+        :type files: tuple of str
 
3374
 
+        untagged = (files is None)
 
3376
 
+            files = list(iter_untagged(tree, None))
 
3377
 
+        previous_files = []
 
3378
 
+        while len(files) > 0:
 
3379
 
+            previous_files.extend(files)
 
3380
 
+            if id_type == "explicit":
 
3381
 
+                cmdutil.add_id(files)
 
3382
 
+            elif id_type == "tagline" or id_type == "implicit":
 
3383
 
+                for file in files:
 
3385
 
+                        implicit = (id_type == "implicit")
 
3386
 
+                        cmdutil.add_tagline_or_explicit_id(file, False,
 
3388
 
+                    except cmdutil.AlreadyTagged:
 
3389
 
+                        print "\"%s\" already has a tagline." % file
 
3390
 
+                    except cmdutil.NoCommentSyntax:
 
3392
 
+            #do inventory after tagging until no untagged files are encountered
 
3395
 
+                for file in iter_untagged(tree, None):
 
3396
 
+                    if not file in previous_files:
 
3397
 
+                        files.append(file)
 
3402
 
+    def get_parser(self):
 
3404
 
+        Returns the options parser to use for the "revision" command.
 
3406
 
+        :rtype: cmdutil.CmdOptionParser
 
3408
 
+        parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
 
3409
 
+# ddaa suggests removing this to promote GUIDs.  Let's see who squalks.
 
3410
 
+#        parser.add_option("-i", "--id", dest="id", 
 
3411
 
+#                         help="Specify id for a single file", default=None)
 
3412
 
+        parser.add_option("--tltl", action="store_true", 
 
3413
 
+                         dest="lord_style",  help="Use Tom Lord's style of id.")
 
3414
 
+        parser.add_option("--explicit", action="store_const", 
 
3415
 
+                         const="explicit", dest="id_type", 
 
3416
 
+                         help="Use an explicit id", default="auto")
 
3417
 
+        parser.add_option("--tagline", action="store_const", 
 
3418
 
+                         const="tagline", dest="id_type", 
 
3419
 
+                         help="Use a tagline id")
 
3420
 
+        parser.add_option("--implicit", action="store_const", 
 
3421
 
+                         const="implicit", dest="id_type", 
 
3422
 
+                         help="Use an implicit id (deprecated)")
 
3423
 
+        parser.add_option("--untagged", action="store_true", 
 
3424
 
+                         dest="untagged", default=False, 
 
3425
 
+                         help="tag all untagged files")
 
3428
 
+    def help(self, parser=None):
 
3430
 
+        Prints a help message.
 
3432
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
3433
 
+        not supplied, it is retrieved.
 
3434
 
+        :type parser: cmdutil.CmdOptionParser
 
3437
 
+            parser=self.get_parser()
 
3438
 
+        parser.print_help()
 
3440
 
+Adds an inventory to the specified file(s) and directories.  If --untagged is
 
3441
 
+specified, adds inventory to all untagged files and directories.
 
3446
 
+class Merge(BaseCommand):
 
3448
 
+    Merges changes from other versions into the current tree
 
3450
 
+    def __init__(self):
 
3451
 
+        self.description="Merges changes from other versions"
 
3453
 
+            self.tree = arch.tree_root()
 
3458
 
+    def get_completer(self, arg, index):
 
3459
 
+        if self.tree is None:
 
3460
 
+            raise arch.errors.TreeRootError
 
3461
 
+        return cmdutil.merge_completions(self.tree, arg, index)
 
3463
 
+    def do_command(self, cmdargs):
 
3465
 
+        Master function that perfoms the "merge" command.
 
3467
 
+        parser=self.get_parser()
 
3468
 
+        (options, args) = parser.parse_args(cmdargs)
 
3470
 
+            action="star-merge"
 
3472
 
+            action = options.action
 
3474
 
+        if self.tree is None:
 
3475
 
+            raise arch.errors.TreeRootError(os.getcwd())
 
3476
 
+        if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
 
3477
 
+            raise UncommittedChanges(self.tree)
 
3482
 
+                revisions.append(cmdutil.determine_revision_arch(self.tree, 
 
3484
 
+            source = "from commandline"
 
3486
 
+            revisions = ancillary.iter_partner_revisions(self.tree, 
 
3487
 
+                                                         self.tree.tree_version)
 
3488
 
+            source = "from partner version"
 
3489
 
+        revisions = misc.rewind_iterator(revisions)
 
3492
 
+            revisions.rewind()
 
3493
 
+        except StopIteration, e:
 
3494
 
+            revision = cmdutil.tag_cur(self.tree)
 
3495
 
+            if revision is None:
 
3496
 
+                raise CantDetermineRevision("", "No version specified, no "
 
3497
 
+                                            "partner-versions, and no tag"
 
3499
 
+            revisions = [revision]
 
3500
 
+            source = "from tag source"
 
3501
 
+        for revision in revisions:
 
3502
 
+            cmdutil.ensure_archive_registered(revision.archive)
 
3503
 
+            cmdutil.colorize(arch.Chatter("* Merging %s [%s]" % 
 
3504
 
+                             (revision, source)))
 
3505
 
+            if action=="native-merge" or action=="update":
 
3506
 
+                if self.native_merge(revision, action) == 0:
 
3508
 
+            elif action=="star-merge":
 
3510
 
+                    self.star_merge(revision, options.diff3)
 
3511
 
+                except errors.MergeProblem, e:
 
3513
 
+            if cmdutil.has_changed(self.tree.tree_version):
 
3516
 
+    def star_merge(self, revision, diff3):
 
3517
 
+        """Perform a star-merge on the current tree.
 
3519
 
+        :param revision: The revision to use for the merge
 
3520
 
+        :type revision: `arch.Revision`
 
3521
 
+        :param diff3: If true, do a diff3 merge
 
3525
 
+            for line in self.tree.iter_star_merge(revision, diff3=diff3):
 
3526
 
+                cmdutil.colorize(line)
 
3527
 
+        except arch.util.ExecProblem, e:
 
3528
 
+            if e.proc.status is not None and e.proc.status == 1:
 
3530
 
+                    print e.proc.error
 
3531
 
+                raise MergeProblem
 
3535
 
+    def native_merge(self, other_revision, action):
 
3536
 
+        """Perform a native-merge on the current tree.
 
3538
 
+        :param other_revision: The revision to use for the merge
 
3539
 
+        :type other_revision: `arch.Revision`
 
3540
 
+        :return: 0 if the merge was skipped, 1 if it was applied
 
3542
 
+        other_tree = arch_compound.find_or_make_local_revision(other_revision)
 
3544
 
+            if action == "native-merge":
 
3545
 
+                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
 
3547
 
+            elif action == "update":
 
3548
 
+                ancestor = arch_compound.tree_latest(self.tree, 
 
3549
 
+                                                     other_revision.version)
 
3550
 
+        except CantDetermineRevision, e:
 
3551
 
+            raise CommandFailedWrapper(e)
 
3552
 
+        cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
 
3553
 
+        if (ancestor == other_revision):
 
3554
 
+            cmdutil.colorize(arch.Chatter("* Skipping redundant merge" 
 
3557
 
+        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
 
3558
 
+        for line in cmdutil.iter_apply_delta_filter(delta):
 
3559
 
+            cmdutil.colorize(line)
 
3564
 
+    def get_parser(self):
 
3566
 
+        Returns the options parser to use for the "merge" command.
 
3568
 
+        :rtype: cmdutil.CmdOptionParser
 
3570
 
+        parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
 
3571
 
+        parser.add_option("-s", "--star-merge", action="store_const",
 
3572
 
+                          dest="action", help="Use star-merge",
 
3573
 
+                          const="star-merge", default="native-merge")
 
3574
 
+        parser.add_option("--update", action="store_const",
 
3575
 
+                          dest="action", help="Use update picker",
 
3577
 
+        parser.add_option("--diff3", action="store_true", 
 
3579
 
+                         help="Use diff3 for merge (implies star-merge)")
 
3582
 
+    def help(self, parser=None):
 
3584
 
+        Prints a help message.
 
3586
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
3587
 
+        not supplied, it is retrieved.
 
3588
 
+        :type parser: cmdutil.CmdOptionParser
 
3591
 
+            parser=self.get_parser()
 
3592
 
+        parser.print_help()
 
3594
 
+Performs a merge operation using the specified version.
 
3598
 
+class ELog(BaseCommand):
 
3600
 
+    Produces a raw patchlog and invokes the user's editor
 
3602
 
+    def __init__(self):
 
3603
 
+        self.description="Edit a patchlog to commit"
 
3605
 
+            self.tree = arch.tree_root()
 
3610
 
+    def do_command(self, cmdargs):
 
3612
 
+        Master function that perfoms the "elog" command.
 
3614
 
+        parser=self.get_parser()
 
3615
 
+        (options, args) = parser.parse_args(cmdargs)
 
3616
 
+        if self.tree is None:
 
3617
 
+            raise arch.errors.TreeRootError
 
3620
 
+            edit_log(self.tree, self.tree.tree_version)
 
3621
 
+        except pylon.errors.NoEditorSpecified, e:
 
3622
 
+            raise pylon.errors.CommandFailedWrapper(e)
 
3624
 
+    def get_parser(self):
 
3626
 
+        Returns the options parser to use for the "merge" command.
 
3628
 
+        :rtype: cmdutil.CmdOptionParser
 
3630
 
+        parser=cmdutil.CmdOptionParser("fai elog")
 
3634
 
+    def help(self, parser=None):
 
3636
 
+        Invokes $EDITOR to produce a log for committing.
 
3638
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
3639
 
+        not supplied, it is retrieved.
 
3640
 
+        :type parser: cmdutil.CmdOptionParser
 
3643
 
+            parser=self.get_parser()
 
3644
 
+        parser.print_help()
 
3646
 
+Invokes $EDITOR to produce a log for committing.
 
3650
 
+def edit_log(tree, version):
 
3651
 
+    """Makes and edits the log for a tree.  Does all kinds of fancy things
 
3652
 
+    like log templates and merge summaries and log-for-merge
 
3654
 
+    :param tree: The tree to edit the log for
 
3655
 
+    :type tree: `arch.WorkingTree`
 
3657
 
+    #ensure we have an editor before preparing the log
 
3658
 
+    cmdutil.find_editor()
 
3659
 
+    log = tree.log_message(create=False, version=version)
 
3660
 
+    log_is_new = False
 
3661
 
+    if log is None or cmdutil.prompt("Overwrite log"):
 
3662
 
+        if log is not None:
 
3663
 
+           os.remove(log.name)
 
3664
 
+        log = tree.log_message(create=True, version=version)
 
3667
 
+        template = pylon.log_template_path(tree)
 
3669
 
+            shutil.copyfile(template, tmplog)
 
3670
 
+        comp_version = ancillary.comp_revision(tree).version
 
3671
 
+        new_merges = cmdutil.iter_new_merges(tree, comp_version)
 
3672
 
+        new_merges = cmdutil.direct_merges(new_merges)
 
3673
 
+        log["Summary"] = pylon.merge_summary(new_merges, 
 
3675
 
+        if len(new_merges) > 0:   
 
3676
 
+            if cmdutil.prompt("Log for merge"):
 
3677
 
+                if cmdutil.prompt("changelog for merge"):
 
3678
 
+                    mergestuff = "Patches applied:\n"
 
3679
 
+                    mergestuff += pylon.changelog_for_merge(new_merges)
 
3681
 
+                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
 
3682
 
+                log.description += mergestuff
 
3685
 
+        cmdutil.invoke_editor(log.name)
 
3688
 
+            os.remove(log.name)
 
3692
 
+class MirrorArchive(BaseCommand):
 
3694
 
+    Updates a mirror from an archive
 
3696
 
+    def __init__(self):
 
3697
 
+        self.description="Update a mirror from an archive"
 
3699
 
+    def do_command(self, cmdargs):
 
3701
 
+        Master function that perfoms the "revision" command.
 
3704
 
+        parser=self.get_parser()
 
3705
 
+        (options, args) = parser.parse_args(cmdargs)
 
3709
 
+            tree = arch.tree_root()
 
3713
 
+        if len(args) == 0:
 
3714
 
+            if tree is not None:
 
3715
 
+                name = tree.tree_version()
 
3717
 
+            name = cmdutil.expand_alias(args[0], tree)
 
3718
 
+            name = arch.NameParser(name)
 
3720
 
+        to_arch = name.get_archive()
 
3721
 
+        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
 
3722
 
+        limit = name.get_nonarch()
 
3724
 
+        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
 
3725
 
+        for line in arch.chatter_classifier(iter):
 
3726
 
+            cmdutil.colorize(line)
 
3728
 
+    def get_parser(self):
 
3730
 
+        Returns the options parser to use for the "revision" command.
 
3732
 
+        :rtype: cmdutil.CmdOptionParser
 
3734
 
+        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
 
3737
 
+    def help(self, parser=None):
 
3739
 
+        Prints a help message.
 
3741
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
3742
 
+        not supplied, it is retrieved.
 
3743
 
+        :type parser: cmdutil.CmdOptionParser
 
3746
 
+            parser=self.get_parser()
 
3747
 
+        parser.print_help()
 
3749
 
+Updates a mirror from an archive.  If a branch, package, or version is
 
3750
 
+supplied, only changes under it are mirrored.
 
3754
 
+def help_tree_spec():
 
3755
 
+    print """Specifying revisions (default: tree)
 
3756
 
+Revisions may be specified by alias, revision, version or patchlevel.
 
3757
 
+Revisions or versions may be fully qualified.  Unqualified revisions, versions, 
 
3758
 
+or patchlevels use the archive of the current project tree.  Versions will
 
3759
 
+use the latest patchlevel in the tree.  Patchlevels will use the current tree-
 
3762
 
+Use "alias" to list available (user and automatic) aliases."""
 
3766
 
+"The latest revision in the archive of the tree-version.  You can specify \
 
3767
 
+a different version like so: acur:foo--bar--0 (aliases can be used)",
 
3769
 
+"""(tree current) The latest revision in the tree of the tree-version. \
 
3770
 
+You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
 
3773
 
+"""(tree previous) The previous revision in the tree of the tree-version.  To \
 
3774
 
+specify an older revision, use a number, e.g. "tprev:4" """,
 
3776
 
+"""(tree ancestor) The ancestor revision of the tree To specify an older \
 
3777
 
+revision, use a number, e.g. "tanc:4".""",
 
3779
 
+"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
 
3781
 
+""" (tree modified) The latest revision to modify a given file, e.g. \
 
3782
 
+"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
 
3784
 
+"""(tree tag) The revision that was tagged into the current tree revision, \
 
3785
 
+according to the tree""",
 
3787
 
+"""(tag current) The latest revision of the version that the current tree \
 
3788
 
+was tagged from.""",
 
3790
 
+"""The common ancestor of the current tree and the specified revision. \
 
3791
 
+Defaults to the first partner-version's latest revision or to tagcur.""",
 
3795
 
+def is_auto_alias(name):
 
3796
 
+    """Determine whether a name is an auto alias name
 
3798
 
+    :param name: the name to check
 
3800
 
+    :return: True if the name is an auto alias, false if not
 
3803
 
+    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
 
3806
 
+def display_def(iter, wrap = 80):
 
3807
 
+    """Display a list of definitions
 
3809
 
+    :param iter: iter of name, definition pairs
 
3810
 
+    :type iter: iter of (str, str)
 
3811
 
+    :param wrap: The width for text wrapping
 
3816
 
+    for (key, value) in vals:
 
3817
 
+        if len(key) > maxlen:
 
3819
 
+    for (key, value) in vals:
 
3820
 
+        tw=textwrap.TextWrapper(width=wrap, 
 
3821
 
+                                initial_indent=key.rjust(maxlen)+" : ",
 
3822
 
+                                subsequent_indent="".rjust(maxlen+3))
 
3823
 
+        print tw.fill(value)
 
3826
 
+def help_aliases(tree):
 
3827
 
+    print """Auto-generated aliases"""
 
3828
 
+    display_def(pylon.util.iter_pairs(auto_alias))
 
3829
 
+    print "User aliases"
 
3830
 
+    display_def(ancillary.iter_all_alias(tree))
 
3832
 
+class Inventory(BaseCommand):
 
3833
 
+    """List the status of files in the tree"""
 
3834
 
+    def __init__(self):
 
3835
 
+        self.description=self.__doc__
 
3837
 
+    def do_command(self, cmdargs):
 
3839
 
+        Master function that perfoms the "revision" command.
 
3842
 
+        parser=self.get_parser()
 
3843
 
+        (options, args) = parser.parse_args(cmdargs)
 
3844
 
+        tree = arch.tree_root()
 
3847
 
+        if (options.source):
 
3848
 
+            categories.append(arch_core.SourceFile)
 
3849
 
+        if (options.precious):
 
3850
 
+            categories.append(arch_core.PreciousFile)
 
3851
 
+        if (options.backup):
 
3852
 
+            categories.append(arch_core.BackupFile)
 
3853
 
+        if (options.junk):
 
3854
 
+            categories.append(arch_core.JunkFile)
 
3856
 
+        if len(categories) == 1:
 
3857
 
+            show_leading = False
 
3859
 
+            show_leading = True
 
3861
 
+        if len(categories) == 0:
 
3864
 
+        if options.untagged:
 
3865
 
+            categories = arch_core.non_root
 
3866
 
+            show_leading = False
 
3871
 
+        for file in arch_core.iter_inventory_filter(tree, None, 
 
3872
 
+            control_files=options.control_files, 
 
3873
 
+            categories = categories, tagged=tagged):
 
3874
 
+            print arch_core.file_line(file, 
 
3875
 
+                                      category = show_leading, 
 
3876
 
+                                      untagged = show_leading,
 
3879
 
+    def get_parser(self):
 
3881
 
+        Returns the options parser to use for the "revision" command.
 
3883
 
+        :rtype: cmdutil.CmdOptionParser
 
3885
 
+        parser=cmdutil.CmdOptionParser("fai inventory [options]")
 
3886
 
+        parser.add_option("--ids", action="store_true", dest="ids", 
 
3887
 
+                          help="Show file ids")
 
3888
 
+        parser.add_option("--control", action="store_true", 
 
3889
 
+                          dest="control_files", help="include control files")
 
3890
 
+        parser.add_option("--source", action="store_true", dest="source",
 
3891
 
+                          help="List source files")
 
3892
 
+        parser.add_option("--backup", action="store_true", dest="backup",
 
3893
 
+                          help="List backup files")
 
3894
 
+        parser.add_option("--precious", action="store_true", dest="precious",
 
3895
 
+                          help="List precious files")
 
3896
 
+        parser.add_option("--junk", action="store_true", dest="junk",
 
3897
 
+                          help="List junk files")
 
3898
 
+        parser.add_option("--unrecognized", action="store_true", 
 
3899
 
+                          dest="unrecognized", help="List unrecognized files")
 
3900
 
+        parser.add_option("--untagged", action="store_true", 
 
3901
 
+                          dest="untagged", help="List only untagged files")
 
3904
 
+    def help(self, parser=None):
 
3906
 
+        Prints a help message.
 
3908
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
3909
 
+        not supplied, it is retrieved.
 
3910
 
+        :type parser: cmdutil.CmdOptionParser
 
3913
 
+            parser=self.get_parser()
 
3914
 
+        parser.print_help()
 
3916
 
+Lists the status of files in the archive:
 
3924
 
+Leading letter are not displayed if only one kind of file is shown
 
3929
 
+class Alias(BaseCommand):
 
3930
 
+    """List or adjust aliases"""
 
3931
 
+    def __init__(self):
 
3932
 
+        self.description=self.__doc__
 
3934
 
+    def get_completer(self, arg, index):
 
3938
 
+            self.tree = arch.tree_root()
 
3943
 
+            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
 
3945
 
+            return cmdutil.iter_revision_completions(arg, self.tree)
 
3948
 
+    def do_command(self, cmdargs):
 
3950
 
+        Master function that perfoms the "revision" command.
 
3953
 
+        parser=self.get_parser()
 
3954
 
+        (options, args) = parser.parse_args(cmdargs)
 
3956
 
+            self.tree =  arch.tree_root()
 
3962
 
+            options.action(args, options)
 
3963
 
+        except cmdutil.ForbiddenAliasSyntax, e:
 
3964
 
+            raise CommandFailedWrapper(e)
 
3966
 
+    def no_prefix(self, alias):
 
3967
 
+        if alias.startswith("^"):
 
3971
 
+    def arg_dispatch(self, args, options):
 
3972
 
+        """Add, modify, or list aliases, depending on number of arguments
 
3974
 
+        :param args: The list of commandline arguments
 
3975
 
+        :type args: list of str
 
3976
 
+        :param options: The commandline options
 
3978
 
+        if len(args) == 0:
 
3979
 
+            help_aliases(self.tree)
 
3982
 
+            alias = self.no_prefix(args[0])
 
3983
 
+            if len(args) == 1:
 
3984
 
+                self.print_alias(alias)
 
3985
 
+            elif (len(args)) == 2:
 
3986
 
+                self.add(alias, args[1], options)
 
3988
 
+                raise cmdutil.GetHelp
 
3990
 
+    def print_alias(self, alias):
 
3992
 
+        if is_auto_alias(alias):
 
3993
 
+            raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
 
3994
 
+                "  Use \"revision\" to expand auto aliases." % alias)
 
3995
 
+        for pair in ancillary.iter_all_alias(self.tree):
 
3996
 
+            if pair[0] == alias:
 
3998
 
+        if answer is not None:
 
4001
 
+            print "The alias %s is not assigned." % alias
 
4003
 
+    def add(self, alias, expansion, options):
 
4004
 
+        """Add or modify aliases
 
4006
 
+        :param alias: The alias name to create/modify
 
4008
 
+        :param expansion: The expansion to assign to the alias name
 
4009
 
+        :type expansion: str
 
4010
 
+        :param options: The commandline options
 
4012
 
+        if is_auto_alias(alias):
 
4013
 
+            raise IsAutoAlias(alias)
 
4016
 
+        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
 
4018
 
+        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
 
4020
 
+        for pair in self.get_iterator(options):
 
4021
 
+            if pair[0] != alias:
 
4022
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
 
4028
 
+        self.write_aliases(newlist, options)
 
4030
 
+    def delete(self, args, options):
 
4031
 
+        """Delete the specified alias
 
4033
 
+        :param args: The list of arguments
 
4034
 
+        :type args: list of str
 
4035
 
+        :param options: The commandline options
 
4038
 
+        if len(args) != 1:
 
4039
 
+            raise cmdutil.GetHelp
 
4040
 
+        alias = self.no_prefix(args[0])
 
4041
 
+        if is_auto_alias(alias):
 
4042
 
+            raise IsAutoAlias(alias)
 
4044
 
+        for pair in self.get_iterator(options):
 
4045
 
+            if pair[0] != alias:
 
4046
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
 
4050
 
+            raise errors.NoSuchAlias(alias)
 
4051
 
+        self.write_aliases(newlist, options)
 
4053
 
+    def get_alias_file(self, options):
 
4054
 
+        """Return the name of the alias file to use
 
4056
 
+        :param options: The commandline options
 
4059
 
+            if self.tree is None:
 
4060
 
+                self.tree == arch.tree_root()
 
4061
 
+            return str(self.tree)+"/{arch}/+aliases"
 
4063
 
+            return "~/.aba/aliases"
 
4065
 
+    def get_iterator(self, options):
 
4066
 
+        """Return the alias iterator to use
 
4068
 
+        :param options: The commandline options
 
4070
 
+        return ancillary.iter_alias(self.get_alias_file(options))
 
4072
 
+    def write_aliases(self, newlist, options):
 
4073
 
+        """Safely rewrite the alias file
 
4074
 
+        :param newlist: The new list of aliases
 
4075
 
+        :type newlist: str
 
4076
 
+        :param options: The commandline options
 
4078
 
+        filename = os.path.expanduser(self.get_alias_file(options))
 
4079
 
+        file = util.NewFileVersion(filename)
 
4080
 
+        file.write(newlist)
 
4084
 
+    def get_parser(self):
 
4086
 
+        Returns the options parser to use for the "alias" command.
 
4088
 
+        :rtype: cmdutil.CmdOptionParser
 
4090
 
+        parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
 
4091
 
+        parser.add_option("-d", "--delete", action="store_const", dest="action",
 
4092
 
+                          const=self.delete, default=self.arg_dispatch, 
 
4093
 
+                          help="Delete an alias")
 
4094
 
+        parser.add_option("--tree", action="store_true", dest="tree", 
 
4095
 
+                          help="Create a per-tree alias", default=False)
 
4098
 
+    def help(self, parser=None):
 
4100
 
+        Prints a help message.
 
4102
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
4103
 
+        not supplied, it is retrieved.
 
4104
 
+        :type parser: cmdutil.CmdOptionParser
 
4107
 
+            parser=self.get_parser()
 
4108
 
+        parser.print_help()
 
4110
 
+Lists current aliases or modifies the list of aliases.
 
4112
 
+If no arguments are supplied, aliases will be listed.  If two arguments are
 
4113
 
+supplied, the specified alias will be created or modified.  If -d or --delete
 
4114
 
+is supplied, the specified alias will be deleted.
 
4116
 
+You can create aliases that refer to any fully-qualified part of the
 
4117
 
+Arch namespace, e.g. 
 
4120
 
+archive/category--branch, 
 
4121
 
+archive/category--branch--version (my favourite)
 
4122
 
+archive/category--branch--version--patchlevel
 
4124
 
+Aliases can be used automatically by native commands.  To use them
 
4125
 
+with external or tla commands, prefix them with ^ (you can do this
 
4126
 
+with native commands, too).
 
4130
 
+class RequestMerge(BaseCommand):
 
4131
 
+    """Submit a merge request to Bug Goo"""
 
4132
 
+    def __init__(self):
 
4133
 
+        self.description=self.__doc__
 
4135
 
+    def do_command(self, cmdargs):
 
4136
 
+        """Submit a merge request
 
4138
 
+        :param cmdargs: The commandline arguments
 
4139
 
+        :type cmdargs: list of str
 
4141
 
+        parser = self.get_parser()
 
4142
 
+        (options, args) = parser.parse_args(cmdargs)
 
4144
 
+            cmdutil.find_editor()
 
4145
 
+        except pylon.errors.NoEditorSpecified, e:
 
4146
 
+            raise pylon.errors.CommandFailedWrapper(e)
 
4148
 
+            self.tree=arch.tree_root()
 
4151
 
+        base, revisions = self.revision_specs(args)
 
4152
 
+        message = self.make_headers(base, revisions)
 
4153
 
+        message += self.make_summary(revisions)
 
4154
 
+        path = self.edit_message(message)
 
4155
 
+        message = self.tidy_message(path)
 
4156
 
+        if cmdutil.prompt("Send merge"):
 
4157
 
+            self.send_message(message)
 
4158
 
+            print "Merge request sent"
 
4160
 
+    def make_headers(self, base, revisions):
 
4161
 
+        """Produce email and Bug Goo header strings
 
4163
 
+        :param base: The base revision to apply merges to
 
4164
 
+        :type base: `arch.Revision`
 
4165
 
+        :param revisions: The revisions to replay into the base
 
4166
 
+        :type revisions: list of `arch.Patchlog`
 
4167
 
+        :return: The headers
 
4170
 
+        headers = "To: gnu-arch-users@gnu.org\n"
 
4171
 
+        headers += "From: %s\n" % options.fromaddr
 
4172
 
+        if len(revisions) == 1:
 
4173
 
+            headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
 
4175
 
+            headers += "Subject: [MERGE REQUEST]\n"
 
4177
 
+        headers += "Base-Revision: %s\n" % base
 
4178
 
+        for revision in revisions:
 
4179
 
+            headers += "Revision: %s\n" % revision.revision
 
4180
 
+        headers += "Bug: \n\n"
 
4183
 
+    def make_summary(self, logs):
 
4184
 
+        """Generate a summary of merges
 
4186
 
+        :param logs: the patchlogs that were directly added by the merges
 
4187
 
+        :type logs: list of `arch.Patchlog`
 
4188
 
+        :return: the summary
 
4193
 
+            summary+=str(log.revision)+"\n"
 
4194
 
+            summary+=log.summary+"\n"
 
4195
 
+            if log.description.strip():
 
4196
 
+                summary+=log.description.strip('\n')+"\n\n"
 
4199
 
+    def revision_specs(self, args):
 
4200
 
+        """Determine the base and merge revisions from tree and arguments.
 
4202
 
+        :param args: The parsed arguments
 
4203
 
+        :type args: list of str
 
4204
 
+        :return: The base revision and merge revisions 
 
4205
 
+        :rtype: `arch.Revision`, list of `arch.Patchlog`
 
4208
 
+            target_revision = cmdutil.determine_revision_arch(self.tree, 
 
4211
 
+            target_revision = arch_compound.tree_latest(self.tree)
 
4213
 
+            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
 
4214
 
+                       self.tree, f)) for f in args[1:] ]
 
4216
 
+            if self.tree is None:
 
4217
 
+                raise CantDetermineRevision("", "Not in a project tree")
 
4218
 
+            merge_iter = cmdutil.iter_new_merges(self.tree, 
 
4219
 
+                                                 target_revision.version, 
 
4221
 
+            merges = [f for f in cmdutil.direct_merges(merge_iter)]
 
4222
 
+        return (target_revision, merges)
 
4224
 
+    def edit_message(self, message):
 
4225
 
+        """Edit an email message in the user's standard editor
 
4227
 
+        :param message: The message to edit
 
4228
 
+        :type message: str
 
4229
 
+        :return: the path of the edited message
 
4232
 
+        if self.tree is None:
 
4233
 
+            path = os.get_cwd()
 
4236
 
+        path += "/,merge-request"
 
4237
 
+        file = open(path, 'w')
 
4238
 
+        file.write(message)
 
4240
 
+        cmdutil.invoke_editor(path)
 
4243
 
+    def tidy_message(self, path):
 
4244
 
+        """Validate and clean up message.
 
4246
 
+        :param path: The path to the message to clean up
 
4248
 
+        :return: The parsed message
 
4249
 
+        :rtype: `email.Message`
 
4251
 
+        mail = email.message_from_file(open(path))
 
4252
 
+        if mail["Subject"].strip() == "[MERGE REQUEST]":
 
4253
 
+            raise BlandSubject
 
4255
 
+        request = email.message_from_string(mail.get_payload())
 
4256
 
+        if request.has_key("Bug"):
 
4257
 
+            if request["Bug"].strip()=="":
 
4258
 
+                del request["Bug"]
 
4259
 
+        mail.set_payload(request.as_string())
 
4262
 
+    def send_message(self, message):
 
4263
 
+        """Send a message, using its headers to address it.
 
4265
 
+        :param message: The message to send
 
4266
 
+        :type message: `email.Message`"""
 
4267
 
+        server = smtplib.SMTP("localhost")
 
4268
 
+        server.sendmail(message['From'], message['To'], message.as_string())
 
4271
 
+    def help(self, parser=None):
 
4272
 
+        """Print a usage message
 
4274
 
+        :param parser: The options parser to use
 
4275
 
+        :type parser: `cmdutil.CmdOptionParser`
 
4277
 
+        if parser is None:
 
4278
 
+            parser = self.get_parser()
 
4279
 
+        parser.print_help()
 
4281
 
+Sends a merge request formatted for Bug Goo.  Intended use: get the tree
 
4282
 
+you'd like to merge into.  Apply the merges you want.  Invoke request-merge.
 
4283
 
+The merge request will open in your $EDITOR.
 
4285
 
+When no TARGET is specified, it uses the current tree revision.  When
 
4286
 
+no MERGE is specified, it uses the direct merges (as in "revisions
 
4287
 
+--direct-merges").  But you can specify just the TARGET, or all the MERGE
 
4291
 
+    def get_parser(self):
 
4292
 
+        """Produce a commandline parser for this command.
 
4294
 
+        :rtype: `cmdutil.CmdOptionParser`
 
4296
 
+        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
 
4300
 
+'changes' : Changes,
 
4303
 
+'apply-changes':ApplyChanges,
 
4306
 
+'revision': Revision,
 
4307
 
+'revisions': Revisions,
 
4314
 
+'mirror-archive': MirrorArchive,
 
4315
 
+'ninventory': Inventory,
 
4317
 
+'request-merge': RequestMerge,
 
4320
 
+def my_import(mod_name):
 
4321
 
+    module = __import__(mod_name)
 
4322
 
+    components = mod_name.split('.')
 
4323
 
+    for comp in components[1:]:
 
4324
 
+        module = getattr(module, comp)
 
4327
 
+def plugin(mod_name):
 
4328
 
+    module = my_import(mod_name)
 
4329
 
+    module.add_command(commands)
 
4331
 
+for file in os.listdir(sys.path[0]+"/command"):
 
4332
 
+    if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
 
4333
 
+        plugin("command."+file[:-3])
 
4336
 
+'apply-delta' : "Try \"apply-changes\".",
 
4337
 
+'delta' : "To compare two revisions, use \"changes\".",
 
4338
 
+'diff-rev' : "To compare two revisions, use \"changes\".",
 
4339
 
+'undo' : "To undo local changes, use \"revert\".",
 
4340
 
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
 
4341
 
+'missing-from' : "Try \"revisions --missing-from\".",
 
4342
 
+'missing' : "Try \"revisions --missing\".",
 
4343
 
+'missing-merge' : "Try \"revisions --partner-missing\".",
 
4344
 
+'new-merges' : "Try \"revisions --new-merges\".",
 
4345
 
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
 
4346
 
+'logs' : "Try \"revisions --logs\"",
 
4347
 
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
 
4348
 
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
 
4349
 
+'change-version' : "Try \"update REVISION\"",
 
4350
 
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
 
4351
 
+'rev-depends' : "Use revisions --dependencies",
 
4352
 
+'auto-get' : "Plain get will do archive lookups",
 
4353
 
+'tagline' : "Use add-id.  It uses taglines in tagline trees",
 
4354
 
+'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
 
4355
 
+'library-revisions' : "Use revisions --library",
 
4356
 
+'file-revert' : "Use revert FILE",
 
4357
 
+'join-branch' : "Use replay --logs-only"
 
4359
 
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
 
4361
 
*** added file 'testdata/orig'
 
4365
 
+# Copyright (C) 2004 Aaron Bentley
 
4366
 
+# <aaron.bentley@utoronto.ca>
 
4368
 
+#    This program is free software; you can redistribute it and/or modify
 
4369
 
+#    it under the terms of the GNU General Public License as published by
 
4370
 
+#    the Free Software Foundation; either version 2 of the License, or
 
4371
 
+#    (at your option) any later version.
 
4373
 
+#    This program is distributed in the hope that it will be useful,
 
4374
 
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
4375
 
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
4376
 
+#    GNU General Public License for more details.
 
4378
 
+#    You should have received a copy of the GNU General Public License
 
4379
 
+#    along with this program; if not, write to the Free Software
 
4380
 
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
4398
 
+from errors import *
 
4406
 
+__docformat__ = "restructuredtext"
 
4407
 
+__doc__ = "Implementation of user (sub) commands"
 
4410
 
+def find_command(cmd):
 
4412
 
+    Return an instance of a command type.  Return None if the type isn't
 
4415
 
+    :param cmd: the name of the command to look for
 
4416
 
+    :type cmd: the type of the command
 
4418
 
+    if commands.has_key(cmd):
 
4419
 
+        return commands[cmd]()
 
4424
 
+    def __call__(self, cmdline):
 
4426
 
+            self.do_command(cmdline.split())
 
4427
 
+        except cmdutil.GetHelp, e:
 
4429
 
+        except Exception, e:
 
4432
 
+    def get_completer(index):
 
4435
 
+    def complete(self, args, text):
 
4437
 
+        Returns a list of possible completions for the given text.
 
4439
 
+        :param args: The complete list of arguments
 
4440
 
+        :type args: List of str
 
4441
 
+        :param text: text to complete (may be shorter than args[-1])
 
4443
 
+        :rtype: list of str
 
4449
 
+            realtext = args[-1]
 
4454
 
+            parser=self.get_parser()
 
4455
 
+            if realtext.startswith('-'):
 
4456
 
+                candidates = parser.iter_options()
 
4458
 
+                (options, parsed_args) = parser.parse_args(args)
 
4460
 
+                if len (parsed_args) > 0:
 
4461
 
+                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
 
4463
 
+                    candidates = self.get_completer("", 0)
 
4466
 
+        if candidates is None:
 
4468
 
+        for candidate in candidates:
 
4469
 
+            candidate = str(candidate)
 
4470
 
+            if candidate.startswith(realtext):
 
4471
 
+                matches.append(candidate[len(realtext)- len(text):])
 
4475
 
+class Help(BaseCommand):
 
4477
 
+    Lists commands, prints help messages.
 
4479
 
+    def __init__(self):
 
4480
 
+        self.description="Prints help mesages"
 
4481
 
+        self.parser = None
 
4483
 
+    def do_command(self, cmdargs):
 
4485
 
+        Prints a help message.
 
4487
 
+        options, args = self.get_parser().parse_args(cmdargs)
 
4489
 
+            raise cmdutil.GetHelp
 
4491
 
+        if options.native or options.suggestions or options.external:
 
4492
 
+            native = options.native
 
4493
 
+            suggestions = options.suggestions
 
4494
 
+            external = options.external
 
4497
 
+            suggestions = False
 
4500
 
+        if len(args) == 0:
 
4501
 
+            self.list_commands(native, suggestions, external)
 
4503
 
+        elif len(args) == 1:
 
4504
 
+            command_help(args[0])
 
4508
 
+        self.get_parser().print_help()
 
4510
 
+If no command is specified, commands are listed.  If a command is
 
4511
 
+specified, help for that command is listed.
 
4514
 
+    def get_parser(self):
 
4516
 
+        Returns the options parser to use for the "revision" command.
 
4518
 
+        :rtype: cmdutil.CmdOptionParser
 
4520
 
+        if self.parser is not None:
 
4521
 
+            return self.parser
 
4522
 
+        parser=cmdutil.CmdOptionParser("fai help [command]")
 
4523
 
+        parser.add_option("-n", "--native", action="store_true", 
 
4524
 
+                         dest="native", help="Show native commands")
 
4525
 
+        parser.add_option("-e", "--external", action="store_true", 
 
4526
 
+                         dest="external", help="Show external commands")
 
4527
 
+        parser.add_option("-s", "--suggest", action="store_true", 
 
4528
 
+                         dest="suggestions", help="Show suggestions")
 
4529
 
+        self.parser = parser
 
4532
 
+    def list_commands(self, native=True, suggest=False, external=True):
 
4534
 
+        Lists supported commands.
 
4536
 
+        :param native: list native, python-based commands
 
4537
 
+        :type native: bool
 
4538
 
+        :param external: list external aba-style commands
 
4539
 
+        :type external: bool
 
4542
 
+            print "Native Fai commands"
 
4543
 
+            keys=commands.keys()
 
4547
 
+                for i in range(28-len(k)):
 
4549
 
+                print space+k+" : "+commands[k]().description
 
4552
 
+            print "Unavailable commands and suggested alternatives"
 
4553
 
+            key_list = suggestions.keys()
 
4555
 
+            for key in key_list:
 
4556
 
+                print "%28s : %s" % (key, suggestions[key])
 
4559
 
+            fake_aba = abacmds.AbaCmds()
 
4560
 
+            if (fake_aba.abadir == ""):
 
4562
 
+            print "External commands"
 
4563
 
+            fake_aba.list_commands()
 
4566
 
+            print "Use help --suggest to list alternatives to tla and aba"\
 
4568
 
+        if options.tla_fallthrough and (native or external):
 
4569
 
+            print "Fai also supports tla commands."
 
4571
 
+def command_help(cmd):
 
4573
 
+    Prints help for a command.
 
4575
 
+    :param cmd: The name of the command to print help for
 
4578
 
+    fake_aba = abacmds.AbaCmds()
 
4579
 
+    cmdobj = find_command(cmd)
 
4580
 
+    if cmdobj != None:
 
4582
 
+    elif suggestions.has_key(cmd):
 
4583
 
+        print "Not available\n" + suggestions[cmd]
 
4585
 
+        abacmd = fake_aba.is_command(cmd)
 
4589
 
+            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
 
4593
 
+class Changes(BaseCommand):
 
4595
 
+    the "changes" command: lists differences between trees/revisions:
 
4598
 
+    def __init__(self):
 
4599
 
+        self.description="Lists what files have changed in the project tree"
 
4601
 
+    def get_completer(self, arg, index):
 
4605
 
+            tree = arch.tree_root()
 
4608
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
4610
 
+    def parse_commandline(self, cmdline):
 
4612
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
 
4614
 
+        :param cmdline: A list of arguments to parse
 
4615
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
4617
 
+        parser=self.get_parser()
 
4618
 
+        (options, args) = parser.parse_args(cmdline)
 
4620
 
+            raise cmdutil.GetHelp
 
4622
 
+        tree=arch.tree_root()
 
4623
 
+        if len(args) == 0:
 
4624
 
+            a_spec = cmdutil.comp_revision(tree)
 
4626
 
+            a_spec = cmdutil.determine_revision_tree(tree, args[0])
 
4627
 
+        cmdutil.ensure_archive_registered(a_spec.archive)
 
4628
 
+        if len(args) == 2:
 
4629
 
+            b_spec = cmdutil.determine_revision_tree(tree, args[1])
 
4630
 
+            cmdutil.ensure_archive_registered(b_spec.archive)
 
4633
 
+        return options, a_spec, b_spec
 
4635
 
+    def do_command(self, cmdargs):
 
4637
 
+        Master function that perfoms the "changes" command.
 
4640
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs);
 
4641
 
+        except cmdutil.CantDetermineRevision, e:
 
4644
 
+        except arch.errors.TreeRootError, e:
 
4647
 
+        if options.changeset:
 
4648
 
+            changeset=options.changeset
 
4651
 
+            tmpdir=cmdutil.tmpdir()
 
4652
 
+            changeset=tmpdir+"/changeset"
 
4654
 
+            delta=arch.iter_delta(a_spec, b_spec, changeset)
 
4656
 
+                for line in delta:
 
4657
 
+                    if cmdutil.chattermatch(line, "changeset:"):
 
4660
 
+                        cmdutil.colorize(line, options.suppress_chatter)
 
4661
 
+            except arch.util.ExecProblem, e:
 
4662
 
+                if e.proc.error and e.proc.error.startswith(
 
4663
 
+                    "missing explicit id for file"):
 
4664
 
+                    raise MissingID(e)
 
4667
 
+            status=delta.status
 
4670
 
+            if (options.perform_diff):
 
4671
 
+                chan = cmdutil.ChangesetMunger(changeset)
 
4672
 
+                chan.read_indices()
 
4673
 
+                if isinstance(b_spec, arch.Revision):
 
4674
 
+                    b_dir = b_spec.library_find()
 
4677
 
+                a_dir = a_spec.library_find()
 
4678
 
+                if options.diffopts is not None:
 
4679
 
+                    diffopts = options.diffopts.split()
 
4680
 
+                    cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
 
4682
 
+                    cmdutil.show_diffs(delta.changeset)
 
4684
 
+            if tmpdir and (os.access(tmpdir, os.X_OK)):
 
4685
 
+                shutil.rmtree(tmpdir)
 
4687
 
+    def get_parser(self):
 
4689
 
+        Returns the options parser to use for the "changes" command.
 
4691
 
+        :rtype: cmdutil.CmdOptionParser
 
4693
 
+        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
 
4695
 
+        parser.add_option("-d", "--diff", action="store_true", 
 
4696
 
+                          dest="perform_diff", default=False, 
 
4697
 
+                          help="Show diffs in summary")
 
4698
 
+        parser.add_option("-c", "--changeset", dest="changeset", 
 
4699
 
+                          help="Store a changeset in the given directory", 
 
4700
 
+                          metavar="DIRECTORY")
 
4701
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
4702
 
+                          dest="suppress_chatter", default=False, 
 
4703
 
+                          help="Suppress chatter messages")
 
4704
 
+        parser.add_option("--diffopts", dest="diffopts", 
 
4705
 
+                          help="Use the specified diff options", 
 
4706
 
+                          metavar="OPTIONS")
 
4710
 
+    def help(self, parser=None):
 
4712
 
+        Prints a help message.
 
4714
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
4715
 
+        not supplied, it is retrieved.
 
4716
 
+        :type parser: cmdutil.CmdOptionParser
 
4718
 
+        if parser is None:
 
4719
 
+            parser=self.get_parser()
 
4720
 
+        parser.print_help()
 
4722
 
+Performs source-tree comparisons
 
4724
 
+If no revision is specified, the current project tree is compared to the
 
4725
 
+last-committed revision.  If one revision is specified, the current project
 
4726
 
+tree is compared to that revision.  If two revisions are specified, they are
 
4727
 
+compared to each other.
 
4733
 
+class ApplyChanges(BaseCommand):
 
4735
 
+    Apply differences between two revisions to a tree
 
4738
 
+    def __init__(self):
 
4739
 
+        self.description="Applies changes to a project tree"
 
4741
 
+    def get_completer(self, arg, index):
 
4745
 
+            tree = arch.tree_root()
 
4748
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
4750
 
+    def parse_commandline(self, cmdline, tree):
 
4752
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
 
4754
 
+        :param cmdline: A list of arguments to parse
 
4755
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
4757
 
+        parser=self.get_parser()
 
4758
 
+        (options, args) = parser.parse_args(cmdline)
 
4759
 
+        if len(args) != 2:
 
4760
 
+            raise cmdutil.GetHelp
 
4762
 
+        a_spec = cmdutil.determine_revision_tree(tree, args[0])
 
4763
 
+        cmdutil.ensure_archive_registered(a_spec.archive)
 
4764
 
+        b_spec = cmdutil.determine_revision_tree(tree, args[1])
 
4765
 
+        cmdutil.ensure_archive_registered(b_spec.archive)
 
4766
 
+        return options, a_spec, b_spec
 
4768
 
+    def do_command(self, cmdargs):
 
4770
 
+        Master function that performs "apply-changes".
 
4773
 
+            tree = arch.tree_root()
 
4774
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
 
4775
 
+        except cmdutil.CantDetermineRevision, e:
 
4778
 
+        except arch.errors.TreeRootError, e:
 
4781
 
+        delta=cmdutil.apply_delta(a_spec, b_spec, tree)
 
4782
 
+        for line in cmdutil.iter_apply_delta_filter(delta):
 
4783
 
+            cmdutil.colorize(line, options.suppress_chatter)
 
4785
 
+    def get_parser(self):
 
4787
 
+        Returns the options parser to use for the "apply-changes" command.
 
4789
 
+        :rtype: cmdutil.CmdOptionParser
 
4791
 
+        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
 
4793
 
+        parser.add_option("-d", "--diff", action="store_true", 
 
4794
 
+                          dest="perform_diff", default=False, 
 
4795
 
+                          help="Show diffs in summary")
 
4796
 
+        parser.add_option("-c", "--changeset", dest="changeset", 
 
4797
 
+                          help="Store a changeset in the given directory", 
 
4798
 
+                          metavar="DIRECTORY")
 
4799
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
4800
 
+                          dest="suppress_chatter", default=False, 
 
4801
 
+                          help="Suppress chatter messages")
 
4804
 
+    def help(self, parser=None):
 
4806
 
+        Prints a help message.
 
4808
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
4809
 
+        not supplied, it is retrieved.
 
4810
 
+        :type parser: cmdutil.CmdOptionParser
 
4812
 
+        if parser is None:
 
4813
 
+            parser=self.get_parser()
 
4814
 
+        parser.print_help()
 
4816
 
+Applies changes to a project tree
 
4818
 
+Compares two revisions and applies the difference between them to the current
 
4824
 
+class Update(BaseCommand):
 
4826
 
+    Updates a project tree to a given revision, preserving un-committed hanges. 
 
4829
 
+    def __init__(self):
 
4830
 
+        self.description="Apply the latest changes to the current directory"
 
4832
 
+    def get_completer(self, arg, index):
 
4836
 
+            tree = arch.tree_root()
 
4839
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
4841
 
+    def parse_commandline(self, cmdline, tree):
 
4843
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
 
4845
 
+        :param cmdline: A list of arguments to parse
 
4846
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
4848
 
+        parser=self.get_parser()
 
4849
 
+        (options, args) = parser.parse_args(cmdline)
 
4851
 
+            raise cmdutil.GetHelp
 
4856
 
+        revision=cmdutil.determine_revision_arch(tree, spec)
 
4857
 
+        cmdutil.ensure_archive_registered(revision.archive)
 
4859
 
+        mirror_source = cmdutil.get_mirror_source(revision.archive)
 
4860
 
+        if mirror_source != None:
 
4861
 
+            if cmdutil.prompt("Mirror update"):
 
4862
 
+                cmd=cmdutil.mirror_archive(mirror_source, 
 
4863
 
+                    revision.archive, arch.NameParser(revision).get_package_version())
 
4864
 
+                for line in arch.chatter_classifier(cmd):
 
4865
 
+                    cmdutil.colorize(line, options.suppress_chatter)
 
4867
 
+                revision=cmdutil.determine_revision_arch(tree, spec)
 
4869
 
+        return options, revision 
 
4871
 
+    def do_command(self, cmdargs):
 
4873
 
+        Master function that perfoms the "update" command.
 
4875
 
+        tree=arch.tree_root()
 
4877
 
+            options, to_revision = self.parse_commandline(cmdargs, tree);
 
4878
 
+        except cmdutil.CantDetermineRevision, e:
 
4881
 
+        except arch.errors.TreeRootError, e:
 
4884
 
+        from_revision=cmdutil.tree_latest(tree)
 
4885
 
+        if from_revision==to_revision:
 
4886
 
+            print "Tree is already up to date with:\n"+str(to_revision)+"."
 
4888
 
+        cmdutil.ensure_archive_registered(from_revision.archive)
 
4889
 
+        cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
 
4890
 
+            options.patch_forward)
 
4891
 
+        for line in cmdutil.iter_apply_delta_filter(cmd):
 
4892
 
+            cmdutil.colorize(line)
 
4893
 
+        if to_revision.version != tree.tree_version:
 
4894
 
+            if cmdutil.prompt("Update version"):
 
4895
 
+                tree.tree_version = to_revision.version
 
4897
 
+    def get_parser(self):
 
4899
 
+        Returns the options parser to use for the "update" command.
 
4901
 
+        :rtype: cmdutil.CmdOptionParser
 
4903
 
+        parser=cmdutil.CmdOptionParser("fai update [options]"
 
4904
 
+                                       " [revision/version]")
 
4905
 
+        parser.add_option("-f", "--forward", action="store_true", 
 
4906
 
+                          dest="patch_forward", default=False, 
 
4907
 
+                          help="pass the --forward option to 'patch'")
 
4908
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
4909
 
+                          dest="suppress_chatter", default=False, 
 
4910
 
+                          help="Suppress chatter messages")
 
4913
 
+    def help(self, parser=None):
 
4915
 
+        Prints a help message.
 
4917
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
4918
 
+        not supplied, it is retrieved.
 
4919
 
+        :type parser: cmdutil.CmdOptionParser
 
4921
 
+        if parser is None:
 
4922
 
+            parser=self.get_parser()
 
4923
 
+        parser.print_help()
 
4925
 
+Updates a working tree to the current archive revision
 
4927
 
+If a revision or version is specified, that is used instead 
 
4933
 
+class Commit(BaseCommand):
 
4935
 
+    Create a revision based on the changes in the current tree.
 
4938
 
+    def __init__(self):
 
4939
 
+        self.description="Write local changes to the archive"
 
4941
 
+    def get_completer(self, arg, index):
 
4944
 
+        return iter_modified_file_completions(arch.tree_root(), arg)
 
4945
 
+#        return iter_source_file_completions(arch.tree_root(), arg)
 
4947
 
+    def parse_commandline(self, cmdline, tree):
 
4949
 
+        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
 
4951
 
+        :param cmdline: A list of arguments to parse
 
4952
 
+        :rtype: (options, Revision, Revision/WorkingTree)
 
4954
 
+        parser=self.get_parser()
 
4955
 
+        (options, args) = parser.parse_args(cmdline)
 
4957
 
+        if len(args) == 0:
 
4959
 
+        revision=cmdutil.determine_revision_arch(tree, options.version)
 
4960
 
+        return options, revision.get_version(), args
 
4962
 
+    def do_command(self, cmdargs):
 
4964
 
+        Master function that perfoms the "commit" command.
 
4966
 
+        tree=arch.tree_root()
 
4967
 
+        options, version, files = self.parse_commandline(cmdargs, tree)
 
4968
 
+        if options.__dict__.has_key("base") and options.base:
 
4969
 
+            base = cmdutil.determine_revision_tree(tree, options.base)
 
4971
 
+            base = cmdutil.submit_revision(tree)
 
4973
 
+        writeversion=version
 
4974
 
+        archive=version.archive
 
4975
 
+        source=cmdutil.get_mirror_source(archive)
 
4977
 
+        writethrough="implicit"
 
4980
 
+            if writethrough=="explicit" and \
 
4981
 
+                cmdutil.prompt("Writethrough"):
 
4982
 
+                writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
 
4983
 
+            elif writethrough=="none":
 
4984
 
+                raise CommitToMirror(archive)
 
4986
 
+        elif archive.is_mirror:
 
4987
 
+            raise CommitToMirror(archive)
 
4990
 
+            last_revision=tree.iter_logs(version, True).next().revision
 
4991
 
+        except StopIteration, e:
 
4992
 
+            if cmdutil.prompt("Import from commit"):
 
4993
 
+                return do_import(version)
 
4995
 
+                raise NoVersionLogs(version)
 
4996
 
+        if last_revision!=version.iter_revisions(True).next():
 
4997
 
+            if not cmdutil.prompt("Out of date"):
 
5003
 
+            if not cmdutil.has_changed(version):
 
5004
 
+                if not cmdutil.prompt("Empty commit"):
 
5006
 
+        except arch.util.ExecProblem, e:
 
5007
 
+            if e.proc.error and e.proc.error.startswith(
 
5008
 
+                "missing explicit id for file"):
 
5009
 
+                raise MissingID(e)
 
5012
 
+        log = tree.log_message(create=False)
 
5015
 
+                if cmdutil.prompt("Create log"):
 
5018
 
+            except cmdutil.NoEditorSpecified, e:
 
5019
 
+                raise CommandFailed(e)
 
5020
 
+            log = tree.log_message(create=False)
 
5022
 
+            raise NoLogMessage
 
5023
 
+        if log["Summary"] is None or len(log["Summary"].strip()) == 0:
 
5024
 
+            if not cmdutil.prompt("Omit log summary"):
 
5025
 
+                raise errors.NoLogSummary
 
5027
 
+            for line in tree.iter_commit(version, seal=options.seal_version,
 
5028
 
+                base=base, out_of_date_ok=allow_old, file_list=files):
 
5029
 
+                cmdutil.colorize(line, options.suppress_chatter)
 
5031
 
+        except arch.util.ExecProblem, e:
 
5032
 
+            if e.proc.error and e.proc.error.startswith(
 
5033
 
+                "These files violate naming conventions:"):
 
5034
 
+                raise LintFailure(e.proc.error)
 
5038
 
+    def get_parser(self):
 
5040
 
+        Returns the options parser to use for the "commit" command.
 
5042
 
+        :rtype: cmdutil.CmdOptionParser
 
5045
 
+        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
 
5047
 
+        parser.add_option("--seal", action="store_true", 
 
5048
 
+                          dest="seal_version", default=False, 
 
5049
 
+                          help="seal this version")
 
5050
 
+        parser.add_option("-v", "--version", dest="version", 
 
5051
 
+                          help="Use the specified version", 
 
5052
 
+                          metavar="VERSION")
 
5053
 
+        parser.add_option("-s", "--silent", action="store_true", 
 
5054
 
+                          dest="suppress_chatter", default=False, 
 
5055
 
+                          help="Suppress chatter messages")
 
5056
 
+        if cmdutil.supports_switch("commit", "--base"):
 
5057
 
+            parser.add_option("--base", dest="base", help="", 
 
5058
 
+                              metavar="REVISION")
 
5061
 
+    def help(self, parser=None):
 
5063
 
+        Prints a help message.
 
5065
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
5066
 
+        not supplied, it is retrieved.
 
5067
 
+        :type parser: cmdutil.CmdOptionParser
 
5069
 
+        if parser is None:
 
5070
 
+            parser=self.get_parser()
 
5071
 
+        parser.print_help()
 
5073
 
+Updates a working tree to the current archive revision
 
5075
 
+If a version is specified, that is used instead 
 
5082
 
+class CatLog(BaseCommand):
 
5084
 
+    Print the log of a given file (from current tree)
 
5086
 
+    def __init__(self):
 
5087
 
+        self.description="Prints the patch log for a revision"
 
5089
 
+    def get_completer(self, arg, index):
 
5093
 
+            tree = arch.tree_root()
 
5096
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
5098
 
+    def do_command(self, cmdargs):
 
5100
 
+        Master function that perfoms the "cat-log" command.
 
5102
 
+        parser=self.get_parser()
 
5103
 
+        (options, args) = parser.parse_args(cmdargs)
 
5105
 
+            tree = arch.tree_root()
 
5106
 
+        except arch.errors.TreeRootError, e:
 
5112
 
+            raise cmdutil.GetHelp()
 
5115
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
 
5117
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
 
5118
 
+        except cmdutil.CantDetermineRevision, e:
 
5119
 
+            raise CommandFailedWrapper(e)
 
5122
 
+        use_tree = (options.source == "tree" or \
 
5123
 
+            (options.source == "any" and tree))
 
5124
 
+        use_arch = (options.source == "archive" or options.source == "any")
 
5128
 
+            for log in tree.iter_logs(revision.get_version()):
 
5129
 
+                if log.revision == revision:
 
5133
 
+        if log is None and use_arch:
 
5134
 
+            cmdutil.ensure_revision_exists(revision)
 
5135
 
+            log = arch.Patchlog(revision)
 
5136
 
+        if log is not None:
 
5137
 
+            for item in log.items():
 
5138
 
+                print "%s: %s" % item
 
5139
 
+            print log.description
 
5141
 
+    def get_parser(self):
 
5143
 
+        Returns the options parser to use for the "cat-log" command.
 
5145
 
+        :rtype: cmdutil.CmdOptionParser
 
5147
 
+        parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
 
5148
 
+        parser.add_option("--archive", action="store_const", dest="source",
 
5149
 
+                          const="archive", default="any",
 
5150
 
+                          help="Always get the log from the archive")
 
5151
 
+        parser.add_option("--tree", action="store_const", dest="source",
 
5152
 
+                          const="tree", help="Always get the log from the tree")
 
5155
 
+    def help(self, parser=None):
 
5157
 
+        Prints a help message.
 
5159
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
5160
 
+        not supplied, it is retrieved.
 
5161
 
+        :type parser: cmdutil.CmdOptionParser
 
5164
 
+            parser=self.get_parser()
 
5165
 
+        parser.print_help()
 
5167
 
+Prints the log for the specified revision
 
5172
 
+class Revert(BaseCommand):
 
5173
 
+    """ Reverts a tree (or aspects of it) to a revision
 
5175
 
+    def __init__(self):
 
5176
 
+        self.description="Reverts a tree (or aspects of it) to a revision "
 
5178
 
+    def get_completer(self, arg, index):
 
5182
 
+            tree = arch.tree_root()
 
5185
 
+        return iter_modified_file_completions(tree, arg)
 
5187
 
+    def do_command(self, cmdargs):
 
5189
 
+        Master function that perfoms the "revert" command.
 
5191
 
+        parser=self.get_parser()
 
5192
 
+        (options, args) = parser.parse_args(cmdargs)
 
5194
 
+            tree = arch.tree_root()
 
5195
 
+        except arch.errors.TreeRootError, e:
 
5196
 
+            raise CommandFailed(e)
 
5198
 
+        if options.revision is not None:
 
5199
 
+            spec=options.revision
 
5201
 
+            if spec is not None:
 
5202
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
 
5204
 
+                revision = cmdutil.comp_revision(tree)
 
5205
 
+        except cmdutil.CantDetermineRevision, e:
 
5206
 
+            raise CommandFailedWrapper(e)
 
5209
 
+        if options.file_contents or options.file_perms or options.deletions\
 
5210
 
+            or options.additions or options.renames or options.hunk_prompt:
 
5211
 
+            munger = cmdutil.MungeOpts()
 
5212
 
+            munger.hunk_prompt = options.hunk_prompt
 
5214
 
+        if len(args) > 0 or options.logs or options.pattern_files or \
 
5216
 
+            if munger is None:
 
5217
 
+                munger = cmdutil.MungeOpts(True)
 
5218
 
+                munger.all_types(True)
 
5220
 
+            t_cwd = cmdutil.tree_cwd(tree)
 
5222
 
+                if len(t_cwd) > 0:
 
5224
 
+                name = "./" + t_cwd + name
 
5225
 
+                munger.add_keep_file(name);
 
5227
 
+        if options.file_perms:
 
5228
 
+            munger.file_perms = True
 
5229
 
+        if options.file_contents:
 
5230
 
+            munger.file_contents = True
 
5231
 
+        if options.deletions:
 
5232
 
+            munger.deletions = True
 
5233
 
+        if options.additions:
 
5234
 
+            munger.additions = True
 
5235
 
+        if options.renames:
 
5236
 
+            munger.renames = True
 
5238
 
+            munger.add_keep_pattern('^\./\{arch\}/[^=].*')
 
5239
 
+        if options.control:
 
5240
 
+            munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
 
5241
 
+                                    "/\.arch-inventory$")
 
5242
 
+        if options.pattern_files:
 
5243
 
+            munger.add_keep_pattern(options.pattern_files)
 
5245
 
+        for line in cmdutil.revert(tree, revision, munger, 
 
5246
 
+                                   not options.no_output):
 
5247
 
+            cmdutil.colorize(line)
 
5250
 
+    def get_parser(self):
 
5252
 
+        Returns the options parser to use for the "cat-log" command.
 
5254
 
+        :rtype: cmdutil.CmdOptionParser
 
5256
 
+        parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
 
5257
 
+        parser.add_option("", "--contents", action="store_true", 
 
5258
 
+                          dest="file_contents", 
 
5259
 
+                          help="Revert file content changes")
 
5260
 
+        parser.add_option("", "--permissions", action="store_true", 
 
5261
 
+                          dest="file_perms", 
 
5262
 
+                          help="Revert file permissions changes")
 
5263
 
+        parser.add_option("", "--deletions", action="store_true", 
 
5265
 
+                          help="Restore deleted files")
 
5266
 
+        parser.add_option("", "--additions", action="store_true", 
 
5268
 
+                          help="Remove added files")
 
5269
 
+        parser.add_option("", "--renames", action="store_true", 
 
5271
 
+                          help="Revert file names")
 
5272
 
+        parser.add_option("--hunks", action="store_true", 
 
5273
 
+                          dest="hunk_prompt", default=False,
 
5274
 
+                          help="Prompt which hunks to revert")
 
5275
 
+        parser.add_option("--pattern-files", dest="pattern_files", 
 
5276
 
+                          help="Revert files that match this pattern", 
 
5278
 
+        parser.add_option("--logs", action="store_true", 
 
5279
 
+                          dest="logs", default=False,
 
5280
 
+                          help="Revert only logs")
 
5281
 
+        parser.add_option("--control-files", action="store_true", 
 
5282
 
+                          dest="control", default=False,
 
5283
 
+                          help="Revert logs and other control files")
 
5284
 
+        parser.add_option("-n", "--no-output", action="store_true", 
 
5286
 
+                          help="Don't keep an undo changeset")
 
5287
 
+        parser.add_option("--revision", dest="revision", 
 
5288
 
+                          help="Revert to the specified revision", 
 
5289
 
+                          metavar="REVISION")
 
5292
 
+    def help(self, parser=None):
 
5294
 
+        Prints a help message.
 
5296
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
5297
 
+        not supplied, it is retrieved.
 
5298
 
+        :type parser: cmdutil.CmdOptionParser
 
5301
 
+            parser=self.get_parser()
 
5302
 
+        parser.print_help()
 
5304
 
+Reverts changes in the current working tree.  If no flags are specified, all
 
5305
 
+types of changes are reverted.  Otherwise, only selected types of changes are
 
5308
 
+If a revision is specified on the commandline, differences between the current
 
5309
 
+tree and that revision are reverted.  If a version is specified, the current
 
5310
 
+tree is used to determine the revision.
 
5312
 
+If files are specified, only those files listed will have any changes applied.
 
5313
 
+To specify a renamed file, you can use either the old or new name. (or both!)
 
5315
 
+Unless "-n" is specified, reversions can be undone with "redo".
 
5319
 
+class Revision(BaseCommand):
 
5321
 
+    Print a revision name based on a revision specifier
 
5323
 
+    def __init__(self):
 
5324
 
+        self.description="Prints the name of a revision"
 
5326
 
+    def get_completer(self, arg, index):
 
5330
 
+            tree = arch.tree_root()
 
5333
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
5335
 
+    def do_command(self, cmdargs):
 
5337
 
+        Master function that perfoms the "revision" command.
 
5339
 
+        parser=self.get_parser()
 
5340
 
+        (options, args) = parser.parse_args(cmdargs)
 
5343
 
+            tree = arch.tree_root()
 
5344
 
+        except arch.errors.TreeRootError:
 
5351
 
+            raise cmdutil.GetHelp
 
5354
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
 
5356
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
 
5357
 
+        except cmdutil.CantDetermineRevision, e:
 
5360
 
+        print options.display(revision)
 
5362
 
+    def get_parser(self):
 
5364
 
+        Returns the options parser to use for the "revision" command.
 
5366
 
+        :rtype: cmdutil.CmdOptionParser
 
5368
 
+        parser=cmdutil.CmdOptionParser("fai revision [revision]")
 
5369
 
+        parser.add_option("", "--location", action="store_const", 
 
5370
 
+                         const=paths.determine_path, dest="display", 
 
5371
 
+                         help="Show location instead of name", default=str)
 
5372
 
+        parser.add_option("--import", action="store_const", 
 
5373
 
+                         const=paths.determine_import_path, dest="display",  
 
5374
 
+                         help="Show location of import file")
 
5375
 
+        parser.add_option("--log", action="store_const", 
 
5376
 
+                         const=paths.determine_log_path, dest="display", 
 
5377
 
+                         help="Show location of log file")
 
5378
 
+        parser.add_option("--patch", action="store_const", 
 
5379
 
+                         dest="display", const=paths.determine_patch_path,
 
5380
 
+                         help="Show location of patchfile")
 
5381
 
+        parser.add_option("--continuation", action="store_const", 
 
5382
 
+                         const=paths.determine_continuation_path, 
 
5384
 
+                         help="Show location of continuation file")
 
5385
 
+        parser.add_option("--cacherev", action="store_const", 
 
5386
 
+                         const=paths.determine_cacherev_path, dest="display",
 
5387
 
+                         help="Show location of cacherev file")
 
5390
 
+    def help(self, parser=None):
 
5392
 
+        Prints a help message.
 
5394
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
5395
 
+        not supplied, it is retrieved.
 
5396
 
+        :type parser: cmdutil.CmdOptionParser
 
5399
 
+            parser=self.get_parser()
 
5400
 
+        parser.print_help()
 
5402
 
+Expands aliases and prints the name of the specified revision.  Instead of
 
5403
 
+the name, several options can be used to print locations.  If more than one is
 
5404
 
+specified, the last one is used.
 
5409
 
+def require_version_exists(version, spec):
 
5410
 
+    if not version.exists():
 
5411
 
+        raise cmdutil.CantDetermineVersion(spec, 
 
5412
 
+                                           "The version %s does not exist." \
 
5415
 
+class Revisions(BaseCommand):
 
5417
 
+    Print a revision name based on a revision specifier
 
5419
 
+    def __init__(self):
 
5420
 
+        self.description="Lists revisions"
 
5422
 
+    def do_command(self, cmdargs):
 
5424
 
+        Master function that perfoms the "revision" command.
 
5426
 
+        (options, args) = self.get_parser().parse_args(cmdargs)
 
5428
 
+            raise cmdutil.GetHelp
 
5430
 
+            self.tree = arch.tree_root()
 
5431
 
+        except arch.errors.TreeRootError:
 
5434
 
+            iter = self.get_iterator(options.type, args, options.reverse, 
 
5436
 
+        except cmdutil.CantDetermineRevision, e:
 
5437
 
+            raise CommandFailedWrapper(e)
 
5439
 
+        if options.skip is not None:
 
5440
 
+            iter = cmdutil.iter_skip(iter, int(options.skip))
 
5442
 
+        for revision in iter:
 
5444
 
+            if isinstance(revision, arch.Patchlog):
 
5446
 
+                revision=revision.revision
 
5447
 
+            print options.display(revision)
 
5448
 
+            if log is None and (options.summary or options.creator or 
 
5449
 
+                                options.date or options.merges):
 
5450
 
+                log = revision.patchlog
 
5451
 
+            if options.creator:
 
5452
 
+                print "    %s" % log.creator
 
5454
 
+                print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
 
5455
 
+            if options.summary:
 
5456
 
+                print "    %s" % log.summary
 
5457
 
+            if options.merges:
 
5458
 
+                showed_title = False
 
5459
 
+                for revision in log.merged_patches:
 
5460
 
+                    if not showed_title:
 
5462
 
+                        showed_title = True
 
5463
 
+                    print "    %s" % revision
 
5465
 
+    def get_iterator(self, type, args, reverse, modified):
 
5470
 
+        if modified is not None:
 
5471
 
+            iter = cmdutil.modified_iter(modified, self.tree)
 
5475
 
+                return cmdutil.iter_reverse(iter)
 
5476
 
+        elif type == "archive":
 
5478
 
+                if self.tree is None:
 
5479
 
+                    raise cmdutil.CantDetermineRevision("", 
 
5480
 
+                                                        "Not in a project tree")
 
5481
 
+                version = cmdutil.determine_version_tree(spec, self.tree)
 
5483
 
+                version = cmdutil.determine_version_arch(spec, self.tree)
 
5484
 
+                cmdutil.ensure_archive_registered(version.archive)
 
5485
 
+                require_version_exists(version, spec)
 
5486
 
+            return version.iter_revisions(reverse)
 
5487
 
+        elif type == "cacherevs":
 
5489
 
+                if self.tree is None:
 
5490
 
+                    raise cmdutil.CantDetermineRevision("", 
 
5491
 
+                                                        "Not in a project tree")
 
5492
 
+                version = cmdutil.determine_version_tree(spec, self.tree)
 
5494
 
+                version = cmdutil.determine_version_arch(spec, self.tree)
 
5495
 
+                cmdutil.ensure_archive_registered(version.archive)
 
5496
 
+                require_version_exists(version, spec)
 
5497
 
+            return cmdutil.iter_cacherevs(version, reverse)
 
5498
 
+        elif type == "library":
 
5500
 
+                if self.tree is None:
 
5501
 
+                    raise cmdutil.CantDetermineRevision("", 
 
5502
 
+                                                        "Not in a project tree")
 
5503
 
+                version = cmdutil.determine_version_tree(spec, self.tree)
 
5505
 
+                version = cmdutil.determine_version_arch(spec, self.tree)
 
5506
 
+            return version.iter_library_revisions(reverse)
 
5507
 
+        elif type == "logs":
 
5508
 
+            if self.tree is None:
 
5509
 
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
5510
 
+            return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
 
5511
 
+                                  self.tree), reverse)
 
5512
 
+        elif type == "missing" or type == "skip-present":
 
5513
 
+            if self.tree is None:
 
5514
 
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
5515
 
+            skip = (type == "skip-present")
 
5516
 
+            version = cmdutil.determine_version_tree(spec, self.tree)
 
5517
 
+            cmdutil.ensure_archive_registered(version.archive)
 
5518
 
+            require_version_exists(version, spec)
 
5519
 
+            return cmdutil.iter_missing(self.tree, version, reverse,
 
5520
 
+                                        skip_present=skip)
 
5522
 
+        elif type == "present":
 
5523
 
+            if self.tree is None:
 
5524
 
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
5525
 
+            version = cmdutil.determine_version_tree(spec, self.tree)
 
5526
 
+            cmdutil.ensure_archive_registered(version.archive)
 
5527
 
+            require_version_exists(version, spec)
 
5528
 
+            return cmdutil.iter_present(self.tree, version, reverse)
 
5530
 
+        elif type == "new-merges" or type == "direct-merges":
 
5531
 
+            if self.tree is None:
 
5532
 
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
5533
 
+            version = cmdutil.determine_version_tree(spec, self.tree)
 
5534
 
+            cmdutil.ensure_archive_registered(version.archive)
 
5535
 
+            require_version_exists(version, spec)
 
5536
 
+            iter = cmdutil.iter_new_merges(self.tree, version, reverse)
 
5537
 
+            if type == "new-merges":
 
5539
 
+            elif type == "direct-merges":
 
5540
 
+                return cmdutil.direct_merges(iter)
 
5542
 
+        elif type == "missing-from":
 
5543
 
+            if self.tree is None:
 
5544
 
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
 
5545
 
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
 
5546
 
+            libtree = cmdutil.find_or_make_local_revision(revision)
 
5547
 
+            return cmdutil.iter_missing(libtree, self.tree.tree_version,
 
5550
 
+        elif type == "partner-missing":
 
5551
 
+            return cmdutil.iter_partner_missing(self.tree, reverse)
 
5553
 
+        elif type == "ancestry":
 
5554
 
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
 
5555
 
+            iter = cmdutil._iter_ancestry(self.tree, revision)
 
5559
 
+                return cmdutil.iter_reverse(iter)
 
5561
 
+        elif type == "dependencies" or type == "non-dependencies":
 
5562
 
+            nondeps = (type == "non-dependencies")
 
5563
 
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
 
5564
 
+            anc_iter = cmdutil._iter_ancestry(self.tree, revision)
 
5565
 
+            iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
 
5567
 
+                return iter_depends
 
5569
 
+                return cmdutil.iter_reverse(iter_depends)
 
5570
 
+        elif type == "micro":
 
5571
 
+            return cmdutil.iter_micro(self.tree)
 
5574
 
+    def get_parser(self):
 
5576
 
+        Returns the options parser to use for the "revision" command.
 
5578
 
+        :rtype: cmdutil.CmdOptionParser
 
5580
 
+        parser=cmdutil.CmdOptionParser("fai revisions [revision]")
 
5581
 
+        select = cmdutil.OptionGroup(parser, "Selection options",
 
5582
 
+                          "Control which revisions are listed.  These options"
 
5583
 
+                          " are mutually exclusive.  If more than one is"
 
5584
 
+                          " specified, the last is used.")
 
5585
 
+        select.add_option("", "--archive", action="store_const", 
 
5586
 
+                          const="archive", dest="type", default="archive",
 
5587
 
+                          help="List all revisions in the archive")
 
5588
 
+        select.add_option("", "--cacherevs", action="store_const", 
 
5589
 
+                          const="cacherevs", dest="type",
 
5590
 
+                          help="List all revisions stored in the archive as "
 
5591
 
+                          "complete copies")
 
5592
 
+        select.add_option("", "--logs", action="store_const", 
 
5593
 
+                          const="logs", dest="type",
 
5594
 
+                          help="List revisions that have a patchlog in the "
 
5596
 
+        select.add_option("", "--missing", action="store_const", 
 
5597
 
+                          const="missing", dest="type",
 
5598
 
+                          help="List revisions from the specified version that"
 
5599
 
+                          " have no patchlog in the tree")
 
5600
 
+        select.add_option("", "--skip-present", action="store_const", 
 
5601
 
+                          const="skip-present", dest="type",
 
5602
 
+                          help="List revisions from the specified version that"
 
5603
 
+                          " have no patchlogs at all in the tree")
 
5604
 
+        select.add_option("", "--present", action="store_const", 
 
5605
 
+                          const="present", dest="type",
 
5606
 
+                          help="List revisions from the specified version that"
 
5607
 
+                          " have no patchlog in the tree, but can't be merged")
 
5608
 
+        select.add_option("", "--missing-from", action="store_const", 
 
5609
 
+                          const="missing-from", dest="type",
 
5610
 
+                          help="List revisions from the specified revision "
 
5611
 
+                          "that have no patchlog for the tree version")
 
5612
 
+        select.add_option("", "--partner-missing", action="store_const", 
 
5613
 
+                          const="partner-missing", dest="type",
 
5614
 
+                          help="List revisions in partner versions that are"
 
5616
 
+        select.add_option("", "--new-merges", action="store_const", 
 
5617
 
+                          const="new-merges", dest="type",
 
5618
 
+                          help="List revisions that have had patchlogs added"
 
5619
 
+                          " to the tree since the last commit")
 
5620
 
+        select.add_option("", "--direct-merges", action="store_const", 
 
5621
 
+                          const="direct-merges", dest="type",
 
5622
 
+                          help="List revisions that have been directly added"
 
5623
 
+                          " to tree since the last commit ")
 
5624
 
+        select.add_option("", "--library", action="store_const", 
 
5625
 
+                          const="library", dest="type",
 
5626
 
+                          help="List revisions in the revision library")
 
5627
 
+        select.add_option("", "--ancestry", action="store_const", 
 
5628
 
+                          const="ancestry", dest="type",
 
5629
 
+                          help="List revisions that are ancestors of the "
 
5630
 
+                          "current tree version")
 
5632
 
+        select.add_option("", "--dependencies", action="store_const", 
 
5633
 
+                          const="dependencies", dest="type",
 
5634
 
+                          help="List revisions that the given revision "
 
5637
 
+        select.add_option("", "--non-dependencies", action="store_const", 
 
5638
 
+                          const="non-dependencies", dest="type",
 
5639
 
+                          help="List revisions that the given revision "
 
5640
 
+                          "does not depend on")
 
5642
 
+        select.add_option("--micro", action="store_const", 
 
5643
 
+                          const="micro", dest="type",
 
5644
 
+                          help="List partner revisions aimed for this "
 
5647
 
+        select.add_option("", "--modified", dest="modified", 
 
5648
 
+                          help="List tree ancestor revisions that modified a "
 
5649
 
+                          "given file", metavar="FILE[:LINE]")
 
5651
 
+        parser.add_option("", "--skip", dest="skip", 
 
5652
 
+                          help="Skip revisions.  Positive numbers skip from "
 
5653
 
+                          "beginning, negative skip from end.",
 
5656
 
+        parser.add_option_group(select)
 
5658
 
+        format = cmdutil.OptionGroup(parser, "Revision format options",
 
5659
 
+                          "These control the appearance of listed revisions")
 
5660
 
+        format.add_option("", "--location", action="store_const", 
 
5661
 
+                         const=paths.determine_path, dest="display", 
 
5662
 
+                         help="Show location instead of name", default=str)
 
5663
 
+        format.add_option("--import", action="store_const", 
 
5664
 
+                         const=paths.determine_import_path, dest="display",  
 
5665
 
+                         help="Show location of import file")
 
5666
 
+        format.add_option("--log", action="store_const", 
 
5667
 
+                         const=paths.determine_log_path, dest="display", 
 
5668
 
+                         help="Show location of log file")
 
5669
 
+        format.add_option("--patch", action="store_const", 
 
5670
 
+                         dest="display", const=paths.determine_patch_path,
 
5671
 
+                         help="Show location of patchfile")
 
5672
 
+        format.add_option("--continuation", action="store_const", 
 
5673
 
+                         const=paths.determine_continuation_path, 
 
5675
 
+                         help="Show location of continuation file")
 
5676
 
+        format.add_option("--cacherev", action="store_const", 
 
5677
 
+                         const=paths.determine_cacherev_path, dest="display",
 
5678
 
+                         help="Show location of cacherev file")
 
5679
 
+        parser.add_option_group(format)
 
5680
 
+        display = cmdutil.OptionGroup(parser, "Display format options",
 
5681
 
+                          "These control the display of data")
 
5682
 
+        display.add_option("-r", "--reverse", action="store_true", 
 
5683
 
+                          dest="reverse", help="Sort from newest to oldest")
 
5684
 
+        display.add_option("-s", "--summary", action="store_true", 
 
5685
 
+                          dest="summary", help="Show patchlog summary")
 
5686
 
+        display.add_option("-D", "--date", action="store_true", 
 
5687
 
+                          dest="date", help="Show patchlog date")
 
5688
 
+        display.add_option("-c", "--creator", action="store_true", 
 
5689
 
+                          dest="creator", help="Show the id that committed the"
 
5691
 
+        display.add_option("-m", "--merges", action="store_true", 
 
5692
 
+                          dest="merges", help="Show the revisions that were"
 
5694
 
+        parser.add_option_group(display)
 
5696
 
+    def help(self, parser=None):
 
5697
 
+        """Attempt to explain the revisions command
 
5699
 
+        :param parser: If supplied, used to determine options
 
5702
 
+            parser=self.get_parser()
 
5703
 
+        parser.print_help()
 
5704
 
+        print """List revisions.
 
5709
 
+class Get(BaseCommand):
 
5711
 
+    Retrieve a revision from the archive
 
5713
 
+    def __init__(self):
 
5714
 
+        self.description="Retrieve a revision from the archive"
 
5715
 
+        self.parser=self.get_parser()
 
5718
 
+    def get_completer(self, arg, index):
 
5722
 
+            tree = arch.tree_root()
 
5725
 
+        return cmdutil.iter_revision_completions(arg, tree)
 
5728
 
+    def do_command(self, cmdargs):
 
5730
 
+        Master function that perfoms the "get" command.
 
5732
 
+        (options, args) = self.parser.parse_args(cmdargs)
 
5734
 
+            return self.help()            
 
5736
 
+            tree = arch.tree_root()
 
5737
 
+        except arch.errors.TreeRootError:
 
5742
 
+            revision, arch_loc = paths.full_path_decode(args[0])
 
5743
 
+        except Exception, e:
 
5744
 
+            revision = cmdutil.determine_revision_arch(tree, args[0], 
 
5745
 
+                check_existence=False, allow_package=True)
 
5747
 
+            directory = args[1]
 
5749
 
+            directory = str(revision.nonarch)
 
5750
 
+        if os.path.exists(directory):
 
5751
 
+            raise DirectoryExists(directory)
 
5752
 
+        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
 
5754
 
+            cmdutil.ensure_revision_exists(revision)
 
5755
 
+        except cmdutil.NoSuchRevision, e:
 
5756
 
+            raise CommandFailedWrapper(e)
 
5758
 
+        link = cmdutil.prompt ("get link")
 
5759
 
+        for line in cmdutil.iter_get(revision, directory, link,
 
5760
 
+                                     options.no_pristine,
 
5761
 
+                                     options.no_greedy_add):
 
5762
 
+            cmdutil.colorize(line)
 
5764
 
+    def get_parser(self):
 
5766
 
+        Returns the options parser to use for the "get" command.
 
5768
 
+        :rtype: cmdutil.CmdOptionParser
 
5770
 
+        parser=cmdutil.CmdOptionParser("fai get revision [dir]")
 
5771
 
+        parser.add_option("--no-pristine", action="store_true", 
 
5772
 
+                         dest="no_pristine", 
 
5773
 
+                         help="Do not make pristine copy for reference")
 
5774
 
+        parser.add_option("--no-greedy-add", action="store_true", 
 
5775
 
+                         dest="no_greedy_add", 
 
5776
 
+                         help="Never add to greedy libraries")
 
5780
 
+    def help(self, parser=None):
 
5782
 
+        Prints a help message.
 
5784
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
5785
 
+        not supplied, it is retrieved.
 
5786
 
+        :type parser: cmdutil.CmdOptionParser
 
5789
 
+            parser=self.get_parser()
 
5790
 
+        parser.print_help()
 
5792
 
+Expands aliases and constructs a project tree for a revision.  If the optional
 
5793
 
+"dir" argument is provided, the project tree will be stored in this directory.
 
5798
 
+class PromptCmd(cmd.Cmd):
 
5799
 
+    def __init__(self):
 
5800
 
+        cmd.Cmd.__init__(self)
 
5801
 
+        self.prompt = "Fai> "
 
5803
 
+            self.tree = arch.tree_root()
 
5808
 
+        self.fake_aba = abacmds.AbaCmds()
 
5809
 
+        self.identchars += '-'
 
5810
 
+        self.history_file = os.path.expanduser("~/.fai-history")
 
5811
 
+        readline.set_completer_delims(string.whitespace)
 
5812
 
+        if os.access(self.history_file, os.R_OK) and \
 
5813
 
+            os.path.isfile(self.history_file):
 
5814
 
+            readline.read_history_file(self.history_file)
 
5816
 
+    def write_history(self):
 
5817
 
+        readline.write_history_file(self.history_file)
 
5819
 
+    def do_quit(self, args):
 
5820
 
+        self.write_history()
 
5823
 
+    def do_exit(self, args):
 
5824
 
+        self.do_quit(args)
 
5826
 
+    def do_EOF(self, args):
 
5828
 
+        self.do_quit(args)
 
5830
 
+    def postcmd(self, line, bar):
 
5834
 
+    def set_prompt(self):
 
5835
 
+        if self.tree is not None:
 
5837
 
+                version = " "+self.tree.tree_version.nonarch
 
5842
 
+        self.prompt = "Fai%s> " % version
 
5844
 
+    def set_title(self, command=None):
 
5846
 
+            version = self.tree.tree_version.nonarch
 
5848
 
+            version = "[no version]"
 
5849
 
+        if command is None:
 
5851
 
+        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
 
5853
 
+    def do_cd(self, line):
 
5857
 
+            os.chdir(os.path.expanduser(line))
 
5858
 
+        except Exception, e:
 
5861
 
+            self.tree = arch.tree_root()
 
5865
 
+    def do_help(self, line):
 
5868
 
+    def default(self, line):
 
5869
 
+        args = line.split()
 
5870
 
+        if find_command(args[0]):
 
5872
 
+                find_command(args[0]).do_command(args[1:])
 
5873
 
+            except cmdutil.BadCommandOption, e:
 
5875
 
+            except cmdutil.GetHelp, e:
 
5876
 
+                find_command(args[0]).help()
 
5877
 
+            except CommandFailed, e:
 
5879
 
+            except arch.errors.ArchiveNotRegistered, e:
 
5881
 
+            except KeyboardInterrupt, e:
 
5882
 
+                print "Interrupted"
 
5883
 
+            except arch.util.ExecProblem, e:
 
5884
 
+                print e.proc.error.rstrip('\n')
 
5885
 
+            except cmdutil.CantDetermineVersion, e:
 
5887
 
+            except cmdutil.CantDetermineRevision, e:
 
5889
 
+            except Exception, e:
 
5890
 
+                print "Unhandled error:\n%s" % cmdutil.exception_str(e)
 
5892
 
+        elif suggestions.has_key(args[0]):
 
5893
 
+            print suggestions[args[0]]
 
5895
 
+        elif self.fake_aba.is_command(args[0]):
 
5898
 
+                tree = arch.tree_root()
 
5899
 
+            except arch.errors.TreeRootError:
 
5901
 
+            cmd = self.fake_aba.is_command(args[0])
 
5903
 
+                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
 
5904
 
+            except KeyboardInterrupt, e:
 
5905
 
+                print "Interrupted"
 
5907
 
+        elif options.tla_fallthrough and args[0] != "rm" and \
 
5908
 
+            cmdutil.is_tla_command(args[0]):
 
5912
 
+                    tree = arch.tree_root()
 
5913
 
+                except arch.errors.TreeRootError:
 
5915
 
+                args = cmdutil.expand_prefix_alias(args, tree)
 
5916
 
+                arch.util.exec_safe('tla', args, stderr=sys.stderr,
 
5918
 
+            except arch.util.ExecProblem, e:
 
5920
 
+            except KeyboardInterrupt, e:
 
5921
 
+                print "Interrupted"
 
5925
 
+                    tree = arch.tree_root()
 
5926
 
+                except arch.errors.TreeRootError:
 
5929
 
+                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
 
5930
 
+            except KeyboardInterrupt, e:
 
5931
 
+                print "Interrupted"
 
5933
 
+    def completenames(self, text, line, begidx, endidx):
 
5935
 
+        iter = iter_command_names(self.fake_aba)
 
5938
 
+                arg = line.split()[-1]
 
5941
 
+            iter = iter_munged_completions(iter, arg, text)
 
5942
 
+        except Exception, e:
 
5946
 
+    def completedefault(self, text, line, begidx, endidx):
 
5947
 
+        """Perform completion for native commands.
 
5949
 
+        :param text: The text to complete
 
5951
 
+        :param line: The entire line to complete
 
5953
 
+        :param begidx: The start of the text in the line
 
5955
 
+        :param endidx: The end of the text in the line
 
5959
 
+            (cmd, args, foo) = self.parseline(line)
 
5960
 
+            command_obj=find_command(cmd)
 
5961
 
+            if command_obj is not None:
 
5962
 
+                return command_obj.complete(args.split(), text)
 
5963
 
+            elif not self.fake_aba.is_command(cmd) and \
 
5964
 
+                cmdutil.is_tla_command(cmd):
 
5965
 
+                iter = cmdutil.iter_supported_switches(cmd)
 
5967
 
+                    arg = args.split()[-1]
 
5970
 
+                if arg.startswith("-"):
 
5971
 
+                    return list(iter_munged_completions(iter, arg, text))
 
5973
 
+                    return list(iter_munged_completions(
 
5974
 
+                        iter_file_completions(arg), arg, text))
 
5979
 
+                    arg = args.split()[-1]
 
5982
 
+                iter = iter_dir_completions(arg)
 
5983
 
+                iter = iter_munged_completions(iter, arg, text)
 
5986
 
+                arg = args.split()[-1]
 
5987
 
+                return list(iter_munged_completions(iter_file_completions(arg),
 
5990
 
+                return self.completenames(text, line, begidx, endidx)
 
5991
 
+        except Exception, e:
 
5995
 
+def iter_command_names(fake_aba):
 
5996
 
+    for entry in cmdutil.iter_combine([commands.iterkeys(), 
 
5997
 
+                                     fake_aba.get_commands(), 
 
5998
 
+                                     cmdutil.iter_tla_commands(False)]):
 
5999
 
+        if not suggestions.has_key(str(entry)):
 
6003
 
+def iter_file_completions(arg, only_dirs = False):
 
6004
 
+    """Generate an iterator that iterates through filename completions.
 
6006
 
+    :param arg: The filename fragment to match
 
6008
 
+    :param only_dirs: If true, match only directories
 
6009
 
+    :type only_dirs: bool
 
6013
 
+        extras = [".", ".."]
 
6016
 
+    (dir, file) = os.path.split(arg)
 
6018
 
+        listingdir = os.path.expanduser(dir)
 
6021
 
+    for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
 
6023
 
+            userfile = dir+'/'+file
 
6026
 
+        if userfile.startswith(arg):
 
6027
 
+            if os.path.isdir(listingdir+'/'+file):
 
6030
 
+            elif not only_dirs:
 
6033
 
+def iter_munged_completions(iter, arg, text):
 
6034
 
+    for completion in iter:
 
6035
 
+        completion = str(completion)
 
6036
 
+        if completion.startswith(arg):
 
6037
 
+            yield completion[len(arg)-len(text):]
 
6039
 
+def iter_source_file_completions(tree, arg):
 
6040
 
+    treepath = cmdutil.tree_cwd(tree)
 
6041
 
+    if len(treepath) > 0:
 
6045
 
+    for file in tree.iter_inventory(dirs, source=True, both=True):
 
6046
 
+        file = file_completion_match(file, treepath, arg)
 
6047
 
+        if file is not None:
 
6051
 
+def iter_untagged(tree, dirs):
 
6052
 
+    for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False, 
 
6053
 
+                                                categories=arch_core.non_root,
 
6054
 
+                                                control_files=True):
 
6058
 
+def iter_untagged_completions(tree, arg):
 
6059
 
+    """Generate an iterator for all visible untagged files that match arg.
 
6061
 
+    :param tree: The tree to look for untagged files in
 
6062
 
+    :type tree: `arch.WorkingTree`
 
6063
 
+    :param arg: The argument to match
 
6065
 
+    :return: An iterator of all matching untagged files
 
6066
 
+    :rtype: iterator of str
 
6068
 
+    treepath = cmdutil.tree_cwd(tree)
 
6069
 
+    if len(treepath) > 0:
 
6074
 
+    for file in iter_untagged(tree, dirs):
 
6075
 
+        file = file_completion_match(file, treepath, arg)
 
6076
 
+        if file is not None:
 
6080
 
+def file_completion_match(file, treepath, arg):
 
6081
 
+    """Determines whether a file within an arch tree matches the argument.
 
6083
 
+    :param file: The rooted filename
 
6085
 
+    :param treepath: The path to the cwd within the tree
 
6086
 
+    :type treepath: str
 
6087
 
+    :param arg: The prefix to match
 
6088
 
+    :return: The completion name, or None if not a match
 
6091
 
+    if not file.startswith(treepath):
 
6093
 
+    if treepath != "":
 
6094
 
+        file = file[len(treepath)+1:]
 
6096
 
+    if not file.startswith(arg):
 
6098
 
+    if os.path.isdir(file):
 
6102
 
+def iter_modified_file_completions(tree, arg):
 
6103
 
+    """Returns a list of modified files that match the specified prefix.
 
6105
 
+    :param tree: The current tree
 
6106
 
+    :type tree: `arch.WorkingTree`
 
6107
 
+    :param arg: The prefix to match
 
6110
 
+    treepath = cmdutil.tree_cwd(tree)
 
6111
 
+    tmpdir = cmdutil.tmpdir()
 
6112
 
+    changeset = tmpdir+"/changeset"
 
6114
 
+    revision = cmdutil.determine_revision_tree(tree)
 
6115
 
+    for line in arch.iter_delta(revision, tree, changeset):
 
6116
 
+        if isinstance(line, arch.FileModification):
 
6117
 
+            file = file_completion_match(line.name[1:], treepath, arg)
 
6118
 
+            if file is not None:
 
6119
 
+                completions.append(file)
 
6120
 
+    shutil.rmtree(tmpdir)
 
6121
 
+    return completions
 
6123
 
+def iter_dir_completions(arg):
 
6124
 
+    """Generate an iterator that iterates through directory name completions.
 
6126
 
+    :param arg: The directory name fragment to match
 
6129
 
+    return iter_file_completions(arg, True)
 
6131
 
+class Shell(BaseCommand):
 
6132
 
+    def __init__(self):
 
6133
 
+        self.description = "Runs Fai as a shell"
 
6135
 
+    def do_command(self, cmdargs):
 
6136
 
+        if len(cmdargs)!=0:
 
6137
 
+            raise cmdutil.GetHelp
 
6138
 
+        prompt = PromptCmd()
 
6142
 
+            prompt.write_history()
 
6144
 
+class AddID(BaseCommand):
 
6146
 
+    Adds an inventory id for the given file
 
6148
 
+    def __init__(self):
 
6149
 
+        self.description="Add an inventory id for a given file"
 
6151
 
+    def get_completer(self, arg, index):
 
6152
 
+        tree = arch.tree_root()
 
6153
 
+        return iter_untagged_completions(tree, arg)
 
6155
 
+    def do_command(self, cmdargs):
 
6157
 
+        Master function that perfoms the "revision" command.
 
6159
 
+        parser=self.get_parser()
 
6160
 
+        (options, args) = parser.parse_args(cmdargs)
 
6162
 
+        tree = arch.tree_root()
 
6164
 
+        if (len(args) == 0) == (options.untagged == False):
 
6165
 
+            raise cmdutil.GetHelp
 
6167
 
+       #if options.id and len(args) != 1:
 
6168
 
+       #    print "If --id is specified, only one file can be named."
 
6171
 
+        method = tree.tagging_method
 
6173
 
+        if options.id_type == "tagline":
 
6174
 
+            if method != "tagline":
 
6175
 
+                if not cmdutil.prompt("Tagline in other tree"):
 
6176
 
+                    if method == "explicit":
 
6177
 
+                        options.id_type == explicit
 
6179
 
+                        print "add-id not supported for \"%s\" tagging method"\
 
6183
 
+        elif options.id_type == "explicit":
 
6184
 
+            if method != "tagline" and method != explicit:
 
6185
 
+                if not prompt("Explicit in other tree"):
 
6186
 
+                    print "add-id not supported for \"%s\" tagging method" % \
 
6190
 
+        if options.id_type == "auto":
 
6191
 
+            if method != "tagline" and method != "explicit":
 
6192
 
+                print "add-id not supported for \"%s\" tagging method" % method
 
6195
 
+                options.id_type = method
 
6196
 
+        if options.untagged:
 
6198
 
+        self.add_ids(tree, options.id_type, args)
 
6200
 
+    def add_ids(self, tree, id_type, files=()):
 
6201
 
+        """Add inventory ids to files.
 
6203
 
+        :param tree: the tree the files are in
 
6204
 
+        :type tree: `arch.WorkingTree`
 
6205
 
+        :param id_type: the type of id to add: "explicit" or "tagline"
 
6206
 
+        :type id_type: str
 
6207
 
+        :param files: The list of files to add.  If None do all untagged.
 
6208
 
+        :type files: tuple of str
 
6211
 
+        untagged = (files is None)
 
6213
 
+            files = list(iter_untagged(tree, None))
 
6214
 
+        previous_files = []
 
6215
 
+        while len(files) > 0:
 
6216
 
+            previous_files.extend(files)
 
6217
 
+            if id_type == "explicit":
 
6218
 
+                cmdutil.add_id(files)
 
6219
 
+            elif id_type == "tagline":
 
6220
 
+                for file in files:
 
6222
 
+                        cmdutil.add_tagline_or_explicit_id(file)
 
6223
 
+                    except cmdutil.AlreadyTagged:
 
6224
 
+                        print "\"%s\" already has a tagline." % file
 
6225
 
+                    except cmdutil.NoCommentSyntax:
 
6227
 
+            #do inventory after tagging until no untagged files are encountered
 
6230
 
+                for file in iter_untagged(tree, None):
 
6231
 
+                    if not file in previous_files:
 
6232
 
+                        files.append(file)
 
6237
 
+    def get_parser(self):
 
6239
 
+        Returns the options parser to use for the "revision" command.
 
6241
 
+        :rtype: cmdutil.CmdOptionParser
 
6243
 
+        parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
 
6244
 
+# ddaa suggests removing this to promote GUIDs.  Let's see who squalks.
 
6245
 
+#        parser.add_option("-i", "--id", dest="id", 
 
6246
 
+#                         help="Specify id for a single file", default=None)
 
6247
 
+        parser.add_option("--tltl", action="store_true", 
 
6248
 
+                         dest="lord_style",  help="Use Tom Lord's style of id.")
 
6249
 
+        parser.add_option("--explicit", action="store_const", 
 
6250
 
+                         const="explicit", dest="id_type", 
 
6251
 
+                         help="Use an explicit id", default="auto")
 
6252
 
+        parser.add_option("--tagline", action="store_const", 
 
6253
 
+                         const="tagline", dest="id_type", 
 
6254
 
+                         help="Use a tagline id")
 
6255
 
+        parser.add_option("--untagged", action="store_true", 
 
6256
 
+                         dest="untagged", default=False, 
 
6257
 
+                         help="tag all untagged files")
 
6260
 
+    def help(self, parser=None):
 
6262
 
+        Prints a help message.
 
6264
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
6265
 
+        not supplied, it is retrieved.
 
6266
 
+        :type parser: cmdutil.CmdOptionParser
 
6269
 
+            parser=self.get_parser()
 
6270
 
+        parser.print_help()
 
6272
 
+Adds an inventory to the specified file(s) and directories.  If --untagged is
 
6273
 
+specified, adds inventory to all untagged files and directories.
 
6278
 
+class Merge(BaseCommand):
 
6280
 
+    Merges changes from other versions into the current tree
 
6282
 
+    def __init__(self):
 
6283
 
+        self.description="Merges changes from other versions"
 
6285
 
+            self.tree = arch.tree_root()
 
6290
 
+    def get_completer(self, arg, index):
 
6291
 
+        if self.tree is None:
 
6292
 
+            raise arch.errors.TreeRootError
 
6293
 
+        completions = list(ancillary.iter_partners(self.tree, 
 
6294
 
+                                                   self.tree.tree_version))
 
6295
 
+        if len(completions) == 0:
 
6296
 
+            completions = list(self.tree.iter_log_versions())
 
6300
 
+            for completion in completions:
 
6301
 
+                alias = ancillary.compact_alias(str(completion), self.tree)
 
6303
 
+                    aliases.extend(alias)
 
6305
 
+            for completion in completions:
 
6306
 
+                if completion.archive == self.tree.tree_version.archive:
 
6307
 
+                    aliases.append(completion.nonarch)
 
6309
 
+        except Exception, e:
 
6312
 
+        completions.extend(aliases)
 
6313
 
+        return completions
 
6315
 
+    def do_command(self, cmdargs):
 
6317
 
+        Master function that perfoms the "merge" command.
 
6319
 
+        parser=self.get_parser()
 
6320
 
+        (options, args) = parser.parse_args(cmdargs)
 
6322
 
+            action="star-merge"
 
6324
 
+            action = options.action
 
6326
 
+        if self.tree is None:
 
6327
 
+            raise arch.errors.TreeRootError(os.getcwd())
 
6328
 
+        if cmdutil.has_changed(self.tree.tree_version):
 
6329
 
+            raise UncommittedChanges(self.tree)
 
6334
 
+                revisions.append(cmdutil.determine_revision_arch(self.tree, 
 
6336
 
+            source = "from commandline"
 
6338
 
+            revisions = ancillary.iter_partner_revisions(self.tree, 
 
6339
 
+                                                         self.tree.tree_version)
 
6340
 
+            source = "from partner version"
 
6341
 
+        revisions = misc.rewind_iterator(revisions)
 
6344
 
+            revisions.rewind()
 
6345
 
+        except StopIteration, e:
 
6346
 
+            revision = cmdutil.tag_cur(self.tree)
 
6347
 
+            if revision is None:
 
6348
 
+                raise CantDetermineRevision("", "No version specified, no "
 
6349
 
+                                            "partner-versions, and no tag"
 
6351
 
+            revisions = [revision]
 
6352
 
+            source = "from tag source"
 
6353
 
+        for revision in revisions:
 
6354
 
+            cmdutil.ensure_archive_registered(revision.archive)
 
6355
 
+            cmdutil.colorize(arch.Chatter("* Merging %s [%s]" % 
 
6356
 
+                             (revision, source)))
 
6357
 
+            if action=="native-merge" or action=="update":
 
6358
 
+                if self.native_merge(revision, action) == 0:
 
6360
 
+            elif action=="star-merge":
 
6362
 
+                    self.star_merge(revision, options.diff3)
 
6363
 
+                except errors.MergeProblem, e:
 
6365
 
+            if cmdutil.has_changed(self.tree.tree_version):
 
6368
 
+    def star_merge(self, revision, diff3):
 
6369
 
+        """Perform a star-merge on the current tree.
 
6371
 
+        :param revision: The revision to use for the merge
 
6372
 
+        :type revision: `arch.Revision`
 
6373
 
+        :param diff3: If true, do a diff3 merge
 
6377
 
+            for line in self.tree.iter_star_merge(revision, diff3=diff3):
 
6378
 
+                cmdutil.colorize(line)
 
6379
 
+        except arch.util.ExecProblem, e:
 
6380
 
+            if e.proc.status is not None and e.proc.status == 1:
 
6382
 
+                    print e.proc.error
 
6383
 
+                raise MergeProblem
 
6387
 
+    def native_merge(self, other_revision, action):
 
6388
 
+        """Perform a native-merge on the current tree.
 
6390
 
+        :param other_revision: The revision to use for the merge
 
6391
 
+        :type other_revision: `arch.Revision`
 
6392
 
+        :return: 0 if the merge was skipped, 1 if it was applied
 
6394
 
+        other_tree = cmdutil.find_or_make_local_revision(other_revision)
 
6396
 
+            if action == "native-merge":
 
6397
 
+                ancestor = cmdutil.merge_ancestor2(self.tree, other_tree, 
 
6399
 
+            elif action == "update":
 
6400
 
+                ancestor = cmdutil.tree_latest(self.tree, 
 
6401
 
+                                               other_revision.version)
 
6402
 
+        except CantDetermineRevision, e:
 
6403
 
+            raise CommandFailedWrapper(e)
 
6404
 
+        cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
 
6405
 
+        if (ancestor == other_revision):
 
6406
 
+            cmdutil.colorize(arch.Chatter("* Skipping redundant merge" 
 
6409
 
+        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
 
6410
 
+        for line in cmdutil.iter_apply_delta_filter(delta):
 
6411
 
+            cmdutil.colorize(line)
 
6416
 
+    def get_parser(self):
 
6418
 
+        Returns the options parser to use for the "merge" command.
 
6420
 
+        :rtype: cmdutil.CmdOptionParser
 
6422
 
+        parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
 
6423
 
+        parser.add_option("-s", "--star-merge", action="store_const",
 
6424
 
+                          dest="action", help="Use star-merge",
 
6425
 
+                          const="star-merge", default="native-merge")
 
6426
 
+        parser.add_option("--update", action="store_const",
 
6427
 
+                          dest="action", help="Use update picker",
 
6429
 
+        parser.add_option("--diff3", action="store_true", 
 
6431
 
+                         help="Use diff3 for merge (implies star-merge)")
 
6434
 
+    def help(self, parser=None):
 
6436
 
+        Prints a help message.
 
6438
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
6439
 
+        not supplied, it is retrieved.
 
6440
 
+        :type parser: cmdutil.CmdOptionParser
 
6443
 
+            parser=self.get_parser()
 
6444
 
+        parser.print_help()
 
6446
 
+Performs a merge operation using the specified version.
 
6450
 
+class ELog(BaseCommand):
 
6452
 
+    Produces a raw patchlog and invokes the user's editor
 
6454
 
+    def __init__(self):
 
6455
 
+        self.description="Edit a patchlog to commit"
 
6457
 
+            self.tree = arch.tree_root()
 
6462
 
+    def do_command(self, cmdargs):
 
6464
 
+        Master function that perfoms the "elog" command.
 
6466
 
+        parser=self.get_parser()
 
6467
 
+        (options, args) = parser.parse_args(cmdargs)
 
6468
 
+        if self.tree is None:
 
6469
 
+            raise arch.errors.TreeRootError
 
6471
 
+        edit_log(self.tree)
 
6473
 
+    def get_parser(self):
 
6475
 
+        Returns the options parser to use for the "merge" command.
 
6477
 
+        :rtype: cmdutil.CmdOptionParser
 
6479
 
+        parser=cmdutil.CmdOptionParser("fai elog")
 
6483
 
+    def help(self, parser=None):
 
6485
 
+        Invokes $EDITOR to produce a log for committing.
 
6487
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
6488
 
+        not supplied, it is retrieved.
 
6489
 
+        :type parser: cmdutil.CmdOptionParser
 
6492
 
+            parser=self.get_parser()
 
6493
 
+        parser.print_help()
 
6495
 
+Invokes $EDITOR to produce a log for committing.
 
6499
 
+def edit_log(tree):
 
6500
 
+    """Makes and edits the log for a tree.  Does all kinds of fancy things
 
6501
 
+    like log templates and merge summaries and log-for-merge
 
6503
 
+    :param tree: The tree to edit the log for
 
6504
 
+    :type tree: `arch.WorkingTree`
 
6506
 
+    #ensure we have an editor before preparing the log
 
6507
 
+    cmdutil.find_editor()
 
6508
 
+    log = tree.log_message(create=False)
 
6509
 
+    log_is_new = False
 
6510
 
+    if log is None or cmdutil.prompt("Overwrite log"):
 
6511
 
+        if log is not None:
 
6512
 
+           os.remove(log.name)
 
6513
 
+        log = tree.log_message(create=True)
 
6516
 
+        template = tree+"/{arch}/=log-template"
 
6517
 
+        if not os.path.exists(template):
 
6518
 
+            template = os.path.expanduser("~/.arch-params/=log-template")
 
6519
 
+            if not os.path.exists(template):
 
6522
 
+            shutil.copyfile(template, tmplog)
 
6524
 
+        new_merges = list(cmdutil.iter_new_merges(tree, 
 
6525
 
+                                                  tree.tree_version))
 
6526
 
+        log["Summary"] = merge_summary(new_merges, tree.tree_version)
 
6527
 
+        if len(new_merges) > 0:   
 
6528
 
+            if cmdutil.prompt("Log for merge"):
 
6529
 
+                mergestuff = cmdutil.log_for_merge(tree)
 
6530
 
+                log.description += mergestuff
 
6533
 
+        cmdutil.invoke_editor(log.name)
 
6536
 
+            os.remove(log.name)
 
6539
 
+def merge_summary(new_merges, tree_version):
 
6540
 
+    if len(new_merges) == 0:
 
6542
 
+    if len(new_merges) == 1:
 
6543
 
+        summary = new_merges[0].summary
 
6548
 
+    for merge in new_merges:
 
6549
 
+        if arch.my_id() != merge.creator:
 
6550
 
+            name = re.sub("<.*>", "", merge.creator).rstrip(" ");
 
6551
 
+            if not name in credits:
 
6552
 
+                credits.append(name)
 
6554
 
+            version = merge.revision.version
 
6555
 
+            if version.archive == tree_version.archive:
 
6556
 
+                if not version.nonarch in credits:
 
6557
 
+                    credits.append(version.nonarch)
 
6558
 
+            elif not str(version) in credits:
 
6559
 
+                credits.append(str(version))
 
6561
 
+    return ("%s (%s)") % (summary, ", ".join(credits))
 
6563
 
+class MirrorArchive(BaseCommand):
 
6565
 
+    Updates a mirror from an archive
 
6567
 
+    def __init__(self):
 
6568
 
+        self.description="Update a mirror from an archive"
 
6570
 
+    def do_command(self, cmdargs):
 
6572
 
+        Master function that perfoms the "revision" command.
 
6575
 
+        parser=self.get_parser()
 
6576
 
+        (options, args) = parser.parse_args(cmdargs)
 
6580
 
+            tree = arch.tree_root()
 
6584
 
+        if len(args) == 0:
 
6585
 
+            if tree is not None:
 
6586
 
+                name = tree.tree_version()
 
6588
 
+            name = cmdutil.expand_alias(args[0], tree)
 
6589
 
+            name = arch.NameParser(name)
 
6591
 
+        to_arch = name.get_archive()
 
6592
 
+        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
 
6593
 
+        limit = name.get_nonarch()
 
6595
 
+        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
 
6596
 
+        for line in arch.chatter_classifier(iter):
 
6597
 
+            cmdutil.colorize(line)
 
6599
 
+    def get_parser(self):
 
6601
 
+        Returns the options parser to use for the "revision" command.
 
6603
 
+        :rtype: cmdutil.CmdOptionParser
 
6605
 
+        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
 
6608
 
+    def help(self, parser=None):
 
6610
 
+        Prints a help message.
 
6612
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
6613
 
+        not supplied, it is retrieved.
 
6614
 
+        :type parser: cmdutil.CmdOptionParser
 
6617
 
+            parser=self.get_parser()
 
6618
 
+        parser.print_help()
 
6620
 
+Updates a mirror from an archive.  If a branch, package, or version is
 
6621
 
+supplied, only changes under it are mirrored.
 
6625
 
+def help_tree_spec():
 
6626
 
+    print """Specifying revisions (default: tree)
 
6627
 
+Revisions may be specified by alias, revision, version or patchlevel.
 
6628
 
+Revisions or versions may be fully qualified.  Unqualified revisions, versions, 
 
6629
 
+or patchlevels use the archive of the current project tree.  Versions will
 
6630
 
+use the latest patchlevel in the tree.  Patchlevels will use the current tree-
 
6633
 
+Use "alias" to list available (user and automatic) aliases."""
 
6635
 
+def help_aliases(tree):
 
6636
 
+    print """Auto-generated aliases
 
6637
 
+ acur : The latest revision in the archive of the tree-version.  You can specfy
 
6638
 
+        a different version like so: acur:foo--bar--0 (aliases can be used)
 
6639
 
+ tcur : (tree current) The latest revision in the tree of the tree-version.
 
6640
 
+        You can specify a different version like so: tcur:foo--bar--0 (aliases
 
6642
 
+tprev : (tree previous) The previous revision in the tree of the tree-version.
 
6643
 
+        To specify an older revision, use a number, e.g. "tprev:4"
 
6644
 
+ tanc : (tree ancestor) The ancestor revision of the tree
 
6645
 
+        To specify an older revision, use a number, e.g. "tanc:4"
 
6646
 
+tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
 
6647
 
+ tmod : (tree modified) The latest revision to modify a given file 
 
6648
 
+        (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
 
6649
 
+ ttag : (tree tag) The revision that was tagged into the current tree revision,
 
6650
 
+        according to the tree.
 
6651
 
+tagcur: (tag current) The latest revision of the version that the current tree
 
6653
 
+mergeanc : The common ancestor of the current tree and the specified revision.
 
6654
 
+        Defaults to the first partner-version's latest revision or to tagcur.
 
6656
 
+    print "User aliases"
 
6657
 
+    for parts in ancillary.iter_all_alias(tree):
 
6658
 
+        print parts[0].rjust(10)+" : "+parts[1]
 
6661
 
+class Inventory(BaseCommand):
 
6662
 
+    """List the status of files in the tree"""
 
6663
 
+    def __init__(self):
 
6664
 
+        self.description=self.__doc__
 
6666
 
+    def do_command(self, cmdargs):
 
6668
 
+        Master function that perfoms the "revision" command.
 
6671
 
+        parser=self.get_parser()
 
6672
 
+        (options, args) = parser.parse_args(cmdargs)
 
6673
 
+        tree = arch.tree_root()
 
6676
 
+        if (options.source):
 
6677
 
+            categories.append(arch_core.SourceFile)
 
6678
 
+        if (options.precious):
 
6679
 
+            categories.append(arch_core.PreciousFile)
 
6680
 
+        if (options.backup):
 
6681
 
+            categories.append(arch_core.BackupFile)
 
6682
 
+        if (options.junk):
 
6683
 
+            categories.append(arch_core.JunkFile)
 
6685
 
+        if len(categories) == 1:
 
6686
 
+            show_leading = False
 
6688
 
+            show_leading = True
 
6690
 
+        if len(categories) == 0:
 
6693
 
+        if options.untagged:
 
6694
 
+            categories = arch_core.non_root
 
6695
 
+            show_leading = False
 
6700
 
+        for file in arch_core.iter_inventory_filter(tree, None, 
 
6701
 
+            control_files=options.control_files, 
 
6702
 
+            categories = categories, tagged=tagged):
 
6703
 
+            print arch_core.file_line(file, 
 
6704
 
+                                      category = show_leading, 
 
6705
 
+                                      untagged = show_leading,
 
6708
 
+    def get_parser(self):
 
6710
 
+        Returns the options parser to use for the "revision" command.
 
6712
 
+        :rtype: cmdutil.CmdOptionParser
 
6714
 
+        parser=cmdutil.CmdOptionParser("fai inventory [options]")
 
6715
 
+        parser.add_option("--ids", action="store_true", dest="ids", 
 
6716
 
+                          help="Show file ids")
 
6717
 
+        parser.add_option("--control", action="store_true", 
 
6718
 
+                          dest="control_files", help="include control files")
 
6719
 
+        parser.add_option("--source", action="store_true", dest="source",
 
6720
 
+                          help="List source files")
 
6721
 
+        parser.add_option("--backup", action="store_true", dest="backup",
 
6722
 
+                          help="List backup files")
 
6723
 
+        parser.add_option("--precious", action="store_true", dest="precious",
 
6724
 
+                          help="List precious files")
 
6725
 
+        parser.add_option("--junk", action="store_true", dest="junk",
 
6726
 
+                          help="List junk files")
 
6727
 
+        parser.add_option("--unrecognized", action="store_true", 
 
6728
 
+                          dest="unrecognized", help="List unrecognized files")
 
6729
 
+        parser.add_option("--untagged", action="store_true", 
 
6730
 
+                          dest="untagged", help="List only untagged files")
 
6733
 
+    def help(self, parser=None):
 
6735
 
+        Prints a help message.
 
6737
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
6738
 
+        not supplied, it is retrieved.
 
6739
 
+        :type parser: cmdutil.CmdOptionParser
 
6742
 
+            parser=self.get_parser()
 
6743
 
+        parser.print_help()
 
6745
 
+Lists the status of files in the archive:
 
6753
 
+Leading letter are not displayed if only one kind of file is shown
 
6758
 
+class Alias(BaseCommand):
 
6759
 
+    """List or adjust aliases"""
 
6760
 
+    def __init__(self):
 
6761
 
+        self.description=self.__doc__
 
6763
 
+    def get_completer(self, arg, index):
 
6767
 
+            self.tree = arch.tree_root()
 
6772
 
+            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
 
6774
 
+            return cmdutil.iter_revision_completions(arg, self.tree)
 
6777
 
+    def do_command(self, cmdargs):
 
6779
 
+        Master function that perfoms the "revision" command.
 
6782
 
+        parser=self.get_parser()
 
6783
 
+        (options, args) = parser.parse_args(cmdargs)
 
6785
 
+            self.tree =  arch.tree_root()
 
6791
 
+            options.action(args, options)
 
6792
 
+        except cmdutil.ForbiddenAliasSyntax, e:
 
6793
 
+            raise CommandFailedWrapper(e)
 
6795
 
+    def arg_dispatch(self, args, options):
 
6796
 
+        """Add, modify, or list aliases, depending on number of arguments
 
6798
 
+        :param args: The list of commandline arguments
 
6799
 
+        :type args: list of str
 
6800
 
+        :param options: The commandline options
 
6802
 
+        if len(args) == 0:
 
6803
 
+            help_aliases(self.tree)
 
6805
 
+        elif len(args) == 1:
 
6806
 
+            self.print_alias(args[0])
 
6807
 
+        elif (len(args)) == 2:
 
6808
 
+            self.add(args[0], args[1], options)
 
6810
 
+            raise cmdutil.GetHelp
 
6812
 
+    def print_alias(self, alias):
 
6814
 
+        for pair in ancillary.iter_all_alias(self.tree):
 
6815
 
+            if pair[0] == alias:
 
6817
 
+        if answer is not None:
 
6820
 
+            print "The alias %s is not assigned." % alias
 
6822
 
+    def add(self, alias, expansion, options):
 
6823
 
+        """Add or modify aliases
 
6825
 
+        :param alias: The alias name to create/modify
 
6827
 
+        :param expansion: The expansion to assign to the alias name
 
6828
 
+        :type expansion: str
 
6829
 
+        :param options: The commandline options
 
6833
 
+        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
 
6835
 
+        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
 
6837
 
+        for pair in self.get_iterator(options):
 
6838
 
+            if pair[0] != alias:
 
6839
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
 
6845
 
+        self.write_aliases(newlist, options)
 
6847
 
+    def delete(self, args, options):
 
6848
 
+        """Delete the specified alias
 
6850
 
+        :param args: The list of arguments
 
6851
 
+        :type args: list of str
 
6852
 
+        :param options: The commandline options
 
6855
 
+        if len(args) != 1:
 
6856
 
+            raise cmdutil.GetHelp
 
6858
 
+        for pair in self.get_iterator(options):
 
6859
 
+            if pair[0] != args[0]:
 
6860
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
 
6864
 
+            raise errors.NoSuchAlias(args[0])
 
6865
 
+        self.write_aliases(newlist, options)
 
6867
 
+    def get_alias_file(self, options):
 
6868
 
+        """Return the name of the alias file to use
 
6870
 
+        :param options: The commandline options
 
6873
 
+            if self.tree is None:
 
6874
 
+                self.tree == arch.tree_root()
 
6875
 
+            return str(self.tree)+"/{arch}/+aliases"
 
6877
 
+            return "~/.aba/aliases"
 
6879
 
+    def get_iterator(self, options):
 
6880
 
+        """Return the alias iterator to use
 
6882
 
+        :param options: The commandline options
 
6884
 
+        return ancillary.iter_alias(self.get_alias_file(options))
 
6886
 
+    def write_aliases(self, newlist, options):
 
6887
 
+        """Safely rewrite the alias file
 
6888
 
+        :param newlist: The new list of aliases
 
6889
 
+        :type newlist: str
 
6890
 
+        :param options: The commandline options
 
6892
 
+        filename = os.path.expanduser(self.get_alias_file(options))
 
6893
 
+        file = cmdutil.NewFileVersion(filename)
 
6894
 
+        file.write(newlist)
 
6898
 
+    def get_parser(self):
 
6900
 
+        Returns the options parser to use for the "alias" command.
 
6902
 
+        :rtype: cmdutil.CmdOptionParser
 
6904
 
+        parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
 
6905
 
+        parser.add_option("-d", "--delete", action="store_const", dest="action",
 
6906
 
+                          const=self.delete, default=self.arg_dispatch, 
 
6907
 
+                          help="Delete an alias")
 
6908
 
+        parser.add_option("--tree", action="store_true", dest="tree", 
 
6909
 
+                          help="Create a per-tree alias", default=False)
 
6912
 
+    def help(self, parser=None):
 
6914
 
+        Prints a help message.
 
6916
 
+        :param parser: If supplied, the parser to use for generating help.  If \
 
6917
 
+        not supplied, it is retrieved.
 
6918
 
+        :type parser: cmdutil.CmdOptionParser
 
6921
 
+            parser=self.get_parser()
 
6922
 
+        parser.print_help()
 
6924
 
+Lists current aliases or modifies the list of aliases.
 
6926
 
+If no arguments are supplied, aliases will be listed.  If two arguments are
 
6927
 
+supplied, the specified alias will be created or modified.  If -d or --delete
 
6928
 
+is supplied, the specified alias will be deleted.
 
6930
 
+You can create aliases that refer to any fully-qualified part of the
 
6931
 
+Arch namespace, e.g. 
 
6934
 
+archive/category--branch, 
 
6935
 
+archive/category--branch--version (my favourite)
 
6936
 
+archive/category--branch--version--patchlevel
 
6938
 
+Aliases can be used automatically by native commands.  To use them
 
6939
 
+with external or tla commands, prefix them with ^ (you can do this
 
6940
 
+with native commands, too).
 
6944
 
+class RequestMerge(BaseCommand):
 
6945
 
+    """Submit a merge request to Bug Goo"""
 
6946
 
+    def __init__(self):
 
6947
 
+        self.description=self.__doc__
 
6949
 
+    def do_command(self, cmdargs):
 
6950
 
+        """Submit a merge request
 
6952
 
+        :param cmdargs: The commandline arguments
 
6953
 
+        :type cmdargs: list of str
 
6955
 
+        cmdutil.find_editor()
 
6956
 
+        parser = self.get_parser()
 
6957
 
+        (options, args) = parser.parse_args(cmdargs)
 
6959
 
+            self.tree=arch.tree_root()
 
6962
 
+        base, revisions = self.revision_specs(args)
 
6963
 
+        message = self.make_headers(base, revisions)
 
6964
 
+        message += self.make_summary(revisions)
 
6965
 
+        path = self.edit_message(message)
 
6966
 
+        message = self.tidy_message(path)
 
6967
 
+        if cmdutil.prompt("Send merge"):
 
6968
 
+            self.send_message(message)
 
6969
 
+            print "Merge request sent"
 
6971
 
+    def make_headers(self, base, revisions):
 
6972
 
+        """Produce email and Bug Goo header strings
 
6974
 
+        :param base: The base revision to apply merges to
 
6975
 
+        :type base: `arch.Revision`
 
6976
 
+        :param revisions: The revisions to replay into the base
 
6977
 
+        :type revisions: list of `arch.Patchlog`
 
6978
 
+        :return: The headers
 
6981
 
+        headers = "To: gnu-arch-users@gnu.org\n"
 
6982
 
+        headers += "From: %s\n" % options.fromaddr
 
6983
 
+        if len(revisions) == 1:
 
6984
 
+            headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
 
6986
 
+            headers += "Subject: [MERGE REQUEST]\n"
 
6988
 
+        headers += "Base-Revision: %s\n" % base
 
6989
 
+        for revision in revisions:
 
6990
 
+            headers += "Revision: %s\n" % revision.revision
 
6991
 
+        headers += "Bug: \n\n"
 
6994
 
+    def make_summary(self, logs):
 
6995
 
+        """Generate a summary of merges
 
6997
 
+        :param logs: the patchlogs that were directly added by the merges
 
6998
 
+        :type logs: list of `arch.Patchlog`
 
6999
 
+        :return: the summary
 
7004
 
+            summary+=str(log.revision)+"\n"
 
7005
 
+            summary+=log.summary+"\n"
 
7006
 
+            if log.description.strip():
 
7007
 
+                summary+=log.description.strip('\n')+"\n\n"
 
7010
 
+    def revision_specs(self, args):
 
7011
 
+        """Determine the base and merge revisions from tree and arguments.
 
7013
 
+        :param args: The parsed arguments
 
7014
 
+        :type args: list of str
 
7015
 
+        :return: The base revision and merge revisions 
 
7016
 
+        :rtype: `arch.Revision`, list of `arch.Patchlog`
 
7019
 
+            target_revision = cmdutil.determine_revision_arch(self.tree, 
 
7022
 
+            target_revision = cmdutil.tree_latest(self.tree)
 
7024
 
+            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
 
7025
 
+                       self.tree, f)) for f in args[1:] ]
 
7027
 
+            if self.tree is None:
 
7028
 
+                raise CantDetermineRevision("", "Not in a project tree")
 
7029
 
+            merge_iter = cmdutil.iter_new_merges(self.tree, 
 
7030
 
+                                                 target_revision.version, 
 
7032
 
+            merges = [f for f in cmdutil.direct_merges(merge_iter)]
 
7033
 
+        return (target_revision, merges)
 
7035
 
+    def edit_message(self, message):
 
7036
 
+        """Edit an email message in the user's standard editor
 
7038
 
+        :param message: The message to edit
 
7039
 
+        :type message: str
 
7040
 
+        :return: the path of the edited message
 
7043
 
+        if self.tree is None:
 
7044
 
+            path = os.get_cwd()
 
7047
 
+        path += "/,merge-request"
 
7048
 
+        file = open(path, 'w')
 
7049
 
+        file.write(message)
 
7051
 
+        cmdutil.invoke_editor(path)
 
7054
 
+    def tidy_message(self, path):
 
7055
 
+        """Validate and clean up message.
 
7057
 
+        :param path: The path to the message to clean up
 
7059
 
+        :return: The parsed message
 
7060
 
+        :rtype: `email.Message`
 
7062
 
+        mail = email.message_from_file(open(path))
 
7063
 
+        if mail["Subject"].strip() == "[MERGE REQUEST]":
 
7064
 
+            raise BlandSubject
 
7066
 
+        request = email.message_from_string(mail.get_payload())
 
7067
 
+        if request.has_key("Bug"):
 
7068
 
+            if request["Bug"].strip()=="":
 
7069
 
+                del request["Bug"]
 
7070
 
+        mail.set_payload(request.as_string())
 
7073
 
+    def send_message(self, message):
 
7074
 
+        """Send a message, using its headers to address it.
 
7076
 
+        :param message: The message to send
 
7077
 
+        :type message: `email.Message`"""
 
7078
 
+        server = smtplib.SMTP()
 
7079
 
+        server.sendmail(message['From'], message['To'], message.as_string())
 
7082
 
+    def help(self, parser=None):
 
7083
 
+        """Print a usage message
 
7085
 
+        :param parser: The options parser to use
 
7086
 
+        :type parser: `cmdutil.CmdOptionParser`
 
7088
 
+        if parser is None:
 
7089
 
+            parser = self.get_parser()
 
7090
 
+        parser.print_help()
 
7092
 
+Sends a merge request formatted for Bug Goo.  Intended use: get the tree
 
7093
 
+you'd like to merge into.  Apply the merges you want.  Invoke request-merge.
 
7094
 
+The merge request will open in your $EDITOR.
 
7096
 
+When no TARGET is specified, it uses the current tree revision.  When
 
7097
 
+no MERGE is specified, it uses the direct merges (as in "revisions
 
7098
 
+--direct-merges").  But you can specify just the TARGET, or all the MERGE
 
7102
 
+    def get_parser(self):
 
7103
 
+        """Produce a commandline parser for this command.
 
7105
 
+        :rtype: `cmdutil.CmdOptionParser`
 
7107
 
+        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
 
7111
 
+'changes' : Changes,
 
7114
 
+'apply-changes':ApplyChanges,
 
7117
 
+'revision': Revision,
 
7118
 
+'revisions': Revisions,
 
7125
 
+'mirror-archive': MirrorArchive,
 
7126
 
+'ninventory': Inventory,
 
7128
 
+'request-merge': RequestMerge,
 
7131
 
+'apply-delta' : "Try \"apply-changes\".",
 
7132
 
+'delta' : "To compare two revisions, use \"changes\".",
 
7133
 
+'diff-rev' : "To compare two revisions, use \"changes\".",
 
7134
 
+'undo' : "To undo local changes, use \"revert\".",
 
7135
 
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
 
7136
 
+'missing-from' : "Try \"revisions --missing-from\".",
 
7137
 
+'missing' : "Try \"revisions --missing\".",
 
7138
 
+'missing-merge' : "Try \"revisions --partner-missing\".",
 
7139
 
+'new-merges' : "Try \"revisions --new-merges\".",
 
7140
 
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
 
7141
 
+'logs' : "Try \"revisions --logs\"",
 
7142
 
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
 
7143
 
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
 
7144
 
+'change-version' : "Try \"update REVISION\"",
 
7145
 
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
 
7146
 
+'rev-depends' : "Use revisions --dependencies",
 
7147
 
+'auto-get' : "Plain get will do archive lookups",
 
7148
 
+'tagline' : "Use add-id.  It uses taglines in tagline trees",
 
7149
 
+'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
 
7150
 
+'library-revisions' : "Use revisions --library",
 
7151
 
+'file-revert' : "Use revert FILE"
 
7153
 
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
 
7155
 
*** modified file 'bzrlib/branch.py'
 
7156
 
--- bzrlib/branch.py 
 
7157
 
+++ bzrlib/branch.py 
 
7159
 
 from revision import Revision
 
7160
 
 from errors import bailout, BzrError
 
7161
 
 from textui import show_status
 
7163
 
+from bzrlib import progress
 
7165
 
 BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
 
7166
 
 ## TODO: Maybe include checks for common corruption of newlines, etc?
 
7167
 
@@ -802,3 +804,36 @@
 
7169
 
     s = hexlify(rand_bytes(8))
 
7170
 
     return '-'.join((name, compact_date(time.time()), s))
 
7173
 
+def iter_anno_data(branch, file_id):
 
7174
 
+    later_revision = branch.revno()
 
7175
 
+    q = range(branch.revno())
 
7177
 
+    later_text_id = branch.basis_tree().inventory[file_id].text_id
 
7181
 
+        cur_tree = branch.revision_tree(branch.lookup_revision(revno))
 
7182
 
+        if file_id not in cur_tree.inventory:
 
7185
 
+            text_id = cur_tree.inventory[file_id].text_id
 
7186
 
+        if text_id != later_text_id:
 
7187
 
+            patch = get_patch(branch, revno, later_revision, file_id)
 
7188
 
+            yield revno, patch.iter_inserted(), patch
 
7189
 
+            later_revision = revno
 
7190
 
+            later_text_id = text_id
 
7191
 
+        yield progress.Progress("revisions", i)
 
7193
 
+def get_patch(branch, old_revno, new_revno, file_id):
 
7194
 
+    old_tree = branch.revision_tree(branch.lookup_revision(old_revno))
 
7195
 
+    new_tree = branch.revision_tree(branch.lookup_revision(new_revno))
 
7196
 
+    if file_id in old_tree.inventory:
 
7197
 
+        old_file = old_tree.get_file(file_id).readlines()
 
7200
 
+    ud = difflib.unified_diff(old_file, new_tree.get_file(file_id).readlines())
 
7201
 
+    return patches.parse_patch(ud)
 
7205
 
*** modified file 'bzrlib/commands.py'
 
7206
 
--- bzrlib/commands.py 
 
7207
 
+++ bzrlib/commands.py 
 
7209
 
 from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
7211
 
 from bzrlib import merge
 
7212
 
+from bzrlib.branch import iter_anno_data
 
7213
 
+from bzrlib import patches
 
7214
 
+from bzrlib import progress
 
7217
 
 def _squish_command_name(cmd):
 
7218
 
@@ -882,7 +885,15 @@
 
7219
 
                 print '%3d FAILED!' % mf
 
7223
 
+        result = bzrlib.patches.test()
 
7224
 
+        resultFailed = len(result.errors) + len(result.failures)
 
7225
 
+        print '%-40s %3d tests' % ('bzrlib.patches', result.testsRun),
 
7227
 
+            print '%3d FAILED!' % resultFailed
 
7230
 
+        tests += result.testsRun
 
7231
 
+        failures += resultFailed
 
7232
 
         print '%-40s %3d tests' % ('total', tests),
 
7234
 
             print '%3d FAILED!' % failures
 
7235
 
@@ -897,6 +908,27 @@
 
7236
 
     """Show version of bzr"""
 
7240
 
+class cmd_annotate(Command):
 
7241
 
+    """Show which revision added each line in a file"""
 
7243
 
+    takes_args = ['filename']
 
7244
 
+    def run(self, filename):
 
7245
 
+        branch = (Branch(filename))
 
7246
 
+        file_id = branch.working_tree().path2id(filename)
 
7247
 
+        lines = branch.basis_tree().get_file(file_id)
 
7248
 
+        total = branch.revno()
 
7249
 
+        anno_d_iter = iter_anno_data(branch, file_id)
 
7250
 
+        for result in patches.iter_annotate_file(lines, anno_d_iter):
 
7251
 
+            if isinstance(result, progress.Progress):
 
7252
 
+                result.total = total
 
7253
 
+                progress.progress_bar(result)
 
7255
 
+                progress.clear_progress_bar()
 
7256
 
+                anno_lines = result
 
7257
 
+        for line in anno_lines:
 
7258
 
+            sys.stdout.write("%4s:%s" % (str(line.log), line.text))
 
7262
 
     print "bzr (bazaar-ng) %s" % bzrlib.__version__