/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to patches/annotate3.patch

  • Committer: Martin Pool
  • Date: 2005-08-24 08:59:32 UTC
  • Revision ID: mbp@sourcefrog.net-20050824085932-c61f1f1f1c930e13
- Add a simple UIFactory 

  The idea of this is to let a client of bzrlib set some 
  policy about how output is displayed.

  In this revision all that's done is that progress bars
  are constructed by a policy established by the application
  rather than being randomly constructed in the library 
  or passed down the calls.  This avoids progress bars
  popping up while running the test suite and cleans up
  some code.

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