/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to patches/annotate4.patch

  • Committer: John Arbash Meinel
  • Date: 2010-01-12 22:51:31 UTC
  • mto: This revision was merged to the branch mainline in revision 4955.
  • Revision ID: john@arbash-meinel.com-20100112225131-he8h411p6aeeb947
Delay grabbing an output stream until we actually go to show a diff.

This makes the test suite happy, but it also seems to be reasonable.
If we aren't going to write anything, we don't need to hold an
output stream open.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
*** added file 'bzrlib/patches.py'
2
 
--- /dev/null 
3
 
+++ bzrlib/patches.py 
4
 
@@ -0,0 +1,497 @@
5
 
+# Copyright (C) 2004, 2005 Aaron Bentley
6
 
+# <aaron.bentley@utoronto.ca>
7
 
+#
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.
12
 
+#
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.
17
 
+#
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
21
 
+import sys
22
 
+import progress
23
 
+class PatchSyntax(Exception):
24
 
+    def __init__(self, msg):
25
 
+        Exception.__init__(self, msg)
26
 
+
27
 
+
28
 
+class MalformedPatchHeader(PatchSyntax):
29
 
+    def __init__(self, desc, line):
30
 
+        self.desc = desc
31
 
+        self.line = line
32
 
+        msg = "Malformed patch header.  %s\n%s" % (self.desc, self.line)
33
 
+        PatchSyntax.__init__(self, msg)
34
 
+
35
 
+class MalformedHunkHeader(PatchSyntax):
36
 
+    def __init__(self, desc, line):
37
 
+        self.desc = desc
38
 
+        self.line = line
39
 
+        msg = "Malformed hunk header.  %s\n%s" % (self.desc, self.line)
40
 
+        PatchSyntax.__init__(self, msg)
41
 
+
42
 
+class MalformedLine(PatchSyntax):
43
 
+    def __init__(self, desc, line):
44
 
+        self.desc = desc
45
 
+        self.line = line
46
 
+        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
47
 
+        PatchSyntax.__init__(self, msg)
48
 
+
49
 
+def get_patch_names(iter_lines):
50
 
+    try:
51
 
+        line = iter_lines.next()
52
 
+        if not line.startswith("--- "):
53
 
+            raise MalformedPatchHeader("No orig name", line)
54
 
+        else:
55
 
+            orig_name = line[4:].rstrip("\n")
56
 
+    except StopIteration:
57
 
+        raise MalformedPatchHeader("No orig line", "")
58
 
+    try:
59
 
+        line = iter_lines.next()
60
 
+        if not line.startswith("+++ "):
61
 
+            raise PatchSyntax("No mod name")
62
 
+        else:
63
 
+            mod_name = line[4:].rstrip("\n")
64
 
+    except StopIteration:
65
 
+        raise MalformedPatchHeader("No mod line", "")
66
 
+    return (orig_name, mod_name)
67
 
+
68
 
+def parse_range(textrange):
69
 
+    """Parse a patch range, handling the "1" special-case
70
 
+
71
 
+    :param textrange: The text to parse
72
 
+    :type textrange: str
73
 
+    :return: the position and range, as a tuple
74
 
+    :rtype: (int, int)
75
 
+    """
76
 
+    tmp = textrange.split(',')
77
 
+    if len(tmp) == 1:
78
 
+        pos = tmp[0]
79
 
+        range = "1"
80
 
+    else:
81
 
+        (pos, range) = tmp
82
 
+    pos = int(pos)
83
 
+    range = int(range)
84
 
+    return (pos, range)
85
 
+
86
 
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)
91
 
+    try:
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)
97
 
+    try:
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)
105
 
+
106
 
+
107
 
+class HunkLine:
108
 
+    def __init__(self, contents):
109
 
+        self.contents = contents
110
 
+
111
 
+    def get_str(self, leadchar):
112
 
+        if self.contents == "\n" and leadchar == " " and False:
113
 
+            return "\n"
114
 
+        return leadchar + self.contents
115
 
+
116
 
+class ContextLine(HunkLine):
117
 
+    def __init__(self, contents):
118
 
+        HunkLine.__init__(self, contents)
119
 
+
120
 
+    def __str__(self):
121
 
+        return self.get_str(" ")
122
 
+
123
 
+
124
 
+class InsertLine(HunkLine):
125
 
+    def __init__(self, contents):
126
 
+        HunkLine.__init__(self, contents)
127
 
+
128
 
+    def __str__(self):
129
 
+        return self.get_str("+")
130
 
+
131
 
+
132
 
+class RemoveLine(HunkLine):
133
 
+    def __init__(self, contents):
134
 
+        HunkLine.__init__(self, contents)
135
 
+
136
 
+    def __str__(self):
137
 
+        return self.get_str("-")
138
 
+
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:])
149
 
+    else:
150
 
+        raise MalformedLine("Unknown line type", line)
151
 
+__pychecker__=""
152
 
+
153
 
+
154
 
+class Hunk:
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
160
 
+        self.lines = []
161
 
+
162
 
+    def get_header(self):
163
 
+        return "@@ -%s +%s @@\n" % (self.range_str(self.orig_pos, 
164
 
+                                                   self.orig_range),
165
 
+                                    self.range_str(self.mod_pos, 
166
 
+                                                   self.mod_range))
167
 
+
168
 
+    def range_str(self, pos, range):
169
 
+        """Return a file range, special-casing for 1-line files.
170
 
+
171
 
+        :param pos: The position in the file
172
 
+        :type pos: int
173
 
+        :range: The range in the file
174
 
+        :type range: int
175
 
+        :return: a string in the format 1,4 except when range == pos == 1
176
 
+        """
177
 
+        if range == 1:
178
 
+            return "%i" % pos
179
 
+        else:
180
 
+            return "%i,%i" % (pos, range)
181
 
+
182
 
+    def __str__(self):
183
 
+        lines = [self.get_header()]
184
 
+        for line in self.lines:
185
 
+            lines.append(str(line))
186
 
+        return "".join(lines)
187
 
+
188
 
+    def shift_to_mod(self, pos):
189
 
+        if pos < self.orig_pos-1:
190
 
+            return 0
191
 
+        elif pos > self.orig_pos+self.orig_range:
192
 
+            return self.mod_range - self.orig_range
193
 
+        else:
194
 
+            return self.shift_to_mod_lines(pos)
195
 
+
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
199
 
+        shift = 0
200
 
+        for line in self.lines:
201
 
+            if isinstance(line, InsertLine):
202
 
+                shift += 1
203
 
+            elif isinstance(line, RemoveLine):
204
 
+                if position == pos:
205
 
+                    return None
206
 
+                shift -= 1
207
 
+                position += 1
208
 
+            elif isinstance(line, ContextLine):
209
 
+                position += 1
210
 
+            if position > pos:
211
 
+                break
212
 
+        return shift
213
 
+
214
 
+def iter_hunks(iter_lines):
215
 
+    hunk = None
216
 
+    for line in iter_lines:
217
 
+        if line.startswith("@@"):
218
 
+            if hunk is not None:
219
 
+                yield hunk
220
 
+            hunk = hunk_from_header(line)
221
 
+        else:
222
 
+            hunk.lines.append(parse_line(line))
223
 
+
224
 
+    if hunk is not None:
225
 
+        yield hunk
226
 
+
227
 
+class Patch:
228
 
+    def __init__(self, oldname, newname):
229
 
+        self.oldname = oldname
230
 
+        self.newname = newname
231
 
+        self.hunks = []
232
 
+
233
 
+    def __str__(self):
234
 
+        ret =  "--- %s\n+++ %s\n" % (self.oldname, self.newname) 
235
 
+        ret += "".join([str(h) for h in self.hunks])
236
 
+        return ret
237
 
+
238
 
+    def stats_str(self):
239
 
+        """Return a string of patch statistics"""
240
 
+        removes = 0
241
 
+        inserts = 0
242
 
+        for hunk in self.hunks:
243
 
+            for line in hunk.lines:
244
 
+                if isinstance(line, InsertLine):
245
 
+                     inserts+=1;
246
 
+                elif isinstance(line, RemoveLine):
247
 
+                     removes+=1;
248
 
+        return "%i inserts, %i removes in %i hunks" % \
249
 
+            (inserts, removes, len(self.hunks))
250
 
+
251
 
+    def pos_in_mod(self, position):
252
 
+        newpos = position
253
 
+        for hunk in self.hunks:
254
 
+            shift = hunk.shift_to_mod(position)
255
 
+            if shift is None:
256
 
+                return None
257
 
+            newpos += shift
258
 
+        return newpos
259
 
+            
260
 
+    def iter_inserted(self):
261
 
+        """Iteraties through inserted lines
262
 
+        
263
 
+        :return: Pair of line number, line
264
 
+        :rtype: iterator of (int, InsertLine)
265
 
+        """
266
 
+        for hunk in self.hunks:
267
 
+            pos = hunk.mod_pos - 1;
268
 
+            for line in hunk.lines:
269
 
+                if isinstance(line, InsertLine):
270
 
+                    yield (pos, line)
271
 
+                    pos += 1
272
 
+                if isinstance(line, ContextLine):
273
 
+                    pos += 1
274
 
+
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)
280
 
+    return patch
281
 
+
282
 
+
283
 
+class AnnotateLine:
284
 
+    """A line associated with the log that produced it"""
285
 
+    def __init__(self, text, log=None):
286
 
+        self.text = text
287
 
+        self.log = log
288
 
+
289
 
+class CantGetRevisionData(Exception):
290
 
+    def __init__(self, revision):
291
 
+        Exception.__init__(self, "Can't get data for revision %s" % revision)
292
 
+        
293
 
+def annotate_file2(file_lines, anno_iter):
294
 
+    for result in iter_annotate_file(file_lines, anno_iter):
295
 
+        pass
296
 
+    return result
297
 
+
298
 
+        
299
 
+def iter_annotate_file(file_lines, anno_iter):
300
 
+    lines = [AnnotateLine(f) for f in file_lines]
301
 
+    patches = []
302
 
+    try:
303
 
+        for result in anno_iter:
304
 
+            if isinstance(result, progress.Progress):
305
 
+                yield result
306
 
+                continue
307
 
+            log, iter_inserted, patch = result
308
 
+            for (num, line) in iter_inserted:
309
 
+                old_num = num
310
 
+
311
 
+                for cur_patch in patches:
312
 
+                    num = cur_patch.pos_in_mod(num)
313
 
+                    if num == None: 
314
 
+                        break
315
 
+
316
 
+                if num >= len(lines):
317
 
+                    continue
318
 
+                if num is not None and lines[num].log is None:
319
 
+                    lines[num].log = log
320
 
+            patches=[patch]+patches
321
 
+    except CantGetRevisionData:
322
 
+        pass
323
 
+    yield lines
324
 
+
325
 
+
326
 
+def difference_index(atext, btext):
327
 
+    """Find the indext of the first character that differs betweeen two texts
328
 
+
329
 
+    :param atext: The first text
330
 
+    :type atext: str
331
 
+    :param btext: The second text
332
 
+    :type str: str
333
 
+    :return: The index, or None if there are no differences within the range
334
 
+    :rtype: int or NoneType
335
 
+    """
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]:
341
 
+            return i;
342
 
+    return None
343
 
+
344
 
+
345
 
+def test():
346
 
+    import unittest
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")
354
 
+
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,
359
 
+                              lines.__iter__())
360
 
+
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)
370
 
+
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)
380
 
+
381
 
+        def makeMalformed(self, header):
382
 
+            self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
383
 
+
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")
395
 
+
396
 
+        def lineThing(self,text, type):
397
 
+            line = parse_line(text)
398
 
+            assert(isinstance(line, type))
399
 
+            assert(str(line)==text)
400
 
+
401
 
+        def makeMalformedLine(self, text):
402
 
+            self.assertRaises(MalformedLine, parse_line, text)
403
 
+
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)
409
 
+        
410
 
+        def testMalformedLine(self):
411
 
+            """Parse invalid valid hunk lines"""
412
 
+            self.makeMalformedLine("hello\n")
413
 
+        
414
 
+        def compare_parsed(self, patchtext):
415
 
+            lines = patchtext.splitlines(True)
416
 
+            patch = parse_patch(lines.__iter__())
417
 
+            pstr = str(patch)
418
 
+            i = difference_index(patchtext, pstr)
419
 
+            if i is not None:
420
 
+                print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
421
 
+            assert (patchtext == str(patch))
422
 
+
423
 
+        def testAll(self):
424
 
+            """Test parsing a whole patch"""
425
 
+            patchtext = """--- orig/commands.py
426
 
++++ mod/commands.py
427
 
+@@ -1337,7 +1337,8 @@
428
 
429
 
+     def set_title(self, command=None):
430
 
+         try:
431
 
+-            version = self.tree.tree_version.nonarch
432
 
++            version = pylon.alias_or_version(self.tree.tree_version, self.tree,
433
 
++                                             full=False)
434
 
+         except:
435
 
+             version = "[no version]"
436
 
+         if command is None:
437
 
+@@ -1983,7 +1984,11 @@
438
 
+                                          version)
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)
445
 
++                else:
446
 
++                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
447
 
+                 log.description += mergestuff
448
 
+         log.save()
449
 
+     try:
450
 
+"""
451
 
+            self.compare_parsed(patchtext)
452
 
+
453
 
+        def testInit(self):
454
 
+            """Handle patches missing half the position, range tuple"""
455
 
+            patchtext = \
456
 
+"""--- orig/__init__.py
457
 
++++ mod/__init__.py
458
 
+@@ -1 +1,2 @@
459
 
+ __docformat__ = "restructuredtext en"
460
 
++__doc__ = An alternate Arch commandline interface"""
461
 
+            self.compare_parsed(patchtext)
462
 
+            
463
 
+
464
 
+
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"))
470
 
+            removals = []
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])
475
 
+                    continue
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,
484
 
+                                             line.contents))
485
 
+                        assert(line.contents == next)
486
 
+            self.assertRaises(StopIteration, rem_iter.next)
487
 
+
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)
492
 
+    
493
 
+            
494
 
+    patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
495
 
+    runner = unittest.TextTestRunner(verbosity=0)
496
 
+    return runner.run(patchesTestSuite)
497
 
+    
498
 
+
499
 
+if __name__ == "__main__":
500
 
+    test()
501
 
+# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683
502
 
 
503
 
*** added directory 'testdata'
504
 
*** added file 'testdata/diff'
505
 
--- /dev/null 
506
 
+++ testdata/diff 
507
 
@@ -0,0 +1,1154 @@
508
 
+--- orig/commands.py
509
 
++++ mod/commands.py
510
 
+@@ -19,25 +19,31 @@
511
 
+ import arch
512
 
+ import arch.util
513
 
+ import arch.arch
514
 
++
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 
524
 
++
525
 
+ import abacmds
526
 
+ import cmdutil
527
 
+ import shutil
528
 
+ import os
529
 
+ import options
530
 
+-import paths 
531
 
+ import time
532
 
+ import cmd
533
 
+ import readline
534
 
+ import re
535
 
+ import string
536
 
+-import arch_core
537
 
+-from errors import *
538
 
+-import errors
539
 
+ import terminal
540
 
+-import ancillary
541
 
+-import misc
542
 
+ import email
543
 
+ import smtplib
544
 
++import textwrap
545
 
546
 
+ __docformat__ = "restructuredtext"
547
 
+ __doc__ = "Implementation of user (sub) commands"
548
 
+@@ -257,7 +263,7 @@
549
 
550
 
+         tree=arch.tree_root()
551
 
+         if len(args) == 0:
552
 
+-            a_spec = cmdutil.comp_revision(tree)
553
 
++            a_spec = ancillary.comp_revision(tree)
554
 
+         else:
555
 
+             a_spec = cmdutil.determine_revision_tree(tree, args[0])
556
 
+         cmdutil.ensure_archive_registered(a_spec.archive)
557
 
+@@ -284,7 +290,7 @@
558
 
+             changeset=options.changeset
559
 
+             tmpdir = None
560
 
+         else:
561
 
+-            tmpdir=cmdutil.tmpdir()
562
 
++            tmpdir=util.tmpdir()
563
 
+             changeset=tmpdir+"/changeset"
564
 
+         try:
565
 
+             delta=arch.iter_delta(a_spec, b_spec, changeset)
566
 
+@@ -304,14 +310,14 @@
567
 
+             if status > 1:
568
 
+                 return
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()
575
 
+-                else:
576
 
+-                    b_dir = b_spec
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()
581
 
++                    else:
582
 
++                        b_dir = b_spec
583
 
++                    a_dir = a_spec.library_find()
584
 
+                     diffopts = options.diffopts.split()
585
 
+                     cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
586
 
+                 else:
587
 
+@@ -517,7 +523,7 @@
588
 
+         except arch.errors.TreeRootError, e:
589
 
+             print e
590
 
+             return
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)+"."
595
 
+             return
596
 
+@@ -592,6 +598,9 @@
597
 
598
 
+         if len(args) == 0:
599
 
+             args = None
600
 
++        if options.version is None:
601
 
++            return options, tree.tree_version, args
602
 
++
603
 
+         revision=cmdutil.determine_revision_arch(tree, options.version)
604
 
+         return options, revision.get_version(), args
605
 
606
 
+@@ -601,11 +610,16 @@
607
 
+         """
608
 
+         tree=arch.tree_root()
609
 
+         options, version, files = self.parse_commandline(cmdargs, tree)
610
 
++        ancestor = None
611
 
+         if options.__dict__.has_key("base") and options.base:
612
 
+             base = cmdutil.determine_revision_tree(tree, options.base)
613
 
++            ancestor = base
614
 
+         else:
615
 
+-            base = cmdutil.submit_revision(tree)
616
 
+-        
617
 
++            base = ancillary.submit_revision(tree)
618
 
++            ancestor = base
619
 
++        if ancestor is None:
620
 
++            ancestor = arch_compound.tree_latest(tree, version)
621
 
++
622
 
+         writeversion=version
623
 
+         archive=version.archive
624
 
+         source=cmdutil.get_mirror_source(archive)
625
 
+@@ -625,18 +639,26 @@
626
 
+         try:
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)
631
 
+-            else:
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)
638
 
++                else:
639
 
++                    raise NoVersionLogs(version)
640
 
++        try:
641
 
++            arch_last_revision = version.iter_revisions(True).next()
642
 
++        except StopIteration, e:
643
 
++            arch_last_revision = None
644
 
++ 
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"):
648
 
+                 raise OutOfDate
649
 
+             else:
650
 
+                 allow_old=True
651
 
652
 
+         try:
653
 
+-            if not cmdutil.has_changed(version):
654
 
++            if not cmdutil.has_changed(ancestor):
655
 
+                 if not cmdutil.prompt("Empty commit"):
656
 
+                     raise EmptyCommit
657
 
+         except arch.util.ExecProblem, e:
658
 
+@@ -645,15 +667,15 @@
659
 
+                 raise MissingID(e)
660
 
+             else:
661
 
+                 raise
662
 
+-        log = tree.log_message(create=False)
663
 
++        log = tree.log_message(create=False, version=version)
664
 
+         if log is None:
665
 
+             try:
666
 
+                 if cmdutil.prompt("Create log"):
667
 
+-                    edit_log(tree)
668
 
++                    edit_log(tree, version)
669
 
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)
674
 
+         if log is None: 
675
 
+             raise NoLogMessage
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)
680
 
+             else:
681
 
+-                revision = cmdutil.comp_revision(tree)
682
 
++                revision = ancillary.comp_revision(tree)
683
 
+         except cmdutil.CantDetermineRevision, e:
684
 
+             raise CommandFailedWrapper(e)
685
 
+         munger = None
686
 
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)
694
 
695
 
+         if len(args) > 0 or options.logs or options.pattern_files or \
696
 
+             options.control:
697
 
+             if munger is None:
698
 
+-                munger = cmdutil.MungeOpts(True)
699
 
++                munger = cmdutil.arch_compound.MungeOpts(True)
700
 
+                 munger.all_types(True)
701
 
+         if len(args) > 0:
702
 
+-            t_cwd = cmdutil.tree_cwd(tree)
703
 
++            t_cwd = arch_compound.tree_cwd(tree)
704
 
+             for name in args:
705
 
+                 if len(t_cwd) > 0:
706
 
+                     t_cwd += "/"
707
 
+@@ -878,7 +901,7 @@
708
 
+         if options.pattern_files:
709
 
+             munger.add_keep_pattern(options.pattern_files)
710
 
+                 
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)
715
 
716
 
+@@ -1042,18 +1065,13 @@
717
 
+         help_tree_spec()
718
 
+         return
719
 
720
 
+-def require_version_exists(version, spec):
721
 
+-    if not version.exists():
722
 
+-        raise cmdutil.CantDetermineVersion(spec, 
723
 
+-                                           "The version %s does not exist." \
724
 
+-                                           % version)
725
 
+-
726
 
+ class Revisions(BaseCommand):
727
 
+     """
728
 
+     Print a revision name based on a revision specifier
729
 
+     """
730
 
+     def __init__(self):
731
 
+         self.description="Lists revisions"
732
 
++        self.cl_revisions = []
733
 
+     
734
 
+     def do_command(self, cmdargs):
735
 
+         """
736
 
+@@ -1066,224 +1084,68 @@
737
 
+             self.tree = arch.tree_root()
738
 
+         except arch.errors.TreeRootError:
739
 
+             self.tree = None
740
 
++        if options.type == "default":
741
 
++            options.type = "archive"
742
 
+         try:
743
 
+-            iter = self.get_iterator(options.type, args, options.reverse, 
744
 
+-                                     options.modified)
745
 
++            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
746
 
++                                             options.reverse, options.modified,
747
 
++                                             options.shallow)
748
 
+         except cmdutil.CantDetermineRevision, e:
749
 
+             raise CommandFailedWrapper(e)
750
 
+-
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))
755
 
756
 
+-        for revision in iter:
757
 
+-            log = None
758
 
+-            if isinstance(revision, arch.Patchlog):
759
 
+-                log = revision
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
767
 
+-            if options.date:
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:
775
 
+-                        print "    Merged:"
776
 
+-                        showed_title = True
777
 
+-                    print "    %s" % revision
778
 
+-
779
 
+-    def get_iterator(self, type, args, reverse, modified):
780
 
+-        if len(args) > 0:
781
 
+-            spec = args[0]
782
 
+-        else:
783
 
+-            spec = None
784
 
+-        if modified is not None:
785
 
+-            iter = cmdutil.modified_iter(modified, self.tree)
786
 
+-            if reverse:
787
 
+-                return iter
788
 
+-            else:
789
 
+-                return cmdutil.iter_reverse(iter)
790
 
+-        elif type == "archive":
791
 
+-            if spec is None:
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)
796
 
+-            else:
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":
802
 
+-            if spec is None:
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)
807
 
+-            else:
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":
813
 
+-            if spec is None:
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)
818
 
+-            else:
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)
835
 
+-
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)
843
 
+-
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":
852
 
+-                return iter
853
 
+-            elif type == "direct-merges":
854
 
+-                return cmdutil.direct_merges(iter)
855
 
+-
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,
862
 
+-                                        reverse)
863
 
+-
864
 
+-        elif type == "partner-missing":
865
 
+-            return cmdutil.iter_partner_missing(self.tree, reverse)
866
 
+-
867
 
+-        elif type == "ancestry":
868
 
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
869
 
+-            iter = cmdutil._iter_ancestry(self.tree, revision)
870
 
+-            if reverse:
871
 
+-                return iter
872
 
+-            else:
873
 
+-                return cmdutil.iter_reverse(iter)
874
 
+-
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)
880
 
+-            if reverse:
881
 
+-                return iter_depends
882
 
+-            else:
883
 
+-                return cmdutil.iter_reverse(iter_depends)
884
 
+-        elif type == "micro":
885
 
+-            return cmdutil.iter_micro(self.tree)
886
 
+-
887
 
+-    
888
 
++        try:
889
 
++            for revision in iter:
890
 
++                log = None
891
 
++                if isinstance(revision, arch.Patchlog):
892
 
++                    log = revision
893
 
++                    revision=revision.revision
894
 
++                out = options.display(revision)
895
 
++                if out is not None:
896
 
++                    print out
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
902
 
++                if options.date:
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:
910
 
++                            print "    Merged:"
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."))
918
 
++
919
 
++    def changelog_append(self, revision):
920
 
++        if isinstance(revision, arch.Revision):
921
 
++            revision=arch.Patchlog(revision)
922
 
++        self.cl_revisions.append(revision)
923
 
++   
924
 
+     def get_parser(self):
925
 
+         """
926
 
+         Returns the options parser to use for the "revision" command.
927
 
928
 
+         :rtype: cmdutil.CmdOptionParser
929
 
+         """
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 "
946
 
+-                          "tree")
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"
966
 
+-                          " missing")
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")
982
 
+-
983
 
+-        select.add_option("", "--dependencies", action="store_const", 
984
 
+-                          const="dependencies", dest="type",
985
 
+-                          help="List revisions that the given revision "
986
 
+-                          "depends on")
987
 
+-
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")
992
 
+-
993
 
+-        select.add_option("--micro", action="store_const", 
994
 
+-                          const="micro", dest="type",
995
 
+-                          help="List partner revisions aimed for this "
996
 
+-                          "micro-branch")
997
 
+-
998
 
+-        select.add_option("", "--modified", dest="modified", 
999
 
+-                          help="List tree ancestor revisions that modified a "
1000
 
+-                          "given file", metavar="FILE[:LINE]")
1001
 
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()
1021
 
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:
1027
 
+             try:
1028
 
+-                version = " "+self.tree.tree_version.nonarch
1029
 
++                prompt = pylon.alias_or_version(self.tree.tree_version, 
1030
 
++                                                self.tree, 
1031
 
++                                                full=False)
1032
 
++                if prompt is not None:
1033
 
++                    prompt = " " + prompt
1034
 
+             except:
1035
 
+-                version = ""
1036
 
++                prompt = ""
1037
 
+         else:
1038
 
+-            version = ""
1039
 
+-        self.prompt = "Fai%s> " % version
1040
 
++            prompt = ""
1041
 
++        self.prompt = "Fai%s> " % prompt
1042
 
1043
 
+     def set_title(self, command=None):
1044
 
+         try:
1045
 
+-            version = self.tree.tree_version.nonarch
1046
 
++            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
1047
 
++                                             full=False)
1048
 
+         except:
1049
 
+             version = "[no version]"
1050
 
+         if command is None:
1051
 
+@@ -1489,8 +1360,15 @@
1052
 
+     def do_cd(self, line):
1053
 
+         if line == "":
1054
 
+             line = "~"
1055
 
++        line = os.path.expanduser(line)
1056
 
++        if os.path.isabs(line):
1057
 
++            newcwd = line
1058
 
++        else:
1059
 
++            newcwd = self.cwd+'/'+line
1060
 
++        newcwd = os.path.normpath(newcwd)
1061
 
+         try:
1062
 
+-            os.chdir(os.path.expanduser(line))
1063
 
++            os.chdir(newcwd)
1064
 
++            self.cwd = newcwd
1065
 
+         except Exception, e:
1066
 
+             print e
1067
 
+         try:
1068
 
+@@ -1523,7 +1401,7 @@
1069
 
+             except cmdutil.CantDetermineRevision, e:
1070
 
+                 print 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)
1074
 
1075
 
+         elif suggestions.has_key(args[0]):
1076
 
+             print suggestions[args[0]]
1077
 
+@@ -1574,7 +1452,7 @@
1078
 
+                 arg = line.split()[-1]
1079
 
+             else:
1080
 
+                 arg = ""
1081
 
+-            iter = iter_munged_completions(iter, arg, text)
1082
 
++            iter = cmdutil.iter_munged_completions(iter, arg, text)
1083
 
+         except Exception, e:
1084
 
+             print e
1085
 
+         return list(iter)
1086
 
+@@ -1604,10 +1482,11 @@
1087
 
+                 else:
1088
 
+                     arg = ""
1089
 
+                 if arg.startswith("-"):
1090
 
+-                    return list(iter_munged_completions(iter, arg, text))
1091
 
++                    return list(cmdutil.iter_munged_completions(iter, arg, 
1092
 
++                                                                text))
1093
 
+                 else:
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))
1098
 
1099
 
1100
 
+             elif cmd == "cd":
1101
 
+@@ -1615,13 +1494,13 @@
1102
 
+                     arg = args.split()[-1]
1103
 
+                 else:
1104
 
+                     arg = ""
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)
1109
 
+                 return list(iter)
1110
 
+             elif len(args)>0:
1111
 
+                 arg = args.split()[-1]
1112
 
+-                return list(iter_munged_completions(iter_file_completions(arg),
1113
 
+-                                                    arg, text))
1114
 
++                iter = cmdutil.iter_file_completions(arg)
1115
 
++                return list(cmdutil.iter_munged_completions(iter, arg, text))
1116
 
+             else:
1117
 
+                 return self.completenames(text, line, begidx, endidx)
1118
 
+         except Exception, e:
1119
 
+@@ -1636,44 +1515,8 @@
1120
 
+             yield entry
1121
 
1122
 
1123
 
+-def iter_file_completions(arg, only_dirs = False):
1124
 
+-    """Generate an iterator that iterates through filename completions.
1125
 
+-
1126
 
+-    :param arg: The filename fragment to match
1127
 
+-    :type arg: str
1128
 
+-    :param only_dirs: If true, match only directories
1129
 
+-    :type only_dirs: bool
1130
 
+-    """
1131
 
+-    cwd = os.getcwd()
1132
 
+-    if cwd != "/":
1133
 
+-        extras = [".", ".."]
1134
 
+-    else:
1135
 
+-        extras = []
1136
 
+-    (dir, file) = os.path.split(arg)
1137
 
+-    if dir != "":
1138
 
+-        listingdir = os.path.expanduser(dir)
1139
 
+-    else:
1140
 
+-        listingdir = cwd
1141
 
+-    for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
1142
 
+-        if dir != "":
1143
 
+-            userfile = dir+'/'+file
1144
 
+-        else:
1145
 
+-            userfile = file
1146
 
+-        if userfile.startswith(arg):
1147
 
+-            if os.path.isdir(listingdir+'/'+file):
1148
 
+-                userfile+='/'
1149
 
+-                yield userfile
1150
 
+-            elif not only_dirs:
1151
 
+-                yield userfile
1152
 
+-
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):]
1158
 
+-
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:
1163
 
+         dirs = [treepath]
1164
 
+     else:
1165
 
+@@ -1701,7 +1544,7 @@
1166
 
+     :return: An iterator of all matching untagged files
1167
 
+     :rtype: iterator of str
1168
 
+     """
1169
 
+-    treepath = cmdutil.tree_cwd(tree)
1170
 
++    treepath = arch_compound.tree_cwd(tree)
1171
 
+     if len(treepath) > 0:
1172
 
+         dirs = [treepath]
1173
 
+     else:
1174
 
+@@ -1743,8 +1586,8 @@
1175
 
+     :param arg: The prefix to match
1176
 
+     :type arg: str
1177
 
+     """
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"
1183
 
+     completions = []
1184
 
+     revision = cmdutil.determine_revision_tree(tree)
1185
 
+@@ -1756,14 +1599,6 @@
1186
 
+     shutil.rmtree(tmpdir)
1187
 
+     return completions
1188
 
1189
 
+-def iter_dir_completions(arg):
1190
 
+-    """Generate an iterator that iterates through directory name completions.
1191
 
+-
1192
 
+-    :param arg: The directory name fragment to match
1193
 
+-    :type arg: str
1194
 
+-    """
1195
 
+-    return iter_file_completions(arg, True)
1196
 
+-
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)
1203
 
1204
 
+-        tree = arch.tree_root()
1205
 
++        try:
1206
 
++            tree = arch.tree_root()
1207
 
++        except arch.errors.TreeRootError, e:
1208
 
++            raise pylon.errors.CommandFailedWrapper(e)
1209
 
++            
1210
 
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
1221
 
+                     else:
1222
 
+                         print "add-id not supported for \"%s\" tagging method"\
1223
 
+                             % method 
1224
 
+                         return
1225
 
+         
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
1231
 
++                    else:
1232
 
++                        print "add-id not supported for \"%s\" tagging method"\
1233
 
++                            % method 
1234
 
++                        return
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 @@
1239
 
+                     return
1240
 
+         
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
1246
 
+                 return
1247
 
+             else:
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:
1255
 
+                     try:
1256
 
+-                        cmdutil.add_tagline_or_explicit_id(file)
1257
 
++                        implicit = (id_type == "implicit")
1258
 
++                        cmdutil.add_tagline_or_explicit_id(file, False,
1259
 
++                                                           implicit)
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())
1281
 
+-
1282
 
+-        aliases = []
1283
 
+-        try:
1284
 
+-            for completion in completions:
1285
 
+-                alias = ancillary.compact_alias(str(completion), self.tree)
1286
 
+-                if alias:
1287
 
+-                    aliases.extend(alias)
1288
 
+-
1289
 
+-            for completion in completions:
1290
 
+-                if completion.archive == self.tree.tree_version.archive:
1291
 
+-                    aliases.append(completion.nonarch)
1292
 
+-
1293
 
+-        except Exception, e:
1294
 
+-            print e
1295
 
+-            
1296
 
+-        completions.extend(aliases)
1297
 
+-        return completions
1298
 
++        return cmdutil.merge_completions(self.tree, arg, index)
1299
 
1300
 
+     def do_command(self, cmdargs):
1301
 
+         """
1302
 
+@@ -1961,7 +1795,7 @@
1303
 
+         
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)
1309
 
1310
 
+         if len(args) > 0:
1311
 
+@@ -2027,14 +1861,14 @@
1312
 
+         :type other_revision: `arch.Revision`
1313
 
+         :return: 0 if the merge was skipped, 1 if it was applied
1314
 
+         """
1315
 
+-        other_tree = cmdutil.find_or_make_local_revision(other_revision)
1316
 
++        other_tree = arch_compound.find_or_make_local_revision(other_revision)
1317
 
+         try:
1318
 
+             if action == "native-merge":
1319
 
+-                ancestor = cmdutil.merge_ancestor2(self.tree, other_tree, 
1320
 
+-                                                   other_revision)
1321
 
++                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
1322
 
++                                                         other_revision)
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
1334
 
1335
 
+-        edit_log(self.tree)
1336
 
++        try:
1337
 
++            edit_log(self.tree, self.tree.tree_version)
1338
 
++        except pylon.errors.NoEditorSpecified, e:
1339
 
++            raise pylon.errors.CommandFailedWrapper(e)
1340
 
1341
 
+     def get_parser(self):
1342
 
+         """
1343
 
+@@ -2132,7 +1969,7 @@
1344
 
+         """
1345
 
+         return
1346
 
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
1351
 
+     
1352
 
+@@ -2141,28 +1978,29 @@
1353
 
+     """
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)
1364
 
+         log_is_new = True
1365
 
+         tmplog = log.name
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):
1370
 
+-                template = None
1371
 
++        template = pylon.log_template_path(tree)
1372
 
+         if template:
1373
 
+             shutil.copyfile(template, tmplog)
1374
 
+-        
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, 
1382
 
++                                         version)
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)
1389
 
++                else:
1390
 
++                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
1391
 
+                 log.description += mergestuff
1392
 
+         log.save()
1393
 
+     try:
1394
 
+@@ -2172,29 +2010,6 @@
1395
 
+             os.remove(log.name)
1396
 
+         raise
1397
 
1398
 
+-def merge_summary(new_merges, tree_version):
1399
 
+-    if len(new_merges) == 0:
1400
 
+-        return ""
1401
 
+-    if len(new_merges) == 1:
1402
 
+-        summary = new_merges[0].summary
1403
 
+-    else:
1404
 
+-        summary = "Merge"
1405
 
+-
1406
 
+-    credits = []
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)
1412
 
+-        else:
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))
1419
 
+-
1420
 
+-    return ("%s (%s)") % (summary, ", ".join(credits))
1421
 
1422
 
+ class MirrorArchive(BaseCommand):
1423
 
+     """
1424
 
+@@ -2268,31 +2083,73 @@
1425
 
1426
 
+ Use "alias" to list available (user and automatic) aliases."""
1427
 
1428
 
++auto_alias = [
1429
 
++"acur", 
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)",
1432
 
++"tcur",
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 \
1435
 
++used).""",
1436
 
++"tprev" , 
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" """,
1439
 
++"tanc" , 
1440
 
++"""(tree ancestor) The ancestor revision of the tree To specify an older \
1441
 
++revision, use a number, e.g. "tanc:4".""",
1442
 
++"tdate" , 
1443
 
++"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
1444
 
++"tmod" , 
1445
 
++""" (tree modified) The latest revision to modify a given file, e.g. \
1446
 
++"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
1447
 
++"ttag" , 
1448
 
++"""(tree tag) The revision that was tagged into the current tree revision, \
1449
 
++according to the tree""",
1450
 
++"tagcur", 
1451
 
++"""(tag current) The latest revision of the version that the current tree \
1452
 
++was tagged from.""",
1453
 
++"mergeanc" , 
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.""",
1456
 
++]
1457
 
++
1458
 
++
1459
 
++def is_auto_alias(name):
1460
 
++    """Determine whether a name is an auto alias name
1461
 
++
1462
 
++    :param name: the name to check
1463
 
++    :type name: str
1464
 
++    :return: True if the name is an auto alias, false if not
1465
 
++    :rtype: bool
1466
 
++    """
1467
 
++    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
1468
 
++
1469
 
++
1470
 
++def display_def(iter, wrap = 80):
1471
 
++    """Display a list of definitions
1472
 
++
1473
 
++    :param iter: iter of name, definition pairs
1474
 
++    :type iter: iter of (str, str)
1475
 
++    :param wrap: The width for text wrapping
1476
 
++    :type wrap: int
1477
 
++    """
1478
 
++    vals = list(iter)
1479
 
++    maxlen = 0
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)
1488
 
++
1489
 
++
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
1496
 
+-        can be used).
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
1507
 
+-        was tagged from.
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.
1510
 
+-   """
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]
1516
 
+-
1517
 
++    display_def(ancillary.iter_all_alias(tree))
1518
 
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)
1524
 
1525
 
++    def no_prefix(self, alias):
1526
 
++        if alias.startswith("^"):
1527
 
++            alias = alias[1:]
1528
 
++        return alias
1529
 
++        
1530
 
+     def arg_dispatch(self, args, options):
1531
 
+         """Add, modify, or list aliases, depending on number of arguments
1532
 
1533
 
+@@ -2438,15 +2300,20 @@
1534
 
+         if len(args) == 0:
1535
 
+             help_aliases(self.tree)
1536
 
+             return
1537
 
+-        elif len(args) == 1:
1538
 
+-            self.print_alias(args[0])
1539
 
+-        elif (len(args)) == 2:
1540
 
+-            self.add(args[0], args[1], options)
1541
 
+         else:
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)
1548
 
++            else:
1549
 
++                raise cmdutil.GetHelp
1550
 
1551
 
+     def print_alias(self, alias):
1552
 
+         answer = None
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:
1558
 
+                 answer = pair[1]
1559
 
+@@ -2464,6 +2331,8 @@
1560
 
+         :type expansion: str
1561
 
+         :param options: The commandline options
1562
 
+         """
1563
 
++        if is_auto_alias(alias):
1564
 
++            raise IsAutoAlias(alias)
1565
 
+         newlist = ""
1566
 
+         written = False
1567
 
+         new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
1568
 
+@@ -2490,14 +2359,17 @@
1569
 
+         deleted = False
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)
1575
 
+         newlist = ""
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])
1580
 
+             else:
1581
 
+                 deleted = True
1582
 
+         if not deleted:
1583
 
+-            raise errors.NoSuchAlias(args[0])
1584
 
++            raise errors.NoSuchAlias(alias)
1585
 
+         self.write_aliases(newlist, options)
1586
 
1587
 
+     def get_alias_file(self, options):
1588
 
+@@ -2526,7 +2398,7 @@
1589
 
+         :param options: The commandline options
1590
 
+         """
1591
 
+         filename = os.path.expanduser(self.get_alias_file(options))
1592
 
+-        file = cmdutil.NewFileVersion(filename)
1593
 
++        file = util.NewFileVersion(filename)
1594
 
+         file.write(newlist)
1595
 
+         file.commit()
1596
 
1597
 
+@@ -2588,10 +2460,13 @@
1598
 
+         :param cmdargs: The commandline arguments
1599
 
+         :type cmdargs: list of str
1600
 
+         """
1601
 
+-        cmdutil.find_editor()
1602
 
+         parser = self.get_parser()
1603
 
+         (options, args) = parser.parse_args(cmdargs)
1604
 
+         try:
1605
 
++            cmdutil.find_editor()
1606
 
++        except pylon.errors.NoEditorSpecified, e:
1607
 
++            raise pylon.errors.CommandFailedWrapper(e)
1608
 
++        try:
1609
 
+             self.tree=arch.tree_root()
1610
 
+         except:
1611
 
+             self.tree=None
1612
 
+@@ -2655,7 +2530,7 @@
1613
 
+             target_revision = cmdutil.determine_revision_arch(self.tree, 
1614
 
+                                                               args[0])
1615
 
+         else:
1616
 
+-            target_revision = cmdutil.tree_latest(self.tree)
1617
 
++            target_revision = arch_compound.tree_latest(self.tree)
1618
 
+         if len(args) > 1:
1619
 
+             merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
1620
 
+                        self.tree, f)) for f in args[1:] ]
1621
 
+@@ -2711,7 +2586,7 @@
1622
 
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())
1628
 
+         server.quit()
1629
 
1630
 
+@@ -2763,6 +2638,22 @@
1631
 
+ 'alias' : Alias,
1632
 
+ 'request-merge': RequestMerge,
1633
 
+ }
1634
 
++
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)
1640
 
++    return module
1641
 
++
1642
 
++def plugin(mod_name):
1643
 
++    module = my_import(mod_name)
1644
 
++    module.add_command(commands)
1645
 
++
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])
1649
 
++
1650
 
+ suggestions = {
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"
1660
 
+ }
1661
 
+ # arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
1662
 
 
1663
 
*** added file 'testdata/insert_top.patch'
1664
 
--- /dev/null 
1665
 
+++ testdata/insert_top.patch 
1666
 
@@ -0,0 +1,7 @@
1667
 
+--- orig/pylon/patches.py
1668
 
++++ mod/pylon/patches.py
1669
 
+@@ -1,3 +1,4 @@
1670
 
++#test
1671
 
+ import util
1672
 
+ import sys
1673
 
+ class PatchSyntax(Exception):
1674
 
 
1675
 
*** added file 'testdata/mod'
1676
 
--- /dev/null 
1677
 
+++ testdata/mod 
1678
 
@@ -0,0 +1,2681 @@
1679
 
+# Copyright (C) 2004 Aaron Bentley
1680
 
+# <aaron.bentley@utoronto.ca>
1681
 
+#
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.
1686
 
+#
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.
1691
 
+#
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
1695
 
+
1696
 
+import sys
1697
 
+import arch
1698
 
+import arch.util
1699
 
+import arch.arch
1700
 
+
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 
1710
 
+
1711
 
+import abacmds
1712
 
+import cmdutil
1713
 
+import shutil
1714
 
+import os
1715
 
+import options
1716
 
+import time
1717
 
+import cmd
1718
 
+import readline
1719
 
+import re
1720
 
+import string
1721
 
+import terminal
1722
 
+import email
1723
 
+import smtplib
1724
 
+import textwrap
1725
 
+
1726
 
+__docformat__ = "restructuredtext"
1727
 
+__doc__ = "Implementation of user (sub) commands"
1728
 
+commands = {}
1729
 
+
1730
 
+def find_command(cmd):
1731
 
+    """
1732
 
+    Return an instance of a command type.  Return None if the type isn't
1733
 
+    registered.
1734
 
+
1735
 
+    :param cmd: the name of the command to look for
1736
 
+    :type cmd: the type of the command
1737
 
+    """
1738
 
+    if commands.has_key(cmd):
1739
 
+        return commands[cmd]()
1740
 
+    else:
1741
 
+        return None
1742
 
+
1743
 
+class BaseCommand:
1744
 
+    def __call__(self, cmdline):
1745
 
+        try:
1746
 
+            self.do_command(cmdline.split())
1747
 
+        except cmdutil.GetHelp, e:
1748
 
+            self.help()
1749
 
+        except Exception, e:
1750
 
+            print e
1751
 
+
1752
 
+    def get_completer(index):
1753
 
+        return None
1754
 
+
1755
 
+    def complete(self, args, text):
1756
 
+        """
1757
 
+        Returns a list of possible completions for the given text.
1758
 
+
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])
1762
 
+        :type text: str
1763
 
+        :rtype: list of str
1764
 
+        """
1765
 
+        matches = []
1766
 
+        candidates = None
1767
 
+
1768
 
+        if len(args) > 0: 
1769
 
+            realtext = args[-1]
1770
 
+        else:
1771
 
+            realtext = ""
1772
 
+
1773
 
+        try:
1774
 
+            parser=self.get_parser()
1775
 
+            if realtext.startswith('-'):
1776
 
+                candidates = parser.iter_options()
1777
 
+            else:
1778
 
+                (options, parsed_args) = parser.parse_args(args)
1779
 
+
1780
 
+                if len (parsed_args) > 0:
1781
 
+                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
1782
 
+                else:
1783
 
+                    candidates = self.get_completer("", 0)
1784
 
+        except:
1785
 
+            pass
1786
 
+        if candidates is None:
1787
 
+            return
1788
 
+        for candidate in candidates:
1789
 
+            candidate = str(candidate)
1790
 
+            if candidate.startswith(realtext):
1791
 
+                matches.append(candidate[len(realtext)- len(text):])
1792
 
+        return matches
1793
 
+
1794
 
+
1795
 
+class Help(BaseCommand):
1796
 
+    """
1797
 
+    Lists commands, prints help messages.
1798
 
+    """
1799
 
+    def __init__(self):
1800
 
+        self.description="Prints help mesages"
1801
 
+        self.parser = None
1802
 
+
1803
 
+    def do_command(self, cmdargs):
1804
 
+        """
1805
 
+        Prints a help message.
1806
 
+        """
1807
 
+        options, args = self.get_parser().parse_args(cmdargs)
1808
 
+        if len(args) > 1:
1809
 
+            raise cmdutil.GetHelp
1810
 
+
1811
 
+        if options.native or options.suggestions or options.external:
1812
 
+            native = options.native
1813
 
+            suggestions = options.suggestions
1814
 
+            external = options.external
1815
 
+        else:
1816
 
+            native = True
1817
 
+            suggestions = False
1818
 
+            external = True
1819
 
+        
1820
 
+        if len(args) == 0:
1821
 
+            self.list_commands(native, suggestions, external)
1822
 
+            return
1823
 
+        elif len(args) == 1:
1824
 
+            command_help(args[0])
1825
 
+            return
1826
 
+
1827
 
+    def help(self):
1828
 
+        self.get_parser().print_help()
1829
 
+        print """
1830
 
+If no command is specified, commands are listed.  If a command is
1831
 
+specified, help for that command is listed.
1832
 
+        """
1833
 
+
1834
 
+    def get_parser(self):
1835
 
+        """
1836
 
+        Returns the options parser to use for the "revision" command.
1837
 
+
1838
 
+        :rtype: cmdutil.CmdOptionParser
1839
 
+        """
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
1850
 
+        return parser 
1851
 
+      
1852
 
+    def list_commands(self, native=True, suggest=False, external=True):
1853
 
+        """
1854
 
+        Lists supported commands.
1855
 
+
1856
 
+        :param native: list native, python-based commands
1857
 
+        :type native: bool
1858
 
+        :param external: list external aba-style commands
1859
 
+        :type external: bool
1860
 
+        """
1861
 
+        if native:
1862
 
+            print "Native Fai commands"
1863
 
+            keys=commands.keys()
1864
 
+            keys.sort()
1865
 
+            for k in keys:
1866
 
+                space=""
1867
 
+                for i in range(28-len(k)):
1868
 
+                    space+=" "
1869
 
+                print space+k+" : "+commands[k]().description
1870
 
+            print
1871
 
+        if suggest:
1872
 
+            print "Unavailable commands and suggested alternatives"
1873
 
+            key_list = suggestions.keys()
1874
 
+            key_list.sort()
1875
 
+            for key in key_list:
1876
 
+                print "%28s : %s" % (key, suggestions[key])
1877
 
+            print
1878
 
+        if external:
1879
 
+            fake_aba = abacmds.AbaCmds()
1880
 
+            if (fake_aba.abadir == ""):
1881
 
+                return
1882
 
+            print "External commands"
1883
 
+            fake_aba.list_commands()
1884
 
+            print
1885
 
+        if not suggest:
1886
 
+            print "Use help --suggest to list alternatives to tla and aba"\
1887
 
+                " commands."
1888
 
+        if options.tla_fallthrough and (native or external):
1889
 
+            print "Fai also supports tla commands."
1890
 
+
1891
 
+def command_help(cmd):
1892
 
+    """
1893
 
+    Prints help for a command.
1894
 
+
1895
 
+    :param cmd: The name of the command to print help for
1896
 
+    :type cmd: str
1897
 
+    """
1898
 
+    fake_aba = abacmds.AbaCmds()
1899
 
+    cmdobj = find_command(cmd)
1900
 
+    if cmdobj != None:
1901
 
+        cmdobj.help()
1902
 
+    elif suggestions.has_key(cmd):
1903
 
+        print "Not available\n" + suggestions[cmd]
1904
 
+    else:
1905
 
+        abacmd = fake_aba.is_command(cmd)
1906
 
+        if abacmd:
1907
 
+            abacmd.help()
1908
 
+        else:
1909
 
+            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
1910
 
+
1911
 
+
1912
 
+
1913
 
+class Changes(BaseCommand):
1914
 
+    """
1915
 
+    the "changes" command: lists differences between trees/revisions:
1916
 
+    """
1917
 
+    
1918
 
+    def __init__(self):
1919
 
+        self.description="Lists what files have changed in the project tree"
1920
 
+
1921
 
+    def get_completer(self, arg, index):
1922
 
+        if index > 1:
1923
 
+            return None
1924
 
+        try:
1925
 
+            tree = arch.tree_root()
1926
 
+        except:
1927
 
+            tree = None
1928
 
+        return cmdutil.iter_revision_completions(arg, tree)
1929
 
+    
1930
 
+    def parse_commandline(self, cmdline):
1931
 
+        """
1932
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
1933
 
+        
1934
 
+        :param cmdline: A list of arguments to parse
1935
 
+        :rtype: (options, Revision, Revision/WorkingTree)
1936
 
+        """
1937
 
+        parser=self.get_parser()
1938
 
+        (options, args) = parser.parse_args(cmdline)
1939
 
+        if len(args) > 2:
1940
 
+            raise cmdutil.GetHelp
1941
 
+
1942
 
+        tree=arch.tree_root()
1943
 
+        if len(args) == 0:
1944
 
+            a_spec = ancillary.comp_revision(tree)
1945
 
+        else:
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)
1951
 
+        else:
1952
 
+            b_spec=tree
1953
 
+        return options, a_spec, b_spec
1954
 
+
1955
 
+    def do_command(self, cmdargs):
1956
 
+        """
1957
 
+        Master function that perfoms the "changes" command.
1958
 
+        """
1959
 
+        try:
1960
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs);
1961
 
+        except cmdutil.CantDetermineRevision, e:
1962
 
+            print e
1963
 
+            return
1964
 
+        except arch.errors.TreeRootError, e:
1965
 
+            print e
1966
 
+            return
1967
 
+        if options.changeset:
1968
 
+            changeset=options.changeset
1969
 
+            tmpdir = None
1970
 
+        else:
1971
 
+            tmpdir=util.tmpdir()
1972
 
+            changeset=tmpdir+"/changeset"
1973
 
+        try:
1974
 
+            delta=arch.iter_delta(a_spec, b_spec, changeset)
1975
 
+            try:
1976
 
+                for line in delta:
1977
 
+                    if cmdutil.chattermatch(line, "changeset:"):
1978
 
+                        pass
1979
 
+                    else:
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)
1985
 
+                else:
1986
 
+                    raise
1987
 
+            status=delta.status
1988
 
+            if status > 1:
1989
 
+                return
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()
1996
 
+                    else:
1997
 
+                        b_dir = b_spec
1998
 
+                    a_dir = a_spec.library_find()
1999
 
+                    diffopts = options.diffopts.split()
2000
 
+                    cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
2001
 
+                else:
2002
 
+                    cmdutil.show_diffs(delta.changeset)
2003
 
+        finally:
2004
 
+            if tmpdir and (os.access(tmpdir, os.X_OK)):
2005
 
+                shutil.rmtree(tmpdir)
2006
 
+
2007
 
+    def get_parser(self):
2008
 
+        """
2009
 
+        Returns the options parser to use for the "changes" command.
2010
 
+
2011
 
+        :rtype: cmdutil.CmdOptionParser
2012
 
+        """
2013
 
+        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
2014
 
+                                       " [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")
2027
 
+
2028
 
+        return parser
2029
 
+
2030
 
+    def help(self, parser=None):
2031
 
+        """
2032
 
+        Prints a help message.
2033
 
+
2034
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2035
 
+        not supplied, it is retrieved.
2036
 
+        :type parser: cmdutil.CmdOptionParser
2037
 
+        """
2038
 
+        if parser is None:
2039
 
+            parser=self.get_parser()
2040
 
+        parser.print_help()
2041
 
+        print """
2042
 
+Performs source-tree comparisons
2043
 
+
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.
2048
 
+        """
2049
 
+        help_tree_spec() 
2050
 
+        return
2051
 
+
2052
 
+
2053
 
+class ApplyChanges(BaseCommand):
2054
 
+    """
2055
 
+    Apply differences between two revisions to a tree
2056
 
+    """
2057
 
+    
2058
 
+    def __init__(self):
2059
 
+        self.description="Applies changes to a project tree"
2060
 
+    
2061
 
+    def get_completer(self, arg, index):
2062
 
+        if index > 1:
2063
 
+            return None
2064
 
+        try:
2065
 
+            tree = arch.tree_root()
2066
 
+        except:
2067
 
+            tree = None
2068
 
+        return cmdutil.iter_revision_completions(arg, tree)
2069
 
+
2070
 
+    def parse_commandline(self, cmdline, tree):
2071
 
+        """
2072
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
2073
 
+        
2074
 
+        :param cmdline: A list of arguments to parse
2075
 
+        :rtype: (options, Revision, Revision/WorkingTree)
2076
 
+        """
2077
 
+        parser=self.get_parser()
2078
 
+        (options, args) = parser.parse_args(cmdline)
2079
 
+        if len(args) != 2:
2080
 
+            raise cmdutil.GetHelp
2081
 
+
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
2087
 
+
2088
 
+    def do_command(self, cmdargs):
2089
 
+        """
2090
 
+        Master function that performs "apply-changes".
2091
 
+        """
2092
 
+        try:
2093
 
+            tree = arch.tree_root()
2094
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
2095
 
+        except cmdutil.CantDetermineRevision, e:
2096
 
+            print e
2097
 
+            return
2098
 
+        except arch.errors.TreeRootError, e:
2099
 
+            print e
2100
 
+            return
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)
2104
 
+
2105
 
+    def get_parser(self):
2106
 
+        """
2107
 
+        Returns the options parser to use for the "apply-changes" command.
2108
 
+
2109
 
+        :rtype: cmdutil.CmdOptionParser
2110
 
+        """
2111
 
+        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
2112
 
+                                       " 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")
2122
 
+        return parser
2123
 
+
2124
 
+    def help(self, parser=None):
2125
 
+        """
2126
 
+        Prints a help message.
2127
 
+
2128
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2129
 
+        not supplied, it is retrieved.
2130
 
+        :type parser: cmdutil.CmdOptionParser
2131
 
+        """
2132
 
+        if parser is None:
2133
 
+            parser=self.get_parser()
2134
 
+        parser.print_help()
2135
 
+        print """
2136
 
+Applies changes to a project tree
2137
 
+
2138
 
+Compares two revisions and applies the difference between them to the current
2139
 
+tree.
2140
 
+        """
2141
 
+        help_tree_spec() 
2142
 
+        return
2143
 
+
2144
 
+class Update(BaseCommand):
2145
 
+    """
2146
 
+    Updates a project tree to a given revision, preserving un-committed hanges. 
2147
 
+    """
2148
 
+    
2149
 
+    def __init__(self):
2150
 
+        self.description="Apply the latest changes to the current directory"
2151
 
+
2152
 
+    def get_completer(self, arg, index):
2153
 
+        if index > 0:
2154
 
+            return None
2155
 
+        try:
2156
 
+            tree = arch.tree_root()
2157
 
+        except:
2158
 
+            tree = None
2159
 
+        return cmdutil.iter_revision_completions(arg, tree)
2160
 
+    
2161
 
+    def parse_commandline(self, cmdline, tree):
2162
 
+        """
2163
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
2164
 
+        
2165
 
+        :param cmdline: A list of arguments to parse
2166
 
+        :rtype: (options, Revision, Revision/WorkingTree)
2167
 
+        """
2168
 
+        parser=self.get_parser()
2169
 
+        (options, args) = parser.parse_args(cmdline)
2170
 
+        if len(args) > 2:
2171
 
+            raise cmdutil.GetHelp
2172
 
+
2173
 
+        spec=None
2174
 
+        if len(args)>0:
2175
 
+            spec=args[0]
2176
 
+        revision=cmdutil.determine_revision_arch(tree, spec)
2177
 
+        cmdutil.ensure_archive_registered(revision.archive)
2178
 
+
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)
2186
 
+
2187
 
+                revision=cmdutil.determine_revision_arch(tree, spec)
2188
 
+
2189
 
+        return options, revision 
2190
 
+
2191
 
+    def do_command(self, cmdargs):
2192
 
+        """
2193
 
+        Master function that perfoms the "update" command.
2194
 
+        """
2195
 
+        tree=arch.tree_root()
2196
 
+        try:
2197
 
+            options, to_revision = self.parse_commandline(cmdargs, tree);
2198
 
+        except cmdutil.CantDetermineRevision, e:
2199
 
+            print e
2200
 
+            return
2201
 
+        except arch.errors.TreeRootError, e:
2202
 
+            print e
2203
 
+            return
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)+"."
2207
 
+            return
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
2216
 
+
2217
 
+    def get_parser(self):
2218
 
+        """
2219
 
+        Returns the options parser to use for the "update" command.
2220
 
+
2221
 
+        :rtype: cmdutil.CmdOptionParser
2222
 
+        """
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")
2231
 
+        return parser
2232
 
+
2233
 
+    def help(self, parser=None):
2234
 
+        """
2235
 
+        Prints a help message.
2236
 
+
2237
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2238
 
+        not supplied, it is retrieved.
2239
 
+        :type parser: cmdutil.CmdOptionParser
2240
 
+        """
2241
 
+        if parser is None:
2242
 
+            parser=self.get_parser()
2243
 
+        parser.print_help()
2244
 
+        print """
2245
 
+Updates a working tree to the current archive revision
2246
 
+
2247
 
+If a revision or version is specified, that is used instead 
2248
 
+        """
2249
 
+        help_tree_spec() 
2250
 
+        return
2251
 
+
2252
 
+
2253
 
+class Commit(BaseCommand):
2254
 
+    """
2255
 
+    Create a revision based on the changes in the current tree.
2256
 
+    """
2257
 
+    
2258
 
+    def __init__(self):
2259
 
+        self.description="Write local changes to the archive"
2260
 
+
2261
 
+    def get_completer(self, arg, index):
2262
 
+        if arg is None:
2263
 
+            arg = ""
2264
 
+        return iter_modified_file_completions(arch.tree_root(), arg)
2265
 
+#        return iter_source_file_completions(arch.tree_root(), arg)
2266
 
+    
2267
 
+    def parse_commandline(self, cmdline, tree):
2268
 
+        """
2269
 
+        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
2270
 
+        
2271
 
+        :param cmdline: A list of arguments to parse
2272
 
+        :rtype: (options, Revision, Revision/WorkingTree)
2273
 
+        """
2274
 
+        parser=self.get_parser()
2275
 
+        (options, args) = parser.parse_args(cmdline)
2276
 
+
2277
 
+        if len(args) == 0:
2278
 
+            args = None
2279
 
+        if options.version is None:
2280
 
+            return options, tree.tree_version, args
2281
 
+
2282
 
+        revision=cmdutil.determine_revision_arch(tree, options.version)
2283
 
+        return options, revision.get_version(), args
2284
 
+
2285
 
+    def do_command(self, cmdargs):
2286
 
+        """
2287
 
+        Master function that perfoms the "commit" command.
2288
 
+        """
2289
 
+        tree=arch.tree_root()
2290
 
+        options, version, files = self.parse_commandline(cmdargs, tree)
2291
 
+        ancestor = None
2292
 
+        if options.__dict__.has_key("base") and options.base:
2293
 
+            base = cmdutil.determine_revision_tree(tree, options.base)
2294
 
+            ancestor = base
2295
 
+        else:
2296
 
+            base = ancillary.submit_revision(tree)
2297
 
+            ancestor = base
2298
 
+        if ancestor is None:
2299
 
+            ancestor = arch_compound.tree_latest(tree, version)
2300
 
+
2301
 
+        writeversion=version
2302
 
+        archive=version.archive
2303
 
+        source=cmdutil.get_mirror_source(archive)
2304
 
+        allow_old=False
2305
 
+        writethrough="implicit"
2306
 
+
2307
 
+        if source!=None:
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)
2313
 
+
2314
 
+        elif archive.is_mirror:
2315
 
+            raise CommitToMirror(archive)
2316
 
+
2317
 
+        try:
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)
2324
 
+                else:
2325
 
+                    raise NoVersionLogs(version)
2326
 
+        try:
2327
 
+            arch_last_revision = version.iter_revisions(True).next()
2328
 
+        except StopIteration, e:
2329
 
+            arch_last_revision = None
2330
 
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"):
2334
 
+                raise OutOfDate
2335
 
+            else:
2336
 
+                allow_old=True
2337
 
+
2338
 
+        try:
2339
 
+            if not cmdutil.has_changed(ancestor):
2340
 
+                if not cmdutil.prompt("Empty commit"):
2341
 
+                    raise EmptyCommit
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)
2346
 
+            else:
2347
 
+                raise
2348
 
+        log = tree.log_message(create=False, version=version)
2349
 
+        if log is None:
2350
 
+            try:
2351
 
+                if cmdutil.prompt("Create log"):
2352
 
+                    edit_log(tree, version)
2353
 
+
2354
 
+            except cmdutil.NoEditorSpecified, e:
2355
 
+                raise CommandFailed(e)
2356
 
+            log = tree.log_message(create=False, version=version)
2357
 
+        if log is None: 
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
2362
 
+        try:
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)
2366
 
+
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)
2371
 
+            else:
2372
 
+                raise
2373
 
+
2374
 
+    def get_parser(self):
2375
 
+        """
2376
 
+        Returns the options parser to use for the "commit" command.
2377
 
+
2378
 
+        :rtype: cmdutil.CmdOptionParser
2379
 
+        """
2380
 
+
2381
 
+        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
2382
 
+                                       " [file2...]")
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")
2395
 
+        return parser
2396
 
+
2397
 
+    def help(self, parser=None):
2398
 
+        """
2399
 
+        Prints a help message.
2400
 
+
2401
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2402
 
+        not supplied, it is retrieved.
2403
 
+        :type parser: cmdutil.CmdOptionParser
2404
 
+        """
2405
 
+        if parser is None:
2406
 
+            parser=self.get_parser()
2407
 
+        parser.print_help()
2408
 
+        print """
2409
 
+Updates a working tree to the current archive revision
2410
 
+
2411
 
+If a version is specified, that is used instead 
2412
 
+        """
2413
 
+#        help_tree_spec() 
2414
 
+        return
2415
 
+
2416
 
+
2417
 
+
2418
 
+class CatLog(BaseCommand):
2419
 
+    """
2420
 
+    Print the log of a given file (from current tree)
2421
 
+    """
2422
 
+    def __init__(self):
2423
 
+        self.description="Prints the patch log for a revision"
2424
 
+
2425
 
+    def get_completer(self, arg, index):
2426
 
+        if index > 0:
2427
 
+            return None
2428
 
+        try:
2429
 
+            tree = arch.tree_root()
2430
 
+        except:
2431
 
+            tree = None
2432
 
+        return cmdutil.iter_revision_completions(arg, tree)
2433
 
+
2434
 
+    def do_command(self, cmdargs):
2435
 
+        """
2436
 
+        Master function that perfoms the "cat-log" command.
2437
 
+        """
2438
 
+        parser=self.get_parser()
2439
 
+        (options, args) = parser.parse_args(cmdargs)
2440
 
+        try:
2441
 
+            tree = arch.tree_root()
2442
 
+        except arch.errors.TreeRootError, e:
2443
 
+            tree = None
2444
 
+        spec=None
2445
 
+        if len(args) > 0:
2446
 
+            spec=args[0]
2447
 
+        if len(args) > 1:
2448
 
+            raise cmdutil.GetHelp()
2449
 
+        try:
2450
 
+            if tree:
2451
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
2452
 
+            else:
2453
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
2454
 
+        except cmdutil.CantDetermineRevision, e:
2455
 
+            raise CommandFailedWrapper(e)
2456
 
+        log = None
2457
 
+        
2458
 
+        use_tree = (options.source == "tree" or \
2459
 
+            (options.source == "any" and tree))
2460
 
+        use_arch = (options.source == "archive" or options.source == "any")
2461
 
+        
2462
 
+        log = None
2463
 
+        if use_tree:
2464
 
+            for log in tree.iter_logs(revision.get_version()):
2465
 
+                if log.revision == revision:
2466
 
+                    break
2467
 
+                else:
2468
 
+                    log = None
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
2476
 
+
2477
 
+    def get_parser(self):
2478
 
+        """
2479
 
+        Returns the options parser to use for the "cat-log" command.
2480
 
+
2481
 
+        :rtype: cmdutil.CmdOptionParser
2482
 
+        """
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")
2489
 
+        return parser 
2490
 
+
2491
 
+    def help(self, parser=None):
2492
 
+        """
2493
 
+        Prints a help message.
2494
 
+
2495
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2496
 
+        not supplied, it is retrieved.
2497
 
+        :type parser: cmdutil.CmdOptionParser
2498
 
+        """
2499
 
+        if parser==None:
2500
 
+            parser=self.get_parser()
2501
 
+        parser.print_help()
2502
 
+        print """
2503
 
+Prints the log for the specified revision
2504
 
+        """
2505
 
+        help_tree_spec()
2506
 
+        return
2507
 
+
2508
 
+class Revert(BaseCommand):
2509
 
+    """ Reverts a tree (or aspects of it) to a revision
2510
 
+    """
2511
 
+    def __init__(self):
2512
 
+        self.description="Reverts a tree (or aspects of it) to a revision "
2513
 
+
2514
 
+    def get_completer(self, arg, index):
2515
 
+        if index > 0:
2516
 
+            return None
2517
 
+        try:
2518
 
+            tree = arch.tree_root()
2519
 
+        except:
2520
 
+            tree = None
2521
 
+        return iter_modified_file_completions(tree, arg)
2522
 
+
2523
 
+    def do_command(self, cmdargs):
2524
 
+        """
2525
 
+        Master function that perfoms the "revert" command.
2526
 
+        """
2527
 
+        parser=self.get_parser()
2528
 
+        (options, args) = parser.parse_args(cmdargs)
2529
 
+        try:
2530
 
+            tree = arch.tree_root()
2531
 
+        except arch.errors.TreeRootError, e:
2532
 
+            raise CommandFailed(e)
2533
 
+        spec=None
2534
 
+        if options.revision is not None:
2535
 
+            spec=options.revision
2536
 
+        try:
2537
 
+            if spec is not None:
2538
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
2539
 
+            else:
2540
 
+                revision = ancillary.comp_revision(tree)
2541
 
+        except cmdutil.CantDetermineRevision, e:
2542
 
+            raise CommandFailedWrapper(e)
2543
 
+        munger = None
2544
 
+
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)
2550
 
+
2551
 
+        if len(args) > 0 or options.logs or options.pattern_files or \
2552
 
+            options.control:
2553
 
+            if munger is None:
2554
 
+                munger = cmdutil.arch_compound.MungeOpts(True)
2555
 
+                munger.all_types(True)
2556
 
+        if len(args) > 0:
2557
 
+            t_cwd = arch_compound.tree_cwd(tree)
2558
 
+            for name in args:
2559
 
+                if len(t_cwd) > 0:
2560
 
+                    t_cwd += "/"
2561
 
+                name = "./" + t_cwd + name
2562
 
+                munger.add_keep_file(name);
2563
 
+
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
2574
 
+        if options.logs:
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)
2581
 
+                
2582
 
+        for line in arch_compound.revert(tree, revision, munger, 
2583
 
+                                   not options.no_output):
2584
 
+            cmdutil.colorize(line)
2585
 
+
2586
 
+
2587
 
+    def get_parser(self):
2588
 
+        """
2589
 
+        Returns the options parser to use for the "cat-log" command.
2590
 
+
2591
 
+        :rtype: cmdutil.CmdOptionParser
2592
 
+        """
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", 
2601
 
+                          dest="deletions", 
2602
 
+                          help="Restore deleted files")
2603
 
+        parser.add_option("", "--additions", action="store_true", 
2604
 
+                          dest="additions", 
2605
 
+                          help="Remove added files")
2606
 
+        parser.add_option("", "--renames", action="store_true", 
2607
 
+                          dest="renames", 
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", 
2614
 
+                          metavar="REGEX")
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", 
2622
 
+                          dest="no_output", 
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")
2627
 
+        return parser 
2628
 
+
2629
 
+    def help(self, parser=None):
2630
 
+        """
2631
 
+        Prints a help message.
2632
 
+
2633
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2634
 
+        not supplied, it is retrieved.
2635
 
+        :type parser: cmdutil.CmdOptionParser
2636
 
+        """
2637
 
+        if parser==None:
2638
 
+            parser=self.get_parser()
2639
 
+        parser.print_help()
2640
 
+        print """
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
2643
 
+reverted.  
2644
 
+
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.
2648
 
+
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!)
2651
 
+
2652
 
+Unless "-n" is specified, reversions can be undone with "redo".
2653
 
+        """
2654
 
+        return
2655
 
+
2656
 
+class Revision(BaseCommand):
2657
 
+    """
2658
 
+    Print a revision name based on a revision specifier
2659
 
+    """
2660
 
+    def __init__(self):
2661
 
+        self.description="Prints the name of a revision"
2662
 
+
2663
 
+    def get_completer(self, arg, index):
2664
 
+        if index > 0:
2665
 
+            return None
2666
 
+        try:
2667
 
+            tree = arch.tree_root()
2668
 
+        except:
2669
 
+            tree = None
2670
 
+        return cmdutil.iter_revision_completions(arg, tree)
2671
 
+
2672
 
+    def do_command(self, cmdargs):
2673
 
+        """
2674
 
+        Master function that perfoms the "revision" command.
2675
 
+        """
2676
 
+        parser=self.get_parser()
2677
 
+        (options, args) = parser.parse_args(cmdargs)
2678
 
+
2679
 
+        try:
2680
 
+            tree = arch.tree_root()
2681
 
+        except arch.errors.TreeRootError:
2682
 
+            tree = None
2683
 
+
2684
 
+        spec=None
2685
 
+        if len(args) > 0:
2686
 
+            spec=args[0]
2687
 
+        if len(args) > 1:
2688
 
+            raise cmdutil.GetHelp
2689
 
+        try:
2690
 
+            if tree:
2691
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
2692
 
+            else:
2693
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
2694
 
+        except cmdutil.CantDetermineRevision, e:
2695
 
+            print str(e)
2696
 
+            return
2697
 
+        print options.display(revision)
2698
 
+
2699
 
+    def get_parser(self):
2700
 
+        """
2701
 
+        Returns the options parser to use for the "revision" command.
2702
 
+
2703
 
+        :rtype: cmdutil.CmdOptionParser
2704
 
+        """
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, 
2720
 
+                         dest="display",
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")
2725
 
+        return parser 
2726
 
+
2727
 
+    def help(self, parser=None):
2728
 
+        """
2729
 
+        Prints a help message.
2730
 
+
2731
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2732
 
+        not supplied, it is retrieved.
2733
 
+        :type parser: cmdutil.CmdOptionParser
2734
 
+        """
2735
 
+        if parser==None:
2736
 
+            parser=self.get_parser()
2737
 
+        parser.print_help()
2738
 
+        print """
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.
2742
 
+        """
2743
 
+        help_tree_spec()
2744
 
+        return
2745
 
+
2746
 
+class Revisions(BaseCommand):
2747
 
+    """
2748
 
+    Print a revision name based on a revision specifier
2749
 
+    """
2750
 
+    def __init__(self):
2751
 
+        self.description="Lists revisions"
2752
 
+        self.cl_revisions = []
2753
 
+    
2754
 
+    def do_command(self, cmdargs):
2755
 
+        """
2756
 
+        Master function that perfoms the "revision" command.
2757
 
+        """
2758
 
+        (options, args) = self.get_parser().parse_args(cmdargs)
2759
 
+        if len(args) > 1:
2760
 
+            raise cmdutil.GetHelp
2761
 
+        try:
2762
 
+            self.tree = arch.tree_root()
2763
 
+        except arch.errors.TreeRootError:
2764
 
+            self.tree = None
2765
 
+        if options.type == "default":
2766
 
+            options.type = "archive"
2767
 
+        try:
2768
 
+            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
2769
 
+                                             options.reverse, options.modified,
2770
 
+                                             options.shallow)
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))
2777
 
+
2778
 
+        try:
2779
 
+            for revision in iter:
2780
 
+                log = None
2781
 
+                if isinstance(revision, arch.Patchlog):
2782
 
+                    log = revision
2783
 
+                    revision=revision.revision
2784
 
+                out = options.display(revision)
2785
 
+                if out is not None:
2786
 
+                    print out
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
2792
 
+                if options.date:
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:
2800
 
+                            print "    Merged:"
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."))
2808
 
+
2809
 
+    def changelog_append(self, revision):
2810
 
+        if isinstance(revision, arch.Revision):
2811
 
+            revision=arch.Patchlog(revision)
2812
 
+        self.cl_revisions.append(revision)
2813
 
+   
2814
 
+    def get_parser(self):
2815
 
+        """
2816
 
+        Returns the options parser to use for the "revision" command.
2817
 
+
2818
 
+        :rtype: cmdutil.CmdOptionParser
2819
 
+        """
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.")
2825
 
+
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.",
2830
 
+                          metavar="NUMBER")
2831
 
+
2832
 
+        parser.add_option_group(select)
2833
 
+
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, 
2850
 
+                         dest="display",
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"
2869
 
+                          " revision")
2870
 
+        display.add_option("-m", "--merges", action="store_true", 
2871
 
+                          dest="merges", help="Show the revisions that were"
2872
 
+                          " merged")
2873
 
+        parser.add_option_group(display)
2874
 
+        return parser 
2875
 
+    def help(self, parser=None):
2876
 
+        """Attempt to explain the revisions command
2877
 
+        
2878
 
+        :param parser: If supplied, used to determine options
2879
 
+        """
2880
 
+        if parser==None:
2881
 
+            parser=self.get_parser()
2882
 
+        parser.print_help()
2883
 
+        print """List revisions.
2884
 
+        """
2885
 
+        help_tree_spec()
2886
 
+
2887
 
+
2888
 
+class Get(BaseCommand):
2889
 
+    """
2890
 
+    Retrieve a revision from the archive
2891
 
+    """
2892
 
+    def __init__(self):
2893
 
+        self.description="Retrieve a revision from the archive"
2894
 
+        self.parser=self.get_parser()
2895
 
+
2896
 
+
2897
 
+    def get_completer(self, arg, index):
2898
 
+        if index > 0:
2899
 
+            return None
2900
 
+        try:
2901
 
+            tree = arch.tree_root()
2902
 
+        except:
2903
 
+            tree = None
2904
 
+        return cmdutil.iter_revision_completions(arg, tree)
2905
 
+
2906
 
+
2907
 
+    def do_command(self, cmdargs):
2908
 
+        """
2909
 
+        Master function that perfoms the "get" command.
2910
 
+        """
2911
 
+        (options, args) = self.parser.parse_args(cmdargs)
2912
 
+        if len(args) < 1:
2913
 
+            return self.help()            
2914
 
+        try:
2915
 
+            tree = arch.tree_root()
2916
 
+        except arch.errors.TreeRootError:
2917
 
+            tree = None
2918
 
+        
2919
 
+        arch_loc = None
2920
 
+        try:
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)
2925
 
+        if len(args) > 1:
2926
 
+            directory = args[1]
2927
 
+        else:
2928
 
+            directory = str(revision.nonarch)
2929
 
+        if os.path.exists(directory):
2930
 
+            raise DirectoryExists(directory)
2931
 
+        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
2932
 
+        try:
2933
 
+            cmdutil.ensure_revision_exists(revision)
2934
 
+        except cmdutil.NoSuchRevision, e:
2935
 
+            raise CommandFailedWrapper(e)
2936
 
+
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)
2942
 
+
2943
 
+    def get_parser(self):
2944
 
+        """
2945
 
+        Returns the options parser to use for the "get" command.
2946
 
+
2947
 
+        :rtype: cmdutil.CmdOptionParser
2948
 
+        """
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")
2956
 
+
2957
 
+        return parser 
2958
 
+
2959
 
+    def help(self, parser=None):
2960
 
+        """
2961
 
+        Prints a help message.
2962
 
+
2963
 
+        :param parser: If supplied, the parser to use for generating help.  If \
2964
 
+        not supplied, it is retrieved.
2965
 
+        :type parser: cmdutil.CmdOptionParser
2966
 
+        """
2967
 
+        if parser==None:
2968
 
+            parser=self.get_parser()
2969
 
+        parser.print_help()
2970
 
+        print """
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.
2973
 
+        """
2974
 
+        help_tree_spec()
2975
 
+        return
2976
 
+
2977
 
+class PromptCmd(cmd.Cmd):
2978
 
+    def __init__(self):
2979
 
+        cmd.Cmd.__init__(self)
2980
 
+        self.prompt = "Fai> "
2981
 
+        try:
2982
 
+            self.tree = arch.tree_root()
2983
 
+        except:
2984
 
+            self.tree = None
2985
 
+        self.set_title()
2986
 
+        self.set_prompt()
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()
2995
 
+
2996
 
+    def write_history(self):
2997
 
+        readline.write_history_file(self.history_file)
2998
 
+
2999
 
+    def do_quit(self, args):
3000
 
+        self.write_history()
3001
 
+        sys.exit(0)
3002
 
+
3003
 
+    def do_exit(self, args):
3004
 
+        self.do_quit(args)
3005
 
+
3006
 
+    def do_EOF(self, args):
3007
 
+        print
3008
 
+        self.do_quit(args)
3009
 
+
3010
 
+    def postcmd(self, line, bar):
3011
 
+        self.set_title()
3012
 
+        self.set_prompt()
3013
 
+
3014
 
+    def set_prompt(self):
3015
 
+        if self.tree is not None:
3016
 
+            try:
3017
 
+                prompt = pylon.alias_or_version(self.tree.tree_version, 
3018
 
+                                                self.tree, 
3019
 
+                                                full=False)
3020
 
+                if prompt is not None:
3021
 
+                    prompt = " " + prompt
3022
 
+            except:
3023
 
+                prompt = ""
3024
 
+        else:
3025
 
+            prompt = ""
3026
 
+        self.prompt = "Fai%s> " % prompt
3027
 
+
3028
 
+    def set_title(self, command=None):
3029
 
+        try:
3030
 
+            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
3031
 
+                                             full=False)
3032
 
+        except:
3033
 
+            version = "[no version]"
3034
 
+        if command is None:
3035
 
+            command = ""
3036
 
+        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
3037
 
+
3038
 
+    def do_cd(self, line):
3039
 
+        if line == "":
3040
 
+            line = "~"
3041
 
+        line = os.path.expanduser(line)
3042
 
+        if os.path.isabs(line):
3043
 
+            newcwd = line
3044
 
+        else:
3045
 
+            newcwd = self.cwd+'/'+line
3046
 
+        newcwd = os.path.normpath(newcwd)
3047
 
+        try:
3048
 
+            os.chdir(newcwd)
3049
 
+            self.cwd = newcwd
3050
 
+        except Exception, e:
3051
 
+            print e
3052
 
+        try:
3053
 
+            self.tree = arch.tree_root()
3054
 
+        except:
3055
 
+            self.tree = None
3056
 
+
3057
 
+    def do_help(self, line):
3058
 
+        Help()(line)
3059
 
+
3060
 
+    def default(self, line):
3061
 
+        args = line.split()
3062
 
+        if find_command(args[0]):
3063
 
+            try:
3064
 
+                find_command(args[0]).do_command(args[1:])
3065
 
+            except cmdutil.BadCommandOption, e:
3066
 
+                print e
3067
 
+            except cmdutil.GetHelp, e:
3068
 
+                find_command(args[0]).help()
3069
 
+            except CommandFailed, e:
3070
 
+                print e
3071
 
+            except arch.errors.ArchiveNotRegistered, e:
3072
 
+                print 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:
3078
 
+                print e
3079
 
+            except cmdutil.CantDetermineRevision, e:
3080
 
+                print e
3081
 
+            except Exception, e:
3082
 
+                print "Unhandled error:\n%s" % errors.exception_str(e)
3083
 
+
3084
 
+        elif suggestions.has_key(args[0]):
3085
 
+            print suggestions[args[0]]
3086
 
+
3087
 
+        elif self.fake_aba.is_command(args[0]):
3088
 
+            tree = None
3089
 
+            try:
3090
 
+                tree = arch.tree_root()
3091
 
+            except arch.errors.TreeRootError:
3092
 
+                pass
3093
 
+            cmd = self.fake_aba.is_command(args[0])
3094
 
+            try:
3095
 
+                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
3096
 
+            except KeyboardInterrupt, e:
3097
 
+                print "Interrupted"
3098
 
+
3099
 
+        elif options.tla_fallthrough and args[0] != "rm" and \
3100
 
+            cmdutil.is_tla_command(args[0]):
3101
 
+            try:
3102
 
+                tree = None
3103
 
+                try:
3104
 
+                    tree = arch.tree_root()
3105
 
+                except arch.errors.TreeRootError:
3106
 
+                    pass
3107
 
+                args = cmdutil.expand_prefix_alias(args, tree)
3108
 
+                arch.util.exec_safe('tla', args, stderr=sys.stderr,
3109
 
+                expected=(0, 1))
3110
 
+            except arch.util.ExecProblem, e:
3111
 
+                pass
3112
 
+            except KeyboardInterrupt, e:
3113
 
+                print "Interrupted"
3114
 
+        else:
3115
 
+            try:
3116
 
+                try:
3117
 
+                    tree = arch.tree_root()
3118
 
+                except arch.errors.TreeRootError:
3119
 
+                    tree = None
3120
 
+                args=line.split()
3121
 
+                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
3122
 
+            except KeyboardInterrupt, e:
3123
 
+                print "Interrupted"
3124
 
+
3125
 
+    def completenames(self, text, line, begidx, endidx):
3126
 
+        completions = []
3127
 
+        iter = iter_command_names(self.fake_aba)
3128
 
+        try:
3129
 
+            if len(line) > 0:
3130
 
+                arg = line.split()[-1]
3131
 
+            else:
3132
 
+                arg = ""
3133
 
+            iter = cmdutil.iter_munged_completions(iter, arg, text)
3134
 
+        except Exception, e:
3135
 
+            print e
3136
 
+        return list(iter)
3137
 
+
3138
 
+    def completedefault(self, text, line, begidx, endidx):
3139
 
+        """Perform completion for native commands.
3140
 
+        
3141
 
+        :param text: The text to complete
3142
 
+        :type text: str
3143
 
+        :param line: The entire line to complete
3144
 
+        :type line: str
3145
 
+        :param begidx: The start of the text in the line
3146
 
+        :type begidx: int
3147
 
+        :param endidx: The end of the text in the line
3148
 
+        :type endidx: int
3149
 
+        """
3150
 
+        try:
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)
3158
 
+                if len(args) > 0:
3159
 
+                    arg = args.split()[-1]
3160
 
+                else:
3161
 
+                    arg = ""
3162
 
+                if arg.startswith("-"):
3163
 
+                    return list(cmdutil.iter_munged_completions(iter, arg, 
3164
 
+                                                                text))
3165
 
+                else:
3166
 
+                    return list(cmdutil.iter_munged_completions(
3167
 
+                        cmdutil.iter_file_completions(arg), arg, text))
3168
 
+
3169
 
+
3170
 
+            elif cmd == "cd":
3171
 
+                if len(args) > 0:
3172
 
+                    arg = args.split()[-1]
3173
 
+                else:
3174
 
+                    arg = ""
3175
 
+                iter = cmdutil.iter_dir_completions(arg)
3176
 
+                iter = cmdutil.iter_munged_completions(iter, arg, text)
3177
 
+                return list(iter)
3178
 
+            elif len(args)>0:
3179
 
+                arg = args.split()[-1]
3180
 
+                iter = cmdutil.iter_file_completions(arg)
3181
 
+                return list(cmdutil.iter_munged_completions(iter, arg, text))
3182
 
+            else:
3183
 
+                return self.completenames(text, line, begidx, endidx)
3184
 
+        except Exception, e:
3185
 
+            print e
3186
 
+
3187
 
+
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)):
3193
 
+            yield entry
3194
 
+
3195
 
+
3196
 
+def iter_source_file_completions(tree, arg):
3197
 
+    treepath = arch_compound.tree_cwd(tree)
3198
 
+    if len(treepath) > 0:
3199
 
+        dirs = [treepath]
3200
 
+    else:
3201
 
+        dirs = None
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:
3205
 
+            yield file
3206
 
+
3207
 
+
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):
3212
 
+        yield file.name 
3213
 
+
3214
 
+
3215
 
+def iter_untagged_completions(tree, arg):
3216
 
+    """Generate an iterator for all visible untagged files that match arg.
3217
 
+
3218
 
+    :param tree: The tree to look for untagged files in
3219
 
+    :type tree: `arch.WorkingTree`
3220
 
+    :param arg: The argument to match
3221
 
+    :type arg: str
3222
 
+    :return: An iterator of all matching untagged files
3223
 
+    :rtype: iterator of str
3224
 
+    """
3225
 
+    treepath = arch_compound.tree_cwd(tree)
3226
 
+    if len(treepath) > 0:
3227
 
+        dirs = [treepath]
3228
 
+    else:
3229
 
+        dirs = None
3230
 
+
3231
 
+    for file in iter_untagged(tree, dirs):
3232
 
+        file = file_completion_match(file, treepath, arg)
3233
 
+        if file is not None:
3234
 
+            yield file
3235
 
+
3236
 
+
3237
 
+def file_completion_match(file, treepath, arg):
3238
 
+    """Determines whether a file within an arch tree matches the argument.
3239
 
+
3240
 
+    :param file: The rooted filename
3241
 
+    :type file: str
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
3246
 
+    :rtype: str
3247
 
+    """
3248
 
+    if not file.startswith(treepath):
3249
 
+        return None
3250
 
+    if treepath != "":
3251
 
+        file = file[len(treepath)+1:]
3252
 
+
3253
 
+    if not file.startswith(arg):
3254
 
+        return None 
3255
 
+    if os.path.isdir(file):
3256
 
+        file += '/'
3257
 
+    return file
3258
 
+
3259
 
+def iter_modified_file_completions(tree, arg):
3260
 
+    """Returns a list of modified files that match the specified prefix.
3261
 
+
3262
 
+    :param tree: The current tree
3263
 
+    :type tree: `arch.WorkingTree`
3264
 
+    :param arg: The prefix to match
3265
 
+    :type arg: str
3266
 
+    """
3267
 
+    treepath = arch_compound.tree_cwd(tree)
3268
 
+    tmpdir = util.tmpdir()
3269
 
+    changeset = tmpdir+"/changeset"
3270
 
+    completions = []
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
3279
 
+
3280
 
+class Shell(BaseCommand):
3281
 
+    def __init__(self):
3282
 
+        self.description = "Runs Fai as a shell"
3283
 
+
3284
 
+    def do_command(self, cmdargs):
3285
 
+        if len(cmdargs)!=0:
3286
 
+            raise cmdutil.GetHelp
3287
 
+        prompt = PromptCmd()
3288
 
+        try:
3289
 
+            prompt.cmdloop()
3290
 
+        finally:
3291
 
+            prompt.write_history()
3292
 
+
3293
 
+class AddID(BaseCommand):
3294
 
+    """
3295
 
+    Adds an inventory id for the given file
3296
 
+    """
3297
 
+    def __init__(self):
3298
 
+        self.description="Add an inventory id for a given file"
3299
 
+
3300
 
+    def get_completer(self, arg, index):
3301
 
+        tree = arch.tree_root()
3302
 
+        return iter_untagged_completions(tree, arg)
3303
 
+
3304
 
+    def do_command(self, cmdargs):
3305
 
+        """
3306
 
+        Master function that perfoms the "revision" command.
3307
 
+        """
3308
 
+        parser=self.get_parser()
3309
 
+        (options, args) = parser.parse_args(cmdargs)
3310
 
+
3311
 
+        try:
3312
 
+            tree = arch.tree_root()
3313
 
+        except arch.errors.TreeRootError, e:
3314
 
+            raise pylon.errors.CommandFailedWrapper(e)
3315
 
+            
3316
 
+
3317
 
+        if (len(args) == 0) == (options.untagged == False):
3318
 
+            raise cmdutil.GetHelp
3319
 
+
3320
 
+       #if options.id and len(args) != 1:
3321
 
+       #    print "If --id is specified, only one file can be named."
3322
 
+       #    return
3323
 
+        
3324
 
+        method = tree.tagging_method
3325
 
+        
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
3331
 
+                    else:
3332
 
+                        print "add-id not supported for \"%s\" tagging method"\
3333
 
+                            % method 
3334
 
+                        return
3335
 
+        
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
3341
 
+                    else:
3342
 
+                        print "add-id not supported for \"%s\" tagging method"\
3343
 
+                            % method 
3344
 
+                        return
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" % \
3349
 
+                        method
3350
 
+                    return
3351
 
+        
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
3356
 
+                return
3357
 
+            else:
3358
 
+                options.id_type = method
3359
 
+        if options.untagged:
3360
 
+            args = None
3361
 
+        self.add_ids(tree, options.id_type, args)
3362
 
+
3363
 
+    def add_ids(self, tree, id_type, files=()):
3364
 
+        """Add inventory ids to files.
3365
 
+        
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
3372
 
+        """
3373
 
+
3374
 
+        untagged = (files is None)
3375
 
+        if untagged:
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:
3384
 
+                    try:
3385
 
+                        implicit = (id_type == "implicit")
3386
 
+                        cmdutil.add_tagline_or_explicit_id(file, False,
3387
 
+                                                           implicit)
3388
 
+                    except cmdutil.AlreadyTagged:
3389
 
+                        print "\"%s\" already has a tagline." % file
3390
 
+                    except cmdutil.NoCommentSyntax:
3391
 
+                        pass
3392
 
+            #do inventory after tagging until no untagged files are encountered
3393
 
+            if untagged:
3394
 
+                files = []
3395
 
+                for file in iter_untagged(tree, None):
3396
 
+                    if not file in previous_files:
3397
 
+                        files.append(file)
3398
 
+
3399
 
+            else:
3400
 
+                break
3401
 
+
3402
 
+    def get_parser(self):
3403
 
+        """
3404
 
+        Returns the options parser to use for the "revision" command.
3405
 
+
3406
 
+        :rtype: cmdutil.CmdOptionParser
3407
 
+        """
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")
3426
 
+        return parser 
3427
 
+
3428
 
+    def help(self, parser=None):
3429
 
+        """
3430
 
+        Prints a help message.
3431
 
+
3432
 
+        :param parser: If supplied, the parser to use for generating help.  If \
3433
 
+        not supplied, it is retrieved.
3434
 
+        :type parser: cmdutil.CmdOptionParser
3435
 
+        """
3436
 
+        if parser==None:
3437
 
+            parser=self.get_parser()
3438
 
+        parser.print_help()
3439
 
+        print """
3440
 
+Adds an inventory to the specified file(s) and directories.  If --untagged is
3441
 
+specified, adds inventory to all untagged files and directories.
3442
 
+        """
3443
 
+        return
3444
 
+
3445
 
+
3446
 
+class Merge(BaseCommand):
3447
 
+    """
3448
 
+    Merges changes from other versions into the current tree
3449
 
+    """
3450
 
+    def __init__(self):
3451
 
+        self.description="Merges changes from other versions"
3452
 
+        try:
3453
 
+            self.tree = arch.tree_root()
3454
 
+        except:
3455
 
+            self.tree = None
3456
 
+
3457
 
+
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)
3462
 
+
3463
 
+    def do_command(self, cmdargs):
3464
 
+        """
3465
 
+        Master function that perfoms the "merge" command.
3466
 
+        """
3467
 
+        parser=self.get_parser()
3468
 
+        (options, args) = parser.parse_args(cmdargs)
3469
 
+        if options.diff3:
3470
 
+            action="star-merge"
3471
 
+        else:
3472
 
+            action = options.action
3473
 
+        
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)
3478
 
+
3479
 
+        if len(args) > 0:
3480
 
+            revisions = []
3481
 
+            for arg in args:
3482
 
+                revisions.append(cmdutil.determine_revision_arch(self.tree, 
3483
 
+                                                                 arg))
3484
 
+            source = "from commandline"
3485
 
+        else:
3486
 
+            revisions = ancillary.iter_partner_revisions(self.tree, 
3487
 
+                                                         self.tree.tree_version)
3488
 
+            source = "from partner version"
3489
 
+        revisions = misc.rewind_iterator(revisions)
3490
 
+        try:
3491
 
+            revisions.next()
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"
3498
 
+                                            " source")
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:
3507
 
+                    continue
3508
 
+            elif action=="star-merge":
3509
 
+                try: 
3510
 
+                    self.star_merge(revision, options.diff3)
3511
 
+                except errors.MergeProblem, e:
3512
 
+                    break
3513
 
+            if cmdutil.has_changed(self.tree.tree_version):
3514
 
+                break
3515
 
+
3516
 
+    def star_merge(self, revision, diff3):
3517
 
+        """Perform a star-merge on the current tree.
3518
 
+        
3519
 
+        :param revision: The revision to use for the merge
3520
 
+        :type revision: `arch.Revision`
3521
 
+        :param diff3: If true, do a diff3 merge
3522
 
+        :type diff3: bool
3523
 
+        """
3524
 
+        try:
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:
3529
 
+                if e.proc.error:
3530
 
+                    print e.proc.error
3531
 
+                raise MergeProblem
3532
 
+            else:
3533
 
+                raise
3534
 
+
3535
 
+    def native_merge(self, other_revision, action):
3536
 
+        """Perform a native-merge on the current tree.
3537
 
+        
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
3541
 
+        """
3542
 
+        other_tree = arch_compound.find_or_make_local_revision(other_revision)
3543
 
+        try:
3544
 
+            if action == "native-merge":
3545
 
+                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
3546
 
+                                                         other_revision)
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" 
3555
 
+                                          % ancestor))
3556
 
+            return 0
3557
 
+        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
3558
 
+        for line in cmdutil.iter_apply_delta_filter(delta):
3559
 
+            cmdutil.colorize(line)
3560
 
+        return 1
3561
 
+
3562
 
+
3563
 
+
3564
 
+    def get_parser(self):
3565
 
+        """
3566
 
+        Returns the options parser to use for the "merge" command.
3567
 
+
3568
 
+        :rtype: cmdutil.CmdOptionParser
3569
 
+        """
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",
3576
 
+                          const="update")
3577
 
+        parser.add_option("--diff3", action="store_true", 
3578
 
+                         dest="diff3",  
3579
 
+                         help="Use diff3 for merge (implies star-merge)")
3580
 
+        return parser 
3581
 
+
3582
 
+    def help(self, parser=None):
3583
 
+        """
3584
 
+        Prints a help message.
3585
 
+
3586
 
+        :param parser: If supplied, the parser to use for generating help.  If \
3587
 
+        not supplied, it is retrieved.
3588
 
+        :type parser: cmdutil.CmdOptionParser
3589
 
+        """
3590
 
+        if parser==None:
3591
 
+            parser=self.get_parser()
3592
 
+        parser.print_help()
3593
 
+        print """
3594
 
+Performs a merge operation using the specified version.
3595
 
+        """
3596
 
+        return
3597
 
+
3598
 
+class ELog(BaseCommand):
3599
 
+    """
3600
 
+    Produces a raw patchlog and invokes the user's editor
3601
 
+    """
3602
 
+    def __init__(self):
3603
 
+        self.description="Edit a patchlog to commit"
3604
 
+        try:
3605
 
+            self.tree = arch.tree_root()
3606
 
+        except:
3607
 
+            self.tree = None
3608
 
+
3609
 
+
3610
 
+    def do_command(self, cmdargs):
3611
 
+        """
3612
 
+        Master function that perfoms the "elog" command.
3613
 
+        """
3614
 
+        parser=self.get_parser()
3615
 
+        (options, args) = parser.parse_args(cmdargs)
3616
 
+        if self.tree is None:
3617
 
+            raise arch.errors.TreeRootError
3618
 
+
3619
 
+        try:
3620
 
+            edit_log(self.tree, self.tree.tree_version)
3621
 
+        except pylon.errors.NoEditorSpecified, e:
3622
 
+            raise pylon.errors.CommandFailedWrapper(e)
3623
 
+
3624
 
+    def get_parser(self):
3625
 
+        """
3626
 
+        Returns the options parser to use for the "merge" command.
3627
 
+
3628
 
+        :rtype: cmdutil.CmdOptionParser
3629
 
+        """
3630
 
+        parser=cmdutil.CmdOptionParser("fai elog")
3631
 
+        return parser 
3632
 
+
3633
 
+
3634
 
+    def help(self, parser=None):
3635
 
+        """
3636
 
+        Invokes $EDITOR to produce a log for committing.
3637
 
+
3638
 
+        :param parser: If supplied, the parser to use for generating help.  If \
3639
 
+        not supplied, it is retrieved.
3640
 
+        :type parser: cmdutil.CmdOptionParser
3641
 
+        """
3642
 
+        if parser==None:
3643
 
+            parser=self.get_parser()
3644
 
+        parser.print_help()
3645
 
+        print """
3646
 
+Invokes $EDITOR to produce a log for committing.
3647
 
+        """
3648
 
+        return
3649
 
+
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
3653
 
+    
3654
 
+    :param tree: The tree to edit the log for
3655
 
+    :type tree: `arch.WorkingTree`
3656
 
+    """
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)
3665
 
+        log_is_new = True
3666
 
+        tmplog = log.name
3667
 
+        template = pylon.log_template_path(tree)
3668
 
+        if template:
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, 
3674
 
+                                         version)
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)
3680
 
+                else:
3681
 
+                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
3682
 
+                log.description += mergestuff
3683
 
+        log.save()
3684
 
+    try:
3685
 
+        cmdutil.invoke_editor(log.name)
3686
 
+    except:
3687
 
+        if log_is_new:
3688
 
+            os.remove(log.name)
3689
 
+        raise
3690
 
+
3691
 
+
3692
 
+class MirrorArchive(BaseCommand):
3693
 
+    """
3694
 
+    Updates a mirror from an archive
3695
 
+    """
3696
 
+    def __init__(self):
3697
 
+        self.description="Update a mirror from an archive"
3698
 
+
3699
 
+    def do_command(self, cmdargs):
3700
 
+        """
3701
 
+        Master function that perfoms the "revision" command.
3702
 
+        """
3703
 
+
3704
 
+        parser=self.get_parser()
3705
 
+        (options, args) = parser.parse_args(cmdargs)
3706
 
+        if len(args) > 1:
3707
 
+            raise GetHelp
3708
 
+        try:
3709
 
+            tree = arch.tree_root()
3710
 
+        except:
3711
 
+            tree = None
3712
 
+
3713
 
+        if len(args) == 0:
3714
 
+            if tree is not None:
3715
 
+                name = tree.tree_version()
3716
 
+        else:
3717
 
+            name = cmdutil.expand_alias(args[0], tree)
3718
 
+            name = arch.NameParser(name)
3719
 
+
3720
 
+        to_arch = name.get_archive()
3721
 
+        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
3722
 
+        limit = name.get_nonarch()
3723
 
+
3724
 
+        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
3725
 
+        for line in arch.chatter_classifier(iter):
3726
 
+            cmdutil.colorize(line)
3727
 
+
3728
 
+    def get_parser(self):
3729
 
+        """
3730
 
+        Returns the options parser to use for the "revision" command.
3731
 
+
3732
 
+        :rtype: cmdutil.CmdOptionParser
3733
 
+        """
3734
 
+        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
3735
 
+        return parser 
3736
 
+
3737
 
+    def help(self, parser=None):
3738
 
+        """
3739
 
+        Prints a help message.
3740
 
+
3741
 
+        :param parser: If supplied, the parser to use for generating help.  If \
3742
 
+        not supplied, it is retrieved.
3743
 
+        :type parser: cmdutil.CmdOptionParser
3744
 
+        """
3745
 
+        if parser==None:
3746
 
+            parser=self.get_parser()
3747
 
+        parser.print_help()
3748
 
+        print """
3749
 
+Updates a mirror from an archive.  If a branch, package, or version is
3750
 
+supplied, only changes under it are mirrored.
3751
 
+        """
3752
 
+        return
3753
 
+
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-
3760
 
+version.
3761
 
+
3762
 
+Use "alias" to list available (user and automatic) aliases."""
3763
 
+
3764
 
+auto_alias = [
3765
 
+"acur", 
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)",
3768
 
+"tcur",
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 \
3771
 
+used).""",
3772
 
+"tprev" , 
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" """,
3775
 
+"tanc" , 
3776
 
+"""(tree ancestor) The ancestor revision of the tree To specify an older \
3777
 
+revision, use a number, e.g. "tanc:4".""",
3778
 
+"tdate" , 
3779
 
+"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
3780
 
+"tmod" , 
3781
 
+""" (tree modified) The latest revision to modify a given file, e.g. \
3782
 
+"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
3783
 
+"ttag" , 
3784
 
+"""(tree tag) The revision that was tagged into the current tree revision, \
3785
 
+according to the tree""",
3786
 
+"tagcur", 
3787
 
+"""(tag current) The latest revision of the version that the current tree \
3788
 
+was tagged from.""",
3789
 
+"mergeanc" , 
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.""",
3792
 
+]
3793
 
+
3794
 
+
3795
 
+def is_auto_alias(name):
3796
 
+    """Determine whether a name is an auto alias name
3797
 
+
3798
 
+    :param name: the name to check
3799
 
+    :type name: str
3800
 
+    :return: True if the name is an auto alias, false if not
3801
 
+    :rtype: bool
3802
 
+    """
3803
 
+    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
3804
 
+
3805
 
+
3806
 
+def display_def(iter, wrap = 80):
3807
 
+    """Display a list of definitions
3808
 
+
3809
 
+    :param iter: iter of name, definition pairs
3810
 
+    :type iter: iter of (str, str)
3811
 
+    :param wrap: The width for text wrapping
3812
 
+    :type wrap: int
3813
 
+    """
3814
 
+    vals = list(iter)
3815
 
+    maxlen = 0
3816
 
+    for (key, value) in vals:
3817
 
+        if len(key) > maxlen:
3818
 
+            maxlen = len(key)
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)
3824
 
+
3825
 
+
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))
3831
 
+
3832
 
+class Inventory(BaseCommand):
3833
 
+    """List the status of files in the tree"""
3834
 
+    def __init__(self):
3835
 
+        self.description=self.__doc__
3836
 
+
3837
 
+    def do_command(self, cmdargs):
3838
 
+        """
3839
 
+        Master function that perfoms the "revision" command.
3840
 
+        """
3841
 
+
3842
 
+        parser=self.get_parser()
3843
 
+        (options, args) = parser.parse_args(cmdargs)
3844
 
+        tree = arch.tree_root()
3845
 
+        categories = []
3846
 
+
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)
3855
 
+
3856
 
+        if len(categories) == 1:
3857
 
+            show_leading = False
3858
 
+        else:
3859
 
+            show_leading = True
3860
 
+
3861
 
+        if len(categories) == 0:
3862
 
+            categories = None
3863
 
+
3864
 
+        if options.untagged:
3865
 
+            categories = arch_core.non_root
3866
 
+            show_leading = False
3867
 
+            tagged = False
3868
 
+        else:
3869
 
+            tagged = None
3870
 
+        
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,
3877
 
+                                      id = options.ids)
3878
 
+
3879
 
+    def get_parser(self):
3880
 
+        """
3881
 
+        Returns the options parser to use for the "revision" command.
3882
 
+
3883
 
+        :rtype: cmdutil.CmdOptionParser
3884
 
+        """
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")
3902
 
+        return parser 
3903
 
+
3904
 
+    def help(self, parser=None):
3905
 
+        """
3906
 
+        Prints a help message.
3907
 
+
3908
 
+        :param parser: If supplied, the parser to use for generating help.  If \
3909
 
+        not supplied, it is retrieved.
3910
 
+        :type parser: cmdutil.CmdOptionParser
3911
 
+        """
3912
 
+        if parser==None:
3913
 
+            parser=self.get_parser()
3914
 
+        parser.print_help()
3915
 
+        print """
3916
 
+Lists the status of files in the archive:
3917
 
+S source
3918
 
+P precious
3919
 
+B backup
3920
 
+J junk
3921
 
+U unrecognized
3922
 
+T tree root
3923
 
+? untagged-source
3924
 
+Leading letter are not displayed if only one kind of file is shown
3925
 
+        """
3926
 
+        return
3927
 
+
3928
 
+
3929
 
+class Alias(BaseCommand):
3930
 
+    """List or adjust aliases"""
3931
 
+    def __init__(self):
3932
 
+        self.description=self.__doc__
3933
 
+
3934
 
+    def get_completer(self, arg, index):
3935
 
+        if index > 2:
3936
 
+            return ()
3937
 
+        try:
3938
 
+            self.tree = arch.tree_root()
3939
 
+        except:
3940
 
+            self.tree = None
3941
 
+
3942
 
+        if index == 0:
3943
 
+            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
3944
 
+        elif index == 1:
3945
 
+            return cmdutil.iter_revision_completions(arg, self.tree)
3946
 
+
3947
 
+
3948
 
+    def do_command(self, cmdargs):
3949
 
+        """
3950
 
+        Master function that perfoms the "revision" command.
3951
 
+        """
3952
 
+
3953
 
+        parser=self.get_parser()
3954
 
+        (options, args) = parser.parse_args(cmdargs)
3955
 
+        try:
3956
 
+            self.tree =  arch.tree_root()
3957
 
+        except:
3958
 
+            self.tree = None
3959
 
+
3960
 
+
3961
 
+        try:
3962
 
+            options.action(args, options)
3963
 
+        except cmdutil.ForbiddenAliasSyntax, e:
3964
 
+            raise CommandFailedWrapper(e)
3965
 
+
3966
 
+    def no_prefix(self, alias):
3967
 
+        if alias.startswith("^"):
3968
 
+            alias = alias[1:]
3969
 
+        return alias
3970
 
+        
3971
 
+    def arg_dispatch(self, args, options):
3972
 
+        """Add, modify, or list aliases, depending on number of arguments
3973
 
+
3974
 
+        :param args: The list of commandline arguments
3975
 
+        :type args: list of str
3976
 
+        :param options: The commandline options
3977
 
+        """
3978
 
+        if len(args) == 0:
3979
 
+            help_aliases(self.tree)
3980
 
+            return
3981
 
+        else:
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)
3987
 
+            else:
3988
 
+                raise cmdutil.GetHelp
3989
 
+
3990
 
+    def print_alias(self, alias):
3991
 
+        answer = None
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:
3997
 
+                answer = pair[1]
3998
 
+        if answer is not None:
3999
 
+            print answer
4000
 
+        else:
4001
 
+            print "The alias %s is not assigned." % alias
4002
 
+
4003
 
+    def add(self, alias, expansion, options):
4004
 
+        """Add or modify aliases
4005
 
+
4006
 
+        :param alias: The alias name to create/modify
4007
 
+        :type alias: str
4008
 
+        :param expansion: The expansion to assign to the alias name
4009
 
+        :type expansion: str
4010
 
+        :param options: The commandline options
4011
 
+        """
4012
 
+        if is_auto_alias(alias):
4013
 
+            raise IsAutoAlias(alias)
4014
 
+        newlist = ""
4015
 
+        written = False
4016
 
+        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
4017
 
+            self.tree))
4018
 
+        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
4019
 
+
4020
 
+        for pair in self.get_iterator(options):
4021
 
+            if pair[0] != alias:
4022
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
4023
 
+            elif not written:
4024
 
+                newlist+=new_line
4025
 
+                written = True
4026
 
+        if not written:
4027
 
+            newlist+=new_line
4028
 
+        self.write_aliases(newlist, options)
4029
 
+            
4030
 
+    def delete(self, args, options):
4031
 
+        """Delete the specified alias
4032
 
+
4033
 
+        :param args: The list of arguments
4034
 
+        :type args: list of str
4035
 
+        :param options: The commandline options
4036
 
+        """
4037
 
+        deleted = False
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)
4043
 
+        newlist = ""
4044
 
+        for pair in self.get_iterator(options):
4045
 
+            if pair[0] != alias:
4046
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
4047
 
+            else:
4048
 
+                deleted = True
4049
 
+        if not deleted:
4050
 
+            raise errors.NoSuchAlias(alias)
4051
 
+        self.write_aliases(newlist, options)
4052
 
+
4053
 
+    def get_alias_file(self, options):
4054
 
+        """Return the name of the alias file to use
4055
 
+
4056
 
+        :param options: The commandline options
4057
 
+        """
4058
 
+        if options.tree:
4059
 
+            if self.tree is None:
4060
 
+                self.tree == arch.tree_root()
4061
 
+            return str(self.tree)+"/{arch}/+aliases"
4062
 
+        else:
4063
 
+            return "~/.aba/aliases"
4064
 
+
4065
 
+    def get_iterator(self, options):
4066
 
+        """Return the alias iterator to use
4067
 
+
4068
 
+        :param options: The commandline options
4069
 
+        """
4070
 
+        return ancillary.iter_alias(self.get_alias_file(options))
4071
 
+
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
4077
 
+        """
4078
 
+        filename = os.path.expanduser(self.get_alias_file(options))
4079
 
+        file = util.NewFileVersion(filename)
4080
 
+        file.write(newlist)
4081
 
+        file.commit()
4082
 
+
4083
 
+
4084
 
+    def get_parser(self):
4085
 
+        """
4086
 
+        Returns the options parser to use for the "alias" command.
4087
 
+
4088
 
+        :rtype: cmdutil.CmdOptionParser
4089
 
+        """
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)
4096
 
+        return parser 
4097
 
+
4098
 
+    def help(self, parser=None):
4099
 
+        """
4100
 
+        Prints a help message.
4101
 
+
4102
 
+        :param parser: If supplied, the parser to use for generating help.  If \
4103
 
+        not supplied, it is retrieved.
4104
 
+        :type parser: cmdutil.CmdOptionParser
4105
 
+        """
4106
 
+        if parser==None:
4107
 
+            parser=self.get_parser()
4108
 
+        parser.print_help()
4109
 
+        print """
4110
 
+Lists current aliases or modifies the list of aliases.
4111
 
+
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.
4115
 
+
4116
 
+You can create aliases that refer to any fully-qualified part of the
4117
 
+Arch namespace, e.g. 
4118
 
+archive, 
4119
 
+archive/category, 
4120
 
+archive/category--branch, 
4121
 
+archive/category--branch--version (my favourite)
4122
 
+archive/category--branch--version--patchlevel
4123
 
+
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).
4127
 
+"""
4128
 
+
4129
 
+
4130
 
+class RequestMerge(BaseCommand):
4131
 
+    """Submit a merge request to Bug Goo"""
4132
 
+    def __init__(self):
4133
 
+        self.description=self.__doc__
4134
 
+
4135
 
+    def do_command(self, cmdargs):
4136
 
+        """Submit a merge request
4137
 
+
4138
 
+        :param cmdargs: The commandline arguments
4139
 
+        :type cmdargs: list of str
4140
 
+        """
4141
 
+        parser = self.get_parser()
4142
 
+        (options, args) = parser.parse_args(cmdargs)
4143
 
+        try:
4144
 
+            cmdutil.find_editor()
4145
 
+        except pylon.errors.NoEditorSpecified, e:
4146
 
+            raise pylon.errors.CommandFailedWrapper(e)
4147
 
+        try:
4148
 
+            self.tree=arch.tree_root()
4149
 
+        except:
4150
 
+            self.tree=None
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"
4159
 
+
4160
 
+    def make_headers(self, base, revisions):
4161
 
+        """Produce email and Bug Goo header strings
4162
 
+
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
4168
 
+        :rtype: str
4169
 
+        """
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
4174
 
+        else:
4175
 
+            headers += "Subject: [MERGE REQUEST]\n"
4176
 
+        headers += "\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"
4181
 
+        return headers
4182
 
+
4183
 
+    def make_summary(self, logs):
4184
 
+        """Generate a summary of merges
4185
 
+
4186
 
+        :param logs: the patchlogs that were directly added by the merges
4187
 
+        :type logs: list of `arch.Patchlog`
4188
 
+        :return: the summary
4189
 
+        :rtype: str
4190
 
+        """ 
4191
 
+        summary = ""
4192
 
+        for log in logs:
4193
 
+            summary+=str(log.revision)+"\n"
4194
 
+            summary+=log.summary+"\n"
4195
 
+            if log.description.strip():
4196
 
+                summary+=log.description.strip('\n')+"\n\n"
4197
 
+        return summary
4198
 
+
4199
 
+    def revision_specs(self, args):
4200
 
+        """Determine the base and merge revisions from tree and arguments.
4201
 
+
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`
4206
 
+        """
4207
 
+        if len(args) > 0:
4208
 
+            target_revision = cmdutil.determine_revision_arch(self.tree, 
4209
 
+                                                              args[0])
4210
 
+        else:
4211
 
+            target_revision = arch_compound.tree_latest(self.tree)
4212
 
+        if len(args) > 1:
4213
 
+            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
4214
 
+                       self.tree, f)) for f in args[1:] ]
4215
 
+        else:
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, 
4220
 
+                                                 False)
4221
 
+            merges = [f for f in cmdutil.direct_merges(merge_iter)]
4222
 
+        return (target_revision, merges)
4223
 
+
4224
 
+    def edit_message(self, message):
4225
 
+        """Edit an email message in the user's standard editor
4226
 
+
4227
 
+        :param message: The message to edit
4228
 
+        :type message: str
4229
 
+        :return: the path of the edited message
4230
 
+        :rtype: str
4231
 
+        """
4232
 
+        if self.tree is None:
4233
 
+            path = os.get_cwd()
4234
 
+        else:
4235
 
+            path = self.tree
4236
 
+        path += "/,merge-request"
4237
 
+        file = open(path, 'w')
4238
 
+        file.write(message)
4239
 
+        file.flush()
4240
 
+        cmdutil.invoke_editor(path)
4241
 
+        return path
4242
 
+
4243
 
+    def tidy_message(self, path):
4244
 
+        """Validate and clean up message.
4245
 
+
4246
 
+        :param path: The path to the message to clean up
4247
 
+        :type path: str
4248
 
+        :return: The parsed message
4249
 
+        :rtype: `email.Message`
4250
 
+        """
4251
 
+        mail = email.message_from_file(open(path))
4252
 
+        if mail["Subject"].strip() == "[MERGE REQUEST]":
4253
 
+            raise BlandSubject
4254
 
+        
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())
4260
 
+        return mail
4261
 
+
4262
 
+    def send_message(self, message):
4263
 
+        """Send a message, using its headers to address it.
4264
 
+
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())
4269
 
+        server.quit()
4270
 
+
4271
 
+    def help(self, parser=None):
4272
 
+        """Print a usage message
4273
 
+
4274
 
+        :param parser: The options parser to use
4275
 
+        :type parser: `cmdutil.CmdOptionParser`
4276
 
+        """
4277
 
+        if parser is None:
4278
 
+            parser = self.get_parser()
4279
 
+        parser.print_help()
4280
 
+        print """
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.
4284
 
+
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
4288
 
+revisions.
4289
 
+"""
4290
 
+
4291
 
+    def get_parser(self):
4292
 
+        """Produce a commandline parser for this command.
4293
 
+
4294
 
+        :rtype: `cmdutil.CmdOptionParser`
4295
 
+        """
4296
 
+        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
4297
 
+        return parser
4298
 
+
4299
 
+commands = { 
4300
 
+'changes' : Changes,
4301
 
+'help' : Help,
4302
 
+'update': Update,
4303
 
+'apply-changes':ApplyChanges,
4304
 
+'cat-log': CatLog,
4305
 
+'commit': Commit,
4306
 
+'revision': Revision,
4307
 
+'revisions': Revisions,
4308
 
+'get': Get,
4309
 
+'revert': Revert,
4310
 
+'shell': Shell,
4311
 
+'add-id': AddID,
4312
 
+'merge': Merge,
4313
 
+'elog': ELog,
4314
 
+'mirror-archive': MirrorArchive,
4315
 
+'ninventory': Inventory,
4316
 
+'alias' : Alias,
4317
 
+'request-merge': RequestMerge,
4318
 
+}
4319
 
+
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)
4325
 
+    return module
4326
 
+
4327
 
+def plugin(mod_name):
4328
 
+    module = my_import(mod_name)
4329
 
+    module.add_command(commands)
4330
 
+
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])
4334
 
+
4335
 
+suggestions = {
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"
4358
 
+}
4359
 
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
4360
 
 
4361
 
*** added file 'testdata/orig'
4362
 
--- /dev/null 
4363
 
+++ testdata/orig 
4364
 
@@ -0,0 +1,2789 @@
4365
 
+# Copyright (C) 2004 Aaron Bentley
4366
 
+# <aaron.bentley@utoronto.ca>
4367
 
+#
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.
4372
 
+#
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.
4377
 
+#
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
4381
 
+
4382
 
+import sys
4383
 
+import arch
4384
 
+import arch.util
4385
 
+import arch.arch
4386
 
+import abacmds
4387
 
+import cmdutil
4388
 
+import shutil
4389
 
+import os
4390
 
+import options
4391
 
+import paths 
4392
 
+import time
4393
 
+import cmd
4394
 
+import readline
4395
 
+import re
4396
 
+import string
4397
 
+import arch_core
4398
 
+from errors import *
4399
 
+import errors
4400
 
+import terminal
4401
 
+import ancillary
4402
 
+import misc
4403
 
+import email
4404
 
+import smtplib
4405
 
+
4406
 
+__docformat__ = "restructuredtext"
4407
 
+__doc__ = "Implementation of user (sub) commands"
4408
 
+commands = {}
4409
 
+
4410
 
+def find_command(cmd):
4411
 
+    """
4412
 
+    Return an instance of a command type.  Return None if the type isn't
4413
 
+    registered.
4414
 
+
4415
 
+    :param cmd: the name of the command to look for
4416
 
+    :type cmd: the type of the command
4417
 
+    """
4418
 
+    if commands.has_key(cmd):
4419
 
+        return commands[cmd]()
4420
 
+    else:
4421
 
+        return None
4422
 
+
4423
 
+class BaseCommand:
4424
 
+    def __call__(self, cmdline):
4425
 
+        try:
4426
 
+            self.do_command(cmdline.split())
4427
 
+        except cmdutil.GetHelp, e:
4428
 
+            self.help()
4429
 
+        except Exception, e:
4430
 
+            print e
4431
 
+
4432
 
+    def get_completer(index):
4433
 
+        return None
4434
 
+
4435
 
+    def complete(self, args, text):
4436
 
+        """
4437
 
+        Returns a list of possible completions for the given text.
4438
 
+
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])
4442
 
+        :type text: str
4443
 
+        :rtype: list of str
4444
 
+        """
4445
 
+        matches = []
4446
 
+        candidates = None
4447
 
+
4448
 
+        if len(args) > 0: 
4449
 
+            realtext = args[-1]
4450
 
+        else:
4451
 
+            realtext = ""
4452
 
+
4453
 
+        try:
4454
 
+            parser=self.get_parser()
4455
 
+            if realtext.startswith('-'):
4456
 
+                candidates = parser.iter_options()
4457
 
+            else:
4458
 
+                (options, parsed_args) = parser.parse_args(args)
4459
 
+
4460
 
+                if len (parsed_args) > 0:
4461
 
+                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
4462
 
+                else:
4463
 
+                    candidates = self.get_completer("", 0)
4464
 
+        except:
4465
 
+            pass
4466
 
+        if candidates is None:
4467
 
+            return
4468
 
+        for candidate in candidates:
4469
 
+            candidate = str(candidate)
4470
 
+            if candidate.startswith(realtext):
4471
 
+                matches.append(candidate[len(realtext)- len(text):])
4472
 
+        return matches
4473
 
+
4474
 
+
4475
 
+class Help(BaseCommand):
4476
 
+    """
4477
 
+    Lists commands, prints help messages.
4478
 
+    """
4479
 
+    def __init__(self):
4480
 
+        self.description="Prints help mesages"
4481
 
+        self.parser = None
4482
 
+
4483
 
+    def do_command(self, cmdargs):
4484
 
+        """
4485
 
+        Prints a help message.
4486
 
+        """
4487
 
+        options, args = self.get_parser().parse_args(cmdargs)
4488
 
+        if len(args) > 1:
4489
 
+            raise cmdutil.GetHelp
4490
 
+
4491
 
+        if options.native or options.suggestions or options.external:
4492
 
+            native = options.native
4493
 
+            suggestions = options.suggestions
4494
 
+            external = options.external
4495
 
+        else:
4496
 
+            native = True
4497
 
+            suggestions = False
4498
 
+            external = True
4499
 
+        
4500
 
+        if len(args) == 0:
4501
 
+            self.list_commands(native, suggestions, external)
4502
 
+            return
4503
 
+        elif len(args) == 1:
4504
 
+            command_help(args[0])
4505
 
+            return
4506
 
+
4507
 
+    def help(self):
4508
 
+        self.get_parser().print_help()
4509
 
+        print """
4510
 
+If no command is specified, commands are listed.  If a command is
4511
 
+specified, help for that command is listed.
4512
 
+        """
4513
 
+
4514
 
+    def get_parser(self):
4515
 
+        """
4516
 
+        Returns the options parser to use for the "revision" command.
4517
 
+
4518
 
+        :rtype: cmdutil.CmdOptionParser
4519
 
+        """
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
4530
 
+        return parser 
4531
 
+      
4532
 
+    def list_commands(self, native=True, suggest=False, external=True):
4533
 
+        """
4534
 
+        Lists supported commands.
4535
 
+
4536
 
+        :param native: list native, python-based commands
4537
 
+        :type native: bool
4538
 
+        :param external: list external aba-style commands
4539
 
+        :type external: bool
4540
 
+        """
4541
 
+        if native:
4542
 
+            print "Native Fai commands"
4543
 
+            keys=commands.keys()
4544
 
+            keys.sort()
4545
 
+            for k in keys:
4546
 
+                space=""
4547
 
+                for i in range(28-len(k)):
4548
 
+                    space+=" "
4549
 
+                print space+k+" : "+commands[k]().description
4550
 
+            print
4551
 
+        if suggest:
4552
 
+            print "Unavailable commands and suggested alternatives"
4553
 
+            key_list = suggestions.keys()
4554
 
+            key_list.sort()
4555
 
+            for key in key_list:
4556
 
+                print "%28s : %s" % (key, suggestions[key])
4557
 
+            print
4558
 
+        if external:
4559
 
+            fake_aba = abacmds.AbaCmds()
4560
 
+            if (fake_aba.abadir == ""):
4561
 
+                return
4562
 
+            print "External commands"
4563
 
+            fake_aba.list_commands()
4564
 
+            print
4565
 
+        if not suggest:
4566
 
+            print "Use help --suggest to list alternatives to tla and aba"\
4567
 
+                " commands."
4568
 
+        if options.tla_fallthrough and (native or external):
4569
 
+            print "Fai also supports tla commands."
4570
 
+
4571
 
+def command_help(cmd):
4572
 
+    """
4573
 
+    Prints help for a command.
4574
 
+
4575
 
+    :param cmd: The name of the command to print help for
4576
 
+    :type cmd: str
4577
 
+    """
4578
 
+    fake_aba = abacmds.AbaCmds()
4579
 
+    cmdobj = find_command(cmd)
4580
 
+    if cmdobj != None:
4581
 
+        cmdobj.help()
4582
 
+    elif suggestions.has_key(cmd):
4583
 
+        print "Not available\n" + suggestions[cmd]
4584
 
+    else:
4585
 
+        abacmd = fake_aba.is_command(cmd)
4586
 
+        if abacmd:
4587
 
+            abacmd.help()
4588
 
+        else:
4589
 
+            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
4590
 
+
4591
 
+
4592
 
+
4593
 
+class Changes(BaseCommand):
4594
 
+    """
4595
 
+    the "changes" command: lists differences between trees/revisions:
4596
 
+    """
4597
 
+    
4598
 
+    def __init__(self):
4599
 
+        self.description="Lists what files have changed in the project tree"
4600
 
+
4601
 
+    def get_completer(self, arg, index):
4602
 
+        if index > 1:
4603
 
+            return None
4604
 
+        try:
4605
 
+            tree = arch.tree_root()
4606
 
+        except:
4607
 
+            tree = None
4608
 
+        return cmdutil.iter_revision_completions(arg, tree)
4609
 
+    
4610
 
+    def parse_commandline(self, cmdline):
4611
 
+        """
4612
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
4613
 
+        
4614
 
+        :param cmdline: A list of arguments to parse
4615
 
+        :rtype: (options, Revision, Revision/WorkingTree)
4616
 
+        """
4617
 
+        parser=self.get_parser()
4618
 
+        (options, args) = parser.parse_args(cmdline)
4619
 
+        if len(args) > 2:
4620
 
+            raise cmdutil.GetHelp
4621
 
+
4622
 
+        tree=arch.tree_root()
4623
 
+        if len(args) == 0:
4624
 
+            a_spec = cmdutil.comp_revision(tree)
4625
 
+        else:
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)
4631
 
+        else:
4632
 
+            b_spec=tree
4633
 
+        return options, a_spec, b_spec
4634
 
+
4635
 
+    def do_command(self, cmdargs):
4636
 
+        """
4637
 
+        Master function that perfoms the "changes" command.
4638
 
+        """
4639
 
+        try:
4640
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs);
4641
 
+        except cmdutil.CantDetermineRevision, e:
4642
 
+            print e
4643
 
+            return
4644
 
+        except arch.errors.TreeRootError, e:
4645
 
+            print e
4646
 
+            return
4647
 
+        if options.changeset:
4648
 
+            changeset=options.changeset
4649
 
+            tmpdir = None
4650
 
+        else:
4651
 
+            tmpdir=cmdutil.tmpdir()
4652
 
+            changeset=tmpdir+"/changeset"
4653
 
+        try:
4654
 
+            delta=arch.iter_delta(a_spec, b_spec, changeset)
4655
 
+            try:
4656
 
+                for line in delta:
4657
 
+                    if cmdutil.chattermatch(line, "changeset:"):
4658
 
+                        pass
4659
 
+                    else:
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)
4665
 
+                else:
4666
 
+                    raise
4667
 
+            status=delta.status
4668
 
+            if status > 1:
4669
 
+                return
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()
4675
 
+                else:
4676
 
+                    b_dir = b_spec
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)
4681
 
+                else:
4682
 
+                    cmdutil.show_diffs(delta.changeset)
4683
 
+        finally:
4684
 
+            if tmpdir and (os.access(tmpdir, os.X_OK)):
4685
 
+                shutil.rmtree(tmpdir)
4686
 
+
4687
 
+    def get_parser(self):
4688
 
+        """
4689
 
+        Returns the options parser to use for the "changes" command.
4690
 
+
4691
 
+        :rtype: cmdutil.CmdOptionParser
4692
 
+        """
4693
 
+        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
4694
 
+                                       " [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")
4707
 
+
4708
 
+        return parser
4709
 
+
4710
 
+    def help(self, parser=None):
4711
 
+        """
4712
 
+        Prints a help message.
4713
 
+
4714
 
+        :param parser: If supplied, the parser to use for generating help.  If \
4715
 
+        not supplied, it is retrieved.
4716
 
+        :type parser: cmdutil.CmdOptionParser
4717
 
+        """
4718
 
+        if parser is None:
4719
 
+            parser=self.get_parser()
4720
 
+        parser.print_help()
4721
 
+        print """
4722
 
+Performs source-tree comparisons
4723
 
+
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.
4728
 
+        """
4729
 
+        help_tree_spec() 
4730
 
+        return
4731
 
+
4732
 
+
4733
 
+class ApplyChanges(BaseCommand):
4734
 
+    """
4735
 
+    Apply differences between two revisions to a tree
4736
 
+    """
4737
 
+    
4738
 
+    def __init__(self):
4739
 
+        self.description="Applies changes to a project tree"
4740
 
+    
4741
 
+    def get_completer(self, arg, index):
4742
 
+        if index > 1:
4743
 
+            return None
4744
 
+        try:
4745
 
+            tree = arch.tree_root()
4746
 
+        except:
4747
 
+            tree = None
4748
 
+        return cmdutil.iter_revision_completions(arg, tree)
4749
 
+
4750
 
+    def parse_commandline(self, cmdline, tree):
4751
 
+        """
4752
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
4753
 
+        
4754
 
+        :param cmdline: A list of arguments to parse
4755
 
+        :rtype: (options, Revision, Revision/WorkingTree)
4756
 
+        """
4757
 
+        parser=self.get_parser()
4758
 
+        (options, args) = parser.parse_args(cmdline)
4759
 
+        if len(args) != 2:
4760
 
+            raise cmdutil.GetHelp
4761
 
+
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
4767
 
+
4768
 
+    def do_command(self, cmdargs):
4769
 
+        """
4770
 
+        Master function that performs "apply-changes".
4771
 
+        """
4772
 
+        try:
4773
 
+            tree = arch.tree_root()
4774
 
+            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
4775
 
+        except cmdutil.CantDetermineRevision, e:
4776
 
+            print e
4777
 
+            return
4778
 
+        except arch.errors.TreeRootError, e:
4779
 
+            print e
4780
 
+            return
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)
4784
 
+
4785
 
+    def get_parser(self):
4786
 
+        """
4787
 
+        Returns the options parser to use for the "apply-changes" command.
4788
 
+
4789
 
+        :rtype: cmdutil.CmdOptionParser
4790
 
+        """
4791
 
+        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
4792
 
+                                       " 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")
4802
 
+        return parser
4803
 
+
4804
 
+    def help(self, parser=None):
4805
 
+        """
4806
 
+        Prints a help message.
4807
 
+
4808
 
+        :param parser: If supplied, the parser to use for generating help.  If \
4809
 
+        not supplied, it is retrieved.
4810
 
+        :type parser: cmdutil.CmdOptionParser
4811
 
+        """
4812
 
+        if parser is None:
4813
 
+            parser=self.get_parser()
4814
 
+        parser.print_help()
4815
 
+        print """
4816
 
+Applies changes to a project tree
4817
 
+
4818
 
+Compares two revisions and applies the difference between them to the current
4819
 
+tree.
4820
 
+        """
4821
 
+        help_tree_spec() 
4822
 
+        return
4823
 
+
4824
 
+class Update(BaseCommand):
4825
 
+    """
4826
 
+    Updates a project tree to a given revision, preserving un-committed hanges. 
4827
 
+    """
4828
 
+    
4829
 
+    def __init__(self):
4830
 
+        self.description="Apply the latest changes to the current directory"
4831
 
+
4832
 
+    def get_completer(self, arg, index):
4833
 
+        if index > 0:
4834
 
+            return None
4835
 
+        try:
4836
 
+            tree = arch.tree_root()
4837
 
+        except:
4838
 
+            tree = None
4839
 
+        return cmdutil.iter_revision_completions(arg, tree)
4840
 
+    
4841
 
+    def parse_commandline(self, cmdline, tree):
4842
 
+        """
4843
 
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
4844
 
+        
4845
 
+        :param cmdline: A list of arguments to parse
4846
 
+        :rtype: (options, Revision, Revision/WorkingTree)
4847
 
+        """
4848
 
+        parser=self.get_parser()
4849
 
+        (options, args) = parser.parse_args(cmdline)
4850
 
+        if len(args) > 2:
4851
 
+            raise cmdutil.GetHelp
4852
 
+
4853
 
+        spec=None
4854
 
+        if len(args)>0:
4855
 
+            spec=args[0]
4856
 
+        revision=cmdutil.determine_revision_arch(tree, spec)
4857
 
+        cmdutil.ensure_archive_registered(revision.archive)
4858
 
+
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)
4866
 
+
4867
 
+                revision=cmdutil.determine_revision_arch(tree, spec)
4868
 
+
4869
 
+        return options, revision 
4870
 
+
4871
 
+    def do_command(self, cmdargs):
4872
 
+        """
4873
 
+        Master function that perfoms the "update" command.
4874
 
+        """
4875
 
+        tree=arch.tree_root()
4876
 
+        try:
4877
 
+            options, to_revision = self.parse_commandline(cmdargs, tree);
4878
 
+        except cmdutil.CantDetermineRevision, e:
4879
 
+            print e
4880
 
+            return
4881
 
+        except arch.errors.TreeRootError, e:
4882
 
+            print e
4883
 
+            return
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)+"."
4887
 
+            return
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
4896
 
+
4897
 
+    def get_parser(self):
4898
 
+        """
4899
 
+        Returns the options parser to use for the "update" command.
4900
 
+
4901
 
+        :rtype: cmdutil.CmdOptionParser
4902
 
+        """
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")
4911
 
+        return parser
4912
 
+
4913
 
+    def help(self, parser=None):
4914
 
+        """
4915
 
+        Prints a help message.
4916
 
+
4917
 
+        :param parser: If supplied, the parser to use for generating help.  If \
4918
 
+        not supplied, it is retrieved.
4919
 
+        :type parser: cmdutil.CmdOptionParser
4920
 
+        """
4921
 
+        if parser is None:
4922
 
+            parser=self.get_parser()
4923
 
+        parser.print_help()
4924
 
+        print """
4925
 
+Updates a working tree to the current archive revision
4926
 
+
4927
 
+If a revision or version is specified, that is used instead 
4928
 
+        """
4929
 
+        help_tree_spec() 
4930
 
+        return
4931
 
+
4932
 
+
4933
 
+class Commit(BaseCommand):
4934
 
+    """
4935
 
+    Create a revision based on the changes in the current tree.
4936
 
+    """
4937
 
+    
4938
 
+    def __init__(self):
4939
 
+        self.description="Write local changes to the archive"
4940
 
+
4941
 
+    def get_completer(self, arg, index):
4942
 
+        if arg is None:
4943
 
+            arg = ""
4944
 
+        return iter_modified_file_completions(arch.tree_root(), arg)
4945
 
+#        return iter_source_file_completions(arch.tree_root(), arg)
4946
 
+    
4947
 
+    def parse_commandline(self, cmdline, tree):
4948
 
+        """
4949
 
+        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
4950
 
+        
4951
 
+        :param cmdline: A list of arguments to parse
4952
 
+        :rtype: (options, Revision, Revision/WorkingTree)
4953
 
+        """
4954
 
+        parser=self.get_parser()
4955
 
+        (options, args) = parser.parse_args(cmdline)
4956
 
+
4957
 
+        if len(args) == 0:
4958
 
+            args = None
4959
 
+        revision=cmdutil.determine_revision_arch(tree, options.version)
4960
 
+        return options, revision.get_version(), args
4961
 
+
4962
 
+    def do_command(self, cmdargs):
4963
 
+        """
4964
 
+        Master function that perfoms the "commit" command.
4965
 
+        """
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)
4970
 
+        else:
4971
 
+            base = cmdutil.submit_revision(tree)
4972
 
+        
4973
 
+        writeversion=version
4974
 
+        archive=version.archive
4975
 
+        source=cmdutil.get_mirror_source(archive)
4976
 
+        allow_old=False
4977
 
+        writethrough="implicit"
4978
 
+
4979
 
+        if source!=None:
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)
4985
 
+
4986
 
+        elif archive.is_mirror:
4987
 
+            raise CommitToMirror(archive)
4988
 
+
4989
 
+        try:
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)
4994
 
+            else:
4995
 
+                raise NoVersionLogs(version)
4996
 
+        if last_revision!=version.iter_revisions(True).next():
4997
 
+            if not cmdutil.prompt("Out of date"):
4998
 
+                raise OutOfDate
4999
 
+            else:
5000
 
+                allow_old=True
5001
 
+
5002
 
+        try:
5003
 
+            if not cmdutil.has_changed(version):
5004
 
+                if not cmdutil.prompt("Empty commit"):
5005
 
+                    raise EmptyCommit
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)
5010
 
+            else:
5011
 
+                raise
5012
 
+        log = tree.log_message(create=False)
5013
 
+        if log is None:
5014
 
+            try:
5015
 
+                if cmdutil.prompt("Create log"):
5016
 
+                    edit_log(tree)
5017
 
+
5018
 
+            except cmdutil.NoEditorSpecified, e:
5019
 
+                raise CommandFailed(e)
5020
 
+            log = tree.log_message(create=False)
5021
 
+        if log is None: 
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
5026
 
+        try:
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)
5030
 
+
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)
5035
 
+            else:
5036
 
+                raise
5037
 
+
5038
 
+    def get_parser(self):
5039
 
+        """
5040
 
+        Returns the options parser to use for the "commit" command.
5041
 
+
5042
 
+        :rtype: cmdutil.CmdOptionParser
5043
 
+        """
5044
 
+
5045
 
+        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
5046
 
+                                       " [file2...]")
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")
5059
 
+        return parser
5060
 
+
5061
 
+    def help(self, parser=None):
5062
 
+        """
5063
 
+        Prints a help message.
5064
 
+
5065
 
+        :param parser: If supplied, the parser to use for generating help.  If \
5066
 
+        not supplied, it is retrieved.
5067
 
+        :type parser: cmdutil.CmdOptionParser
5068
 
+        """
5069
 
+        if parser is None:
5070
 
+            parser=self.get_parser()
5071
 
+        parser.print_help()
5072
 
+        print """
5073
 
+Updates a working tree to the current archive revision
5074
 
+
5075
 
+If a version is specified, that is used instead 
5076
 
+        """
5077
 
+#        help_tree_spec() 
5078
 
+        return
5079
 
+
5080
 
+
5081
 
+
5082
 
+class CatLog(BaseCommand):
5083
 
+    """
5084
 
+    Print the log of a given file (from current tree)
5085
 
+    """
5086
 
+    def __init__(self):
5087
 
+        self.description="Prints the patch log for a revision"
5088
 
+
5089
 
+    def get_completer(self, arg, index):
5090
 
+        if index > 0:
5091
 
+            return None
5092
 
+        try:
5093
 
+            tree = arch.tree_root()
5094
 
+        except:
5095
 
+            tree = None
5096
 
+        return cmdutil.iter_revision_completions(arg, tree)
5097
 
+
5098
 
+    def do_command(self, cmdargs):
5099
 
+        """
5100
 
+        Master function that perfoms the "cat-log" command.
5101
 
+        """
5102
 
+        parser=self.get_parser()
5103
 
+        (options, args) = parser.parse_args(cmdargs)
5104
 
+        try:
5105
 
+            tree = arch.tree_root()
5106
 
+        except arch.errors.TreeRootError, e:
5107
 
+            tree = None
5108
 
+        spec=None
5109
 
+        if len(args) > 0:
5110
 
+            spec=args[0]
5111
 
+        if len(args) > 1:
5112
 
+            raise cmdutil.GetHelp()
5113
 
+        try:
5114
 
+            if tree:
5115
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
5116
 
+            else:
5117
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
5118
 
+        except cmdutil.CantDetermineRevision, e:
5119
 
+            raise CommandFailedWrapper(e)
5120
 
+        log = None
5121
 
+        
5122
 
+        use_tree = (options.source == "tree" or \
5123
 
+            (options.source == "any" and tree))
5124
 
+        use_arch = (options.source == "archive" or options.source == "any")
5125
 
+        
5126
 
+        log = None
5127
 
+        if use_tree:
5128
 
+            for log in tree.iter_logs(revision.get_version()):
5129
 
+                if log.revision == revision:
5130
 
+                    break
5131
 
+                else:
5132
 
+                    log = None
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
5140
 
+
5141
 
+    def get_parser(self):
5142
 
+        """
5143
 
+        Returns the options parser to use for the "cat-log" command.
5144
 
+
5145
 
+        :rtype: cmdutil.CmdOptionParser
5146
 
+        """
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")
5153
 
+        return parser 
5154
 
+
5155
 
+    def help(self, parser=None):
5156
 
+        """
5157
 
+        Prints a help message.
5158
 
+
5159
 
+        :param parser: If supplied, the parser to use for generating help.  If \
5160
 
+        not supplied, it is retrieved.
5161
 
+        :type parser: cmdutil.CmdOptionParser
5162
 
+        """
5163
 
+        if parser==None:
5164
 
+            parser=self.get_parser()
5165
 
+        parser.print_help()
5166
 
+        print """
5167
 
+Prints the log for the specified revision
5168
 
+        """
5169
 
+        help_tree_spec()
5170
 
+        return
5171
 
+
5172
 
+class Revert(BaseCommand):
5173
 
+    """ Reverts a tree (or aspects of it) to a revision
5174
 
+    """
5175
 
+    def __init__(self):
5176
 
+        self.description="Reverts a tree (or aspects of it) to a revision "
5177
 
+
5178
 
+    def get_completer(self, arg, index):
5179
 
+        if index > 0:
5180
 
+            return None
5181
 
+        try:
5182
 
+            tree = arch.tree_root()
5183
 
+        except:
5184
 
+            tree = None
5185
 
+        return iter_modified_file_completions(tree, arg)
5186
 
+
5187
 
+    def do_command(self, cmdargs):
5188
 
+        """
5189
 
+        Master function that perfoms the "revert" command.
5190
 
+        """
5191
 
+        parser=self.get_parser()
5192
 
+        (options, args) = parser.parse_args(cmdargs)
5193
 
+        try:
5194
 
+            tree = arch.tree_root()
5195
 
+        except arch.errors.TreeRootError, e:
5196
 
+            raise CommandFailed(e)
5197
 
+        spec=None
5198
 
+        if options.revision is not None:
5199
 
+            spec=options.revision
5200
 
+        try:
5201
 
+            if spec is not None:
5202
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
5203
 
+            else:
5204
 
+                revision = cmdutil.comp_revision(tree)
5205
 
+        except cmdutil.CantDetermineRevision, e:
5206
 
+            raise CommandFailedWrapper(e)
5207
 
+        munger = None
5208
 
+
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
5213
 
+
5214
 
+        if len(args) > 0 or options.logs or options.pattern_files or \
5215
 
+            options.control:
5216
 
+            if munger is None:
5217
 
+                munger = cmdutil.MungeOpts(True)
5218
 
+                munger.all_types(True)
5219
 
+        if len(args) > 0:
5220
 
+            t_cwd = cmdutil.tree_cwd(tree)
5221
 
+            for name in args:
5222
 
+                if len(t_cwd) > 0:
5223
 
+                    t_cwd += "/"
5224
 
+                name = "./" + t_cwd + name
5225
 
+                munger.add_keep_file(name);
5226
 
+
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
5237
 
+        if options.logs:
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)
5244
 
+                
5245
 
+        for line in cmdutil.revert(tree, revision, munger, 
5246
 
+                                   not options.no_output):
5247
 
+            cmdutil.colorize(line)
5248
 
+
5249
 
+
5250
 
+    def get_parser(self):
5251
 
+        """
5252
 
+        Returns the options parser to use for the "cat-log" command.
5253
 
+
5254
 
+        :rtype: cmdutil.CmdOptionParser
5255
 
+        """
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", 
5264
 
+                          dest="deletions", 
5265
 
+                          help="Restore deleted files")
5266
 
+        parser.add_option("", "--additions", action="store_true", 
5267
 
+                          dest="additions", 
5268
 
+                          help="Remove added files")
5269
 
+        parser.add_option("", "--renames", action="store_true", 
5270
 
+                          dest="renames", 
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", 
5277
 
+                          metavar="REGEX")
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", 
5285
 
+                          dest="no_output", 
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")
5290
 
+        return parser 
5291
 
+
5292
 
+    def help(self, parser=None):
5293
 
+        """
5294
 
+        Prints a help message.
5295
 
+
5296
 
+        :param parser: If supplied, the parser to use for generating help.  If \
5297
 
+        not supplied, it is retrieved.
5298
 
+        :type parser: cmdutil.CmdOptionParser
5299
 
+        """
5300
 
+        if parser==None:
5301
 
+            parser=self.get_parser()
5302
 
+        parser.print_help()
5303
 
+        print """
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
5306
 
+reverted.  
5307
 
+
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.
5311
 
+
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!)
5314
 
+
5315
 
+Unless "-n" is specified, reversions can be undone with "redo".
5316
 
+        """
5317
 
+        return
5318
 
+
5319
 
+class Revision(BaseCommand):
5320
 
+    """
5321
 
+    Print a revision name based on a revision specifier
5322
 
+    """
5323
 
+    def __init__(self):
5324
 
+        self.description="Prints the name of a revision"
5325
 
+
5326
 
+    def get_completer(self, arg, index):
5327
 
+        if index > 0:
5328
 
+            return None
5329
 
+        try:
5330
 
+            tree = arch.tree_root()
5331
 
+        except:
5332
 
+            tree = None
5333
 
+        return cmdutil.iter_revision_completions(arg, tree)
5334
 
+
5335
 
+    def do_command(self, cmdargs):
5336
 
+        """
5337
 
+        Master function that perfoms the "revision" command.
5338
 
+        """
5339
 
+        parser=self.get_parser()
5340
 
+        (options, args) = parser.parse_args(cmdargs)
5341
 
+
5342
 
+        try:
5343
 
+            tree = arch.tree_root()
5344
 
+        except arch.errors.TreeRootError:
5345
 
+            tree = None
5346
 
+
5347
 
+        spec=None
5348
 
+        if len(args) > 0:
5349
 
+            spec=args[0]
5350
 
+        if len(args) > 1:
5351
 
+            raise cmdutil.GetHelp
5352
 
+        try:
5353
 
+            if tree:
5354
 
+                revision = cmdutil.determine_revision_tree(tree, spec)
5355
 
+            else:
5356
 
+                revision = cmdutil.determine_revision_arch(tree, spec)
5357
 
+        except cmdutil.CantDetermineRevision, e:
5358
 
+            print str(e)
5359
 
+            return
5360
 
+        print options.display(revision)
5361
 
+
5362
 
+    def get_parser(self):
5363
 
+        """
5364
 
+        Returns the options parser to use for the "revision" command.
5365
 
+
5366
 
+        :rtype: cmdutil.CmdOptionParser
5367
 
+        """
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, 
5383
 
+                         dest="display",
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")
5388
 
+        return parser 
5389
 
+
5390
 
+    def help(self, parser=None):
5391
 
+        """
5392
 
+        Prints a help message.
5393
 
+
5394
 
+        :param parser: If supplied, the parser to use for generating help.  If \
5395
 
+        not supplied, it is retrieved.
5396
 
+        :type parser: cmdutil.CmdOptionParser
5397
 
+        """
5398
 
+        if parser==None:
5399
 
+            parser=self.get_parser()
5400
 
+        parser.print_help()
5401
 
+        print """
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.
5405
 
+        """
5406
 
+        help_tree_spec()
5407
 
+        return
5408
 
+
5409
 
+def require_version_exists(version, spec):
5410
 
+    if not version.exists():
5411
 
+        raise cmdutil.CantDetermineVersion(spec, 
5412
 
+                                           "The version %s does not exist." \
5413
 
+                                           % version)
5414
 
+
5415
 
+class Revisions(BaseCommand):
5416
 
+    """
5417
 
+    Print a revision name based on a revision specifier
5418
 
+    """
5419
 
+    def __init__(self):
5420
 
+        self.description="Lists revisions"
5421
 
+    
5422
 
+    def do_command(self, cmdargs):
5423
 
+        """
5424
 
+        Master function that perfoms the "revision" command.
5425
 
+        """
5426
 
+        (options, args) = self.get_parser().parse_args(cmdargs)
5427
 
+        if len(args) > 1:
5428
 
+            raise cmdutil.GetHelp
5429
 
+        try:
5430
 
+            self.tree = arch.tree_root()
5431
 
+        except arch.errors.TreeRootError:
5432
 
+            self.tree = None
5433
 
+        try:
5434
 
+            iter = self.get_iterator(options.type, args, options.reverse, 
5435
 
+                                     options.modified)
5436
 
+        except cmdutil.CantDetermineRevision, e:
5437
 
+            raise CommandFailedWrapper(e)
5438
 
+
5439
 
+        if options.skip is not None:
5440
 
+            iter = cmdutil.iter_skip(iter, int(options.skip))
5441
 
+
5442
 
+        for revision in iter:
5443
 
+            log = None
5444
 
+            if isinstance(revision, arch.Patchlog):
5445
 
+                log = revision
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
5453
 
+            if options.date:
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:
5461
 
+                        print "    Merged:"
5462
 
+                        showed_title = True
5463
 
+                    print "    %s" % revision
5464
 
+
5465
 
+    def get_iterator(self, type, args, reverse, modified):
5466
 
+        if len(args) > 0:
5467
 
+            spec = args[0]
5468
 
+        else:
5469
 
+            spec = None
5470
 
+        if modified is not None:
5471
 
+            iter = cmdutil.modified_iter(modified, self.tree)
5472
 
+            if reverse:
5473
 
+                return iter
5474
 
+            else:
5475
 
+                return cmdutil.iter_reverse(iter)
5476
 
+        elif type == "archive":
5477
 
+            if spec is None:
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)
5482
 
+            else:
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":
5488
 
+            if spec is None:
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)
5493
 
+            else:
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":
5499
 
+            if spec is None:
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)
5504
 
+            else:
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)
5521
 
+
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)
5529
 
+
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":
5538
 
+                return iter
5539
 
+            elif type == "direct-merges":
5540
 
+                return cmdutil.direct_merges(iter)
5541
 
+
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,
5548
 
+                                        reverse)
5549
 
+
5550
 
+        elif type == "partner-missing":
5551
 
+            return cmdutil.iter_partner_missing(self.tree, reverse)
5552
 
+
5553
 
+        elif type == "ancestry":
5554
 
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
5555
 
+            iter = cmdutil._iter_ancestry(self.tree, revision)
5556
 
+            if reverse:
5557
 
+                return iter
5558
 
+            else:
5559
 
+                return cmdutil.iter_reverse(iter)
5560
 
+
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)
5566
 
+            if reverse:
5567
 
+                return iter_depends
5568
 
+            else:
5569
 
+                return cmdutil.iter_reverse(iter_depends)
5570
 
+        elif type == "micro":
5571
 
+            return cmdutil.iter_micro(self.tree)
5572
 
+
5573
 
+    
5574
 
+    def get_parser(self):
5575
 
+        """
5576
 
+        Returns the options parser to use for the "revision" command.
5577
 
+
5578
 
+        :rtype: cmdutil.CmdOptionParser
5579
 
+        """
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 "
5595
 
+                          "tree")
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"
5615
 
+                          " missing")
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")
5631
 
+
5632
 
+        select.add_option("", "--dependencies", action="store_const", 
5633
 
+                          const="dependencies", dest="type",
5634
 
+                          help="List revisions that the given revision "
5635
 
+                          "depends on")
5636
 
+
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")
5641
 
+
5642
 
+        select.add_option("--micro", action="store_const", 
5643
 
+                          const="micro", dest="type",
5644
 
+                          help="List partner revisions aimed for this "
5645
 
+                          "micro-branch")
5646
 
+
5647
 
+        select.add_option("", "--modified", dest="modified", 
5648
 
+                          help="List tree ancestor revisions that modified a "
5649
 
+                          "given file", metavar="FILE[:LINE]")
5650
 
+
5651
 
+        parser.add_option("", "--skip", dest="skip", 
5652
 
+                          help="Skip revisions.  Positive numbers skip from "
5653
 
+                          "beginning, negative skip from end.",
5654
 
+                          metavar="NUMBER")
5655
 
+
5656
 
+        parser.add_option_group(select)
5657
 
+
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, 
5674
 
+                         dest="display",
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"
5690
 
+                          " revision")
5691
 
+        display.add_option("-m", "--merges", action="store_true", 
5692
 
+                          dest="merges", help="Show the revisions that were"
5693
 
+                          " merged")
5694
 
+        parser.add_option_group(display)
5695
 
+        return parser 
5696
 
+    def help(self, parser=None):
5697
 
+        """Attempt to explain the revisions command
5698
 
+        
5699
 
+        :param parser: If supplied, used to determine options
5700
 
+        """
5701
 
+        if parser==None:
5702
 
+            parser=self.get_parser()
5703
 
+        parser.print_help()
5704
 
+        print """List revisions.
5705
 
+        """
5706
 
+        help_tree_spec()
5707
 
+
5708
 
+
5709
 
+class Get(BaseCommand):
5710
 
+    """
5711
 
+    Retrieve a revision from the archive
5712
 
+    """
5713
 
+    def __init__(self):
5714
 
+        self.description="Retrieve a revision from the archive"
5715
 
+        self.parser=self.get_parser()
5716
 
+
5717
 
+
5718
 
+    def get_completer(self, arg, index):
5719
 
+        if index > 0:
5720
 
+            return None
5721
 
+        try:
5722
 
+            tree = arch.tree_root()
5723
 
+        except:
5724
 
+            tree = None
5725
 
+        return cmdutil.iter_revision_completions(arg, tree)
5726
 
+
5727
 
+
5728
 
+    def do_command(self, cmdargs):
5729
 
+        """
5730
 
+        Master function that perfoms the "get" command.
5731
 
+        """
5732
 
+        (options, args) = self.parser.parse_args(cmdargs)
5733
 
+        if len(args) < 1:
5734
 
+            return self.help()            
5735
 
+        try:
5736
 
+            tree = arch.tree_root()
5737
 
+        except arch.errors.TreeRootError:
5738
 
+            tree = None
5739
 
+        
5740
 
+        arch_loc = None
5741
 
+        try:
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)
5746
 
+        if len(args) > 1:
5747
 
+            directory = args[1]
5748
 
+        else:
5749
 
+            directory = str(revision.nonarch)
5750
 
+        if os.path.exists(directory):
5751
 
+            raise DirectoryExists(directory)
5752
 
+        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
5753
 
+        try:
5754
 
+            cmdutil.ensure_revision_exists(revision)
5755
 
+        except cmdutil.NoSuchRevision, e:
5756
 
+            raise CommandFailedWrapper(e)
5757
 
+
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)
5763
 
+
5764
 
+    def get_parser(self):
5765
 
+        """
5766
 
+        Returns the options parser to use for the "get" command.
5767
 
+
5768
 
+        :rtype: cmdutil.CmdOptionParser
5769
 
+        """
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")
5777
 
+
5778
 
+        return parser 
5779
 
+
5780
 
+    def help(self, parser=None):
5781
 
+        """
5782
 
+        Prints a help message.
5783
 
+
5784
 
+        :param parser: If supplied, the parser to use for generating help.  If \
5785
 
+        not supplied, it is retrieved.
5786
 
+        :type parser: cmdutil.CmdOptionParser
5787
 
+        """
5788
 
+        if parser==None:
5789
 
+            parser=self.get_parser()
5790
 
+        parser.print_help()
5791
 
+        print """
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.
5794
 
+        """
5795
 
+        help_tree_spec()
5796
 
+        return
5797
 
+
5798
 
+class PromptCmd(cmd.Cmd):
5799
 
+    def __init__(self):
5800
 
+        cmd.Cmd.__init__(self)
5801
 
+        self.prompt = "Fai> "
5802
 
+        try:
5803
 
+            self.tree = arch.tree_root()
5804
 
+        except:
5805
 
+            self.tree = None
5806
 
+        self.set_title()
5807
 
+        self.set_prompt()
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)
5815
 
+
5816
 
+    def write_history(self):
5817
 
+        readline.write_history_file(self.history_file)
5818
 
+
5819
 
+    def do_quit(self, args):
5820
 
+        self.write_history()
5821
 
+        sys.exit(0)
5822
 
+
5823
 
+    def do_exit(self, args):
5824
 
+        self.do_quit(args)
5825
 
+
5826
 
+    def do_EOF(self, args):
5827
 
+        print
5828
 
+        self.do_quit(args)
5829
 
+
5830
 
+    def postcmd(self, line, bar):
5831
 
+        self.set_title()
5832
 
+        self.set_prompt()
5833
 
+
5834
 
+    def set_prompt(self):
5835
 
+        if self.tree is not None:
5836
 
+            try:
5837
 
+                version = " "+self.tree.tree_version.nonarch
5838
 
+            except:
5839
 
+                version = ""
5840
 
+        else:
5841
 
+            version = ""
5842
 
+        self.prompt = "Fai%s> " % version
5843
 
+
5844
 
+    def set_title(self, command=None):
5845
 
+        try:
5846
 
+            version = self.tree.tree_version.nonarch
5847
 
+        except:
5848
 
+            version = "[no version]"
5849
 
+        if command is None:
5850
 
+            command = ""
5851
 
+        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
5852
 
+
5853
 
+    def do_cd(self, line):
5854
 
+        if line == "":
5855
 
+            line = "~"
5856
 
+        try:
5857
 
+            os.chdir(os.path.expanduser(line))
5858
 
+        except Exception, e:
5859
 
+            print e
5860
 
+        try:
5861
 
+            self.tree = arch.tree_root()
5862
 
+        except:
5863
 
+            self.tree = None
5864
 
+
5865
 
+    def do_help(self, line):
5866
 
+        Help()(line)
5867
 
+
5868
 
+    def default(self, line):
5869
 
+        args = line.split()
5870
 
+        if find_command(args[0]):
5871
 
+            try:
5872
 
+                find_command(args[0]).do_command(args[1:])
5873
 
+            except cmdutil.BadCommandOption, e:
5874
 
+                print e
5875
 
+            except cmdutil.GetHelp, e:
5876
 
+                find_command(args[0]).help()
5877
 
+            except CommandFailed, e:
5878
 
+                print e
5879
 
+            except arch.errors.ArchiveNotRegistered, e:
5880
 
+                print 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:
5886
 
+                print e
5887
 
+            except cmdutil.CantDetermineRevision, e:
5888
 
+                print e
5889
 
+            except Exception, e:
5890
 
+                print "Unhandled error:\n%s" % cmdutil.exception_str(e)
5891
 
+
5892
 
+        elif suggestions.has_key(args[0]):
5893
 
+            print suggestions[args[0]]
5894
 
+
5895
 
+        elif self.fake_aba.is_command(args[0]):
5896
 
+            tree = None
5897
 
+            try:
5898
 
+                tree = arch.tree_root()
5899
 
+            except arch.errors.TreeRootError:
5900
 
+                pass
5901
 
+            cmd = self.fake_aba.is_command(args[0])
5902
 
+            try:
5903
 
+                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
5904
 
+            except KeyboardInterrupt, e:
5905
 
+                print "Interrupted"
5906
 
+
5907
 
+        elif options.tla_fallthrough and args[0] != "rm" and \
5908
 
+            cmdutil.is_tla_command(args[0]):
5909
 
+            try:
5910
 
+                tree = None
5911
 
+                try:
5912
 
+                    tree = arch.tree_root()
5913
 
+                except arch.errors.TreeRootError:
5914
 
+                    pass
5915
 
+                args = cmdutil.expand_prefix_alias(args, tree)
5916
 
+                arch.util.exec_safe('tla', args, stderr=sys.stderr,
5917
 
+                expected=(0, 1))
5918
 
+            except arch.util.ExecProblem, e:
5919
 
+                pass
5920
 
+            except KeyboardInterrupt, e:
5921
 
+                print "Interrupted"
5922
 
+        else:
5923
 
+            try:
5924
 
+                try:
5925
 
+                    tree = arch.tree_root()
5926
 
+                except arch.errors.TreeRootError:
5927
 
+                    tree = None
5928
 
+                args=line.split()
5929
 
+                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
5930
 
+            except KeyboardInterrupt, e:
5931
 
+                print "Interrupted"
5932
 
+
5933
 
+    def completenames(self, text, line, begidx, endidx):
5934
 
+        completions = []
5935
 
+        iter = iter_command_names(self.fake_aba)
5936
 
+        try:
5937
 
+            if len(line) > 0:
5938
 
+                arg = line.split()[-1]
5939
 
+            else:
5940
 
+                arg = ""
5941
 
+            iter = iter_munged_completions(iter, arg, text)
5942
 
+        except Exception, e:
5943
 
+            print e
5944
 
+        return list(iter)
5945
 
+
5946
 
+    def completedefault(self, text, line, begidx, endidx):
5947
 
+        """Perform completion for native commands.
5948
 
+        
5949
 
+        :param text: The text to complete
5950
 
+        :type text: str
5951
 
+        :param line: The entire line to complete
5952
 
+        :type line: str
5953
 
+        :param begidx: The start of the text in the line
5954
 
+        :type begidx: int
5955
 
+        :param endidx: The end of the text in the line
5956
 
+        :type endidx: int
5957
 
+        """
5958
 
+        try:
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)
5966
 
+                if len(args) > 0:
5967
 
+                    arg = args.split()[-1]
5968
 
+                else:
5969
 
+                    arg = ""
5970
 
+                if arg.startswith("-"):
5971
 
+                    return list(iter_munged_completions(iter, arg, text))
5972
 
+                else:
5973
 
+                    return list(iter_munged_completions(
5974
 
+                        iter_file_completions(arg), arg, text))
5975
 
+
5976
 
+
5977
 
+            elif cmd == "cd":
5978
 
+                if len(args) > 0:
5979
 
+                    arg = args.split()[-1]
5980
 
+                else:
5981
 
+                    arg = ""
5982
 
+                iter = iter_dir_completions(arg)
5983
 
+                iter = iter_munged_completions(iter, arg, text)
5984
 
+                return list(iter)
5985
 
+            elif len(args)>0:
5986
 
+                arg = args.split()[-1]
5987
 
+                return list(iter_munged_completions(iter_file_completions(arg),
5988
 
+                                                    arg, text))
5989
 
+            else:
5990
 
+                return self.completenames(text, line, begidx, endidx)
5991
 
+        except Exception, e:
5992
 
+            print e
5993
 
+
5994
 
+
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)):
6000
 
+            yield entry
6001
 
+
6002
 
+
6003
 
+def iter_file_completions(arg, only_dirs = False):
6004
 
+    """Generate an iterator that iterates through filename completions.
6005
 
+
6006
 
+    :param arg: The filename fragment to match
6007
 
+    :type arg: str
6008
 
+    :param only_dirs: If true, match only directories
6009
 
+    :type only_dirs: bool
6010
 
+    """
6011
 
+    cwd = os.getcwd()
6012
 
+    if cwd != "/":
6013
 
+        extras = [".", ".."]
6014
 
+    else:
6015
 
+        extras = []
6016
 
+    (dir, file) = os.path.split(arg)
6017
 
+    if dir != "":
6018
 
+        listingdir = os.path.expanduser(dir)
6019
 
+    else:
6020
 
+        listingdir = cwd
6021
 
+    for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
6022
 
+        if dir != "":
6023
 
+            userfile = dir+'/'+file
6024
 
+        else:
6025
 
+            userfile = file
6026
 
+        if userfile.startswith(arg):
6027
 
+            if os.path.isdir(listingdir+'/'+file):
6028
 
+                userfile+='/'
6029
 
+                yield userfile
6030
 
+            elif not only_dirs:
6031
 
+                yield userfile
6032
 
+
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):]
6038
 
+
6039
 
+def iter_source_file_completions(tree, arg):
6040
 
+    treepath = cmdutil.tree_cwd(tree)
6041
 
+    if len(treepath) > 0:
6042
 
+        dirs = [treepath]
6043
 
+    else:
6044
 
+        dirs = None
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:
6048
 
+            yield file
6049
 
+
6050
 
+
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):
6055
 
+        yield file.name 
6056
 
+
6057
 
+
6058
 
+def iter_untagged_completions(tree, arg):
6059
 
+    """Generate an iterator for all visible untagged files that match arg.
6060
 
+
6061
 
+    :param tree: The tree to look for untagged files in
6062
 
+    :type tree: `arch.WorkingTree`
6063
 
+    :param arg: The argument to match
6064
 
+    :type arg: str
6065
 
+    :return: An iterator of all matching untagged files
6066
 
+    :rtype: iterator of str
6067
 
+    """
6068
 
+    treepath = cmdutil.tree_cwd(tree)
6069
 
+    if len(treepath) > 0:
6070
 
+        dirs = [treepath]
6071
 
+    else:
6072
 
+        dirs = None
6073
 
+
6074
 
+    for file in iter_untagged(tree, dirs):
6075
 
+        file = file_completion_match(file, treepath, arg)
6076
 
+        if file is not None:
6077
 
+            yield file
6078
 
+
6079
 
+
6080
 
+def file_completion_match(file, treepath, arg):
6081
 
+    """Determines whether a file within an arch tree matches the argument.
6082
 
+
6083
 
+    :param file: The rooted filename
6084
 
+    :type file: str
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
6089
 
+    :rtype: str
6090
 
+    """
6091
 
+    if not file.startswith(treepath):
6092
 
+        return None
6093
 
+    if treepath != "":
6094
 
+        file = file[len(treepath)+1:]
6095
 
+
6096
 
+    if not file.startswith(arg):
6097
 
+        return None 
6098
 
+    if os.path.isdir(file):
6099
 
+        file += '/'
6100
 
+    return file
6101
 
+
6102
 
+def iter_modified_file_completions(tree, arg):
6103
 
+    """Returns a list of modified files that match the specified prefix.
6104
 
+
6105
 
+    :param tree: The current tree
6106
 
+    :type tree: `arch.WorkingTree`
6107
 
+    :param arg: The prefix to match
6108
 
+    :type arg: str
6109
 
+    """
6110
 
+    treepath = cmdutil.tree_cwd(tree)
6111
 
+    tmpdir = cmdutil.tmpdir()
6112
 
+    changeset = tmpdir+"/changeset"
6113
 
+    completions = []
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
6122
 
+
6123
 
+def iter_dir_completions(arg):
6124
 
+    """Generate an iterator that iterates through directory name completions.
6125
 
+
6126
 
+    :param arg: The directory name fragment to match
6127
 
+    :type arg: str
6128
 
+    """
6129
 
+    return iter_file_completions(arg, True)
6130
 
+
6131
 
+class Shell(BaseCommand):
6132
 
+    def __init__(self):
6133
 
+        self.description = "Runs Fai as a shell"
6134
 
+
6135
 
+    def do_command(self, cmdargs):
6136
 
+        if len(cmdargs)!=0:
6137
 
+            raise cmdutil.GetHelp
6138
 
+        prompt = PromptCmd()
6139
 
+        try:
6140
 
+            prompt.cmdloop()
6141
 
+        finally:
6142
 
+            prompt.write_history()
6143
 
+
6144
 
+class AddID(BaseCommand):
6145
 
+    """
6146
 
+    Adds an inventory id for the given file
6147
 
+    """
6148
 
+    def __init__(self):
6149
 
+        self.description="Add an inventory id for a given file"
6150
 
+
6151
 
+    def get_completer(self, arg, index):
6152
 
+        tree = arch.tree_root()
6153
 
+        return iter_untagged_completions(tree, arg)
6154
 
+
6155
 
+    def do_command(self, cmdargs):
6156
 
+        """
6157
 
+        Master function that perfoms the "revision" command.
6158
 
+        """
6159
 
+        parser=self.get_parser()
6160
 
+        (options, args) = parser.parse_args(cmdargs)
6161
 
+
6162
 
+        tree = arch.tree_root()
6163
 
+
6164
 
+        if (len(args) == 0) == (options.untagged == False):
6165
 
+            raise cmdutil.GetHelp
6166
 
+
6167
 
+       #if options.id and len(args) != 1:
6168
 
+       #    print "If --id is specified, only one file can be named."
6169
 
+       #    return
6170
 
+        
6171
 
+        method = tree.tagging_method
6172
 
+        
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
6178
 
+                    else:
6179
 
+                        print "add-id not supported for \"%s\" tagging method"\
6180
 
+                            % method 
6181
 
+                        return
6182
 
+        
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" % \
6187
 
+                        method
6188
 
+                    return
6189
 
+        
6190
 
+        if options.id_type == "auto":
6191
 
+            if method != "tagline" and method != "explicit":
6192
 
+                print "add-id not supported for \"%s\" tagging method" % method
6193
 
+                return
6194
 
+            else:
6195
 
+                options.id_type = method
6196
 
+        if options.untagged:
6197
 
+            args = None
6198
 
+        self.add_ids(tree, options.id_type, args)
6199
 
+
6200
 
+    def add_ids(self, tree, id_type, files=()):
6201
 
+        """Add inventory ids to files.
6202
 
+        
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
6209
 
+        """
6210
 
+
6211
 
+        untagged = (files is None)
6212
 
+        if untagged:
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:
6221
 
+                    try:
6222
 
+                        cmdutil.add_tagline_or_explicit_id(file)
6223
 
+                    except cmdutil.AlreadyTagged:
6224
 
+                        print "\"%s\" already has a tagline." % file
6225
 
+                    except cmdutil.NoCommentSyntax:
6226
 
+                        pass
6227
 
+            #do inventory after tagging until no untagged files are encountered
6228
 
+            if untagged:
6229
 
+                files = []
6230
 
+                for file in iter_untagged(tree, None):
6231
 
+                    if not file in previous_files:
6232
 
+                        files.append(file)
6233
 
+
6234
 
+            else:
6235
 
+                break
6236
 
+
6237
 
+    def get_parser(self):
6238
 
+        """
6239
 
+        Returns the options parser to use for the "revision" command.
6240
 
+
6241
 
+        :rtype: cmdutil.CmdOptionParser
6242
 
+        """
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")
6258
 
+        return parser 
6259
 
+
6260
 
+    def help(self, parser=None):
6261
 
+        """
6262
 
+        Prints a help message.
6263
 
+
6264
 
+        :param parser: If supplied, the parser to use for generating help.  If \
6265
 
+        not supplied, it is retrieved.
6266
 
+        :type parser: cmdutil.CmdOptionParser
6267
 
+        """
6268
 
+        if parser==None:
6269
 
+            parser=self.get_parser()
6270
 
+        parser.print_help()
6271
 
+        print """
6272
 
+Adds an inventory to the specified file(s) and directories.  If --untagged is
6273
 
+specified, adds inventory to all untagged files and directories.
6274
 
+        """
6275
 
+        return
6276
 
+
6277
 
+
6278
 
+class Merge(BaseCommand):
6279
 
+    """
6280
 
+    Merges changes from other versions into the current tree
6281
 
+    """
6282
 
+    def __init__(self):
6283
 
+        self.description="Merges changes from other versions"
6284
 
+        try:
6285
 
+            self.tree = arch.tree_root()
6286
 
+        except:
6287
 
+            self.tree = None
6288
 
+
6289
 
+
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())
6297
 
+
6298
 
+        aliases = []
6299
 
+        try:
6300
 
+            for completion in completions:
6301
 
+                alias = ancillary.compact_alias(str(completion), self.tree)
6302
 
+                if alias:
6303
 
+                    aliases.extend(alias)
6304
 
+
6305
 
+            for completion in completions:
6306
 
+                if completion.archive == self.tree.tree_version.archive:
6307
 
+                    aliases.append(completion.nonarch)
6308
 
+
6309
 
+        except Exception, e:
6310
 
+            print e
6311
 
+            
6312
 
+        completions.extend(aliases)
6313
 
+        return completions
6314
 
+
6315
 
+    def do_command(self, cmdargs):
6316
 
+        """
6317
 
+        Master function that perfoms the "merge" command.
6318
 
+        """
6319
 
+        parser=self.get_parser()
6320
 
+        (options, args) = parser.parse_args(cmdargs)
6321
 
+        if options.diff3:
6322
 
+            action="star-merge"
6323
 
+        else:
6324
 
+            action = options.action
6325
 
+        
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)
6330
 
+
6331
 
+        if len(args) > 0:
6332
 
+            revisions = []
6333
 
+            for arg in args:
6334
 
+                revisions.append(cmdutil.determine_revision_arch(self.tree, 
6335
 
+                                                                 arg))
6336
 
+            source = "from commandline"
6337
 
+        else:
6338
 
+            revisions = ancillary.iter_partner_revisions(self.tree, 
6339
 
+                                                         self.tree.tree_version)
6340
 
+            source = "from partner version"
6341
 
+        revisions = misc.rewind_iterator(revisions)
6342
 
+        try:
6343
 
+            revisions.next()
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"
6350
 
+                                            " source")
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:
6359
 
+                    continue
6360
 
+            elif action=="star-merge":
6361
 
+                try: 
6362
 
+                    self.star_merge(revision, options.diff3)
6363
 
+                except errors.MergeProblem, e:
6364
 
+                    break
6365
 
+            if cmdutil.has_changed(self.tree.tree_version):
6366
 
+                break
6367
 
+
6368
 
+    def star_merge(self, revision, diff3):
6369
 
+        """Perform a star-merge on the current tree.
6370
 
+        
6371
 
+        :param revision: The revision to use for the merge
6372
 
+        :type revision: `arch.Revision`
6373
 
+        :param diff3: If true, do a diff3 merge
6374
 
+        :type diff3: bool
6375
 
+        """
6376
 
+        try:
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:
6381
 
+                if e.proc.error:
6382
 
+                    print e.proc.error
6383
 
+                raise MergeProblem
6384
 
+            else:
6385
 
+                raise
6386
 
+
6387
 
+    def native_merge(self, other_revision, action):
6388
 
+        """Perform a native-merge on the current tree.
6389
 
+        
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
6393
 
+        """
6394
 
+        other_tree = cmdutil.find_or_make_local_revision(other_revision)
6395
 
+        try:
6396
 
+            if action == "native-merge":
6397
 
+                ancestor = cmdutil.merge_ancestor2(self.tree, other_tree, 
6398
 
+                                                   other_revision)
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" 
6407
 
+                                          % ancestor))
6408
 
+            return 0
6409
 
+        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
6410
 
+        for line in cmdutil.iter_apply_delta_filter(delta):
6411
 
+            cmdutil.colorize(line)
6412
 
+        return 1
6413
 
+
6414
 
+
6415
 
+
6416
 
+    def get_parser(self):
6417
 
+        """
6418
 
+        Returns the options parser to use for the "merge" command.
6419
 
+
6420
 
+        :rtype: cmdutil.CmdOptionParser
6421
 
+        """
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",
6428
 
+                          const="update")
6429
 
+        parser.add_option("--diff3", action="store_true", 
6430
 
+                         dest="diff3",  
6431
 
+                         help="Use diff3 for merge (implies star-merge)")
6432
 
+        return parser 
6433
 
+
6434
 
+    def help(self, parser=None):
6435
 
+        """
6436
 
+        Prints a help message.
6437
 
+
6438
 
+        :param parser: If supplied, the parser to use for generating help.  If \
6439
 
+        not supplied, it is retrieved.
6440
 
+        :type parser: cmdutil.CmdOptionParser
6441
 
+        """
6442
 
+        if parser==None:
6443
 
+            parser=self.get_parser()
6444
 
+        parser.print_help()
6445
 
+        print """
6446
 
+Performs a merge operation using the specified version.
6447
 
+        """
6448
 
+        return
6449
 
+
6450
 
+class ELog(BaseCommand):
6451
 
+    """
6452
 
+    Produces a raw patchlog and invokes the user's editor
6453
 
+    """
6454
 
+    def __init__(self):
6455
 
+        self.description="Edit a patchlog to commit"
6456
 
+        try:
6457
 
+            self.tree = arch.tree_root()
6458
 
+        except:
6459
 
+            self.tree = None
6460
 
+
6461
 
+
6462
 
+    def do_command(self, cmdargs):
6463
 
+        """
6464
 
+        Master function that perfoms the "elog" command.
6465
 
+        """
6466
 
+        parser=self.get_parser()
6467
 
+        (options, args) = parser.parse_args(cmdargs)
6468
 
+        if self.tree is None:
6469
 
+            raise arch.errors.TreeRootError
6470
 
+
6471
 
+        edit_log(self.tree)
6472
 
+
6473
 
+    def get_parser(self):
6474
 
+        """
6475
 
+        Returns the options parser to use for the "merge" command.
6476
 
+
6477
 
+        :rtype: cmdutil.CmdOptionParser
6478
 
+        """
6479
 
+        parser=cmdutil.CmdOptionParser("fai elog")
6480
 
+        return parser 
6481
 
+
6482
 
+
6483
 
+    def help(self, parser=None):
6484
 
+        """
6485
 
+        Invokes $EDITOR to produce a log for committing.
6486
 
+
6487
 
+        :param parser: If supplied, the parser to use for generating help.  If \
6488
 
+        not supplied, it is retrieved.
6489
 
+        :type parser: cmdutil.CmdOptionParser
6490
 
+        """
6491
 
+        if parser==None:
6492
 
+            parser=self.get_parser()
6493
 
+        parser.print_help()
6494
 
+        print """
6495
 
+Invokes $EDITOR to produce a log for committing.
6496
 
+        """
6497
 
+        return
6498
 
+
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
6502
 
+    
6503
 
+    :param tree: The tree to edit the log for
6504
 
+    :type tree: `arch.WorkingTree`
6505
 
+    """
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)
6514
 
+        log_is_new = True
6515
 
+        tmplog = log.name
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):
6520
 
+                template = None
6521
 
+        if template:
6522
 
+            shutil.copyfile(template, tmplog)
6523
 
+        
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
6531
 
+        log.save()
6532
 
+    try:
6533
 
+        cmdutil.invoke_editor(log.name)
6534
 
+    except:
6535
 
+        if log_is_new:
6536
 
+            os.remove(log.name)
6537
 
+        raise
6538
 
+
6539
 
+def merge_summary(new_merges, tree_version):
6540
 
+    if len(new_merges) == 0:
6541
 
+        return ""
6542
 
+    if len(new_merges) == 1:
6543
 
+        summary = new_merges[0].summary
6544
 
+    else:
6545
 
+        summary = "Merge"
6546
 
+
6547
 
+    credits = []
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)
6553
 
+        else:
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))
6560
 
+
6561
 
+    return ("%s (%s)") % (summary, ", ".join(credits))
6562
 
+
6563
 
+class MirrorArchive(BaseCommand):
6564
 
+    """
6565
 
+    Updates a mirror from an archive
6566
 
+    """
6567
 
+    def __init__(self):
6568
 
+        self.description="Update a mirror from an archive"
6569
 
+
6570
 
+    def do_command(self, cmdargs):
6571
 
+        """
6572
 
+        Master function that perfoms the "revision" command.
6573
 
+        """
6574
 
+
6575
 
+        parser=self.get_parser()
6576
 
+        (options, args) = parser.parse_args(cmdargs)
6577
 
+        if len(args) > 1:
6578
 
+            raise GetHelp
6579
 
+        try:
6580
 
+            tree = arch.tree_root()
6581
 
+        except:
6582
 
+            tree = None
6583
 
+
6584
 
+        if len(args) == 0:
6585
 
+            if tree is not None:
6586
 
+                name = tree.tree_version()
6587
 
+        else:
6588
 
+            name = cmdutil.expand_alias(args[0], tree)
6589
 
+            name = arch.NameParser(name)
6590
 
+
6591
 
+        to_arch = name.get_archive()
6592
 
+        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
6593
 
+        limit = name.get_nonarch()
6594
 
+
6595
 
+        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
6596
 
+        for line in arch.chatter_classifier(iter):
6597
 
+            cmdutil.colorize(line)
6598
 
+
6599
 
+    def get_parser(self):
6600
 
+        """
6601
 
+        Returns the options parser to use for the "revision" command.
6602
 
+
6603
 
+        :rtype: cmdutil.CmdOptionParser
6604
 
+        """
6605
 
+        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
6606
 
+        return parser 
6607
 
+
6608
 
+    def help(self, parser=None):
6609
 
+        """
6610
 
+        Prints a help message.
6611
 
+
6612
 
+        :param parser: If supplied, the parser to use for generating help.  If \
6613
 
+        not supplied, it is retrieved.
6614
 
+        :type parser: cmdutil.CmdOptionParser
6615
 
+        """
6616
 
+        if parser==None:
6617
 
+            parser=self.get_parser()
6618
 
+        parser.print_help()
6619
 
+        print """
6620
 
+Updates a mirror from an archive.  If a branch, package, or version is
6621
 
+supplied, only changes under it are mirrored.
6622
 
+        """
6623
 
+        return
6624
 
+
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-
6631
 
+version.
6632
 
+
6633
 
+Use "alias" to list available (user and automatic) aliases."""
6634
 
+
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
6641
 
+        can be used).
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
6652
 
+        was tagged from.
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.
6655
 
+   """
6656
 
+    print "User aliases"
6657
 
+    for parts in ancillary.iter_all_alias(tree):
6658
 
+        print parts[0].rjust(10)+" : "+parts[1]
6659
 
+
6660
 
+
6661
 
+class Inventory(BaseCommand):
6662
 
+    """List the status of files in the tree"""
6663
 
+    def __init__(self):
6664
 
+        self.description=self.__doc__
6665
 
+
6666
 
+    def do_command(self, cmdargs):
6667
 
+        """
6668
 
+        Master function that perfoms the "revision" command.
6669
 
+        """
6670
 
+
6671
 
+        parser=self.get_parser()
6672
 
+        (options, args) = parser.parse_args(cmdargs)
6673
 
+        tree = arch.tree_root()
6674
 
+        categories = []
6675
 
+
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)
6684
 
+
6685
 
+        if len(categories) == 1:
6686
 
+            show_leading = False
6687
 
+        else:
6688
 
+            show_leading = True
6689
 
+
6690
 
+        if len(categories) == 0:
6691
 
+            categories = None
6692
 
+
6693
 
+        if options.untagged:
6694
 
+            categories = arch_core.non_root
6695
 
+            show_leading = False
6696
 
+            tagged = False
6697
 
+        else:
6698
 
+            tagged = None
6699
 
+        
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,
6706
 
+                                      id = options.ids)
6707
 
+
6708
 
+    def get_parser(self):
6709
 
+        """
6710
 
+        Returns the options parser to use for the "revision" command.
6711
 
+
6712
 
+        :rtype: cmdutil.CmdOptionParser
6713
 
+        """
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")
6731
 
+        return parser 
6732
 
+
6733
 
+    def help(self, parser=None):
6734
 
+        """
6735
 
+        Prints a help message.
6736
 
+
6737
 
+        :param parser: If supplied, the parser to use for generating help.  If \
6738
 
+        not supplied, it is retrieved.
6739
 
+        :type parser: cmdutil.CmdOptionParser
6740
 
+        """
6741
 
+        if parser==None:
6742
 
+            parser=self.get_parser()
6743
 
+        parser.print_help()
6744
 
+        print """
6745
 
+Lists the status of files in the archive:
6746
 
+S source
6747
 
+P precious
6748
 
+B backup
6749
 
+J junk
6750
 
+U unrecognized
6751
 
+T tree root
6752
 
+? untagged-source
6753
 
+Leading letter are not displayed if only one kind of file is shown
6754
 
+        """
6755
 
+        return
6756
 
+
6757
 
+
6758
 
+class Alias(BaseCommand):
6759
 
+    """List or adjust aliases"""
6760
 
+    def __init__(self):
6761
 
+        self.description=self.__doc__
6762
 
+
6763
 
+    def get_completer(self, arg, index):
6764
 
+        if index > 2:
6765
 
+            return ()
6766
 
+        try:
6767
 
+            self.tree = arch.tree_root()
6768
 
+        except:
6769
 
+            self.tree = None
6770
 
+
6771
 
+        if index == 0:
6772
 
+            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
6773
 
+        elif index == 1:
6774
 
+            return cmdutil.iter_revision_completions(arg, self.tree)
6775
 
+
6776
 
+
6777
 
+    def do_command(self, cmdargs):
6778
 
+        """
6779
 
+        Master function that perfoms the "revision" command.
6780
 
+        """
6781
 
+
6782
 
+        parser=self.get_parser()
6783
 
+        (options, args) = parser.parse_args(cmdargs)
6784
 
+        try:
6785
 
+            self.tree =  arch.tree_root()
6786
 
+        except:
6787
 
+            self.tree = None
6788
 
+
6789
 
+
6790
 
+        try:
6791
 
+            options.action(args, options)
6792
 
+        except cmdutil.ForbiddenAliasSyntax, e:
6793
 
+            raise CommandFailedWrapper(e)
6794
 
+
6795
 
+    def arg_dispatch(self, args, options):
6796
 
+        """Add, modify, or list aliases, depending on number of arguments
6797
 
+
6798
 
+        :param args: The list of commandline arguments
6799
 
+        :type args: list of str
6800
 
+        :param options: The commandline options
6801
 
+        """
6802
 
+        if len(args) == 0:
6803
 
+            help_aliases(self.tree)
6804
 
+            return
6805
 
+        elif len(args) == 1:
6806
 
+            self.print_alias(args[0])
6807
 
+        elif (len(args)) == 2:
6808
 
+            self.add(args[0], args[1], options)
6809
 
+        else:
6810
 
+            raise cmdutil.GetHelp
6811
 
+
6812
 
+    def print_alias(self, alias):
6813
 
+        answer = None
6814
 
+        for pair in ancillary.iter_all_alias(self.tree):
6815
 
+            if pair[0] == alias:
6816
 
+                answer = pair[1]
6817
 
+        if answer is not None:
6818
 
+            print answer
6819
 
+        else:
6820
 
+            print "The alias %s is not assigned." % alias
6821
 
+
6822
 
+    def add(self, alias, expansion, options):
6823
 
+        """Add or modify aliases
6824
 
+
6825
 
+        :param alias: The alias name to create/modify
6826
 
+        :type alias: str
6827
 
+        :param expansion: The expansion to assign to the alias name
6828
 
+        :type expansion: str
6829
 
+        :param options: The commandline options
6830
 
+        """
6831
 
+        newlist = ""
6832
 
+        written = False
6833
 
+        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
6834
 
+            self.tree))
6835
 
+        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
6836
 
+
6837
 
+        for pair in self.get_iterator(options):
6838
 
+            if pair[0] != alias:
6839
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
6840
 
+            elif not written:
6841
 
+                newlist+=new_line
6842
 
+                written = True
6843
 
+        if not written:
6844
 
+            newlist+=new_line
6845
 
+        self.write_aliases(newlist, options)
6846
 
+            
6847
 
+    def delete(self, args, options):
6848
 
+        """Delete the specified alias
6849
 
+
6850
 
+        :param args: The list of arguments
6851
 
+        :type args: list of str
6852
 
+        :param options: The commandline options
6853
 
+        """
6854
 
+        deleted = False
6855
 
+        if len(args) != 1:
6856
 
+            raise cmdutil.GetHelp
6857
 
+        newlist = ""
6858
 
+        for pair in self.get_iterator(options):
6859
 
+            if pair[0] != args[0]:
6860
 
+                newlist+="%s=%s\n" % (pair[0], pair[1])
6861
 
+            else:
6862
 
+                deleted = True
6863
 
+        if not deleted:
6864
 
+            raise errors.NoSuchAlias(args[0])
6865
 
+        self.write_aliases(newlist, options)
6866
 
+
6867
 
+    def get_alias_file(self, options):
6868
 
+        """Return the name of the alias file to use
6869
 
+
6870
 
+        :param options: The commandline options
6871
 
+        """
6872
 
+        if options.tree:
6873
 
+            if self.tree is None:
6874
 
+                self.tree == arch.tree_root()
6875
 
+            return str(self.tree)+"/{arch}/+aliases"
6876
 
+        else:
6877
 
+            return "~/.aba/aliases"
6878
 
+
6879
 
+    def get_iterator(self, options):
6880
 
+        """Return the alias iterator to use
6881
 
+
6882
 
+        :param options: The commandline options
6883
 
+        """
6884
 
+        return ancillary.iter_alias(self.get_alias_file(options))
6885
 
+
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
6891
 
+        """
6892
 
+        filename = os.path.expanduser(self.get_alias_file(options))
6893
 
+        file = cmdutil.NewFileVersion(filename)
6894
 
+        file.write(newlist)
6895
 
+        file.commit()
6896
 
+
6897
 
+
6898
 
+    def get_parser(self):
6899
 
+        """
6900
 
+        Returns the options parser to use for the "alias" command.
6901
 
+
6902
 
+        :rtype: cmdutil.CmdOptionParser
6903
 
+        """
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)
6910
 
+        return parser 
6911
 
+
6912
 
+    def help(self, parser=None):
6913
 
+        """
6914
 
+        Prints a help message.
6915
 
+
6916
 
+        :param parser: If supplied, the parser to use for generating help.  If \
6917
 
+        not supplied, it is retrieved.
6918
 
+        :type parser: cmdutil.CmdOptionParser
6919
 
+        """
6920
 
+        if parser==None:
6921
 
+            parser=self.get_parser()
6922
 
+        parser.print_help()
6923
 
+        print """
6924
 
+Lists current aliases or modifies the list of aliases.
6925
 
+
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.
6929
 
+
6930
 
+You can create aliases that refer to any fully-qualified part of the
6931
 
+Arch namespace, e.g. 
6932
 
+archive, 
6933
 
+archive/category, 
6934
 
+archive/category--branch, 
6935
 
+archive/category--branch--version (my favourite)
6936
 
+archive/category--branch--version--patchlevel
6937
 
+
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).
6941
 
+"""
6942
 
+
6943
 
+
6944
 
+class RequestMerge(BaseCommand):
6945
 
+    """Submit a merge request to Bug Goo"""
6946
 
+    def __init__(self):
6947
 
+        self.description=self.__doc__
6948
 
+
6949
 
+    def do_command(self, cmdargs):
6950
 
+        """Submit a merge request
6951
 
+
6952
 
+        :param cmdargs: The commandline arguments
6953
 
+        :type cmdargs: list of str
6954
 
+        """
6955
 
+        cmdutil.find_editor()
6956
 
+        parser = self.get_parser()
6957
 
+        (options, args) = parser.parse_args(cmdargs)
6958
 
+        try:
6959
 
+            self.tree=arch.tree_root()
6960
 
+        except:
6961
 
+            self.tree=None
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"
6970
 
+
6971
 
+    def make_headers(self, base, revisions):
6972
 
+        """Produce email and Bug Goo header strings
6973
 
+
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
6979
 
+        :rtype: str
6980
 
+        """
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
6985
 
+        else:
6986
 
+            headers += "Subject: [MERGE REQUEST]\n"
6987
 
+        headers += "\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"
6992
 
+        return headers
6993
 
+
6994
 
+    def make_summary(self, logs):
6995
 
+        """Generate a summary of merges
6996
 
+
6997
 
+        :param logs: the patchlogs that were directly added by the merges
6998
 
+        :type logs: list of `arch.Patchlog`
6999
 
+        :return: the summary
7000
 
+        :rtype: str
7001
 
+        """ 
7002
 
+        summary = ""
7003
 
+        for log in logs:
7004
 
+            summary+=str(log.revision)+"\n"
7005
 
+            summary+=log.summary+"\n"
7006
 
+            if log.description.strip():
7007
 
+                summary+=log.description.strip('\n')+"\n\n"
7008
 
+        return summary
7009
 
+
7010
 
+    def revision_specs(self, args):
7011
 
+        """Determine the base and merge revisions from tree and arguments.
7012
 
+
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`
7017
 
+        """
7018
 
+        if len(args) > 0:
7019
 
+            target_revision = cmdutil.determine_revision_arch(self.tree, 
7020
 
+                                                              args[0])
7021
 
+        else:
7022
 
+            target_revision = cmdutil.tree_latest(self.tree)
7023
 
+        if len(args) > 1:
7024
 
+            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
7025
 
+                       self.tree, f)) for f in args[1:] ]
7026
 
+        else:
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, 
7031
 
+                                                 False)
7032
 
+            merges = [f for f in cmdutil.direct_merges(merge_iter)]
7033
 
+        return (target_revision, merges)
7034
 
+
7035
 
+    def edit_message(self, message):
7036
 
+        """Edit an email message in the user's standard editor
7037
 
+
7038
 
+        :param message: The message to edit
7039
 
+        :type message: str
7040
 
+        :return: the path of the edited message
7041
 
+        :rtype: str
7042
 
+        """
7043
 
+        if self.tree is None:
7044
 
+            path = os.get_cwd()
7045
 
+        else:
7046
 
+            path = self.tree
7047
 
+        path += "/,merge-request"
7048
 
+        file = open(path, 'w')
7049
 
+        file.write(message)
7050
 
+        file.flush()
7051
 
+        cmdutil.invoke_editor(path)
7052
 
+        return path
7053
 
+
7054
 
+    def tidy_message(self, path):
7055
 
+        """Validate and clean up message.
7056
 
+
7057
 
+        :param path: The path to the message to clean up
7058
 
+        :type path: str
7059
 
+        :return: The parsed message
7060
 
+        :rtype: `email.Message`
7061
 
+        """
7062
 
+        mail = email.message_from_file(open(path))
7063
 
+        if mail["Subject"].strip() == "[MERGE REQUEST]":
7064
 
+            raise BlandSubject
7065
 
+        
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())
7071
 
+        return mail
7072
 
+
7073
 
+    def send_message(self, message):
7074
 
+        """Send a message, using its headers to address it.
7075
 
+
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())
7080
 
+        server.quit()
7081
 
+
7082
 
+    def help(self, parser=None):
7083
 
+        """Print a usage message
7084
 
+
7085
 
+        :param parser: The options parser to use
7086
 
+        :type parser: `cmdutil.CmdOptionParser`
7087
 
+        """
7088
 
+        if parser is None:
7089
 
+            parser = self.get_parser()
7090
 
+        parser.print_help()
7091
 
+        print """
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.
7095
 
+
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
7099
 
+revisions.
7100
 
+"""
7101
 
+
7102
 
+    def get_parser(self):
7103
 
+        """Produce a commandline parser for this command.
7104
 
+
7105
 
+        :rtype: `cmdutil.CmdOptionParser`
7106
 
+        """
7107
 
+        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
7108
 
+        return parser
7109
 
+
7110
 
+commands = { 
7111
 
+'changes' : Changes,
7112
 
+'help' : Help,
7113
 
+'update': Update,
7114
 
+'apply-changes':ApplyChanges,
7115
 
+'cat-log': CatLog,
7116
 
+'commit': Commit,
7117
 
+'revision': Revision,
7118
 
+'revisions': Revisions,
7119
 
+'get': Get,
7120
 
+'revert': Revert,
7121
 
+'shell': Shell,
7122
 
+'add-id': AddID,
7123
 
+'merge': Merge,
7124
 
+'elog': ELog,
7125
 
+'mirror-archive': MirrorArchive,
7126
 
+'ninventory': Inventory,
7127
 
+'alias' : Alias,
7128
 
+'request-merge': RequestMerge,
7129
 
+}
7130
 
+suggestions = {
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"
7152
 
+}
7153
 
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
7154
 
 
7155
 
*** modified file 'bzrlib/branch.py'
7156
 
--- bzrlib/branch.py 
7157
 
+++ bzrlib/branch.py 
7158
 
@@ -31,6 +31,8 @@
7159
 
 from revision import Revision
7160
 
 from errors import bailout, BzrError
7161
 
 from textui import show_status
7162
 
+import patches
7163
 
+from bzrlib import progress
7164
 
 
7165
 
 BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
7166
 
 ## TODO: Maybe include checks for common corruption of newlines, etc?
7167
 
@@ -864,3 +866,36 @@
7168
 
 
7169
 
     s = hexlify(rand_bytes(8))
7170
 
     return '-'.join((name, compact_date(time.time()), s))
7171
 
+
7172
 
+
7173
 
+def iter_anno_data(branch, file_id):
7174
 
+    later_revision = branch.revno()
7175
 
+    q = range(branch.revno())
7176
 
+    q.reverse()
7177
 
+    later_text_id = branch.basis_tree().inventory[file_id].text_id
7178
 
+    i = 0
7179
 
+    for revno in q:
7180
 
+        i += 1
7181
 
+        cur_tree = branch.revision_tree(branch.lookup_revision(revno))
7182
 
+        if file_id not in cur_tree.inventory:
7183
 
+            text_id = None
7184
 
+        else:
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)
7192
 
+
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()
7198
 
+    else:
7199
 
+        old_file = []
7200
 
+    ud = difflib.unified_diff(old_file, new_tree.get_file(file_id).readlines())
7201
 
+    return patches.parse_patch(ud)
7202
 
+
7203
 
+
7204
 
 
7205
 
*** modified file 'bzrlib/commands.py'
7206
 
--- bzrlib/commands.py 
7207
 
+++ bzrlib/commands.py 
7208
 
@@ -27,6 +27,9 @@
7209
 
 from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
7210
 
      format_date
7211
 
 from bzrlib import merge
7212
 
+from bzrlib.branch import iter_anno_data
7213
 
+from bzrlib import patches
7214
 
+from bzrlib import progress
7215
 
 
7216
 
 
7217
 
 def _squish_command_name(cmd):
7218
 
@@ -882,7 +885,15 @@
7219
 
                 print '%3d FAILED!' % mf
7220
 
             else:
7221
 
                 print
7222
 
-
7223
 
+        result = bzrlib.patches.test()
7224
 
+        resultFailed = len(result.errors) + len(result.failures)
7225
 
+        print '%-40s %3d tests' % ('bzrlib.patches', result.testsRun),
7226
 
+        if resultFailed:
7227
 
+            print '%3d FAILED!' % resultFailed
7228
 
+        else:
7229
 
+            print
7230
 
+        tests += result.testsRun
7231
 
+        failures += resultFailed
7232
 
         print '%-40s %3d tests' % ('total', tests),
7233
 
         if failures:
7234
 
             print '%3d FAILED!' % failures
7235
 
@@ -897,6 +908,34 @@
7236
 
     """Show version of bzr"""
7237
 
     def run(self):
7238
 
         show_version()
7239
 
+
7240
 
+class cmd_annotate(Command):
7241
 
+    """Show which revision added each line in a file"""
7242
 
+
7243
 
+    takes_args = ['filename']
7244
 
+    def run(self, filename):
7245
 
+        if not os.path.exists(filename):
7246
 
+            raise BzrCommandError("The file %s does not exist." % filename)
7247
 
+        branch = (Branch(filename))
7248
 
+        file_id = branch.working_tree().path2id(filename)
7249
 
+        if file_id is None:
7250
 
+            raise BzrCommandError("The file %s is not versioned." % filename)
7251
 
+        lines = branch.basis_tree().get_file(file_id)
7252
 
+        total = branch.revno()
7253
 
+        anno_d_iter = iter_anno_data(branch, file_id)
7254
 
+        progress_bar = progress.ProgressBar()
7255
 
+        try:
7256
 
+            for result in patches.iter_annotate_file(lines, anno_d_iter):
7257
 
+                if isinstance(result, progress.Progress):
7258
 
+                    result.total = total
7259
 
+                    progress_bar(result)
7260
 
+                else:
7261
 
+                    anno_lines = result
7262
 
+        finally:
7263
 
+            progress.clear_progress_bar()
7264
 
+        for line in anno_lines:
7265
 
+            sys.stdout.write("%4s:%s" % (str(line.log), line.text))
7266
 
+
7267
 
 
7268
 
 def show_version():
7269
 
     print "bzr (bazaar-ng) %s" % bzrlib.__version__
7270