/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
644 by Martin Pool
- add aaron's pending patch for annotate
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 file 'bzrlib/progress.py'
504
--- /dev/null 
505
+++ bzrlib/progress.py 
506
@@ -0,0 +1,91 @@
507
+# Copyright (C) 2005 Aaron Bentley
508
+# <aaron.bentley@utoronto.ca>
509
+#
510
+#    This program is free software; you can redistribute it and/or modify
511
+#    it under the terms of the GNU General Public License as published by
512
+#    the Free Software Foundation; either version 2 of the License, or
513
+#    (at your option) any later version.
514
+#
515
+#    This program is distributed in the hope that it will be useful,
516
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
517
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
518
+#    GNU General Public License for more details.
519
+#
520
+#    You should have received a copy of the GNU General Public License
521
+#    along with this program; if not, write to the Free Software
522
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
523
+
524
+import sys
525
+
526
+class Progress(object):
527
+    def __init__(self, units, current, total=None):
528
+        self.units = units
529
+        self.current = current
530
+        self.total = total
531
+        self.percent = None
532
+        if self.total is not None:
533
+            self.percent = 100.0 * current / total
534
+
535
+    def __str__(self):
536
+        if self.total is not None:
537
+            return "%i of %i %s %.1f%%" % (self.current, self.total, self.units,
538
+                                         self.percent)
539
+        else:
540
+            return "%i %s" (self.current, self.units) 
541
+
542
+
543
+def progress_bar(progress):
544
+    fmt = " %i of %i %s (%.1f%%)"
545
+    f = fmt % (progress.total, progress.total, progress.units, 100.0)
546
+    max = len(f)
547
+    cols = 77 - max
548
+    markers = int (float(cols) * progress.current / progress.total)
549
+    txt = fmt % (progress.current, progress.total, progress.units,
550
+                 progress.percent)
551
+    sys.stderr.write("\r[%s%s]%s" % ('='*markers, ' '*(cols-markers), txt))
552
+
553
+def clear_progress_bar():
554
+    sys.stderr.write('\r%s\r' % (' '*79))
555
+
556
+def spinner_str(progress, show_text=False):
557
+    """
558
+    Produces the string for a textual "spinner" progress indicator
559
+    :param progress: an object represinting current progress
560
+    :param show_text: If true, show progress text as well
561
+    :return: The spinner string
562
+
563
+    >>> spinner_str(Progress("baloons", 0))
564
+    '|'
565
+    >>> spinner_str(Progress("baloons", 5))
566
+    '/'
567
+    >>> spinner_str(Progress("baloons", 6), show_text=True)
568
+    '- 6 baloons'
569
+    """
570
+    positions = ('|', '/', '-', '\\')
571
+    text = positions[progress.current % 4]
572
+    if show_text:
573
+        text+=" %i %s" % (progress.current, progress.units)
574
+    return text
575
+
576
+def spinner(progress, show_text=False, output=sys.stderr):
577
+    """
578
+    Update a spinner progress indicator on an output
579
+    :param progress: The progress to display
580
+    :param show_text: If true, show text as well as spinner
581
+    :param output: The output to write to
582
+
583
+    >>> spinner(Progress("baloons", 6), show_text=True, output=sys.stdout)
584
+    \r- 6 baloons
585
+    """
586
+    output.write('\r%s' % spinner_str(progress, show_text))
587
+
588
+def run_tests():
589
+    import doctest
590
+    result = doctest.testmod()
591
+    if result[1] > 0:
592
+        if result[0] == 0:
593
+            print "All tests passed"
594
+    else:
595
+        print "No tests to run"
596
+if __name__ == "__main__":
597
+    run_tests()
598
599
*** added directory 'testdata'
600
*** added file 'testdata/diff'
601
--- /dev/null 
602
+++ testdata/diff 
603
@@ -0,0 +1,1154 @@
604
+--- orig/commands.py
605
++++ mod/commands.py
606
+@@ -19,25 +19,31 @@
607
+ import arch
608
+ import arch.util
609
+ import arch.arch
610
++
611
++import pylon.errors
612
++from pylon.errors import *
613
++from pylon import errors
614
++from pylon import util
615
++from pylon import arch_core
616
++from pylon import arch_compound
617
++from pylon import ancillary
618
++from pylon import misc
619
++from pylon import paths 
620
++
621
+ import abacmds
622
+ import cmdutil
623
+ import shutil
624
+ import os
625
+ import options
626
+-import paths 
627
+ import time
628
+ import cmd
629
+ import readline
630
+ import re
631
+ import string
632
+-import arch_core
633
+-from errors import *
634
+-import errors
635
+ import terminal
636
+-import ancillary
637
+-import misc
638
+ import email
639
+ import smtplib
640
++import textwrap
641
+ 
642
+ __docformat__ = "restructuredtext"
643
+ __doc__ = "Implementation of user (sub) commands"
644
+@@ -257,7 +263,7 @@
645
+ 
646
+         tree=arch.tree_root()
647
+         if len(args) == 0:
648
+-            a_spec = cmdutil.comp_revision(tree)
649
++            a_spec = ancillary.comp_revision(tree)
650
+         else:
651
+             a_spec = cmdutil.determine_revision_tree(tree, args[0])
652
+         cmdutil.ensure_archive_registered(a_spec.archive)
653
+@@ -284,7 +290,7 @@
654
+             changeset=options.changeset
655
+             tmpdir = None
656
+         else:
657
+-            tmpdir=cmdutil.tmpdir()
658
++            tmpdir=util.tmpdir()
659
+             changeset=tmpdir+"/changeset"
660
+         try:
661
+             delta=arch.iter_delta(a_spec, b_spec, changeset)
662
+@@ -304,14 +310,14 @@
663
+             if status > 1:
664
+                 return
665
+             if (options.perform_diff):
666
+-                chan = cmdutil.ChangesetMunger(changeset)
667
++                chan = arch_compound.ChangesetMunger(changeset)
668
+                 chan.read_indices()
669
+-                if isinstance(b_spec, arch.Revision):
670
+-                    b_dir = b_spec.library_find()
671
+-                else:
672
+-                    b_dir = b_spec
673
+-                a_dir = a_spec.library_find()
674
+                 if options.diffopts is not None:
675
++                    if isinstance(b_spec, arch.Revision):
676
++                        b_dir = b_spec.library_find()
677
++                    else:
678
++                        b_dir = b_spec
679
++                    a_dir = a_spec.library_find()
680
+                     diffopts = options.diffopts.split()
681
+                     cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
682
+                 else:
683
+@@ -517,7 +523,7 @@
684
+         except arch.errors.TreeRootError, e:
685
+             print e
686
+             return
687
+-        from_revision=cmdutil.tree_latest(tree)
688
++        from_revision = arch_compound.tree_latest(tree)
689
+         if from_revision==to_revision:
690
+             print "Tree is already up to date with:\n"+str(to_revision)+"."
691
+             return
692
+@@ -592,6 +598,9 @@
693
+ 
694
+         if len(args) == 0:
695
+             args = None
696
++        if options.version is None:
697
++            return options, tree.tree_version, args
698
++
699
+         revision=cmdutil.determine_revision_arch(tree, options.version)
700
+         return options, revision.get_version(), args
701
+ 
702
+@@ -601,11 +610,16 @@
703
+         """
704
+         tree=arch.tree_root()
705
+         options, version, files = self.parse_commandline(cmdargs, tree)
706
++        ancestor = None
707
+         if options.__dict__.has_key("base") and options.base:
708
+             base = cmdutil.determine_revision_tree(tree, options.base)
709
++            ancestor = base
710
+         else:
711
+-            base = cmdutil.submit_revision(tree)
712
+-        
713
++            base = ancillary.submit_revision(tree)
714
++            ancestor = base
715
++        if ancestor is None:
716
++            ancestor = arch_compound.tree_latest(tree, version)
717
++
718
+         writeversion=version
719
+         archive=version.archive
720
+         source=cmdutil.get_mirror_source(archive)
721
+@@ -625,18 +639,26 @@
722
+         try:
723
+             last_revision=tree.iter_logs(version, True).next().revision
724
+         except StopIteration, e:
725
+-            if cmdutil.prompt("Import from commit"):
726
+-                return do_import(version)
727
+-            else:
728
+-                raise NoVersionLogs(version)
729
+-        if last_revision!=version.iter_revisions(True).next():
730
++            last_revision = None
731
++            if ancestor is None:
732
++                if cmdutil.prompt("Import from commit"):
733
++                    return do_import(version)
734
++                else:
735
++                    raise NoVersionLogs(version)
736
++        try:
737
++            arch_last_revision = version.iter_revisions(True).next()
738
++        except StopIteration, e:
739
++            arch_last_revision = None
740
++ 
741
++        if last_revision != arch_last_revision:
742
++            print "Tree is not up to date with %s" % str(version)
743
+             if not cmdutil.prompt("Out of date"):
744
+                 raise OutOfDate
745
+             else:
746
+                 allow_old=True
747
+ 
748
+         try:
749
+-            if not cmdutil.has_changed(version):
750
++            if not cmdutil.has_changed(ancestor):
751
+                 if not cmdutil.prompt("Empty commit"):
752
+                     raise EmptyCommit
753
+         except arch.util.ExecProblem, e:
754
+@@ -645,15 +667,15 @@
755
+                 raise MissingID(e)
756
+             else:
757
+                 raise
758
+-        log = tree.log_message(create=False)
759
++        log = tree.log_message(create=False, version=version)
760
+         if log is None:
761
+             try:
762
+                 if cmdutil.prompt("Create log"):
763
+-                    edit_log(tree)
764
++                    edit_log(tree, version)
765
+ 
766
+             except cmdutil.NoEditorSpecified, e:
767
+                 raise CommandFailed(e)
768
+-            log = tree.log_message(create=False)
769
++            log = tree.log_message(create=False, version=version)
770
+         if log is None: 
771
+             raise NoLogMessage
772
+         if log["Summary"] is None or len(log["Summary"].strip()) == 0:
773
+@@ -837,23 +859,24 @@
774
+             if spec is not None:
775
+                 revision = cmdutil.determine_revision_tree(tree, spec)
776
+             else:
777
+-                revision = cmdutil.comp_revision(tree)
778
++                revision = ancillary.comp_revision(tree)
779
+         except cmdutil.CantDetermineRevision, e:
780
+             raise CommandFailedWrapper(e)
781
+         munger = None
782
+ 
783
+         if options.file_contents or options.file_perms or options.deletions\
784
+             or options.additions or options.renames or options.hunk_prompt:
785
+-            munger = cmdutil.MungeOpts()
786
+-            munger.hunk_prompt = options.hunk_prompt
787
++            munger = arch_compound.MungeOpts()
788
++            munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
789
++                                   options.hunk_prompt)
790
+ 
791
+         if len(args) > 0 or options.logs or options.pattern_files or \
792
+             options.control:
793
+             if munger is None:
794
+-                munger = cmdutil.MungeOpts(True)
795
++                munger = cmdutil.arch_compound.MungeOpts(True)
796
+                 munger.all_types(True)
797
+         if len(args) > 0:
798
+-            t_cwd = cmdutil.tree_cwd(tree)
799
++            t_cwd = arch_compound.tree_cwd(tree)
800
+             for name in args:
801
+                 if len(t_cwd) > 0:
802
+                     t_cwd += "/"
803
+@@ -878,7 +901,7 @@
804
+         if options.pattern_files:
805
+             munger.add_keep_pattern(options.pattern_files)
806
+                 
807
+-        for line in cmdutil.revert(tree, revision, munger, 
808
++        for line in arch_compound.revert(tree, revision, munger, 
809
+                                    not options.no_output):
810
+             cmdutil.colorize(line)
811
+ 
812
+@@ -1042,18 +1065,13 @@
813
+         help_tree_spec()
814
+         return
815
+ 
816
+-def require_version_exists(version, spec):
817
+-    if not version.exists():
818
+-        raise cmdutil.CantDetermineVersion(spec, 
819
+-                                           "The version %s does not exist." \
820
+-                                           % version)
821
+-
822
+ class Revisions(BaseCommand):
823
+     """
824
+     Print a revision name based on a revision specifier
825
+     """
826
+     def __init__(self):
827
+         self.description="Lists revisions"
828
++        self.cl_revisions = []
829
+     
830
+     def do_command(self, cmdargs):
831
+         """
832
+@@ -1066,224 +1084,68 @@
833
+             self.tree = arch.tree_root()
834
+         except arch.errors.TreeRootError:
835
+             self.tree = None
836
++        if options.type == "default":
837
++            options.type = "archive"
838
+         try:
839
+-            iter = self.get_iterator(options.type, args, options.reverse, 
840
+-                                     options.modified)
841
++            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
842
++                                             options.reverse, options.modified,
843
++                                             options.shallow)
844
+         except cmdutil.CantDetermineRevision, e:
845
+             raise CommandFailedWrapper(e)
846
+-
847
++        except cmdutil.CantDetermineVersion, e:
848
++            raise CommandFailedWrapper(e)
849
+         if options.skip is not None:
850
+             iter = cmdutil.iter_skip(iter, int(options.skip))
851
+ 
852
+-        for revision in iter:
853
+-            log = None
854
+-            if isinstance(revision, arch.Patchlog):
855
+-                log = revision
856
+-                revision=revision.revision
857
+-            print options.display(revision)
858
+-            if log is None and (options.summary or options.creator or 
859
+-                                options.date or options.merges):
860
+-                log = revision.patchlog
861
+-            if options.creator:
862
+-                print "    %s" % log.creator
863
+-            if options.date:
864
+-                print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
865
+-            if options.summary:
866
+-                print "    %s" % log.summary
867
+-            if options.merges:
868
+-                showed_title = False
869
+-                for revision in log.merged_patches:
870
+-                    if not showed_title:
871
+-                        print "    Merged:"
872
+-                        showed_title = True
873
+-                    print "    %s" % revision
874
+-
875
+-    def get_iterator(self, type, args, reverse, modified):
876
+-        if len(args) > 0:
877
+-            spec = args[0]
878
+-        else:
879
+-            spec = None
880
+-        if modified is not None:
881
+-            iter = cmdutil.modified_iter(modified, self.tree)
882
+-            if reverse:
883
+-                return iter
884
+-            else:
885
+-                return cmdutil.iter_reverse(iter)
886
+-        elif type == "archive":
887
+-            if spec is None:
888
+-                if self.tree is None:
889
+-                    raise cmdutil.CantDetermineRevision("", 
890
+-                                                        "Not in a project tree")
891
+-                version = cmdutil.determine_version_tree(spec, self.tree)
892
+-            else:
893
+-                version = cmdutil.determine_version_arch(spec, self.tree)
894
+-                cmdutil.ensure_archive_registered(version.archive)
895
+-                require_version_exists(version, spec)
896
+-            return version.iter_revisions(reverse)
897
+-        elif type == "cacherevs":
898
+-            if spec is None:
899
+-                if self.tree is None:
900
+-                    raise cmdutil.CantDetermineRevision("", 
901
+-                                                        "Not in a project tree")
902
+-                version = cmdutil.determine_version_tree(spec, self.tree)
903
+-            else:
904
+-                version = cmdutil.determine_version_arch(spec, self.tree)
905
+-                cmdutil.ensure_archive_registered(version.archive)
906
+-                require_version_exists(version, spec)
907
+-            return cmdutil.iter_cacherevs(version, reverse)
908
+-        elif type == "library":
909
+-            if spec is None:
910
+-                if self.tree is None:
911
+-                    raise cmdutil.CantDetermineRevision("", 
912
+-                                                        "Not in a project tree")
913
+-                version = cmdutil.determine_version_tree(spec, self.tree)
914
+-            else:
915
+-                version = cmdutil.determine_version_arch(spec, self.tree)
916
+-            return version.iter_library_revisions(reverse)
917
+-        elif type == "logs":
918
+-            if self.tree is None:
919
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
920
+-            return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
921
+-                                  self.tree), reverse)
922
+-        elif type == "missing" or type == "skip-present":
923
+-            if self.tree is None:
924
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
925
+-            skip = (type == "skip-present")
926
+-            version = cmdutil.determine_version_tree(spec, self.tree)
927
+-            cmdutil.ensure_archive_registered(version.archive)
928
+-            require_version_exists(version, spec)
929
+-            return cmdutil.iter_missing(self.tree, version, reverse,
930
+-                                        skip_present=skip)
931
+-
932
+-        elif type == "present":
933
+-            if self.tree is None:
934
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
935
+-            version = cmdutil.determine_version_tree(spec, self.tree)
936
+-            cmdutil.ensure_archive_registered(version.archive)
937
+-            require_version_exists(version, spec)
938
+-            return cmdutil.iter_present(self.tree, version, reverse)
939
+-
940
+-        elif type == "new-merges" or type == "direct-merges":
941
+-            if self.tree is None:
942
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
943
+-            version = cmdutil.determine_version_tree(spec, self.tree)
944
+-            cmdutil.ensure_archive_registered(version.archive)
945
+-            require_version_exists(version, spec)
946
+-            iter = cmdutil.iter_new_merges(self.tree, version, reverse)
947
+-            if type == "new-merges":
948
+-                return iter
949
+-            elif type == "direct-merges":
950
+-                return cmdutil.direct_merges(iter)
951
+-
952
+-        elif type == "missing-from":
953
+-            if self.tree is None:
954
+-                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
955
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
956
+-            libtree = cmdutil.find_or_make_local_revision(revision)
957
+-            return cmdutil.iter_missing(libtree, self.tree.tree_version,
958
+-                                        reverse)
959
+-
960
+-        elif type == "partner-missing":
961
+-            return cmdutil.iter_partner_missing(self.tree, reverse)
962
+-
963
+-        elif type == "ancestry":
964
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
965
+-            iter = cmdutil._iter_ancestry(self.tree, revision)
966
+-            if reverse:
967
+-                return iter
968
+-            else:
969
+-                return cmdutil.iter_reverse(iter)
970
+-
971
+-        elif type == "dependencies" or type == "non-dependencies":
972
+-            nondeps = (type == "non-dependencies")
973
+-            revision = cmdutil.determine_revision_tree(self.tree, spec)
974
+-            anc_iter = cmdutil._iter_ancestry(self.tree, revision)
975
+-            iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
976
+-            if reverse:
977
+-                return iter_depends
978
+-            else:
979
+-                return cmdutil.iter_reverse(iter_depends)
980
+-        elif type == "micro":
981
+-            return cmdutil.iter_micro(self.tree)
982
+-
983
+-    
984
++        try:
985
++            for revision in iter:
986
++                log = None
987
++                if isinstance(revision, arch.Patchlog):
988
++                    log = revision
989
++                    revision=revision.revision
990
++                out = options.display(revision)
991
++                if out is not None:
992
++                    print out
993
++                if log is None and (options.summary or options.creator or 
994
++                                    options.date or options.merges):
995
++                    log = revision.patchlog
996
++                if options.creator:
997
++                    print "    %s" % log.creator
998
++                if options.date:
999
++                    print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
1000
++                if options.summary:
1001
++                    print "    %s" % log.summary
1002
++                if options.merges:
1003
++                    showed_title = False
1004
++                    for revision in log.merged_patches:
1005
++                        if not showed_title:
1006
++                            print "    Merged:"
1007
++                            showed_title = True
1008
++                        print "    %s" % revision
1009
++            if len(self.cl_revisions) > 0:
1010
++                print pylon.changelog_for_merge(self.cl_revisions)
1011
++        except pylon.errors.TreeRootNone:
1012
++            raise CommandFailedWrapper(
1013
++                Exception("This option can only be used in a project tree."))
1014
++
1015
++    def changelog_append(self, revision):
1016
++        if isinstance(revision, arch.Revision):
1017
++            revision=arch.Patchlog(revision)
1018
++        self.cl_revisions.append(revision)
1019
++   
1020
+     def get_parser(self):
1021
+         """
1022
+         Returns the options parser to use for the "revision" command.
1023
+ 
1024
+         :rtype: cmdutil.CmdOptionParser
1025
+         """
1026
+-        parser=cmdutil.CmdOptionParser("fai revisions [revision]")
1027
++        parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
1028
+         select = cmdutil.OptionGroup(parser, "Selection options",
1029
+                           "Control which revisions are listed.  These options"
1030
+                           " are mutually exclusive.  If more than one is"
1031
+                           " specified, the last is used.")
1032
+-        select.add_option("", "--archive", action="store_const", 
1033
+-                          const="archive", dest="type", default="archive",
1034
+-                          help="List all revisions in the archive")
1035
+-        select.add_option("", "--cacherevs", action="store_const", 
1036
+-                          const="cacherevs", dest="type",
1037
+-                          help="List all revisions stored in the archive as "
1038
+-                          "complete copies")
1039
+-        select.add_option("", "--logs", action="store_const", 
1040
+-                          const="logs", dest="type",
1041
+-                          help="List revisions that have a patchlog in the "
1042
+-                          "tree")
1043
+-        select.add_option("", "--missing", action="store_const", 
1044
+-                          const="missing", dest="type",
1045
+-                          help="List revisions from the specified version that"
1046
+-                          " have no patchlog in the tree")
1047
+-        select.add_option("", "--skip-present", action="store_const", 
1048
+-                          const="skip-present", dest="type",
1049
+-                          help="List revisions from the specified version that"
1050
+-                          " have no patchlogs at all in the tree")
1051
+-        select.add_option("", "--present", action="store_const", 
1052
+-                          const="present", dest="type",
1053
+-                          help="List revisions from the specified version that"
1054
+-                          " have no patchlog in the tree, but can't be merged")
1055
+-        select.add_option("", "--missing-from", action="store_const", 
1056
+-                          const="missing-from", dest="type",
1057
+-                          help="List revisions from the specified revision "
1058
+-                          "that have no patchlog for the tree version")
1059
+-        select.add_option("", "--partner-missing", action="store_const", 
1060
+-                          const="partner-missing", dest="type",
1061
+-                          help="List revisions in partner versions that are"
1062
+-                          " missing")
1063
+-        select.add_option("", "--new-merges", action="store_const", 
1064
+-                          const="new-merges", dest="type",
1065
+-                          help="List revisions that have had patchlogs added"
1066
+-                          " to the tree since the last commit")
1067
+-        select.add_option("", "--direct-merges", action="store_const", 
1068
+-                          const="direct-merges", dest="type",
1069
+-                          help="List revisions that have been directly added"
1070
+-                          " to tree since the last commit ")
1071
+-        select.add_option("", "--library", action="store_const", 
1072
+-                          const="library", dest="type",
1073
+-                          help="List revisions in the revision library")
1074
+-        select.add_option("", "--ancestry", action="store_const", 
1075
+-                          const="ancestry", dest="type",
1076
+-                          help="List revisions that are ancestors of the "
1077
+-                          "current tree version")
1078
+-
1079
+-        select.add_option("", "--dependencies", action="store_const", 
1080
+-                          const="dependencies", dest="type",
1081
+-                          help="List revisions that the given revision "
1082
+-                          "depends on")
1083
+-
1084
+-        select.add_option("", "--non-dependencies", action="store_const", 
1085
+-                          const="non-dependencies", dest="type",
1086
+-                          help="List revisions that the given revision "
1087
+-                          "does not depend on")
1088
+-
1089
+-        select.add_option("--micro", action="store_const", 
1090
+-                          const="micro", dest="type",
1091
+-                          help="List partner revisions aimed for this "
1092
+-                          "micro-branch")
1093
+-
1094
+-        select.add_option("", "--modified", dest="modified", 
1095
+-                          help="List tree ancestor revisions that modified a "
1096
+-                          "given file", metavar="FILE[:LINE]")
1097
+ 
1098
++        cmdutil.add_revision_iter_options(select)
1099
+         parser.add_option("", "--skip", dest="skip", 
1100
+                           help="Skip revisions.  Positive numbers skip from "
1101
+                           "beginning, negative skip from end.",
1102
+@@ -1312,6 +1174,9 @@
1103
+         format.add_option("--cacherev", action="store_const", 
1104
+                          const=paths.determine_cacherev_path, dest="display",
1105
+                          help="Show location of cacherev file")
1106
++        format.add_option("--changelog", action="store_const", 
1107
++                         const=self.changelog_append, dest="display",
1108
++                         help="Show location of cacherev file")
1109
+         parser.add_option_group(format)
1110
+         display = cmdutil.OptionGroup(parser, "Display format options",
1111
+                           "These control the display of data")
1112
+@@ -1448,6 +1313,7 @@
1113
+         if os.access(self.history_file, os.R_OK) and \
1114
+             os.path.isfile(self.history_file):
1115
+             readline.read_history_file(self.history_file)
1116
++        self.cwd = os.getcwd()
1117
+ 
1118
+     def write_history(self):
1119
+         readline.write_history_file(self.history_file)
1120
+@@ -1470,16 +1336,21 @@
1121
+     def set_prompt(self):
1122
+         if self.tree is not None:
1123
+             try:
1124
+-                version = " "+self.tree.tree_version.nonarch
1125
++                prompt = pylon.alias_or_version(self.tree.tree_version, 
1126
++                                                self.tree, 
1127
++                                                full=False)
1128
++                if prompt is not None:
1129
++                    prompt = " " + prompt
1130
+             except:
1131
+-                version = ""
1132
++                prompt = ""
1133
+         else:
1134
+-            version = ""
1135
+-        self.prompt = "Fai%s> " % version
1136
++            prompt = ""
1137
++        self.prompt = "Fai%s> " % prompt
1138
+ 
1139
+     def set_title(self, command=None):
1140
+         try:
1141
+-            version = self.tree.tree_version.nonarch
1142
++            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
1143
++                                             full=False)
1144
+         except:
1145
+             version = "[no version]"
1146
+         if command is None:
1147
+@@ -1489,8 +1360,15 @@
1148
+     def do_cd(self, line):
1149
+         if line == "":
1150
+             line = "~"
1151
++        line = os.path.expanduser(line)
1152
++        if os.path.isabs(line):
1153
++            newcwd = line
1154
++        else:
1155
++            newcwd = self.cwd+'/'+line
1156
++        newcwd = os.path.normpath(newcwd)
1157
+         try:
1158
+-            os.chdir(os.path.expanduser(line))
1159
++            os.chdir(newcwd)
1160
++            self.cwd = newcwd
1161
+         except Exception, e:
1162
+             print e
1163
+         try:
1164
+@@ -1523,7 +1401,7 @@
1165
+             except cmdutil.CantDetermineRevision, e:
1166
+                 print e
1167
+             except Exception, e:
1168
+-                print "Unhandled error:\n%s" % cmdutil.exception_str(e)
1169
++                print "Unhandled error:\n%s" % errors.exception_str(e)
1170
+ 
1171
+         elif suggestions.has_key(args[0]):
1172
+             print suggestions[args[0]]
1173
+@@ -1574,7 +1452,7 @@
1174
+                 arg = line.split()[-1]
1175
+             else:
1176
+                 arg = ""
1177
+-            iter = iter_munged_completions(iter, arg, text)
1178
++            iter = cmdutil.iter_munged_completions(iter, arg, text)
1179
+         except Exception, e:
1180
+             print e
1181
+         return list(iter)
1182
+@@ -1604,10 +1482,11 @@
1183
+                 else:
1184
+                     arg = ""
1185
+                 if arg.startswith("-"):
1186
+-                    return list(iter_munged_completions(iter, arg, text))
1187
++                    return list(cmdutil.iter_munged_completions(iter, arg, 
1188
++                                                                text))
1189
+                 else:
1190
+-                    return list(iter_munged_completions(
1191
+-                        iter_file_completions(arg), arg, text))
1192
++                    return list(cmdutil.iter_munged_completions(
1193
++                        cmdutil.iter_file_completions(arg), arg, text))
1194
+ 
1195
+ 
1196
+             elif cmd == "cd":
1197
+@@ -1615,13 +1494,13 @@
1198
+                     arg = args.split()[-1]
1199
+                 else:
1200
+                     arg = ""
1201
+-                iter = iter_dir_completions(arg)
1202
+-                iter = iter_munged_completions(iter, arg, text)
1203
++                iter = cmdutil.iter_dir_completions(arg)
1204
++                iter = cmdutil.iter_munged_completions(iter, arg, text)
1205
+                 return list(iter)
1206
+             elif len(args)>0:
1207
+                 arg = args.split()[-1]
1208
+-                return list(iter_munged_completions(iter_file_completions(arg),
1209
+-                                                    arg, text))
1210
++                iter = cmdutil.iter_file_completions(arg)
1211
++                return list(cmdutil.iter_munged_completions(iter, arg, text))
1212
+             else:
1213
+                 return self.completenames(text, line, begidx, endidx)
1214
+         except Exception, e:
1215
+@@ -1636,44 +1515,8 @@
1216
+             yield entry
1217
+ 
1218
+ 
1219
+-def iter_file_completions(arg, only_dirs = False):
1220
+-    """Generate an iterator that iterates through filename completions.
1221
+-
1222
+-    :param arg: The filename fragment to match
1223
+-    :type arg: str
1224
+-    :param only_dirs: If true, match only directories
1225
+-    :type only_dirs: bool
1226
+-    """
1227
+-    cwd = os.getcwd()
1228
+-    if cwd != "/":
1229
+-        extras = [".", ".."]
1230
+-    else:
1231
+-        extras = []
1232
+-    (dir, file) = os.path.split(arg)
1233
+-    if dir != "":
1234
+-        listingdir = os.path.expanduser(dir)
1235
+-    else:
1236
+-        listingdir = cwd
1237
+-    for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
1238
+-        if dir != "":
1239
+-            userfile = dir+'/'+file
1240
+-        else:
1241
+-            userfile = file
1242
+-        if userfile.startswith(arg):
1243
+-            if os.path.isdir(listingdir+'/'+file):
1244
+-                userfile+='/'
1245
+-                yield userfile
1246
+-            elif not only_dirs:
1247
+-                yield userfile
1248
+-
1249
+-def iter_munged_completions(iter, arg, text):
1250
+-    for completion in iter:
1251
+-        completion = str(completion)
1252
+-        if completion.startswith(arg):
1253
+-            yield completion[len(arg)-len(text):]
1254
+-
1255
+ def iter_source_file_completions(tree, arg):
1256
+-    treepath = cmdutil.tree_cwd(tree)
1257
++    treepath = arch_compound.tree_cwd(tree)
1258
+     if len(treepath) > 0:
1259
+         dirs = [treepath]
1260
+     else:
1261
+@@ -1701,7 +1544,7 @@
1262
+     :return: An iterator of all matching untagged files
1263
+     :rtype: iterator of str
1264
+     """
1265
+-    treepath = cmdutil.tree_cwd(tree)
1266
++    treepath = arch_compound.tree_cwd(tree)
1267
+     if len(treepath) > 0:
1268
+         dirs = [treepath]
1269
+     else:
1270
+@@ -1743,8 +1586,8 @@
1271
+     :param arg: The prefix to match
1272
+     :type arg: str
1273
+     """
1274
+-    treepath = cmdutil.tree_cwd(tree)
1275
+-    tmpdir = cmdutil.tmpdir()
1276
++    treepath = arch_compound.tree_cwd(tree)
1277
++    tmpdir = util.tmpdir()
1278
+     changeset = tmpdir+"/changeset"
1279
+     completions = []
1280
+     revision = cmdutil.determine_revision_tree(tree)
1281
+@@ -1756,14 +1599,6 @@
1282
+     shutil.rmtree(tmpdir)
1283
+     return completions
1284
+ 
1285
+-def iter_dir_completions(arg):
1286
+-    """Generate an iterator that iterates through directory name completions.
1287
+-
1288
+-    :param arg: The directory name fragment to match
1289
+-    :type arg: str
1290
+-    """
1291
+-    return iter_file_completions(arg, True)
1292
+-
1293
+ class Shell(BaseCommand):
1294
+     def __init__(self):
1295
+         self.description = "Runs Fai as a shell"
1296
+@@ -1795,7 +1630,11 @@
1297
+         parser=self.get_parser()
1298
+         (options, args) = parser.parse_args(cmdargs)
1299
+ 
1300
+-        tree = arch.tree_root()
1301
++        try:
1302
++            tree = arch.tree_root()
1303
++        except arch.errors.TreeRootError, e:
1304
++            raise pylon.errors.CommandFailedWrapper(e)
1305
++            
1306
+ 
1307
+         if (len(args) == 0) == (options.untagged == False):
1308
+             raise cmdutil.GetHelp
1309
+@@ -1809,13 +1648,22 @@
1310
+         if options.id_type == "tagline":
1311
+             if method != "tagline":
1312
+                 if not cmdutil.prompt("Tagline in other tree"):
1313
+-                    if method == "explicit":
1314
+-                        options.id_type == explicit
1315
++                    if method == "explicit" or method == "implicit":
1316
++                        options.id_type == method
1317
+                     else:
1318
+                         print "add-id not supported for \"%s\" tagging method"\
1319
+                             % method 
1320
+                         return
1321
+         
1322
++        elif options.id_type == "implicit":
1323
++            if method != "implicit":
1324
++                if not cmdutil.prompt("Implicit in other tree"):
1325
++                    if method == "explicit" or method == "tagline":
1326
++                        options.id_type == method
1327
++                    else:
1328
++                        print "add-id not supported for \"%s\" tagging method"\
1329
++                            % method 
1330
++                        return
1331
+         elif options.id_type == "explicit":
1332
+             if method != "tagline" and method != explicit:
1333
+                 if not prompt("Explicit in other tree"):
1334
+@@ -1824,7 +1672,8 @@
1335
+                     return
1336
+         
1337
+         if options.id_type == "auto":
1338
+-            if method != "tagline" and method != "explicit":
1339
++            if method != "tagline" and method != "explicit" \
1340
++                and method !="implicit":
1341
+                 print "add-id not supported for \"%s\" tagging method" % method
1342
+                 return
1343
+             else:
1344
+@@ -1852,10 +1701,12 @@
1345
+             previous_files.extend(files)
1346
+             if id_type == "explicit":
1347
+                 cmdutil.add_id(files)
1348
+-            elif id_type == "tagline":
1349
++            elif id_type == "tagline" or id_type == "implicit":
1350
+                 for file in files:
1351
+                     try:
1352
+-                        cmdutil.add_tagline_or_explicit_id(file)
1353
++                        implicit = (id_type == "implicit")
1354
++                        cmdutil.add_tagline_or_explicit_id(file, False,
1355
++                                                           implicit)
1356
+                     except cmdutil.AlreadyTagged:
1357
+                         print "\"%s\" already has a tagline." % file
1358
+                     except cmdutil.NoCommentSyntax:
1359
+@@ -1888,6 +1739,9 @@
1360
+         parser.add_option("--tagline", action="store_const", 
1361
+                          const="tagline", dest="id_type", 
1362
+                          help="Use a tagline id")
1363
++        parser.add_option("--implicit", action="store_const", 
1364
++                         const="implicit", dest="id_type", 
1365
++                         help="Use an implicit id (deprecated)")
1366
+         parser.add_option("--untagged", action="store_true", 
1367
+                          dest="untagged", default=False, 
1368
+                          help="tag all untagged files")
1369
+@@ -1926,27 +1780,7 @@
1370
+     def get_completer(self, arg, index):
1371
+         if self.tree is None:
1372
+             raise arch.errors.TreeRootError
1373
+-        completions = list(ancillary.iter_partners(self.tree, 
1374
+-                                                   self.tree.tree_version))
1375
+-        if len(completions) == 0:
1376
+-            completions = list(self.tree.iter_log_versions())
1377
+-
1378
+-        aliases = []
1379
+-        try:
1380
+-            for completion in completions:
1381
+-                alias = ancillary.compact_alias(str(completion), self.tree)
1382
+-                if alias:
1383
+-                    aliases.extend(alias)
1384
+-
1385
+-            for completion in completions:
1386
+-                if completion.archive == self.tree.tree_version.archive:
1387
+-                    aliases.append(completion.nonarch)
1388
+-
1389
+-        except Exception, e:
1390
+-            print e
1391
+-            
1392
+-        completions.extend(aliases)
1393
+-        return completions
1394
++        return cmdutil.merge_completions(self.tree, arg, index)
1395
+ 
1396
+     def do_command(self, cmdargs):
1397
+         """
1398
+@@ -1961,7 +1795,7 @@
1399
+         
1400
+         if self.tree is None:
1401
+             raise arch.errors.TreeRootError(os.getcwd())
1402
+-        if cmdutil.has_changed(self.tree.tree_version):
1403
++        if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
1404
+             raise UncommittedChanges(self.tree)
1405
+ 
1406
+         if len(args) > 0:
1407
+@@ -2027,14 +1861,14 @@
1408
+         :type other_revision: `arch.Revision`
1409
+         :return: 0 if the merge was skipped, 1 if it was applied
1410
+         """
1411
+-        other_tree = cmdutil.find_or_make_local_revision(other_revision)
1412
++        other_tree = arch_compound.find_or_make_local_revision(other_revision)
1413
+         try:
1414
+             if action == "native-merge":
1415
+-                ancestor = cmdutil.merge_ancestor2(self.tree, other_tree, 
1416
+-                                                   other_revision)
1417
++                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
1418
++                                                         other_revision)
1419
+             elif action == "update":
1420
+-                ancestor = cmdutil.tree_latest(self.tree, 
1421
+-                                               other_revision.version)
1422
++                ancestor = arch_compound.tree_latest(self.tree, 
1423
++                                                     other_revision.version)
1424
+         except CantDetermineRevision, e:
1425
+             raise CommandFailedWrapper(e)
1426
+         cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
1427
+@@ -2104,7 +1938,10 @@
1428
+         if self.tree is None:
1429
+             raise arch.errors.TreeRootError
1430
+ 
1431
+-        edit_log(self.tree)
1432
++        try:
1433
++            edit_log(self.tree, self.tree.tree_version)
1434
++        except pylon.errors.NoEditorSpecified, e:
1435
++            raise pylon.errors.CommandFailedWrapper(e)
1436
+ 
1437
+     def get_parser(self):
1438
+         """
1439
+@@ -2132,7 +1969,7 @@
1440
+         """
1441
+         return
1442
+ 
1443
+-def edit_log(tree):
1444
++def edit_log(tree, version):
1445
+     """Makes and edits the log for a tree.  Does all kinds of fancy things
1446
+     like log templates and merge summaries and log-for-merge
1447
+     
1448
+@@ -2141,28 +1978,29 @@
1449
+     """
1450
+     #ensure we have an editor before preparing the log
1451
+     cmdutil.find_editor()
1452
+-    log = tree.log_message(create=False)
1453
++    log = tree.log_message(create=False, version=version)
1454
+     log_is_new = False
1455
+     if log is None or cmdutil.prompt("Overwrite log"):
1456
+         if log is not None:
1457
+            os.remove(log.name)
1458
+-        log = tree.log_message(create=True)
1459
++        log = tree.log_message(create=True, version=version)
1460
+         log_is_new = True
1461
+         tmplog = log.name
1462
+-        template = tree+"/{arch}/=log-template"
1463
+-        if not os.path.exists(template):
1464
+-            template = os.path.expanduser("~/.arch-params/=log-template")
1465
+-            if not os.path.exists(template):
1466
+-                template = None
1467
++        template = pylon.log_template_path(tree)
1468
+         if template:
1469
+             shutil.copyfile(template, tmplog)
1470
+-        
1471
+-        new_merges = list(cmdutil.iter_new_merges(tree, 
1472
+-                                                  tree.tree_version))
1473
+-        log["Summary"] = merge_summary(new_merges, tree.tree_version)
1474
++        comp_version = ancillary.comp_revision(tree).version
1475
++        new_merges = cmdutil.iter_new_merges(tree, comp_version)
1476
++        new_merges = cmdutil.direct_merges(new_merges)
1477
++        log["Summary"] = pylon.merge_summary(new_merges, 
1478
++                                         version)
1479
+         if len(new_merges) > 0:   
1480
+             if cmdutil.prompt("Log for merge"):
1481
+-                mergestuff = cmdutil.log_for_merge(tree)
1482
++                if cmdutil.prompt("changelog for merge"):
1483
++                    mergestuff = "Patches applied:\n"
1484
++                    mergestuff += pylon.changelog_for_merge(new_merges)
1485
++                else:
1486
++                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
1487
+                 log.description += mergestuff
1488
+         log.save()
1489
+     try:
1490
+@@ -2172,29 +2010,6 @@
1491
+             os.remove(log.name)
1492
+         raise
1493
+ 
1494
+-def merge_summary(new_merges, tree_version):
1495
+-    if len(new_merges) == 0:
1496
+-        return ""
1497
+-    if len(new_merges) == 1:
1498
+-        summary = new_merges[0].summary
1499
+-    else:
1500
+-        summary = "Merge"
1501
+-
1502
+-    credits = []
1503
+-    for merge in new_merges:
1504
+-        if arch.my_id() != merge.creator:
1505
+-            name = re.sub("<.*>", "", merge.creator).rstrip(" ");
1506
+-            if not name in credits:
1507
+-                credits.append(name)
1508
+-        else:
1509
+-            version = merge.revision.version
1510
+-            if version.archive == tree_version.archive:
1511
+-                if not version.nonarch in credits:
1512
+-                    credits.append(version.nonarch)
1513
+-            elif not str(version) in credits:
1514
+-                credits.append(str(version))
1515
+-
1516
+-    return ("%s (%s)") % (summary, ", ".join(credits))
1517
+ 
1518
+ class MirrorArchive(BaseCommand):
1519
+     """
1520
+@@ -2268,31 +2083,73 @@
1521
+ 
1522
+ Use "alias" to list available (user and automatic) aliases."""
1523
+ 
1524
++auto_alias = [
1525
++"acur", 
1526
++"The latest revision in the archive of the tree-version.  You can specify \
1527
++a different version like so: acur:foo--bar--0 (aliases can be used)",
1528
++"tcur",
1529
++"""(tree current) The latest revision in the tree of the tree-version. \
1530
++You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
1531
++used).""",
1532
++"tprev" , 
1533
++"""(tree previous) The previous revision in the tree of the tree-version.  To \
1534
++specify an older revision, use a number, e.g. "tprev:4" """,
1535
++"tanc" , 
1536
++"""(tree ancestor) The ancestor revision of the tree To specify an older \
1537
++revision, use a number, e.g. "tanc:4".""",
1538
++"tdate" , 
1539
++"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
1540
++"tmod" , 
1541
++""" (tree modified) The latest revision to modify a given file, e.g. \
1542
++"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
1543
++"ttag" , 
1544
++"""(tree tag) The revision that was tagged into the current tree revision, \
1545
++according to the tree""",
1546
++"tagcur", 
1547
++"""(tag current) The latest revision of the version that the current tree \
1548
++was tagged from.""",
1549
++"mergeanc" , 
1550
++"""The common ancestor of the current tree and the specified revision. \
1551
++Defaults to the first partner-version's latest revision or to tagcur.""",
1552
++]
1553
++
1554
++
1555
++def is_auto_alias(name):
1556
++    """Determine whether a name is an auto alias name
1557
++
1558
++    :param name: the name to check
1559
++    :type name: str
1560
++    :return: True if the name is an auto alias, false if not
1561
++    :rtype: bool
1562
++    """
1563
++    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
1564
++
1565
++
1566
++def display_def(iter, wrap = 80):
1567
++    """Display a list of definitions
1568
++
1569
++    :param iter: iter of name, definition pairs
1570
++    :type iter: iter of (str, str)
1571
++    :param wrap: The width for text wrapping
1572
++    :type wrap: int
1573
++    """
1574
++    vals = list(iter)
1575
++    maxlen = 0
1576
++    for (key, value) in vals:
1577
++        if len(key) > maxlen:
1578
++            maxlen = len(key)
1579
++    for (key, value) in vals:
1580
++        tw=textwrap.TextWrapper(width=wrap, 
1581
++                                initial_indent=key.rjust(maxlen)+" : ",
1582
++                                subsequent_indent="".rjust(maxlen+3))
1583
++        print tw.fill(value)
1584
++
1585
++
1586
+ def help_aliases(tree):
1587
+-    print """Auto-generated aliases
1588
+- acur : The latest revision in the archive of the tree-version.  You can specfy
1589
+-        a different version like so: acur:foo--bar--0 (aliases can be used)
1590
+- tcur : (tree current) The latest revision in the tree of the tree-version.
1591
+-        You can specify a different version like so: tcur:foo--bar--0 (aliases
1592
+-        can be used).
1593
+-tprev : (tree previous) The previous revision in the tree of the tree-version.
1594
+-        To specify an older revision, use a number, e.g. "tprev:4"
1595
+- tanc : (tree ancestor) The ancestor revision of the tree
1596
+-        To specify an older revision, use a number, e.g. "tanc:4"
1597
+-tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
1598
+- tmod : (tree modified) The latest revision to modify a given file 
1599
+-        (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
1600
+- ttag : (tree tag) The revision that was tagged into the current tree revision,
1601
+-        according to the tree.
1602
+-tagcur: (tag current) The latest revision of the version that the current tree
1603
+-        was tagged from.
1604
+-mergeanc : The common ancestor of the current tree and the specified revision.
1605
+-        Defaults to the first partner-version's latest revision or to tagcur.
1606
+-   """
1607
++    print """Auto-generated aliases"""
1608
++    display_def(pylon.util.iter_pairs(auto_alias))
1609
+     print "User aliases"
1610
+-    for parts in ancillary.iter_all_alias(tree):
1611
+-        print parts[0].rjust(10)+" : "+parts[1]
1612
+-
1613
++    display_def(ancillary.iter_all_alias(tree))
1614
+ 
1615
+ class Inventory(BaseCommand):
1616
+     """List the status of files in the tree"""
1617
+@@ -2428,6 +2285,11 @@
1618
+         except cmdutil.ForbiddenAliasSyntax, e:
1619
+             raise CommandFailedWrapper(e)
1620
+ 
1621
++    def no_prefix(self, alias):
1622
++        if alias.startswith("^"):
1623
++            alias = alias[1:]
1624
++        return alias
1625
++        
1626
+     def arg_dispatch(self, args, options):
1627
+         """Add, modify, or list aliases, depending on number of arguments
1628
+ 
1629
+@@ -2438,15 +2300,20 @@
1630
+         if len(args) == 0:
1631
+             help_aliases(self.tree)
1632
+             return
1633
+-        elif len(args) == 1:
1634
+-            self.print_alias(args[0])
1635
+-        elif (len(args)) == 2:
1636
+-            self.add(args[0], args[1], options)
1637
+         else:
1638
+-            raise cmdutil.GetHelp
1639
++            alias = self.no_prefix(args[0])
1640
++            if len(args) == 1:
1641
++                self.print_alias(alias)
1642
++            elif (len(args)) == 2:
1643
++                self.add(alias, args[1], options)
1644
++            else:
1645
++                raise cmdutil.GetHelp
1646
+ 
1647
+     def print_alias(self, alias):
1648
+         answer = None
1649
++        if is_auto_alias(alias):
1650
++            raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
1651
++                "  Use \"revision\" to expand auto aliases." % alias)
1652
+         for pair in ancillary.iter_all_alias(self.tree):
1653
+             if pair[0] == alias:
1654
+                 answer = pair[1]
1655
+@@ -2464,6 +2331,8 @@
1656
+         :type expansion: str
1657
+         :param options: The commandline options
1658
+         """
1659
++        if is_auto_alias(alias):
1660
++            raise IsAutoAlias(alias)
1661
+         newlist = ""
1662
+         written = False
1663
+         new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
1664
+@@ -2490,14 +2359,17 @@
1665
+         deleted = False
1666
+         if len(args) != 1:
1667
+             raise cmdutil.GetHelp
1668
++        alias = self.no_prefix(args[0])
1669
++        if is_auto_alias(alias):
1670
++            raise IsAutoAlias(alias)
1671
+         newlist = ""
1672
+         for pair in self.get_iterator(options):
1673
+-            if pair[0] != args[0]:
1674
++            if pair[0] != alias:
1675
+                 newlist+="%s=%s\n" % (pair[0], pair[1])
1676
+             else:
1677
+                 deleted = True
1678
+         if not deleted:
1679
+-            raise errors.NoSuchAlias(args[0])
1680
++            raise errors.NoSuchAlias(alias)
1681
+         self.write_aliases(newlist, options)
1682
+ 
1683
+     def get_alias_file(self, options):
1684
+@@ -2526,7 +2398,7 @@
1685
+         :param options: The commandline options
1686
+         """
1687
+         filename = os.path.expanduser(self.get_alias_file(options))
1688
+-        file = cmdutil.NewFileVersion(filename)
1689
++        file = util.NewFileVersion(filename)
1690
+         file.write(newlist)
1691
+         file.commit()
1692
+ 
1693
+@@ -2588,10 +2460,13 @@
1694
+         :param cmdargs: The commandline arguments
1695
+         :type cmdargs: list of str
1696
+         """
1697
+-        cmdutil.find_editor()
1698
+         parser = self.get_parser()
1699
+         (options, args) = parser.parse_args(cmdargs)
1700
+         try:
1701
++            cmdutil.find_editor()
1702
++        except pylon.errors.NoEditorSpecified, e:
1703
++            raise pylon.errors.CommandFailedWrapper(e)
1704
++        try:
1705
+             self.tree=arch.tree_root()
1706
+         except:
1707
+             self.tree=None
1708
+@@ -2655,7 +2530,7 @@
1709
+             target_revision = cmdutil.determine_revision_arch(self.tree, 
1710
+                                                               args[0])
1711
+         else:
1712
+-            target_revision = cmdutil.tree_latest(self.tree)
1713
++            target_revision = arch_compound.tree_latest(self.tree)
1714
+         if len(args) > 1:
1715
+             merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
1716
+                        self.tree, f)) for f in args[1:] ]
1717
+@@ -2711,7 +2586,7 @@
1718
+ 
1719
+         :param message: The message to send
1720
+         :type message: `email.Message`"""
1721
+-        server = smtplib.SMTP()
1722
++        server = smtplib.SMTP("localhost")
1723
+         server.sendmail(message['From'], message['To'], message.as_string())
1724
+         server.quit()
1725
+ 
1726
+@@ -2763,6 +2638,22 @@
1727
+ 'alias' : Alias,
1728
+ 'request-merge': RequestMerge,
1729
+ }
1730
++
1731
++def my_import(mod_name):
1732
++    module = __import__(mod_name)
1733
++    components = mod_name.split('.')
1734
++    for comp in components[1:]:
1735
++        module = getattr(module, comp)
1736
++    return module
1737
++
1738
++def plugin(mod_name):
1739
++    module = my_import(mod_name)
1740
++    module.add_command(commands)
1741
++
1742
++for file in os.listdir(sys.path[0]+"/command"):
1743
++    if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
1744
++        plugin("command."+file[:-3])
1745
++
1746
+ suggestions = {
1747
+ 'apply-delta' : "Try \"apply-changes\".",
1748
+ 'delta' : "To compare two revisions, use \"changes\".",
1749
+@@ -2784,6 +2675,7 @@
1750
+ 'tagline' : "Use add-id.  It uses taglines in tagline trees",
1751
+ 'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
1752
+ 'library-revisions' : "Use revisions --library",
1753
+-'file-revert' : "Use revert FILE"
1754
++'file-revert' : "Use revert FILE",
1755
++'join-branch' : "Use replay --logs-only"
1756
+ }
1757
+ # arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
1758
1759
*** added file 'testdata/insert_top.patch'
1760
--- /dev/null 
1761
+++ testdata/insert_top.patch 
1762
@@ -0,0 +1,7 @@
1763
+--- orig/pylon/patches.py
1764
++++ mod/pylon/patches.py
1765
+@@ -1,3 +1,4 @@
1766
++#test
1767
+ import util
1768
+ import sys
1769
+ class PatchSyntax(Exception):
1770
1771
*** added file 'testdata/mod'
1772
--- /dev/null 
1773
+++ testdata/mod 
1774
@@ -0,0 +1,2681 @@
1775
+# Copyright (C) 2004 Aaron Bentley
1776
+# <aaron.bentley@utoronto.ca>
1777
+#
1778
+#    This program is free software; you can redistribute it and/or modify
1779
+#    it under the terms of the GNU General Public License as published by
1780
+#    the Free Software Foundation; either version 2 of the License, or
1781
+#    (at your option) any later version.
1782
+#
1783
+#    This program is distributed in the hope that it will be useful,
1784
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
1785
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1786
+#    GNU General Public License for more details.
1787
+#
1788
+#    You should have received a copy of the GNU General Public License
1789
+#    along with this program; if not, write to the Free Software
1790
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
1791
+
1792
+import sys
1793
+import arch
1794
+import arch.util
1795
+import arch.arch
1796
+
1797
+import pylon.errors
1798
+from pylon.errors import *
1799
+from pylon import errors
1800
+from pylon import util
1801
+from pylon import arch_core
1802
+from pylon import arch_compound
1803
+from pylon import ancillary
1804
+from pylon import misc
1805
+from pylon import paths 
1806
+
1807
+import abacmds
1808
+import cmdutil
1809
+import shutil
1810
+import os
1811
+import options
1812
+import time
1813
+import cmd
1814
+import readline
1815
+import re
1816
+import string
1817
+import terminal
1818
+import email
1819
+import smtplib
1820
+import textwrap
1821
+
1822
+__docformat__ = "restructuredtext"
1823
+__doc__ = "Implementation of user (sub) commands"
1824
+commands = {}
1825
+
1826
+def find_command(cmd):
1827
+    """
1828
+    Return an instance of a command type.  Return None if the type isn't
1829
+    registered.
1830
+
1831
+    :param cmd: the name of the command to look for
1832
+    :type cmd: the type of the command
1833
+    """
1834
+    if commands.has_key(cmd):
1835
+        return commands[cmd]()
1836
+    else:
1837
+        return None
1838
+
1839
+class BaseCommand:
1840
+    def __call__(self, cmdline):
1841
+        try:
1842
+            self.do_command(cmdline.split())
1843
+        except cmdutil.GetHelp, e:
1844
+            self.help()
1845
+        except Exception, e:
1846
+            print e
1847
+
1848
+    def get_completer(index):
1849
+        return None
1850
+
1851
+    def complete(self, args, text):
1852
+        """
1853
+        Returns a list of possible completions for the given text.
1854
+
1855
+        :param args: The complete list of arguments
1856
+        :type args: List of str
1857
+        :param text: text to complete (may be shorter than args[-1])
1858
+        :type text: str
1859
+        :rtype: list of str
1860
+        """
1861
+        matches = []
1862
+        candidates = None
1863
+
1864
+        if len(args) > 0: 
1865
+            realtext = args[-1]
1866
+        else:
1867
+            realtext = ""
1868
+
1869
+        try:
1870
+            parser=self.get_parser()
1871
+            if realtext.startswith('-'):
1872
+                candidates = parser.iter_options()
1873
+            else:
1874
+                (options, parsed_args) = parser.parse_args(args)
1875
+
1876
+                if len (parsed_args) > 0:
1877
+                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
1878
+                else:
1879
+                    candidates = self.get_completer("", 0)
1880
+        except:
1881
+            pass
1882
+        if candidates is None:
1883
+            return
1884
+        for candidate in candidates:
1885
+            candidate = str(candidate)
1886
+            if candidate.startswith(realtext):
1887
+                matches.append(candidate[len(realtext)- len(text):])
1888
+        return matches
1889
+
1890
+
1891
+class Help(BaseCommand):
1892
+    """
1893
+    Lists commands, prints help messages.
1894
+    """
1895
+    def __init__(self):
1896
+        self.description="Prints help mesages"
1897
+        self.parser = None
1898
+
1899
+    def do_command(self, cmdargs):
1900
+        """
1901
+        Prints a help message.
1902
+        """
1903
+        options, args = self.get_parser().parse_args(cmdargs)
1904
+        if len(args) > 1:
1905
+            raise cmdutil.GetHelp
1906
+
1907
+        if options.native or options.suggestions or options.external:
1908
+            native = options.native
1909
+            suggestions = options.suggestions
1910
+            external = options.external
1911
+        else:
1912
+            native = True
1913
+            suggestions = False
1914
+            external = True
1915
+        
1916
+        if len(args) == 0:
1917
+            self.list_commands(native, suggestions, external)
1918
+            return
1919
+        elif len(args) == 1:
1920
+            command_help(args[0])
1921
+            return
1922
+
1923
+    def help(self):
1924
+        self.get_parser().print_help()
1925
+        print """
1926
+If no command is specified, commands are listed.  If a command is
1927
+specified, help for that command is listed.
1928
+        """
1929
+
1930
+    def get_parser(self):
1931
+        """
1932
+        Returns the options parser to use for the "revision" command.
1933
+
1934
+        :rtype: cmdutil.CmdOptionParser
1935
+        """
1936
+        if self.parser is not None:
1937
+            return self.parser
1938
+        parser=cmdutil.CmdOptionParser("fai help [command]")
1939
+        parser.add_option("-n", "--native", action="store_true", 
1940
+                         dest="native", help="Show native commands")
1941
+        parser.add_option("-e", "--external", action="store_true", 
1942
+                         dest="external", help="Show external commands")
1943
+        parser.add_option("-s", "--suggest", action="store_true", 
1944
+                         dest="suggestions", help="Show suggestions")
1945
+        self.parser = parser
1946
+        return parser 
1947
+      
1948
+    def list_commands(self, native=True, suggest=False, external=True):
1949
+        """
1950
+        Lists supported commands.
1951
+
1952
+        :param native: list native, python-based commands
1953
+        :type native: bool
1954
+        :param external: list external aba-style commands
1955
+        :type external: bool
1956
+        """
1957
+        if native:
1958
+            print "Native Fai commands"
1959
+            keys=commands.keys()
1960
+            keys.sort()
1961
+            for k in keys:
1962
+                space=""
1963
+                for i in range(28-len(k)):
1964
+                    space+=" "
1965
+                print space+k+" : "+commands[k]().description
1966
+            print
1967
+        if suggest:
1968
+            print "Unavailable commands and suggested alternatives"
1969
+            key_list = suggestions.keys()
1970
+            key_list.sort()
1971
+            for key in key_list:
1972
+                print "%28s : %s" % (key, suggestions[key])
1973
+            print
1974
+        if external:
1975
+            fake_aba = abacmds.AbaCmds()
1976
+            if (fake_aba.abadir == ""):
1977
+                return
1978
+            print "External commands"
1979
+            fake_aba.list_commands()
1980
+            print
1981
+        if not suggest:
1982
+            print "Use help --suggest to list alternatives to tla and aba"\
1983
+                " commands."
1984
+        if options.tla_fallthrough and (native or external):
1985
+            print "Fai also supports tla commands."
1986
+
1987
+def command_help(cmd):
1988
+    """
1989
+    Prints help for a command.
1990
+
1991
+    :param cmd: The name of the command to print help for
1992
+    :type cmd: str
1993
+    """
1994
+    fake_aba = abacmds.AbaCmds()
1995
+    cmdobj = find_command(cmd)
1996
+    if cmdobj != None:
1997
+        cmdobj.help()
1998
+    elif suggestions.has_key(cmd):
1999
+        print "Not available\n" + suggestions[cmd]
2000
+    else:
2001
+        abacmd = fake_aba.is_command(cmd)
2002
+        if abacmd:
2003
+            abacmd.help()
2004
+        else:
2005
+            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
2006
+
2007
+
2008
+
2009
+class Changes(BaseCommand):
2010
+    """
2011
+    the "changes" command: lists differences between trees/revisions:
2012
+    """
2013
+    
2014
+    def __init__(self):
2015
+        self.description="Lists what files have changed in the project tree"
2016
+
2017
+    def get_completer(self, arg, index):
2018
+        if index > 1:
2019
+            return None
2020
+        try:
2021
+            tree = arch.tree_root()
2022
+        except:
2023
+            tree = None
2024
+        return cmdutil.iter_revision_completions(arg, tree)
2025
+    
2026
+    def parse_commandline(self, cmdline):
2027
+        """
2028
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
2029
+        
2030
+        :param cmdline: A list of arguments to parse
2031
+        :rtype: (options, Revision, Revision/WorkingTree)
2032
+        """
2033
+        parser=self.get_parser()
2034
+        (options, args) = parser.parse_args(cmdline)
2035
+        if len(args) > 2:
2036
+            raise cmdutil.GetHelp
2037
+
2038
+        tree=arch.tree_root()
2039
+        if len(args) == 0:
2040
+            a_spec = ancillary.comp_revision(tree)
2041
+        else:
2042
+            a_spec = cmdutil.determine_revision_tree(tree, args[0])
2043
+        cmdutil.ensure_archive_registered(a_spec.archive)
2044
+        if len(args) == 2:
2045
+            b_spec = cmdutil.determine_revision_tree(tree, args[1])
2046
+            cmdutil.ensure_archive_registered(b_spec.archive)
2047
+        else:
2048
+            b_spec=tree
2049
+        return options, a_spec, b_spec
2050
+
2051
+    def do_command(self, cmdargs):
2052
+        """
2053
+        Master function that perfoms the "changes" command.
2054
+        """
2055
+        try:
2056
+            options, a_spec, b_spec = self.parse_commandline(cmdargs);
2057
+        except cmdutil.CantDetermineRevision, e:
2058
+            print e
2059
+            return
2060
+        except arch.errors.TreeRootError, e:
2061
+            print e
2062
+            return
2063
+        if options.changeset:
2064
+            changeset=options.changeset
2065
+            tmpdir = None
2066
+        else:
2067
+            tmpdir=util.tmpdir()
2068
+            changeset=tmpdir+"/changeset"
2069
+        try:
2070
+            delta=arch.iter_delta(a_spec, b_spec, changeset)
2071
+            try:
2072
+                for line in delta:
2073
+                    if cmdutil.chattermatch(line, "changeset:"):
2074
+                        pass
2075
+                    else:
2076
+                        cmdutil.colorize(line, options.suppress_chatter)
2077
+            except arch.util.ExecProblem, e:
2078
+                if e.proc.error and e.proc.error.startswith(
2079
+                    "missing explicit id for file"):
2080
+                    raise MissingID(e)
2081
+                else:
2082
+                    raise
2083
+            status=delta.status
2084
+            if status > 1:
2085
+                return
2086
+            if (options.perform_diff):
2087
+                chan = arch_compound.ChangesetMunger(changeset)
2088
+                chan.read_indices()
2089
+                if options.diffopts is not None:
2090
+                    if isinstance(b_spec, arch.Revision):
2091
+                        b_dir = b_spec.library_find()
2092
+                    else:
2093
+                        b_dir = b_spec
2094
+                    a_dir = a_spec.library_find()
2095
+                    diffopts = options.diffopts.split()
2096
+                    cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
2097
+                else:
2098
+                    cmdutil.show_diffs(delta.changeset)
2099
+        finally:
2100
+            if tmpdir and (os.access(tmpdir, os.X_OK)):
2101
+                shutil.rmtree(tmpdir)
2102
+
2103
+    def get_parser(self):
2104
+        """
2105
+        Returns the options parser to use for the "changes" command.
2106
+
2107
+        :rtype: cmdutil.CmdOptionParser
2108
+        """
2109
+        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
2110
+                                       " [revision]")
2111
+        parser.add_option("-d", "--diff", action="store_true", 
2112
+                          dest="perform_diff", default=False, 
2113
+                          help="Show diffs in summary")
2114
+        parser.add_option("-c", "--changeset", dest="changeset", 
2115
+                          help="Store a changeset in the given directory", 
2116
+                          metavar="DIRECTORY")
2117
+        parser.add_option("-s", "--silent", action="store_true", 
2118
+                          dest="suppress_chatter", default=False, 
2119
+                          help="Suppress chatter messages")
2120
+        parser.add_option("--diffopts", dest="diffopts", 
2121
+                          help="Use the specified diff options", 
2122
+                          metavar="OPTIONS")
2123
+
2124
+        return parser
2125
+
2126
+    def help(self, parser=None):
2127
+        """
2128
+        Prints a help message.
2129
+
2130
+        :param parser: If supplied, the parser to use for generating help.  If \
2131
+        not supplied, it is retrieved.
2132
+        :type parser: cmdutil.CmdOptionParser
2133
+        """
2134
+        if parser is None:
2135
+            parser=self.get_parser()
2136
+        parser.print_help()
2137
+        print """
2138
+Performs source-tree comparisons
2139
+
2140
+If no revision is specified, the current project tree is compared to the
2141
+last-committed revision.  If one revision is specified, the current project
2142
+tree is compared to that revision.  If two revisions are specified, they are
2143
+compared to each other.
2144
+        """
2145
+        help_tree_spec() 
2146
+        return
2147
+
2148
+
2149
+class ApplyChanges(BaseCommand):
2150
+    """
2151
+    Apply differences between two revisions to a tree
2152
+    """
2153
+    
2154
+    def __init__(self):
2155
+        self.description="Applies changes to a project tree"
2156
+    
2157
+    def get_completer(self, arg, index):
2158
+        if index > 1:
2159
+            return None
2160
+        try:
2161
+            tree = arch.tree_root()
2162
+        except:
2163
+            tree = None
2164
+        return cmdutil.iter_revision_completions(arg, tree)
2165
+
2166
+    def parse_commandline(self, cmdline, tree):
2167
+        """
2168
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
2169
+        
2170
+        :param cmdline: A list of arguments to parse
2171
+        :rtype: (options, Revision, Revision/WorkingTree)
2172
+        """
2173
+        parser=self.get_parser()
2174
+        (options, args) = parser.parse_args(cmdline)
2175
+        if len(args) != 2:
2176
+            raise cmdutil.GetHelp
2177
+
2178
+        a_spec = cmdutil.determine_revision_tree(tree, args[0])
2179
+        cmdutil.ensure_archive_registered(a_spec.archive)
2180
+        b_spec = cmdutil.determine_revision_tree(tree, args[1])
2181
+        cmdutil.ensure_archive_registered(b_spec.archive)
2182
+        return options, a_spec, b_spec
2183
+
2184
+    def do_command(self, cmdargs):
2185
+        """
2186
+        Master function that performs "apply-changes".
2187
+        """
2188
+        try:
2189
+            tree = arch.tree_root()
2190
+            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
2191
+        except cmdutil.CantDetermineRevision, e:
2192
+            print e
2193
+            return
2194
+        except arch.errors.TreeRootError, e:
2195
+            print e
2196
+            return
2197
+        delta=cmdutil.apply_delta(a_spec, b_spec, tree)
2198
+        for line in cmdutil.iter_apply_delta_filter(delta):
2199
+            cmdutil.colorize(line, options.suppress_chatter)
2200
+
2201
+    def get_parser(self):
2202
+        """
2203
+        Returns the options parser to use for the "apply-changes" command.
2204
+
2205
+        :rtype: cmdutil.CmdOptionParser
2206
+        """
2207
+        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
2208
+                                       " revision")
2209
+        parser.add_option("-d", "--diff", action="store_true", 
2210
+                          dest="perform_diff", default=False, 
2211
+                          help="Show diffs in summary")
2212
+        parser.add_option("-c", "--changeset", dest="changeset", 
2213
+                          help="Store a changeset in the given directory", 
2214
+                          metavar="DIRECTORY")
2215
+        parser.add_option("-s", "--silent", action="store_true", 
2216
+                          dest="suppress_chatter", default=False, 
2217
+                          help="Suppress chatter messages")
2218
+        return parser
2219
+
2220
+    def help(self, parser=None):
2221
+        """
2222
+        Prints a help message.
2223
+
2224
+        :param parser: If supplied, the parser to use for generating help.  If \
2225
+        not supplied, it is retrieved.
2226
+        :type parser: cmdutil.CmdOptionParser
2227
+        """
2228
+        if parser is None:
2229
+            parser=self.get_parser()
2230
+        parser.print_help()
2231
+        print """
2232
+Applies changes to a project tree
2233
+
2234
+Compares two revisions and applies the difference between them to the current
2235
+tree.
2236
+        """
2237
+        help_tree_spec() 
2238
+        return
2239
+
2240
+class Update(BaseCommand):
2241
+    """
2242
+    Updates a project tree to a given revision, preserving un-committed hanges. 
2243
+    """
2244
+    
2245
+    def __init__(self):
2246
+        self.description="Apply the latest changes to the current directory"
2247
+
2248
+    def get_completer(self, arg, index):
2249
+        if index > 0:
2250
+            return None
2251
+        try:
2252
+            tree = arch.tree_root()
2253
+        except:
2254
+            tree = None
2255
+        return cmdutil.iter_revision_completions(arg, tree)
2256
+    
2257
+    def parse_commandline(self, cmdline, tree):
2258
+        """
2259
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
2260
+        
2261
+        :param cmdline: A list of arguments to parse
2262
+        :rtype: (options, Revision, Revision/WorkingTree)
2263
+        """
2264
+        parser=self.get_parser()
2265
+        (options, args) = parser.parse_args(cmdline)
2266
+        if len(args) > 2:
2267
+            raise cmdutil.GetHelp
2268
+
2269
+        spec=None
2270
+        if len(args)>0:
2271
+            spec=args[0]
2272
+        revision=cmdutil.determine_revision_arch(tree, spec)
2273
+        cmdutil.ensure_archive_registered(revision.archive)
2274
+
2275
+        mirror_source = cmdutil.get_mirror_source(revision.archive)
2276
+        if mirror_source != None:
2277
+            if cmdutil.prompt("Mirror update"):
2278
+                cmd=cmdutil.mirror_archive(mirror_source, 
2279
+                    revision.archive, arch.NameParser(revision).get_package_version())
2280
+                for line in arch.chatter_classifier(cmd):
2281
+                    cmdutil.colorize(line, options.suppress_chatter)
2282
+
2283
+                revision=cmdutil.determine_revision_arch(tree, spec)
2284
+
2285
+        return options, revision 
2286
+
2287
+    def do_command(self, cmdargs):
2288
+        """
2289
+        Master function that perfoms the "update" command.
2290
+        """
2291
+        tree=arch.tree_root()
2292
+        try:
2293
+            options, to_revision = self.parse_commandline(cmdargs, tree);
2294
+        except cmdutil.CantDetermineRevision, e:
2295
+            print e
2296
+            return
2297
+        except arch.errors.TreeRootError, e:
2298
+            print e
2299
+            return
2300
+        from_revision = arch_compound.tree_latest(tree)
2301
+        if from_revision==to_revision:
2302
+            print "Tree is already up to date with:\n"+str(to_revision)+"."
2303
+            return
2304
+        cmdutil.ensure_archive_registered(from_revision.archive)
2305
+        cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
2306
+            options.patch_forward)
2307
+        for line in cmdutil.iter_apply_delta_filter(cmd):
2308
+            cmdutil.colorize(line)
2309
+        if to_revision.version != tree.tree_version:
2310
+            if cmdutil.prompt("Update version"):
2311
+                tree.tree_version = to_revision.version
2312
+
2313
+    def get_parser(self):
2314
+        """
2315
+        Returns the options parser to use for the "update" command.
2316
+
2317
+        :rtype: cmdutil.CmdOptionParser
2318
+        """
2319
+        parser=cmdutil.CmdOptionParser("fai update [options]"
2320
+                                       " [revision/version]")
2321
+        parser.add_option("-f", "--forward", action="store_true", 
2322
+                          dest="patch_forward", default=False, 
2323
+                          help="pass the --forward option to 'patch'")
2324
+        parser.add_option("-s", "--silent", action="store_true", 
2325
+                          dest="suppress_chatter", default=False, 
2326
+                          help="Suppress chatter messages")
2327
+        return parser
2328
+
2329
+    def help(self, parser=None):
2330
+        """
2331
+        Prints a help message.
2332
+
2333
+        :param parser: If supplied, the parser to use for generating help.  If \
2334
+        not supplied, it is retrieved.
2335
+        :type parser: cmdutil.CmdOptionParser
2336
+        """
2337
+        if parser is None:
2338
+            parser=self.get_parser()
2339
+        parser.print_help()
2340
+        print """
2341
+Updates a working tree to the current archive revision
2342
+
2343
+If a revision or version is specified, that is used instead 
2344
+        """
2345
+        help_tree_spec() 
2346
+        return
2347
+
2348
+
2349
+class Commit(BaseCommand):
2350
+    """
2351
+    Create a revision based on the changes in the current tree.
2352
+    """
2353
+    
2354
+    def __init__(self):
2355
+        self.description="Write local changes to the archive"
2356
+
2357
+    def get_completer(self, arg, index):
2358
+        if arg is None:
2359
+            arg = ""
2360
+        return iter_modified_file_completions(arch.tree_root(), arg)
2361
+#        return iter_source_file_completions(arch.tree_root(), arg)
2362
+    
2363
+    def parse_commandline(self, cmdline, tree):
2364
+        """
2365
+        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
2366
+        
2367
+        :param cmdline: A list of arguments to parse
2368
+        :rtype: (options, Revision, Revision/WorkingTree)
2369
+        """
2370
+        parser=self.get_parser()
2371
+        (options, args) = parser.parse_args(cmdline)
2372
+
2373
+        if len(args) == 0:
2374
+            args = None
2375
+        if options.version is None:
2376
+            return options, tree.tree_version, args
2377
+
2378
+        revision=cmdutil.determine_revision_arch(tree, options.version)
2379
+        return options, revision.get_version(), args
2380
+
2381
+    def do_command(self, cmdargs):
2382
+        """
2383
+        Master function that perfoms the "commit" command.
2384
+        """
2385
+        tree=arch.tree_root()
2386
+        options, version, files = self.parse_commandline(cmdargs, tree)
2387
+        ancestor = None
2388
+        if options.__dict__.has_key("base") and options.base:
2389
+            base = cmdutil.determine_revision_tree(tree, options.base)
2390
+            ancestor = base
2391
+        else:
2392
+            base = ancillary.submit_revision(tree)
2393
+            ancestor = base
2394
+        if ancestor is None:
2395
+            ancestor = arch_compound.tree_latest(tree, version)
2396
+
2397
+        writeversion=version
2398
+        archive=version.archive
2399
+        source=cmdutil.get_mirror_source(archive)
2400
+        allow_old=False
2401
+        writethrough="implicit"
2402
+
2403
+        if source!=None:
2404
+            if writethrough=="explicit" and \
2405
+                cmdutil.prompt("Writethrough"):
2406
+                writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
2407
+            elif writethrough=="none":
2408
+                raise CommitToMirror(archive)
2409
+
2410
+        elif archive.is_mirror:
2411
+            raise CommitToMirror(archive)
2412
+
2413
+        try:
2414
+            last_revision=tree.iter_logs(version, True).next().revision
2415
+        except StopIteration, e:
2416
+            last_revision = None
2417
+            if ancestor is None:
2418
+                if cmdutil.prompt("Import from commit"):
2419
+                    return do_import(version)
2420
+                else:
2421
+                    raise NoVersionLogs(version)
2422
+        try:
2423
+            arch_last_revision = version.iter_revisions(True).next()
2424
+        except StopIteration, e:
2425
+            arch_last_revision = None
2426
+ 
2427
+        if last_revision != arch_last_revision:
2428
+            print "Tree is not up to date with %s" % str(version)
2429
+            if not cmdutil.prompt("Out of date"):
2430
+                raise OutOfDate
2431
+            else:
2432
+                allow_old=True
2433
+
2434
+        try:
2435
+            if not cmdutil.has_changed(ancestor):
2436
+                if not cmdutil.prompt("Empty commit"):
2437
+                    raise EmptyCommit
2438
+        except arch.util.ExecProblem, e:
2439
+            if e.proc.error and e.proc.error.startswith(
2440
+                "missing explicit id for file"):
2441
+                raise MissingID(e)
2442
+            else:
2443
+                raise
2444
+        log = tree.log_message(create=False, version=version)
2445
+        if log is None:
2446
+            try:
2447
+                if cmdutil.prompt("Create log"):
2448
+                    edit_log(tree, version)
2449
+
2450
+            except cmdutil.NoEditorSpecified, e:
2451
+                raise CommandFailed(e)
2452
+            log = tree.log_message(create=False, version=version)
2453
+        if log is None: 
2454
+            raise NoLogMessage
2455
+        if log["Summary"] is None or len(log["Summary"].strip()) == 0:
2456
+            if not cmdutil.prompt("Omit log summary"):
2457
+                raise errors.NoLogSummary
2458
+        try:
2459
+            for line in tree.iter_commit(version, seal=options.seal_version,
2460
+                base=base, out_of_date_ok=allow_old, file_list=files):
2461
+                cmdutil.colorize(line, options.suppress_chatter)
2462
+
2463
+        except arch.util.ExecProblem, e:
2464
+            if e.proc.error and e.proc.error.startswith(
2465
+                "These files violate naming conventions:"):
2466
+                raise LintFailure(e.proc.error)
2467
+            else:
2468
+                raise
2469
+
2470
+    def get_parser(self):
2471
+        """
2472
+        Returns the options parser to use for the "commit" command.
2473
+
2474
+        :rtype: cmdutil.CmdOptionParser
2475
+        """
2476
+
2477
+        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
2478
+                                       " [file2...]")
2479
+        parser.add_option("--seal", action="store_true", 
2480
+                          dest="seal_version", default=False, 
2481
+                          help="seal this version")
2482
+        parser.add_option("-v", "--version", dest="version", 
2483
+                          help="Use the specified version", 
2484
+                          metavar="VERSION")
2485
+        parser.add_option("-s", "--silent", action="store_true", 
2486
+                          dest="suppress_chatter", default=False, 
2487
+                          help="Suppress chatter messages")
2488
+        if cmdutil.supports_switch("commit", "--base"):
2489
+            parser.add_option("--base", dest="base", help="", 
2490
+                              metavar="REVISION")
2491
+        return parser
2492
+
2493
+    def help(self, parser=None):
2494
+        """
2495
+        Prints a help message.
2496
+
2497
+        :param parser: If supplied, the parser to use for generating help.  If \
2498
+        not supplied, it is retrieved.
2499
+        :type parser: cmdutil.CmdOptionParser
2500
+        """
2501
+        if parser is None:
2502
+            parser=self.get_parser()
2503
+        parser.print_help()
2504
+        print """
2505
+Updates a working tree to the current archive revision
2506
+
2507
+If a version is specified, that is used instead 
2508
+        """
2509
+#        help_tree_spec() 
2510
+        return
2511
+
2512
+
2513
+
2514
+class CatLog(BaseCommand):
2515
+    """
2516
+    Print the log of a given file (from current tree)
2517
+    """
2518
+    def __init__(self):
2519
+        self.description="Prints the patch log for a revision"
2520
+
2521
+    def get_completer(self, arg, index):
2522
+        if index > 0:
2523
+            return None
2524
+        try:
2525
+            tree = arch.tree_root()
2526
+        except:
2527
+            tree = None
2528
+        return cmdutil.iter_revision_completions(arg, tree)
2529
+
2530
+    def do_command(self, cmdargs):
2531
+        """
2532
+        Master function that perfoms the "cat-log" command.
2533
+        """
2534
+        parser=self.get_parser()
2535
+        (options, args) = parser.parse_args(cmdargs)
2536
+        try:
2537
+            tree = arch.tree_root()
2538
+        except arch.errors.TreeRootError, e:
2539
+            tree = None
2540
+        spec=None
2541
+        if len(args) > 0:
2542
+            spec=args[0]
2543
+        if len(args) > 1:
2544
+            raise cmdutil.GetHelp()
2545
+        try:
2546
+            if tree:
2547
+                revision = cmdutil.determine_revision_tree(tree, spec)
2548
+            else:
2549
+                revision = cmdutil.determine_revision_arch(tree, spec)
2550
+        except cmdutil.CantDetermineRevision, e:
2551
+            raise CommandFailedWrapper(e)
2552
+        log = None
2553
+        
2554
+        use_tree = (options.source == "tree" or \
2555
+            (options.source == "any" and tree))
2556
+        use_arch = (options.source == "archive" or options.source == "any")
2557
+        
2558
+        log = None
2559
+        if use_tree:
2560
+            for log in tree.iter_logs(revision.get_version()):
2561
+                if log.revision == revision:
2562
+                    break
2563
+                else:
2564
+                    log = None
2565
+        if log is None and use_arch:
2566
+            cmdutil.ensure_revision_exists(revision)
2567
+            log = arch.Patchlog(revision)
2568
+        if log is not None:
2569
+            for item in log.items():
2570
+                print "%s: %s" % item
2571
+            print log.description
2572
+
2573
+    def get_parser(self):
2574
+        """
2575
+        Returns the options parser to use for the "cat-log" command.
2576
+
2577
+        :rtype: cmdutil.CmdOptionParser
2578
+        """
2579
+        parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
2580
+        parser.add_option("--archive", action="store_const", dest="source",
2581
+                          const="archive", default="any",
2582
+                          help="Always get the log from the archive")
2583
+        parser.add_option("--tree", action="store_const", dest="source",
2584
+                          const="tree", help="Always get the log from the tree")
2585
+        return parser 
2586
+
2587
+    def help(self, parser=None):
2588
+        """
2589
+        Prints a help message.
2590
+
2591
+        :param parser: If supplied, the parser to use for generating help.  If \
2592
+        not supplied, it is retrieved.
2593
+        :type parser: cmdutil.CmdOptionParser
2594
+        """
2595
+        if parser==None:
2596
+            parser=self.get_parser()
2597
+        parser.print_help()
2598
+        print """
2599
+Prints the log for the specified revision
2600
+        """
2601
+        help_tree_spec()
2602
+        return
2603
+
2604
+class Revert(BaseCommand):
2605
+    """ Reverts a tree (or aspects of it) to a revision
2606
+    """
2607
+    def __init__(self):
2608
+        self.description="Reverts a tree (or aspects of it) to a revision "
2609
+
2610
+    def get_completer(self, arg, index):
2611
+        if index > 0:
2612
+            return None
2613
+        try:
2614
+            tree = arch.tree_root()
2615
+        except:
2616
+            tree = None
2617
+        return iter_modified_file_completions(tree, arg)
2618
+
2619
+    def do_command(self, cmdargs):
2620
+        """
2621
+        Master function that perfoms the "revert" command.
2622
+        """
2623
+        parser=self.get_parser()
2624
+        (options, args) = parser.parse_args(cmdargs)
2625
+        try:
2626
+            tree = arch.tree_root()
2627
+        except arch.errors.TreeRootError, e:
2628
+            raise CommandFailed(e)
2629
+        spec=None
2630
+        if options.revision is not None:
2631
+            spec=options.revision
2632
+        try:
2633
+            if spec is not None:
2634
+                revision = cmdutil.determine_revision_tree(tree, spec)
2635
+            else:
2636
+                revision = ancillary.comp_revision(tree)
2637
+        except cmdutil.CantDetermineRevision, e:
2638
+            raise CommandFailedWrapper(e)
2639
+        munger = None
2640
+
2641
+        if options.file_contents or options.file_perms or options.deletions\
2642
+            or options.additions or options.renames or options.hunk_prompt:
2643
+            munger = arch_compound.MungeOpts()
2644
+            munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
2645
+                                   options.hunk_prompt)
2646
+
2647
+        if len(args) > 0 or options.logs or options.pattern_files or \
2648
+            options.control:
2649
+            if munger is None:
2650
+                munger = cmdutil.arch_compound.MungeOpts(True)
2651
+                munger.all_types(True)
2652
+        if len(args) > 0:
2653
+            t_cwd = arch_compound.tree_cwd(tree)
2654
+            for name in args:
2655
+                if len(t_cwd) > 0:
2656
+                    t_cwd += "/"
2657
+                name = "./" + t_cwd + name
2658
+                munger.add_keep_file(name);
2659
+
2660
+        if options.file_perms:
2661
+            munger.file_perms = True
2662
+        if options.file_contents:
2663
+            munger.file_contents = True
2664
+        if options.deletions:
2665
+            munger.deletions = True
2666
+        if options.additions:
2667
+            munger.additions = True
2668
+        if options.renames:
2669
+            munger.renames = True
2670
+        if options.logs:
2671
+            munger.add_keep_pattern('^\./\{arch\}/[^=].*')
2672
+        if options.control:
2673
+            munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
2674
+                                    "/\.arch-inventory$")
2675
+        if options.pattern_files:
2676
+            munger.add_keep_pattern(options.pattern_files)
2677
+                
2678
+        for line in arch_compound.revert(tree, revision, munger, 
2679
+                                   not options.no_output):
2680
+            cmdutil.colorize(line)
2681
+
2682
+
2683
+    def get_parser(self):
2684
+        """
2685
+        Returns the options parser to use for the "cat-log" command.
2686
+
2687
+        :rtype: cmdutil.CmdOptionParser
2688
+        """
2689
+        parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
2690
+        parser.add_option("", "--contents", action="store_true", 
2691
+                          dest="file_contents", 
2692
+                          help="Revert file content changes")
2693
+        parser.add_option("", "--permissions", action="store_true", 
2694
+                          dest="file_perms", 
2695
+                          help="Revert file permissions changes")
2696
+        parser.add_option("", "--deletions", action="store_true", 
2697
+                          dest="deletions", 
2698
+                          help="Restore deleted files")
2699
+        parser.add_option("", "--additions", action="store_true", 
2700
+                          dest="additions", 
2701
+                          help="Remove added files")
2702
+        parser.add_option("", "--renames", action="store_true", 
2703
+                          dest="renames", 
2704
+                          help="Revert file names")
2705
+        parser.add_option("--hunks", action="store_true", 
2706
+                          dest="hunk_prompt", default=False,
2707
+                          help="Prompt which hunks to revert")
2708
+        parser.add_option("--pattern-files", dest="pattern_files", 
2709
+                          help="Revert files that match this pattern", 
2710
+                          metavar="REGEX")
2711
+        parser.add_option("--logs", action="store_true", 
2712
+                          dest="logs", default=False,
2713
+                          help="Revert only logs")
2714
+        parser.add_option("--control-files", action="store_true", 
2715
+                          dest="control", default=False,
2716
+                          help="Revert logs and other control files")
2717
+        parser.add_option("-n", "--no-output", action="store_true", 
2718
+                          dest="no_output", 
2719
+                          help="Don't keep an undo changeset")
2720
+        parser.add_option("--revision", dest="revision", 
2721
+                          help="Revert to the specified revision", 
2722
+                          metavar="REVISION")
2723
+        return parser 
2724
+
2725
+    def help(self, parser=None):
2726
+        """
2727
+        Prints a help message.
2728
+
2729
+        :param parser: If supplied, the parser to use for generating help.  If \
2730
+        not supplied, it is retrieved.
2731
+        :type parser: cmdutil.CmdOptionParser
2732
+        """
2733
+        if parser==None:
2734
+            parser=self.get_parser()
2735
+        parser.print_help()
2736
+        print """
2737
+Reverts changes in the current working tree.  If no flags are specified, all
2738
+types of changes are reverted.  Otherwise, only selected types of changes are
2739
+reverted.  
2740
+
2741
+If a revision is specified on the commandline, differences between the current
2742
+tree and that revision are reverted.  If a version is specified, the current
2743
+tree is used to determine the revision.
2744
+
2745
+If files are specified, only those files listed will have any changes applied.
2746
+To specify a renamed file, you can use either the old or new name. (or both!)
2747
+
2748
+Unless "-n" is specified, reversions can be undone with "redo".
2749
+        """
2750
+        return
2751
+
2752
+class Revision(BaseCommand):
2753
+    """
2754
+    Print a revision name based on a revision specifier
2755
+    """
2756
+    def __init__(self):
2757
+        self.description="Prints the name of a revision"
2758
+
2759
+    def get_completer(self, arg, index):
2760
+        if index > 0:
2761
+            return None
2762
+        try:
2763
+            tree = arch.tree_root()
2764
+        except:
2765
+            tree = None
2766
+        return cmdutil.iter_revision_completions(arg, tree)
2767
+
2768
+    def do_command(self, cmdargs):
2769
+        """
2770
+        Master function that perfoms the "revision" command.
2771
+        """
2772
+        parser=self.get_parser()
2773
+        (options, args) = parser.parse_args(cmdargs)
2774
+
2775
+        try:
2776
+            tree = arch.tree_root()
2777
+        except arch.errors.TreeRootError:
2778
+            tree = None
2779
+
2780
+        spec=None
2781
+        if len(args) > 0:
2782
+            spec=args[0]
2783
+        if len(args) > 1:
2784
+            raise cmdutil.GetHelp
2785
+        try:
2786
+            if tree:
2787
+                revision = cmdutil.determine_revision_tree(tree, spec)
2788
+            else:
2789
+                revision = cmdutil.determine_revision_arch(tree, spec)
2790
+        except cmdutil.CantDetermineRevision, e:
2791
+            print str(e)
2792
+            return
2793
+        print options.display(revision)
2794
+
2795
+    def get_parser(self):
2796
+        """
2797
+        Returns the options parser to use for the "revision" command.
2798
+
2799
+        :rtype: cmdutil.CmdOptionParser
2800
+        """
2801
+        parser=cmdutil.CmdOptionParser("fai revision [revision]")
2802
+        parser.add_option("", "--location", action="store_const", 
2803
+                         const=paths.determine_path, dest="display", 
2804
+                         help="Show location instead of name", default=str)
2805
+        parser.add_option("--import", action="store_const", 
2806
+                         const=paths.determine_import_path, dest="display",  
2807
+                         help="Show location of import file")
2808
+        parser.add_option("--log", action="store_const", 
2809
+                         const=paths.determine_log_path, dest="display", 
2810
+                         help="Show location of log file")
2811
+        parser.add_option("--patch", action="store_const", 
2812
+                         dest="display", const=paths.determine_patch_path,
2813
+                         help="Show location of patchfile")
2814
+        parser.add_option("--continuation", action="store_const", 
2815
+                         const=paths.determine_continuation_path, 
2816
+                         dest="display",
2817
+                         help="Show location of continuation file")
2818
+        parser.add_option("--cacherev", action="store_const", 
2819
+                         const=paths.determine_cacherev_path, dest="display",
2820
+                         help="Show location of cacherev file")
2821
+        return parser 
2822
+
2823
+    def help(self, parser=None):
2824
+        """
2825
+        Prints a help message.
2826
+
2827
+        :param parser: If supplied, the parser to use for generating help.  If \
2828
+        not supplied, it is retrieved.
2829
+        :type parser: cmdutil.CmdOptionParser
2830
+        """
2831
+        if parser==None:
2832
+            parser=self.get_parser()
2833
+        parser.print_help()
2834
+        print """
2835
+Expands aliases and prints the name of the specified revision.  Instead of
2836
+the name, several options can be used to print locations.  If more than one is
2837
+specified, the last one is used.
2838
+        """
2839
+        help_tree_spec()
2840
+        return
2841
+
2842
+class Revisions(BaseCommand):
2843
+    """
2844
+    Print a revision name based on a revision specifier
2845
+    """
2846
+    def __init__(self):
2847
+        self.description="Lists revisions"
2848
+        self.cl_revisions = []
2849
+    
2850
+    def do_command(self, cmdargs):
2851
+        """
2852
+        Master function that perfoms the "revision" command.
2853
+        """
2854
+        (options, args) = self.get_parser().parse_args(cmdargs)
2855
+        if len(args) > 1:
2856
+            raise cmdutil.GetHelp
2857
+        try:
2858
+            self.tree = arch.tree_root()
2859
+        except arch.errors.TreeRootError:
2860
+            self.tree = None
2861
+        if options.type == "default":
2862
+            options.type = "archive"
2863
+        try:
2864
+            iter = cmdutil.revision_iterator(self.tree, options.type, args, 
2865
+                                             options.reverse, options.modified,
2866
+                                             options.shallow)
2867
+        except cmdutil.CantDetermineRevision, e:
2868
+            raise CommandFailedWrapper(e)
2869
+        except cmdutil.CantDetermineVersion, e:
2870
+            raise CommandFailedWrapper(e)
2871
+        if options.skip is not None:
2872
+            iter = cmdutil.iter_skip(iter, int(options.skip))
2873
+
2874
+        try:
2875
+            for revision in iter:
2876
+                log = None
2877
+                if isinstance(revision, arch.Patchlog):
2878
+                    log = revision
2879
+                    revision=revision.revision
2880
+                out = options.display(revision)
2881
+                if out is not None:
2882
+                    print out
2883
+                if log is None and (options.summary or options.creator or 
2884
+                                    options.date or options.merges):
2885
+                    log = revision.patchlog
2886
+                if options.creator:
2887
+                    print "    %s" % log.creator
2888
+                if options.date:
2889
+                    print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
2890
+                if options.summary:
2891
+                    print "    %s" % log.summary
2892
+                if options.merges:
2893
+                    showed_title = False
2894
+                    for revision in log.merged_patches:
2895
+                        if not showed_title:
2896
+                            print "    Merged:"
2897
+                            showed_title = True
2898
+                        print "    %s" % revision
2899
+            if len(self.cl_revisions) > 0:
2900
+                print pylon.changelog_for_merge(self.cl_revisions)
2901
+        except pylon.errors.TreeRootNone:
2902
+            raise CommandFailedWrapper(
2903
+                Exception("This option can only be used in a project tree."))
2904
+
2905
+    def changelog_append(self, revision):
2906
+        if isinstance(revision, arch.Revision):
2907
+            revision=arch.Patchlog(revision)
2908
+        self.cl_revisions.append(revision)
2909
+   
2910
+    def get_parser(self):
2911
+        """
2912
+        Returns the options parser to use for the "revision" command.
2913
+
2914
+        :rtype: cmdutil.CmdOptionParser
2915
+        """
2916
+        parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
2917
+        select = cmdutil.OptionGroup(parser, "Selection options",
2918
+                          "Control which revisions are listed.  These options"
2919
+                          " are mutually exclusive.  If more than one is"
2920
+                          " specified, the last is used.")
2921
+
2922
+        cmdutil.add_revision_iter_options(select)
2923
+        parser.add_option("", "--skip", dest="skip", 
2924
+                          help="Skip revisions.  Positive numbers skip from "
2925
+                          "beginning, negative skip from end.",
2926
+                          metavar="NUMBER")
2927
+
2928
+        parser.add_option_group(select)
2929
+
2930
+        format = cmdutil.OptionGroup(parser, "Revision format options",
2931
+                          "These control the appearance of listed revisions")
2932
+        format.add_option("", "--location", action="store_const", 
2933
+                         const=paths.determine_path, dest="display", 
2934
+                         help="Show location instead of name", default=str)
2935
+        format.add_option("--import", action="store_const", 
2936
+                         const=paths.determine_import_path, dest="display",  
2937
+                         help="Show location of import file")
2938
+        format.add_option("--log", action="store_const", 
2939
+                         const=paths.determine_log_path, dest="display", 
2940
+                         help="Show location of log file")
2941
+        format.add_option("--patch", action="store_const", 
2942
+                         dest="display", const=paths.determine_patch_path,
2943
+                         help="Show location of patchfile")
2944
+        format.add_option("--continuation", action="store_const", 
2945
+                         const=paths.determine_continuation_path, 
2946
+                         dest="display",
2947
+                         help="Show location of continuation file")
2948
+        format.add_option("--cacherev", action="store_const", 
2949
+                         const=paths.determine_cacherev_path, dest="display",
2950
+                         help="Show location of cacherev file")
2951
+        format.add_option("--changelog", action="store_const", 
2952
+                         const=self.changelog_append, dest="display",
2953
+                         help="Show location of cacherev file")
2954
+        parser.add_option_group(format)
2955
+        display = cmdutil.OptionGroup(parser, "Display format options",
2956
+                          "These control the display of data")
2957
+        display.add_option("-r", "--reverse", action="store_true", 
2958
+                          dest="reverse", help="Sort from newest to oldest")
2959
+        display.add_option("-s", "--summary", action="store_true", 
2960
+                          dest="summary", help="Show patchlog summary")
2961
+        display.add_option("-D", "--date", action="store_true", 
2962
+                          dest="date", help="Show patchlog date")
2963
+        display.add_option("-c", "--creator", action="store_true", 
2964
+                          dest="creator", help="Show the id that committed the"
2965
+                          " revision")
2966
+        display.add_option("-m", "--merges", action="store_true", 
2967
+                          dest="merges", help="Show the revisions that were"
2968
+                          " merged")
2969
+        parser.add_option_group(display)
2970
+        return parser 
2971
+    def help(self, parser=None):
2972
+        """Attempt to explain the revisions command
2973
+        
2974
+        :param parser: If supplied, used to determine options
2975
+        """
2976
+        if parser==None:
2977
+            parser=self.get_parser()
2978
+        parser.print_help()
2979
+        print """List revisions.
2980
+        """
2981
+        help_tree_spec()
2982
+
2983
+
2984
+class Get(BaseCommand):
2985
+    """
2986
+    Retrieve a revision from the archive
2987
+    """
2988
+    def __init__(self):
2989
+        self.description="Retrieve a revision from the archive"
2990
+        self.parser=self.get_parser()
2991
+
2992
+
2993
+    def get_completer(self, arg, index):
2994
+        if index > 0:
2995
+            return None
2996
+        try:
2997
+            tree = arch.tree_root()
2998
+        except:
2999
+            tree = None
3000
+        return cmdutil.iter_revision_completions(arg, tree)
3001
+
3002
+
3003
+    def do_command(self, cmdargs):
3004
+        """
3005
+        Master function that perfoms the "get" command.
3006
+        """
3007
+        (options, args) = self.parser.parse_args(cmdargs)
3008
+        if len(args) < 1:
3009
+            return self.help()            
3010
+        try:
3011
+            tree = arch.tree_root()
3012
+        except arch.errors.TreeRootError:
3013
+            tree = None
3014
+        
3015
+        arch_loc = None
3016
+        try:
3017
+            revision, arch_loc = paths.full_path_decode(args[0])
3018
+        except Exception, e:
3019
+            revision = cmdutil.determine_revision_arch(tree, args[0], 
3020
+                check_existence=False, allow_package=True)
3021
+        if len(args) > 1:
3022
+            directory = args[1]
3023
+        else:
3024
+            directory = str(revision.nonarch)
3025
+        if os.path.exists(directory):
3026
+            raise DirectoryExists(directory)
3027
+        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
3028
+        try:
3029
+            cmdutil.ensure_revision_exists(revision)
3030
+        except cmdutil.NoSuchRevision, e:
3031
+            raise CommandFailedWrapper(e)
3032
+
3033
+        link = cmdutil.prompt ("get link")
3034
+        for line in cmdutil.iter_get(revision, directory, link,
3035
+                                     options.no_pristine,
3036
+                                     options.no_greedy_add):
3037
+            cmdutil.colorize(line)
3038
+
3039
+    def get_parser(self):
3040
+        """
3041
+        Returns the options parser to use for the "get" command.
3042
+
3043
+        :rtype: cmdutil.CmdOptionParser
3044
+        """
3045
+        parser=cmdutil.CmdOptionParser("fai get revision [dir]")
3046
+        parser.add_option("--no-pristine", action="store_true", 
3047
+                         dest="no_pristine", 
3048
+                         help="Do not make pristine copy for reference")
3049
+        parser.add_option("--no-greedy-add", action="store_true", 
3050
+                         dest="no_greedy_add", 
3051
+                         help="Never add to greedy libraries")
3052
+
3053
+        return parser 
3054
+
3055
+    def help(self, parser=None):
3056
+        """
3057
+        Prints a help message.
3058
+
3059
+        :param parser: If supplied, the parser to use for generating help.  If \
3060
+        not supplied, it is retrieved.
3061
+        :type parser: cmdutil.CmdOptionParser
3062
+        """
3063
+        if parser==None:
3064
+            parser=self.get_parser()
3065
+        parser.print_help()
3066
+        print """
3067
+Expands aliases and constructs a project tree for a revision.  If the optional
3068
+"dir" argument is provided, the project tree will be stored in this directory.
3069
+        """
3070
+        help_tree_spec()
3071
+        return
3072
+
3073
+class PromptCmd(cmd.Cmd):
3074
+    def __init__(self):
3075
+        cmd.Cmd.__init__(self)
3076
+        self.prompt = "Fai> "
3077
+        try:
3078
+            self.tree = arch.tree_root()
3079
+        except:
3080
+            self.tree = None
3081
+        self.set_title()
3082
+        self.set_prompt()
3083
+        self.fake_aba = abacmds.AbaCmds()
3084
+        self.identchars += '-'
3085
+        self.history_file = os.path.expanduser("~/.fai-history")
3086
+        readline.set_completer_delims(string.whitespace)
3087
+        if os.access(self.history_file, os.R_OK) and \
3088
+            os.path.isfile(self.history_file):
3089
+            readline.read_history_file(self.history_file)
3090
+        self.cwd = os.getcwd()
3091
+
3092
+    def write_history(self):
3093
+        readline.write_history_file(self.history_file)
3094
+
3095
+    def do_quit(self, args):
3096
+        self.write_history()
3097
+        sys.exit(0)
3098
+
3099
+    def do_exit(self, args):
3100
+        self.do_quit(args)
3101
+
3102
+    def do_EOF(self, args):
3103
+        print
3104
+        self.do_quit(args)
3105
+
3106
+    def postcmd(self, line, bar):
3107
+        self.set_title()
3108
+        self.set_prompt()
3109
+
3110
+    def set_prompt(self):
3111
+        if self.tree is not None:
3112
+            try:
3113
+                prompt = pylon.alias_or_version(self.tree.tree_version, 
3114
+                                                self.tree, 
3115
+                                                full=False)
3116
+                if prompt is not None:
3117
+                    prompt = " " + prompt
3118
+            except:
3119
+                prompt = ""
3120
+        else:
3121
+            prompt = ""
3122
+        self.prompt = "Fai%s> " % prompt
3123
+
3124
+    def set_title(self, command=None):
3125
+        try:
3126
+            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
3127
+                                             full=False)
3128
+        except:
3129
+            version = "[no version]"
3130
+        if command is None:
3131
+            command = ""
3132
+        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
3133
+
3134
+    def do_cd(self, line):
3135
+        if line == "":
3136
+            line = "~"
3137
+        line = os.path.expanduser(line)
3138
+        if os.path.isabs(line):
3139
+            newcwd = line
3140
+        else:
3141
+            newcwd = self.cwd+'/'+line
3142
+        newcwd = os.path.normpath(newcwd)
3143
+        try:
3144
+            os.chdir(newcwd)
3145
+            self.cwd = newcwd
3146
+        except Exception, e:
3147
+            print e
3148
+        try:
3149
+            self.tree = arch.tree_root()
3150
+        except:
3151
+            self.tree = None
3152
+
3153
+    def do_help(self, line):
3154
+        Help()(line)
3155
+
3156
+    def default(self, line):
3157
+        args = line.split()
3158
+        if find_command(args[0]):
3159
+            try:
3160
+                find_command(args[0]).do_command(args[1:])
3161
+            except cmdutil.BadCommandOption, e:
3162
+                print e
3163
+            except cmdutil.GetHelp, e:
3164
+                find_command(args[0]).help()
3165
+            except CommandFailed, e:
3166
+                print e
3167
+            except arch.errors.ArchiveNotRegistered, e:
3168
+                print e
3169
+            except KeyboardInterrupt, e:
3170
+                print "Interrupted"
3171
+            except arch.util.ExecProblem, e:
3172
+                print e.proc.error.rstrip('\n')
3173
+            except cmdutil.CantDetermineVersion, e:
3174
+                print e
3175
+            except cmdutil.CantDetermineRevision, e:
3176
+                print e
3177
+            except Exception, e:
3178
+                print "Unhandled error:\n%s" % errors.exception_str(e)
3179
+
3180
+        elif suggestions.has_key(args[0]):
3181
+            print suggestions[args[0]]
3182
+
3183
+        elif self.fake_aba.is_command(args[0]):
3184
+            tree = None
3185
+            try:
3186
+                tree = arch.tree_root()
3187
+            except arch.errors.TreeRootError:
3188
+                pass
3189
+            cmd = self.fake_aba.is_command(args[0])
3190
+            try:
3191
+                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
3192
+            except KeyboardInterrupt, e:
3193
+                print "Interrupted"
3194
+
3195
+        elif options.tla_fallthrough and args[0] != "rm" and \
3196
+            cmdutil.is_tla_command(args[0]):
3197
+            try:
3198
+                tree = None
3199
+                try:
3200
+                    tree = arch.tree_root()
3201
+                except arch.errors.TreeRootError:
3202
+                    pass
3203
+                args = cmdutil.expand_prefix_alias(args, tree)
3204
+                arch.util.exec_safe('tla', args, stderr=sys.stderr,
3205
+                expected=(0, 1))
3206
+            except arch.util.ExecProblem, e:
3207
+                pass
3208
+            except KeyboardInterrupt, e:
3209
+                print "Interrupted"
3210
+        else:
3211
+            try:
3212
+                try:
3213
+                    tree = arch.tree_root()
3214
+                except arch.errors.TreeRootError:
3215
+                    tree = None
3216
+                args=line.split()
3217
+                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
3218
+            except KeyboardInterrupt, e:
3219
+                print "Interrupted"
3220
+
3221
+    def completenames(self, text, line, begidx, endidx):
3222
+        completions = []
3223
+        iter = iter_command_names(self.fake_aba)
3224
+        try:
3225
+            if len(line) > 0:
3226
+                arg = line.split()[-1]
3227
+            else:
3228
+                arg = ""
3229
+            iter = cmdutil.iter_munged_completions(iter, arg, text)
3230
+        except Exception, e:
3231
+            print e
3232
+        return list(iter)
3233
+
3234
+    def completedefault(self, text, line, begidx, endidx):
3235
+        """Perform completion for native commands.
3236
+        
3237
+        :param text: The text to complete
3238
+        :type text: str
3239
+        :param line: The entire line to complete
3240
+        :type line: str
3241
+        :param begidx: The start of the text in the line
3242
+        :type begidx: int
3243
+        :param endidx: The end of the text in the line
3244
+        :type endidx: int
3245
+        """
3246
+        try:
3247
+            (cmd, args, foo) = self.parseline(line)
3248
+            command_obj=find_command(cmd)
3249
+            if command_obj is not None:
3250
+                return command_obj.complete(args.split(), text)
3251
+            elif not self.fake_aba.is_command(cmd) and \
3252
+                cmdutil.is_tla_command(cmd):
3253
+                iter = cmdutil.iter_supported_switches(cmd)
3254
+                if len(args) > 0:
3255
+                    arg = args.split()[-1]
3256
+                else:
3257
+                    arg = ""
3258
+                if arg.startswith("-"):
3259
+                    return list(cmdutil.iter_munged_completions(iter, arg, 
3260
+                                                                text))
3261
+                else:
3262
+                    return list(cmdutil.iter_munged_completions(
3263
+                        cmdutil.iter_file_completions(arg), arg, text))
3264
+
3265
+
3266
+            elif cmd == "cd":
3267
+                if len(args) > 0:
3268
+                    arg = args.split()[-1]
3269
+                else:
3270
+                    arg = ""
3271
+                iter = cmdutil.iter_dir_completions(arg)
3272
+                iter = cmdutil.iter_munged_completions(iter, arg, text)
3273
+                return list(iter)
3274
+            elif len(args)>0:
3275
+                arg = args.split()[-1]
3276
+                iter = cmdutil.iter_file_completions(arg)
3277
+                return list(cmdutil.iter_munged_completions(iter, arg, text))
3278
+            else:
3279
+                return self.completenames(text, line, begidx, endidx)
3280
+        except Exception, e:
3281
+            print e
3282
+
3283
+
3284
+def iter_command_names(fake_aba):
3285
+    for entry in cmdutil.iter_combine([commands.iterkeys(), 
3286
+                                     fake_aba.get_commands(), 
3287
+                                     cmdutil.iter_tla_commands(False)]):
3288
+        if not suggestions.has_key(str(entry)):
3289
+            yield entry
3290
+
3291
+
3292
+def iter_source_file_completions(tree, arg):
3293
+    treepath = arch_compound.tree_cwd(tree)
3294
+    if len(treepath) > 0:
3295
+        dirs = [treepath]
3296
+    else:
3297
+        dirs = None
3298
+    for file in tree.iter_inventory(dirs, source=True, both=True):
3299
+        file = file_completion_match(file, treepath, arg)
3300
+        if file is not None:
3301
+            yield file
3302
+
3303
+
3304
+def iter_untagged(tree, dirs):
3305
+    for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False, 
3306
+                                                categories=arch_core.non_root,
3307
+                                                control_files=True):
3308
+        yield file.name 
3309
+
3310
+
3311
+def iter_untagged_completions(tree, arg):
3312
+    """Generate an iterator for all visible untagged files that match arg.
3313
+
3314
+    :param tree: The tree to look for untagged files in
3315
+    :type tree: `arch.WorkingTree`
3316
+    :param arg: The argument to match
3317
+    :type arg: str
3318
+    :return: An iterator of all matching untagged files
3319
+    :rtype: iterator of str
3320
+    """
3321
+    treepath = arch_compound.tree_cwd(tree)
3322
+    if len(treepath) > 0:
3323
+        dirs = [treepath]
3324
+    else:
3325
+        dirs = None
3326
+
3327
+    for file in iter_untagged(tree, dirs):
3328
+        file = file_completion_match(file, treepath, arg)
3329
+        if file is not None:
3330
+            yield file
3331
+
3332
+
3333
+def file_completion_match(file, treepath, arg):
3334
+    """Determines whether a file within an arch tree matches the argument.
3335
+
3336
+    :param file: The rooted filename
3337
+    :type file: str
3338
+    :param treepath: The path to the cwd within the tree
3339
+    :type treepath: str
3340
+    :param arg: The prefix to match
3341
+    :return: The completion name, or None if not a match
3342
+    :rtype: str
3343
+    """
3344
+    if not file.startswith(treepath):
3345
+        return None
3346
+    if treepath != "":
3347
+        file = file[len(treepath)+1:]
3348
+
3349
+    if not file.startswith(arg):
3350
+        return None 
3351
+    if os.path.isdir(file):
3352
+        file += '/'
3353
+    return file
3354
+
3355
+def iter_modified_file_completions(tree, arg):
3356
+    """Returns a list of modified files that match the specified prefix.
3357
+
3358
+    :param tree: The current tree
3359
+    :type tree: `arch.WorkingTree`
3360
+    :param arg: The prefix to match
3361
+    :type arg: str
3362
+    """
3363
+    treepath = arch_compound.tree_cwd(tree)
3364
+    tmpdir = util.tmpdir()
3365
+    changeset = tmpdir+"/changeset"
3366
+    completions = []
3367
+    revision = cmdutil.determine_revision_tree(tree)
3368
+    for line in arch.iter_delta(revision, tree, changeset):
3369
+        if isinstance(line, arch.FileModification):
3370
+            file = file_completion_match(line.name[1:], treepath, arg)
3371
+            if file is not None:
3372
+                completions.append(file)
3373
+    shutil.rmtree(tmpdir)
3374
+    return completions
3375
+
3376
+class Shell(BaseCommand):
3377
+    def __init__(self):
3378
+        self.description = "Runs Fai as a shell"
3379
+
3380
+    def do_command(self, cmdargs):
3381
+        if len(cmdargs)!=0:
3382
+            raise cmdutil.GetHelp
3383
+        prompt = PromptCmd()
3384
+        try:
3385
+            prompt.cmdloop()
3386
+        finally:
3387
+            prompt.write_history()
3388
+
3389
+class AddID(BaseCommand):
3390
+    """
3391
+    Adds an inventory id for the given file
3392
+    """
3393
+    def __init__(self):
3394
+        self.description="Add an inventory id for a given file"
3395
+
3396
+    def get_completer(self, arg, index):
3397
+        tree = arch.tree_root()
3398
+        return iter_untagged_completions(tree, arg)
3399
+
3400
+    def do_command(self, cmdargs):
3401
+        """
3402
+        Master function that perfoms the "revision" command.
3403
+        """
3404
+        parser=self.get_parser()
3405
+        (options, args) = parser.parse_args(cmdargs)
3406
+
3407
+        try:
3408
+            tree = arch.tree_root()
3409
+        except arch.errors.TreeRootError, e:
3410
+            raise pylon.errors.CommandFailedWrapper(e)
3411
+            
3412
+
3413
+        if (len(args) == 0) == (options.untagged == False):
3414
+            raise cmdutil.GetHelp
3415
+
3416
+       #if options.id and len(args) != 1:
3417
+       #    print "If --id is specified, only one file can be named."
3418
+       #    return
3419
+        
3420
+        method = tree.tagging_method
3421
+        
3422
+        if options.id_type == "tagline":
3423
+            if method != "tagline":
3424
+                if not cmdutil.prompt("Tagline in other tree"):
3425
+                    if method == "explicit" or method == "implicit":
3426
+                        options.id_type == method
3427
+                    else:
3428
+                        print "add-id not supported for \"%s\" tagging method"\
3429
+                            % method 
3430
+                        return
3431
+        
3432
+        elif options.id_type == "implicit":
3433
+            if method != "implicit":
3434
+                if not cmdutil.prompt("Implicit in other tree"):
3435
+                    if method == "explicit" or method == "tagline":
3436
+                        options.id_type == method
3437
+                    else:
3438
+                        print "add-id not supported for \"%s\" tagging method"\
3439
+                            % method 
3440
+                        return
3441
+        elif options.id_type == "explicit":
3442
+            if method != "tagline" and method != explicit:
3443
+                if not prompt("Explicit in other tree"):
3444
+                    print "add-id not supported for \"%s\" tagging method" % \
3445
+                        method
3446
+                    return
3447
+        
3448
+        if options.id_type == "auto":
3449
+            if method != "tagline" and method != "explicit" \
3450
+                and method !="implicit":
3451
+                print "add-id not supported for \"%s\" tagging method" % method
3452
+                return
3453
+            else:
3454
+                options.id_type = method
3455
+        if options.untagged:
3456
+            args = None
3457
+        self.add_ids(tree, options.id_type, args)
3458
+
3459
+    def add_ids(self, tree, id_type, files=()):
3460
+        """Add inventory ids to files.
3461
+        
3462
+        :param tree: the tree the files are in
3463
+        :type tree: `arch.WorkingTree`
3464
+        :param id_type: the type of id to add: "explicit" or "tagline"
3465
+        :type id_type: str
3466
+        :param files: The list of files to add.  If None do all untagged.
3467
+        :type files: tuple of str
3468
+        """
3469
+
3470
+        untagged = (files is None)
3471
+        if untagged:
3472
+            files = list(iter_untagged(tree, None))
3473
+        previous_files = []
3474
+        while len(files) > 0:
3475
+            previous_files.extend(files)
3476
+            if id_type == "explicit":
3477
+                cmdutil.add_id(files)
3478
+            elif id_type == "tagline" or id_type == "implicit":
3479
+                for file in files:
3480
+                    try:
3481
+                        implicit = (id_type == "implicit")
3482
+                        cmdutil.add_tagline_or_explicit_id(file, False,
3483
+                                                           implicit)
3484
+                    except cmdutil.AlreadyTagged:
3485
+                        print "\"%s\" already has a tagline." % file
3486
+                    except cmdutil.NoCommentSyntax:
3487
+                        pass
3488
+            #do inventory after tagging until no untagged files are encountered
3489
+            if untagged:
3490
+                files = []
3491
+                for file in iter_untagged(tree, None):
3492
+                    if not file in previous_files:
3493
+                        files.append(file)
3494
+
3495
+            else:
3496
+                break
3497
+
3498
+    def get_parser(self):
3499
+        """
3500
+        Returns the options parser to use for the "revision" command.
3501
+
3502
+        :rtype: cmdutil.CmdOptionParser
3503
+        """
3504
+        parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
3505
+# ddaa suggests removing this to promote GUIDs.  Let's see who squalks.
3506
+#        parser.add_option("-i", "--id", dest="id", 
3507
+#                         help="Specify id for a single file", default=None)
3508
+        parser.add_option("--tltl", action="store_true", 
3509
+                         dest="lord_style",  help="Use Tom Lord's style of id.")
3510
+        parser.add_option("--explicit", action="store_const", 
3511
+                         const="explicit", dest="id_type", 
3512
+                         help="Use an explicit id", default="auto")
3513
+        parser.add_option("--tagline", action="store_const", 
3514
+                         const="tagline", dest="id_type", 
3515
+                         help="Use a tagline id")
3516
+        parser.add_option("--implicit", action="store_const", 
3517
+                         const="implicit", dest="id_type", 
3518
+                         help="Use an implicit id (deprecated)")
3519
+        parser.add_option("--untagged", action="store_true", 
3520
+                         dest="untagged", default=False, 
3521
+                         help="tag all untagged files")
3522
+        return parser 
3523
+
3524
+    def help(self, parser=None):
3525
+        """
3526
+        Prints a help message.
3527
+
3528
+        :param parser: If supplied, the parser to use for generating help.  If \
3529
+        not supplied, it is retrieved.
3530
+        :type parser: cmdutil.CmdOptionParser
3531
+        """
3532
+        if parser==None:
3533
+            parser=self.get_parser()
3534
+        parser.print_help()
3535
+        print """
3536
+Adds an inventory to the specified file(s) and directories.  If --untagged is
3537
+specified, adds inventory to all untagged files and directories.
3538
+        """
3539
+        return
3540
+
3541
+
3542
+class Merge(BaseCommand):
3543
+    """
3544
+    Merges changes from other versions into the current tree
3545
+    """
3546
+    def __init__(self):
3547
+        self.description="Merges changes from other versions"
3548
+        try:
3549
+            self.tree = arch.tree_root()
3550
+        except:
3551
+            self.tree = None
3552
+
3553
+
3554
+    def get_completer(self, arg, index):
3555
+        if self.tree is None:
3556
+            raise arch.errors.TreeRootError
3557
+        return cmdutil.merge_completions(self.tree, arg, index)
3558
+
3559
+    def do_command(self, cmdargs):
3560
+        """
3561
+        Master function that perfoms the "merge" command.
3562
+        """
3563
+        parser=self.get_parser()
3564
+        (options, args) = parser.parse_args(cmdargs)
3565
+        if options.diff3:
3566
+            action="star-merge"
3567
+        else:
3568
+            action = options.action
3569
+        
3570
+        if self.tree is None:
3571
+            raise arch.errors.TreeRootError(os.getcwd())
3572
+        if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
3573
+            raise UncommittedChanges(self.tree)
3574
+
3575
+        if len(args) > 0:
3576
+            revisions = []
3577
+            for arg in args:
3578
+                revisions.append(cmdutil.determine_revision_arch(self.tree, 
3579
+                                                                 arg))
3580
+            source = "from commandline"
3581
+        else:
3582
+            revisions = ancillary.iter_partner_revisions(self.tree, 
3583
+                                                         self.tree.tree_version)
3584
+            source = "from partner version"
3585
+        revisions = misc.rewind_iterator(revisions)
3586
+        try:
3587
+            revisions.next()
3588
+            revisions.rewind()
3589
+        except StopIteration, e:
3590
+            revision = cmdutil.tag_cur(self.tree)
3591
+            if revision is None:
3592
+                raise CantDetermineRevision("", "No version specified, no "
3593
+                                            "partner-versions, and no tag"
3594
+                                            " source")
3595
+            revisions = [revision]
3596
+            source = "from tag source"
3597
+        for revision in revisions:
3598
+            cmdutil.ensure_archive_registered(revision.archive)
3599
+            cmdutil.colorize(arch.Chatter("* Merging %s [%s]" % 
3600
+                             (revision, source)))
3601
+            if action=="native-merge" or action=="update":
3602
+                if self.native_merge(revision, action) == 0:
3603
+                    continue
3604
+            elif action=="star-merge":
3605
+                try: 
3606
+                    self.star_merge(revision, options.diff3)
3607
+                except errors.MergeProblem, e:
3608
+                    break
3609
+            if cmdutil.has_changed(self.tree.tree_version):
3610
+                break
3611
+
3612
+    def star_merge(self, revision, diff3):
3613
+        """Perform a star-merge on the current tree.
3614
+        
3615
+        :param revision: The revision to use for the merge
3616
+        :type revision: `arch.Revision`
3617
+        :param diff3: If true, do a diff3 merge
3618
+        :type diff3: bool
3619
+        """
3620
+        try:
3621
+            for line in self.tree.iter_star_merge(revision, diff3=diff3):
3622
+                cmdutil.colorize(line)
3623
+        except arch.util.ExecProblem, e:
3624
+            if e.proc.status is not None and e.proc.status == 1:
3625
+                if e.proc.error:
3626
+                    print e.proc.error
3627
+                raise MergeProblem
3628
+            else:
3629
+                raise
3630
+
3631
+    def native_merge(self, other_revision, action):
3632
+        """Perform a native-merge on the current tree.
3633
+        
3634
+        :param other_revision: The revision to use for the merge
3635
+        :type other_revision: `arch.Revision`
3636
+        :return: 0 if the merge was skipped, 1 if it was applied
3637
+        """
3638
+        other_tree = arch_compound.find_or_make_local_revision(other_revision)
3639
+        try:
3640
+            if action == "native-merge":
3641
+                ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, 
3642
+                                                         other_revision)
3643
+            elif action == "update":
3644
+                ancestor = arch_compound.tree_latest(self.tree, 
3645
+                                                     other_revision.version)
3646
+        except CantDetermineRevision, e:
3647
+            raise CommandFailedWrapper(e)
3648
+        cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
3649
+        if (ancestor == other_revision):
3650
+            cmdutil.colorize(arch.Chatter("* Skipping redundant merge" 
3651
+                                          % ancestor))
3652
+            return 0
3653
+        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
3654
+        for line in cmdutil.iter_apply_delta_filter(delta):
3655
+            cmdutil.colorize(line)
3656
+        return 1
3657
+
3658
+
3659
+
3660
+    def get_parser(self):
3661
+        """
3662
+        Returns the options parser to use for the "merge" command.
3663
+
3664
+        :rtype: cmdutil.CmdOptionParser
3665
+        """
3666
+        parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
3667
+        parser.add_option("-s", "--star-merge", action="store_const",
3668
+                          dest="action", help="Use star-merge",
3669
+                          const="star-merge", default="native-merge")
3670
+        parser.add_option("--update", action="store_const",
3671
+                          dest="action", help="Use update picker",
3672
+                          const="update")
3673
+        parser.add_option("--diff3", action="store_true", 
3674
+                         dest="diff3",  
3675
+                         help="Use diff3 for merge (implies star-merge)")
3676
+        return parser 
3677
+
3678
+    def help(self, parser=None):
3679
+        """
3680
+        Prints a help message.
3681
+
3682
+        :param parser: If supplied, the parser to use for generating help.  If \
3683
+        not supplied, it is retrieved.
3684
+        :type parser: cmdutil.CmdOptionParser
3685
+        """
3686
+        if parser==None:
3687
+            parser=self.get_parser()
3688
+        parser.print_help()
3689
+        print """
3690
+Performs a merge operation using the specified version.
3691
+        """
3692
+        return
3693
+
3694
+class ELog(BaseCommand):
3695
+    """
3696
+    Produces a raw patchlog and invokes the user's editor
3697
+    """
3698
+    def __init__(self):
3699
+        self.description="Edit a patchlog to commit"
3700
+        try:
3701
+            self.tree = arch.tree_root()
3702
+        except:
3703
+            self.tree = None
3704
+
3705
+
3706
+    def do_command(self, cmdargs):
3707
+        """
3708
+        Master function that perfoms the "elog" command.
3709
+        """
3710
+        parser=self.get_parser()
3711
+        (options, args) = parser.parse_args(cmdargs)
3712
+        if self.tree is None:
3713
+            raise arch.errors.TreeRootError
3714
+
3715
+        try:
3716
+            edit_log(self.tree, self.tree.tree_version)
3717
+        except pylon.errors.NoEditorSpecified, e:
3718
+            raise pylon.errors.CommandFailedWrapper(e)
3719
+
3720
+    def get_parser(self):
3721
+        """
3722
+        Returns the options parser to use for the "merge" command.
3723
+
3724
+        :rtype: cmdutil.CmdOptionParser
3725
+        """
3726
+        parser=cmdutil.CmdOptionParser("fai elog")
3727
+        return parser 
3728
+
3729
+
3730
+    def help(self, parser=None):
3731
+        """
3732
+        Invokes $EDITOR to produce a log for committing.
3733
+
3734
+        :param parser: If supplied, the parser to use for generating help.  If \
3735
+        not supplied, it is retrieved.
3736
+        :type parser: cmdutil.CmdOptionParser
3737
+        """
3738
+        if parser==None:
3739
+            parser=self.get_parser()
3740
+        parser.print_help()
3741
+        print """
3742
+Invokes $EDITOR to produce a log for committing.
3743
+        """
3744
+        return
3745
+
3746
+def edit_log(tree, version):
3747
+    """Makes and edits the log for a tree.  Does all kinds of fancy things
3748
+    like log templates and merge summaries and log-for-merge
3749
+    
3750
+    :param tree: The tree to edit the log for
3751
+    :type tree: `arch.WorkingTree`
3752
+    """
3753
+    #ensure we have an editor before preparing the log
3754
+    cmdutil.find_editor()
3755
+    log = tree.log_message(create=False, version=version)
3756
+    log_is_new = False
3757
+    if log is None or cmdutil.prompt("Overwrite log"):
3758
+        if log is not None:
3759
+           os.remove(log.name)
3760
+        log = tree.log_message(create=True, version=version)
3761
+        log_is_new = True
3762
+        tmplog = log.name
3763
+        template = pylon.log_template_path(tree)
3764
+        if template:
3765
+            shutil.copyfile(template, tmplog)
3766
+        comp_version = ancillary.comp_revision(tree).version
3767
+        new_merges = cmdutil.iter_new_merges(tree, comp_version)
3768
+        new_merges = cmdutil.direct_merges(new_merges)
3769
+        log["Summary"] = pylon.merge_summary(new_merges, 
3770
+                                         version)
3771
+        if len(new_merges) > 0:   
3772
+            if cmdutil.prompt("Log for merge"):
3773
+                if cmdutil.prompt("changelog for merge"):
3774
+                    mergestuff = "Patches applied:\n"
3775
+                    mergestuff += pylon.changelog_for_merge(new_merges)
3776
+                else:
3777
+                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
3778
+                log.description += mergestuff
3779
+        log.save()
3780
+    try:
3781
+        cmdutil.invoke_editor(log.name)
3782
+    except:
3783
+        if log_is_new:
3784
+            os.remove(log.name)
3785
+        raise
3786
+
3787
+
3788
+class MirrorArchive(BaseCommand):
3789
+    """
3790
+    Updates a mirror from an archive
3791
+    """
3792
+    def __init__(self):
3793
+        self.description="Update a mirror from an archive"
3794
+
3795
+    def do_command(self, cmdargs):
3796
+        """
3797
+        Master function that perfoms the "revision" command.
3798
+        """
3799
+
3800
+        parser=self.get_parser()
3801
+        (options, args) = parser.parse_args(cmdargs)
3802
+        if len(args) > 1:
3803
+            raise GetHelp
3804
+        try:
3805
+            tree = arch.tree_root()
3806
+        except:
3807
+            tree = None
3808
+
3809
+        if len(args) == 0:
3810
+            if tree is not None:
3811
+                name = tree.tree_version()
3812
+        else:
3813
+            name = cmdutil.expand_alias(args[0], tree)
3814
+            name = arch.NameParser(name)
3815
+
3816
+        to_arch = name.get_archive()
3817
+        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
3818
+        limit = name.get_nonarch()
3819
+
3820
+        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
3821
+        for line in arch.chatter_classifier(iter):
3822
+            cmdutil.colorize(line)
3823
+
3824
+    def get_parser(self):
3825
+        """
3826
+        Returns the options parser to use for the "revision" command.
3827
+
3828
+        :rtype: cmdutil.CmdOptionParser
3829
+        """
3830
+        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
3831
+        return parser 
3832
+
3833
+    def help(self, parser=None):
3834
+        """
3835
+        Prints a help message.
3836
+
3837
+        :param parser: If supplied, the parser to use for generating help.  If \
3838
+        not supplied, it is retrieved.
3839
+        :type parser: cmdutil.CmdOptionParser
3840
+        """
3841
+        if parser==None:
3842
+            parser=self.get_parser()
3843
+        parser.print_help()
3844
+        print """
3845
+Updates a mirror from an archive.  If a branch, package, or version is
3846
+supplied, only changes under it are mirrored.
3847
+        """
3848
+        return
3849
+
3850
+def help_tree_spec():
3851
+    print """Specifying revisions (default: tree)
3852
+Revisions may be specified by alias, revision, version or patchlevel.
3853
+Revisions or versions may be fully qualified.  Unqualified revisions, versions, 
3854
+or patchlevels use the archive of the current project tree.  Versions will
3855
+use the latest patchlevel in the tree.  Patchlevels will use the current tree-
3856
+version.
3857
+
3858
+Use "alias" to list available (user and automatic) aliases."""
3859
+
3860
+auto_alias = [
3861
+"acur", 
3862
+"The latest revision in the archive of the tree-version.  You can specify \
3863
+a different version like so: acur:foo--bar--0 (aliases can be used)",
3864
+"tcur",
3865
+"""(tree current) The latest revision in the tree of the tree-version. \
3866
+You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
3867
+used).""",
3868
+"tprev" , 
3869
+"""(tree previous) The previous revision in the tree of the tree-version.  To \
3870
+specify an older revision, use a number, e.g. "tprev:4" """,
3871
+"tanc" , 
3872
+"""(tree ancestor) The ancestor revision of the tree To specify an older \
3873
+revision, use a number, e.g. "tanc:4".""",
3874
+"tdate" , 
3875
+"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
3876
+"tmod" , 
3877
+""" (tree modified) The latest revision to modify a given file, e.g. \
3878
+"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
3879
+"ttag" , 
3880
+"""(tree tag) The revision that was tagged into the current tree revision, \
3881
+according to the tree""",
3882
+"tagcur", 
3883
+"""(tag current) The latest revision of the version that the current tree \
3884
+was tagged from.""",
3885
+"mergeanc" , 
3886
+"""The common ancestor of the current tree and the specified revision. \
3887
+Defaults to the first partner-version's latest revision or to tagcur.""",
3888
+]
3889
+
3890
+
3891
+def is_auto_alias(name):
3892
+    """Determine whether a name is an auto alias name
3893
+
3894
+    :param name: the name to check
3895
+    :type name: str
3896
+    :return: True if the name is an auto alias, false if not
3897
+    :rtype: bool
3898
+    """
3899
+    return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
3900
+
3901
+
3902
+def display_def(iter, wrap = 80):
3903
+    """Display a list of definitions
3904
+
3905
+    :param iter: iter of name, definition pairs
3906
+    :type iter: iter of (str, str)
3907
+    :param wrap: The width for text wrapping
3908
+    :type wrap: int
3909
+    """
3910
+    vals = list(iter)
3911
+    maxlen = 0
3912
+    for (key, value) in vals:
3913
+        if len(key) > maxlen:
3914
+            maxlen = len(key)
3915
+    for (key, value) in vals:
3916
+        tw=textwrap.TextWrapper(width=wrap, 
3917
+                                initial_indent=key.rjust(maxlen)+" : ",
3918
+                                subsequent_indent="".rjust(maxlen+3))
3919
+        print tw.fill(value)
3920
+
3921
+
3922
+def help_aliases(tree):
3923
+    print """Auto-generated aliases"""
3924
+    display_def(pylon.util.iter_pairs(auto_alias))
3925
+    print "User aliases"
3926
+    display_def(ancillary.iter_all_alias(tree))
3927
+
3928
+class Inventory(BaseCommand):
3929
+    """List the status of files in the tree"""
3930
+    def __init__(self):
3931
+        self.description=self.__doc__
3932
+
3933
+    def do_command(self, cmdargs):
3934
+        """
3935
+        Master function that perfoms the "revision" command.
3936
+        """
3937
+
3938
+        parser=self.get_parser()
3939
+        (options, args) = parser.parse_args(cmdargs)
3940
+        tree = arch.tree_root()
3941
+        categories = []
3942
+
3943
+        if (options.source):
3944
+            categories.append(arch_core.SourceFile)
3945
+        if (options.precious):
3946
+            categories.append(arch_core.PreciousFile)
3947
+        if (options.backup):
3948
+            categories.append(arch_core.BackupFile)
3949
+        if (options.junk):
3950
+            categories.append(arch_core.JunkFile)
3951
+
3952
+        if len(categories) == 1:
3953
+            show_leading = False
3954
+        else:
3955
+            show_leading = True
3956
+
3957
+        if len(categories) == 0:
3958
+            categories = None
3959
+
3960
+        if options.untagged:
3961
+            categories = arch_core.non_root
3962
+            show_leading = False
3963
+            tagged = False
3964
+        else:
3965
+            tagged = None
3966
+        
3967
+        for file in arch_core.iter_inventory_filter(tree, None, 
3968
+            control_files=options.control_files, 
3969
+            categories = categories, tagged=tagged):
3970
+            print arch_core.file_line(file, 
3971
+                                      category = show_leading, 
3972
+                                      untagged = show_leading,
3973
+                                      id = options.ids)
3974
+
3975
+    def get_parser(self):
3976
+        """
3977
+        Returns the options parser to use for the "revision" command.
3978
+
3979
+        :rtype: cmdutil.CmdOptionParser
3980
+        """
3981
+        parser=cmdutil.CmdOptionParser("fai inventory [options]")
3982
+        parser.add_option("--ids", action="store_true", dest="ids", 
3983
+                          help="Show file ids")
3984
+        parser.add_option("--control", action="store_true", 
3985
+                          dest="control_files", help="include control files")
3986
+        parser.add_option("--source", action="store_true", dest="source",
3987
+                          help="List source files")
3988
+        parser.add_option("--backup", action="store_true", dest="backup",
3989
+                          help="List backup files")
3990
+        parser.add_option("--precious", action="store_true", dest="precious",
3991
+                          help="List precious files")
3992
+        parser.add_option("--junk", action="store_true", dest="junk",
3993
+                          help="List junk files")
3994
+        parser.add_option("--unrecognized", action="store_true", 
3995
+                          dest="unrecognized", help="List unrecognized files")
3996
+        parser.add_option("--untagged", action="store_true", 
3997
+                          dest="untagged", help="List only untagged files")
3998
+        return parser 
3999
+
4000
+    def help(self, parser=None):
4001
+        """
4002
+        Prints a help message.
4003
+
4004
+        :param parser: If supplied, the parser to use for generating help.  If \
4005
+        not supplied, it is retrieved.
4006
+        :type parser: cmdutil.CmdOptionParser
4007
+        """
4008
+        if parser==None:
4009
+            parser=self.get_parser()
4010
+        parser.print_help()
4011
+        print """
4012
+Lists the status of files in the archive:
4013
+S source
4014
+P precious
4015
+B backup
4016
+J junk
4017
+U unrecognized
4018
+T tree root
4019
+? untagged-source
4020
+Leading letter are not displayed if only one kind of file is shown
4021
+        """
4022
+        return
4023
+
4024
+
4025
+class Alias(BaseCommand):
4026
+    """List or adjust aliases"""
4027
+    def __init__(self):
4028
+        self.description=self.__doc__
4029
+
4030
+    def get_completer(self, arg, index):
4031
+        if index > 2:
4032
+            return ()
4033
+        try:
4034
+            self.tree = arch.tree_root()
4035
+        except:
4036
+            self.tree = None
4037
+
4038
+        if index == 0:
4039
+            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
4040
+        elif index == 1:
4041
+            return cmdutil.iter_revision_completions(arg, self.tree)
4042
+
4043
+
4044
+    def do_command(self, cmdargs):
4045
+        """
4046
+        Master function that perfoms the "revision" command.
4047
+        """
4048
+
4049
+        parser=self.get_parser()
4050
+        (options, args) = parser.parse_args(cmdargs)
4051
+        try:
4052
+            self.tree =  arch.tree_root()
4053
+        except:
4054
+            self.tree = None
4055
+
4056
+
4057
+        try:
4058
+            options.action(args, options)
4059
+        except cmdutil.ForbiddenAliasSyntax, e:
4060
+            raise CommandFailedWrapper(e)
4061
+
4062
+    def no_prefix(self, alias):
4063
+        if alias.startswith("^"):
4064
+            alias = alias[1:]
4065
+        return alias
4066
+        
4067
+    def arg_dispatch(self, args, options):
4068
+        """Add, modify, or list aliases, depending on number of arguments
4069
+
4070
+        :param args: The list of commandline arguments
4071
+        :type args: list of str
4072
+        :param options: The commandline options
4073
+        """
4074
+        if len(args) == 0:
4075
+            help_aliases(self.tree)
4076
+            return
4077
+        else:
4078
+            alias = self.no_prefix(args[0])
4079
+            if len(args) == 1:
4080
+                self.print_alias(alias)
4081
+            elif (len(args)) == 2:
4082
+                self.add(alias, args[1], options)
4083
+            else:
4084
+                raise cmdutil.GetHelp
4085
+
4086
+    def print_alias(self, alias):
4087
+        answer = None
4088
+        if is_auto_alias(alias):
4089
+            raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
4090
+                "  Use \"revision\" to expand auto aliases." % alias)
4091
+        for pair in ancillary.iter_all_alias(self.tree):
4092
+            if pair[0] == alias:
4093
+                answer = pair[1]
4094
+        if answer is not None:
4095
+            print answer
4096
+        else:
4097
+            print "The alias %s is not assigned." % alias
4098
+
4099
+    def add(self, alias, expansion, options):
4100
+        """Add or modify aliases
4101
+
4102
+        :param alias: The alias name to create/modify
4103
+        :type alias: str
4104
+        :param expansion: The expansion to assign to the alias name
4105
+        :type expansion: str
4106
+        :param options: The commandline options
4107
+        """
4108
+        if is_auto_alias(alias):
4109
+            raise IsAutoAlias(alias)
4110
+        newlist = ""
4111
+        written = False
4112
+        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
4113
+            self.tree))
4114
+        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
4115
+
4116
+        for pair in self.get_iterator(options):
4117
+            if pair[0] != alias:
4118
+                newlist+="%s=%s\n" % (pair[0], pair[1])
4119
+            elif not written:
4120
+                newlist+=new_line
4121
+                written = True
4122
+        if not written:
4123
+            newlist+=new_line
4124
+        self.write_aliases(newlist, options)
4125
+            
4126
+    def delete(self, args, options):
4127
+        """Delete the specified alias
4128
+
4129
+        :param args: The list of arguments
4130
+        :type args: list of str
4131
+        :param options: The commandline options
4132
+        """
4133
+        deleted = False
4134
+        if len(args) != 1:
4135
+            raise cmdutil.GetHelp
4136
+        alias = self.no_prefix(args[0])
4137
+        if is_auto_alias(alias):
4138
+            raise IsAutoAlias(alias)
4139
+        newlist = ""
4140
+        for pair in self.get_iterator(options):
4141
+            if pair[0] != alias:
4142
+                newlist+="%s=%s\n" % (pair[0], pair[1])
4143
+            else:
4144
+                deleted = True
4145
+        if not deleted:
4146
+            raise errors.NoSuchAlias(alias)
4147
+        self.write_aliases(newlist, options)
4148
+
4149
+    def get_alias_file(self, options):
4150
+        """Return the name of the alias file to use
4151
+
4152
+        :param options: The commandline options
4153
+        """
4154
+        if options.tree:
4155
+            if self.tree is None:
4156
+                self.tree == arch.tree_root()
4157
+            return str(self.tree)+"/{arch}/+aliases"
4158
+        else:
4159
+            return "~/.aba/aliases"
4160
+
4161
+    def get_iterator(self, options):
4162
+        """Return the alias iterator to use
4163
+
4164
+        :param options: The commandline options
4165
+        """
4166
+        return ancillary.iter_alias(self.get_alias_file(options))
4167
+
4168
+    def write_aliases(self, newlist, options):
4169
+        """Safely rewrite the alias file
4170
+        :param newlist: The new list of aliases
4171
+        :type newlist: str
4172
+        :param options: The commandline options
4173
+        """
4174
+        filename = os.path.expanduser(self.get_alias_file(options))
4175
+        file = util.NewFileVersion(filename)
4176
+        file.write(newlist)
4177
+        file.commit()
4178
+
4179
+
4180
+    def get_parser(self):
4181
+        """
4182
+        Returns the options parser to use for the "alias" command.
4183
+
4184
+        :rtype: cmdutil.CmdOptionParser
4185
+        """
4186
+        parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
4187
+        parser.add_option("-d", "--delete", action="store_const", dest="action",
4188
+                          const=self.delete, default=self.arg_dispatch, 
4189
+                          help="Delete an alias")
4190
+        parser.add_option("--tree", action="store_true", dest="tree", 
4191
+                          help="Create a per-tree alias", default=False)
4192
+        return parser 
4193
+
4194
+    def help(self, parser=None):
4195
+        """
4196
+        Prints a help message.
4197
+
4198
+        :param parser: If supplied, the parser to use for generating help.  If \
4199
+        not supplied, it is retrieved.
4200
+        :type parser: cmdutil.CmdOptionParser
4201
+        """
4202
+        if parser==None:
4203
+            parser=self.get_parser()
4204
+        parser.print_help()
4205
+        print """
4206
+Lists current aliases or modifies the list of aliases.
4207
+
4208
+If no arguments are supplied, aliases will be listed.  If two arguments are
4209
+supplied, the specified alias will be created or modified.  If -d or --delete
4210
+is supplied, the specified alias will be deleted.
4211
+
4212
+You can create aliases that refer to any fully-qualified part of the
4213
+Arch namespace, e.g. 
4214
+archive, 
4215
+archive/category, 
4216
+archive/category--branch, 
4217
+archive/category--branch--version (my favourite)
4218
+archive/category--branch--version--patchlevel
4219
+
4220
+Aliases can be used automatically by native commands.  To use them
4221
+with external or tla commands, prefix them with ^ (you can do this
4222
+with native commands, too).
4223
+"""
4224
+
4225
+
4226
+class RequestMerge(BaseCommand):
4227
+    """Submit a merge request to Bug Goo"""
4228
+    def __init__(self):
4229
+        self.description=self.__doc__
4230
+
4231
+    def do_command(self, cmdargs):
4232
+        """Submit a merge request
4233
+
4234
+        :param cmdargs: The commandline arguments
4235
+        :type cmdargs: list of str
4236
+        """
4237
+        parser = self.get_parser()
4238
+        (options, args) = parser.parse_args(cmdargs)
4239
+        try:
4240
+            cmdutil.find_editor()
4241
+        except pylon.errors.NoEditorSpecified, e:
4242
+            raise pylon.errors.CommandFailedWrapper(e)
4243
+        try:
4244
+            self.tree=arch.tree_root()
4245
+        except:
4246
+            self.tree=None
4247
+        base, revisions = self.revision_specs(args)
4248
+        message = self.make_headers(base, revisions)
4249
+        message += self.make_summary(revisions)
4250
+        path = self.edit_message(message)
4251
+        message = self.tidy_message(path)
4252
+        if cmdutil.prompt("Send merge"):
4253
+            self.send_message(message)
4254
+            print "Merge request sent"
4255
+
4256
+    def make_headers(self, base, revisions):
4257
+        """Produce email and Bug Goo header strings
4258
+
4259
+        :param base: The base revision to apply merges to
4260
+        :type base: `arch.Revision`
4261
+        :param revisions: The revisions to replay into the base
4262
+        :type revisions: list of `arch.Patchlog`
4263
+        :return: The headers
4264
+        :rtype: str
4265
+        """
4266
+        headers = "To: gnu-arch-users@gnu.org\n"
4267
+        headers += "From: %s\n" % options.fromaddr
4268
+        if len(revisions) == 1:
4269
+            headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
4270
+        else:
4271
+            headers += "Subject: [MERGE REQUEST]\n"
4272
+        headers += "\n"
4273
+        headers += "Base-Revision: %s\n" % base
4274
+        for revision in revisions:
4275
+            headers += "Revision: %s\n" % revision.revision
4276
+        headers += "Bug: \n\n"
4277
+        return headers
4278
+
4279
+    def make_summary(self, logs):
4280
+        """Generate a summary of merges
4281
+
4282
+        :param logs: the patchlogs that were directly added by the merges
4283
+        :type logs: list of `arch.Patchlog`
4284
+        :return: the summary
4285
+        :rtype: str
4286
+        """ 
4287
+        summary = ""
4288
+        for log in logs:
4289
+            summary+=str(log.revision)+"\n"
4290
+            summary+=log.summary+"\n"
4291
+            if log.description.strip():
4292
+                summary+=log.description.strip('\n')+"\n\n"
4293
+        return summary
4294
+
4295
+    def revision_specs(self, args):
4296
+        """Determine the base and merge revisions from tree and arguments.
4297
+
4298
+        :param args: The parsed arguments
4299
+        :type args: list of str
4300
+        :return: The base revision and merge revisions 
4301
+        :rtype: `arch.Revision`, list of `arch.Patchlog`
4302
+        """
4303
+        if len(args) > 0:
4304
+            target_revision = cmdutil.determine_revision_arch(self.tree, 
4305
+                                                              args[0])
4306
+        else:
4307
+            target_revision = arch_compound.tree_latest(self.tree)
4308
+        if len(args) > 1:
4309
+            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
4310
+                       self.tree, f)) for f in args[1:] ]
4311
+        else:
4312
+            if self.tree is None:
4313
+                raise CantDetermineRevision("", "Not in a project tree")
4314
+            merge_iter = cmdutil.iter_new_merges(self.tree, 
4315
+                                                 target_revision.version, 
4316
+                                                 False)
4317
+            merges = [f for f in cmdutil.direct_merges(merge_iter)]
4318
+        return (target_revision, merges)
4319
+
4320
+    def edit_message(self, message):
4321
+        """Edit an email message in the user's standard editor
4322
+
4323
+        :param message: The message to edit
4324
+        :type message: str
4325
+        :return: the path of the edited message
4326
+        :rtype: str
4327
+        """
4328
+        if self.tree is None:
4329
+            path = os.get_cwd()
4330
+        else:
4331
+            path = self.tree
4332
+        path += "/,merge-request"
4333
+        file = open(path, 'w')
4334
+        file.write(message)
4335
+        file.flush()
4336
+        cmdutil.invoke_editor(path)
4337
+        return path
4338
+
4339
+    def tidy_message(self, path):
4340
+        """Validate and clean up message.
4341
+
4342
+        :param path: The path to the message to clean up
4343
+        :type path: str
4344
+        :return: The parsed message
4345
+        :rtype: `email.Message`
4346
+        """
4347
+        mail = email.message_from_file(open(path))
4348
+        if mail["Subject"].strip() == "[MERGE REQUEST]":
4349
+            raise BlandSubject
4350
+        
4351
+        request = email.message_from_string(mail.get_payload())
4352
+        if request.has_key("Bug"):
4353
+            if request["Bug"].strip()=="":
4354
+                del request["Bug"]
4355
+        mail.set_payload(request.as_string())
4356
+        return mail
4357
+
4358
+    def send_message(self, message):
4359
+        """Send a message, using its headers to address it.
4360
+
4361
+        :param message: The message to send
4362
+        :type message: `email.Message`"""
4363
+        server = smtplib.SMTP("localhost")
4364
+        server.sendmail(message['From'], message['To'], message.as_string())
4365
+        server.quit()
4366
+
4367
+    def help(self, parser=None):
4368
+        """Print a usage message
4369
+
4370
+        :param parser: The options parser to use
4371
+        :type parser: `cmdutil.CmdOptionParser`
4372
+        """
4373
+        if parser is None:
4374
+            parser = self.get_parser()
4375
+        parser.print_help()
4376
+        print """
4377
+Sends a merge request formatted for Bug Goo.  Intended use: get the tree
4378
+you'd like to merge into.  Apply the merges you want.  Invoke request-merge.
4379
+The merge request will open in your $EDITOR.
4380
+
4381
+When no TARGET is specified, it uses the current tree revision.  When
4382
+no MERGE is specified, it uses the direct merges (as in "revisions
4383
+--direct-merges").  But you can specify just the TARGET, or all the MERGE
4384
+revisions.
4385
+"""
4386
+
4387
+    def get_parser(self):
4388
+        """Produce a commandline parser for this command.
4389
+
4390
+        :rtype: `cmdutil.CmdOptionParser`
4391
+        """
4392
+        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
4393
+        return parser
4394
+
4395
+commands = { 
4396
+'changes' : Changes,
4397
+'help' : Help,
4398
+'update': Update,
4399
+'apply-changes':ApplyChanges,
4400
+'cat-log': CatLog,
4401
+'commit': Commit,
4402
+'revision': Revision,
4403
+'revisions': Revisions,
4404
+'get': Get,
4405
+'revert': Revert,
4406
+'shell': Shell,
4407
+'add-id': AddID,
4408
+'merge': Merge,
4409
+'elog': ELog,
4410
+'mirror-archive': MirrorArchive,
4411
+'ninventory': Inventory,
4412
+'alias' : Alias,
4413
+'request-merge': RequestMerge,
4414
+}
4415
+
4416
+def my_import(mod_name):
4417
+    module = __import__(mod_name)
4418
+    components = mod_name.split('.')
4419
+    for comp in components[1:]:
4420
+        module = getattr(module, comp)
4421
+    return module
4422
+
4423
+def plugin(mod_name):
4424
+    module = my_import(mod_name)
4425
+    module.add_command(commands)
4426
+
4427
+for file in os.listdir(sys.path[0]+"/command"):
4428
+    if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
4429
+        plugin("command."+file[:-3])
4430
+
4431
+suggestions = {
4432
+'apply-delta' : "Try \"apply-changes\".",
4433
+'delta' : "To compare two revisions, use \"changes\".",
4434
+'diff-rev' : "To compare two revisions, use \"changes\".",
4435
+'undo' : "To undo local changes, use \"revert\".",
4436
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
4437
+'missing-from' : "Try \"revisions --missing-from\".",
4438
+'missing' : "Try \"revisions --missing\".",
4439
+'missing-merge' : "Try \"revisions --partner-missing\".",
4440
+'new-merges' : "Try \"revisions --new-merges\".",
4441
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
4442
+'logs' : "Try \"revisions --logs\"",
4443
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
4444
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
4445
+'change-version' : "Try \"update REVISION\"",
4446
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
4447
+'rev-depends' : "Use revisions --dependencies",
4448
+'auto-get' : "Plain get will do archive lookups",
4449
+'tagline' : "Use add-id.  It uses taglines in tagline trees",
4450
+'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
4451
+'library-revisions' : "Use revisions --library",
4452
+'file-revert' : "Use revert FILE",
4453
+'join-branch' : "Use replay --logs-only"
4454
+}
4455
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
4456
4457
*** added file 'testdata/orig'
4458
--- /dev/null 
4459
+++ testdata/orig 
4460
@@ -0,0 +1,2789 @@
4461
+# Copyright (C) 2004 Aaron Bentley
4462
+# <aaron.bentley@utoronto.ca>
4463
+#
4464
+#    This program is free software; you can redistribute it and/or modify
4465
+#    it under the terms of the GNU General Public License as published by
4466
+#    the Free Software Foundation; either version 2 of the License, or
4467
+#    (at your option) any later version.
4468
+#
4469
+#    This program is distributed in the hope that it will be useful,
4470
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
4471
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4472
+#    GNU General Public License for more details.
4473
+#
4474
+#    You should have received a copy of the GNU General Public License
4475
+#    along with this program; if not, write to the Free Software
4476
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
4477
+
4478
+import sys
4479
+import arch
4480
+import arch.util
4481
+import arch.arch
4482
+import abacmds
4483
+import cmdutil
4484
+import shutil
4485
+import os
4486
+import options
4487
+import paths 
4488
+import time
4489
+import cmd
4490
+import readline
4491
+import re
4492
+import string
4493
+import arch_core
4494
+from errors import *
4495
+import errors
4496
+import terminal
4497
+import ancillary
4498
+import misc
4499
+import email
4500
+import smtplib
4501
+
4502
+__docformat__ = "restructuredtext"
4503
+__doc__ = "Implementation of user (sub) commands"
4504
+commands = {}
4505
+
4506
+def find_command(cmd):
4507
+    """
4508
+    Return an instance of a command type.  Return None if the type isn't
4509
+    registered.
4510
+
4511
+    :param cmd: the name of the command to look for
4512
+    :type cmd: the type of the command
4513
+    """
4514
+    if commands.has_key(cmd):
4515
+        return commands[cmd]()
4516
+    else:
4517
+        return None
4518
+
4519
+class BaseCommand:
4520
+    def __call__(self, cmdline):
4521
+        try:
4522
+            self.do_command(cmdline.split())
4523
+        except cmdutil.GetHelp, e:
4524
+            self.help()
4525
+        except Exception, e:
4526
+            print e
4527
+
4528
+    def get_completer(index):
4529
+        return None
4530
+
4531
+    def complete(self, args, text):
4532
+        """
4533
+        Returns a list of possible completions for the given text.
4534
+
4535
+        :param args: The complete list of arguments
4536
+        :type args: List of str
4537
+        :param text: text to complete (may be shorter than args[-1])
4538
+        :type text: str
4539
+        :rtype: list of str
4540
+        """
4541
+        matches = []
4542
+        candidates = None
4543
+
4544
+        if len(args) > 0: 
4545
+            realtext = args[-1]
4546
+        else:
4547
+            realtext = ""
4548
+
4549
+        try:
4550
+            parser=self.get_parser()
4551
+            if realtext.startswith('-'):
4552
+                candidates = parser.iter_options()
4553
+            else:
4554
+                (options, parsed_args) = parser.parse_args(args)
4555
+
4556
+                if len (parsed_args) > 0:
4557
+                    candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
4558
+                else:
4559
+                    candidates = self.get_completer("", 0)
4560
+        except:
4561
+            pass
4562
+        if candidates is None:
4563
+            return
4564
+        for candidate in candidates:
4565
+            candidate = str(candidate)
4566
+            if candidate.startswith(realtext):
4567
+                matches.append(candidate[len(realtext)- len(text):])
4568
+        return matches
4569
+
4570
+
4571
+class Help(BaseCommand):
4572
+    """
4573
+    Lists commands, prints help messages.
4574
+    """
4575
+    def __init__(self):
4576
+        self.description="Prints help mesages"
4577
+        self.parser = None
4578
+
4579
+    def do_command(self, cmdargs):
4580
+        """
4581
+        Prints a help message.
4582
+        """
4583
+        options, args = self.get_parser().parse_args(cmdargs)
4584
+        if len(args) > 1:
4585
+            raise cmdutil.GetHelp
4586
+
4587
+        if options.native or options.suggestions or options.external:
4588
+            native = options.native
4589
+            suggestions = options.suggestions
4590
+            external = options.external
4591
+        else:
4592
+            native = True
4593
+            suggestions = False
4594
+            external = True
4595
+        
4596
+        if len(args) == 0:
4597
+            self.list_commands(native, suggestions, external)
4598
+            return
4599
+        elif len(args) == 1:
4600
+            command_help(args[0])
4601
+            return
4602
+
4603
+    def help(self):
4604
+        self.get_parser().print_help()
4605
+        print """
4606
+If no command is specified, commands are listed.  If a command is
4607
+specified, help for that command is listed.
4608
+        """
4609
+
4610
+    def get_parser(self):
4611
+        """
4612
+        Returns the options parser to use for the "revision" command.
4613
+
4614
+        :rtype: cmdutil.CmdOptionParser
4615
+        """
4616
+        if self.parser is not None:
4617
+            return self.parser
4618
+        parser=cmdutil.CmdOptionParser("fai help [command]")
4619
+        parser.add_option("-n", "--native", action="store_true", 
4620
+                         dest="native", help="Show native commands")
4621
+        parser.add_option("-e", "--external", action="store_true", 
4622
+                         dest="external", help="Show external commands")
4623
+        parser.add_option("-s", "--suggest", action="store_true", 
4624
+                         dest="suggestions", help="Show suggestions")
4625
+        self.parser = parser
4626
+        return parser 
4627
+      
4628
+    def list_commands(self, native=True, suggest=False, external=True):
4629
+        """
4630
+        Lists supported commands.
4631
+
4632
+        :param native: list native, python-based commands
4633
+        :type native: bool
4634
+        :param external: list external aba-style commands
4635
+        :type external: bool
4636
+        """
4637
+        if native:
4638
+            print "Native Fai commands"
4639
+            keys=commands.keys()
4640
+            keys.sort()
4641
+            for k in keys:
4642
+                space=""
4643
+                for i in range(28-len(k)):
4644
+                    space+=" "
4645
+                print space+k+" : "+commands[k]().description
4646
+            print
4647
+        if suggest:
4648
+            print "Unavailable commands and suggested alternatives"
4649
+            key_list = suggestions.keys()
4650
+            key_list.sort()
4651
+            for key in key_list:
4652
+                print "%28s : %s" % (key, suggestions[key])
4653
+            print
4654
+        if external:
4655
+            fake_aba = abacmds.AbaCmds()
4656
+            if (fake_aba.abadir == ""):
4657
+                return
4658
+            print "External commands"
4659
+            fake_aba.list_commands()
4660
+            print
4661
+        if not suggest:
4662
+            print "Use help --suggest to list alternatives to tla and aba"\
4663
+                " commands."
4664
+        if options.tla_fallthrough and (native or external):
4665
+            print "Fai also supports tla commands."
4666
+
4667
+def command_help(cmd):
4668
+    """
4669
+    Prints help for a command.
4670
+
4671
+    :param cmd: The name of the command to print help for
4672
+    :type cmd: str
4673
+    """
4674
+    fake_aba = abacmds.AbaCmds()
4675
+    cmdobj = find_command(cmd)
4676
+    if cmdobj != None:
4677
+        cmdobj.help()
4678
+    elif suggestions.has_key(cmd):
4679
+        print "Not available\n" + suggestions[cmd]
4680
+    else:
4681
+        abacmd = fake_aba.is_command(cmd)
4682
+        if abacmd:
4683
+            abacmd.help()
4684
+        else:
4685
+            print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
4686
+
4687
+
4688
+
4689
+class Changes(BaseCommand):
4690
+    """
4691
+    the "changes" command: lists differences between trees/revisions:
4692
+    """
4693
+    
4694
+    def __init__(self):
4695
+        self.description="Lists what files have changed in the project tree"
4696
+
4697
+    def get_completer(self, arg, index):
4698
+        if index > 1:
4699
+            return None
4700
+        try:
4701
+            tree = arch.tree_root()
4702
+        except:
4703
+            tree = None
4704
+        return cmdutil.iter_revision_completions(arg, tree)
4705
+    
4706
+    def parse_commandline(self, cmdline):
4707
+        """
4708
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
4709
+        
4710
+        :param cmdline: A list of arguments to parse
4711
+        :rtype: (options, Revision, Revision/WorkingTree)
4712
+        """
4713
+        parser=self.get_parser()
4714
+        (options, args) = parser.parse_args(cmdline)
4715
+        if len(args) > 2:
4716
+            raise cmdutil.GetHelp
4717
+
4718
+        tree=arch.tree_root()
4719
+        if len(args) == 0:
4720
+            a_spec = cmdutil.comp_revision(tree)
4721
+        else:
4722
+            a_spec = cmdutil.determine_revision_tree(tree, args[0])
4723
+        cmdutil.ensure_archive_registered(a_spec.archive)
4724
+        if len(args) == 2:
4725
+            b_spec = cmdutil.determine_revision_tree(tree, args[1])
4726
+            cmdutil.ensure_archive_registered(b_spec.archive)
4727
+        else:
4728
+            b_spec=tree
4729
+        return options, a_spec, b_spec
4730
+
4731
+    def do_command(self, cmdargs):
4732
+        """
4733
+        Master function that perfoms the "changes" command.
4734
+        """
4735
+        try:
4736
+            options, a_spec, b_spec = self.parse_commandline(cmdargs);
4737
+        except cmdutil.CantDetermineRevision, e:
4738
+            print e
4739
+            return
4740
+        except arch.errors.TreeRootError, e:
4741
+            print e
4742
+            return
4743
+        if options.changeset:
4744
+            changeset=options.changeset
4745
+            tmpdir = None
4746
+        else:
4747
+            tmpdir=cmdutil.tmpdir()
4748
+            changeset=tmpdir+"/changeset"
4749
+        try:
4750
+            delta=arch.iter_delta(a_spec, b_spec, changeset)
4751
+            try:
4752
+                for line in delta:
4753
+                    if cmdutil.chattermatch(line, "changeset:"):
4754
+                        pass
4755
+                    else:
4756
+                        cmdutil.colorize(line, options.suppress_chatter)
4757
+            except arch.util.ExecProblem, e:
4758
+                if e.proc.error and e.proc.error.startswith(
4759
+                    "missing explicit id for file"):
4760
+                    raise MissingID(e)
4761
+                else:
4762
+                    raise
4763
+            status=delta.status
4764
+            if status > 1:
4765
+                return
4766
+            if (options.perform_diff):
4767
+                chan = cmdutil.ChangesetMunger(changeset)
4768
+                chan.read_indices()
4769
+                if isinstance(b_spec, arch.Revision):
4770
+                    b_dir = b_spec.library_find()
4771
+                else:
4772
+                    b_dir = b_spec
4773
+                a_dir = a_spec.library_find()
4774
+                if options.diffopts is not None:
4775
+                    diffopts = options.diffopts.split()
4776
+                    cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
4777
+                else:
4778
+                    cmdutil.show_diffs(delta.changeset)
4779
+        finally:
4780
+            if tmpdir and (os.access(tmpdir, os.X_OK)):
4781
+                shutil.rmtree(tmpdir)
4782
+
4783
+    def get_parser(self):
4784
+        """
4785
+        Returns the options parser to use for the "changes" command.
4786
+
4787
+        :rtype: cmdutil.CmdOptionParser
4788
+        """
4789
+        parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
4790
+                                       " [revision]")
4791
+        parser.add_option("-d", "--diff", action="store_true", 
4792
+                          dest="perform_diff", default=False, 
4793
+                          help="Show diffs in summary")
4794
+        parser.add_option("-c", "--changeset", dest="changeset", 
4795
+                          help="Store a changeset in the given directory", 
4796
+                          metavar="DIRECTORY")
4797
+        parser.add_option("-s", "--silent", action="store_true", 
4798
+                          dest="suppress_chatter", default=False, 
4799
+                          help="Suppress chatter messages")
4800
+        parser.add_option("--diffopts", dest="diffopts", 
4801
+                          help="Use the specified diff options", 
4802
+                          metavar="OPTIONS")
4803
+
4804
+        return parser
4805
+
4806
+    def help(self, parser=None):
4807
+        """
4808
+        Prints a help message.
4809
+
4810
+        :param parser: If supplied, the parser to use for generating help.  If \
4811
+        not supplied, it is retrieved.
4812
+        :type parser: cmdutil.CmdOptionParser
4813
+        """
4814
+        if parser is None:
4815
+            parser=self.get_parser()
4816
+        parser.print_help()
4817
+        print """
4818
+Performs source-tree comparisons
4819
+
4820
+If no revision is specified, the current project tree is compared to the
4821
+last-committed revision.  If one revision is specified, the current project
4822
+tree is compared to that revision.  If two revisions are specified, they are
4823
+compared to each other.
4824
+        """
4825
+        help_tree_spec() 
4826
+        return
4827
+
4828
+
4829
+class ApplyChanges(BaseCommand):
4830
+    """
4831
+    Apply differences between two revisions to a tree
4832
+    """
4833
+    
4834
+    def __init__(self):
4835
+        self.description="Applies changes to a project tree"
4836
+    
4837
+    def get_completer(self, arg, index):
4838
+        if index > 1:
4839
+            return None
4840
+        try:
4841
+            tree = arch.tree_root()
4842
+        except:
4843
+            tree = None
4844
+        return cmdutil.iter_revision_completions(arg, tree)
4845
+
4846
+    def parse_commandline(self, cmdline, tree):
4847
+        """
4848
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
4849
+        
4850
+        :param cmdline: A list of arguments to parse
4851
+        :rtype: (options, Revision, Revision/WorkingTree)
4852
+        """
4853
+        parser=self.get_parser()
4854
+        (options, args) = parser.parse_args(cmdline)
4855
+        if len(args) != 2:
4856
+            raise cmdutil.GetHelp
4857
+
4858
+        a_spec = cmdutil.determine_revision_tree(tree, args[0])
4859
+        cmdutil.ensure_archive_registered(a_spec.archive)
4860
+        b_spec = cmdutil.determine_revision_tree(tree, args[1])
4861
+        cmdutil.ensure_archive_registered(b_spec.archive)
4862
+        return options, a_spec, b_spec
4863
+
4864
+    def do_command(self, cmdargs):
4865
+        """
4866
+        Master function that performs "apply-changes".
4867
+        """
4868
+        try:
4869
+            tree = arch.tree_root()
4870
+            options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
4871
+        except cmdutil.CantDetermineRevision, e:
4872
+            print e
4873
+            return
4874
+        except arch.errors.TreeRootError, e:
4875
+            print e
4876
+            return
4877
+        delta=cmdutil.apply_delta(a_spec, b_spec, tree)
4878
+        for line in cmdutil.iter_apply_delta_filter(delta):
4879
+            cmdutil.colorize(line, options.suppress_chatter)
4880
+
4881
+    def get_parser(self):
4882
+        """
4883
+        Returns the options parser to use for the "apply-changes" command.
4884
+
4885
+        :rtype: cmdutil.CmdOptionParser
4886
+        """
4887
+        parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
4888
+                                       " revision")
4889
+        parser.add_option("-d", "--diff", action="store_true", 
4890
+                          dest="perform_diff", default=False, 
4891
+                          help="Show diffs in summary")
4892
+        parser.add_option("-c", "--changeset", dest="changeset", 
4893
+                          help="Store a changeset in the given directory", 
4894
+                          metavar="DIRECTORY")
4895
+        parser.add_option("-s", "--silent", action="store_true", 
4896
+                          dest="suppress_chatter", default=False, 
4897
+                          help="Suppress chatter messages")
4898
+        return parser
4899
+
4900
+    def help(self, parser=None):
4901
+        """
4902
+        Prints a help message.
4903
+
4904
+        :param parser: If supplied, the parser to use for generating help.  If \
4905
+        not supplied, it is retrieved.
4906
+        :type parser: cmdutil.CmdOptionParser
4907
+        """
4908
+        if parser is None:
4909
+            parser=self.get_parser()
4910
+        parser.print_help()
4911
+        print """
4912
+Applies changes to a project tree
4913
+
4914
+Compares two revisions and applies the difference between them to the current
4915
+tree.
4916
+        """
4917
+        help_tree_spec() 
4918
+        return
4919
+
4920
+class Update(BaseCommand):
4921
+    """
4922
+    Updates a project tree to a given revision, preserving un-committed hanges. 
4923
+    """
4924
+    
4925
+    def __init__(self):
4926
+        self.description="Apply the latest changes to the current directory"
4927
+
4928
+    def get_completer(self, arg, index):
4929
+        if index > 0:
4930
+            return None
4931
+        try:
4932
+            tree = arch.tree_root()
4933
+        except:
4934
+            tree = None
4935
+        return cmdutil.iter_revision_completions(arg, tree)
4936
+    
4937
+    def parse_commandline(self, cmdline, tree):
4938
+        """
4939
+        Parse commandline arguments.  Raises cmdutil.GetHelp if help is needed.
4940
+        
4941
+        :param cmdline: A list of arguments to parse
4942
+        :rtype: (options, Revision, Revision/WorkingTree)
4943
+        """
4944
+        parser=self.get_parser()
4945
+        (options, args) = parser.parse_args(cmdline)
4946
+        if len(args) > 2:
4947
+            raise cmdutil.GetHelp
4948
+
4949
+        spec=None
4950
+        if len(args)>0:
4951
+            spec=args[0]
4952
+        revision=cmdutil.determine_revision_arch(tree, spec)
4953
+        cmdutil.ensure_archive_registered(revision.archive)
4954
+
4955
+        mirror_source = cmdutil.get_mirror_source(revision.archive)
4956
+        if mirror_source != None:
4957
+            if cmdutil.prompt("Mirror update"):
4958
+                cmd=cmdutil.mirror_archive(mirror_source, 
4959
+                    revision.archive, arch.NameParser(revision).get_package_version())
4960
+                for line in arch.chatter_classifier(cmd):
4961
+                    cmdutil.colorize(line, options.suppress_chatter)
4962
+
4963
+                revision=cmdutil.determine_revision_arch(tree, spec)
4964
+
4965
+        return options, revision 
4966
+
4967
+    def do_command(self, cmdargs):
4968
+        """
4969
+        Master function that perfoms the "update" command.
4970
+        """
4971
+        tree=arch.tree_root()
4972
+        try:
4973
+            options, to_revision = self.parse_commandline(cmdargs, tree);
4974
+        except cmdutil.CantDetermineRevision, e:
4975
+            print e
4976
+            return
4977
+        except arch.errors.TreeRootError, e:
4978
+            print e
4979
+            return
4980
+        from_revision=cmdutil.tree_latest(tree)
4981
+        if from_revision==to_revision:
4982
+            print "Tree is already up to date with:\n"+str(to_revision)+"."
4983
+            return
4984
+        cmdutil.ensure_archive_registered(from_revision.archive)
4985
+        cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
4986
+            options.patch_forward)
4987
+        for line in cmdutil.iter_apply_delta_filter(cmd):
4988
+            cmdutil.colorize(line)
4989
+        if to_revision.version != tree.tree_version:
4990
+            if cmdutil.prompt("Update version"):
4991
+                tree.tree_version = to_revision.version
4992
+
4993
+    def get_parser(self):
4994
+        """
4995
+        Returns the options parser to use for the "update" command.
4996
+
4997
+        :rtype: cmdutil.CmdOptionParser
4998
+        """
4999
+        parser=cmdutil.CmdOptionParser("fai update [options]"
5000
+                                       " [revision/version]")
5001
+        parser.add_option("-f", "--forward", action="store_true", 
5002
+                          dest="patch_forward", default=False, 
5003
+                          help="pass the --forward option to 'patch'")
5004
+        parser.add_option("-s", "--silent", action="store_true", 
5005
+                          dest="suppress_chatter", default=False, 
5006
+                          help="Suppress chatter messages")
5007
+        return parser
5008
+
5009
+    def help(self, parser=None):
5010
+        """
5011
+        Prints a help message.
5012
+
5013
+        :param parser: If supplied, the parser to use for generating help.  If \
5014
+        not supplied, it is retrieved.
5015
+        :type parser: cmdutil.CmdOptionParser
5016
+        """
5017
+        if parser is None:
5018
+            parser=self.get_parser()
5019
+        parser.print_help()
5020
+        print """
5021
+Updates a working tree to the current archive revision
5022
+
5023
+If a revision or version is specified, that is used instead 
5024
+        """
5025
+        help_tree_spec() 
5026
+        return
5027
+
5028
+
5029
+class Commit(BaseCommand):
5030
+    """
5031
+    Create a revision based on the changes in the current tree.
5032
+    """
5033
+    
5034
+    def __init__(self):
5035
+        self.description="Write local changes to the archive"
5036
+
5037
+    def get_completer(self, arg, index):
5038
+        if arg is None:
5039
+            arg = ""
5040
+        return iter_modified_file_completions(arch.tree_root(), arg)
5041
+#        return iter_source_file_completions(arch.tree_root(), arg)
5042
+    
5043
+    def parse_commandline(self, cmdline, tree):
5044
+        """
5045
+        Parse commandline arguments.  Raise cmtutil.GetHelp if help is needed.
5046
+        
5047
+        :param cmdline: A list of arguments to parse
5048
+        :rtype: (options, Revision, Revision/WorkingTree)
5049
+        """
5050
+        parser=self.get_parser()
5051
+        (options, args) = parser.parse_args(cmdline)
5052
+
5053
+        if len(args) == 0:
5054
+            args = None
5055
+        revision=cmdutil.determine_revision_arch(tree, options.version)
5056
+        return options, revision.get_version(), args
5057
+
5058
+    def do_command(self, cmdargs):
5059
+        """
5060
+        Master function that perfoms the "commit" command.
5061
+        """
5062
+        tree=arch.tree_root()
5063
+        options, version, files = self.parse_commandline(cmdargs, tree)
5064
+        if options.__dict__.has_key("base") and options.base:
5065
+            base = cmdutil.determine_revision_tree(tree, options.base)
5066
+        else:
5067
+            base = cmdutil.submit_revision(tree)
5068
+        
5069
+        writeversion=version
5070
+        archive=version.archive
5071
+        source=cmdutil.get_mirror_source(archive)
5072
+        allow_old=False
5073
+        writethrough="implicit"
5074
+
5075
+        if source!=None:
5076
+            if writethrough=="explicit" and \
5077
+                cmdutil.prompt("Writethrough"):
5078
+                writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
5079
+            elif writethrough=="none":
5080
+                raise CommitToMirror(archive)
5081
+
5082
+        elif archive.is_mirror:
5083
+            raise CommitToMirror(archive)
5084
+
5085
+        try:
5086
+            last_revision=tree.iter_logs(version, True).next().revision
5087
+        except StopIteration, e:
5088
+            if cmdutil.prompt("Import from commit"):
5089
+                return do_import(version)
5090
+            else:
5091
+                raise NoVersionLogs(version)
5092
+        if last_revision!=version.iter_revisions(True).next():
5093
+            if not cmdutil.prompt("Out of date"):
5094
+                raise OutOfDate
5095
+            else:
5096
+                allow_old=True
5097
+
5098
+        try:
5099
+            if not cmdutil.has_changed(version):
5100
+                if not cmdutil.prompt("Empty commit"):
5101
+                    raise EmptyCommit
5102
+        except arch.util.ExecProblem, e:
5103
+            if e.proc.error and e.proc.error.startswith(
5104
+                "missing explicit id for file"):
5105
+                raise MissingID(e)
5106
+            else:
5107
+                raise
5108
+        log = tree.log_message(create=False)
5109
+        if log is None:
5110
+            try:
5111
+                if cmdutil.prompt("Create log"):
5112
+                    edit_log(tree)
5113
+
5114
+            except cmdutil.NoEditorSpecified, e:
5115
+                raise CommandFailed(e)
5116
+            log = tree.log_message(create=False)
5117
+        if log is None: 
5118
+            raise NoLogMessage
5119
+        if log["Summary"] is None or len(log["Summary"].strip()) == 0:
5120
+            if not cmdutil.prompt("Omit log summary"):
5121
+                raise errors.NoLogSummary
5122
+        try:
5123
+            for line in tree.iter_commit(version, seal=options.seal_version,
5124
+                base=base, out_of_date_ok=allow_old, file_list=files):
5125
+                cmdutil.colorize(line, options.suppress_chatter)
5126
+
5127
+        except arch.util.ExecProblem, e:
5128
+            if e.proc.error and e.proc.error.startswith(
5129
+                "These files violate naming conventions:"):
5130
+                raise LintFailure(e.proc.error)
5131
+            else:
5132
+                raise
5133
+
5134
+    def get_parser(self):
5135
+        """
5136
+        Returns the options parser to use for the "commit" command.
5137
+
5138
+        :rtype: cmdutil.CmdOptionParser
5139
+        """
5140
+
5141
+        parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
5142
+                                       " [file2...]")
5143
+        parser.add_option("--seal", action="store_true", 
5144
+                          dest="seal_version", default=False, 
5145
+                          help="seal this version")
5146
+        parser.add_option("-v", "--version", dest="version", 
5147
+                          help="Use the specified version", 
5148
+                          metavar="VERSION")
5149
+        parser.add_option("-s", "--silent", action="store_true", 
5150
+                          dest="suppress_chatter", default=False, 
5151
+                          help="Suppress chatter messages")
5152
+        if cmdutil.supports_switch("commit", "--base"):
5153
+            parser.add_option("--base", dest="base", help="", 
5154
+                              metavar="REVISION")
5155
+        return parser
5156
+
5157
+    def help(self, parser=None):
5158
+        """
5159
+        Prints a help message.
5160
+
5161
+        :param parser: If supplied, the parser to use for generating help.  If \
5162
+        not supplied, it is retrieved.
5163
+        :type parser: cmdutil.CmdOptionParser
5164
+        """
5165
+        if parser is None:
5166
+            parser=self.get_parser()
5167
+        parser.print_help()
5168
+        print """
5169
+Updates a working tree to the current archive revision
5170
+
5171
+If a version is specified, that is used instead 
5172
+        """
5173
+#        help_tree_spec() 
5174
+        return
5175
+
5176
+
5177
+
5178
+class CatLog(BaseCommand):
5179
+    """
5180
+    Print the log of a given file (from current tree)
5181
+    """
5182
+    def __init__(self):
5183
+        self.description="Prints the patch log for a revision"
5184
+
5185
+    def get_completer(self, arg, index):
5186
+        if index > 0:
5187
+            return None
5188
+        try:
5189
+            tree = arch.tree_root()
5190
+        except:
5191
+            tree = None
5192
+        return cmdutil.iter_revision_completions(arg, tree)
5193
+
5194
+    def do_command(self, cmdargs):
5195
+        """
5196
+        Master function that perfoms the "cat-log" command.
5197
+        """
5198
+        parser=self.get_parser()
5199
+        (options, args) = parser.parse_args(cmdargs)
5200
+        try:
5201
+            tree = arch.tree_root()
5202
+        except arch.errors.TreeRootError, e:
5203
+            tree = None
5204
+        spec=None
5205
+        if len(args) > 0:
5206
+            spec=args[0]
5207
+        if len(args) > 1:
5208
+            raise cmdutil.GetHelp()
5209
+        try:
5210
+            if tree:
5211
+                revision = cmdutil.determine_revision_tree(tree, spec)
5212
+            else:
5213
+                revision = cmdutil.determine_revision_arch(tree, spec)
5214
+        except cmdutil.CantDetermineRevision, e:
5215
+            raise CommandFailedWrapper(e)
5216
+        log = None
5217
+        
5218
+        use_tree = (options.source == "tree" or \
5219
+            (options.source == "any" and tree))
5220
+        use_arch = (options.source == "archive" or options.source == "any")
5221
+        
5222
+        log = None
5223
+        if use_tree:
5224
+            for log in tree.iter_logs(revision.get_version()):
5225
+                if log.revision == revision:
5226
+                    break
5227
+                else:
5228
+                    log = None
5229
+        if log is None and use_arch:
5230
+            cmdutil.ensure_revision_exists(revision)
5231
+            log = arch.Patchlog(revision)
5232
+        if log is not None:
5233
+            for item in log.items():
5234
+                print "%s: %s" % item
5235
+            print log.description
5236
+
5237
+    def get_parser(self):
5238
+        """
5239
+        Returns the options parser to use for the "cat-log" command.
5240
+
5241
+        :rtype: cmdutil.CmdOptionParser
5242
+        """
5243
+        parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
5244
+        parser.add_option("--archive", action="store_const", dest="source",
5245
+                          const="archive", default="any",
5246
+                          help="Always get the log from the archive")
5247
+        parser.add_option("--tree", action="store_const", dest="source",
5248
+                          const="tree", help="Always get the log from the tree")
5249
+        return parser 
5250
+
5251
+    def help(self, parser=None):
5252
+        """
5253
+        Prints a help message.
5254
+
5255
+        :param parser: If supplied, the parser to use for generating help.  If \
5256
+        not supplied, it is retrieved.
5257
+        :type parser: cmdutil.CmdOptionParser
5258
+        """
5259
+        if parser==None:
5260
+            parser=self.get_parser()
5261
+        parser.print_help()
5262
+        print """
5263
+Prints the log for the specified revision
5264
+        """
5265
+        help_tree_spec()
5266
+        return
5267
+
5268
+class Revert(BaseCommand):
5269
+    """ Reverts a tree (or aspects of it) to a revision
5270
+    """
5271
+    def __init__(self):
5272
+        self.description="Reverts a tree (or aspects of it) to a revision "
5273
+
5274
+    def get_completer(self, arg, index):
5275
+        if index > 0:
5276
+            return None
5277
+        try:
5278
+            tree = arch.tree_root()
5279
+        except:
5280
+            tree = None
5281
+        return iter_modified_file_completions(tree, arg)
5282
+
5283
+    def do_command(self, cmdargs):
5284
+        """
5285
+        Master function that perfoms the "revert" command.
5286
+        """
5287
+        parser=self.get_parser()
5288
+        (options, args) = parser.parse_args(cmdargs)
5289
+        try:
5290
+            tree = arch.tree_root()
5291
+        except arch.errors.TreeRootError, e:
5292
+            raise CommandFailed(e)
5293
+        spec=None
5294
+        if options.revision is not None:
5295
+            spec=options.revision
5296
+        try:
5297
+            if spec is not None:
5298
+                revision = cmdutil.determine_revision_tree(tree, spec)
5299
+            else:
5300
+                revision = cmdutil.comp_revision(tree)
5301
+        except cmdutil.CantDetermineRevision, e:
5302
+            raise CommandFailedWrapper(e)
5303
+        munger = None
5304
+
5305
+        if options.file_contents or options.file_perms or options.deletions\
5306
+            or options.additions or options.renames or options.hunk_prompt:
5307
+            munger = cmdutil.MungeOpts()
5308
+            munger.hunk_prompt = options.hunk_prompt
5309
+
5310
+        if len(args) > 0 or options.logs or options.pattern_files or \
5311
+            options.control:
5312
+            if munger is None:
5313
+                munger = cmdutil.MungeOpts(True)
5314
+                munger.all_types(True)
5315
+        if len(args) > 0:
5316
+            t_cwd = cmdutil.tree_cwd(tree)
5317
+            for name in args:
5318
+                if len(t_cwd) > 0:
5319
+                    t_cwd += "/"
5320
+                name = "./" + t_cwd + name
5321
+                munger.add_keep_file(name);
5322
+
5323
+        if options.file_perms:
5324
+            munger.file_perms = True
5325
+        if options.file_contents:
5326
+            munger.file_contents = True
5327
+        if options.deletions:
5328
+            munger.deletions = True
5329
+        if options.additions:
5330
+            munger.additions = True
5331
+        if options.renames:
5332
+            munger.renames = True
5333
+        if options.logs:
5334
+            munger.add_keep_pattern('^\./\{arch\}/[^=].*')
5335
+        if options.control:
5336
+            munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
5337
+                                    "/\.arch-inventory$")
5338
+        if options.pattern_files:
5339
+            munger.add_keep_pattern(options.pattern_files)
5340
+                
5341
+        for line in cmdutil.revert(tree, revision, munger, 
5342
+                                   not options.no_output):
5343
+            cmdutil.colorize(line)
5344
+
5345
+
5346
+    def get_parser(self):
5347
+        """
5348
+        Returns the options parser to use for the "cat-log" command.
5349
+
5350
+        :rtype: cmdutil.CmdOptionParser
5351
+        """
5352
+        parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
5353
+        parser.add_option("", "--contents", action="store_true", 
5354
+                          dest="file_contents", 
5355
+                          help="Revert file content changes")
5356
+        parser.add_option("", "--permissions", action="store_true", 
5357
+                          dest="file_perms", 
5358
+                          help="Revert file permissions changes")
5359
+        parser.add_option("", "--deletions", action="store_true", 
5360
+                          dest="deletions", 
5361
+                          help="Restore deleted files")
5362
+        parser.add_option("", "--additions", action="store_true", 
5363
+                          dest="additions", 
5364
+                          help="Remove added files")
5365
+        parser.add_option("", "--renames", action="store_true", 
5366
+                          dest="renames", 
5367
+                          help="Revert file names")
5368
+        parser.add_option("--hunks", action="store_true", 
5369
+                          dest="hunk_prompt", default=False,
5370
+                          help="Prompt which hunks to revert")
5371
+        parser.add_option("--pattern-files", dest="pattern_files", 
5372
+                          help="Revert files that match this pattern", 
5373
+                          metavar="REGEX")
5374
+        parser.add_option("--logs", action="store_true", 
5375
+                          dest="logs", default=False,
5376
+                          help="Revert only logs")
5377
+        parser.add_option("--control-files", action="store_true", 
5378
+                          dest="control", default=False,
5379
+                          help="Revert logs and other control files")
5380
+        parser.add_option("-n", "--no-output", action="store_true", 
5381
+                          dest="no_output", 
5382
+                          help="Don't keep an undo changeset")
5383
+        parser.add_option("--revision", dest="revision", 
5384
+                          help="Revert to the specified revision", 
5385
+                          metavar="REVISION")
5386
+        return parser 
5387
+
5388
+    def help(self, parser=None):
5389
+        """
5390
+        Prints a help message.
5391
+
5392
+        :param parser: If supplied, the parser to use for generating help.  If \
5393
+        not supplied, it is retrieved.
5394
+        :type parser: cmdutil.CmdOptionParser
5395
+        """
5396
+        if parser==None:
5397
+            parser=self.get_parser()
5398
+        parser.print_help()
5399
+        print """
5400
+Reverts changes in the current working tree.  If no flags are specified, all
5401
+types of changes are reverted.  Otherwise, only selected types of changes are
5402
+reverted.  
5403
+
5404
+If a revision is specified on the commandline, differences between the current
5405
+tree and that revision are reverted.  If a version is specified, the current
5406
+tree is used to determine the revision.
5407
+
5408
+If files are specified, only those files listed will have any changes applied.
5409
+To specify a renamed file, you can use either the old or new name. (or both!)
5410
+
5411
+Unless "-n" is specified, reversions can be undone with "redo".
5412
+        """
5413
+        return
5414
+
5415
+class Revision(BaseCommand):
5416
+    """
5417
+    Print a revision name based on a revision specifier
5418
+    """
5419
+    def __init__(self):
5420
+        self.description="Prints the name of a revision"
5421
+
5422
+    def get_completer(self, arg, index):
5423
+        if index > 0:
5424
+            return None
5425
+        try:
5426
+            tree = arch.tree_root()
5427
+        except:
5428
+            tree = None
5429
+        return cmdutil.iter_revision_completions(arg, tree)
5430
+
5431
+    def do_command(self, cmdargs):
5432
+        """
5433
+        Master function that perfoms the "revision" command.
5434
+        """
5435
+        parser=self.get_parser()
5436
+        (options, args) = parser.parse_args(cmdargs)
5437
+
5438
+        try:
5439
+            tree = arch.tree_root()
5440
+        except arch.errors.TreeRootError:
5441
+            tree = None
5442
+
5443
+        spec=None
5444
+        if len(args) > 0:
5445
+            spec=args[0]
5446
+        if len(args) > 1:
5447
+            raise cmdutil.GetHelp
5448
+        try:
5449
+            if tree:
5450
+                revision = cmdutil.determine_revision_tree(tree, spec)
5451
+            else:
5452
+                revision = cmdutil.determine_revision_arch(tree, spec)
5453
+        except cmdutil.CantDetermineRevision, e:
5454
+            print str(e)
5455
+            return
5456
+        print options.display(revision)
5457
+
5458
+    def get_parser(self):
5459
+        """
5460
+        Returns the options parser to use for the "revision" command.
5461
+
5462
+        :rtype: cmdutil.CmdOptionParser
5463
+        """
5464
+        parser=cmdutil.CmdOptionParser("fai revision [revision]")
5465
+        parser.add_option("", "--location", action="store_const", 
5466
+                         const=paths.determine_path, dest="display", 
5467
+                         help="Show location instead of name", default=str)
5468
+        parser.add_option("--import", action="store_const", 
5469
+                         const=paths.determine_import_path, dest="display",  
5470
+                         help="Show location of import file")
5471
+        parser.add_option("--log", action="store_const", 
5472
+                         const=paths.determine_log_path, dest="display", 
5473
+                         help="Show location of log file")
5474
+        parser.add_option("--patch", action="store_const", 
5475
+                         dest="display", const=paths.determine_patch_path,
5476
+                         help="Show location of patchfile")
5477
+        parser.add_option("--continuation", action="store_const", 
5478
+                         const=paths.determine_continuation_path, 
5479
+                         dest="display",
5480
+                         help="Show location of continuation file")
5481
+        parser.add_option("--cacherev", action="store_const", 
5482
+                         const=paths.determine_cacherev_path, dest="display",
5483
+                         help="Show location of cacherev file")
5484
+        return parser 
5485
+
5486
+    def help(self, parser=None):
5487
+        """
5488
+        Prints a help message.
5489
+
5490
+        :param parser: If supplied, the parser to use for generating help.  If \
5491
+        not supplied, it is retrieved.
5492
+        :type parser: cmdutil.CmdOptionParser
5493
+        """
5494
+        if parser==None:
5495
+            parser=self.get_parser()
5496
+        parser.print_help()
5497
+        print """
5498
+Expands aliases and prints the name of the specified revision.  Instead of
5499
+the name, several options can be used to print locations.  If more than one is
5500
+specified, the last one is used.
5501
+        """
5502
+        help_tree_spec()
5503
+        return
5504
+
5505
+def require_version_exists(version, spec):
5506
+    if not version.exists():
5507
+        raise cmdutil.CantDetermineVersion(spec, 
5508
+                                           "The version %s does not exist." \
5509
+                                           % version)
5510
+
5511
+class Revisions(BaseCommand):
5512
+    """
5513
+    Print a revision name based on a revision specifier
5514
+    """
5515
+    def __init__(self):
5516
+        self.description="Lists revisions"
5517
+    
5518
+    def do_command(self, cmdargs):
5519
+        """
5520
+        Master function that perfoms the "revision" command.
5521
+        """
5522
+        (options, args) = self.get_parser().parse_args(cmdargs)
5523
+        if len(args) > 1:
5524
+            raise cmdutil.GetHelp
5525
+        try:
5526
+            self.tree = arch.tree_root()
5527
+        except arch.errors.TreeRootError:
5528
+            self.tree = None
5529
+        try:
5530
+            iter = self.get_iterator(options.type, args, options.reverse, 
5531
+                                     options.modified)
5532
+        except cmdutil.CantDetermineRevision, e:
5533
+            raise CommandFailedWrapper(e)
5534
+
5535
+        if options.skip is not None:
5536
+            iter = cmdutil.iter_skip(iter, int(options.skip))
5537
+
5538
+        for revision in iter:
5539
+            log = None
5540
+            if isinstance(revision, arch.Patchlog):
5541
+                log = revision
5542
+                revision=revision.revision
5543
+            print options.display(revision)
5544
+            if log is None and (options.summary or options.creator or 
5545
+                                options.date or options.merges):
5546
+                log = revision.patchlog
5547
+            if options.creator:
5548
+                print "    %s" % log.creator
5549
+            if options.date:
5550
+                print "    %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
5551
+            if options.summary:
5552
+                print "    %s" % log.summary
5553
+            if options.merges:
5554
+                showed_title = False
5555
+                for revision in log.merged_patches:
5556
+                    if not showed_title:
5557
+                        print "    Merged:"
5558
+                        showed_title = True
5559
+                    print "    %s" % revision
5560
+
5561
+    def get_iterator(self, type, args, reverse, modified):
5562
+        if len(args) > 0:
5563
+            spec = args[0]
5564
+        else:
5565
+            spec = None
5566
+        if modified is not None:
5567
+            iter = cmdutil.modified_iter(modified, self.tree)
5568
+            if reverse:
5569
+                return iter
5570
+            else:
5571
+                return cmdutil.iter_reverse(iter)
5572
+        elif type == "archive":
5573
+            if spec is None:
5574
+                if self.tree is None:
5575
+                    raise cmdutil.CantDetermineRevision("", 
5576
+                                                        "Not in a project tree")
5577
+                version = cmdutil.determine_version_tree(spec, self.tree)
5578
+            else:
5579
+                version = cmdutil.determine_version_arch(spec, self.tree)
5580
+                cmdutil.ensure_archive_registered(version.archive)
5581
+                require_version_exists(version, spec)
5582
+            return version.iter_revisions(reverse)
5583
+        elif type == "cacherevs":
5584
+            if spec is None:
5585
+                if self.tree is None:
5586
+                    raise cmdutil.CantDetermineRevision("", 
5587
+                                                        "Not in a project tree")
5588
+                version = cmdutil.determine_version_tree(spec, self.tree)
5589
+            else:
5590
+                version = cmdutil.determine_version_arch(spec, self.tree)
5591
+                cmdutil.ensure_archive_registered(version.archive)
5592
+                require_version_exists(version, spec)
5593
+            return cmdutil.iter_cacherevs(version, reverse)
5594
+        elif type == "library":
5595
+            if spec is None:
5596
+                if self.tree is None:
5597
+                    raise cmdutil.CantDetermineRevision("", 
5598
+                                                        "Not in a project tree")
5599
+                version = cmdutil.determine_version_tree(spec, self.tree)
5600
+            else:
5601
+                version = cmdutil.determine_version_arch(spec, self.tree)
5602
+            return version.iter_library_revisions(reverse)
5603
+        elif type == "logs":
5604
+            if self.tree is None:
5605
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5606
+            return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
5607
+                                  self.tree), reverse)
5608
+        elif type == "missing" or type == "skip-present":
5609
+            if self.tree is None:
5610
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5611
+            skip = (type == "skip-present")
5612
+            version = cmdutil.determine_version_tree(spec, self.tree)
5613
+            cmdutil.ensure_archive_registered(version.archive)
5614
+            require_version_exists(version, spec)
5615
+            return cmdutil.iter_missing(self.tree, version, reverse,
5616
+                                        skip_present=skip)
5617
+
5618
+        elif type == "present":
5619
+            if self.tree is None:
5620
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5621
+            version = cmdutil.determine_version_tree(spec, self.tree)
5622
+            cmdutil.ensure_archive_registered(version.archive)
5623
+            require_version_exists(version, spec)
5624
+            return cmdutil.iter_present(self.tree, version, reverse)
5625
+
5626
+        elif type == "new-merges" or type == "direct-merges":
5627
+            if self.tree is None:
5628
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5629
+            version = cmdutil.determine_version_tree(spec, self.tree)
5630
+            cmdutil.ensure_archive_registered(version.archive)
5631
+            require_version_exists(version, spec)
5632
+            iter = cmdutil.iter_new_merges(self.tree, version, reverse)
5633
+            if type == "new-merges":
5634
+                return iter
5635
+            elif type == "direct-merges":
5636
+                return cmdutil.direct_merges(iter)
5637
+
5638
+        elif type == "missing-from":
5639
+            if self.tree is None:
5640
+                raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5641
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
5642
+            libtree = cmdutil.find_or_make_local_revision(revision)
5643
+            return cmdutil.iter_missing(libtree, self.tree.tree_version,
5644
+                                        reverse)
5645
+
5646
+        elif type == "partner-missing":
5647
+            return cmdutil.iter_partner_missing(self.tree, reverse)
5648
+
5649
+        elif type == "ancestry":
5650
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
5651
+            iter = cmdutil._iter_ancestry(self.tree, revision)
5652
+            if reverse:
5653
+                return iter
5654
+            else:
5655
+                return cmdutil.iter_reverse(iter)
5656
+
5657
+        elif type == "dependencies" or type == "non-dependencies":
5658
+            nondeps = (type == "non-dependencies")
5659
+            revision = cmdutil.determine_revision_tree(self.tree, spec)
5660
+            anc_iter = cmdutil._iter_ancestry(self.tree, revision)
5661
+            iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
5662
+            if reverse:
5663
+                return iter_depends
5664
+            else:
5665
+                return cmdutil.iter_reverse(iter_depends)
5666
+        elif type == "micro":
5667
+            return cmdutil.iter_micro(self.tree)
5668
+
5669
+    
5670
+    def get_parser(self):
5671
+        """
5672
+        Returns the options parser to use for the "revision" command.
5673
+
5674
+        :rtype: cmdutil.CmdOptionParser
5675
+        """
5676
+        parser=cmdutil.CmdOptionParser("fai revisions [revision]")
5677
+        select = cmdutil.OptionGroup(parser, "Selection options",
5678
+                          "Control which revisions are listed.  These options"
5679
+                          " are mutually exclusive.  If more than one is"
5680
+                          " specified, the last is used.")
5681
+        select.add_option("", "--archive", action="store_const", 
5682
+                          const="archive", dest="type", default="archive",
5683
+                          help="List all revisions in the archive")
5684
+        select.add_option("", "--cacherevs", action="store_const", 
5685
+                          const="cacherevs", dest="type",
5686
+                          help="List all revisions stored in the archive as "
5687
+                          "complete copies")
5688
+        select.add_option("", "--logs", action="store_const", 
5689
+                          const="logs", dest="type",
5690
+                          help="List revisions that have a patchlog in the "
5691
+                          "tree")
5692
+        select.add_option("", "--missing", action="store_const", 
5693
+                          const="missing", dest="type",
5694
+                          help="List revisions from the specified version that"
5695
+                          " have no patchlog in the tree")
5696
+        select.add_option("", "--skip-present", action="store_const", 
5697
+                          const="skip-present", dest="type",
5698
+                          help="List revisions from the specified version that"
5699
+                          " have no patchlogs at all in the tree")
5700
+        select.add_option("", "--present", action="store_const", 
5701
+                          const="present", dest="type",
5702
+                          help="List revisions from the specified version that"
5703
+                          " have no patchlog in the tree, but can't be merged")
5704
+        select.add_option("", "--missing-from", action="store_const", 
5705
+                          const="missing-from", dest="type",
5706
+                          help="List revisions from the specified revision "
5707
+                          "that have no patchlog for the tree version")
5708
+        select.add_option("", "--partner-missing", action="store_const", 
5709
+                          const="partner-missing", dest="type",
5710
+                          help="List revisions in partner versions that are"
5711
+                          " missing")
5712
+        select.add_option("", "--new-merges", action="store_const", 
5713
+                          const="new-merges", dest="type",
5714
+                          help="List revisions that have had patchlogs added"
5715
+                          " to the tree since the last commit")
5716
+        select.add_option("", "--direct-merges", action="store_const", 
5717
+                          const="direct-merges", dest="type",
5718
+                          help="List revisions that have been directly added"
5719
+                          " to tree since the last commit ")
5720
+        select.add_option("", "--library", action="store_const", 
5721
+                          const="library", dest="type",
5722
+                          help="List revisions in the revision library")
5723
+        select.add_option("", "--ancestry", action="store_const", 
5724
+                          const="ancestry", dest="type",
5725
+                          help="List revisions that are ancestors of the "
5726
+                          "current tree version")
5727
+
5728
+        select.add_option("", "--dependencies", action="store_const", 
5729
+                          const="dependencies", dest="type",
5730
+                          help="List revisions that the given revision "
5731
+                          "depends on")
5732
+
5733
+        select.add_option("", "--non-dependencies", action="store_const", 
5734
+                          const="non-dependencies", dest="type",
5735
+                          help="List revisions that the given revision "
5736
+                          "does not depend on")
5737
+
5738
+        select.add_option("--micro", action="store_const", 
5739
+                          const="micro", dest="type",
5740
+                          help="List partner revisions aimed for this "
5741
+                          "micro-branch")
5742
+
5743
+        select.add_option("", "--modified", dest="modified", 
5744
+                          help="List tree ancestor revisions that modified a "
5745
+                          "given file", metavar="FILE[:LINE]")
5746
+
5747
+        parser.add_option("", "--skip", dest="skip", 
5748
+                          help="Skip revisions.  Positive numbers skip from "
5749
+                          "beginning, negative skip from end.",
5750
+                          metavar="NUMBER")
5751
+
5752
+        parser.add_option_group(select)
5753
+
5754
+        format = cmdutil.OptionGroup(parser, "Revision format options",
5755
+                          "These control the appearance of listed revisions")
5756
+        format.add_option("", "--location", action="store_const", 
5757
+                         const=paths.determine_path, dest="display", 
5758
+                         help="Show location instead of name", default=str)
5759
+        format.add_option("--import", action="store_const", 
5760
+                         const=paths.determine_import_path, dest="display",  
5761
+                         help="Show location of import file")
5762
+        format.add_option("--log", action="store_const", 
5763
+                         const=paths.determine_log_path, dest="display", 
5764
+                         help="Show location of log file")
5765
+        format.add_option("--patch", action="store_const", 
5766
+                         dest="display", const=paths.determine_patch_path,
5767
+                         help="Show location of patchfile")
5768
+        format.add_option("--continuation", action="store_const", 
5769
+                         const=paths.determine_continuation_path, 
5770
+                         dest="display",
5771
+                         help="Show location of continuation file")
5772
+        format.add_option("--cacherev", action="store_const", 
5773
+                         const=paths.determine_cacherev_path, dest="display",
5774
+                         help="Show location of cacherev file")
5775
+        parser.add_option_group(format)
5776
+        display = cmdutil.OptionGroup(parser, "Display format options",
5777
+                          "These control the display of data")
5778
+        display.add_option("-r", "--reverse", action="store_true", 
5779
+                          dest="reverse", help="Sort from newest to oldest")
5780
+        display.add_option("-s", "--summary", action="store_true", 
5781
+                          dest="summary", help="Show patchlog summary")
5782
+        display.add_option("-D", "--date", action="store_true", 
5783
+                          dest="date", help="Show patchlog date")
5784
+        display.add_option("-c", "--creator", action="store_true", 
5785
+                          dest="creator", help="Show the id that committed the"
5786
+                          " revision")
5787
+        display.add_option("-m", "--merges", action="store_true", 
5788
+                          dest="merges", help="Show the revisions that were"
5789
+                          " merged")
5790
+        parser.add_option_group(display)
5791
+        return parser 
5792
+    def help(self, parser=None):
5793
+        """Attempt to explain the revisions command
5794
+        
5795
+        :param parser: If supplied, used to determine options
5796
+        """
5797
+        if parser==None:
5798
+            parser=self.get_parser()
5799
+        parser.print_help()
5800
+        print """List revisions.
5801
+        """
5802
+        help_tree_spec()
5803
+
5804
+
5805
+class Get(BaseCommand):
5806
+    """
5807
+    Retrieve a revision from the archive
5808
+    """
5809
+    def __init__(self):
5810
+        self.description="Retrieve a revision from the archive"
5811
+        self.parser=self.get_parser()
5812
+
5813
+
5814
+    def get_completer(self, arg, index):
5815
+        if index > 0:
5816
+            return None
5817
+        try:
5818
+            tree = arch.tree_root()
5819
+        except:
5820
+            tree = None
5821
+        return cmdutil.iter_revision_completions(arg, tree)
5822
+
5823
+
5824
+    def do_command(self, cmdargs):
5825
+        """
5826
+        Master function that perfoms the "get" command.
5827
+        """
5828
+        (options, args) = self.parser.parse_args(cmdargs)
5829
+        if len(args) < 1:
5830
+            return self.help()            
5831
+        try:
5832
+            tree = arch.tree_root()
5833
+        except arch.errors.TreeRootError:
5834
+            tree = None
5835
+        
5836
+        arch_loc = None
5837
+        try:
5838
+            revision, arch_loc = paths.full_path_decode(args[0])
5839
+        except Exception, e:
5840
+            revision = cmdutil.determine_revision_arch(tree, args[0], 
5841
+                check_existence=False, allow_package=True)
5842
+        if len(args) > 1:
5843
+            directory = args[1]
5844
+        else:
5845
+            directory = str(revision.nonarch)
5846
+        if os.path.exists(directory):
5847
+            raise DirectoryExists(directory)
5848
+        cmdutil.ensure_archive_registered(revision.archive, arch_loc)
5849
+        try:
5850
+            cmdutil.ensure_revision_exists(revision)
5851
+        except cmdutil.NoSuchRevision, e:
5852
+            raise CommandFailedWrapper(e)
5853
+
5854
+        link = cmdutil.prompt ("get link")
5855
+        for line in cmdutil.iter_get(revision, directory, link,
5856
+                                     options.no_pristine,
5857
+                                     options.no_greedy_add):
5858
+            cmdutil.colorize(line)
5859
+
5860
+    def get_parser(self):
5861
+        """
5862
+        Returns the options parser to use for the "get" command.
5863
+
5864
+        :rtype: cmdutil.CmdOptionParser
5865
+        """
5866
+        parser=cmdutil.CmdOptionParser("fai get revision [dir]")
5867
+        parser.add_option("--no-pristine", action="store_true", 
5868
+                         dest="no_pristine", 
5869
+                         help="Do not make pristine copy for reference")
5870
+        parser.add_option("--no-greedy-add", action="store_true", 
5871
+                         dest="no_greedy_add", 
5872
+                         help="Never add to greedy libraries")
5873
+
5874
+        return parser 
5875
+
5876
+    def help(self, parser=None):
5877
+        """
5878
+        Prints a help message.
5879
+
5880
+        :param parser: If supplied, the parser to use for generating help.  If \
5881
+        not supplied, it is retrieved.
5882
+        :type parser: cmdutil.CmdOptionParser
5883
+        """
5884
+        if parser==None:
5885
+            parser=self.get_parser()
5886
+        parser.print_help()
5887
+        print """
5888
+Expands aliases and constructs a project tree for a revision.  If the optional
5889
+"dir" argument is provided, the project tree will be stored in this directory.
5890
+        """
5891
+        help_tree_spec()
5892
+        return
5893
+
5894
+class PromptCmd(cmd.Cmd):
5895
+    def __init__(self):
5896
+        cmd.Cmd.__init__(self)
5897
+        self.prompt = "Fai> "
5898
+        try:
5899
+            self.tree = arch.tree_root()
5900
+        except:
5901
+            self.tree = None
5902
+        self.set_title()
5903
+        self.set_prompt()
5904
+        self.fake_aba = abacmds.AbaCmds()
5905
+        self.identchars += '-'
5906
+        self.history_file = os.path.expanduser("~/.fai-history")
5907
+        readline.set_completer_delims(string.whitespace)
5908
+        if os.access(self.history_file, os.R_OK) and \
5909
+            os.path.isfile(self.history_file):
5910
+            readline.read_history_file(self.history_file)
5911
+
5912
+    def write_history(self):
5913
+        readline.write_history_file(self.history_file)
5914
+
5915
+    def do_quit(self, args):
5916
+        self.write_history()
5917
+        sys.exit(0)
5918
+
5919
+    def do_exit(self, args):
5920
+        self.do_quit(args)
5921
+
5922
+    def do_EOF(self, args):
5923
+        print
5924
+        self.do_quit(args)
5925
+
5926
+    def postcmd(self, line, bar):
5927
+        self.set_title()
5928
+        self.set_prompt()
5929
+
5930
+    def set_prompt(self):
5931
+        if self.tree is not None:
5932
+            try:
5933
+                version = " "+self.tree.tree_version.nonarch
5934
+            except:
5935
+                version = ""
5936
+        else:
5937
+            version = ""
5938
+        self.prompt = "Fai%s> " % version
5939
+
5940
+    def set_title(self, command=None):
5941
+        try:
5942
+            version = self.tree.tree_version.nonarch
5943
+        except:
5944
+            version = "[no version]"
5945
+        if command is None:
5946
+            command = ""
5947
+        sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
5948
+
5949
+    def do_cd(self, line):
5950
+        if line == "":
5951
+            line = "~"
5952
+        try:
5953
+            os.chdir(os.path.expanduser(line))
5954
+        except Exception, e:
5955
+            print e
5956
+        try:
5957
+            self.tree = arch.tree_root()
5958
+        except:
5959
+            self.tree = None
5960
+
5961
+    def do_help(self, line):
5962
+        Help()(line)
5963
+
5964
+    def default(self, line):
5965
+        args = line.split()
5966
+        if find_command(args[0]):
5967
+            try:
5968
+                find_command(args[0]).do_command(args[1:])
5969
+            except cmdutil.BadCommandOption, e:
5970
+                print e
5971
+            except cmdutil.GetHelp, e:
5972
+                find_command(args[0]).help()
5973
+            except CommandFailed, e:
5974
+                print e
5975
+            except arch.errors.ArchiveNotRegistered, e:
5976
+                print e
5977
+            except KeyboardInterrupt, e:
5978
+                print "Interrupted"
5979
+            except arch.util.ExecProblem, e:
5980
+                print e.proc.error.rstrip('\n')
5981
+            except cmdutil.CantDetermineVersion, e:
5982
+                print e
5983
+            except cmdutil.CantDetermineRevision, e:
5984
+                print e
5985
+            except Exception, e:
5986
+                print "Unhandled error:\n%s" % cmdutil.exception_str(e)
5987
+
5988
+        elif suggestions.has_key(args[0]):
5989
+            print suggestions[args[0]]
5990
+
5991
+        elif self.fake_aba.is_command(args[0]):
5992
+            tree = None
5993
+            try:
5994
+                tree = arch.tree_root()
5995
+            except arch.errors.TreeRootError:
5996
+                pass
5997
+            cmd = self.fake_aba.is_command(args[0])
5998
+            try:
5999
+                cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
6000
+            except KeyboardInterrupt, e:
6001
+                print "Interrupted"
6002
+
6003
+        elif options.tla_fallthrough and args[0] != "rm" and \
6004
+            cmdutil.is_tla_command(args[0]):
6005
+            try:
6006
+                tree = None
6007
+                try:
6008
+                    tree = arch.tree_root()
6009
+                except arch.errors.TreeRootError:
6010
+                    pass
6011
+                args = cmdutil.expand_prefix_alias(args, tree)
6012
+                arch.util.exec_safe('tla', args, stderr=sys.stderr,
6013
+                expected=(0, 1))
6014
+            except arch.util.ExecProblem, e:
6015
+                pass
6016
+            except KeyboardInterrupt, e:
6017
+                print "Interrupted"
6018
+        else:
6019
+            try:
6020
+                try:
6021
+                    tree = arch.tree_root()
6022
+                except arch.errors.TreeRootError:
6023
+                    tree = None
6024
+                args=line.split()
6025
+                os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
6026
+            except KeyboardInterrupt, e:
6027
+                print "Interrupted"
6028
+
6029
+    def completenames(self, text, line, begidx, endidx):
6030
+        completions = []
6031
+        iter = iter_command_names(self.fake_aba)
6032
+        try:
6033
+            if len(line) > 0:
6034
+                arg = line.split()[-1]
6035
+            else:
6036
+                arg = ""
6037
+            iter = iter_munged_completions(iter, arg, text)
6038
+        except Exception, e:
6039
+            print e
6040
+        return list(iter)
6041
+
6042
+    def completedefault(self, text, line, begidx, endidx):
6043
+        """Perform completion for native commands.
6044
+        
6045
+        :param text: The text to complete
6046
+        :type text: str
6047
+        :param line: The entire line to complete
6048
+        :type line: str
6049
+        :param begidx: The start of the text in the line
6050
+        :type begidx: int
6051
+        :param endidx: The end of the text in the line
6052
+        :type endidx: int
6053
+        """
6054
+        try:
6055
+            (cmd, args, foo) = self.parseline(line)
6056
+            command_obj=find_command(cmd)
6057
+            if command_obj is not None:
6058
+                return command_obj.complete(args.split(), text)
6059
+            elif not self.fake_aba.is_command(cmd) and \
6060
+                cmdutil.is_tla_command(cmd):
6061
+                iter = cmdutil.iter_supported_switches(cmd)
6062
+                if len(args) > 0:
6063
+                    arg = args.split()[-1]
6064
+                else:
6065
+                    arg = ""
6066
+                if arg.startswith("-"):
6067
+                    return list(iter_munged_completions(iter, arg, text))
6068
+                else:
6069
+                    return list(iter_munged_completions(
6070
+                        iter_file_completions(arg), arg, text))
6071
+
6072
+
6073
+            elif cmd == "cd":
6074
+                if len(args) > 0:
6075
+                    arg = args.split()[-1]
6076
+                else:
6077
+                    arg = ""
6078
+                iter = iter_dir_completions(arg)
6079
+                iter = iter_munged_completions(iter, arg, text)
6080
+                return list(iter)
6081
+            elif len(args)>0:
6082
+                arg = args.split()[-1]
6083
+                return list(iter_munged_completions(iter_file_completions(arg),
6084
+                                                    arg, text))
6085
+            else:
6086
+                return self.completenames(text, line, begidx, endidx)
6087
+        except Exception, e:
6088
+            print e
6089
+
6090
+
6091
+def iter_command_names(fake_aba):
6092
+    for entry in cmdutil.iter_combine([commands.iterkeys(), 
6093
+                                     fake_aba.get_commands(), 
6094
+                                     cmdutil.iter_tla_commands(False)]):
6095
+        if not suggestions.has_key(str(entry)):
6096
+            yield entry
6097
+
6098
+
6099
+def iter_file_completions(arg, only_dirs = False):
6100
+    """Generate an iterator that iterates through filename completions.
6101
+
6102
+    :param arg: The filename fragment to match
6103
+    :type arg: str
6104
+    :param only_dirs: If true, match only directories
6105
+    :type only_dirs: bool
6106
+    """
6107
+    cwd = os.getcwd()
6108
+    if cwd != "/":
6109
+        extras = [".", ".."]
6110
+    else:
6111
+        extras = []
6112
+    (dir, file) = os.path.split(arg)
6113
+    if dir != "":
6114
+        listingdir = os.path.expanduser(dir)
6115
+    else:
6116
+        listingdir = cwd
6117
+    for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
6118
+        if dir != "":
6119
+            userfile = dir+'/'+file
6120
+        else:
6121
+            userfile = file
6122
+        if userfile.startswith(arg):
6123
+            if os.path.isdir(listingdir+'/'+file):
6124
+                userfile+='/'
6125
+                yield userfile
6126
+            elif not only_dirs:
6127
+                yield userfile
6128
+
6129
+def iter_munged_completions(iter, arg, text):
6130
+    for completion in iter:
6131
+        completion = str(completion)
6132
+        if completion.startswith(arg):
6133
+            yield completion[len(arg)-len(text):]
6134
+
6135
+def iter_source_file_completions(tree, arg):
6136
+    treepath = cmdutil.tree_cwd(tree)
6137
+    if len(treepath) > 0:
6138
+        dirs = [treepath]
6139
+    else:
6140
+        dirs = None
6141
+    for file in tree.iter_inventory(dirs, source=True, both=True):
6142
+        file = file_completion_match(file, treepath, arg)
6143
+        if file is not None:
6144
+            yield file
6145
+
6146
+
6147
+def iter_untagged(tree, dirs):
6148
+    for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False, 
6149
+                                                categories=arch_core.non_root,
6150
+                                                control_files=True):
6151
+        yield file.name 
6152
+
6153
+
6154
+def iter_untagged_completions(tree, arg):
6155
+    """Generate an iterator for all visible untagged files that match arg.
6156
+
6157
+    :param tree: The tree to look for untagged files in
6158
+    :type tree: `arch.WorkingTree`
6159
+    :param arg: The argument to match
6160
+    :type arg: str
6161
+    :return: An iterator of all matching untagged files
6162
+    :rtype: iterator of str
6163
+    """
6164
+    treepath = cmdutil.tree_cwd(tree)
6165
+    if len(treepath) > 0:
6166
+        dirs = [treepath]
6167
+    else:
6168
+        dirs = None
6169
+
6170
+    for file in iter_untagged(tree, dirs):
6171
+        file = file_completion_match(file, treepath, arg)
6172
+        if file is not None:
6173
+            yield file
6174
+
6175
+
6176
+def file_completion_match(file, treepath, arg):
6177
+    """Determines whether a file within an arch tree matches the argument.
6178
+
6179
+    :param file: The rooted filename
6180
+    :type file: str
6181
+    :param treepath: The path to the cwd within the tree
6182
+    :type treepath: str
6183
+    :param arg: The prefix to match
6184
+    :return: The completion name, or None if not a match
6185
+    :rtype: str
6186
+    """
6187
+    if not file.startswith(treepath):
6188
+        return None
6189
+    if treepath != "":
6190
+        file = file[len(treepath)+1:]
6191
+
6192
+    if not file.startswith(arg):
6193
+        return None 
6194
+    if os.path.isdir(file):
6195
+        file += '/'
6196
+    return file
6197
+
6198
+def iter_modified_file_completions(tree, arg):
6199
+    """Returns a list of modified files that match the specified prefix.
6200
+
6201
+    :param tree: The current tree
6202
+    :type tree: `arch.WorkingTree`
6203
+    :param arg: The prefix to match
6204
+    :type arg: str
6205
+    """
6206
+    treepath = cmdutil.tree_cwd(tree)
6207
+    tmpdir = cmdutil.tmpdir()
6208
+    changeset = tmpdir+"/changeset"
6209
+    completions = []
6210
+    revision = cmdutil.determine_revision_tree(tree)
6211
+    for line in arch.iter_delta(revision, tree, changeset):
6212
+        if isinstance(line, arch.FileModification):
6213
+            file = file_completion_match(line.name[1:], treepath, arg)
6214
+            if file is not None:
6215
+                completions.append(file)
6216
+    shutil.rmtree(tmpdir)
6217
+    return completions
6218
+
6219
+def iter_dir_completions(arg):
6220
+    """Generate an iterator that iterates through directory name completions.
6221
+
6222
+    :param arg: The directory name fragment to match
6223
+    :type arg: str
6224
+    """
6225
+    return iter_file_completions(arg, True)
6226
+
6227
+class Shell(BaseCommand):
6228
+    def __init__(self):
6229
+        self.description = "Runs Fai as a shell"
6230
+
6231
+    def do_command(self, cmdargs):
6232
+        if len(cmdargs)!=0:
6233
+            raise cmdutil.GetHelp
6234
+        prompt = PromptCmd()
6235
+        try:
6236
+            prompt.cmdloop()
6237
+        finally:
6238
+            prompt.write_history()
6239
+
6240
+class AddID(BaseCommand):
6241
+    """
6242
+    Adds an inventory id for the given file
6243
+    """
6244
+    def __init__(self):
6245
+        self.description="Add an inventory id for a given file"
6246
+
6247
+    def get_completer(self, arg, index):
6248
+        tree = arch.tree_root()
6249
+        return iter_untagged_completions(tree, arg)
6250
+
6251
+    def do_command(self, cmdargs):
6252
+        """
6253
+        Master function that perfoms the "revision" command.
6254
+        """
6255
+        parser=self.get_parser()
6256
+        (options, args) = parser.parse_args(cmdargs)
6257
+
6258
+        tree = arch.tree_root()
6259
+
6260
+        if (len(args) == 0) == (options.untagged == False):
6261
+            raise cmdutil.GetHelp
6262
+
6263
+       #if options.id and len(args) != 1:
6264
+       #    print "If --id is specified, only one file can be named."
6265
+       #    return
6266
+        
6267
+        method = tree.tagging_method
6268
+        
6269
+        if options.id_type == "tagline":
6270
+            if method != "tagline":
6271
+                if not cmdutil.prompt("Tagline in other tree"):
6272
+                    if method == "explicit":
6273
+                        options.id_type == explicit
6274
+                    else:
6275
+                        print "add-id not supported for \"%s\" tagging method"\
6276
+                            % method 
6277
+                        return
6278
+        
6279
+        elif options.id_type == "explicit":
6280
+            if method != "tagline" and method != explicit:
6281
+                if not prompt("Explicit in other tree"):
6282
+                    print "add-id not supported for \"%s\" tagging method" % \
6283
+                        method
6284
+                    return
6285
+        
6286
+        if options.id_type == "auto":
6287
+            if method != "tagline" and method != "explicit":
6288
+                print "add-id not supported for \"%s\" tagging method" % method
6289
+                return
6290
+            else:
6291
+                options.id_type = method
6292
+        if options.untagged:
6293
+            args = None
6294
+        self.add_ids(tree, options.id_type, args)
6295
+
6296
+    def add_ids(self, tree, id_type, files=()):
6297
+        """Add inventory ids to files.
6298
+        
6299
+        :param tree: the tree the files are in
6300
+        :type tree: `arch.WorkingTree`
6301
+        :param id_type: the type of id to add: "explicit" or "tagline"
6302
+        :type id_type: str
6303
+        :param files: The list of files to add.  If None do all untagged.
6304
+        :type files: tuple of str
6305
+        """
6306
+
6307
+        untagged = (files is None)
6308
+        if untagged:
6309
+            files = list(iter_untagged(tree, None))
6310
+        previous_files = []
6311
+        while len(files) > 0:
6312
+            previous_files.extend(files)
6313
+            if id_type == "explicit":
6314
+                cmdutil.add_id(files)
6315
+            elif id_type == "tagline":
6316
+                for file in files:
6317
+                    try:
6318
+                        cmdutil.add_tagline_or_explicit_id(file)
6319
+                    except cmdutil.AlreadyTagged:
6320
+                        print "\"%s\" already has a tagline." % file
6321
+                    except cmdutil.NoCommentSyntax:
6322
+                        pass
6323
+            #do inventory after tagging until no untagged files are encountered
6324
+            if untagged:
6325
+                files = []
6326
+                for file in iter_untagged(tree, None):
6327
+                    if not file in previous_files:
6328
+                        files.append(file)
6329
+
6330
+            else:
6331
+                break
6332
+
6333
+    def get_parser(self):
6334
+        """
6335
+        Returns the options parser to use for the "revision" command.
6336
+
6337
+        :rtype: cmdutil.CmdOptionParser
6338
+        """
6339
+        parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
6340
+# ddaa suggests removing this to promote GUIDs.  Let's see who squalks.
6341
+#        parser.add_option("-i", "--id", dest="id", 
6342
+#                         help="Specify id for a single file", default=None)
6343
+        parser.add_option("--tltl", action="store_true", 
6344
+                         dest="lord_style",  help="Use Tom Lord's style of id.")
6345
+        parser.add_option("--explicit", action="store_const", 
6346
+                         const="explicit", dest="id_type", 
6347
+                         help="Use an explicit id", default="auto")
6348
+        parser.add_option("--tagline", action="store_const", 
6349
+                         const="tagline", dest="id_type", 
6350
+                         help="Use a tagline id")
6351
+        parser.add_option("--untagged", action="store_true", 
6352
+                         dest="untagged", default=False, 
6353
+                         help="tag all untagged files")
6354
+        return parser 
6355
+
6356
+    def help(self, parser=None):
6357
+        """
6358
+        Prints a help message.
6359
+
6360
+        :param parser: If supplied, the parser to use for generating help.  If \
6361
+        not supplied, it is retrieved.
6362
+        :type parser: cmdutil.CmdOptionParser
6363
+        """
6364
+        if parser==None:
6365
+            parser=self.get_parser()
6366
+        parser.print_help()
6367
+        print """
6368
+Adds an inventory to the specified file(s) and directories.  If --untagged is
6369
+specified, adds inventory to all untagged files and directories.
6370
+        """
6371
+        return
6372
+
6373
+
6374
+class Merge(BaseCommand):
6375
+    """
6376
+    Merges changes from other versions into the current tree
6377
+    """
6378
+    def __init__(self):
6379
+        self.description="Merges changes from other versions"
6380
+        try:
6381
+            self.tree = arch.tree_root()
6382
+        except:
6383
+            self.tree = None
6384
+
6385
+
6386
+    def get_completer(self, arg, index):
6387
+        if self.tree is None:
6388
+            raise arch.errors.TreeRootError
6389
+        completions = list(ancillary.iter_partners(self.tree, 
6390
+                                                   self.tree.tree_version))
6391
+        if len(completions) == 0:
6392
+            completions = list(self.tree.iter_log_versions())
6393
+
6394
+        aliases = []
6395
+        try:
6396
+            for completion in completions:
6397
+                alias = ancillary.compact_alias(str(completion), self.tree)
6398
+                if alias:
6399
+                    aliases.extend(alias)
6400
+
6401
+            for completion in completions:
6402
+                if completion.archive == self.tree.tree_version.archive:
6403
+                    aliases.append(completion.nonarch)
6404
+
6405
+        except Exception, e:
6406
+            print e
6407
+            
6408
+        completions.extend(aliases)
6409
+        return completions
6410
+
6411
+    def do_command(self, cmdargs):
6412
+        """
6413
+        Master function that perfoms the "merge" command.
6414
+        """
6415
+        parser=self.get_parser()
6416
+        (options, args) = parser.parse_args(cmdargs)
6417
+        if options.diff3:
6418
+            action="star-merge"
6419
+        else:
6420
+            action = options.action
6421
+        
6422
+        if self.tree is None:
6423
+            raise arch.errors.TreeRootError(os.getcwd())
6424
+        if cmdutil.has_changed(self.tree.tree_version):
6425
+            raise UncommittedChanges(self.tree)
6426
+
6427
+        if len(args) > 0:
6428
+            revisions = []
6429
+            for arg in args:
6430
+                revisions.append(cmdutil.determine_revision_arch(self.tree, 
6431
+                                                                 arg))
6432
+            source = "from commandline"
6433
+        else:
6434
+            revisions = ancillary.iter_partner_revisions(self.tree, 
6435
+                                                         self.tree.tree_version)
6436
+            source = "from partner version"
6437
+        revisions = misc.rewind_iterator(revisions)
6438
+        try:
6439
+            revisions.next()
6440
+            revisions.rewind()
6441
+        except StopIteration, e:
6442
+            revision = cmdutil.tag_cur(self.tree)
6443
+            if revision is None:
6444
+                raise CantDetermineRevision("", "No version specified, no "
6445
+                                            "partner-versions, and no tag"
6446
+                                            " source")
6447
+            revisions = [revision]
6448
+            source = "from tag source"
6449
+        for revision in revisions:
6450
+            cmdutil.ensure_archive_registered(revision.archive)
6451
+            cmdutil.colorize(arch.Chatter("* Merging %s [%s]" % 
6452
+                             (revision, source)))
6453
+            if action=="native-merge" or action=="update":
6454
+                if self.native_merge(revision, action) == 0:
6455
+                    continue
6456
+            elif action=="star-merge":
6457
+                try: 
6458
+                    self.star_merge(revision, options.diff3)
6459
+                except errors.MergeProblem, e:
6460
+                    break
6461
+            if cmdutil.has_changed(self.tree.tree_version):
6462
+                break
6463
+
6464
+    def star_merge(self, revision, diff3):
6465
+        """Perform a star-merge on the current tree.
6466
+        
6467
+        :param revision: The revision to use for the merge
6468
+        :type revision: `arch.Revision`
6469
+        :param diff3: If true, do a diff3 merge
6470
+        :type diff3: bool
6471
+        """
6472
+        try:
6473
+            for line in self.tree.iter_star_merge(revision, diff3=diff3):
6474
+                cmdutil.colorize(line)
6475
+        except arch.util.ExecProblem, e:
6476
+            if e.proc.status is not None and e.proc.status == 1:
6477
+                if e.proc.error:
6478
+                    print e.proc.error
6479
+                raise MergeProblem
6480
+            else:
6481
+                raise
6482
+
6483
+    def native_merge(self, other_revision, action):
6484
+        """Perform a native-merge on the current tree.
6485
+        
6486
+        :param other_revision: The revision to use for the merge
6487
+        :type other_revision: `arch.Revision`
6488
+        :return: 0 if the merge was skipped, 1 if it was applied
6489
+        """
6490
+        other_tree = cmdutil.find_or_make_local_revision(other_revision)
6491
+        try:
6492
+            if action == "native-merge":
6493
+                ancestor = cmdutil.merge_ancestor2(self.tree, other_tree, 
6494
+                                                   other_revision)
6495
+            elif action == "update":
6496
+                ancestor = cmdutil.tree_latest(self.tree, 
6497
+                                               other_revision.version)
6498
+        except CantDetermineRevision, e:
6499
+            raise CommandFailedWrapper(e)
6500
+        cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
6501
+        if (ancestor == other_revision):
6502
+            cmdutil.colorize(arch.Chatter("* Skipping redundant merge" 
6503
+                                          % ancestor))
6504
+            return 0
6505
+        delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)    
6506
+        for line in cmdutil.iter_apply_delta_filter(delta):
6507
+            cmdutil.colorize(line)
6508
+        return 1
6509
+
6510
+
6511
+
6512
+    def get_parser(self):
6513
+        """
6514
+        Returns the options parser to use for the "merge" command.
6515
+
6516
+        :rtype: cmdutil.CmdOptionParser
6517
+        """
6518
+        parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
6519
+        parser.add_option("-s", "--star-merge", action="store_const",
6520
+                          dest="action", help="Use star-merge",
6521
+                          const="star-merge", default="native-merge")
6522
+        parser.add_option("--update", action="store_const",
6523
+                          dest="action", help="Use update picker",
6524
+                          const="update")
6525
+        parser.add_option("--diff3", action="store_true", 
6526
+                         dest="diff3",  
6527
+                         help="Use diff3 for merge (implies star-merge)")
6528
+        return parser 
6529
+
6530
+    def help(self, parser=None):
6531
+        """
6532
+        Prints a help message.
6533
+
6534
+        :param parser: If supplied, the parser to use for generating help.  If \
6535
+        not supplied, it is retrieved.
6536
+        :type parser: cmdutil.CmdOptionParser
6537
+        """
6538
+        if parser==None:
6539
+            parser=self.get_parser()
6540
+        parser.print_help()
6541
+        print """
6542
+Performs a merge operation using the specified version.
6543
+        """
6544
+        return
6545
+
6546
+class ELog(BaseCommand):
6547
+    """
6548
+    Produces a raw patchlog and invokes the user's editor
6549
+    """
6550
+    def __init__(self):
6551
+        self.description="Edit a patchlog to commit"
6552
+        try:
6553
+            self.tree = arch.tree_root()
6554
+        except:
6555
+            self.tree = None
6556
+
6557
+
6558
+    def do_command(self, cmdargs):
6559
+        """
6560
+        Master function that perfoms the "elog" command.
6561
+        """
6562
+        parser=self.get_parser()
6563
+        (options, args) = parser.parse_args(cmdargs)
6564
+        if self.tree is None:
6565
+            raise arch.errors.TreeRootError
6566
+
6567
+        edit_log(self.tree)
6568
+
6569
+    def get_parser(self):
6570
+        """
6571
+        Returns the options parser to use for the "merge" command.
6572
+
6573
+        :rtype: cmdutil.CmdOptionParser
6574
+        """
6575
+        parser=cmdutil.CmdOptionParser("fai elog")
6576
+        return parser 
6577
+
6578
+
6579
+    def help(self, parser=None):
6580
+        """
6581
+        Invokes $EDITOR to produce a log for committing.
6582
+
6583
+        :param parser: If supplied, the parser to use for generating help.  If \
6584
+        not supplied, it is retrieved.
6585
+        :type parser: cmdutil.CmdOptionParser
6586
+        """
6587
+        if parser==None:
6588
+            parser=self.get_parser()
6589
+        parser.print_help()
6590
+        print """
6591
+Invokes $EDITOR to produce a log for committing.
6592
+        """
6593
+        return
6594
+
6595
+def edit_log(tree):
6596
+    """Makes and edits the log for a tree.  Does all kinds of fancy things
6597
+    like log templates and merge summaries and log-for-merge
6598
+    
6599
+    :param tree: The tree to edit the log for
6600
+    :type tree: `arch.WorkingTree`
6601
+    """
6602
+    #ensure we have an editor before preparing the log
6603
+    cmdutil.find_editor()
6604
+    log = tree.log_message(create=False)
6605
+    log_is_new = False
6606
+    if log is None or cmdutil.prompt("Overwrite log"):
6607
+        if log is not None:
6608
+           os.remove(log.name)
6609
+        log = tree.log_message(create=True)
6610
+        log_is_new = True
6611
+        tmplog = log.name
6612
+        template = tree+"/{arch}/=log-template"
6613
+        if not os.path.exists(template):
6614
+            template = os.path.expanduser("~/.arch-params/=log-template")
6615
+            if not os.path.exists(template):
6616
+                template = None
6617
+        if template:
6618
+            shutil.copyfile(template, tmplog)
6619
+        
6620
+        new_merges = list(cmdutil.iter_new_merges(tree, 
6621
+                                                  tree.tree_version))
6622
+        log["Summary"] = merge_summary(new_merges, tree.tree_version)
6623
+        if len(new_merges) > 0:   
6624
+            if cmdutil.prompt("Log for merge"):
6625
+                mergestuff = cmdutil.log_for_merge(tree)
6626
+                log.description += mergestuff
6627
+        log.save()
6628
+    try:
6629
+        cmdutil.invoke_editor(log.name)
6630
+    except:
6631
+        if log_is_new:
6632
+            os.remove(log.name)
6633
+        raise
6634
+
6635
+def merge_summary(new_merges, tree_version):
6636
+    if len(new_merges) == 0:
6637
+        return ""
6638
+    if len(new_merges) == 1:
6639
+        summary = new_merges[0].summary
6640
+    else:
6641
+        summary = "Merge"
6642
+
6643
+    credits = []
6644
+    for merge in new_merges:
6645
+        if arch.my_id() != merge.creator:
6646
+            name = re.sub("<.*>", "", merge.creator).rstrip(" ");
6647
+            if not name in credits:
6648
+                credits.append(name)
6649
+        else:
6650
+            version = merge.revision.version
6651
+            if version.archive == tree_version.archive:
6652
+                if not version.nonarch in credits:
6653
+                    credits.append(version.nonarch)
6654
+            elif not str(version) in credits:
6655
+                credits.append(str(version))
6656
+
6657
+    return ("%s (%s)") % (summary, ", ".join(credits))
6658
+
6659
+class MirrorArchive(BaseCommand):
6660
+    """
6661
+    Updates a mirror from an archive
6662
+    """
6663
+    def __init__(self):
6664
+        self.description="Update a mirror from an archive"
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
+        if len(args) > 1:
6674
+            raise GetHelp
6675
+        try:
6676
+            tree = arch.tree_root()
6677
+        except:
6678
+            tree = None
6679
+
6680
+        if len(args) == 0:
6681
+            if tree is not None:
6682
+                name = tree.tree_version()
6683
+        else:
6684
+            name = cmdutil.expand_alias(args[0], tree)
6685
+            name = arch.NameParser(name)
6686
+
6687
+        to_arch = name.get_archive()
6688
+        from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
6689
+        limit = name.get_nonarch()
6690
+
6691
+        iter = arch_core.mirror_archive(from_arch,to_arch, limit)
6692
+        for line in arch.chatter_classifier(iter):
6693
+            cmdutil.colorize(line)
6694
+
6695
+    def get_parser(self):
6696
+        """
6697
+        Returns the options parser to use for the "revision" command.
6698
+
6699
+        :rtype: cmdutil.CmdOptionParser
6700
+        """
6701
+        parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
6702
+        return parser 
6703
+
6704
+    def help(self, parser=None):
6705
+        """
6706
+        Prints a help message.
6707
+
6708
+        :param parser: If supplied, the parser to use for generating help.  If \
6709
+        not supplied, it is retrieved.
6710
+        :type parser: cmdutil.CmdOptionParser
6711
+        """
6712
+        if parser==None:
6713
+            parser=self.get_parser()
6714
+        parser.print_help()
6715
+        print """
6716
+Updates a mirror from an archive.  If a branch, package, or version is
6717
+supplied, only changes under it are mirrored.
6718
+        """
6719
+        return
6720
+
6721
+def help_tree_spec():
6722
+    print """Specifying revisions (default: tree)
6723
+Revisions may be specified by alias, revision, version or patchlevel.
6724
+Revisions or versions may be fully qualified.  Unqualified revisions, versions, 
6725
+or patchlevels use the archive of the current project tree.  Versions will
6726
+use the latest patchlevel in the tree.  Patchlevels will use the current tree-
6727
+version.
6728
+
6729
+Use "alias" to list available (user and automatic) aliases."""
6730
+
6731
+def help_aliases(tree):
6732
+    print """Auto-generated aliases
6733
+ acur : The latest revision in the archive of the tree-version.  You can specfy
6734
+        a different version like so: acur:foo--bar--0 (aliases can be used)
6735
+ tcur : (tree current) The latest revision in the tree of the tree-version.
6736
+        You can specify a different version like so: tcur:foo--bar--0 (aliases
6737
+        can be used).
6738
+tprev : (tree previous) The previous revision in the tree of the tree-version.
6739
+        To specify an older revision, use a number, e.g. "tprev:4"
6740
+ tanc : (tree ancestor) The ancestor revision of the tree
6741
+        To specify an older revision, use a number, e.g. "tanc:4"
6742
+tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
6743
+ tmod : (tree modified) The latest revision to modify a given file 
6744
+        (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
6745
+ ttag : (tree tag) The revision that was tagged into the current tree revision,
6746
+        according to the tree.
6747
+tagcur: (tag current) The latest revision of the version that the current tree
6748
+        was tagged from.
6749
+mergeanc : The common ancestor of the current tree and the specified revision.
6750
+        Defaults to the first partner-version's latest revision or to tagcur.
6751
+   """
6752
+    print "User aliases"
6753
+    for parts in ancillary.iter_all_alias(tree):
6754
+        print parts[0].rjust(10)+" : "+parts[1]
6755
+
6756
+
6757
+class Inventory(BaseCommand):
6758
+    """List the status of files in the tree"""
6759
+    def __init__(self):
6760
+        self.description=self.__doc__
6761
+
6762
+    def do_command(self, cmdargs):
6763
+        """
6764
+        Master function that perfoms the "revision" command.
6765
+        """
6766
+
6767
+        parser=self.get_parser()
6768
+        (options, args) = parser.parse_args(cmdargs)
6769
+        tree = arch.tree_root()
6770
+        categories = []
6771
+
6772
+        if (options.source):
6773
+            categories.append(arch_core.SourceFile)
6774
+        if (options.precious):
6775
+            categories.append(arch_core.PreciousFile)
6776
+        if (options.backup):
6777
+            categories.append(arch_core.BackupFile)
6778
+        if (options.junk):
6779
+            categories.append(arch_core.JunkFile)
6780
+
6781
+        if len(categories) == 1:
6782
+            show_leading = False
6783
+        else:
6784
+            show_leading = True
6785
+
6786
+        if len(categories) == 0:
6787
+            categories = None
6788
+
6789
+        if options.untagged:
6790
+            categories = arch_core.non_root
6791
+            show_leading = False
6792
+            tagged = False
6793
+        else:
6794
+            tagged = None
6795
+        
6796
+        for file in arch_core.iter_inventory_filter(tree, None, 
6797
+            control_files=options.control_files, 
6798
+            categories = categories, tagged=tagged):
6799
+            print arch_core.file_line(file, 
6800
+                                      category = show_leading, 
6801
+                                      untagged = show_leading,
6802
+                                      id = options.ids)
6803
+
6804
+    def get_parser(self):
6805
+        """
6806
+        Returns the options parser to use for the "revision" command.
6807
+
6808
+        :rtype: cmdutil.CmdOptionParser
6809
+        """
6810
+        parser=cmdutil.CmdOptionParser("fai inventory [options]")
6811
+        parser.add_option("--ids", action="store_true", dest="ids", 
6812
+                          help="Show file ids")
6813
+        parser.add_option("--control", action="store_true", 
6814
+                          dest="control_files", help="include control files")
6815
+        parser.add_option("--source", action="store_true", dest="source",
6816
+                          help="List source files")
6817
+        parser.add_option("--backup", action="store_true", dest="backup",
6818
+                          help="List backup files")
6819
+        parser.add_option("--precious", action="store_true", dest="precious",
6820
+                          help="List precious files")
6821
+        parser.add_option("--junk", action="store_true", dest="junk",
6822
+                          help="List junk files")
6823
+        parser.add_option("--unrecognized", action="store_true", 
6824
+                          dest="unrecognized", help="List unrecognized files")
6825
+        parser.add_option("--untagged", action="store_true", 
6826
+                          dest="untagged", help="List only untagged files")
6827
+        return parser 
6828
+
6829
+    def help(self, parser=None):
6830
+        """
6831
+        Prints a help message.
6832
+
6833
+        :param parser: If supplied, the parser to use for generating help.  If \
6834
+        not supplied, it is retrieved.
6835
+        :type parser: cmdutil.CmdOptionParser
6836
+        """
6837
+        if parser==None:
6838
+            parser=self.get_parser()
6839
+        parser.print_help()
6840
+        print """
6841
+Lists the status of files in the archive:
6842
+S source
6843
+P precious
6844
+B backup
6845
+J junk
6846
+U unrecognized
6847
+T tree root
6848
+? untagged-source
6849
+Leading letter are not displayed if only one kind of file is shown
6850
+        """
6851
+        return
6852
+
6853
+
6854
+class Alias(BaseCommand):
6855
+    """List or adjust aliases"""
6856
+    def __init__(self):
6857
+        self.description=self.__doc__
6858
+
6859
+    def get_completer(self, arg, index):
6860
+        if index > 2:
6861
+            return ()
6862
+        try:
6863
+            self.tree = arch.tree_root()
6864
+        except:
6865
+            self.tree = None
6866
+
6867
+        if index == 0:
6868
+            return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
6869
+        elif index == 1:
6870
+            return cmdutil.iter_revision_completions(arg, self.tree)
6871
+
6872
+
6873
+    def do_command(self, cmdargs):
6874
+        """
6875
+        Master function that perfoms the "revision" command.
6876
+        """
6877
+
6878
+        parser=self.get_parser()
6879
+        (options, args) = parser.parse_args(cmdargs)
6880
+        try:
6881
+            self.tree =  arch.tree_root()
6882
+        except:
6883
+            self.tree = None
6884
+
6885
+
6886
+        try:
6887
+            options.action(args, options)
6888
+        except cmdutil.ForbiddenAliasSyntax, e:
6889
+            raise CommandFailedWrapper(e)
6890
+
6891
+    def arg_dispatch(self, args, options):
6892
+        """Add, modify, or list aliases, depending on number of arguments
6893
+
6894
+        :param args: The list of commandline arguments
6895
+        :type args: list of str
6896
+        :param options: The commandline options
6897
+        """
6898
+        if len(args) == 0:
6899
+            help_aliases(self.tree)
6900
+            return
6901
+        elif len(args) == 1:
6902
+            self.print_alias(args[0])
6903
+        elif (len(args)) == 2:
6904
+            self.add(args[0], args[1], options)
6905
+        else:
6906
+            raise cmdutil.GetHelp
6907
+
6908
+    def print_alias(self, alias):
6909
+        answer = None
6910
+        for pair in ancillary.iter_all_alias(self.tree):
6911
+            if pair[0] == alias:
6912
+                answer = pair[1]
6913
+        if answer is not None:
6914
+            print answer
6915
+        else:
6916
+            print "The alias %s is not assigned." % alias
6917
+
6918
+    def add(self, alias, expansion, options):
6919
+        """Add or modify aliases
6920
+
6921
+        :param alias: The alias name to create/modify
6922
+        :type alias: str
6923
+        :param expansion: The expansion to assign to the alias name
6924
+        :type expansion: str
6925
+        :param options: The commandline options
6926
+        """
6927
+        newlist = ""
6928
+        written = False
6929
+        new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, 
6930
+            self.tree))
6931
+        ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
6932
+
6933
+        for pair in self.get_iterator(options):
6934
+            if pair[0] != alias:
6935
+                newlist+="%s=%s\n" % (pair[0], pair[1])
6936
+            elif not written:
6937
+                newlist+=new_line
6938
+                written = True
6939
+        if not written:
6940
+            newlist+=new_line
6941
+        self.write_aliases(newlist, options)
6942
+            
6943
+    def delete(self, args, options):
6944
+        """Delete the specified alias
6945
+
6946
+        :param args: The list of arguments
6947
+        :type args: list of str
6948
+        :param options: The commandline options
6949
+        """
6950
+        deleted = False
6951
+        if len(args) != 1:
6952
+            raise cmdutil.GetHelp
6953
+        newlist = ""
6954
+        for pair in self.get_iterator(options):
6955
+            if pair[0] != args[0]:
6956
+                newlist+="%s=%s\n" % (pair[0], pair[1])
6957
+            else:
6958
+                deleted = True
6959
+        if not deleted:
6960
+            raise errors.NoSuchAlias(args[0])
6961
+        self.write_aliases(newlist, options)
6962
+
6963
+    def get_alias_file(self, options):
6964
+        """Return the name of the alias file to use
6965
+
6966
+        :param options: The commandline options
6967
+        """
6968
+        if options.tree:
6969
+            if self.tree is None:
6970
+                self.tree == arch.tree_root()
6971
+            return str(self.tree)+"/{arch}/+aliases"
6972
+        else:
6973
+            return "~/.aba/aliases"
6974
+
6975
+    def get_iterator(self, options):
6976
+        """Return the alias iterator to use
6977
+
6978
+        :param options: The commandline options
6979
+        """
6980
+        return ancillary.iter_alias(self.get_alias_file(options))
6981
+
6982
+    def write_aliases(self, newlist, options):
6983
+        """Safely rewrite the alias file
6984
+        :param newlist: The new list of aliases
6985
+        :type newlist: str
6986
+        :param options: The commandline options
6987
+        """
6988
+        filename = os.path.expanduser(self.get_alias_file(options))
6989
+        file = cmdutil.NewFileVersion(filename)
6990
+        file.write(newlist)
6991
+        file.commit()
6992
+
6993
+
6994
+    def get_parser(self):
6995
+        """
6996
+        Returns the options parser to use for the "alias" command.
6997
+
6998
+        :rtype: cmdutil.CmdOptionParser
6999
+        """
7000
+        parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
7001
+        parser.add_option("-d", "--delete", action="store_const", dest="action",
7002
+                          const=self.delete, default=self.arg_dispatch, 
7003
+                          help="Delete an alias")
7004
+        parser.add_option("--tree", action="store_true", dest="tree", 
7005
+                          help="Create a per-tree alias", default=False)
7006
+        return parser 
7007
+
7008
+    def help(self, parser=None):
7009
+        """
7010
+        Prints a help message.
7011
+
7012
+        :param parser: If supplied, the parser to use for generating help.  If \
7013
+        not supplied, it is retrieved.
7014
+        :type parser: cmdutil.CmdOptionParser
7015
+        """
7016
+        if parser==None:
7017
+            parser=self.get_parser()
7018
+        parser.print_help()
7019
+        print """
7020
+Lists current aliases or modifies the list of aliases.
7021
+
7022
+If no arguments are supplied, aliases will be listed.  If two arguments are
7023
+supplied, the specified alias will be created or modified.  If -d or --delete
7024
+is supplied, the specified alias will be deleted.
7025
+
7026
+You can create aliases that refer to any fully-qualified part of the
7027
+Arch namespace, e.g. 
7028
+archive, 
7029
+archive/category, 
7030
+archive/category--branch, 
7031
+archive/category--branch--version (my favourite)
7032
+archive/category--branch--version--patchlevel
7033
+
7034
+Aliases can be used automatically by native commands.  To use them
7035
+with external or tla commands, prefix them with ^ (you can do this
7036
+with native commands, too).
7037
+"""
7038
+
7039
+
7040
+class RequestMerge(BaseCommand):
7041
+    """Submit a merge request to Bug Goo"""
7042
+    def __init__(self):
7043
+        self.description=self.__doc__
7044
+
7045
+    def do_command(self, cmdargs):
7046
+        """Submit a merge request
7047
+
7048
+        :param cmdargs: The commandline arguments
7049
+        :type cmdargs: list of str
7050
+        """
7051
+        cmdutil.find_editor()
7052
+        parser = self.get_parser()
7053
+        (options, args) = parser.parse_args(cmdargs)
7054
+        try:
7055
+            self.tree=arch.tree_root()
7056
+        except:
7057
+            self.tree=None
7058
+        base, revisions = self.revision_specs(args)
7059
+        message = self.make_headers(base, revisions)
7060
+        message += self.make_summary(revisions)
7061
+        path = self.edit_message(message)
7062
+        message = self.tidy_message(path)
7063
+        if cmdutil.prompt("Send merge"):
7064
+            self.send_message(message)
7065
+            print "Merge request sent"
7066
+
7067
+    def make_headers(self, base, revisions):
7068
+        """Produce email and Bug Goo header strings
7069
+
7070
+        :param base: The base revision to apply merges to
7071
+        :type base: `arch.Revision`
7072
+        :param revisions: The revisions to replay into the base
7073
+        :type revisions: list of `arch.Patchlog`
7074
+        :return: The headers
7075
+        :rtype: str
7076
+        """
7077
+        headers = "To: gnu-arch-users@gnu.org\n"
7078
+        headers += "From: %s\n" % options.fromaddr
7079
+        if len(revisions) == 1:
7080
+            headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
7081
+        else:
7082
+            headers += "Subject: [MERGE REQUEST]\n"
7083
+        headers += "\n"
7084
+        headers += "Base-Revision: %s\n" % base
7085
+        for revision in revisions:
7086
+            headers += "Revision: %s\n" % revision.revision
7087
+        headers += "Bug: \n\n"
7088
+        return headers
7089
+
7090
+    def make_summary(self, logs):
7091
+        """Generate a summary of merges
7092
+
7093
+        :param logs: the patchlogs that were directly added by the merges
7094
+        :type logs: list of `arch.Patchlog`
7095
+        :return: the summary
7096
+        :rtype: str
7097
+        """ 
7098
+        summary = ""
7099
+        for log in logs:
7100
+            summary+=str(log.revision)+"\n"
7101
+            summary+=log.summary+"\n"
7102
+            if log.description.strip():
7103
+                summary+=log.description.strip('\n')+"\n\n"
7104
+        return summary
7105
+
7106
+    def revision_specs(self, args):
7107
+        """Determine the base and merge revisions from tree and arguments.
7108
+
7109
+        :param args: The parsed arguments
7110
+        :type args: list of str
7111
+        :return: The base revision and merge revisions 
7112
+        :rtype: `arch.Revision`, list of `arch.Patchlog`
7113
+        """
7114
+        if len(args) > 0:
7115
+            target_revision = cmdutil.determine_revision_arch(self.tree, 
7116
+                                                              args[0])
7117
+        else:
7118
+            target_revision = cmdutil.tree_latest(self.tree)
7119
+        if len(args) > 1:
7120
+            merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
7121
+                       self.tree, f)) for f in args[1:] ]
7122
+        else:
7123
+            if self.tree is None:
7124
+                raise CantDetermineRevision("", "Not in a project tree")
7125
+            merge_iter = cmdutil.iter_new_merges(self.tree, 
7126
+                                                 target_revision.version, 
7127
+                                                 False)
7128
+            merges = [f for f in cmdutil.direct_merges(merge_iter)]
7129
+        return (target_revision, merges)
7130
+
7131
+    def edit_message(self, message):
7132
+        """Edit an email message in the user's standard editor
7133
+
7134
+        :param message: The message to edit
7135
+        :type message: str
7136
+        :return: the path of the edited message
7137
+        :rtype: str
7138
+        """
7139
+        if self.tree is None:
7140
+            path = os.get_cwd()
7141
+        else:
7142
+            path = self.tree
7143
+        path += "/,merge-request"
7144
+        file = open(path, 'w')
7145
+        file.write(message)
7146
+        file.flush()
7147
+        cmdutil.invoke_editor(path)
7148
+        return path
7149
+
7150
+    def tidy_message(self, path):
7151
+        """Validate and clean up message.
7152
+
7153
+        :param path: The path to the message to clean up
7154
+        :type path: str
7155
+        :return: The parsed message
7156
+        :rtype: `email.Message`
7157
+        """
7158
+        mail = email.message_from_file(open(path))
7159
+        if mail["Subject"].strip() == "[MERGE REQUEST]":
7160
+            raise BlandSubject
7161
+        
7162
+        request = email.message_from_string(mail.get_payload())
7163
+        if request.has_key("Bug"):
7164
+            if request["Bug"].strip()=="":
7165
+                del request["Bug"]
7166
+        mail.set_payload(request.as_string())
7167
+        return mail
7168
+
7169
+    def send_message(self, message):
7170
+        """Send a message, using its headers to address it.
7171
+
7172
+        :param message: The message to send
7173
+        :type message: `email.Message`"""
7174
+        server = smtplib.SMTP()
7175
+        server.sendmail(message['From'], message['To'], message.as_string())
7176
+        server.quit()
7177
+
7178
+    def help(self, parser=None):
7179
+        """Print a usage message
7180
+
7181
+        :param parser: The options parser to use
7182
+        :type parser: `cmdutil.CmdOptionParser`
7183
+        """
7184
+        if parser is None:
7185
+            parser = self.get_parser()
7186
+        parser.print_help()
7187
+        print """
7188
+Sends a merge request formatted for Bug Goo.  Intended use: get the tree
7189
+you'd like to merge into.  Apply the merges you want.  Invoke request-merge.
7190
+The merge request will open in your $EDITOR.
7191
+
7192
+When no TARGET is specified, it uses the current tree revision.  When
7193
+no MERGE is specified, it uses the direct merges (as in "revisions
7194
+--direct-merges").  But you can specify just the TARGET, or all the MERGE
7195
+revisions.
7196
+"""
7197
+
7198
+    def get_parser(self):
7199
+        """Produce a commandline parser for this command.
7200
+
7201
+        :rtype: `cmdutil.CmdOptionParser`
7202
+        """
7203
+        parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
7204
+        return parser
7205
+
7206
+commands = { 
7207
+'changes' : Changes,
7208
+'help' : Help,
7209
+'update': Update,
7210
+'apply-changes':ApplyChanges,
7211
+'cat-log': CatLog,
7212
+'commit': Commit,
7213
+'revision': Revision,
7214
+'revisions': Revisions,
7215
+'get': Get,
7216
+'revert': Revert,
7217
+'shell': Shell,
7218
+'add-id': AddID,
7219
+'merge': Merge,
7220
+'elog': ELog,
7221
+'mirror-archive': MirrorArchive,
7222
+'ninventory': Inventory,
7223
+'alias' : Alias,
7224
+'request-merge': RequestMerge,
7225
+}
7226
+suggestions = {
7227
+'apply-delta' : "Try \"apply-changes\".",
7228
+'delta' : "To compare two revisions, use \"changes\".",
7229
+'diff-rev' : "To compare two revisions, use \"changes\".",
7230
+'undo' : "To undo local changes, use \"revert\".",
7231
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
7232
+'missing-from' : "Try \"revisions --missing-from\".",
7233
+'missing' : "Try \"revisions --missing\".",
7234
+'missing-merge' : "Try \"revisions --partner-missing\".",
7235
+'new-merges' : "Try \"revisions --new-merges\".",
7236
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
7237
+'logs' : "Try \"revisions --logs\"",
7238
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
7239
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
7240
+'change-version' : "Try \"update REVISION\"",
7241
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
7242
+'rev-depends' : "Use revisions --dependencies",
7243
+'auto-get' : "Plain get will do archive lookups",
7244
+'tagline' : "Use add-id.  It uses taglines in tagline trees",
7245
+'emlog' : "Use elog.  It automatically adds log-for-merge text, if any",
7246
+'library-revisions' : "Use revisions --library",
7247
+'file-revert' : "Use revert FILE"
7248
+}
7249
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
7250
7251
*** modified file 'bzrlib/branch.py'
7252
--- bzrlib/branch.py 
7253
+++ bzrlib/branch.py 
7254
@@ -31,6 +31,8 @@
7255
 from revision import Revision
7256
 from errors import bailout, BzrError
7257
 from textui import show_status
7258
+import patches
7259
+from bzrlib import progress
7260
 
7261
 BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
7262
 ## TODO: Maybe include checks for common corruption of newlines, etc?
7263
@@ -802,3 +804,36 @@
7264
 
7265
     s = hexlify(rand_bytes(8))
7266
     return '-'.join((name, compact_date(time.time()), s))
7267
+
7268
+
7269
+def iter_anno_data(branch, file_id):
7270
+    later_revision = branch.revno()
7271
+    q = range(branch.revno())
7272
+    q.reverse()
7273
+    later_text_id = branch.basis_tree().inventory[file_id].text_id
7274
+    i = 0
7275
+    for revno in q:
7276
+        i += 1
7277
+        cur_tree = branch.revision_tree(branch.lookup_revision(revno))
7278
+        if file_id not in cur_tree.inventory:
7279
+            text_id = None
7280
+        else:
7281
+            text_id = cur_tree.inventory[file_id].text_id
7282
+        if text_id != later_text_id:
7283
+            patch = get_patch(branch, revno, later_revision, file_id)
7284
+            yield revno, patch.iter_inserted(), patch
7285
+            later_revision = revno
7286
+            later_text_id = text_id
7287
+        yield progress.Progress("revisions", i)
7288
+
7289
+def get_patch(branch, old_revno, new_revno, file_id):
7290
+    old_tree = branch.revision_tree(branch.lookup_revision(old_revno))
7291
+    new_tree = branch.revision_tree(branch.lookup_revision(new_revno))
7292
+    if file_id in old_tree.inventory:
7293
+        old_file = old_tree.get_file(file_id).readlines()
7294
+    else:
7295
+        old_file = []
7296
+    ud = difflib.unified_diff(old_file, new_tree.get_file(file_id).readlines())
7297
+    return patches.parse_patch(ud)
7298
+
7299
+
7300
7301
*** modified file 'bzrlib/commands.py'
7302
--- bzrlib/commands.py 
7303
+++ bzrlib/commands.py 
7304
@@ -27,6 +27,9 @@
7305
 from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
7306
      format_date
7307
 from bzrlib import merge
7308
+from bzrlib.branch import iter_anno_data
7309
+from bzrlib import patches
7310
+from bzrlib import progress
7311
 
7312
 
7313
 def _squish_command_name(cmd):
7314
@@ -882,7 +885,15 @@
7315
                 print '%3d FAILED!' % mf
7316
             else:
7317
                 print
7318
-
7319
+        result = bzrlib.patches.test()
7320
+        resultFailed = len(result.errors) + len(result.failures)
7321
+        print '%-40s %3d tests' % ('bzrlib.patches', result.testsRun),
7322
+        if resultFailed:
7323
+            print '%3d FAILED!' % resultFailed
7324
+        else:
7325
+            print
7326
+        tests += result.testsRun
7327
+        failures += resultFailed
7328
         print '%-40s %3d tests' % ('total', tests),
7329
         if failures:
7330
             print '%3d FAILED!' % failures
7331
@@ -897,6 +908,27 @@
7332
     """Show version of bzr"""
7333
     def run(self):
7334
         show_version()
7335
+
7336
+class cmd_annotate(Command):
7337
+    """Show which revision added each line in a file"""
7338
+
7339
+    takes_args = ['filename']
7340
+    def run(self, filename):
7341
+        branch = (Branch(filename))
7342
+        file_id = branch.working_tree().path2id(filename)
7343
+        lines = branch.basis_tree().get_file(file_id)
7344
+        total = branch.revno()
7345
+        anno_d_iter = iter_anno_data(branch, file_id)
7346
+        for result in patches.iter_annotate_file(lines, anno_d_iter):
7347
+            if isinstance(result, progress.Progress):
7348
+                result.total = total
7349
+                progress.progress_bar(result)
7350
+            else:
7351
+                progress.clear_progress_bar()
7352
+                anno_lines = result
7353
+        for line in anno_lines:
7354
+            sys.stdout.write("%4s:%s" % (str(line.log), line.text))
7355
+
7356
 
7357
 def show_version():
7358
     print "bzr (bazaar-ng) %s" % bzrlib.__version__
7359