~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to patches/annotate3.patch

  • Committer: Martin Pool
  • Date: 2005-06-10 06:26:31 UTC
  • Revision ID: mbp@sourcefrog.net-20050610062631-9790adda7dd24c15
- add aaron's pending patch for annotate

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