~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to patches/annotate3.patch

  • Committer: Martin Pool
  • Date: 2005-07-13 00:30:30 UTC
  • Revision ID: mbp@sourcefrog.net-20050713003030-2e89871a9ce24c7b
- typo in testsweet

Show diffs side-by-side

added added

removed removed

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