/brz/remove-bazaar

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