1
*** added file 'bzrlib/patches.py'
5
+# Copyright (C) 2004, 2005 Aaron Bentley
6
+# <aaron.bentley@utoronto.ca>
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.
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.
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
23
+class PatchSyntax(Exception):
24
+ def __init__(self, msg):
25
+ Exception.__init__(self, msg)
28
+class MalformedPatchHeader(PatchSyntax):
29
+ def __init__(self, desc, line):
32
+ msg = "Malformed patch header. %s\n%s" % (self.desc, self.line)
33
+ PatchSyntax.__init__(self, msg)
35
+class MalformedHunkHeader(PatchSyntax):
36
+ def __init__(self, desc, line):
39
+ msg = "Malformed hunk header. %s\n%s" % (self.desc, self.line)
40
+ PatchSyntax.__init__(self, msg)
42
+class MalformedLine(PatchSyntax):
43
+ def __init__(self, desc, line):
46
+ msg = "Malformed line. %s\n%s" % (self.desc, self.line)
47
+ PatchSyntax.__init__(self, msg)
49
+def get_patch_names(iter_lines):
51
+ line = iter_lines.next()
52
+ if not line.startswith("--- "):
53
+ raise MalformedPatchHeader("No orig name", line)
55
+ orig_name = line[4:].rstrip("\n")
56
+ except StopIteration:
57
+ raise MalformedPatchHeader("No orig line", "")
59
+ line = iter_lines.next()
60
+ if not line.startswith("+++ "):
61
+ raise PatchSyntax("No mod name")
63
+ mod_name = line[4:].rstrip("\n")
64
+ except StopIteration:
65
+ raise MalformedPatchHeader("No mod line", "")
66
+ return (orig_name, mod_name)
68
+def parse_range(textrange):
69
+ """Parse a patch range, handling the "1" special-case
71
+ :param textrange: The text to parse
72
+ :type textrange: str
73
+ :return: the position and range, as a tuple
76
+ tmp = textrange.split(',')
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)
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)
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)
108
+ def __init__(self, contents):
109
+ self.contents = contents
111
+ def get_str(self, leadchar):
112
+ if self.contents == "\n" and leadchar == " " and False:
114
+ return leadchar + self.contents
116
+class ContextLine(HunkLine):
117
+ def __init__(self, contents):
118
+ HunkLine.__init__(self, contents)
121
+ return self.get_str(" ")
124
+class InsertLine(HunkLine):
125
+ def __init__(self, contents):
126
+ HunkLine.__init__(self, contents)
129
+ return self.get_str("+")
132
+class RemoveLine(HunkLine):
133
+ def __init__(self, contents):
134
+ HunkLine.__init__(self, contents)
137
+ return self.get_str("-")
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:])
150
+ raise MalformedLine("Unknown line type", line)
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
162
+ def get_header(self):
163
+ return "@@ -%s +%s @@\n" % (self.range_str(self.orig_pos,
165
+ self.range_str(self.mod_pos,
168
+ def range_str(self, pos, range):
169
+ """Return a file range, special-casing for 1-line files.
171
+ :param pos: The position in the file
173
+ :range: The range in the file
175
+ :return: a string in the format 1,4 except when range == pos == 1
180
+ return "%i,%i" % (pos, range)
183
+ lines = [self.get_header()]
184
+ for line in self.lines:
185
+ lines.append(str(line))
186
+ return "".join(lines)
188
+ def shift_to_mod(self, pos):
189
+ if pos < self.orig_pos-1:
191
+ elif pos > self.orig_pos+self.orig_range:
192
+ return self.mod_range - self.orig_range
194
+ return self.shift_to_mod_lines(pos)
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
200
+ for line in self.lines:
201
+ if isinstance(line, InsertLine):
203
+ elif isinstance(line, RemoveLine):
204
+ if position == pos:
208
+ elif isinstance(line, ContextLine):
214
+def iter_hunks(iter_lines):
216
+ for line in iter_lines:
217
+ if line.startswith("@@"):
218
+ if hunk is not None:
220
+ hunk = hunk_from_header(line)
222
+ hunk.lines.append(parse_line(line))
224
+ if hunk is not None:
228
+ def __init__(self, oldname, newname):
229
+ self.oldname = oldname
230
+ self.newname = newname
234
+ ret = "--- %s\n+++ %s\n" % (self.oldname, self.newname)
235
+ ret += "".join([str(h) for h in self.hunks])
238
+ def stats_str(self):
239
+ """Return a string of patch statistics"""
242
+ for hunk in self.hunks:
243
+ for line in hunk.lines:
244
+ if isinstance(line, InsertLine):
246
+ elif isinstance(line, RemoveLine):
248
+ return "%i inserts, %i removes in %i hunks" % \
249
+ (inserts, removes, len(self.hunks))
251
+ def pos_in_mod(self, position):
253
+ for hunk in self.hunks:
254
+ shift = hunk.shift_to_mod(position)
260
+ def iter_inserted(self):
261
+ """Iteraties through inserted lines
263
+ :return: Pair of line number, line
264
+ :rtype: iterator of (int, InsertLine)
266
+ for hunk in self.hunks:
267
+ pos = hunk.mod_pos - 1;
268
+ for line in hunk.lines:
269
+ if isinstance(line, InsertLine):
272
+ if isinstance(line, ContextLine):
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)
284
+ """A line associated with the log that produced it"""
285
+ def __init__(self, text, log=None):
289
+class CantGetRevisionData(Exception):
290
+ def __init__(self, revision):
291
+ Exception.__init__(self, "Can't get data for revision %s" % revision)
293
+def annotate_file2(file_lines, anno_iter):
294
+ for result in iter_annotate_file(file_lines, anno_iter):
299
+def iter_annotate_file(file_lines, anno_iter):
300
+ lines = [AnnotateLine(f) for f in file_lines]
303
+ for result in anno_iter:
304
+ if isinstance(result, progress.Progress):
307
+ log, iter_inserted, patch = result
308
+ for (num, line) in iter_inserted:
311
+ for cur_patch in patches:
312
+ num = cur_patch.pos_in_mod(num)
316
+ if num >= len(lines):
318
+ if num is not None and lines[num].log is None:
319
+ lines[num].log = log
320
+ patches=[patch]+patches
321
+ except CantGetRevisionData:
326
+def difference_index(atext, btext):
327
+ """Find the indext of the first character that differs betweeen two texts
329
+ :param atext: The first text
331
+ :param btext: The second text
333
+ :return: The index, or None if there are no differences within the range
334
+ :rtype: int or NoneType
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]:
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")
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,
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)
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)
381
+ def makeMalformed(self, header):
382
+ self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
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")
396
+ def lineThing(self,text, type):
397
+ line = parse_line(text)
398
+ assert(isinstance(line, type))
399
+ assert(str(line)==text)
401
+ def makeMalformedLine(self, text):
402
+ self.assertRaises(MalformedLine, parse_line, text)
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)
410
+ def testMalformedLine(self):
411
+ """Parse invalid valid hunk lines"""
412
+ self.makeMalformedLine("hello\n")
414
+ def compare_parsed(self, patchtext):
415
+ lines = patchtext.splitlines(True)
416
+ patch = parse_patch(lines.__iter__())
418
+ i = difference_index(patchtext, pstr)
420
+ print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
421
+ assert (patchtext == str(patch))
424
+ """Test parsing a whole patch"""
425
+ patchtext = """--- orig/commands.py
427
+@@ -1337,7 +1337,8 @@
429
+ def set_title(self, command=None):
431
+- version = self.tree.tree_version.nonarch
432
++ version = pylon.alias_or_version(self.tree.tree_version, self.tree,
435
+ version = "[no version]"
436
+ if command is None:
437
+@@ -1983,7 +1984,11 @@
439
+ if len(new_merges) > 0:
440
+ if cmdutil.prompt("Log for merge"):
441
+- mergestuff = cmdutil.log_for_merge(tree, comp_version)
442
++ if cmdutil.prompt("changelog for merge"):
443
++ mergestuff = "Patches applied:\\n"
444
++ mergestuff += pylon.changelog_for_merge(new_merges)
446
++ mergestuff = cmdutil.log_for_merge(tree, comp_version)
447
+ log.description += mergestuff
451
+ self.compare_parsed(patchtext)
453
+ def testInit(self):
454
+ """Handle patches missing half the position, range tuple"""
456
+"""--- orig/__init__.py
459
+ __docformat__ = "restructuredtext en"
460
++__doc__ = An alternate Arch commandline interface"""
461
+ self.compare_parsed(patchtext)
465
+ def testLineLookup(self):
466
+ """Make sure we can accurately look up mod line from orig"""
467
+ patch = parse_patch(open("testdata/diff"))
468
+ orig = list(open("testdata/orig"))
469
+ mod = list(open("testdata/mod"))
471
+ for i in range(len(orig)):
472
+ mod_pos = patch.pos_in_mod(i)
473
+ if mod_pos is None:
474
+ removals.append(orig[i])
476
+ assert(mod[mod_pos]==orig[i])
477
+ rem_iter = removals.__iter__()
478
+ for hunk in patch.hunks:
479
+ for line in hunk.lines:
480
+ if isinstance(line, RemoveLine):
481
+ next = rem_iter.next()
482
+ if line.contents != next:
483
+ sys.stdout.write(" orig:%spatch:%s" % (next,
485
+ assert(line.contents == next)
486
+ self.assertRaises(StopIteration, rem_iter.next)
488
+ def testFirstLineRenumber(self):
489
+ """Make sure we handle lines at the beginning of the hunk"""
490
+ patch = parse_patch(open("testdata/insert_top.patch"))
491
+ assert (patch.pos_in_mod(0)==1)
494
+ patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
495
+ runner = unittest.TextTestRunner(verbosity=0)
496
+ return runner.run(patchesTestSuite)
499
+if __name__ == "__main__":
501
+# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683
503
*** added directory 'testdata'
504
*** added file 'testdata/diff'
508
+--- orig/commands.py
515
++import pylon.errors
516
++from pylon.errors import *
517
++from pylon import errors
518
++from pylon import util
519
++from pylon import arch_core
520
++from pylon import arch_compound
521
++from pylon import ancillary
522
++from pylon import misc
523
++from pylon import paths
537
+-from errors import *
546
+ __docformat__ = "restructuredtext"
547
+ __doc__ = "Implementation of user (sub) commands"
550
+ tree=arch.tree_root()
552
+- a_spec = cmdutil.comp_revision(tree)
553
++ a_spec = ancillary.comp_revision(tree)
555
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
556
+ cmdutil.ensure_archive_registered(a_spec.archive)
558
+ changeset=options.changeset
561
+- tmpdir=cmdutil.tmpdir()
562
++ tmpdir=util.tmpdir()
563
+ changeset=tmpdir+"/changeset"
565
+ delta=arch.iter_delta(a_spec, b_spec, changeset)
566
+@@ -304,14 +310,14 @@
569
+ if (options.perform_diff):
570
+- chan = cmdutil.ChangesetMunger(changeset)
571
++ chan = arch_compound.ChangesetMunger(changeset)
572
+ chan.read_indices()
573
+- if isinstance(b_spec, arch.Revision):
574
+- b_dir = b_spec.library_find()
577
+- a_dir = a_spec.library_find()
578
+ if options.diffopts is not None:
579
++ if isinstance(b_spec, arch.Revision):
580
++ b_dir = b_spec.library_find()
583
++ a_dir = a_spec.library_find()
584
+ diffopts = options.diffopts.split()
585
+ cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
588
+ except arch.errors.TreeRootError, e:
591
+- from_revision=cmdutil.tree_latest(tree)
592
++ from_revision = arch_compound.tree_latest(tree)
593
+ if from_revision==to_revision:
594
+ print "Tree is already up to date with:\n"+str(to_revision)+"."
600
++ if options.version is None:
601
++ return options, tree.tree_version, args
603
+ revision=cmdutil.determine_revision_arch(tree, options.version)
604
+ return options, revision.get_version(), args
606
+@@ -601,11 +610,16 @@
608
+ tree=arch.tree_root()
609
+ options, version, files = self.parse_commandline(cmdargs, tree)
611
+ if options.__dict__.has_key("base") and options.base:
612
+ base = cmdutil.determine_revision_tree(tree, options.base)
615
+- base = cmdutil.submit_revision(tree)
617
++ base = ancillary.submit_revision(tree)
619
++ if ancestor is None:
620
++ ancestor = arch_compound.tree_latest(tree, version)
622
+ writeversion=version
623
+ archive=version.archive
624
+ source=cmdutil.get_mirror_source(archive)
625
+@@ -625,18 +639,26 @@
627
+ last_revision=tree.iter_logs(version, True).next().revision
628
+ except StopIteration, e:
629
+- if cmdutil.prompt("Import from commit"):
630
+- return do_import(version)
632
+- raise NoVersionLogs(version)
633
+- if last_revision!=version.iter_revisions(True).next():
634
++ last_revision = None
635
++ if ancestor is None:
636
++ if cmdutil.prompt("Import from commit"):
637
++ return do_import(version)
639
++ raise NoVersionLogs(version)
641
++ arch_last_revision = version.iter_revisions(True).next()
642
++ except StopIteration, e:
643
++ arch_last_revision = None
645
++ if last_revision != arch_last_revision:
646
++ print "Tree is not up to date with %s" % str(version)
647
+ if not cmdutil.prompt("Out of date"):
653
+- if not cmdutil.has_changed(version):
654
++ if not cmdutil.has_changed(ancestor):
655
+ if not cmdutil.prompt("Empty commit"):
657
+ except arch.util.ExecProblem, e:
658
+@@ -645,15 +667,15 @@
662
+- log = tree.log_message(create=False)
663
++ log = tree.log_message(create=False, version=version)
666
+ if cmdutil.prompt("Create log"):
668
++ edit_log(tree, version)
670
+ except cmdutil.NoEditorSpecified, e:
671
+ raise CommandFailed(e)
672
+- log = tree.log_message(create=False)
673
++ log = tree.log_message(create=False, version=version)
676
+ if log["Summary"] is None or len(log["Summary"].strip()) == 0:
677
+@@ -837,23 +859,24 @@
678
+ if spec is not None:
679
+ revision = cmdutil.determine_revision_tree(tree, spec)
681
+- revision = cmdutil.comp_revision(tree)
682
++ revision = ancillary.comp_revision(tree)
683
+ except cmdutil.CantDetermineRevision, e:
684
+ raise CommandFailedWrapper(e)
687
+ if options.file_contents or options.file_perms or options.deletions\
688
+ or options.additions or options.renames or options.hunk_prompt:
689
+- munger = cmdutil.MungeOpts()
690
+- munger.hunk_prompt = options.hunk_prompt
691
++ munger = arch_compound.MungeOpts()
692
++ munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
693
++ options.hunk_prompt)
695
+ if len(args) > 0 or options.logs or options.pattern_files or \
698
+- munger = cmdutil.MungeOpts(True)
699
++ munger = cmdutil.arch_compound.MungeOpts(True)
700
+ munger.all_types(True)
702
+- t_cwd = cmdutil.tree_cwd(tree)
703
++ t_cwd = arch_compound.tree_cwd(tree)
708
+ if options.pattern_files:
709
+ munger.add_keep_pattern(options.pattern_files)
711
+- for line in cmdutil.revert(tree, revision, munger,
712
++ for line in arch_compound.revert(tree, revision, munger,
713
+ not options.no_output):
714
+ cmdutil.colorize(line)
716
+@@ -1042,18 +1065,13 @@
720
+-def require_version_exists(version, spec):
721
+- if not version.exists():
722
+- raise cmdutil.CantDetermineVersion(spec,
723
+- "The version %s does not exist." \
726
+ class Revisions(BaseCommand):
728
+ Print a revision name based on a revision specifier
730
+ def __init__(self):
731
+ self.description="Lists revisions"
732
++ self.cl_revisions = []
734
+ def do_command(self, cmdargs):
736
+@@ -1066,224 +1084,68 @@
737
+ self.tree = arch.tree_root()
738
+ except arch.errors.TreeRootError:
740
++ if options.type == "default":
741
++ options.type = "archive"
743
+- iter = self.get_iterator(options.type, args, options.reverse,
745
++ iter = cmdutil.revision_iterator(self.tree, options.type, args,
746
++ options.reverse, options.modified,
748
+ except cmdutil.CantDetermineRevision, e:
749
+ raise CommandFailedWrapper(e)
751
++ except cmdutil.CantDetermineVersion, e:
752
++ raise CommandFailedWrapper(e)
753
+ if options.skip is not None:
754
+ iter = cmdutil.iter_skip(iter, int(options.skip))
756
+- for revision in iter:
758
+- if isinstance(revision, arch.Patchlog):
760
+- revision=revision.revision
761
+- print options.display(revision)
762
+- if log is None and (options.summary or options.creator or
763
+- options.date or options.merges):
764
+- log = revision.patchlog
765
+- if options.creator:
766
+- print " %s" % log.creator
768
+- print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
769
+- if options.summary:
770
+- print " %s" % log.summary
771
+- if options.merges:
772
+- showed_title = False
773
+- for revision in log.merged_patches:
774
+- if not showed_title:
776
+- showed_title = True
777
+- print " %s" % revision
779
+- def get_iterator(self, type, args, reverse, modified):
784
+- if modified is not None:
785
+- iter = cmdutil.modified_iter(modified, self.tree)
789
+- return cmdutil.iter_reverse(iter)
790
+- elif type == "archive":
792
+- if self.tree is None:
793
+- raise cmdutil.CantDetermineRevision("",
794
+- "Not in a project tree")
795
+- version = cmdutil.determine_version_tree(spec, self.tree)
797
+- version = cmdutil.determine_version_arch(spec, self.tree)
798
+- cmdutil.ensure_archive_registered(version.archive)
799
+- require_version_exists(version, spec)
800
+- return version.iter_revisions(reverse)
801
+- elif type == "cacherevs":
803
+- if self.tree is None:
804
+- raise cmdutil.CantDetermineRevision("",
805
+- "Not in a project tree")
806
+- version = cmdutil.determine_version_tree(spec, self.tree)
808
+- version = cmdutil.determine_version_arch(spec, self.tree)
809
+- cmdutil.ensure_archive_registered(version.archive)
810
+- require_version_exists(version, spec)
811
+- return cmdutil.iter_cacherevs(version, reverse)
812
+- elif type == "library":
814
+- if self.tree is None:
815
+- raise cmdutil.CantDetermineRevision("",
816
+- "Not in a project tree")
817
+- version = cmdutil.determine_version_tree(spec, self.tree)
819
+- version = cmdutil.determine_version_arch(spec, self.tree)
820
+- return version.iter_library_revisions(reverse)
821
+- elif type == "logs":
822
+- if self.tree is None:
823
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
824
+- return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
825
+- self.tree), reverse)
826
+- elif type == "missing" or type == "skip-present":
827
+- if self.tree is None:
828
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
829
+- skip = (type == "skip-present")
830
+- version = cmdutil.determine_version_tree(spec, self.tree)
831
+- cmdutil.ensure_archive_registered(version.archive)
832
+- require_version_exists(version, spec)
833
+- return cmdutil.iter_missing(self.tree, version, reverse,
834
+- skip_present=skip)
836
+- elif type == "present":
837
+- if self.tree is None:
838
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
839
+- version = cmdutil.determine_version_tree(spec, self.tree)
840
+- cmdutil.ensure_archive_registered(version.archive)
841
+- require_version_exists(version, spec)
842
+- return cmdutil.iter_present(self.tree, version, reverse)
844
+- elif type == "new-merges" or type == "direct-merges":
845
+- if self.tree is None:
846
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
847
+- version = cmdutil.determine_version_tree(spec, self.tree)
848
+- cmdutil.ensure_archive_registered(version.archive)
849
+- require_version_exists(version, spec)
850
+- iter = cmdutil.iter_new_merges(self.tree, version, reverse)
851
+- if type == "new-merges":
853
+- elif type == "direct-merges":
854
+- return cmdutil.direct_merges(iter)
856
+- elif type == "missing-from":
857
+- if self.tree is None:
858
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
859
+- revision = cmdutil.determine_revision_tree(self.tree, spec)
860
+- libtree = cmdutil.find_or_make_local_revision(revision)
861
+- return cmdutil.iter_missing(libtree, self.tree.tree_version,
864
+- elif type == "partner-missing":
865
+- return cmdutil.iter_partner_missing(self.tree, reverse)
867
+- elif type == "ancestry":
868
+- revision = cmdutil.determine_revision_tree(self.tree, spec)
869
+- iter = cmdutil._iter_ancestry(self.tree, revision)
873
+- return cmdutil.iter_reverse(iter)
875
+- elif type == "dependencies" or type == "non-dependencies":
876
+- nondeps = (type == "non-dependencies")
877
+- revision = cmdutil.determine_revision_tree(self.tree, spec)
878
+- anc_iter = cmdutil._iter_ancestry(self.tree, revision)
879
+- iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
881
+- return iter_depends
883
+- return cmdutil.iter_reverse(iter_depends)
884
+- elif type == "micro":
885
+- return cmdutil.iter_micro(self.tree)
889
++ for revision in iter:
891
++ if isinstance(revision, arch.Patchlog):
893
++ revision=revision.revision
894
++ out = options.display(revision)
895
++ if out is not None:
897
++ if log is None and (options.summary or options.creator or
898
++ options.date or options.merges):
899
++ log = revision.patchlog
900
++ if options.creator:
901
++ print " %s" % log.creator
903
++ print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
904
++ if options.summary:
905
++ print " %s" % log.summary
906
++ if options.merges:
907
++ showed_title = False
908
++ for revision in log.merged_patches:
909
++ if not showed_title:
911
++ showed_title = True
912
++ print " %s" % revision
913
++ if len(self.cl_revisions) > 0:
914
++ print pylon.changelog_for_merge(self.cl_revisions)
915
++ except pylon.errors.TreeRootNone:
916
++ raise CommandFailedWrapper(
917
++ Exception("This option can only be used in a project tree."))
919
++ def changelog_append(self, revision):
920
++ if isinstance(revision, arch.Revision):
921
++ revision=arch.Patchlog(revision)
922
++ self.cl_revisions.append(revision)
924
+ def get_parser(self):
926
+ Returns the options parser to use for the "revision" command.
928
+ :rtype: cmdutil.CmdOptionParser
930
+- parser=cmdutil.CmdOptionParser("fai revisions [revision]")
931
++ parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
932
+ select = cmdutil.OptionGroup(parser, "Selection options",
933
+ "Control which revisions are listed. These options"
934
+ " are mutually exclusive. If more than one is"
935
+ " specified, the last is used.")
936
+- select.add_option("", "--archive", action="store_const",
937
+- const="archive", dest="type", default="archive",
938
+- help="List all revisions in the archive")
939
+- select.add_option("", "--cacherevs", action="store_const",
940
+- const="cacherevs", dest="type",
941
+- help="List all revisions stored in the archive as "
942
+- "complete copies")
943
+- select.add_option("", "--logs", action="store_const",
944
+- const="logs", dest="type",
945
+- help="List revisions that have a patchlog in the "
947
+- select.add_option("", "--missing", action="store_const",
948
+- const="missing", dest="type",
949
+- help="List revisions from the specified version that"
950
+- " have no patchlog in the tree")
951
+- select.add_option("", "--skip-present", action="store_const",
952
+- const="skip-present", dest="type",
953
+- help="List revisions from the specified version that"
954
+- " have no patchlogs at all in the tree")
955
+- select.add_option("", "--present", action="store_const",
956
+- const="present", dest="type",
957
+- help="List revisions from the specified version that"
958
+- " have no patchlog in the tree, but can't be merged")
959
+- select.add_option("", "--missing-from", action="store_const",
960
+- const="missing-from", dest="type",
961
+- help="List revisions from the specified revision "
962
+- "that have no patchlog for the tree version")
963
+- select.add_option("", "--partner-missing", action="store_const",
964
+- const="partner-missing", dest="type",
965
+- help="List revisions in partner versions that are"
967
+- select.add_option("", "--new-merges", action="store_const",
968
+- const="new-merges", dest="type",
969
+- help="List revisions that have had patchlogs added"
970
+- " to the tree since the last commit")
971
+- select.add_option("", "--direct-merges", action="store_const",
972
+- const="direct-merges", dest="type",
973
+- help="List revisions that have been directly added"
974
+- " to tree since the last commit ")
975
+- select.add_option("", "--library", action="store_const",
976
+- const="library", dest="type",
977
+- help="List revisions in the revision library")
978
+- select.add_option("", "--ancestry", action="store_const",
979
+- const="ancestry", dest="type",
980
+- help="List revisions that are ancestors of the "
981
+- "current tree version")
983
+- select.add_option("", "--dependencies", action="store_const",
984
+- const="dependencies", dest="type",
985
+- help="List revisions that the given revision "
988
+- select.add_option("", "--non-dependencies", action="store_const",
989
+- const="non-dependencies", dest="type",
990
+- help="List revisions that the given revision "
991
+- "does not depend on")
993
+- select.add_option("--micro", action="store_const",
994
+- const="micro", dest="type",
995
+- help="List partner revisions aimed for this "
998
+- select.add_option("", "--modified", dest="modified",
999
+- help="List tree ancestor revisions that modified a "
1000
+- "given file", metavar="FILE[:LINE]")
1002
++ cmdutil.add_revision_iter_options(select)
1003
+ parser.add_option("", "--skip", dest="skip",
1004
+ help="Skip revisions. Positive numbers skip from "
1005
+ "beginning, negative skip from end.",
1006
+@@ -1312,6 +1174,9 @@
1007
+ format.add_option("--cacherev", action="store_const",
1008
+ const=paths.determine_cacherev_path, dest="display",
1009
+ help="Show location of cacherev file")
1010
++ format.add_option("--changelog", action="store_const",
1011
++ const=self.changelog_append, 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
++ self.cwd = os.getcwd()
1022
+ def write_history(self):
1023
+ readline.write_history_file(self.history_file)
1024
+@@ -1470,16 +1336,21 @@
1025
+ def set_prompt(self):
1026
+ if self.tree is not None:
1028
+- version = " "+self.tree.tree_version.nonarch
1029
++ prompt = pylon.alias_or_version(self.tree.tree_version,
1032
++ if prompt is not None:
1033
++ prompt = " " + prompt
1039
+- self.prompt = "Fai%s> " % version
1041
++ self.prompt = "Fai%s> " % prompt
1043
+ def set_title(self, command=None):
1045
+- version = self.tree.tree_version.nonarch
1046
++ version = pylon.alias_or_version(self.tree.tree_version, self.tree,
1049
+ version = "[no version]"
1050
+ if command is None:
1051
+@@ -1489,8 +1360,15 @@
1052
+ def do_cd(self, line):
1055
++ line = os.path.expanduser(line)
1056
++ if os.path.isabs(line):
1059
++ newcwd = self.cwd+'/'+line
1060
++ newcwd = os.path.normpath(newcwd)
1062
+- os.chdir(os.path.expanduser(line))
1064
++ self.cwd = newcwd
1065
+ except Exception, e:
1068
+@@ -1523,7 +1401,7 @@
1069
+ except cmdutil.CantDetermineRevision, e:
1071
+ except Exception, e:
1072
+- print "Unhandled error:\n%s" % cmdutil.exception_str(e)
1073
++ print "Unhandled error:\n%s" % errors.exception_str(e)
1075
+ elif suggestions.has_key(args[0]):
1076
+ print suggestions[args[0]]
1077
+@@ -1574,7 +1452,7 @@
1078
+ arg = line.split()[-1]
1081
+- iter = iter_munged_completions(iter, arg, text)
1082
++ iter = cmdutil.iter_munged_completions(iter, arg, text)
1083
+ except Exception, e:
1086
+@@ -1604,10 +1482,11 @@
1089
+ if arg.startswith("-"):
1090
+- return list(iter_munged_completions(iter, arg, text))
1091
++ return list(cmdutil.iter_munged_completions(iter, arg,
1094
+- return list(iter_munged_completions(
1095
+- iter_file_completions(arg), arg, text))
1096
++ return list(cmdutil.iter_munged_completions(
1097
++ cmdutil.iter_file_completions(arg), arg, text))
1101
+@@ -1615,13 +1494,13 @@
1102
+ arg = args.split()[-1]
1105
+- iter = iter_dir_completions(arg)
1106
+- iter = iter_munged_completions(iter, arg, text)
1107
++ iter = cmdutil.iter_dir_completions(arg)
1108
++ iter = cmdutil.iter_munged_completions(iter, arg, text)
1111
+ arg = args.split()[-1]
1112
+- return list(iter_munged_completions(iter_file_completions(arg),
1114
++ iter = cmdutil.iter_file_completions(arg)
1115
++ return list(cmdutil.iter_munged_completions(iter, arg, text))
1117
+ return self.completenames(text, line, begidx, endidx)
1118
+ except Exception, e:
1119
+@@ -1636,44 +1515,8 @@
1123
+-def iter_file_completions(arg, only_dirs = False):
1124
+- """Generate an iterator that iterates through filename completions.
1126
+- :param arg: The filename fragment to match
1128
+- :param only_dirs: If true, match only directories
1129
+- :type only_dirs: bool
1131
+- cwd = os.getcwd()
1133
+- extras = [".", ".."]
1136
+- (dir, file) = os.path.split(arg)
1138
+- listingdir = os.path.expanduser(dir)
1141
+- for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
1143
+- userfile = dir+'/'+file
1146
+- if userfile.startswith(arg):
1147
+- if os.path.isdir(listingdir+'/'+file):
1150
+- elif not only_dirs:
1153
+-def iter_munged_completions(iter, arg, text):
1154
+- for completion in iter:
1155
+- completion = str(completion)
1156
+- if completion.startswith(arg):
1157
+- yield completion[len(arg)-len(text):]
1159
+ def iter_source_file_completions(tree, arg):
1160
+- treepath = cmdutil.tree_cwd(tree)
1161
++ treepath = arch_compound.tree_cwd(tree)
1162
+ if len(treepath) > 0:
1165
+@@ -1701,7 +1544,7 @@
1166
+ :return: An iterator of all matching untagged files
1167
+ :rtype: iterator of str
1169
+- treepath = cmdutil.tree_cwd(tree)
1170
++ treepath = arch_compound.tree_cwd(tree)
1171
+ if len(treepath) > 0:
1174
+@@ -1743,8 +1586,8 @@
1175
+ :param arg: The prefix to match
1178
+- treepath = cmdutil.tree_cwd(tree)
1179
+- tmpdir = cmdutil.tmpdir()
1180
++ treepath = arch_compound.tree_cwd(tree)
1181
++ tmpdir = util.tmpdir()
1182
+ changeset = tmpdir+"/changeset"
1184
+ revision = cmdutil.determine_revision_tree(tree)
1185
+@@ -1756,14 +1599,6 @@
1186
+ shutil.rmtree(tmpdir)
1187
+ return completions
1189
+-def iter_dir_completions(arg):
1190
+- """Generate an iterator that iterates through directory name completions.
1192
+- :param arg: The directory name fragment to match
1195
+- return iter_file_completions(arg, True)
1197
+ class Shell(BaseCommand):
1198
+ def __init__(self):
1199
+ self.description = "Runs Fai as a shell"
1200
+@@ -1795,7 +1630,11 @@
1201
+ parser=self.get_parser()
1202
+ (options, args) = parser.parse_args(cmdargs)
1204
+- tree = arch.tree_root()
1206
++ tree = arch.tree_root()
1207
++ except arch.errors.TreeRootError, e:
1208
++ raise pylon.errors.CommandFailedWrapper(e)
1211
+ if (len(args) == 0) == (options.untagged == False):
1212
+ raise cmdutil.GetHelp
1213
+@@ -1809,13 +1648,22 @@
1214
+ if options.id_type == "tagline":
1215
+ if method != "tagline":
1216
+ if not cmdutil.prompt("Tagline in other tree"):
1217
+- if method == "explicit":
1218
+- options.id_type == explicit
1219
++ if method == "explicit" or method == "implicit":
1220
++ options.id_type == method
1222
+ print "add-id not supported for \"%s\" tagging method"\
1226
++ elif options.id_type == "implicit":
1227
++ if method != "implicit":
1228
++ if not cmdutil.prompt("Implicit in other tree"):
1229
++ if method == "explicit" or method == "tagline":
1230
++ options.id_type == method
1232
++ print "add-id not supported for \"%s\" tagging method"\
1235
+ elif options.id_type == "explicit":
1236
+ if method != "tagline" and method != explicit:
1237
+ if not prompt("Explicit in other tree"):
1238
+@@ -1824,7 +1672,8 @@
1241
+ if options.id_type == "auto":
1242
+- if method != "tagline" and method != "explicit":
1243
++ if method != "tagline" and method != "explicit" \
1244
++ and method !="implicit":
1245
+ print "add-id not supported for \"%s\" tagging method" % method
1248
+@@ -1852,10 +1701,12 @@
1249
+ previous_files.extend(files)
1250
+ if id_type == "explicit":
1251
+ cmdutil.add_id(files)
1252
+- elif id_type == "tagline":
1253
++ elif id_type == "tagline" or id_type == "implicit":
1254
+ for file in files:
1256
+- cmdutil.add_tagline_or_explicit_id(file)
1257
++ implicit = (id_type == "implicit")
1258
++ cmdutil.add_tagline_or_explicit_id(file, False,
1260
+ except cmdutil.AlreadyTagged:
1261
+ print "\"%s\" already has a tagline." % file
1262
+ except cmdutil.NoCommentSyntax:
1263
+@@ -1888,6 +1739,9 @@
1264
+ parser.add_option("--tagline", action="store_const",
1265
+ const="tagline", dest="id_type",
1266
+ help="Use a tagline id")
1267
++ parser.add_option("--implicit", action="store_const",
1268
++ const="implicit", dest="id_type",
1269
++ help="Use an implicit id (deprecated)")
1270
+ parser.add_option("--untagged", action="store_true",
1271
+ dest="untagged", default=False,
1272
+ help="tag all untagged files")
1273
+@@ -1926,27 +1780,7 @@
1274
+ def get_completer(self, arg, index):
1275
+ if self.tree is None:
1276
+ raise arch.errors.TreeRootError
1277
+- completions = list(ancillary.iter_partners(self.tree,
1278
+- self.tree.tree_version))
1279
+- if len(completions) == 0:
1280
+- completions = list(self.tree.iter_log_versions())
1284
+- for completion in completions:
1285
+- alias = ancillary.compact_alias(str(completion), self.tree)
1287
+- aliases.extend(alias)
1289
+- for completion in completions:
1290
+- if completion.archive == self.tree.tree_version.archive:
1291
+- aliases.append(completion.nonarch)
1293
+- except Exception, e:
1296
+- completions.extend(aliases)
1297
+- return completions
1298
++ return cmdutil.merge_completions(self.tree, arg, index)
1300
+ def do_command(self, cmdargs):
1302
+@@ -1961,7 +1795,7 @@
1304
+ if self.tree is None:
1305
+ raise arch.errors.TreeRootError(os.getcwd())
1306
+- if cmdutil.has_changed(self.tree.tree_version):
1307
++ if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
1308
+ raise UncommittedChanges(self.tree)
1311
+@@ -2027,14 +1861,14 @@
1312
+ :type other_revision: `arch.Revision`
1313
+ :return: 0 if the merge was skipped, 1 if it was applied
1315
+- other_tree = cmdutil.find_or_make_local_revision(other_revision)
1316
++ other_tree = arch_compound.find_or_make_local_revision(other_revision)
1318
+ if action == "native-merge":
1319
+- ancestor = cmdutil.merge_ancestor2(self.tree, other_tree,
1321
++ ancestor = arch_compound.merge_ancestor2(self.tree, other_tree,
1323
+ elif action == "update":
1324
+- ancestor = cmdutil.tree_latest(self.tree,
1325
+- other_revision.version)
1326
++ ancestor = arch_compound.tree_latest(self.tree,
1327
++ other_revision.version)
1328
+ except CantDetermineRevision, e:
1329
+ raise CommandFailedWrapper(e)
1330
+ cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
1331
+@@ -2104,7 +1938,10 @@
1332
+ if self.tree is None:
1333
+ raise arch.errors.TreeRootError
1335
+- edit_log(self.tree)
1337
++ edit_log(self.tree, self.tree.tree_version)
1338
++ except pylon.errors.NoEditorSpecified, e:
1339
++ raise pylon.errors.CommandFailedWrapper(e)
1341
+ def get_parser(self):
1343
+@@ -2132,7 +1969,7 @@
1347
+-def edit_log(tree):
1348
++def edit_log(tree, version):
1349
+ """Makes and edits the log for a tree. Does all kinds of fancy things
1350
+ like log templates and merge summaries and log-for-merge
1352
+@@ -2141,28 +1978,29 @@
1354
+ #ensure we have an editor before preparing the log
1355
+ cmdutil.find_editor()
1356
+- log = tree.log_message(create=False)
1357
++ log = tree.log_message(create=False, version=version)
1358
+ log_is_new = False
1359
+ if log is None or cmdutil.prompt("Overwrite log"):
1360
+ if log is not None:
1361
+ os.remove(log.name)
1362
+- log = tree.log_message(create=True)
1363
++ log = tree.log_message(create=True, version=version)
1366
+- template = tree+"/{arch}/=log-template"
1367
+- if not os.path.exists(template):
1368
+- template = os.path.expanduser("~/.arch-params/=log-template")
1369
+- if not os.path.exists(template):
1371
++ template = pylon.log_template_path(tree)
1373
+ shutil.copyfile(template, tmplog)
1375
+- new_merges = list(cmdutil.iter_new_merges(tree,
1376
+- tree.tree_version))
1377
+- log["Summary"] = merge_summary(new_merges, tree.tree_version)
1378
++ comp_version = ancillary.comp_revision(tree).version
1379
++ new_merges = cmdutil.iter_new_merges(tree, comp_version)
1380
++ new_merges = cmdutil.direct_merges(new_merges)
1381
++ log["Summary"] = pylon.merge_summary(new_merges,
1383
+ if len(new_merges) > 0:
1384
+ if cmdutil.prompt("Log for merge"):
1385
+- mergestuff = cmdutil.log_for_merge(tree)
1386
++ if cmdutil.prompt("changelog for merge"):
1387
++ mergestuff = "Patches applied:\n"
1388
++ mergestuff += pylon.changelog_for_merge(new_merges)
1390
++ mergestuff = cmdutil.log_for_merge(tree, comp_version)
1391
+ log.description += mergestuff
1394
+@@ -2172,29 +2010,6 @@
1395
+ os.remove(log.name)
1398
+-def merge_summary(new_merges, tree_version):
1399
+- if len(new_merges) == 0:
1401
+- if len(new_merges) == 1:
1402
+- summary = new_merges[0].summary
1404
+- summary = "Merge"
1407
+- for merge in new_merges:
1408
+- if arch.my_id() != merge.creator:
1409
+- name = re.sub("<.*>", "", merge.creator).rstrip(" ");
1410
+- if not name in credits:
1411
+- credits.append(name)
1413
+- version = merge.revision.version
1414
+- if version.archive == tree_version.archive:
1415
+- if not version.nonarch in credits:
1416
+- credits.append(version.nonarch)
1417
+- elif not str(version) in credits:
1418
+- credits.append(str(version))
1420
+- return ("%s (%s)") % (summary, ", ".join(credits))
1422
+ class MirrorArchive(BaseCommand):
1424
+@@ -2268,31 +2083,73 @@
1426
+ Use "alias" to list available (user and automatic) aliases."""
1430
++"The latest revision in the archive of the tree-version. You can specify \
1431
++a different version like so: acur:foo--bar--0 (aliases can be used)",
1433
++"""(tree current) The latest revision in the tree of the tree-version. \
1434
++You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
1437
++"""(tree previous) The previous revision in the tree of the tree-version. To \
1438
++specify an older revision, use a number, e.g. "tprev:4" """,
1440
++"""(tree ancestor) The ancestor revision of the tree To specify an older \
1441
++revision, use a number, e.g. "tanc:4".""",
1443
++"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
1445
++""" (tree modified) The latest revision to modify a given file, e.g. \
1446
++"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
1448
++"""(tree tag) The revision that was tagged into the current tree revision, \
1449
++according to the tree""",
1451
++"""(tag current) The latest revision of the version that the current tree \
1452
++was tagged from.""",
1454
++"""The common ancestor of the current tree and the specified revision. \
1455
++Defaults to the first partner-version's latest revision or to tagcur.""",
1459
++def is_auto_alias(name):
1460
++ """Determine whether a name is an auto alias name
1462
++ :param name: the name to check
1464
++ :return: True if the name is an auto alias, false if not
1467
++ return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
1470
++def display_def(iter, wrap = 80):
1471
++ """Display a list of definitions
1473
++ :param iter: iter of name, definition pairs
1474
++ :type iter: iter of (str, str)
1475
++ :param wrap: The width for text wrapping
1478
++ vals = list(iter)
1480
++ for (key, value) in vals:
1481
++ if len(key) > maxlen:
1482
++ maxlen = len(key)
1483
++ for (key, value) in vals:
1484
++ tw=textwrap.TextWrapper(width=wrap,
1485
++ initial_indent=key.rjust(maxlen)+" : ",
1486
++ subsequent_indent="".rjust(maxlen+3))
1487
++ print tw.fill(value)
1490
+ def help_aliases(tree):
1491
+- print """Auto-generated aliases
1492
+- acur : The latest revision in the archive of the tree-version. You can specfy
1493
+- a different version like so: acur:foo--bar--0 (aliases can be used)
1494
+- tcur : (tree current) The latest revision in the tree of the tree-version.
1495
+- You can specify a different version like so: tcur:foo--bar--0 (aliases
1497
+-tprev : (tree previous) The previous revision in the tree of the tree-version.
1498
+- To specify an older revision, use a number, e.g. "tprev:4"
1499
+- tanc : (tree ancestor) The ancestor revision of the tree
1500
+- To specify an older revision, use a number, e.g. "tanc:4"
1501
+-tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
1502
+- tmod : (tree modified) The latest revision to modify a given file
1503
+- (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
1504
+- ttag : (tree tag) The revision that was tagged into the current tree revision,
1505
+- according to the tree.
1506
+-tagcur: (tag current) The latest revision of the version that the current tree
1508
+-mergeanc : The common ancestor of the current tree and the specified revision.
1509
+- Defaults to the first partner-version's latest revision or to tagcur.
1511
++ print """Auto-generated aliases"""
1512
++ display_def(pylon.util.iter_pairs(auto_alias))
1513
+ print "User aliases"
1514
+- for parts in ancillary.iter_all_alias(tree):
1515
+- print parts[0].rjust(10)+" : "+parts[1]
1517
++ display_def(ancillary.iter_all_alias(tree))
1519
+ class Inventory(BaseCommand):
1520
+ """List the status of files in the tree"""
1521
+@@ -2428,6 +2285,11 @@
1522
+ except cmdutil.ForbiddenAliasSyntax, e:
1523
+ raise CommandFailedWrapper(e)
1525
++ def no_prefix(self, alias):
1526
++ if alias.startswith("^"):
1527
++ alias = alias[1:]
1530
+ def arg_dispatch(self, args, options):
1531
+ """Add, modify, or list aliases, depending on number of arguments
1533
+@@ -2438,15 +2300,20 @@
1534
+ if len(args) == 0:
1535
+ help_aliases(self.tree)
1537
+- elif len(args) == 1:
1538
+- self.print_alias(args[0])
1539
+- elif (len(args)) == 2:
1540
+- self.add(args[0], args[1], options)
1542
+- raise cmdutil.GetHelp
1543
++ alias = self.no_prefix(args[0])
1544
++ if len(args) == 1:
1545
++ self.print_alias(alias)
1546
++ elif (len(args)) == 2:
1547
++ self.add(alias, args[1], options)
1549
++ raise cmdutil.GetHelp
1551
+ def print_alias(self, alias):
1553
++ if is_auto_alias(alias):
1554
++ raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
1555
++ " Use \"revision\" to expand auto aliases." % alias)
1556
+ for pair in ancillary.iter_all_alias(self.tree):
1557
+ if pair[0] == alias:
1559
+@@ -2464,6 +2331,8 @@
1560
+ :type expansion: str
1561
+ :param options: The commandline options
1563
++ if is_auto_alias(alias):
1564
++ raise IsAutoAlias(alias)
1567
+ new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
1568
+@@ -2490,14 +2359,17 @@
1570
+ if len(args) != 1:
1571
+ raise cmdutil.GetHelp
1572
++ alias = self.no_prefix(args[0])
1573
++ if is_auto_alias(alias):
1574
++ raise IsAutoAlias(alias)
1576
+ for pair in self.get_iterator(options):
1577
+- if pair[0] != args[0]:
1578
++ if pair[0] != alias:
1579
+ newlist+="%s=%s\n" % (pair[0], pair[1])
1583
+- raise errors.NoSuchAlias(args[0])
1584
++ raise errors.NoSuchAlias(alias)
1585
+ self.write_aliases(newlist, options)
1587
+ def get_alias_file(self, options):
1588
+@@ -2526,7 +2398,7 @@
1589
+ :param options: The commandline options
1591
+ filename = os.path.expanduser(self.get_alias_file(options))
1592
+- file = cmdutil.NewFileVersion(filename)
1593
++ file = util.NewFileVersion(filename)
1594
+ file.write(newlist)
1597
+@@ -2588,10 +2460,13 @@
1598
+ :param cmdargs: The commandline arguments
1599
+ :type cmdargs: list of str
1601
+- cmdutil.find_editor()
1602
+ parser = self.get_parser()
1603
+ (options, args) = parser.parse_args(cmdargs)
1605
++ cmdutil.find_editor()
1606
++ except pylon.errors.NoEditorSpecified, e:
1607
++ raise pylon.errors.CommandFailedWrapper(e)
1609
+ self.tree=arch.tree_root()
1612
+@@ -2655,7 +2530,7 @@
1613
+ target_revision = cmdutil.determine_revision_arch(self.tree,
1616
+- target_revision = cmdutil.tree_latest(self.tree)
1617
++ target_revision = arch_compound.tree_latest(self.tree)
1619
+ merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
1620
+ self.tree, f)) for f in args[1:] ]
1621
+@@ -2711,7 +2586,7 @@
1623
+ :param message: The message to send
1624
+ :type message: `email.Message`"""
1625
+- server = smtplib.SMTP()
1626
++ server = smtplib.SMTP("localhost")
1627
+ server.sendmail(message['From'], message['To'], message.as_string())
1630
+@@ -2763,6 +2638,22 @@
1632
+ 'request-merge': RequestMerge,
1635
++def my_import(mod_name):
1636
++ module = __import__(mod_name)
1637
++ components = mod_name.split('.')
1638
++ for comp in components[1:]:
1639
++ module = getattr(module, comp)
1642
++def plugin(mod_name):
1643
++ module = my_import(mod_name)
1644
++ module.add_command(commands)
1646
++for file in os.listdir(sys.path[0]+"/command"):
1647
++ if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
1648
++ plugin("command."+file[:-3])
1651
+ 'apply-delta' : "Try \"apply-changes\".",
1652
+ 'delta' : "To compare two revisions, use \"changes\".",
1653
+@@ -2784,6 +2675,7 @@
1654
+ 'tagline' : "Use add-id. It uses taglines in tagline trees",
1655
+ 'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
1656
+ 'library-revisions' : "Use revisions --library",
1657
+-'file-revert' : "Use revert FILE"
1658
++'file-revert' : "Use revert FILE",
1659
++'join-branch' : "Use replay --logs-only"
1661
+ # arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
1663
*** added file 'testdata/insert_top.patch'
1665
+++ testdata/insert_top.patch
1667
+--- orig/pylon/patches.py
1668
++++ mod/pylon/patches.py
1673
+ class PatchSyntax(Exception):
1675
*** added file 'testdata/mod'
1679
+# Copyright (C) 2004 Aaron Bentley
1680
+# <aaron.bentley@utoronto.ca>
1682
+# This program is free software; you can redistribute it and/or modify
1683
+# it under the terms of the GNU General Public License as published by
1684
+# the Free Software Foundation; either version 2 of the License, or
1685
+# (at your option) any later version.
1687
+# This program is distributed in the hope that it will be useful,
1688
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1689
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1690
+# GNU General Public License for more details.
1692
+# You should have received a copy of the GNU General Public License
1693
+# along with this program; if not, write to the Free Software
1694
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1701
+import pylon.errors
1702
+from pylon.errors import *
1703
+from pylon import errors
1704
+from pylon import util
1705
+from pylon import arch_core
1706
+from pylon import arch_compound
1707
+from pylon import ancillary
1708
+from pylon import misc
1709
+from pylon import paths
1726
+__docformat__ = "restructuredtext"
1727
+__doc__ = "Implementation of user (sub) commands"
1730
+def find_command(cmd):
1732
+ Return an instance of a command type. Return None if the type isn't
1735
+ :param cmd: the name of the command to look for
1736
+ :type cmd: the type of the command
1738
+ if commands.has_key(cmd):
1739
+ return commands[cmd]()
1744
+ def __call__(self, cmdline):
1746
+ self.do_command(cmdline.split())
1747
+ except cmdutil.GetHelp, e:
1749
+ except Exception, e:
1752
+ def get_completer(index):
1755
+ def complete(self, args, text):
1757
+ Returns a list of possible completions for the given text.
1759
+ :param args: The complete list of arguments
1760
+ :type args: List of str
1761
+ :param text: text to complete (may be shorter than args[-1])
1763
+ :rtype: list of str
1769
+ realtext = args[-1]
1774
+ parser=self.get_parser()
1775
+ if realtext.startswith('-'):
1776
+ candidates = parser.iter_options()
1778
+ (options, parsed_args) = parser.parse_args(args)
1780
+ if len (parsed_args) > 0:
1781
+ candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
1783
+ candidates = self.get_completer("", 0)
1786
+ if candidates is None:
1788
+ for candidate in candidates:
1789
+ candidate = str(candidate)
1790
+ if candidate.startswith(realtext):
1791
+ matches.append(candidate[len(realtext)- len(text):])
1795
+class Help(BaseCommand):
1797
+ Lists commands, prints help messages.
1799
+ def __init__(self):
1800
+ self.description="Prints help mesages"
1801
+ self.parser = None
1803
+ def do_command(self, cmdargs):
1805
+ Prints a help message.
1807
+ options, args = self.get_parser().parse_args(cmdargs)
1809
+ raise cmdutil.GetHelp
1811
+ if options.native or options.suggestions or options.external:
1812
+ native = options.native
1813
+ suggestions = options.suggestions
1814
+ external = options.external
1817
+ suggestions = False
1820
+ if len(args) == 0:
1821
+ self.list_commands(native, suggestions, external)
1823
+ elif len(args) == 1:
1824
+ command_help(args[0])
1828
+ self.get_parser().print_help()
1830
+If no command is specified, commands are listed. If a command is
1831
+specified, help for that command is listed.
1834
+ def get_parser(self):
1836
+ Returns the options parser to use for the "revision" command.
1838
+ :rtype: cmdutil.CmdOptionParser
1840
+ if self.parser is not None:
1841
+ return self.parser
1842
+ parser=cmdutil.CmdOptionParser("fai help [command]")
1843
+ parser.add_option("-n", "--native", action="store_true",
1844
+ dest="native", help="Show native commands")
1845
+ parser.add_option("-e", "--external", action="store_true",
1846
+ dest="external", help="Show external commands")
1847
+ parser.add_option("-s", "--suggest", action="store_true",
1848
+ dest="suggestions", help="Show suggestions")
1849
+ self.parser = parser
1852
+ def list_commands(self, native=True, suggest=False, external=True):
1854
+ Lists supported commands.
1856
+ :param native: list native, python-based commands
1857
+ :type native: bool
1858
+ :param external: list external aba-style commands
1859
+ :type external: bool
1862
+ print "Native Fai commands"
1863
+ keys=commands.keys()
1867
+ for i in range(28-len(k)):
1869
+ print space+k+" : "+commands[k]().description
1872
+ print "Unavailable commands and suggested alternatives"
1873
+ key_list = suggestions.keys()
1875
+ for key in key_list:
1876
+ print "%28s : %s" % (key, suggestions[key])
1879
+ fake_aba = abacmds.AbaCmds()
1880
+ if (fake_aba.abadir == ""):
1882
+ print "External commands"
1883
+ fake_aba.list_commands()
1886
+ print "Use help --suggest to list alternatives to tla and aba"\
1888
+ if options.tla_fallthrough and (native or external):
1889
+ print "Fai also supports tla commands."
1891
+def command_help(cmd):
1893
+ Prints help for a command.
1895
+ :param cmd: The name of the command to print help for
1898
+ fake_aba = abacmds.AbaCmds()
1899
+ cmdobj = find_command(cmd)
1900
+ if cmdobj != None:
1902
+ elif suggestions.has_key(cmd):
1903
+ print "Not available\n" + suggestions[cmd]
1905
+ abacmd = fake_aba.is_command(cmd)
1909
+ print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
1913
+class Changes(BaseCommand):
1915
+ the "changes" command: lists differences between trees/revisions:
1918
+ def __init__(self):
1919
+ self.description="Lists what files have changed in the project tree"
1921
+ def get_completer(self, arg, index):
1925
+ tree = arch.tree_root()
1928
+ return cmdutil.iter_revision_completions(arg, tree)
1930
+ def parse_commandline(self, cmdline):
1932
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
1934
+ :param cmdline: A list of arguments to parse
1935
+ :rtype: (options, Revision, Revision/WorkingTree)
1937
+ parser=self.get_parser()
1938
+ (options, args) = parser.parse_args(cmdline)
1940
+ raise cmdutil.GetHelp
1942
+ tree=arch.tree_root()
1943
+ if len(args) == 0:
1944
+ a_spec = ancillary.comp_revision(tree)
1946
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
1947
+ cmdutil.ensure_archive_registered(a_spec.archive)
1948
+ if len(args) == 2:
1949
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
1950
+ cmdutil.ensure_archive_registered(b_spec.archive)
1953
+ return options, a_spec, b_spec
1955
+ def do_command(self, cmdargs):
1957
+ Master function that perfoms the "changes" command.
1960
+ options, a_spec, b_spec = self.parse_commandline(cmdargs);
1961
+ except cmdutil.CantDetermineRevision, e:
1964
+ except arch.errors.TreeRootError, e:
1967
+ if options.changeset:
1968
+ changeset=options.changeset
1971
+ tmpdir=util.tmpdir()
1972
+ changeset=tmpdir+"/changeset"
1974
+ delta=arch.iter_delta(a_spec, b_spec, changeset)
1976
+ for line in delta:
1977
+ if cmdutil.chattermatch(line, "changeset:"):
1980
+ cmdutil.colorize(line, options.suppress_chatter)
1981
+ except arch.util.ExecProblem, e:
1982
+ if e.proc.error and e.proc.error.startswith(
1983
+ "missing explicit id for file"):
1984
+ raise MissingID(e)
1987
+ status=delta.status
1990
+ if (options.perform_diff):
1991
+ chan = arch_compound.ChangesetMunger(changeset)
1992
+ chan.read_indices()
1993
+ if options.diffopts is not None:
1994
+ if isinstance(b_spec, arch.Revision):
1995
+ b_dir = b_spec.library_find()
1998
+ a_dir = a_spec.library_find()
1999
+ diffopts = options.diffopts.split()
2000
+ cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
2002
+ cmdutil.show_diffs(delta.changeset)
2004
+ if tmpdir and (os.access(tmpdir, os.X_OK)):
2005
+ shutil.rmtree(tmpdir)
2007
+ def get_parser(self):
2009
+ Returns the options parser to use for the "changes" command.
2011
+ :rtype: cmdutil.CmdOptionParser
2013
+ parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
2015
+ parser.add_option("-d", "--diff", action="store_true",
2016
+ dest="perform_diff", default=False,
2017
+ help="Show diffs in summary")
2018
+ parser.add_option("-c", "--changeset", dest="changeset",
2019
+ help="Store a changeset in the given directory",
2020
+ metavar="DIRECTORY")
2021
+ parser.add_option("-s", "--silent", action="store_true",
2022
+ dest="suppress_chatter", default=False,
2023
+ help="Suppress chatter messages")
2024
+ parser.add_option("--diffopts", dest="diffopts",
2025
+ help="Use the specified diff options",
2026
+ metavar="OPTIONS")
2030
+ def help(self, parser=None):
2032
+ Prints a help message.
2034
+ :param parser: If supplied, the parser to use for generating help. If \
2035
+ not supplied, it is retrieved.
2036
+ :type parser: cmdutil.CmdOptionParser
2038
+ if parser is None:
2039
+ parser=self.get_parser()
2040
+ parser.print_help()
2042
+Performs source-tree comparisons
2044
+If no revision is specified, the current project tree is compared to the
2045
+last-committed revision. If one revision is specified, the current project
2046
+tree is compared to that revision. If two revisions are specified, they are
2047
+compared to each other.
2053
+class ApplyChanges(BaseCommand):
2055
+ Apply differences between two revisions to a tree
2058
+ def __init__(self):
2059
+ self.description="Applies changes to a project tree"
2061
+ def get_completer(self, arg, index):
2065
+ tree = arch.tree_root()
2068
+ return cmdutil.iter_revision_completions(arg, tree)
2070
+ def parse_commandline(self, cmdline, tree):
2072
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
2074
+ :param cmdline: A list of arguments to parse
2075
+ :rtype: (options, Revision, Revision/WorkingTree)
2077
+ parser=self.get_parser()
2078
+ (options, args) = parser.parse_args(cmdline)
2079
+ if len(args) != 2:
2080
+ raise cmdutil.GetHelp
2082
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
2083
+ cmdutil.ensure_archive_registered(a_spec.archive)
2084
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
2085
+ cmdutil.ensure_archive_registered(b_spec.archive)
2086
+ return options, a_spec, b_spec
2088
+ def do_command(self, cmdargs):
2090
+ Master function that performs "apply-changes".
2093
+ tree = arch.tree_root()
2094
+ options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
2095
+ except cmdutil.CantDetermineRevision, e:
2098
+ except arch.errors.TreeRootError, e:
2101
+ delta=cmdutil.apply_delta(a_spec, b_spec, tree)
2102
+ for line in cmdutil.iter_apply_delta_filter(delta):
2103
+ cmdutil.colorize(line, options.suppress_chatter)
2105
+ def get_parser(self):
2107
+ Returns the options parser to use for the "apply-changes" command.
2109
+ :rtype: cmdutil.CmdOptionParser
2111
+ parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
2113
+ parser.add_option("-d", "--diff", action="store_true",
2114
+ dest="perform_diff", default=False,
2115
+ help="Show diffs in summary")
2116
+ parser.add_option("-c", "--changeset", dest="changeset",
2117
+ help="Store a changeset in the given directory",
2118
+ metavar="DIRECTORY")
2119
+ parser.add_option("-s", "--silent", action="store_true",
2120
+ dest="suppress_chatter", default=False,
2121
+ help="Suppress chatter messages")
2124
+ def help(self, parser=None):
2126
+ Prints a help message.
2128
+ :param parser: If supplied, the parser to use for generating help. If \
2129
+ not supplied, it is retrieved.
2130
+ :type parser: cmdutil.CmdOptionParser
2132
+ if parser is None:
2133
+ parser=self.get_parser()
2134
+ parser.print_help()
2136
+Applies changes to a project tree
2138
+Compares two revisions and applies the difference between them to the current
2144
+class Update(BaseCommand):
2146
+ Updates a project tree to a given revision, preserving un-committed hanges.
2149
+ def __init__(self):
2150
+ self.description="Apply the latest changes to the current directory"
2152
+ def get_completer(self, arg, index):
2156
+ tree = arch.tree_root()
2159
+ return cmdutil.iter_revision_completions(arg, tree)
2161
+ def parse_commandline(self, cmdline, tree):
2163
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
2165
+ :param cmdline: A list of arguments to parse
2166
+ :rtype: (options, Revision, Revision/WorkingTree)
2168
+ parser=self.get_parser()
2169
+ (options, args) = parser.parse_args(cmdline)
2171
+ raise cmdutil.GetHelp
2176
+ revision=cmdutil.determine_revision_arch(tree, spec)
2177
+ cmdutil.ensure_archive_registered(revision.archive)
2179
+ mirror_source = cmdutil.get_mirror_source(revision.archive)
2180
+ if mirror_source != None:
2181
+ if cmdutil.prompt("Mirror update"):
2182
+ cmd=cmdutil.mirror_archive(mirror_source,
2183
+ revision.archive, arch.NameParser(revision).get_package_version())
2184
+ for line in arch.chatter_classifier(cmd):
2185
+ cmdutil.colorize(line, options.suppress_chatter)
2187
+ revision=cmdutil.determine_revision_arch(tree, spec)
2189
+ return options, revision
2191
+ def do_command(self, cmdargs):
2193
+ Master function that perfoms the "update" command.
2195
+ tree=arch.tree_root()
2197
+ options, to_revision = self.parse_commandline(cmdargs, tree);
2198
+ except cmdutil.CantDetermineRevision, e:
2201
+ except arch.errors.TreeRootError, e:
2204
+ from_revision = arch_compound.tree_latest(tree)
2205
+ if from_revision==to_revision:
2206
+ print "Tree is already up to date with:\n"+str(to_revision)+"."
2208
+ cmdutil.ensure_archive_registered(from_revision.archive)
2209
+ cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
2210
+ options.patch_forward)
2211
+ for line in cmdutil.iter_apply_delta_filter(cmd):
2212
+ cmdutil.colorize(line)
2213
+ if to_revision.version != tree.tree_version:
2214
+ if cmdutil.prompt("Update version"):
2215
+ tree.tree_version = to_revision.version
2217
+ def get_parser(self):
2219
+ Returns the options parser to use for the "update" command.
2221
+ :rtype: cmdutil.CmdOptionParser
2223
+ parser=cmdutil.CmdOptionParser("fai update [options]"
2224
+ " [revision/version]")
2225
+ parser.add_option("-f", "--forward", action="store_true",
2226
+ dest="patch_forward", default=False,
2227
+ help="pass the --forward option to 'patch'")
2228
+ parser.add_option("-s", "--silent", action="store_true",
2229
+ dest="suppress_chatter", default=False,
2230
+ help="Suppress chatter messages")
2233
+ def help(self, parser=None):
2235
+ Prints a help message.
2237
+ :param parser: If supplied, the parser to use for generating help. If \
2238
+ not supplied, it is retrieved.
2239
+ :type parser: cmdutil.CmdOptionParser
2241
+ if parser is None:
2242
+ parser=self.get_parser()
2243
+ parser.print_help()
2245
+Updates a working tree to the current archive revision
2247
+If a revision or version is specified, that is used instead
2253
+class Commit(BaseCommand):
2255
+ Create a revision based on the changes in the current tree.
2258
+ def __init__(self):
2259
+ self.description="Write local changes to the archive"
2261
+ def get_completer(self, arg, index):
2264
+ return iter_modified_file_completions(arch.tree_root(), arg)
2265
+# return iter_source_file_completions(arch.tree_root(), arg)
2267
+ def parse_commandline(self, cmdline, tree):
2269
+ Parse commandline arguments. Raise cmtutil.GetHelp if help is needed.
2271
+ :param cmdline: A list of arguments to parse
2272
+ :rtype: (options, Revision, Revision/WorkingTree)
2274
+ parser=self.get_parser()
2275
+ (options, args) = parser.parse_args(cmdline)
2277
+ if len(args) == 0:
2279
+ if options.version is None:
2280
+ return options, tree.tree_version, args
2282
+ revision=cmdutil.determine_revision_arch(tree, options.version)
2283
+ return options, revision.get_version(), args
2285
+ def do_command(self, cmdargs):
2287
+ Master function that perfoms the "commit" command.
2289
+ tree=arch.tree_root()
2290
+ options, version, files = self.parse_commandline(cmdargs, tree)
2292
+ if options.__dict__.has_key("base") and options.base:
2293
+ base = cmdutil.determine_revision_tree(tree, options.base)
2296
+ base = ancillary.submit_revision(tree)
2298
+ if ancestor is None:
2299
+ ancestor = arch_compound.tree_latest(tree, version)
2301
+ writeversion=version
2302
+ archive=version.archive
2303
+ source=cmdutil.get_mirror_source(archive)
2305
+ writethrough="implicit"
2308
+ if writethrough=="explicit" and \
2309
+ cmdutil.prompt("Writethrough"):
2310
+ writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
2311
+ elif writethrough=="none":
2312
+ raise CommitToMirror(archive)
2314
+ elif archive.is_mirror:
2315
+ raise CommitToMirror(archive)
2318
+ last_revision=tree.iter_logs(version, True).next().revision
2319
+ except StopIteration, e:
2320
+ last_revision = None
2321
+ if ancestor is None:
2322
+ if cmdutil.prompt("Import from commit"):
2323
+ return do_import(version)
2325
+ raise NoVersionLogs(version)
2327
+ arch_last_revision = version.iter_revisions(True).next()
2328
+ except StopIteration, e:
2329
+ arch_last_revision = None
2331
+ if last_revision != arch_last_revision:
2332
+ print "Tree is not up to date with %s" % str(version)
2333
+ if not cmdutil.prompt("Out of date"):
2339
+ if not cmdutil.has_changed(ancestor):
2340
+ if not cmdutil.prompt("Empty commit"):
2342
+ except arch.util.ExecProblem, e:
2343
+ if e.proc.error and e.proc.error.startswith(
2344
+ "missing explicit id for file"):
2345
+ raise MissingID(e)
2348
+ log = tree.log_message(create=False, version=version)
2351
+ if cmdutil.prompt("Create log"):
2352
+ edit_log(tree, version)
2354
+ except cmdutil.NoEditorSpecified, e:
2355
+ raise CommandFailed(e)
2356
+ log = tree.log_message(create=False, version=version)
2358
+ raise NoLogMessage
2359
+ if log["Summary"] is None or len(log["Summary"].strip()) == 0:
2360
+ if not cmdutil.prompt("Omit log summary"):
2361
+ raise errors.NoLogSummary
2363
+ for line in tree.iter_commit(version, seal=options.seal_version,
2364
+ base=base, out_of_date_ok=allow_old, file_list=files):
2365
+ cmdutil.colorize(line, options.suppress_chatter)
2367
+ except arch.util.ExecProblem, e:
2368
+ if e.proc.error and e.proc.error.startswith(
2369
+ "These files violate naming conventions:"):
2370
+ raise LintFailure(e.proc.error)
2374
+ def get_parser(self):
2376
+ Returns the options parser to use for the "commit" command.
2378
+ :rtype: cmdutil.CmdOptionParser
2381
+ parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
2383
+ parser.add_option("--seal", action="store_true",
2384
+ dest="seal_version", default=False,
2385
+ help="seal this version")
2386
+ parser.add_option("-v", "--version", dest="version",
2387
+ help="Use the specified version",
2388
+ metavar="VERSION")
2389
+ parser.add_option("-s", "--silent", action="store_true",
2390
+ dest="suppress_chatter", default=False,
2391
+ help="Suppress chatter messages")
2392
+ if cmdutil.supports_switch("commit", "--base"):
2393
+ parser.add_option("--base", dest="base", help="",
2394
+ metavar="REVISION")
2397
+ def help(self, parser=None):
2399
+ Prints a help message.
2401
+ :param parser: If supplied, the parser to use for generating help. If \
2402
+ not supplied, it is retrieved.
2403
+ :type parser: cmdutil.CmdOptionParser
2405
+ if parser is None:
2406
+ parser=self.get_parser()
2407
+ parser.print_help()
2409
+Updates a working tree to the current archive revision
2411
+If a version is specified, that is used instead
2418
+class CatLog(BaseCommand):
2420
+ Print the log of a given file (from current tree)
2422
+ def __init__(self):
2423
+ self.description="Prints the patch log for a revision"
2425
+ def get_completer(self, arg, index):
2429
+ tree = arch.tree_root()
2432
+ return cmdutil.iter_revision_completions(arg, tree)
2434
+ def do_command(self, cmdargs):
2436
+ Master function that perfoms the "cat-log" command.
2438
+ parser=self.get_parser()
2439
+ (options, args) = parser.parse_args(cmdargs)
2441
+ tree = arch.tree_root()
2442
+ except arch.errors.TreeRootError, e:
2448
+ raise cmdutil.GetHelp()
2451
+ revision = cmdutil.determine_revision_tree(tree, spec)
2453
+ revision = cmdutil.determine_revision_arch(tree, spec)
2454
+ except cmdutil.CantDetermineRevision, e:
2455
+ raise CommandFailedWrapper(e)
2458
+ use_tree = (options.source == "tree" or \
2459
+ (options.source == "any" and tree))
2460
+ use_arch = (options.source == "archive" or options.source == "any")
2464
+ for log in tree.iter_logs(revision.get_version()):
2465
+ if log.revision == revision:
2469
+ if log is None and use_arch:
2470
+ cmdutil.ensure_revision_exists(revision)
2471
+ log = arch.Patchlog(revision)
2472
+ if log is not None:
2473
+ for item in log.items():
2474
+ print "%s: %s" % item
2475
+ print log.description
2477
+ def get_parser(self):
2479
+ Returns the options parser to use for the "cat-log" command.
2481
+ :rtype: cmdutil.CmdOptionParser
2483
+ parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
2484
+ parser.add_option("--archive", action="store_const", dest="source",
2485
+ const="archive", default="any",
2486
+ help="Always get the log from the archive")
2487
+ parser.add_option("--tree", action="store_const", dest="source",
2488
+ const="tree", help="Always get the log from the tree")
2491
+ def help(self, parser=None):
2493
+ Prints a help message.
2495
+ :param parser: If supplied, the parser to use for generating help. If \
2496
+ not supplied, it is retrieved.
2497
+ :type parser: cmdutil.CmdOptionParser
2500
+ parser=self.get_parser()
2501
+ parser.print_help()
2503
+Prints the log for the specified revision
2508
+class Revert(BaseCommand):
2509
+ """ Reverts a tree (or aspects of it) to a revision
2511
+ def __init__(self):
2512
+ self.description="Reverts a tree (or aspects of it) to a revision "
2514
+ def get_completer(self, arg, index):
2518
+ tree = arch.tree_root()
2521
+ return iter_modified_file_completions(tree, arg)
2523
+ def do_command(self, cmdargs):
2525
+ Master function that perfoms the "revert" command.
2527
+ parser=self.get_parser()
2528
+ (options, args) = parser.parse_args(cmdargs)
2530
+ tree = arch.tree_root()
2531
+ except arch.errors.TreeRootError, e:
2532
+ raise CommandFailed(e)
2534
+ if options.revision is not None:
2535
+ spec=options.revision
2537
+ if spec is not None:
2538
+ revision = cmdutil.determine_revision_tree(tree, spec)
2540
+ revision = ancillary.comp_revision(tree)
2541
+ except cmdutil.CantDetermineRevision, e:
2542
+ raise CommandFailedWrapper(e)
2545
+ if options.file_contents or options.file_perms or options.deletions\
2546
+ or options.additions or options.renames or options.hunk_prompt:
2547
+ munger = arch_compound.MungeOpts()
2548
+ munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
2549
+ options.hunk_prompt)
2551
+ if len(args) > 0 or options.logs or options.pattern_files or \
2553
+ if munger is None:
2554
+ munger = cmdutil.arch_compound.MungeOpts(True)
2555
+ munger.all_types(True)
2557
+ t_cwd = arch_compound.tree_cwd(tree)
2559
+ if len(t_cwd) > 0:
2561
+ name = "./" + t_cwd + name
2562
+ munger.add_keep_file(name);
2564
+ if options.file_perms:
2565
+ munger.file_perms = True
2566
+ if options.file_contents:
2567
+ munger.file_contents = True
2568
+ if options.deletions:
2569
+ munger.deletions = True
2570
+ if options.additions:
2571
+ munger.additions = True
2572
+ if options.renames:
2573
+ munger.renames = True
2575
+ munger.add_keep_pattern('^\./\{arch\}/[^=].*')
2576
+ if options.control:
2577
+ munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
2578
+ "/\.arch-inventory$")
2579
+ if options.pattern_files:
2580
+ munger.add_keep_pattern(options.pattern_files)
2582
+ for line in arch_compound.revert(tree, revision, munger,
2583
+ not options.no_output):
2584
+ cmdutil.colorize(line)
2587
+ def get_parser(self):
2589
+ Returns the options parser to use for the "cat-log" command.
2591
+ :rtype: cmdutil.CmdOptionParser
2593
+ parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
2594
+ parser.add_option("", "--contents", action="store_true",
2595
+ dest="file_contents",
2596
+ help="Revert file content changes")
2597
+ parser.add_option("", "--permissions", action="store_true",
2598
+ dest="file_perms",
2599
+ help="Revert file permissions changes")
2600
+ parser.add_option("", "--deletions", action="store_true",
2602
+ help="Restore deleted files")
2603
+ parser.add_option("", "--additions", action="store_true",
2605
+ help="Remove added files")
2606
+ parser.add_option("", "--renames", action="store_true",
2608
+ help="Revert file names")
2609
+ parser.add_option("--hunks", action="store_true",
2610
+ dest="hunk_prompt", default=False,
2611
+ help="Prompt which hunks to revert")
2612
+ parser.add_option("--pattern-files", dest="pattern_files",
2613
+ help="Revert files that match this pattern",
2615
+ parser.add_option("--logs", action="store_true",
2616
+ dest="logs", default=False,
2617
+ help="Revert only logs")
2618
+ parser.add_option("--control-files", action="store_true",
2619
+ dest="control", default=False,
2620
+ help="Revert logs and other control files")
2621
+ parser.add_option("-n", "--no-output", action="store_true",
2623
+ help="Don't keep an undo changeset")
2624
+ parser.add_option("--revision", dest="revision",
2625
+ help="Revert to the specified revision",
2626
+ metavar="REVISION")
2629
+ def help(self, parser=None):
2631
+ Prints a help message.
2633
+ :param parser: If supplied, the parser to use for generating help. If \
2634
+ not supplied, it is retrieved.
2635
+ :type parser: cmdutil.CmdOptionParser
2638
+ parser=self.get_parser()
2639
+ parser.print_help()
2641
+Reverts changes in the current working tree. If no flags are specified, all
2642
+types of changes are reverted. Otherwise, only selected types of changes are
2645
+If a revision is specified on the commandline, differences between the current
2646
+tree and that revision are reverted. If a version is specified, the current
2647
+tree is used to determine the revision.
2649
+If files are specified, only those files listed will have any changes applied.
2650
+To specify a renamed file, you can use either the old or new name. (or both!)
2652
+Unless "-n" is specified, reversions can be undone with "redo".
2656
+class Revision(BaseCommand):
2658
+ Print a revision name based on a revision specifier
2660
+ def __init__(self):
2661
+ self.description="Prints the name of a revision"
2663
+ def get_completer(self, arg, index):
2667
+ tree = arch.tree_root()
2670
+ return cmdutil.iter_revision_completions(arg, tree)
2672
+ def do_command(self, cmdargs):
2674
+ Master function that perfoms the "revision" command.
2676
+ parser=self.get_parser()
2677
+ (options, args) = parser.parse_args(cmdargs)
2680
+ tree = arch.tree_root()
2681
+ except arch.errors.TreeRootError:
2688
+ raise cmdutil.GetHelp
2691
+ revision = cmdutil.determine_revision_tree(tree, spec)
2693
+ revision = cmdutil.determine_revision_arch(tree, spec)
2694
+ except cmdutil.CantDetermineRevision, e:
2697
+ print options.display(revision)
2699
+ def get_parser(self):
2701
+ Returns the options parser to use for the "revision" command.
2703
+ :rtype: cmdutil.CmdOptionParser
2705
+ parser=cmdutil.CmdOptionParser("fai revision [revision]")
2706
+ parser.add_option("", "--location", action="store_const",
2707
+ const=paths.determine_path, dest="display",
2708
+ help="Show location instead of name", default=str)
2709
+ parser.add_option("--import", action="store_const",
2710
+ const=paths.determine_import_path, dest="display",
2711
+ help="Show location of import file")
2712
+ parser.add_option("--log", action="store_const",
2713
+ const=paths.determine_log_path, dest="display",
2714
+ help="Show location of log file")
2715
+ parser.add_option("--patch", action="store_const",
2716
+ dest="display", const=paths.determine_patch_path,
2717
+ help="Show location of patchfile")
2718
+ parser.add_option("--continuation", action="store_const",
2719
+ const=paths.determine_continuation_path,
2721
+ help="Show location of continuation file")
2722
+ parser.add_option("--cacherev", action="store_const",
2723
+ const=paths.determine_cacherev_path, dest="display",
2724
+ help="Show location of cacherev file")
2727
+ def help(self, parser=None):
2729
+ Prints a help message.
2731
+ :param parser: If supplied, the parser to use for generating help. If \
2732
+ not supplied, it is retrieved.
2733
+ :type parser: cmdutil.CmdOptionParser
2736
+ parser=self.get_parser()
2737
+ parser.print_help()
2739
+Expands aliases and prints the name of the specified revision. Instead of
2740
+the name, several options can be used to print locations. If more than one is
2741
+specified, the last one is used.
2746
+class Revisions(BaseCommand):
2748
+ Print a revision name based on a revision specifier
2750
+ def __init__(self):
2751
+ self.description="Lists revisions"
2752
+ self.cl_revisions = []
2754
+ def do_command(self, cmdargs):
2756
+ Master function that perfoms the "revision" command.
2758
+ (options, args) = self.get_parser().parse_args(cmdargs)
2760
+ raise cmdutil.GetHelp
2762
+ self.tree = arch.tree_root()
2763
+ except arch.errors.TreeRootError:
2765
+ if options.type == "default":
2766
+ options.type = "archive"
2768
+ iter = cmdutil.revision_iterator(self.tree, options.type, args,
2769
+ options.reverse, options.modified,
2771
+ except cmdutil.CantDetermineRevision, e:
2772
+ raise CommandFailedWrapper(e)
2773
+ except cmdutil.CantDetermineVersion, e:
2774
+ raise CommandFailedWrapper(e)
2775
+ if options.skip is not None:
2776
+ iter = cmdutil.iter_skip(iter, int(options.skip))
2779
+ for revision in iter:
2781
+ if isinstance(revision, arch.Patchlog):
2783
+ revision=revision.revision
2784
+ out = options.display(revision)
2785
+ if out is not None:
2787
+ if log is None and (options.summary or options.creator or
2788
+ options.date or options.merges):
2789
+ log = revision.patchlog
2790
+ if options.creator:
2791
+ print " %s" % log.creator
2793
+ print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
2794
+ if options.summary:
2795
+ print " %s" % log.summary
2796
+ if options.merges:
2797
+ showed_title = False
2798
+ for revision in log.merged_patches:
2799
+ if not showed_title:
2801
+ showed_title = True
2802
+ print " %s" % revision
2803
+ if len(self.cl_revisions) > 0:
2804
+ print pylon.changelog_for_merge(self.cl_revisions)
2805
+ except pylon.errors.TreeRootNone:
2806
+ raise CommandFailedWrapper(
2807
+ Exception("This option can only be used in a project tree."))
2809
+ def changelog_append(self, revision):
2810
+ if isinstance(revision, arch.Revision):
2811
+ revision=arch.Patchlog(revision)
2812
+ self.cl_revisions.append(revision)
2814
+ def get_parser(self):
2816
+ Returns the options parser to use for the "revision" command.
2818
+ :rtype: cmdutil.CmdOptionParser
2820
+ parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
2821
+ select = cmdutil.OptionGroup(parser, "Selection options",
2822
+ "Control which revisions are listed. These options"
2823
+ " are mutually exclusive. If more than one is"
2824
+ " specified, the last is used.")
2826
+ cmdutil.add_revision_iter_options(select)
2827
+ parser.add_option("", "--skip", dest="skip",
2828
+ help="Skip revisions. Positive numbers skip from "
2829
+ "beginning, negative skip from end.",
2832
+ parser.add_option_group(select)
2834
+ format = cmdutil.OptionGroup(parser, "Revision format options",
2835
+ "These control the appearance of listed revisions")
2836
+ format.add_option("", "--location", action="store_const",
2837
+ const=paths.determine_path, dest="display",
2838
+ help="Show location instead of name", default=str)
2839
+ format.add_option("--import", action="store_const",
2840
+ const=paths.determine_import_path, dest="display",
2841
+ help="Show location of import file")
2842
+ format.add_option("--log", action="store_const",
2843
+ const=paths.determine_log_path, dest="display",
2844
+ help="Show location of log file")
2845
+ format.add_option("--patch", action="store_const",
2846
+ dest="display", const=paths.determine_patch_path,
2847
+ help="Show location of patchfile")
2848
+ format.add_option("--continuation", action="store_const",
2849
+ const=paths.determine_continuation_path,
2851
+ help="Show location of continuation file")
2852
+ format.add_option("--cacherev", action="store_const",
2853
+ const=paths.determine_cacherev_path, dest="display",
2854
+ help="Show location of cacherev file")
2855
+ format.add_option("--changelog", action="store_const",
2856
+ const=self.changelog_append, dest="display",
2857
+ help="Show location of cacherev file")
2858
+ parser.add_option_group(format)
2859
+ display = cmdutil.OptionGroup(parser, "Display format options",
2860
+ "These control the display of data")
2861
+ display.add_option("-r", "--reverse", action="store_true",
2862
+ dest="reverse", help="Sort from newest to oldest")
2863
+ display.add_option("-s", "--summary", action="store_true",
2864
+ dest="summary", help="Show patchlog summary")
2865
+ display.add_option("-D", "--date", action="store_true",
2866
+ dest="date", help="Show patchlog date")
2867
+ display.add_option("-c", "--creator", action="store_true",
2868
+ dest="creator", help="Show the id that committed the"
2870
+ display.add_option("-m", "--merges", action="store_true",
2871
+ dest="merges", help="Show the revisions that were"
2873
+ parser.add_option_group(display)
2875
+ def help(self, parser=None):
2876
+ """Attempt to explain the revisions command
2878
+ :param parser: If supplied, used to determine options
2881
+ parser=self.get_parser()
2882
+ parser.print_help()
2883
+ print """List revisions.
2888
+class Get(BaseCommand):
2890
+ Retrieve a revision from the archive
2892
+ def __init__(self):
2893
+ self.description="Retrieve a revision from the archive"
2894
+ self.parser=self.get_parser()
2897
+ def get_completer(self, arg, index):
2901
+ tree = arch.tree_root()
2904
+ return cmdutil.iter_revision_completions(arg, tree)
2907
+ def do_command(self, cmdargs):
2909
+ Master function that perfoms the "get" command.
2911
+ (options, args) = self.parser.parse_args(cmdargs)
2913
+ return self.help()
2915
+ tree = arch.tree_root()
2916
+ except arch.errors.TreeRootError:
2921
+ revision, arch_loc = paths.full_path_decode(args[0])
2922
+ except Exception, e:
2923
+ revision = cmdutil.determine_revision_arch(tree, args[0],
2924
+ check_existence=False, allow_package=True)
2926
+ directory = args[1]
2928
+ directory = str(revision.nonarch)
2929
+ if os.path.exists(directory):
2930
+ raise DirectoryExists(directory)
2931
+ cmdutil.ensure_archive_registered(revision.archive, arch_loc)
2933
+ cmdutil.ensure_revision_exists(revision)
2934
+ except cmdutil.NoSuchRevision, e:
2935
+ raise CommandFailedWrapper(e)
2937
+ link = cmdutil.prompt ("get link")
2938
+ for line in cmdutil.iter_get(revision, directory, link,
2939
+ options.no_pristine,
2940
+ options.no_greedy_add):
2941
+ cmdutil.colorize(line)
2943
+ def get_parser(self):
2945
+ Returns the options parser to use for the "get" command.
2947
+ :rtype: cmdutil.CmdOptionParser
2949
+ parser=cmdutil.CmdOptionParser("fai get revision [dir]")
2950
+ parser.add_option("--no-pristine", action="store_true",
2951
+ dest="no_pristine",
2952
+ help="Do not make pristine copy for reference")
2953
+ parser.add_option("--no-greedy-add", action="store_true",
2954
+ dest="no_greedy_add",
2955
+ help="Never add to greedy libraries")
2959
+ def help(self, parser=None):
2961
+ Prints a help message.
2963
+ :param parser: If supplied, the parser to use for generating help. If \
2964
+ not supplied, it is retrieved.
2965
+ :type parser: cmdutil.CmdOptionParser
2968
+ parser=self.get_parser()
2969
+ parser.print_help()
2971
+Expands aliases and constructs a project tree for a revision. If the optional
2972
+"dir" argument is provided, the project tree will be stored in this directory.
2977
+class PromptCmd(cmd.Cmd):
2978
+ def __init__(self):
2979
+ cmd.Cmd.__init__(self)
2980
+ self.prompt = "Fai> "
2982
+ self.tree = arch.tree_root()
2987
+ self.fake_aba = abacmds.AbaCmds()
2988
+ self.identchars += '-'
2989
+ self.history_file = os.path.expanduser("~/.fai-history")
2990
+ readline.set_completer_delims(string.whitespace)
2991
+ if os.access(self.history_file, os.R_OK) and \
2992
+ os.path.isfile(self.history_file):
2993
+ readline.read_history_file(self.history_file)
2994
+ self.cwd = os.getcwd()
2996
+ def write_history(self):
2997
+ readline.write_history_file(self.history_file)
2999
+ def do_quit(self, args):
3000
+ self.write_history()
3003
+ def do_exit(self, args):
3004
+ self.do_quit(args)
3006
+ def do_EOF(self, args):
3008
+ self.do_quit(args)
3010
+ def postcmd(self, line, bar):
3014
+ def set_prompt(self):
3015
+ if self.tree is not None:
3017
+ prompt = pylon.alias_or_version(self.tree.tree_version,
3020
+ if prompt is not None:
3021
+ prompt = " " + prompt
3026
+ self.prompt = "Fai%s> " % prompt
3028
+ def set_title(self, command=None):
3030
+ version = pylon.alias_or_version(self.tree.tree_version, self.tree,
3033
+ version = "[no version]"
3034
+ if command is None:
3036
+ sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
3038
+ def do_cd(self, line):
3041
+ line = os.path.expanduser(line)
3042
+ if os.path.isabs(line):
3045
+ newcwd = self.cwd+'/'+line
3046
+ newcwd = os.path.normpath(newcwd)
3050
+ except Exception, e:
3053
+ self.tree = arch.tree_root()
3057
+ def do_help(self, line):
3060
+ def default(self, line):
3061
+ args = line.split()
3062
+ if find_command(args[0]):
3064
+ find_command(args[0]).do_command(args[1:])
3065
+ except cmdutil.BadCommandOption, e:
3067
+ except cmdutil.GetHelp, e:
3068
+ find_command(args[0]).help()
3069
+ except CommandFailed, e:
3071
+ except arch.errors.ArchiveNotRegistered, e:
3073
+ except KeyboardInterrupt, e:
3074
+ print "Interrupted"
3075
+ except arch.util.ExecProblem, e:
3076
+ print e.proc.error.rstrip('\n')
3077
+ except cmdutil.CantDetermineVersion, e:
3079
+ except cmdutil.CantDetermineRevision, e:
3081
+ except Exception, e:
3082
+ print "Unhandled error:\n%s" % errors.exception_str(e)
3084
+ elif suggestions.has_key(args[0]):
3085
+ print suggestions[args[0]]
3087
+ elif self.fake_aba.is_command(args[0]):
3090
+ tree = arch.tree_root()
3091
+ except arch.errors.TreeRootError:
3093
+ cmd = self.fake_aba.is_command(args[0])
3095
+ cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
3096
+ except KeyboardInterrupt, e:
3097
+ print "Interrupted"
3099
+ elif options.tla_fallthrough and args[0] != "rm" and \
3100
+ cmdutil.is_tla_command(args[0]):
3104
+ tree = arch.tree_root()
3105
+ except arch.errors.TreeRootError:
3107
+ args = cmdutil.expand_prefix_alias(args, tree)
3108
+ arch.util.exec_safe('tla', args, stderr=sys.stderr,
3110
+ except arch.util.ExecProblem, e:
3112
+ except KeyboardInterrupt, e:
3113
+ print "Interrupted"
3117
+ tree = arch.tree_root()
3118
+ except arch.errors.TreeRootError:
3121
+ os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
3122
+ except KeyboardInterrupt, e:
3123
+ print "Interrupted"
3125
+ def completenames(self, text, line, begidx, endidx):
3127
+ iter = iter_command_names(self.fake_aba)
3130
+ arg = line.split()[-1]
3133
+ iter = cmdutil.iter_munged_completions(iter, arg, text)
3134
+ except Exception, e:
3138
+ def completedefault(self, text, line, begidx, endidx):
3139
+ """Perform completion for native commands.
3141
+ :param text: The text to complete
3143
+ :param line: The entire line to complete
3145
+ :param begidx: The start of the text in the line
3147
+ :param endidx: The end of the text in the line
3151
+ (cmd, args, foo) = self.parseline(line)
3152
+ command_obj=find_command(cmd)
3153
+ if command_obj is not None:
3154
+ return command_obj.complete(args.split(), text)
3155
+ elif not self.fake_aba.is_command(cmd) and \
3156
+ cmdutil.is_tla_command(cmd):
3157
+ iter = cmdutil.iter_supported_switches(cmd)
3159
+ arg = args.split()[-1]
3162
+ if arg.startswith("-"):
3163
+ return list(cmdutil.iter_munged_completions(iter, arg,
3166
+ return list(cmdutil.iter_munged_completions(
3167
+ cmdutil.iter_file_completions(arg), arg, text))
3172
+ arg = args.split()[-1]
3175
+ iter = cmdutil.iter_dir_completions(arg)
3176
+ iter = cmdutil.iter_munged_completions(iter, arg, text)
3179
+ arg = args.split()[-1]
3180
+ iter = cmdutil.iter_file_completions(arg)
3181
+ return list(cmdutil.iter_munged_completions(iter, arg, text))
3183
+ return self.completenames(text, line, begidx, endidx)
3184
+ except Exception, e:
3188
+def iter_command_names(fake_aba):
3189
+ for entry in cmdutil.iter_combine([commands.iterkeys(),
3190
+ fake_aba.get_commands(),
3191
+ cmdutil.iter_tla_commands(False)]):
3192
+ if not suggestions.has_key(str(entry)):
3196
+def iter_source_file_completions(tree, arg):
3197
+ treepath = arch_compound.tree_cwd(tree)
3198
+ if len(treepath) > 0:
3202
+ for file in tree.iter_inventory(dirs, source=True, both=True):
3203
+ file = file_completion_match(file, treepath, arg)
3204
+ if file is not None:
3208
+def iter_untagged(tree, dirs):
3209
+ for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False,
3210
+ categories=arch_core.non_root,
3211
+ control_files=True):
3215
+def iter_untagged_completions(tree, arg):
3216
+ """Generate an iterator for all visible untagged files that match arg.
3218
+ :param tree: The tree to look for untagged files in
3219
+ :type tree: `arch.WorkingTree`
3220
+ :param arg: The argument to match
3222
+ :return: An iterator of all matching untagged files
3223
+ :rtype: iterator of str
3225
+ treepath = arch_compound.tree_cwd(tree)
3226
+ if len(treepath) > 0:
3231
+ for file in iter_untagged(tree, dirs):
3232
+ file = file_completion_match(file, treepath, arg)
3233
+ if file is not None:
3237
+def file_completion_match(file, treepath, arg):
3238
+ """Determines whether a file within an arch tree matches the argument.
3240
+ :param file: The rooted filename
3242
+ :param treepath: The path to the cwd within the tree
3243
+ :type treepath: str
3244
+ :param arg: The prefix to match
3245
+ :return: The completion name, or None if not a match
3248
+ if not file.startswith(treepath):
3250
+ if treepath != "":
3251
+ file = file[len(treepath)+1:]
3253
+ if not file.startswith(arg):
3255
+ if os.path.isdir(file):
3259
+def iter_modified_file_completions(tree, arg):
3260
+ """Returns a list of modified files that match the specified prefix.
3262
+ :param tree: The current tree
3263
+ :type tree: `arch.WorkingTree`
3264
+ :param arg: The prefix to match
3267
+ treepath = arch_compound.tree_cwd(tree)
3268
+ tmpdir = util.tmpdir()
3269
+ changeset = tmpdir+"/changeset"
3271
+ revision = cmdutil.determine_revision_tree(tree)
3272
+ for line in arch.iter_delta(revision, tree, changeset):
3273
+ if isinstance(line, arch.FileModification):
3274
+ file = file_completion_match(line.name[1:], treepath, arg)
3275
+ if file is not None:
3276
+ completions.append(file)
3277
+ shutil.rmtree(tmpdir)
3278
+ return completions
3280
+class Shell(BaseCommand):
3281
+ def __init__(self):
3282
+ self.description = "Runs Fai as a shell"
3284
+ def do_command(self, cmdargs):
3285
+ if len(cmdargs)!=0:
3286
+ raise cmdutil.GetHelp
3287
+ prompt = PromptCmd()
3291
+ prompt.write_history()
3293
+class AddID(BaseCommand):
3295
+ Adds an inventory id for the given file
3297
+ def __init__(self):
3298
+ self.description="Add an inventory id for a given file"
3300
+ def get_completer(self, arg, index):
3301
+ tree = arch.tree_root()
3302
+ return iter_untagged_completions(tree, arg)
3304
+ def do_command(self, cmdargs):
3306
+ Master function that perfoms the "revision" command.
3308
+ parser=self.get_parser()
3309
+ (options, args) = parser.parse_args(cmdargs)
3312
+ tree = arch.tree_root()
3313
+ except arch.errors.TreeRootError, e:
3314
+ raise pylon.errors.CommandFailedWrapper(e)
3317
+ if (len(args) == 0) == (options.untagged == False):
3318
+ raise cmdutil.GetHelp
3320
+ #if options.id and len(args) != 1:
3321
+ # print "If --id is specified, only one file can be named."
3324
+ method = tree.tagging_method
3326
+ if options.id_type == "tagline":
3327
+ if method != "tagline":
3328
+ if not cmdutil.prompt("Tagline in other tree"):
3329
+ if method == "explicit" or method == "implicit":
3330
+ options.id_type == method
3332
+ print "add-id not supported for \"%s\" tagging method"\
3336
+ elif options.id_type == "implicit":
3337
+ if method != "implicit":
3338
+ if not cmdutil.prompt("Implicit in other tree"):
3339
+ if method == "explicit" or method == "tagline":
3340
+ options.id_type == method
3342
+ print "add-id not supported for \"%s\" tagging method"\
3345
+ elif options.id_type == "explicit":
3346
+ if method != "tagline" and method != explicit:
3347
+ if not prompt("Explicit in other tree"):
3348
+ print "add-id not supported for \"%s\" tagging method" % \
3352
+ if options.id_type == "auto":
3353
+ if method != "tagline" and method != "explicit" \
3354
+ and method !="implicit":
3355
+ print "add-id not supported for \"%s\" tagging method" % method
3358
+ options.id_type = method
3359
+ if options.untagged:
3361
+ self.add_ids(tree, options.id_type, args)
3363
+ def add_ids(self, tree, id_type, files=()):
3364
+ """Add inventory ids to files.
3366
+ :param tree: the tree the files are in
3367
+ :type tree: `arch.WorkingTree`
3368
+ :param id_type: the type of id to add: "explicit" or "tagline"
3369
+ :type id_type: str
3370
+ :param files: The list of files to add. If None do all untagged.
3371
+ :type files: tuple of str
3374
+ untagged = (files is None)
3376
+ files = list(iter_untagged(tree, None))
3377
+ previous_files = []
3378
+ while len(files) > 0:
3379
+ previous_files.extend(files)
3380
+ if id_type == "explicit":
3381
+ cmdutil.add_id(files)
3382
+ elif id_type == "tagline" or id_type == "implicit":
3383
+ for file in files:
3385
+ implicit = (id_type == "implicit")
3386
+ cmdutil.add_tagline_or_explicit_id(file, False,
3388
+ except cmdutil.AlreadyTagged:
3389
+ print "\"%s\" already has a tagline." % file
3390
+ except cmdutil.NoCommentSyntax:
3392
+ #do inventory after tagging until no untagged files are encountered
3395
+ for file in iter_untagged(tree, None):
3396
+ if not file in previous_files:
3397
+ files.append(file)
3402
+ def get_parser(self):
3404
+ Returns the options parser to use for the "revision" command.
3406
+ :rtype: cmdutil.CmdOptionParser
3408
+ parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
3409
+# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
3410
+# parser.add_option("-i", "--id", dest="id",
3411
+# help="Specify id for a single file", default=None)
3412
+ parser.add_option("--tltl", action="store_true",
3413
+ dest="lord_style", help="Use Tom Lord's style of id.")
3414
+ parser.add_option("--explicit", action="store_const",
3415
+ const="explicit", dest="id_type",
3416
+ help="Use an explicit id", default="auto")
3417
+ parser.add_option("--tagline", action="store_const",
3418
+ const="tagline", dest="id_type",
3419
+ help="Use a tagline id")
3420
+ parser.add_option("--implicit", action="store_const",
3421
+ const="implicit", dest="id_type",
3422
+ help="Use an implicit id (deprecated)")
3423
+ parser.add_option("--untagged", action="store_true",
3424
+ dest="untagged", default=False,
3425
+ help="tag all untagged files")
3428
+ def help(self, parser=None):
3430
+ Prints a help message.
3432
+ :param parser: If supplied, the parser to use for generating help. If \
3433
+ not supplied, it is retrieved.
3434
+ :type parser: cmdutil.CmdOptionParser
3437
+ parser=self.get_parser()
3438
+ parser.print_help()
3440
+Adds an inventory to the specified file(s) and directories. If --untagged is
3441
+specified, adds inventory to all untagged files and directories.
3446
+class Merge(BaseCommand):
3448
+ Merges changes from other versions into the current tree
3450
+ def __init__(self):
3451
+ self.description="Merges changes from other versions"
3453
+ self.tree = arch.tree_root()
3458
+ def get_completer(self, arg, index):
3459
+ if self.tree is None:
3460
+ raise arch.errors.TreeRootError
3461
+ return cmdutil.merge_completions(self.tree, arg, index)
3463
+ def do_command(self, cmdargs):
3465
+ Master function that perfoms the "merge" command.
3467
+ parser=self.get_parser()
3468
+ (options, args) = parser.parse_args(cmdargs)
3470
+ action="star-merge"
3472
+ action = options.action
3474
+ if self.tree is None:
3475
+ raise arch.errors.TreeRootError(os.getcwd())
3476
+ if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
3477
+ raise UncommittedChanges(self.tree)
3482
+ revisions.append(cmdutil.determine_revision_arch(self.tree,
3484
+ source = "from commandline"
3486
+ revisions = ancillary.iter_partner_revisions(self.tree,
3487
+ self.tree.tree_version)
3488
+ source = "from partner version"
3489
+ revisions = misc.rewind_iterator(revisions)
3492
+ revisions.rewind()
3493
+ except StopIteration, e:
3494
+ revision = cmdutil.tag_cur(self.tree)
3495
+ if revision is None:
3496
+ raise CantDetermineRevision("", "No version specified, no "
3497
+ "partner-versions, and no tag"
3499
+ revisions = [revision]
3500
+ source = "from tag source"
3501
+ for revision in revisions:
3502
+ cmdutil.ensure_archive_registered(revision.archive)
3503
+ cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
3504
+ (revision, source)))
3505
+ if action=="native-merge" or action=="update":
3506
+ if self.native_merge(revision, action) == 0:
3508
+ elif action=="star-merge":
3510
+ self.star_merge(revision, options.diff3)
3511
+ except errors.MergeProblem, e:
3513
+ if cmdutil.has_changed(self.tree.tree_version):
3516
+ def star_merge(self, revision, diff3):
3517
+ """Perform a star-merge on the current tree.
3519
+ :param revision: The revision to use for the merge
3520
+ :type revision: `arch.Revision`
3521
+ :param diff3: If true, do a diff3 merge
3525
+ for line in self.tree.iter_star_merge(revision, diff3=diff3):
3526
+ cmdutil.colorize(line)
3527
+ except arch.util.ExecProblem, e:
3528
+ if e.proc.status is not None and e.proc.status == 1:
3530
+ print e.proc.error
3531
+ raise MergeProblem
3535
+ def native_merge(self, other_revision, action):
3536
+ """Perform a native-merge on the current tree.
3538
+ :param other_revision: The revision to use for the merge
3539
+ :type other_revision: `arch.Revision`
3540
+ :return: 0 if the merge was skipped, 1 if it was applied
3542
+ other_tree = arch_compound.find_or_make_local_revision(other_revision)
3544
+ if action == "native-merge":
3545
+ ancestor = arch_compound.merge_ancestor2(self.tree, other_tree,
3547
+ elif action == "update":
3548
+ ancestor = arch_compound.tree_latest(self.tree,
3549
+ other_revision.version)
3550
+ except CantDetermineRevision, e:
3551
+ raise CommandFailedWrapper(e)
3552
+ cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
3553
+ if (ancestor == other_revision):
3554
+ cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
3557
+ delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
3558
+ for line in cmdutil.iter_apply_delta_filter(delta):
3559
+ cmdutil.colorize(line)
3564
+ def get_parser(self):
3566
+ Returns the options parser to use for the "merge" command.
3568
+ :rtype: cmdutil.CmdOptionParser
3570
+ parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
3571
+ parser.add_option("-s", "--star-merge", action="store_const",
3572
+ dest="action", help="Use star-merge",
3573
+ const="star-merge", default="native-merge")
3574
+ parser.add_option("--update", action="store_const",
3575
+ dest="action", help="Use update picker",
3577
+ parser.add_option("--diff3", action="store_true",
3579
+ help="Use diff3 for merge (implies star-merge)")
3582
+ def help(self, parser=None):
3584
+ Prints a help message.
3586
+ :param parser: If supplied, the parser to use for generating help. If \
3587
+ not supplied, it is retrieved.
3588
+ :type parser: cmdutil.CmdOptionParser
3591
+ parser=self.get_parser()
3592
+ parser.print_help()
3594
+Performs a merge operation using the specified version.
3598
+class ELog(BaseCommand):
3600
+ Produces a raw patchlog and invokes the user's editor
3602
+ def __init__(self):
3603
+ self.description="Edit a patchlog to commit"
3605
+ self.tree = arch.tree_root()
3610
+ def do_command(self, cmdargs):
3612
+ Master function that perfoms the "elog" command.
3614
+ parser=self.get_parser()
3615
+ (options, args) = parser.parse_args(cmdargs)
3616
+ if self.tree is None:
3617
+ raise arch.errors.TreeRootError
3620
+ edit_log(self.tree, self.tree.tree_version)
3621
+ except pylon.errors.NoEditorSpecified, e:
3622
+ raise pylon.errors.CommandFailedWrapper(e)
3624
+ def get_parser(self):
3626
+ Returns the options parser to use for the "merge" command.
3628
+ :rtype: cmdutil.CmdOptionParser
3630
+ parser=cmdutil.CmdOptionParser("fai elog")
3634
+ def help(self, parser=None):
3636
+ Invokes $EDITOR to produce a log for committing.
3638
+ :param parser: If supplied, the parser to use for generating help. If \
3639
+ not supplied, it is retrieved.
3640
+ :type parser: cmdutil.CmdOptionParser
3643
+ parser=self.get_parser()
3644
+ parser.print_help()
3646
+Invokes $EDITOR to produce a log for committing.
3650
+def edit_log(tree, version):
3651
+ """Makes and edits the log for a tree. Does all kinds of fancy things
3652
+ like log templates and merge summaries and log-for-merge
3654
+ :param tree: The tree to edit the log for
3655
+ :type tree: `arch.WorkingTree`
3657
+ #ensure we have an editor before preparing the log
3658
+ cmdutil.find_editor()
3659
+ log = tree.log_message(create=False, version=version)
3660
+ log_is_new = False
3661
+ if log is None or cmdutil.prompt("Overwrite log"):
3662
+ if log is not None:
3663
+ os.remove(log.name)
3664
+ log = tree.log_message(create=True, version=version)
3667
+ template = pylon.log_template_path(tree)
3669
+ shutil.copyfile(template, tmplog)
3670
+ comp_version = ancillary.comp_revision(tree).version
3671
+ new_merges = cmdutil.iter_new_merges(tree, comp_version)
3672
+ new_merges = cmdutil.direct_merges(new_merges)
3673
+ log["Summary"] = pylon.merge_summary(new_merges,
3675
+ if len(new_merges) > 0:
3676
+ if cmdutil.prompt("Log for merge"):
3677
+ if cmdutil.prompt("changelog for merge"):
3678
+ mergestuff = "Patches applied:\n"
3679
+ mergestuff += pylon.changelog_for_merge(new_merges)
3681
+ mergestuff = cmdutil.log_for_merge(tree, comp_version)
3682
+ log.description += mergestuff
3685
+ cmdutil.invoke_editor(log.name)
3688
+ os.remove(log.name)
3692
+class MirrorArchive(BaseCommand):
3694
+ Updates a mirror from an archive
3696
+ def __init__(self):
3697
+ self.description="Update a mirror from an archive"
3699
+ def do_command(self, cmdargs):
3701
+ Master function that perfoms the "revision" command.
3704
+ parser=self.get_parser()
3705
+ (options, args) = parser.parse_args(cmdargs)
3709
+ tree = arch.tree_root()
3713
+ if len(args) == 0:
3714
+ if tree is not None:
3715
+ name = tree.tree_version()
3717
+ name = cmdutil.expand_alias(args[0], tree)
3718
+ name = arch.NameParser(name)
3720
+ to_arch = name.get_archive()
3721
+ from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
3722
+ limit = name.get_nonarch()
3724
+ iter = arch_core.mirror_archive(from_arch,to_arch, limit)
3725
+ for line in arch.chatter_classifier(iter):
3726
+ cmdutil.colorize(line)
3728
+ def get_parser(self):
3730
+ Returns the options parser to use for the "revision" command.
3732
+ :rtype: cmdutil.CmdOptionParser
3734
+ parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
3737
+ def help(self, parser=None):
3739
+ Prints a help message.
3741
+ :param parser: If supplied, the parser to use for generating help. If \
3742
+ not supplied, it is retrieved.
3743
+ :type parser: cmdutil.CmdOptionParser
3746
+ parser=self.get_parser()
3747
+ parser.print_help()
3749
+Updates a mirror from an archive. If a branch, package, or version is
3750
+supplied, only changes under it are mirrored.
3754
+def help_tree_spec():
3755
+ print """Specifying revisions (default: tree)
3756
+Revisions may be specified by alias, revision, version or patchlevel.
3757
+Revisions or versions may be fully qualified. Unqualified revisions, versions,
3758
+or patchlevels use the archive of the current project tree. Versions will
3759
+use the latest patchlevel in the tree. Patchlevels will use the current tree-
3762
+Use "alias" to list available (user and automatic) aliases."""
3766
+"The latest revision in the archive of the tree-version. You can specify \
3767
+a different version like so: acur:foo--bar--0 (aliases can be used)",
3769
+"""(tree current) The latest revision in the tree of the tree-version. \
3770
+You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
3773
+"""(tree previous) The previous revision in the tree of the tree-version. To \
3774
+specify an older revision, use a number, e.g. "tprev:4" """,
3776
+"""(tree ancestor) The ancestor revision of the tree To specify an older \
3777
+revision, use a number, e.g. "tanc:4".""",
3779
+"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
3781
+""" (tree modified) The latest revision to modify a given file, e.g. \
3782
+"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
3784
+"""(tree tag) The revision that was tagged into the current tree revision, \
3785
+according to the tree""",
3787
+"""(tag current) The latest revision of the version that the current tree \
3788
+was tagged from.""",
3790
+"""The common ancestor of the current tree and the specified revision. \
3791
+Defaults to the first partner-version's latest revision or to tagcur.""",
3795
+def is_auto_alias(name):
3796
+ """Determine whether a name is an auto alias name
3798
+ :param name: the name to check
3800
+ :return: True if the name is an auto alias, false if not
3803
+ return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
3806
+def display_def(iter, wrap = 80):
3807
+ """Display a list of definitions
3809
+ :param iter: iter of name, definition pairs
3810
+ :type iter: iter of (str, str)
3811
+ :param wrap: The width for text wrapping
3816
+ for (key, value) in vals:
3817
+ if len(key) > maxlen:
3819
+ for (key, value) in vals:
3820
+ tw=textwrap.TextWrapper(width=wrap,
3821
+ initial_indent=key.rjust(maxlen)+" : ",
3822
+ subsequent_indent="".rjust(maxlen+3))
3823
+ print tw.fill(value)
3826
+def help_aliases(tree):
3827
+ print """Auto-generated aliases"""
3828
+ display_def(pylon.util.iter_pairs(auto_alias))
3829
+ print "User aliases"
3830
+ display_def(ancillary.iter_all_alias(tree))
3832
+class Inventory(BaseCommand):
3833
+ """List the status of files in the tree"""
3834
+ def __init__(self):
3835
+ self.description=self.__doc__
3837
+ def do_command(self, cmdargs):
3839
+ Master function that perfoms the "revision" command.
3842
+ parser=self.get_parser()
3843
+ (options, args) = parser.parse_args(cmdargs)
3844
+ tree = arch.tree_root()
3847
+ if (options.source):
3848
+ categories.append(arch_core.SourceFile)
3849
+ if (options.precious):
3850
+ categories.append(arch_core.PreciousFile)
3851
+ if (options.backup):
3852
+ categories.append(arch_core.BackupFile)
3853
+ if (options.junk):
3854
+ categories.append(arch_core.JunkFile)
3856
+ if len(categories) == 1:
3857
+ show_leading = False
3859
+ show_leading = True
3861
+ if len(categories) == 0:
3864
+ if options.untagged:
3865
+ categories = arch_core.non_root
3866
+ show_leading = False
3871
+ for file in arch_core.iter_inventory_filter(tree, None,
3872
+ control_files=options.control_files,
3873
+ categories = categories, tagged=tagged):
3874
+ print arch_core.file_line(file,
3875
+ category = show_leading,
3876
+ untagged = show_leading,
3879
+ def get_parser(self):
3881
+ Returns the options parser to use for the "revision" command.
3883
+ :rtype: cmdutil.CmdOptionParser
3885
+ parser=cmdutil.CmdOptionParser("fai inventory [options]")
3886
+ parser.add_option("--ids", action="store_true", dest="ids",
3887
+ help="Show file ids")
3888
+ parser.add_option("--control", action="store_true",
3889
+ dest="control_files", help="include control files")
3890
+ parser.add_option("--source", action="store_true", dest="source",
3891
+ help="List source files")
3892
+ parser.add_option("--backup", action="store_true", dest="backup",
3893
+ help="List backup files")
3894
+ parser.add_option("--precious", action="store_true", dest="precious",
3895
+ help="List precious files")
3896
+ parser.add_option("--junk", action="store_true", dest="junk",
3897
+ help="List junk files")
3898
+ parser.add_option("--unrecognized", action="store_true",
3899
+ dest="unrecognized", help="List unrecognized files")
3900
+ parser.add_option("--untagged", action="store_true",
3901
+ dest="untagged", help="List only untagged files")
3904
+ def help(self, parser=None):
3906
+ Prints a help message.
3908
+ :param parser: If supplied, the parser to use for generating help. If \
3909
+ not supplied, it is retrieved.
3910
+ :type parser: cmdutil.CmdOptionParser
3913
+ parser=self.get_parser()
3914
+ parser.print_help()
3916
+Lists the status of files in the archive:
3924
+Leading letter are not displayed if only one kind of file is shown
3929
+class Alias(BaseCommand):
3930
+ """List or adjust aliases"""
3931
+ def __init__(self):
3932
+ self.description=self.__doc__
3934
+ def get_completer(self, arg, index):
3938
+ self.tree = arch.tree_root()
3943
+ return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
3945
+ return cmdutil.iter_revision_completions(arg, self.tree)
3948
+ def do_command(self, cmdargs):
3950
+ Master function that perfoms the "revision" command.
3953
+ parser=self.get_parser()
3954
+ (options, args) = parser.parse_args(cmdargs)
3956
+ self.tree = arch.tree_root()
3962
+ options.action(args, options)
3963
+ except cmdutil.ForbiddenAliasSyntax, e:
3964
+ raise CommandFailedWrapper(e)
3966
+ def no_prefix(self, alias):
3967
+ if alias.startswith("^"):
3971
+ def arg_dispatch(self, args, options):
3972
+ """Add, modify, or list aliases, depending on number of arguments
3974
+ :param args: The list of commandline arguments
3975
+ :type args: list of str
3976
+ :param options: The commandline options
3978
+ if len(args) == 0:
3979
+ help_aliases(self.tree)
3982
+ alias = self.no_prefix(args[0])
3983
+ if len(args) == 1:
3984
+ self.print_alias(alias)
3985
+ elif (len(args)) == 2:
3986
+ self.add(alias, args[1], options)
3988
+ raise cmdutil.GetHelp
3990
+ def print_alias(self, alias):
3992
+ if is_auto_alias(alias):
3993
+ raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
3994
+ " Use \"revision\" to expand auto aliases." % alias)
3995
+ for pair in ancillary.iter_all_alias(self.tree):
3996
+ if pair[0] == alias:
3998
+ if answer is not None:
4001
+ print "The alias %s is not assigned." % alias
4003
+ def add(self, alias, expansion, options):
4004
+ """Add or modify aliases
4006
+ :param alias: The alias name to create/modify
4008
+ :param expansion: The expansion to assign to the alias name
4009
+ :type expansion: str
4010
+ :param options: The commandline options
4012
+ if is_auto_alias(alias):
4013
+ raise IsAutoAlias(alias)
4016
+ new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
4018
+ ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
4020
+ for pair in self.get_iterator(options):
4021
+ if pair[0] != alias:
4022
+ newlist+="%s=%s\n" % (pair[0], pair[1])
4028
+ self.write_aliases(newlist, options)
4030
+ def delete(self, args, options):
4031
+ """Delete the specified alias
4033
+ :param args: The list of arguments
4034
+ :type args: list of str
4035
+ :param options: The commandline options
4038
+ if len(args) != 1:
4039
+ raise cmdutil.GetHelp
4040
+ alias = self.no_prefix(args[0])
4041
+ if is_auto_alias(alias):
4042
+ raise IsAutoAlias(alias)
4044
+ for pair in self.get_iterator(options):
4045
+ if pair[0] != alias:
4046
+ newlist+="%s=%s\n" % (pair[0], pair[1])
4050
+ raise errors.NoSuchAlias(alias)
4051
+ self.write_aliases(newlist, options)
4053
+ def get_alias_file(self, options):
4054
+ """Return the name of the alias file to use
4056
+ :param options: The commandline options
4059
+ if self.tree is None:
4060
+ self.tree == arch.tree_root()
4061
+ return str(self.tree)+"/{arch}/+aliases"
4063
+ return "~/.aba/aliases"
4065
+ def get_iterator(self, options):
4066
+ """Return the alias iterator to use
4068
+ :param options: The commandline options
4070
+ return ancillary.iter_alias(self.get_alias_file(options))
4072
+ def write_aliases(self, newlist, options):
4073
+ """Safely rewrite the alias file
4074
+ :param newlist: The new list of aliases
4075
+ :type newlist: str
4076
+ :param options: The commandline options
4078
+ filename = os.path.expanduser(self.get_alias_file(options))
4079
+ file = util.NewFileVersion(filename)
4080
+ file.write(newlist)
4084
+ def get_parser(self):
4086
+ Returns the options parser to use for the "alias" command.
4088
+ :rtype: cmdutil.CmdOptionParser
4090
+ parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
4091
+ parser.add_option("-d", "--delete", action="store_const", dest="action",
4092
+ const=self.delete, default=self.arg_dispatch,
4093
+ help="Delete an alias")
4094
+ parser.add_option("--tree", action="store_true", dest="tree",
4095
+ help="Create a per-tree alias", default=False)
4098
+ def help(self, parser=None):
4100
+ Prints a help message.
4102
+ :param parser: If supplied, the parser to use for generating help. If \
4103
+ not supplied, it is retrieved.
4104
+ :type parser: cmdutil.CmdOptionParser
4107
+ parser=self.get_parser()
4108
+ parser.print_help()
4110
+Lists current aliases or modifies the list of aliases.
4112
+If no arguments are supplied, aliases will be listed. If two arguments are
4113
+supplied, the specified alias will be created or modified. If -d or --delete
4114
+is supplied, the specified alias will be deleted.
4116
+You can create aliases that refer to any fully-qualified part of the
4117
+Arch namespace, e.g.
4120
+archive/category--branch,
4121
+archive/category--branch--version (my favourite)
4122
+archive/category--branch--version--patchlevel
4124
+Aliases can be used automatically by native commands. To use them
4125
+with external or tla commands, prefix them with ^ (you can do this
4126
+with native commands, too).
4130
+class RequestMerge(BaseCommand):
4131
+ """Submit a merge request to Bug Goo"""
4132
+ def __init__(self):
4133
+ self.description=self.__doc__
4135
+ def do_command(self, cmdargs):
4136
+ """Submit a merge request
4138
+ :param cmdargs: The commandline arguments
4139
+ :type cmdargs: list of str
4141
+ parser = self.get_parser()
4142
+ (options, args) = parser.parse_args(cmdargs)
4144
+ cmdutil.find_editor()
4145
+ except pylon.errors.NoEditorSpecified, e:
4146
+ raise pylon.errors.CommandFailedWrapper(e)
4148
+ self.tree=arch.tree_root()
4151
+ base, revisions = self.revision_specs(args)
4152
+ message = self.make_headers(base, revisions)
4153
+ message += self.make_summary(revisions)
4154
+ path = self.edit_message(message)
4155
+ message = self.tidy_message(path)
4156
+ if cmdutil.prompt("Send merge"):
4157
+ self.send_message(message)
4158
+ print "Merge request sent"
4160
+ def make_headers(self, base, revisions):
4161
+ """Produce email and Bug Goo header strings
4163
+ :param base: The base revision to apply merges to
4164
+ :type base: `arch.Revision`
4165
+ :param revisions: The revisions to replay into the base
4166
+ :type revisions: list of `arch.Patchlog`
4167
+ :return: The headers
4170
+ headers = "To: gnu-arch-users@gnu.org\n"
4171
+ headers += "From: %s\n" % options.fromaddr
4172
+ if len(revisions) == 1:
4173
+ headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
4175
+ headers += "Subject: [MERGE REQUEST]\n"
4177
+ headers += "Base-Revision: %s\n" % base
4178
+ for revision in revisions:
4179
+ headers += "Revision: %s\n" % revision.revision
4180
+ headers += "Bug: \n\n"
4183
+ def make_summary(self, logs):
4184
+ """Generate a summary of merges
4186
+ :param logs: the patchlogs that were directly added by the merges
4187
+ :type logs: list of `arch.Patchlog`
4188
+ :return: the summary
4193
+ summary+=str(log.revision)+"\n"
4194
+ summary+=log.summary+"\n"
4195
+ if log.description.strip():
4196
+ summary+=log.description.strip('\n')+"\n\n"
4199
+ def revision_specs(self, args):
4200
+ """Determine the base and merge revisions from tree and arguments.
4202
+ :param args: The parsed arguments
4203
+ :type args: list of str
4204
+ :return: The base revision and merge revisions
4205
+ :rtype: `arch.Revision`, list of `arch.Patchlog`
4208
+ target_revision = cmdutil.determine_revision_arch(self.tree,
4211
+ target_revision = arch_compound.tree_latest(self.tree)
4213
+ merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
4214
+ self.tree, f)) for f in args[1:] ]
4216
+ if self.tree is None:
4217
+ raise CantDetermineRevision("", "Not in a project tree")
4218
+ merge_iter = cmdutil.iter_new_merges(self.tree,
4219
+ target_revision.version,
4221
+ merges = [f for f in cmdutil.direct_merges(merge_iter)]
4222
+ return (target_revision, merges)
4224
+ def edit_message(self, message):
4225
+ """Edit an email message in the user's standard editor
4227
+ :param message: The message to edit
4228
+ :type message: str
4229
+ :return: the path of the edited message
4232
+ if self.tree is None:
4233
+ path = os.get_cwd()
4236
+ path += "/,merge-request"
4237
+ file = open(path, 'w')
4238
+ file.write(message)
4240
+ cmdutil.invoke_editor(path)
4243
+ def tidy_message(self, path):
4244
+ """Validate and clean up message.
4246
+ :param path: The path to the message to clean up
4248
+ :return: The parsed message
4249
+ :rtype: `email.Message`
4251
+ mail = email.message_from_file(open(path))
4252
+ if mail["Subject"].strip() == "[MERGE REQUEST]":
4253
+ raise BlandSubject
4255
+ request = email.message_from_string(mail.get_payload())
4256
+ if request.has_key("Bug"):
4257
+ if request["Bug"].strip()=="":
4258
+ del request["Bug"]
4259
+ mail.set_payload(request.as_string())
4262
+ def send_message(self, message):
4263
+ """Send a message, using its headers to address it.
4265
+ :param message: The message to send
4266
+ :type message: `email.Message`"""
4267
+ server = smtplib.SMTP("localhost")
4268
+ server.sendmail(message['From'], message['To'], message.as_string())
4271
+ def help(self, parser=None):
4272
+ """Print a usage message
4274
+ :param parser: The options parser to use
4275
+ :type parser: `cmdutil.CmdOptionParser`
4277
+ if parser is None:
4278
+ parser = self.get_parser()
4279
+ parser.print_help()
4281
+Sends a merge request formatted for Bug Goo. Intended use: get the tree
4282
+you'd like to merge into. Apply the merges you want. Invoke request-merge.
4283
+The merge request will open in your $EDITOR.
4285
+When no TARGET is specified, it uses the current tree revision. When
4286
+no MERGE is specified, it uses the direct merges (as in "revisions
4287
+--direct-merges"). But you can specify just the TARGET, or all the MERGE
4291
+ def get_parser(self):
4292
+ """Produce a commandline parser for this command.
4294
+ :rtype: `cmdutil.CmdOptionParser`
4296
+ parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
4300
+'changes' : Changes,
4303
+'apply-changes':ApplyChanges,
4306
+'revision': Revision,
4307
+'revisions': Revisions,
4314
+'mirror-archive': MirrorArchive,
4315
+'ninventory': Inventory,
4317
+'request-merge': RequestMerge,
4320
+def my_import(mod_name):
4321
+ module = __import__(mod_name)
4322
+ components = mod_name.split('.')
4323
+ for comp in components[1:]:
4324
+ module = getattr(module, comp)
4327
+def plugin(mod_name):
4328
+ module = my_import(mod_name)
4329
+ module.add_command(commands)
4331
+for file in os.listdir(sys.path[0]+"/command"):
4332
+ if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
4333
+ plugin("command."+file[:-3])
4336
+'apply-delta' : "Try \"apply-changes\".",
4337
+'delta' : "To compare two revisions, use \"changes\".",
4338
+'diff-rev' : "To compare two revisions, use \"changes\".",
4339
+'undo' : "To undo local changes, use \"revert\".",
4340
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
4341
+'missing-from' : "Try \"revisions --missing-from\".",
4342
+'missing' : "Try \"revisions --missing\".",
4343
+'missing-merge' : "Try \"revisions --partner-missing\".",
4344
+'new-merges' : "Try \"revisions --new-merges\".",
4345
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
4346
+'logs' : "Try \"revisions --logs\"",
4347
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
4348
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
4349
+'change-version' : "Try \"update REVISION\"",
4350
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
4351
+'rev-depends' : "Use revisions --dependencies",
4352
+'auto-get' : "Plain get will do archive lookups",
4353
+'tagline' : "Use add-id. It uses taglines in tagline trees",
4354
+'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
4355
+'library-revisions' : "Use revisions --library",
4356
+'file-revert' : "Use revert FILE",
4357
+'join-branch' : "Use replay --logs-only"
4359
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
4361
*** added file 'testdata/orig'
4365
+# Copyright (C) 2004 Aaron Bentley
4366
+# <aaron.bentley@utoronto.ca>
4368
+# This program is free software; you can redistribute it and/or modify
4369
+# it under the terms of the GNU General Public License as published by
4370
+# the Free Software Foundation; either version 2 of the License, or
4371
+# (at your option) any later version.
4373
+# This program is distributed in the hope that it will be useful,
4374
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4375
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4376
+# GNU General Public License for more details.
4378
+# You should have received a copy of the GNU General Public License
4379
+# along with this program; if not, write to the Free Software
4380
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
4398
+from errors import *
4406
+__docformat__ = "restructuredtext"
4407
+__doc__ = "Implementation of user (sub) commands"
4410
+def find_command(cmd):
4412
+ Return an instance of a command type. Return None if the type isn't
4415
+ :param cmd: the name of the command to look for
4416
+ :type cmd: the type of the command
4418
+ if commands.has_key(cmd):
4419
+ return commands[cmd]()
4424
+ def __call__(self, cmdline):
4426
+ self.do_command(cmdline.split())
4427
+ except cmdutil.GetHelp, e:
4429
+ except Exception, e:
4432
+ def get_completer(index):
4435
+ def complete(self, args, text):
4437
+ Returns a list of possible completions for the given text.
4439
+ :param args: The complete list of arguments
4440
+ :type args: List of str
4441
+ :param text: text to complete (may be shorter than args[-1])
4443
+ :rtype: list of str
4449
+ realtext = args[-1]
4454
+ parser=self.get_parser()
4455
+ if realtext.startswith('-'):
4456
+ candidates = parser.iter_options()
4458
+ (options, parsed_args) = parser.parse_args(args)
4460
+ if len (parsed_args) > 0:
4461
+ candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
4463
+ candidates = self.get_completer("", 0)
4466
+ if candidates is None:
4468
+ for candidate in candidates:
4469
+ candidate = str(candidate)
4470
+ if candidate.startswith(realtext):
4471
+ matches.append(candidate[len(realtext)- len(text):])
4475
+class Help(BaseCommand):
4477
+ Lists commands, prints help messages.
4479
+ def __init__(self):
4480
+ self.description="Prints help mesages"
4481
+ self.parser = None
4483
+ def do_command(self, cmdargs):
4485
+ Prints a help message.
4487
+ options, args = self.get_parser().parse_args(cmdargs)
4489
+ raise cmdutil.GetHelp
4491
+ if options.native or options.suggestions or options.external:
4492
+ native = options.native
4493
+ suggestions = options.suggestions
4494
+ external = options.external
4497
+ suggestions = False
4500
+ if len(args) == 0:
4501
+ self.list_commands(native, suggestions, external)
4503
+ elif len(args) == 1:
4504
+ command_help(args[0])
4508
+ self.get_parser().print_help()
4510
+If no command is specified, commands are listed. If a command is
4511
+specified, help for that command is listed.
4514
+ def get_parser(self):
4516
+ Returns the options parser to use for the "revision" command.
4518
+ :rtype: cmdutil.CmdOptionParser
4520
+ if self.parser is not None:
4521
+ return self.parser
4522
+ parser=cmdutil.CmdOptionParser("fai help [command]")
4523
+ parser.add_option("-n", "--native", action="store_true",
4524
+ dest="native", help="Show native commands")
4525
+ parser.add_option("-e", "--external", action="store_true",
4526
+ dest="external", help="Show external commands")
4527
+ parser.add_option("-s", "--suggest", action="store_true",
4528
+ dest="suggestions", help="Show suggestions")
4529
+ self.parser = parser
4532
+ def list_commands(self, native=True, suggest=False, external=True):
4534
+ Lists supported commands.
4536
+ :param native: list native, python-based commands
4537
+ :type native: bool
4538
+ :param external: list external aba-style commands
4539
+ :type external: bool
4542
+ print "Native Fai commands"
4543
+ keys=commands.keys()
4547
+ for i in range(28-len(k)):
4549
+ print space+k+" : "+commands[k]().description
4552
+ print "Unavailable commands and suggested alternatives"
4553
+ key_list = suggestions.keys()
4555
+ for key in key_list:
4556
+ print "%28s : %s" % (key, suggestions[key])
4559
+ fake_aba = abacmds.AbaCmds()
4560
+ if (fake_aba.abadir == ""):
4562
+ print "External commands"
4563
+ fake_aba.list_commands()
4566
+ print "Use help --suggest to list alternatives to tla and aba"\
4568
+ if options.tla_fallthrough and (native or external):
4569
+ print "Fai also supports tla commands."
4571
+def command_help(cmd):
4573
+ Prints help for a command.
4575
+ :param cmd: The name of the command to print help for
4578
+ fake_aba = abacmds.AbaCmds()
4579
+ cmdobj = find_command(cmd)
4580
+ if cmdobj != None:
4582
+ elif suggestions.has_key(cmd):
4583
+ print "Not available\n" + suggestions[cmd]
4585
+ abacmd = fake_aba.is_command(cmd)
4589
+ print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
4593
+class Changes(BaseCommand):
4595
+ the "changes" command: lists differences between trees/revisions:
4598
+ def __init__(self):
4599
+ self.description="Lists what files have changed in the project tree"
4601
+ def get_completer(self, arg, index):
4605
+ tree = arch.tree_root()
4608
+ return cmdutil.iter_revision_completions(arg, tree)
4610
+ def parse_commandline(self, cmdline):
4612
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
4614
+ :param cmdline: A list of arguments to parse
4615
+ :rtype: (options, Revision, Revision/WorkingTree)
4617
+ parser=self.get_parser()
4618
+ (options, args) = parser.parse_args(cmdline)
4620
+ raise cmdutil.GetHelp
4622
+ tree=arch.tree_root()
4623
+ if len(args) == 0:
4624
+ a_spec = cmdutil.comp_revision(tree)
4626
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
4627
+ cmdutil.ensure_archive_registered(a_spec.archive)
4628
+ if len(args) == 2:
4629
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
4630
+ cmdutil.ensure_archive_registered(b_spec.archive)
4633
+ return options, a_spec, b_spec
4635
+ def do_command(self, cmdargs):
4637
+ Master function that perfoms the "changes" command.
4640
+ options, a_spec, b_spec = self.parse_commandline(cmdargs);
4641
+ except cmdutil.CantDetermineRevision, e:
4644
+ except arch.errors.TreeRootError, e:
4647
+ if options.changeset:
4648
+ changeset=options.changeset
4651
+ tmpdir=cmdutil.tmpdir()
4652
+ changeset=tmpdir+"/changeset"
4654
+ delta=arch.iter_delta(a_spec, b_spec, changeset)
4656
+ for line in delta:
4657
+ if cmdutil.chattermatch(line, "changeset:"):
4660
+ cmdutil.colorize(line, options.suppress_chatter)
4661
+ except arch.util.ExecProblem, e:
4662
+ if e.proc.error and e.proc.error.startswith(
4663
+ "missing explicit id for file"):
4664
+ raise MissingID(e)
4667
+ status=delta.status
4670
+ if (options.perform_diff):
4671
+ chan = cmdutil.ChangesetMunger(changeset)
4672
+ chan.read_indices()
4673
+ if isinstance(b_spec, arch.Revision):
4674
+ b_dir = b_spec.library_find()
4677
+ a_dir = a_spec.library_find()
4678
+ if options.diffopts is not None:
4679
+ diffopts = options.diffopts.split()
4680
+ cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
4682
+ cmdutil.show_diffs(delta.changeset)
4684
+ if tmpdir and (os.access(tmpdir, os.X_OK)):
4685
+ shutil.rmtree(tmpdir)
4687
+ def get_parser(self):
4689
+ Returns the options parser to use for the "changes" command.
4691
+ :rtype: cmdutil.CmdOptionParser
4693
+ parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
4695
+ parser.add_option("-d", "--diff", action="store_true",
4696
+ dest="perform_diff", default=False,
4697
+ help="Show diffs in summary")
4698
+ parser.add_option("-c", "--changeset", dest="changeset",
4699
+ help="Store a changeset in the given directory",
4700
+ metavar="DIRECTORY")
4701
+ parser.add_option("-s", "--silent", action="store_true",
4702
+ dest="suppress_chatter", default=False,
4703
+ help="Suppress chatter messages")
4704
+ parser.add_option("--diffopts", dest="diffopts",
4705
+ help="Use the specified diff options",
4706
+ metavar="OPTIONS")
4710
+ def help(self, parser=None):
4712
+ Prints a help message.
4714
+ :param parser: If supplied, the parser to use for generating help. If \
4715
+ not supplied, it is retrieved.
4716
+ :type parser: cmdutil.CmdOptionParser
4718
+ if parser is None:
4719
+ parser=self.get_parser()
4720
+ parser.print_help()
4722
+Performs source-tree comparisons
4724
+If no revision is specified, the current project tree is compared to the
4725
+last-committed revision. If one revision is specified, the current project
4726
+tree is compared to that revision. If two revisions are specified, they are
4727
+compared to each other.
4733
+class ApplyChanges(BaseCommand):
4735
+ Apply differences between two revisions to a tree
4738
+ def __init__(self):
4739
+ self.description="Applies changes to a project tree"
4741
+ def get_completer(self, arg, index):
4745
+ tree = arch.tree_root()
4748
+ return cmdutil.iter_revision_completions(arg, tree)
4750
+ def parse_commandline(self, cmdline, tree):
4752
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
4754
+ :param cmdline: A list of arguments to parse
4755
+ :rtype: (options, Revision, Revision/WorkingTree)
4757
+ parser=self.get_parser()
4758
+ (options, args) = parser.parse_args(cmdline)
4759
+ if len(args) != 2:
4760
+ raise cmdutil.GetHelp
4762
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
4763
+ cmdutil.ensure_archive_registered(a_spec.archive)
4764
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
4765
+ cmdutil.ensure_archive_registered(b_spec.archive)
4766
+ return options, a_spec, b_spec
4768
+ def do_command(self, cmdargs):
4770
+ Master function that performs "apply-changes".
4773
+ tree = arch.tree_root()
4774
+ options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
4775
+ except cmdutil.CantDetermineRevision, e:
4778
+ except arch.errors.TreeRootError, e:
4781
+ delta=cmdutil.apply_delta(a_spec, b_spec, tree)
4782
+ for line in cmdutil.iter_apply_delta_filter(delta):
4783
+ cmdutil.colorize(line, options.suppress_chatter)
4785
+ def get_parser(self):
4787
+ Returns the options parser to use for the "apply-changes" command.
4789
+ :rtype: cmdutil.CmdOptionParser
4791
+ parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
4793
+ parser.add_option("-d", "--diff", action="store_true",
4794
+ dest="perform_diff", default=False,
4795
+ help="Show diffs in summary")
4796
+ parser.add_option("-c", "--changeset", dest="changeset",
4797
+ help="Store a changeset in the given directory",
4798
+ metavar="DIRECTORY")
4799
+ parser.add_option("-s", "--silent", action="store_true",
4800
+ dest="suppress_chatter", default=False,
4801
+ help="Suppress chatter messages")
4804
+ def help(self, parser=None):
4806
+ Prints a help message.
4808
+ :param parser: If supplied, the parser to use for generating help. If \
4809
+ not supplied, it is retrieved.
4810
+ :type parser: cmdutil.CmdOptionParser
4812
+ if parser is None:
4813
+ parser=self.get_parser()
4814
+ parser.print_help()
4816
+Applies changes to a project tree
4818
+Compares two revisions and applies the difference between them to the current
4824
+class Update(BaseCommand):
4826
+ Updates a project tree to a given revision, preserving un-committed hanges.
4829
+ def __init__(self):
4830
+ self.description="Apply the latest changes to the current directory"
4832
+ def get_completer(self, arg, index):
4836
+ tree = arch.tree_root()
4839
+ return cmdutil.iter_revision_completions(arg, tree)
4841
+ def parse_commandline(self, cmdline, tree):
4843
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
4845
+ :param cmdline: A list of arguments to parse
4846
+ :rtype: (options, Revision, Revision/WorkingTree)
4848
+ parser=self.get_parser()
4849
+ (options, args) = parser.parse_args(cmdline)
4851
+ raise cmdutil.GetHelp
4856
+ revision=cmdutil.determine_revision_arch(tree, spec)
4857
+ cmdutil.ensure_archive_registered(revision.archive)
4859
+ mirror_source = cmdutil.get_mirror_source(revision.archive)
4860
+ if mirror_source != None:
4861
+ if cmdutil.prompt("Mirror update"):
4862
+ cmd=cmdutil.mirror_archive(mirror_source,
4863
+ revision.archive, arch.NameParser(revision).get_package_version())
4864
+ for line in arch.chatter_classifier(cmd):
4865
+ cmdutil.colorize(line, options.suppress_chatter)
4867
+ revision=cmdutil.determine_revision_arch(tree, spec)
4869
+ return options, revision
4871
+ def do_command(self, cmdargs):
4873
+ Master function that perfoms the "update" command.
4875
+ tree=arch.tree_root()
4877
+ options, to_revision = self.parse_commandline(cmdargs, tree);
4878
+ except cmdutil.CantDetermineRevision, e:
4881
+ except arch.errors.TreeRootError, e:
4884
+ from_revision=cmdutil.tree_latest(tree)
4885
+ if from_revision==to_revision:
4886
+ print "Tree is already up to date with:\n"+str(to_revision)+"."
4888
+ cmdutil.ensure_archive_registered(from_revision.archive)
4889
+ cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
4890
+ options.patch_forward)
4891
+ for line in cmdutil.iter_apply_delta_filter(cmd):
4892
+ cmdutil.colorize(line)
4893
+ if to_revision.version != tree.tree_version:
4894
+ if cmdutil.prompt("Update version"):
4895
+ tree.tree_version = to_revision.version
4897
+ def get_parser(self):
4899
+ Returns the options parser to use for the "update" command.
4901
+ :rtype: cmdutil.CmdOptionParser
4903
+ parser=cmdutil.CmdOptionParser("fai update [options]"
4904
+ " [revision/version]")
4905
+ parser.add_option("-f", "--forward", action="store_true",
4906
+ dest="patch_forward", default=False,
4907
+ help="pass the --forward option to 'patch'")
4908
+ parser.add_option("-s", "--silent", action="store_true",
4909
+ dest="suppress_chatter", default=False,
4910
+ help="Suppress chatter messages")
4913
+ def help(self, parser=None):
4915
+ Prints a help message.
4917
+ :param parser: If supplied, the parser to use for generating help. If \
4918
+ not supplied, it is retrieved.
4919
+ :type parser: cmdutil.CmdOptionParser
4921
+ if parser is None:
4922
+ parser=self.get_parser()
4923
+ parser.print_help()
4925
+Updates a working tree to the current archive revision
4927
+If a revision or version is specified, that is used instead
4933
+class Commit(BaseCommand):
4935
+ Create a revision based on the changes in the current tree.
4938
+ def __init__(self):
4939
+ self.description="Write local changes to the archive"
4941
+ def get_completer(self, arg, index):
4944
+ return iter_modified_file_completions(arch.tree_root(), arg)
4945
+# return iter_source_file_completions(arch.tree_root(), arg)
4947
+ def parse_commandline(self, cmdline, tree):
4949
+ Parse commandline arguments. Raise cmtutil.GetHelp if help is needed.
4951
+ :param cmdline: A list of arguments to parse
4952
+ :rtype: (options, Revision, Revision/WorkingTree)
4954
+ parser=self.get_parser()
4955
+ (options, args) = parser.parse_args(cmdline)
4957
+ if len(args) == 0:
4959
+ revision=cmdutil.determine_revision_arch(tree, options.version)
4960
+ return options, revision.get_version(), args
4962
+ def do_command(self, cmdargs):
4964
+ Master function that perfoms the "commit" command.
4966
+ tree=arch.tree_root()
4967
+ options, version, files = self.parse_commandline(cmdargs, tree)
4968
+ if options.__dict__.has_key("base") and options.base:
4969
+ base = cmdutil.determine_revision_tree(tree, options.base)
4971
+ base = cmdutil.submit_revision(tree)
4973
+ writeversion=version
4974
+ archive=version.archive
4975
+ source=cmdutil.get_mirror_source(archive)
4977
+ writethrough="implicit"
4980
+ if writethrough=="explicit" and \
4981
+ cmdutil.prompt("Writethrough"):
4982
+ writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
4983
+ elif writethrough=="none":
4984
+ raise CommitToMirror(archive)
4986
+ elif archive.is_mirror:
4987
+ raise CommitToMirror(archive)
4990
+ last_revision=tree.iter_logs(version, True).next().revision
4991
+ except StopIteration, e:
4992
+ if cmdutil.prompt("Import from commit"):
4993
+ return do_import(version)
4995
+ raise NoVersionLogs(version)
4996
+ if last_revision!=version.iter_revisions(True).next():
4997
+ if not cmdutil.prompt("Out of date"):
5003
+ if not cmdutil.has_changed(version):
5004
+ if not cmdutil.prompt("Empty commit"):
5006
+ except arch.util.ExecProblem, e:
5007
+ if e.proc.error and e.proc.error.startswith(
5008
+ "missing explicit id for file"):
5009
+ raise MissingID(e)
5012
+ log = tree.log_message(create=False)
5015
+ if cmdutil.prompt("Create log"):
5018
+ except cmdutil.NoEditorSpecified, e:
5019
+ raise CommandFailed(e)
5020
+ log = tree.log_message(create=False)
5022
+ raise NoLogMessage
5023
+ if log["Summary"] is None or len(log["Summary"].strip()) == 0:
5024
+ if not cmdutil.prompt("Omit log summary"):
5025
+ raise errors.NoLogSummary
5027
+ for line in tree.iter_commit(version, seal=options.seal_version,
5028
+ base=base, out_of_date_ok=allow_old, file_list=files):
5029
+ cmdutil.colorize(line, options.suppress_chatter)
5031
+ except arch.util.ExecProblem, e:
5032
+ if e.proc.error and e.proc.error.startswith(
5033
+ "These files violate naming conventions:"):
5034
+ raise LintFailure(e.proc.error)
5038
+ def get_parser(self):
5040
+ Returns the options parser to use for the "commit" command.
5042
+ :rtype: cmdutil.CmdOptionParser
5045
+ parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
5047
+ parser.add_option("--seal", action="store_true",
5048
+ dest="seal_version", default=False,
5049
+ help="seal this version")
5050
+ parser.add_option("-v", "--version", dest="version",
5051
+ help="Use the specified version",
5052
+ metavar="VERSION")
5053
+ parser.add_option("-s", "--silent", action="store_true",
5054
+ dest="suppress_chatter", default=False,
5055
+ help="Suppress chatter messages")
5056
+ if cmdutil.supports_switch("commit", "--base"):
5057
+ parser.add_option("--base", dest="base", help="",
5058
+ metavar="REVISION")
5061
+ def help(self, parser=None):
5063
+ Prints a help message.
5065
+ :param parser: If supplied, the parser to use for generating help. If \
5066
+ not supplied, it is retrieved.
5067
+ :type parser: cmdutil.CmdOptionParser
5069
+ if parser is None:
5070
+ parser=self.get_parser()
5071
+ parser.print_help()
5073
+Updates a working tree to the current archive revision
5075
+If a version is specified, that is used instead
5082
+class CatLog(BaseCommand):
5084
+ Print the log of a given file (from current tree)
5086
+ def __init__(self):
5087
+ self.description="Prints the patch log for a revision"
5089
+ def get_completer(self, arg, index):
5093
+ tree = arch.tree_root()
5096
+ return cmdutil.iter_revision_completions(arg, tree)
5098
+ def do_command(self, cmdargs):
5100
+ Master function that perfoms the "cat-log" command.
5102
+ parser=self.get_parser()
5103
+ (options, args) = parser.parse_args(cmdargs)
5105
+ tree = arch.tree_root()
5106
+ except arch.errors.TreeRootError, e:
5112
+ raise cmdutil.GetHelp()
5115
+ revision = cmdutil.determine_revision_tree(tree, spec)
5117
+ revision = cmdutil.determine_revision_arch(tree, spec)
5118
+ except cmdutil.CantDetermineRevision, e:
5119
+ raise CommandFailedWrapper(e)
5122
+ use_tree = (options.source == "tree" or \
5123
+ (options.source == "any" and tree))
5124
+ use_arch = (options.source == "archive" or options.source == "any")
5128
+ for log in tree.iter_logs(revision.get_version()):
5129
+ if log.revision == revision:
5133
+ if log is None and use_arch:
5134
+ cmdutil.ensure_revision_exists(revision)
5135
+ log = arch.Patchlog(revision)
5136
+ if log is not None:
5137
+ for item in log.items():
5138
+ print "%s: %s" % item
5139
+ print log.description
5141
+ def get_parser(self):
5143
+ Returns the options parser to use for the "cat-log" command.
5145
+ :rtype: cmdutil.CmdOptionParser
5147
+ parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
5148
+ parser.add_option("--archive", action="store_const", dest="source",
5149
+ const="archive", default="any",
5150
+ help="Always get the log from the archive")
5151
+ parser.add_option("--tree", action="store_const", dest="source",
5152
+ const="tree", help="Always get the log from the tree")
5155
+ def help(self, parser=None):
5157
+ Prints a help message.
5159
+ :param parser: If supplied, the parser to use for generating help. If \
5160
+ not supplied, it is retrieved.
5161
+ :type parser: cmdutil.CmdOptionParser
5164
+ parser=self.get_parser()
5165
+ parser.print_help()
5167
+Prints the log for the specified revision
5172
+class Revert(BaseCommand):
5173
+ """ Reverts a tree (or aspects of it) to a revision
5175
+ def __init__(self):
5176
+ self.description="Reverts a tree (or aspects of it) to a revision "
5178
+ def get_completer(self, arg, index):
5182
+ tree = arch.tree_root()
5185
+ return iter_modified_file_completions(tree, arg)
5187
+ def do_command(self, cmdargs):
5189
+ Master function that perfoms the "revert" command.
5191
+ parser=self.get_parser()
5192
+ (options, args) = parser.parse_args(cmdargs)
5194
+ tree = arch.tree_root()
5195
+ except arch.errors.TreeRootError, e:
5196
+ raise CommandFailed(e)
5198
+ if options.revision is not None:
5199
+ spec=options.revision
5201
+ if spec is not None:
5202
+ revision = cmdutil.determine_revision_tree(tree, spec)
5204
+ revision = cmdutil.comp_revision(tree)
5205
+ except cmdutil.CantDetermineRevision, e:
5206
+ raise CommandFailedWrapper(e)
5209
+ if options.file_contents or options.file_perms or options.deletions\
5210
+ or options.additions or options.renames or options.hunk_prompt:
5211
+ munger = cmdutil.MungeOpts()
5212
+ munger.hunk_prompt = options.hunk_prompt
5214
+ if len(args) > 0 or options.logs or options.pattern_files or \
5216
+ if munger is None:
5217
+ munger = cmdutil.MungeOpts(True)
5218
+ munger.all_types(True)
5220
+ t_cwd = cmdutil.tree_cwd(tree)
5222
+ if len(t_cwd) > 0:
5224
+ name = "./" + t_cwd + name
5225
+ munger.add_keep_file(name);
5227
+ if options.file_perms:
5228
+ munger.file_perms = True
5229
+ if options.file_contents:
5230
+ munger.file_contents = True
5231
+ if options.deletions:
5232
+ munger.deletions = True
5233
+ if options.additions:
5234
+ munger.additions = True
5235
+ if options.renames:
5236
+ munger.renames = True
5238
+ munger.add_keep_pattern('^\./\{arch\}/[^=].*')
5239
+ if options.control:
5240
+ munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
5241
+ "/\.arch-inventory$")
5242
+ if options.pattern_files:
5243
+ munger.add_keep_pattern(options.pattern_files)
5245
+ for line in cmdutil.revert(tree, revision, munger,
5246
+ not options.no_output):
5247
+ cmdutil.colorize(line)
5250
+ def get_parser(self):
5252
+ Returns the options parser to use for the "cat-log" command.
5254
+ :rtype: cmdutil.CmdOptionParser
5256
+ parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
5257
+ parser.add_option("", "--contents", action="store_true",
5258
+ dest="file_contents",
5259
+ help="Revert file content changes")
5260
+ parser.add_option("", "--permissions", action="store_true",
5261
+ dest="file_perms",
5262
+ help="Revert file permissions changes")
5263
+ parser.add_option("", "--deletions", action="store_true",
5265
+ help="Restore deleted files")
5266
+ parser.add_option("", "--additions", action="store_true",
5268
+ help="Remove added files")
5269
+ parser.add_option("", "--renames", action="store_true",
5271
+ help="Revert file names")
5272
+ parser.add_option("--hunks", action="store_true",
5273
+ dest="hunk_prompt", default=False,
5274
+ help="Prompt which hunks to revert")
5275
+ parser.add_option("--pattern-files", dest="pattern_files",
5276
+ help="Revert files that match this pattern",
5278
+ parser.add_option("--logs", action="store_true",
5279
+ dest="logs", default=False,
5280
+ help="Revert only logs")
5281
+ parser.add_option("--control-files", action="store_true",
5282
+ dest="control", default=False,
5283
+ help="Revert logs and other control files")
5284
+ parser.add_option("-n", "--no-output", action="store_true",
5286
+ help="Don't keep an undo changeset")
5287
+ parser.add_option("--revision", dest="revision",
5288
+ help="Revert to the specified revision",
5289
+ metavar="REVISION")
5292
+ def help(self, parser=None):
5294
+ Prints a help message.
5296
+ :param parser: If supplied, the parser to use for generating help. If \
5297
+ not supplied, it is retrieved.
5298
+ :type parser: cmdutil.CmdOptionParser
5301
+ parser=self.get_parser()
5302
+ parser.print_help()
5304
+Reverts changes in the current working tree. If no flags are specified, all
5305
+types of changes are reverted. Otherwise, only selected types of changes are
5308
+If a revision is specified on the commandline, differences between the current
5309
+tree and that revision are reverted. If a version is specified, the current
5310
+tree is used to determine the revision.
5312
+If files are specified, only those files listed will have any changes applied.
5313
+To specify a renamed file, you can use either the old or new name. (or both!)
5315
+Unless "-n" is specified, reversions can be undone with "redo".
5319
+class Revision(BaseCommand):
5321
+ Print a revision name based on a revision specifier
5323
+ def __init__(self):
5324
+ self.description="Prints the name of a revision"
5326
+ def get_completer(self, arg, index):
5330
+ tree = arch.tree_root()
5333
+ return cmdutil.iter_revision_completions(arg, tree)
5335
+ def do_command(self, cmdargs):
5337
+ Master function that perfoms the "revision" command.
5339
+ parser=self.get_parser()
5340
+ (options, args) = parser.parse_args(cmdargs)
5343
+ tree = arch.tree_root()
5344
+ except arch.errors.TreeRootError:
5351
+ raise cmdutil.GetHelp
5354
+ revision = cmdutil.determine_revision_tree(tree, spec)
5356
+ revision = cmdutil.determine_revision_arch(tree, spec)
5357
+ except cmdutil.CantDetermineRevision, e:
5360
+ print options.display(revision)
5362
+ def get_parser(self):
5364
+ Returns the options parser to use for the "revision" command.
5366
+ :rtype: cmdutil.CmdOptionParser
5368
+ parser=cmdutil.CmdOptionParser("fai revision [revision]")
5369
+ parser.add_option("", "--location", action="store_const",
5370
+ const=paths.determine_path, dest="display",
5371
+ help="Show location instead of name", default=str)
5372
+ parser.add_option("--import", action="store_const",
5373
+ const=paths.determine_import_path, dest="display",
5374
+ help="Show location of import file")
5375
+ parser.add_option("--log", action="store_const",
5376
+ const=paths.determine_log_path, dest="display",
5377
+ help="Show location of log file")
5378
+ parser.add_option("--patch", action="store_const",
5379
+ dest="display", const=paths.determine_patch_path,
5380
+ help="Show location of patchfile")
5381
+ parser.add_option("--continuation", action="store_const",
5382
+ const=paths.determine_continuation_path,
5384
+ help="Show location of continuation file")
5385
+ parser.add_option("--cacherev", action="store_const",
5386
+ const=paths.determine_cacherev_path, dest="display",
5387
+ help="Show location of cacherev file")
5390
+ def help(self, parser=None):
5392
+ Prints a help message.
5394
+ :param parser: If supplied, the parser to use for generating help. If \
5395
+ not supplied, it is retrieved.
5396
+ :type parser: cmdutil.CmdOptionParser
5399
+ parser=self.get_parser()
5400
+ parser.print_help()
5402
+Expands aliases and prints the name of the specified revision. Instead of
5403
+the name, several options can be used to print locations. If more than one is
5404
+specified, the last one is used.
5409
+def require_version_exists(version, spec):
5410
+ if not version.exists():
5411
+ raise cmdutil.CantDetermineVersion(spec,
5412
+ "The version %s does not exist." \
5415
+class Revisions(BaseCommand):
5417
+ Print a revision name based on a revision specifier
5419
+ def __init__(self):
5420
+ self.description="Lists revisions"
5422
+ def do_command(self, cmdargs):
5424
+ Master function that perfoms the "revision" command.
5426
+ (options, args) = self.get_parser().parse_args(cmdargs)
5428
+ raise cmdutil.GetHelp
5430
+ self.tree = arch.tree_root()
5431
+ except arch.errors.TreeRootError:
5434
+ iter = self.get_iterator(options.type, args, options.reverse,
5436
+ except cmdutil.CantDetermineRevision, e:
5437
+ raise CommandFailedWrapper(e)
5439
+ if options.skip is not None:
5440
+ iter = cmdutil.iter_skip(iter, int(options.skip))
5442
+ for revision in iter:
5444
+ if isinstance(revision, arch.Patchlog):
5446
+ revision=revision.revision
5447
+ print options.display(revision)
5448
+ if log is None and (options.summary or options.creator or
5449
+ options.date or options.merges):
5450
+ log = revision.patchlog
5451
+ if options.creator:
5452
+ print " %s" % log.creator
5454
+ print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
5455
+ if options.summary:
5456
+ print " %s" % log.summary
5457
+ if options.merges:
5458
+ showed_title = False
5459
+ for revision in log.merged_patches:
5460
+ if not showed_title:
5462
+ showed_title = True
5463
+ print " %s" % revision
5465
+ def get_iterator(self, type, args, reverse, modified):
5470
+ if modified is not None:
5471
+ iter = cmdutil.modified_iter(modified, self.tree)
5475
+ return cmdutil.iter_reverse(iter)
5476
+ elif type == "archive":
5478
+ if self.tree is None:
5479
+ raise cmdutil.CantDetermineRevision("",
5480
+ "Not in a project tree")
5481
+ version = cmdutil.determine_version_tree(spec, self.tree)
5483
+ version = cmdutil.determine_version_arch(spec, self.tree)
5484
+ cmdutil.ensure_archive_registered(version.archive)
5485
+ require_version_exists(version, spec)
5486
+ return version.iter_revisions(reverse)
5487
+ elif type == "cacherevs":
5489
+ if self.tree is None:
5490
+ raise cmdutil.CantDetermineRevision("",
5491
+ "Not in a project tree")
5492
+ version = cmdutil.determine_version_tree(spec, self.tree)
5494
+ version = cmdutil.determine_version_arch(spec, self.tree)
5495
+ cmdutil.ensure_archive_registered(version.archive)
5496
+ require_version_exists(version, spec)
5497
+ return cmdutil.iter_cacherevs(version, reverse)
5498
+ elif type == "library":
5500
+ if self.tree is None:
5501
+ raise cmdutil.CantDetermineRevision("",
5502
+ "Not in a project tree")
5503
+ version = cmdutil.determine_version_tree(spec, self.tree)
5505
+ version = cmdutil.determine_version_arch(spec, self.tree)
5506
+ return version.iter_library_revisions(reverse)
5507
+ elif type == "logs":
5508
+ if self.tree is None:
5509
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5510
+ return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
5511
+ self.tree), reverse)
5512
+ elif type == "missing" or type == "skip-present":
5513
+ if self.tree is None:
5514
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5515
+ skip = (type == "skip-present")
5516
+ version = cmdutil.determine_version_tree(spec, self.tree)
5517
+ cmdutil.ensure_archive_registered(version.archive)
5518
+ require_version_exists(version, spec)
5519
+ return cmdutil.iter_missing(self.tree, version, reverse,
5520
+ skip_present=skip)
5522
+ elif type == "present":
5523
+ if self.tree is None:
5524
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5525
+ version = cmdutil.determine_version_tree(spec, self.tree)
5526
+ cmdutil.ensure_archive_registered(version.archive)
5527
+ require_version_exists(version, spec)
5528
+ return cmdutil.iter_present(self.tree, version, reverse)
5530
+ elif type == "new-merges" or type == "direct-merges":
5531
+ if self.tree is None:
5532
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5533
+ version = cmdutil.determine_version_tree(spec, self.tree)
5534
+ cmdutil.ensure_archive_registered(version.archive)
5535
+ require_version_exists(version, spec)
5536
+ iter = cmdutil.iter_new_merges(self.tree, version, reverse)
5537
+ if type == "new-merges":
5539
+ elif type == "direct-merges":
5540
+ return cmdutil.direct_merges(iter)
5542
+ elif type == "missing-from":
5543
+ if self.tree is None:
5544
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5545
+ revision = cmdutil.determine_revision_tree(self.tree, spec)
5546
+ libtree = cmdutil.find_or_make_local_revision(revision)
5547
+ return cmdutil.iter_missing(libtree, self.tree.tree_version,
5550
+ elif type == "partner-missing":
5551
+ return cmdutil.iter_partner_missing(self.tree, reverse)
5553
+ elif type == "ancestry":
5554
+ revision = cmdutil.determine_revision_tree(self.tree, spec)
5555
+ iter = cmdutil._iter_ancestry(self.tree, revision)
5559
+ return cmdutil.iter_reverse(iter)
5561
+ elif type == "dependencies" or type == "non-dependencies":
5562
+ nondeps = (type == "non-dependencies")
5563
+ revision = cmdutil.determine_revision_tree(self.tree, spec)
5564
+ anc_iter = cmdutil._iter_ancestry(self.tree, revision)
5565
+ iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
5567
+ return iter_depends
5569
+ return cmdutil.iter_reverse(iter_depends)
5570
+ elif type == "micro":
5571
+ return cmdutil.iter_micro(self.tree)
5574
+ def get_parser(self):
5576
+ Returns the options parser to use for the "revision" command.
5578
+ :rtype: cmdutil.CmdOptionParser
5580
+ parser=cmdutil.CmdOptionParser("fai revisions [revision]")
5581
+ select = cmdutil.OptionGroup(parser, "Selection options",
5582
+ "Control which revisions are listed. These options"
5583
+ " are mutually exclusive. If more than one is"
5584
+ " specified, the last is used.")
5585
+ select.add_option("", "--archive", action="store_const",
5586
+ const="archive", dest="type", default="archive",
5587
+ help="List all revisions in the archive")
5588
+ select.add_option("", "--cacherevs", action="store_const",
5589
+ const="cacherevs", dest="type",
5590
+ help="List all revisions stored in the archive as "
5591
+ "complete copies")
5592
+ select.add_option("", "--logs", action="store_const",
5593
+ const="logs", dest="type",
5594
+ help="List revisions that have a patchlog in the "
5596
+ select.add_option("", "--missing", action="store_const",
5597
+ const="missing", dest="type",
5598
+ help="List revisions from the specified version that"
5599
+ " have no patchlog in the tree")
5600
+ select.add_option("", "--skip-present", action="store_const",
5601
+ const="skip-present", dest="type",
5602
+ help="List revisions from the specified version that"
5603
+ " have no patchlogs at all in the tree")
5604
+ select.add_option("", "--present", action="store_const",
5605
+ const="present", dest="type",
5606
+ help="List revisions from the specified version that"
5607
+ " have no patchlog in the tree, but can't be merged")
5608
+ select.add_option("", "--missing-from", action="store_const",
5609
+ const="missing-from", dest="type",
5610
+ help="List revisions from the specified revision "
5611
+ "that have no patchlog for the tree version")
5612
+ select.add_option("", "--partner-missing", action="store_const",
5613
+ const="partner-missing", dest="type",
5614
+ help="List revisions in partner versions that are"
5616
+ select.add_option("", "--new-merges", action="store_const",
5617
+ const="new-merges", dest="type",
5618
+ help="List revisions that have had patchlogs added"
5619
+ " to the tree since the last commit")
5620
+ select.add_option("", "--direct-merges", action="store_const",
5621
+ const="direct-merges", dest="type",
5622
+ help="List revisions that have been directly added"
5623
+ " to tree since the last commit ")
5624
+ select.add_option("", "--library", action="store_const",
5625
+ const="library", dest="type",
5626
+ help="List revisions in the revision library")
5627
+ select.add_option("", "--ancestry", action="store_const",
5628
+ const="ancestry", dest="type",
5629
+ help="List revisions that are ancestors of the "
5630
+ "current tree version")
5632
+ select.add_option("", "--dependencies", action="store_const",
5633
+ const="dependencies", dest="type",
5634
+ help="List revisions that the given revision "
5637
+ select.add_option("", "--non-dependencies", action="store_const",
5638
+ const="non-dependencies", dest="type",
5639
+ help="List revisions that the given revision "
5640
+ "does not depend on")
5642
+ select.add_option("--micro", action="store_const",
5643
+ const="micro", dest="type",
5644
+ help="List partner revisions aimed for this "
5647
+ select.add_option("", "--modified", dest="modified",
5648
+ help="List tree ancestor revisions that modified a "
5649
+ "given file", metavar="FILE[:LINE]")
5651
+ parser.add_option("", "--skip", dest="skip",
5652
+ help="Skip revisions. Positive numbers skip from "
5653
+ "beginning, negative skip from end.",
5656
+ parser.add_option_group(select)
5658
+ format = cmdutil.OptionGroup(parser, "Revision format options",
5659
+ "These control the appearance of listed revisions")
5660
+ format.add_option("", "--location", action="store_const",
5661
+ const=paths.determine_path, dest="display",
5662
+ help="Show location instead of name", default=str)
5663
+ format.add_option("--import", action="store_const",
5664
+ const=paths.determine_import_path, dest="display",
5665
+ help="Show location of import file")
5666
+ format.add_option("--log", action="store_const",
5667
+ const=paths.determine_log_path, dest="display",
5668
+ help="Show location of log file")
5669
+ format.add_option("--patch", action="store_const",
5670
+ dest="display", const=paths.determine_patch_path,
5671
+ help="Show location of patchfile")
5672
+ format.add_option("--continuation", action="store_const",
5673
+ const=paths.determine_continuation_path,
5675
+ help="Show location of continuation file")
5676
+ format.add_option("--cacherev", action="store_const",
5677
+ const=paths.determine_cacherev_path, dest="display",
5678
+ help="Show location of cacherev file")
5679
+ parser.add_option_group(format)
5680
+ display = cmdutil.OptionGroup(parser, "Display format options",
5681
+ "These control the display of data")
5682
+ display.add_option("-r", "--reverse", action="store_true",
5683
+ dest="reverse", help="Sort from newest to oldest")
5684
+ display.add_option("-s", "--summary", action="store_true",
5685
+ dest="summary", help="Show patchlog summary")
5686
+ display.add_option("-D", "--date", action="store_true",
5687
+ dest="date", help="Show patchlog date")
5688
+ display.add_option("-c", "--creator", action="store_true",
5689
+ dest="creator", help="Show the id that committed the"
5691
+ display.add_option("-m", "--merges", action="store_true",
5692
+ dest="merges", help="Show the revisions that were"
5694
+ parser.add_option_group(display)
5696
+ def help(self, parser=None):
5697
+ """Attempt to explain the revisions command
5699
+ :param parser: If supplied, used to determine options
5702
+ parser=self.get_parser()
5703
+ parser.print_help()
5704
+ print """List revisions.
5709
+class Get(BaseCommand):
5711
+ Retrieve a revision from the archive
5713
+ def __init__(self):
5714
+ self.description="Retrieve a revision from the archive"
5715
+ self.parser=self.get_parser()
5718
+ def get_completer(self, arg, index):
5722
+ tree = arch.tree_root()
5725
+ return cmdutil.iter_revision_completions(arg, tree)
5728
+ def do_command(self, cmdargs):
5730
+ Master function that perfoms the "get" command.
5732
+ (options, args) = self.parser.parse_args(cmdargs)
5734
+ return self.help()
5736
+ tree = arch.tree_root()
5737
+ except arch.errors.TreeRootError:
5742
+ revision, arch_loc = paths.full_path_decode(args[0])
5743
+ except Exception, e:
5744
+ revision = cmdutil.determine_revision_arch(tree, args[0],
5745
+ check_existence=False, allow_package=True)
5747
+ directory = args[1]
5749
+ directory = str(revision.nonarch)
5750
+ if os.path.exists(directory):
5751
+ raise DirectoryExists(directory)
5752
+ cmdutil.ensure_archive_registered(revision.archive, arch_loc)
5754
+ cmdutil.ensure_revision_exists(revision)
5755
+ except cmdutil.NoSuchRevision, e:
5756
+ raise CommandFailedWrapper(e)
5758
+ link = cmdutil.prompt ("get link")
5759
+ for line in cmdutil.iter_get(revision, directory, link,
5760
+ options.no_pristine,
5761
+ options.no_greedy_add):
5762
+ cmdutil.colorize(line)
5764
+ def get_parser(self):
5766
+ Returns the options parser to use for the "get" command.
5768
+ :rtype: cmdutil.CmdOptionParser
5770
+ parser=cmdutil.CmdOptionParser("fai get revision [dir]")
5771
+ parser.add_option("--no-pristine", action="store_true",
5772
+ dest="no_pristine",
5773
+ help="Do not make pristine copy for reference")
5774
+ parser.add_option("--no-greedy-add", action="store_true",
5775
+ dest="no_greedy_add",
5776
+ help="Never add to greedy libraries")
5780
+ def help(self, parser=None):
5782
+ Prints a help message.
5784
+ :param parser: If supplied, the parser to use for generating help. If \
5785
+ not supplied, it is retrieved.
5786
+ :type parser: cmdutil.CmdOptionParser
5789
+ parser=self.get_parser()
5790
+ parser.print_help()
5792
+Expands aliases and constructs a project tree for a revision. If the optional
5793
+"dir" argument is provided, the project tree will be stored in this directory.
5798
+class PromptCmd(cmd.Cmd):
5799
+ def __init__(self):
5800
+ cmd.Cmd.__init__(self)
5801
+ self.prompt = "Fai> "
5803
+ self.tree = arch.tree_root()
5808
+ self.fake_aba = abacmds.AbaCmds()
5809
+ self.identchars += '-'
5810
+ self.history_file = os.path.expanduser("~/.fai-history")
5811
+ readline.set_completer_delims(string.whitespace)
5812
+ if os.access(self.history_file, os.R_OK) and \
5813
+ os.path.isfile(self.history_file):
5814
+ readline.read_history_file(self.history_file)
5816
+ def write_history(self):
5817
+ readline.write_history_file(self.history_file)
5819
+ def do_quit(self, args):
5820
+ self.write_history()
5823
+ def do_exit(self, args):
5824
+ self.do_quit(args)
5826
+ def do_EOF(self, args):
5828
+ self.do_quit(args)
5830
+ def postcmd(self, line, bar):
5834
+ def set_prompt(self):
5835
+ if self.tree is not None:
5837
+ version = " "+self.tree.tree_version.nonarch
5842
+ self.prompt = "Fai%s> " % version
5844
+ def set_title(self, command=None):
5846
+ version = self.tree.tree_version.nonarch
5848
+ version = "[no version]"
5849
+ if command is None:
5851
+ sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
5853
+ def do_cd(self, line):
5857
+ os.chdir(os.path.expanduser(line))
5858
+ except Exception, e:
5861
+ self.tree = arch.tree_root()
5865
+ def do_help(self, line):
5868
+ def default(self, line):
5869
+ args = line.split()
5870
+ if find_command(args[0]):
5872
+ find_command(args[0]).do_command(args[1:])
5873
+ except cmdutil.BadCommandOption, e:
5875
+ except cmdutil.GetHelp, e:
5876
+ find_command(args[0]).help()
5877
+ except CommandFailed, e:
5879
+ except arch.errors.ArchiveNotRegistered, e:
5881
+ except KeyboardInterrupt, e:
5882
+ print "Interrupted"
5883
+ except arch.util.ExecProblem, e:
5884
+ print e.proc.error.rstrip('\n')
5885
+ except cmdutil.CantDetermineVersion, e:
5887
+ except cmdutil.CantDetermineRevision, e:
5889
+ except Exception, e:
5890
+ print "Unhandled error:\n%s" % cmdutil.exception_str(e)
5892
+ elif suggestions.has_key(args[0]):
5893
+ print suggestions[args[0]]
5895
+ elif self.fake_aba.is_command(args[0]):
5898
+ tree = arch.tree_root()
5899
+ except arch.errors.TreeRootError:
5901
+ cmd = self.fake_aba.is_command(args[0])
5903
+ cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
5904
+ except KeyboardInterrupt, e:
5905
+ print "Interrupted"
5907
+ elif options.tla_fallthrough and args[0] != "rm" and \
5908
+ cmdutil.is_tla_command(args[0]):
5912
+ tree = arch.tree_root()
5913
+ except arch.errors.TreeRootError:
5915
+ args = cmdutil.expand_prefix_alias(args, tree)
5916
+ arch.util.exec_safe('tla', args, stderr=sys.stderr,
5918
+ except arch.util.ExecProblem, e:
5920
+ except KeyboardInterrupt, e:
5921
+ print "Interrupted"
5925
+ tree = arch.tree_root()
5926
+ except arch.errors.TreeRootError:
5929
+ os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
5930
+ except KeyboardInterrupt, e:
5931
+ print "Interrupted"
5933
+ def completenames(self, text, line, begidx, endidx):
5935
+ iter = iter_command_names(self.fake_aba)
5938
+ arg = line.split()[-1]
5941
+ iter = iter_munged_completions(iter, arg, text)
5942
+ except Exception, e:
5946
+ def completedefault(self, text, line, begidx, endidx):
5947
+ """Perform completion for native commands.
5949
+ :param text: The text to complete
5951
+ :param line: The entire line to complete
5953
+ :param begidx: The start of the text in the line
5955
+ :param endidx: The end of the text in the line
5959
+ (cmd, args, foo) = self.parseline(line)
5960
+ command_obj=find_command(cmd)
5961
+ if command_obj is not None:
5962
+ return command_obj.complete(args.split(), text)
5963
+ elif not self.fake_aba.is_command(cmd) and \
5964
+ cmdutil.is_tla_command(cmd):
5965
+ iter = cmdutil.iter_supported_switches(cmd)
5967
+ arg = args.split()[-1]
5970
+ if arg.startswith("-"):
5971
+ return list(iter_munged_completions(iter, arg, text))
5973
+ return list(iter_munged_completions(
5974
+ iter_file_completions(arg), arg, text))
5979
+ arg = args.split()[-1]
5982
+ iter = iter_dir_completions(arg)
5983
+ iter = iter_munged_completions(iter, arg, text)
5986
+ arg = args.split()[-1]
5987
+ return list(iter_munged_completions(iter_file_completions(arg),
5990
+ return self.completenames(text, line, begidx, endidx)
5991
+ except Exception, e:
5995
+def iter_command_names(fake_aba):
5996
+ for entry in cmdutil.iter_combine([commands.iterkeys(),
5997
+ fake_aba.get_commands(),
5998
+ cmdutil.iter_tla_commands(False)]):
5999
+ if not suggestions.has_key(str(entry)):
6003
+def iter_file_completions(arg, only_dirs = False):
6004
+ """Generate an iterator that iterates through filename completions.
6006
+ :param arg: The filename fragment to match
6008
+ :param only_dirs: If true, match only directories
6009
+ :type only_dirs: bool
6013
+ extras = [".", ".."]
6016
+ (dir, file) = os.path.split(arg)
6018
+ listingdir = os.path.expanduser(dir)
6021
+ for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
6023
+ userfile = dir+'/'+file
6026
+ if userfile.startswith(arg):
6027
+ if os.path.isdir(listingdir+'/'+file):
6030
+ elif not only_dirs:
6033
+def iter_munged_completions(iter, arg, text):
6034
+ for completion in iter:
6035
+ completion = str(completion)
6036
+ if completion.startswith(arg):
6037
+ yield completion[len(arg)-len(text):]
6039
+def iter_source_file_completions(tree, arg):
6040
+ treepath = cmdutil.tree_cwd(tree)
6041
+ if len(treepath) > 0:
6045
+ for file in tree.iter_inventory(dirs, source=True, both=True):
6046
+ file = file_completion_match(file, treepath, arg)
6047
+ if file is not None:
6051
+def iter_untagged(tree, dirs):
6052
+ for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False,
6053
+ categories=arch_core.non_root,
6054
+ control_files=True):
6058
+def iter_untagged_completions(tree, arg):
6059
+ """Generate an iterator for all visible untagged files that match arg.
6061
+ :param tree: The tree to look for untagged files in
6062
+ :type tree: `arch.WorkingTree`
6063
+ :param arg: The argument to match
6065
+ :return: An iterator of all matching untagged files
6066
+ :rtype: iterator of str
6068
+ treepath = cmdutil.tree_cwd(tree)
6069
+ if len(treepath) > 0:
6074
+ for file in iter_untagged(tree, dirs):
6075
+ file = file_completion_match(file, treepath, arg)
6076
+ if file is not None:
6080
+def file_completion_match(file, treepath, arg):
6081
+ """Determines whether a file within an arch tree matches the argument.
6083
+ :param file: The rooted filename
6085
+ :param treepath: The path to the cwd within the tree
6086
+ :type treepath: str
6087
+ :param arg: The prefix to match
6088
+ :return: The completion name, or None if not a match
6091
+ if not file.startswith(treepath):
6093
+ if treepath != "":
6094
+ file = file[len(treepath)+1:]
6096
+ if not file.startswith(arg):
6098
+ if os.path.isdir(file):
6102
+def iter_modified_file_completions(tree, arg):
6103
+ """Returns a list of modified files that match the specified prefix.
6105
+ :param tree: The current tree
6106
+ :type tree: `arch.WorkingTree`
6107
+ :param arg: The prefix to match
6110
+ treepath = cmdutil.tree_cwd(tree)
6111
+ tmpdir = cmdutil.tmpdir()
6112
+ changeset = tmpdir+"/changeset"
6114
+ revision = cmdutil.determine_revision_tree(tree)
6115
+ for line in arch.iter_delta(revision, tree, changeset):
6116
+ if isinstance(line, arch.FileModification):
6117
+ file = file_completion_match(line.name[1:], treepath, arg)
6118
+ if file is not None:
6119
+ completions.append(file)
6120
+ shutil.rmtree(tmpdir)
6121
+ return completions
6123
+def iter_dir_completions(arg):
6124
+ """Generate an iterator that iterates through directory name completions.
6126
+ :param arg: The directory name fragment to match
6129
+ return iter_file_completions(arg, True)
6131
+class Shell(BaseCommand):
6132
+ def __init__(self):
6133
+ self.description = "Runs Fai as a shell"
6135
+ def do_command(self, cmdargs):
6136
+ if len(cmdargs)!=0:
6137
+ raise cmdutil.GetHelp
6138
+ prompt = PromptCmd()
6142
+ prompt.write_history()
6144
+class AddID(BaseCommand):
6146
+ Adds an inventory id for the given file
6148
+ def __init__(self):
6149
+ self.description="Add an inventory id for a given file"
6151
+ def get_completer(self, arg, index):
6152
+ tree = arch.tree_root()
6153
+ return iter_untagged_completions(tree, arg)
6155
+ def do_command(self, cmdargs):
6157
+ Master function that perfoms the "revision" command.
6159
+ parser=self.get_parser()
6160
+ (options, args) = parser.parse_args(cmdargs)
6162
+ tree = arch.tree_root()
6164
+ if (len(args) == 0) == (options.untagged == False):
6165
+ raise cmdutil.GetHelp
6167
+ #if options.id and len(args) != 1:
6168
+ # print "If --id is specified, only one file can be named."
6171
+ method = tree.tagging_method
6173
+ if options.id_type == "tagline":
6174
+ if method != "tagline":
6175
+ if not cmdutil.prompt("Tagline in other tree"):
6176
+ if method == "explicit":
6177
+ options.id_type == explicit
6179
+ print "add-id not supported for \"%s\" tagging method"\
6183
+ elif options.id_type == "explicit":
6184
+ if method != "tagline" and method != explicit:
6185
+ if not prompt("Explicit in other tree"):
6186
+ print "add-id not supported for \"%s\" tagging method" % \
6190
+ if options.id_type == "auto":
6191
+ if method != "tagline" and method != "explicit":
6192
+ print "add-id not supported for \"%s\" tagging method" % method
6195
+ options.id_type = method
6196
+ if options.untagged:
6198
+ self.add_ids(tree, options.id_type, args)
6200
+ def add_ids(self, tree, id_type, files=()):
6201
+ """Add inventory ids to files.
6203
+ :param tree: the tree the files are in
6204
+ :type tree: `arch.WorkingTree`
6205
+ :param id_type: the type of id to add: "explicit" or "tagline"
6206
+ :type id_type: str
6207
+ :param files: The list of files to add. If None do all untagged.
6208
+ :type files: tuple of str
6211
+ untagged = (files is None)
6213
+ files = list(iter_untagged(tree, None))
6214
+ previous_files = []
6215
+ while len(files) > 0:
6216
+ previous_files.extend(files)
6217
+ if id_type == "explicit":
6218
+ cmdutil.add_id(files)
6219
+ elif id_type == "tagline":
6220
+ for file in files:
6222
+ cmdutil.add_tagline_or_explicit_id(file)
6223
+ except cmdutil.AlreadyTagged:
6224
+ print "\"%s\" already has a tagline." % file
6225
+ except cmdutil.NoCommentSyntax:
6227
+ #do inventory after tagging until no untagged files are encountered
6230
+ for file in iter_untagged(tree, None):
6231
+ if not file in previous_files:
6232
+ files.append(file)
6237
+ def get_parser(self):
6239
+ Returns the options parser to use for the "revision" command.
6241
+ :rtype: cmdutil.CmdOptionParser
6243
+ parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
6244
+# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
6245
+# parser.add_option("-i", "--id", dest="id",
6246
+# help="Specify id for a single file", default=None)
6247
+ parser.add_option("--tltl", action="store_true",
6248
+ dest="lord_style", help="Use Tom Lord's style of id.")
6249
+ parser.add_option("--explicit", action="store_const",
6250
+ const="explicit", dest="id_type",
6251
+ help="Use an explicit id", default="auto")
6252
+ parser.add_option("--tagline", action="store_const",
6253
+ const="tagline", dest="id_type",
6254
+ help="Use a tagline id")
6255
+ parser.add_option("--untagged", action="store_true",
6256
+ dest="untagged", default=False,
6257
+ help="tag all untagged files")
6260
+ def help(self, parser=None):
6262
+ Prints a help message.
6264
+ :param parser: If supplied, the parser to use for generating help. If \
6265
+ not supplied, it is retrieved.
6266
+ :type parser: cmdutil.CmdOptionParser
6269
+ parser=self.get_parser()
6270
+ parser.print_help()
6272
+Adds an inventory to the specified file(s) and directories. If --untagged is
6273
+specified, adds inventory to all untagged files and directories.
6278
+class Merge(BaseCommand):
6280
+ Merges changes from other versions into the current tree
6282
+ def __init__(self):
6283
+ self.description="Merges changes from other versions"
6285
+ self.tree = arch.tree_root()
6290
+ def get_completer(self, arg, index):
6291
+ if self.tree is None:
6292
+ raise arch.errors.TreeRootError
6293
+ completions = list(ancillary.iter_partners(self.tree,
6294
+ self.tree.tree_version))
6295
+ if len(completions) == 0:
6296
+ completions = list(self.tree.iter_log_versions())
6300
+ for completion in completions:
6301
+ alias = ancillary.compact_alias(str(completion), self.tree)
6303
+ aliases.extend(alias)
6305
+ for completion in completions:
6306
+ if completion.archive == self.tree.tree_version.archive:
6307
+ aliases.append(completion.nonarch)
6309
+ except Exception, e:
6312
+ completions.extend(aliases)
6313
+ return completions
6315
+ def do_command(self, cmdargs):
6317
+ Master function that perfoms the "merge" command.
6319
+ parser=self.get_parser()
6320
+ (options, args) = parser.parse_args(cmdargs)
6322
+ action="star-merge"
6324
+ action = options.action
6326
+ if self.tree is None:
6327
+ raise arch.errors.TreeRootError(os.getcwd())
6328
+ if cmdutil.has_changed(self.tree.tree_version):
6329
+ raise UncommittedChanges(self.tree)
6334
+ revisions.append(cmdutil.determine_revision_arch(self.tree,
6336
+ source = "from commandline"
6338
+ revisions = ancillary.iter_partner_revisions(self.tree,
6339
+ self.tree.tree_version)
6340
+ source = "from partner version"
6341
+ revisions = misc.rewind_iterator(revisions)
6344
+ revisions.rewind()
6345
+ except StopIteration, e:
6346
+ revision = cmdutil.tag_cur(self.tree)
6347
+ if revision is None:
6348
+ raise CantDetermineRevision("", "No version specified, no "
6349
+ "partner-versions, and no tag"
6351
+ revisions = [revision]
6352
+ source = "from tag source"
6353
+ for revision in revisions:
6354
+ cmdutil.ensure_archive_registered(revision.archive)
6355
+ cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
6356
+ (revision, source)))
6357
+ if action=="native-merge" or action=="update":
6358
+ if self.native_merge(revision, action) == 0:
6360
+ elif action=="star-merge":
6362
+ self.star_merge(revision, options.diff3)
6363
+ except errors.MergeProblem, e:
6365
+ if cmdutil.has_changed(self.tree.tree_version):
6368
+ def star_merge(self, revision, diff3):
6369
+ """Perform a star-merge on the current tree.
6371
+ :param revision: The revision to use for the merge
6372
+ :type revision: `arch.Revision`
6373
+ :param diff3: If true, do a diff3 merge
6377
+ for line in self.tree.iter_star_merge(revision, diff3=diff3):
6378
+ cmdutil.colorize(line)
6379
+ except arch.util.ExecProblem, e:
6380
+ if e.proc.status is not None and e.proc.status == 1:
6382
+ print e.proc.error
6383
+ raise MergeProblem
6387
+ def native_merge(self, other_revision, action):
6388
+ """Perform a native-merge on the current tree.
6390
+ :param other_revision: The revision to use for the merge
6391
+ :type other_revision: `arch.Revision`
6392
+ :return: 0 if the merge was skipped, 1 if it was applied
6394
+ other_tree = cmdutil.find_or_make_local_revision(other_revision)
6396
+ if action == "native-merge":
6397
+ ancestor = cmdutil.merge_ancestor2(self.tree, other_tree,
6399
+ elif action == "update":
6400
+ ancestor = cmdutil.tree_latest(self.tree,
6401
+ other_revision.version)
6402
+ except CantDetermineRevision, e:
6403
+ raise CommandFailedWrapper(e)
6404
+ cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
6405
+ if (ancestor == other_revision):
6406
+ cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
6409
+ delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
6410
+ for line in cmdutil.iter_apply_delta_filter(delta):
6411
+ cmdutil.colorize(line)
6416
+ def get_parser(self):
6418
+ Returns the options parser to use for the "merge" command.
6420
+ :rtype: cmdutil.CmdOptionParser
6422
+ parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
6423
+ parser.add_option("-s", "--star-merge", action="store_const",
6424
+ dest="action", help="Use star-merge",
6425
+ const="star-merge", default="native-merge")
6426
+ parser.add_option("--update", action="store_const",
6427
+ dest="action", help="Use update picker",
6429
+ parser.add_option("--diff3", action="store_true",
6431
+ help="Use diff3 for merge (implies star-merge)")
6434
+ def help(self, parser=None):
6436
+ Prints a help message.
6438
+ :param parser: If supplied, the parser to use for generating help. If \
6439
+ not supplied, it is retrieved.
6440
+ :type parser: cmdutil.CmdOptionParser
6443
+ parser=self.get_parser()
6444
+ parser.print_help()
6446
+Performs a merge operation using the specified version.
6450
+class ELog(BaseCommand):
6452
+ Produces a raw patchlog and invokes the user's editor
6454
+ def __init__(self):
6455
+ self.description="Edit a patchlog to commit"
6457
+ self.tree = arch.tree_root()
6462
+ def do_command(self, cmdargs):
6464
+ Master function that perfoms the "elog" command.
6466
+ parser=self.get_parser()
6467
+ (options, args) = parser.parse_args(cmdargs)
6468
+ if self.tree is None:
6469
+ raise arch.errors.TreeRootError
6471
+ edit_log(self.tree)
6473
+ def get_parser(self):
6475
+ Returns the options parser to use for the "merge" command.
6477
+ :rtype: cmdutil.CmdOptionParser
6479
+ parser=cmdutil.CmdOptionParser("fai elog")
6483
+ def help(self, parser=None):
6485
+ Invokes $EDITOR to produce a log for committing.
6487
+ :param parser: If supplied, the parser to use for generating help. If \
6488
+ not supplied, it is retrieved.
6489
+ :type parser: cmdutil.CmdOptionParser
6492
+ parser=self.get_parser()
6493
+ parser.print_help()
6495
+Invokes $EDITOR to produce a log for committing.
6499
+def edit_log(tree):
6500
+ """Makes and edits the log for a tree. Does all kinds of fancy things
6501
+ like log templates and merge summaries and log-for-merge
6503
+ :param tree: The tree to edit the log for
6504
+ :type tree: `arch.WorkingTree`
6506
+ #ensure we have an editor before preparing the log
6507
+ cmdutil.find_editor()
6508
+ log = tree.log_message(create=False)
6509
+ log_is_new = False
6510
+ if log is None or cmdutil.prompt("Overwrite log"):
6511
+ if log is not None:
6512
+ os.remove(log.name)
6513
+ log = tree.log_message(create=True)
6516
+ template = tree+"/{arch}/=log-template"
6517
+ if not os.path.exists(template):
6518
+ template = os.path.expanduser("~/.arch-params/=log-template")
6519
+ if not os.path.exists(template):
6522
+ shutil.copyfile(template, tmplog)
6524
+ new_merges = list(cmdutil.iter_new_merges(tree,
6525
+ tree.tree_version))
6526
+ log["Summary"] = merge_summary(new_merges, tree.tree_version)
6527
+ if len(new_merges) > 0:
6528
+ if cmdutil.prompt("Log for merge"):
6529
+ mergestuff = cmdutil.log_for_merge(tree)
6530
+ log.description += mergestuff
6533
+ cmdutil.invoke_editor(log.name)
6536
+ os.remove(log.name)
6539
+def merge_summary(new_merges, tree_version):
6540
+ if len(new_merges) == 0:
6542
+ if len(new_merges) == 1:
6543
+ summary = new_merges[0].summary
6548
+ for merge in new_merges:
6549
+ if arch.my_id() != merge.creator:
6550
+ name = re.sub("<.*>", "", merge.creator).rstrip(" ");
6551
+ if not name in credits:
6552
+ credits.append(name)
6554
+ version = merge.revision.version
6555
+ if version.archive == tree_version.archive:
6556
+ if not version.nonarch in credits:
6557
+ credits.append(version.nonarch)
6558
+ elif not str(version) in credits:
6559
+ credits.append(str(version))
6561
+ return ("%s (%s)") % (summary, ", ".join(credits))
6563
+class MirrorArchive(BaseCommand):
6565
+ Updates a mirror from an archive
6567
+ def __init__(self):
6568
+ self.description="Update a mirror from an archive"
6570
+ def do_command(self, cmdargs):
6572
+ Master function that perfoms the "revision" command.
6575
+ parser=self.get_parser()
6576
+ (options, args) = parser.parse_args(cmdargs)
6580
+ tree = arch.tree_root()
6584
+ if len(args) == 0:
6585
+ if tree is not None:
6586
+ name = tree.tree_version()
6588
+ name = cmdutil.expand_alias(args[0], tree)
6589
+ name = arch.NameParser(name)
6591
+ to_arch = name.get_archive()
6592
+ from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
6593
+ limit = name.get_nonarch()
6595
+ iter = arch_core.mirror_archive(from_arch,to_arch, limit)
6596
+ for line in arch.chatter_classifier(iter):
6597
+ cmdutil.colorize(line)
6599
+ def get_parser(self):
6601
+ Returns the options parser to use for the "revision" command.
6603
+ :rtype: cmdutil.CmdOptionParser
6605
+ parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
6608
+ def help(self, parser=None):
6610
+ Prints a help message.
6612
+ :param parser: If supplied, the parser to use for generating help. If \
6613
+ not supplied, it is retrieved.
6614
+ :type parser: cmdutil.CmdOptionParser
6617
+ parser=self.get_parser()
6618
+ parser.print_help()
6620
+Updates a mirror from an archive. If a branch, package, or version is
6621
+supplied, only changes under it are mirrored.
6625
+def help_tree_spec():
6626
+ print """Specifying revisions (default: tree)
6627
+Revisions may be specified by alias, revision, version or patchlevel.
6628
+Revisions or versions may be fully qualified. Unqualified revisions, versions,
6629
+or patchlevels use the archive of the current project tree. Versions will
6630
+use the latest patchlevel in the tree. Patchlevels will use the current tree-
6633
+Use "alias" to list available (user and automatic) aliases."""
6635
+def help_aliases(tree):
6636
+ print """Auto-generated aliases
6637
+ acur : The latest revision in the archive of the tree-version. You can specfy
6638
+ a different version like so: acur:foo--bar--0 (aliases can be used)
6639
+ tcur : (tree current) The latest revision in the tree of the tree-version.
6640
+ You can specify a different version like so: tcur:foo--bar--0 (aliases
6642
+tprev : (tree previous) The previous revision in the tree of the tree-version.
6643
+ To specify an older revision, use a number, e.g. "tprev:4"
6644
+ tanc : (tree ancestor) The ancestor revision of the tree
6645
+ To specify an older revision, use a number, e.g. "tanc:4"
6646
+tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
6647
+ tmod : (tree modified) The latest revision to modify a given file
6648
+ (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
6649
+ ttag : (tree tag) The revision that was tagged into the current tree revision,
6650
+ according to the tree.
6651
+tagcur: (tag current) The latest revision of the version that the current tree
6653
+mergeanc : The common ancestor of the current tree and the specified revision.
6654
+ Defaults to the first partner-version's latest revision or to tagcur.
6656
+ print "User aliases"
6657
+ for parts in ancillary.iter_all_alias(tree):
6658
+ print parts[0].rjust(10)+" : "+parts[1]
6661
+class Inventory(BaseCommand):
6662
+ """List the status of files in the tree"""
6663
+ def __init__(self):
6664
+ self.description=self.__doc__
6666
+ def do_command(self, cmdargs):
6668
+ Master function that perfoms the "revision" command.
6671
+ parser=self.get_parser()
6672
+ (options, args) = parser.parse_args(cmdargs)
6673
+ tree = arch.tree_root()
6676
+ if (options.source):
6677
+ categories.append(arch_core.SourceFile)
6678
+ if (options.precious):
6679
+ categories.append(arch_core.PreciousFile)
6680
+ if (options.backup):
6681
+ categories.append(arch_core.BackupFile)
6682
+ if (options.junk):
6683
+ categories.append(arch_core.JunkFile)
6685
+ if len(categories) == 1:
6686
+ show_leading = False
6688
+ show_leading = True
6690
+ if len(categories) == 0:
6693
+ if options.untagged:
6694
+ categories = arch_core.non_root
6695
+ show_leading = False
6700
+ for file in arch_core.iter_inventory_filter(tree, None,
6701
+ control_files=options.control_files,
6702
+ categories = categories, tagged=tagged):
6703
+ print arch_core.file_line(file,
6704
+ category = show_leading,
6705
+ untagged = show_leading,
6708
+ def get_parser(self):
6710
+ Returns the options parser to use for the "revision" command.
6712
+ :rtype: cmdutil.CmdOptionParser
6714
+ parser=cmdutil.CmdOptionParser("fai inventory [options]")
6715
+ parser.add_option("--ids", action="store_true", dest="ids",
6716
+ help="Show file ids")
6717
+ parser.add_option("--control", action="store_true",
6718
+ dest="control_files", help="include control files")
6719
+ parser.add_option("--source", action="store_true", dest="source",
6720
+ help="List source files")
6721
+ parser.add_option("--backup", action="store_true", dest="backup",
6722
+ help="List backup files")
6723
+ parser.add_option("--precious", action="store_true", dest="precious",
6724
+ help="List precious files")
6725
+ parser.add_option("--junk", action="store_true", dest="junk",
6726
+ help="List junk files")
6727
+ parser.add_option("--unrecognized", action="store_true",
6728
+ dest="unrecognized", help="List unrecognized files")
6729
+ parser.add_option("--untagged", action="store_true",
6730
+ dest="untagged", help="List only untagged files")
6733
+ def help(self, parser=None):
6735
+ Prints a help message.
6737
+ :param parser: If supplied, the parser to use for generating help. If \
6738
+ not supplied, it is retrieved.
6739
+ :type parser: cmdutil.CmdOptionParser
6742
+ parser=self.get_parser()
6743
+ parser.print_help()
6745
+Lists the status of files in the archive:
6753
+Leading letter are not displayed if only one kind of file is shown
6758
+class Alias(BaseCommand):
6759
+ """List or adjust aliases"""
6760
+ def __init__(self):
6761
+ self.description=self.__doc__
6763
+ def get_completer(self, arg, index):
6767
+ self.tree = arch.tree_root()
6772
+ return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
6774
+ return cmdutil.iter_revision_completions(arg, self.tree)
6777
+ def do_command(self, cmdargs):
6779
+ Master function that perfoms the "revision" command.
6782
+ parser=self.get_parser()
6783
+ (options, args) = parser.parse_args(cmdargs)
6785
+ self.tree = arch.tree_root()
6791
+ options.action(args, options)
6792
+ except cmdutil.ForbiddenAliasSyntax, e:
6793
+ raise CommandFailedWrapper(e)
6795
+ def arg_dispatch(self, args, options):
6796
+ """Add, modify, or list aliases, depending on number of arguments
6798
+ :param args: The list of commandline arguments
6799
+ :type args: list of str
6800
+ :param options: The commandline options
6802
+ if len(args) == 0:
6803
+ help_aliases(self.tree)
6805
+ elif len(args) == 1:
6806
+ self.print_alias(args[0])
6807
+ elif (len(args)) == 2:
6808
+ self.add(args[0], args[1], options)
6810
+ raise cmdutil.GetHelp
6812
+ def print_alias(self, alias):
6814
+ for pair in ancillary.iter_all_alias(self.tree):
6815
+ if pair[0] == alias:
6817
+ if answer is not None:
6820
+ print "The alias %s is not assigned." % alias
6822
+ def add(self, alias, expansion, options):
6823
+ """Add or modify aliases
6825
+ :param alias: The alias name to create/modify
6827
+ :param expansion: The expansion to assign to the alias name
6828
+ :type expansion: str
6829
+ :param options: The commandline options
6833
+ new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
6835
+ ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
6837
+ for pair in self.get_iterator(options):
6838
+ if pair[0] != alias:
6839
+ newlist+="%s=%s\n" % (pair[0], pair[1])
6845
+ self.write_aliases(newlist, options)
6847
+ def delete(self, args, options):
6848
+ """Delete the specified alias
6850
+ :param args: The list of arguments
6851
+ :type args: list of str
6852
+ :param options: The commandline options
6855
+ if len(args) != 1:
6856
+ raise cmdutil.GetHelp
6858
+ for pair in self.get_iterator(options):
6859
+ if pair[0] != args[0]:
6860
+ newlist+="%s=%s\n" % (pair[0], pair[1])
6864
+ raise errors.NoSuchAlias(args[0])
6865
+ self.write_aliases(newlist, options)
6867
+ def get_alias_file(self, options):
6868
+ """Return the name of the alias file to use
6870
+ :param options: The commandline options
6873
+ if self.tree is None:
6874
+ self.tree == arch.tree_root()
6875
+ return str(self.tree)+"/{arch}/+aliases"
6877
+ return "~/.aba/aliases"
6879
+ def get_iterator(self, options):
6880
+ """Return the alias iterator to use
6882
+ :param options: The commandline options
6884
+ return ancillary.iter_alias(self.get_alias_file(options))
6886
+ def write_aliases(self, newlist, options):
6887
+ """Safely rewrite the alias file
6888
+ :param newlist: The new list of aliases
6889
+ :type newlist: str
6890
+ :param options: The commandline options
6892
+ filename = os.path.expanduser(self.get_alias_file(options))
6893
+ file = cmdutil.NewFileVersion(filename)
6894
+ file.write(newlist)
6898
+ def get_parser(self):
6900
+ Returns the options parser to use for the "alias" command.
6902
+ :rtype: cmdutil.CmdOptionParser
6904
+ parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
6905
+ parser.add_option("-d", "--delete", action="store_const", dest="action",
6906
+ const=self.delete, default=self.arg_dispatch,
6907
+ help="Delete an alias")
6908
+ parser.add_option("--tree", action="store_true", dest="tree",
6909
+ help="Create a per-tree alias", default=False)
6912
+ def help(self, parser=None):
6914
+ Prints a help message.
6916
+ :param parser: If supplied, the parser to use for generating help. If \
6917
+ not supplied, it is retrieved.
6918
+ :type parser: cmdutil.CmdOptionParser
6921
+ parser=self.get_parser()
6922
+ parser.print_help()
6924
+Lists current aliases or modifies the list of aliases.
6926
+If no arguments are supplied, aliases will be listed. If two arguments are
6927
+supplied, the specified alias will be created or modified. If -d or --delete
6928
+is supplied, the specified alias will be deleted.
6930
+You can create aliases that refer to any fully-qualified part of the
6931
+Arch namespace, e.g.
6934
+archive/category--branch,
6935
+archive/category--branch--version (my favourite)
6936
+archive/category--branch--version--patchlevel
6938
+Aliases can be used automatically by native commands. To use them
6939
+with external or tla commands, prefix them with ^ (you can do this
6940
+with native commands, too).
6944
+class RequestMerge(BaseCommand):
6945
+ """Submit a merge request to Bug Goo"""
6946
+ def __init__(self):
6947
+ self.description=self.__doc__
6949
+ def do_command(self, cmdargs):
6950
+ """Submit a merge request
6952
+ :param cmdargs: The commandline arguments
6953
+ :type cmdargs: list of str
6955
+ cmdutil.find_editor()
6956
+ parser = self.get_parser()
6957
+ (options, args) = parser.parse_args(cmdargs)
6959
+ self.tree=arch.tree_root()
6962
+ base, revisions = self.revision_specs(args)
6963
+ message = self.make_headers(base, revisions)
6964
+ message += self.make_summary(revisions)
6965
+ path = self.edit_message(message)
6966
+ message = self.tidy_message(path)
6967
+ if cmdutil.prompt("Send merge"):
6968
+ self.send_message(message)
6969
+ print "Merge request sent"
6971
+ def make_headers(self, base, revisions):
6972
+ """Produce email and Bug Goo header strings
6974
+ :param base: The base revision to apply merges to
6975
+ :type base: `arch.Revision`
6976
+ :param revisions: The revisions to replay into the base
6977
+ :type revisions: list of `arch.Patchlog`
6978
+ :return: The headers
6981
+ headers = "To: gnu-arch-users@gnu.org\n"
6982
+ headers += "From: %s\n" % options.fromaddr
6983
+ if len(revisions) == 1:
6984
+ headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
6986
+ headers += "Subject: [MERGE REQUEST]\n"
6988
+ headers += "Base-Revision: %s\n" % base
6989
+ for revision in revisions:
6990
+ headers += "Revision: %s\n" % revision.revision
6991
+ headers += "Bug: \n\n"
6994
+ def make_summary(self, logs):
6995
+ """Generate a summary of merges
6997
+ :param logs: the patchlogs that were directly added by the merges
6998
+ :type logs: list of `arch.Patchlog`
6999
+ :return: the summary
7004
+ summary+=str(log.revision)+"\n"
7005
+ summary+=log.summary+"\n"
7006
+ if log.description.strip():
7007
+ summary+=log.description.strip('\n')+"\n\n"
7010
+ def revision_specs(self, args):
7011
+ """Determine the base and merge revisions from tree and arguments.
7013
+ :param args: The parsed arguments
7014
+ :type args: list of str
7015
+ :return: The base revision and merge revisions
7016
+ :rtype: `arch.Revision`, list of `arch.Patchlog`
7019
+ target_revision = cmdutil.determine_revision_arch(self.tree,
7022
+ target_revision = cmdutil.tree_latest(self.tree)
7024
+ merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
7025
+ self.tree, f)) for f in args[1:] ]
7027
+ if self.tree is None:
7028
+ raise CantDetermineRevision("", "Not in a project tree")
7029
+ merge_iter = cmdutil.iter_new_merges(self.tree,
7030
+ target_revision.version,
7032
+ merges = [f for f in cmdutil.direct_merges(merge_iter)]
7033
+ return (target_revision, merges)
7035
+ def edit_message(self, message):
7036
+ """Edit an email message in the user's standard editor
7038
+ :param message: The message to edit
7039
+ :type message: str
7040
+ :return: the path of the edited message
7043
+ if self.tree is None:
7044
+ path = os.get_cwd()
7047
+ path += "/,merge-request"
7048
+ file = open(path, 'w')
7049
+ file.write(message)
7051
+ cmdutil.invoke_editor(path)
7054
+ def tidy_message(self, path):
7055
+ """Validate and clean up message.
7057
+ :param path: The path to the message to clean up
7059
+ :return: The parsed message
7060
+ :rtype: `email.Message`
7062
+ mail = email.message_from_file(open(path))
7063
+ if mail["Subject"].strip() == "[MERGE REQUEST]":
7064
+ raise BlandSubject
7066
+ request = email.message_from_string(mail.get_payload())
7067
+ if request.has_key("Bug"):
7068
+ if request["Bug"].strip()=="":
7069
+ del request["Bug"]
7070
+ mail.set_payload(request.as_string())
7073
+ def send_message(self, message):
7074
+ """Send a message, using its headers to address it.
7076
+ :param message: The message to send
7077
+ :type message: `email.Message`"""
7078
+ server = smtplib.SMTP()
7079
+ server.sendmail(message['From'], message['To'], message.as_string())
7082
+ def help(self, parser=None):
7083
+ """Print a usage message
7085
+ :param parser: The options parser to use
7086
+ :type parser: `cmdutil.CmdOptionParser`
7088
+ if parser is None:
7089
+ parser = self.get_parser()
7090
+ parser.print_help()
7092
+Sends a merge request formatted for Bug Goo. Intended use: get the tree
7093
+you'd like to merge into. Apply the merges you want. Invoke request-merge.
7094
+The merge request will open in your $EDITOR.
7096
+When no TARGET is specified, it uses the current tree revision. When
7097
+no MERGE is specified, it uses the direct merges (as in "revisions
7098
+--direct-merges"). But you can specify just the TARGET, or all the MERGE
7102
+ def get_parser(self):
7103
+ """Produce a commandline parser for this command.
7105
+ :rtype: `cmdutil.CmdOptionParser`
7107
+ parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
7111
+'changes' : Changes,
7114
+'apply-changes':ApplyChanges,
7117
+'revision': Revision,
7118
+'revisions': Revisions,
7125
+'mirror-archive': MirrorArchive,
7126
+'ninventory': Inventory,
7128
+'request-merge': RequestMerge,
7131
+'apply-delta' : "Try \"apply-changes\".",
7132
+'delta' : "To compare two revisions, use \"changes\".",
7133
+'diff-rev' : "To compare two revisions, use \"changes\".",
7134
+'undo' : "To undo local changes, use \"revert\".",
7135
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
7136
+'missing-from' : "Try \"revisions --missing-from\".",
7137
+'missing' : "Try \"revisions --missing\".",
7138
+'missing-merge' : "Try \"revisions --partner-missing\".",
7139
+'new-merges' : "Try \"revisions --new-merges\".",
7140
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
7141
+'logs' : "Try \"revisions --logs\"",
7142
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
7143
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
7144
+'change-version' : "Try \"update REVISION\"",
7145
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
7146
+'rev-depends' : "Use revisions --dependencies",
7147
+'auto-get' : "Plain get will do archive lookups",
7148
+'tagline' : "Use add-id. It uses taglines in tagline trees",
7149
+'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
7150
+'library-revisions' : "Use revisions --library",
7151
+'file-revert' : "Use revert FILE"
7153
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
7155
*** modified file 'bzrlib/branch.py'
7156
--- bzrlib/branch.py
7157
+++ bzrlib/branch.py
7159
from revision import Revision
7160
from errors import bailout, BzrError
7161
from textui import show_status
7163
+from bzrlib import progress
7165
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
7166
## TODO: Maybe include checks for common corruption of newlines, etc?
7167
@@ -864,3 +866,36 @@
7169
s = hexlify(rand_bytes(8))
7170
return '-'.join((name, compact_date(time.time()), s))
7173
+def iter_anno_data(branch, file_id):
7174
+ later_revision = branch.revno()
7175
+ q = range(branch.revno())
7177
+ later_text_id = branch.basis_tree().inventory[file_id].text_id
7181
+ cur_tree = branch.revision_tree(branch.lookup_revision(revno))
7182
+ if file_id not in cur_tree.inventory:
7185
+ text_id = cur_tree.inventory[file_id].text_id
7186
+ if text_id != later_text_id:
7187
+ patch = get_patch(branch, revno, later_revision, file_id)
7188
+ yield revno, patch.iter_inserted(), patch
7189
+ later_revision = revno
7190
+ later_text_id = text_id
7191
+ yield progress.Progress("revisions", i)
7193
+def get_patch(branch, old_revno, new_revno, file_id):
7194
+ old_tree = branch.revision_tree(branch.lookup_revision(old_revno))
7195
+ new_tree = branch.revision_tree(branch.lookup_revision(new_revno))
7196
+ if file_id in old_tree.inventory:
7197
+ old_file = old_tree.get_file(file_id).readlines()
7200
+ ud = difflib.unified_diff(old_file, new_tree.get_file(file_id).readlines())
7201
+ return patches.parse_patch(ud)
7205
*** modified file 'bzrlib/commands.py'
7206
--- bzrlib/commands.py
7207
+++ bzrlib/commands.py
7209
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
7211
from bzrlib import merge
7212
+from bzrlib.branch import iter_anno_data
7213
+from bzrlib import patches
7214
+from bzrlib import progress
7217
def _squish_command_name(cmd):
7218
@@ -882,7 +885,15 @@
7219
print '%3d FAILED!' % mf
7223
+ result = bzrlib.patches.test()
7224
+ resultFailed = len(result.errors) + len(result.failures)
7225
+ print '%-40s %3d tests' % ('bzrlib.patches', result.testsRun),
7227
+ print '%3d FAILED!' % resultFailed
7230
+ tests += result.testsRun
7231
+ failures += resultFailed
7232
print '%-40s %3d tests' % ('total', tests),
7234
print '%3d FAILED!' % failures
7235
@@ -897,6 +908,34 @@
7236
"""Show version of bzr"""
7240
+class cmd_annotate(Command):
7241
+ """Show which revision added each line in a file"""
7243
+ takes_args = ['filename']
7244
+ def run(self, filename):
7245
+ if not os.path.exists(filename):
7246
+ raise BzrCommandError("The file %s does not exist." % filename)
7247
+ branch = (Branch(filename))
7248
+ file_id = branch.working_tree().path2id(filename)
7249
+ if file_id is None:
7250
+ raise BzrCommandError("The file %s is not versioned." % filename)
7251
+ lines = branch.basis_tree().get_file(file_id)
7252
+ total = branch.revno()
7253
+ anno_d_iter = iter_anno_data(branch, file_id)
7254
+ progress_bar = progress.ProgressBar()
7256
+ for result in patches.iter_annotate_file(lines, anno_d_iter):
7257
+ if isinstance(result, progress.Progress):
7258
+ result.total = total
7259
+ progress_bar(result)
7261
+ anno_lines = result
7263
+ progress.clear_progress_bar()
7264
+ for line in anno_lines:
7265
+ sys.stdout.write("%4s:%s" % (str(line.log), line.text))
7269
print "bzr (bazaar-ng) %s" % bzrlib.__version__