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
433
+ version = "[no version]"
434
+ if command is None:
435
+@@ -1983,7 +1984,11 @@
437
+ if len(new_merges) > 0:
438
+ if cmdutil.prompt("Log for merge"):
439
+- mergestuff = cmdutil.log_for_merge(tree, comp_version)
440
+ log.description += mergestuff
444
+ self.compare_parsed(patchtext)
446
+ def testInit(self):
447
+ """Handle patches missing half the position, range tuple"""
449
+"""--- orig/__init__.py
452
+ __docformat__ = "restructuredtext en"
453
++__doc__ = An alternate Arch commandline interface"""
454
+ self.compare_parsed(patchtext)
458
+ def testLineLookup(self):
459
+ """Make sure we can accurately look up mod line from orig"""
460
+ patch = parse_patch(open("testdata/diff"))
461
+ orig = list(open("testdata/orig"))
462
+ mod = list(open("testdata/mod"))
464
+ for i in range(len(orig)):
465
+ mod_pos = patch.pos_in_mod(i)
466
+ if mod_pos is None:
467
+ removals.append(orig[i])
469
+ assert(mod[mod_pos]==orig[i])
470
+ rem_iter = removals.__iter__()
471
+ for hunk in patch.hunks:
472
+ for line in hunk.lines:
473
+ if isinstance(line, RemoveLine):
474
+ next = rem_iter.next()
475
+ if line.contents != next:
476
+ sys.stdout.write(" orig:%spatch:%s" % (next,
478
+ assert(line.contents == next)
479
+ self.assertRaises(StopIteration, rem_iter.next)
481
+ def testFirstLineRenumber(self):
482
+ """Make sure we handle lines at the beginning of the hunk"""
483
+ patch = parse_patch(open("testdata/insert_top.patch"))
484
+ assert (patch.pos_in_mod(0)==1)
487
+ patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
488
+ runner = unittest.TextTestRunner(verbosity=0)
489
+ return runner.run(patchesTestSuite)
492
+if __name__ == "__main__":
494
+# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683
496
*** added directory 'testdata'
497
*** added file 'testdata/diff'
501
+--- orig/commands.py
508
++import pylon.errors
509
++from pylon.errors import *
510
++from pylon import errors
511
++from pylon import util
512
++from pylon import arch_core
513
++from pylon import arch_compound
514
++from pylon import ancillary
515
++from pylon import misc
516
++from pylon import paths
530
+-from errors import *
539
+ __docformat__ = "restructuredtext"
540
+ __doc__ = "Implementation of user (sub) commands"
543
+ tree=arch.tree_root()
545
+- a_spec = cmdutil.comp_revision(tree)
547
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
548
+ cmdutil.ensure_archive_registered(a_spec.archive)
550
+ changeset=options.changeset
553
+- tmpdir=cmdutil.tmpdir()
554
+ changeset=tmpdir+"/changeset"
556
+ delta=arch.iter_delta(a_spec, b_spec, changeset)
557
+@@ -304,14 +310,14 @@
560
+ if (options.perform_diff):
561
+- chan = cmdutil.ChangesetMunger(changeset)
562
+ chan.read_indices()
563
+- if isinstance(b_spec, arch.Revision):
564
+- b_dir = b_spec.library_find()
567
+- a_dir = a_spec.library_find()
568
+ if options.diffopts is not None:
569
+ diffopts = options.diffopts.split()
570
+ cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
573
+ except arch.errors.TreeRootError, e:
576
+- from_revision=cmdutil.tree_latest(tree)
577
+ if from_revision==to_revision:
578
+ print "Tree is already up to date with:\n"+str(to_revision)+"."
585
+ revision=cmdutil.determine_revision_arch(tree, options.version)
586
+ return options, revision.get_version(), args
588
+@@ -601,11 +610,16 @@
590
+ tree=arch.tree_root()
591
+ options, version, files = self.parse_commandline(cmdargs, tree)
592
+ if options.__dict__.has_key("base") and options.base:
593
+ base = cmdutil.determine_revision_tree(tree, options.base)
595
+- base = cmdutil.submit_revision(tree)
598
+ writeversion=version
599
+ archive=version.archive
600
+ source=cmdutil.get_mirror_source(archive)
601
+@@ -625,18 +639,26 @@
603
+ last_revision=tree.iter_logs(version, True).next().revision
604
+ except StopIteration, e:
605
+- if cmdutil.prompt("Import from commit"):
606
+- return do_import(version)
608
+- raise NoVersionLogs(version)
609
+- if last_revision!=version.iter_revisions(True).next():
610
+ if not cmdutil.prompt("Out of date"):
616
+- if not cmdutil.has_changed(version):
617
+ if not cmdutil.prompt("Empty commit"):
619
+ except arch.util.ExecProblem, e:
620
+@@ -645,15 +667,15 @@
624
+- log = tree.log_message(create=False)
627
+ if cmdutil.prompt("Create log"):
630
+ except cmdutil.NoEditorSpecified, e:
631
+ raise CommandFailed(e)
632
+- log = tree.log_message(create=False)
635
+ if log["Summary"] is None or len(log["Summary"].strip()) == 0:
636
+@@ -837,23 +859,24 @@
637
+ if spec is not None:
638
+ revision = cmdutil.determine_revision_tree(tree, spec)
640
+- revision = cmdutil.comp_revision(tree)
641
+ except cmdutil.CantDetermineRevision, e:
642
+ raise CommandFailedWrapper(e)
645
+ if options.file_contents or options.file_perms or options.deletions\
646
+ or options.additions or options.renames or options.hunk_prompt:
647
+- munger = cmdutil.MungeOpts()
648
+- munger.hunk_prompt = options.hunk_prompt
650
+ if len(args) > 0 or options.logs or options.pattern_files or \
653
+- munger = cmdutil.MungeOpts(True)
654
+ munger.all_types(True)
656
+- t_cwd = cmdutil.tree_cwd(tree)
661
+ if options.pattern_files:
662
+ munger.add_keep_pattern(options.pattern_files)
664
+- for line in cmdutil.revert(tree, revision, munger,
665
+ not options.no_output):
666
+ cmdutil.colorize(line)
668
+@@ -1042,18 +1065,13 @@
672
+-def require_version_exists(version, spec):
673
+- if not version.exists():
674
+- raise cmdutil.CantDetermineVersion(spec,
675
+- "The version %s does not exist." \
678
+ class Revisions(BaseCommand):
680
+ Print a revision name based on a revision specifier
682
+ def __init__(self):
683
+ self.description="Lists revisions"
685
+ def do_command(self, cmdargs):
687
+@@ -1066,224 +1084,68 @@
688
+ self.tree = arch.tree_root()
689
+ except arch.errors.TreeRootError:
692
+- iter = self.get_iterator(options.type, args, options.reverse,
694
+ except cmdutil.CantDetermineRevision, e:
695
+ raise CommandFailedWrapper(e)
697
+ if options.skip is not None:
698
+ iter = cmdutil.iter_skip(iter, int(options.skip))
700
+- for revision in iter:
702
+- if isinstance(revision, arch.Patchlog):
704
+- revision=revision.revision
705
+- print options.display(revision)
706
+- if log is None and (options.summary or options.creator or
707
+- options.date or options.merges):
708
+- log = revision.patchlog
709
+- if options.creator:
710
+- print " %s" % log.creator
712
+- print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
713
+- if options.summary:
714
+- print " %s" % log.summary
715
+- if options.merges:
716
+- showed_title = False
717
+- for revision in log.merged_patches:
718
+- if not showed_title:
720
+- showed_title = True
721
+- print " %s" % revision
723
+- def get_iterator(self, type, args, reverse, modified):
728
+- if modified is not None:
729
+- iter = cmdutil.modified_iter(modified, self.tree)
733
+- return cmdutil.iter_reverse(iter)
734
+- elif type == "archive":
736
+- if self.tree is None:
737
+- raise cmdutil.CantDetermineRevision("",
738
+- "Not in a project tree")
739
+- version = cmdutil.determine_version_tree(spec, self.tree)
741
+- version = cmdutil.determine_version_arch(spec, self.tree)
742
+- cmdutil.ensure_archive_registered(version.archive)
743
+- require_version_exists(version, spec)
744
+- return version.iter_revisions(reverse)
745
+- elif type == "cacherevs":
747
+- if self.tree is None:
748
+- raise cmdutil.CantDetermineRevision("",
749
+- "Not in a project tree")
750
+- version = cmdutil.determine_version_tree(spec, self.tree)
752
+- version = cmdutil.determine_version_arch(spec, self.tree)
753
+- cmdutil.ensure_archive_registered(version.archive)
754
+- require_version_exists(version, spec)
755
+- return cmdutil.iter_cacherevs(version, reverse)
756
+- elif type == "library":
758
+- if self.tree is None:
759
+- raise cmdutil.CantDetermineRevision("",
760
+- "Not in a project tree")
761
+- version = cmdutil.determine_version_tree(spec, self.tree)
763
+- version = cmdutil.determine_version_arch(spec, self.tree)
764
+- return version.iter_library_revisions(reverse)
765
+- elif type == "logs":
766
+- if self.tree is None:
767
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
768
+- return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
769
+- self.tree), reverse)
770
+- elif type == "missing" or type == "skip-present":
771
+- if self.tree is None:
772
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
773
+- skip = (type == "skip-present")
774
+- version = cmdutil.determine_version_tree(spec, self.tree)
775
+- cmdutil.ensure_archive_registered(version.archive)
776
+- require_version_exists(version, spec)
777
+- return cmdutil.iter_missing(self.tree, version, reverse,
778
+- skip_present=skip)
780
+- elif type == "present":
781
+- if self.tree is None:
782
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
783
+- version = cmdutil.determine_version_tree(spec, self.tree)
784
+- cmdutil.ensure_archive_registered(version.archive)
785
+- require_version_exists(version, spec)
786
+- return cmdutil.iter_present(self.tree, version, reverse)
788
+- elif type == "new-merges" or type == "direct-merges":
789
+- if self.tree is None:
790
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
791
+- version = cmdutil.determine_version_tree(spec, self.tree)
792
+- cmdutil.ensure_archive_registered(version.archive)
793
+- require_version_exists(version, spec)
794
+- iter = cmdutil.iter_new_merges(self.tree, version, reverse)
795
+- if type == "new-merges":
797
+- elif type == "direct-merges":
798
+- return cmdutil.direct_merges(iter)
800
+- elif type == "missing-from":
801
+- if self.tree is None:
802
+- raise cmdutil.CantDetermineRevision("", "Not in a project tree")
803
+- revision = cmdutil.determine_revision_tree(self.tree, spec)
804
+- libtree = cmdutil.find_or_make_local_revision(revision)
805
+- return cmdutil.iter_missing(libtree, self.tree.tree_version,
808
+- elif type == "partner-missing":
809
+- return cmdutil.iter_partner_missing(self.tree, reverse)
811
+- elif type == "ancestry":
812
+- revision = cmdutil.determine_revision_tree(self.tree, spec)
813
+- iter = cmdutil._iter_ancestry(self.tree, revision)
817
+- return cmdutil.iter_reverse(iter)
819
+- elif type == "dependencies" or type == "non-dependencies":
820
+- nondeps = (type == "non-dependencies")
821
+- revision = cmdutil.determine_revision_tree(self.tree, spec)
822
+- anc_iter = cmdutil._iter_ancestry(self.tree, revision)
823
+- iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
825
+- return iter_depends
827
+- return cmdutil.iter_reverse(iter_depends)
828
+- elif type == "micro":
829
+- return cmdutil.iter_micro(self.tree)
833
+ def get_parser(self):
835
+ Returns the options parser to use for the "revision" command.
837
+ :rtype: cmdutil.CmdOptionParser
839
+- parser=cmdutil.CmdOptionParser("fai revisions [revision]")
840
+ select = cmdutil.OptionGroup(parser, "Selection options",
841
+ "Control which revisions are listed. These options"
842
+ " are mutually exclusive. If more than one is"
843
+ " specified, the last is used.")
844
+- select.add_option("", "--archive", action="store_const",
845
+- const="archive", dest="type", default="archive",
846
+- help="List all revisions in the archive")
847
+- select.add_option("", "--cacherevs", action="store_const",
848
+- const="cacherevs", dest="type",
849
+- help="List all revisions stored in the archive as "
850
+- "complete copies")
851
+- select.add_option("", "--logs", action="store_const",
852
+- const="logs", dest="type",
853
+- help="List revisions that have a patchlog in the "
855
+- select.add_option("", "--missing", action="store_const",
856
+- const="missing", dest="type",
857
+- help="List revisions from the specified version that"
858
+- " have no patchlog in the tree")
859
+- select.add_option("", "--skip-present", action="store_const",
860
+- const="skip-present", dest="type",
861
+- help="List revisions from the specified version that"
862
+- " have no patchlogs at all in the tree")
863
+- select.add_option("", "--present", action="store_const",
864
+- const="present", dest="type",
865
+- help="List revisions from the specified version that"
866
+- " have no patchlog in the tree, but can't be merged")
867
+- select.add_option("", "--missing-from", action="store_const",
868
+- const="missing-from", dest="type",
869
+- help="List revisions from the specified revision "
870
+- "that have no patchlog for the tree version")
871
+- select.add_option("", "--partner-missing", action="store_const",
872
+- const="partner-missing", dest="type",
873
+- help="List revisions in partner versions that are"
875
+- select.add_option("", "--new-merges", action="store_const",
876
+- const="new-merges", dest="type",
877
+- help="List revisions that have had patchlogs added"
878
+- " to the tree since the last commit")
879
+- select.add_option("", "--direct-merges", action="store_const",
880
+- const="direct-merges", dest="type",
881
+- help="List revisions that have been directly added"
882
+- " to tree since the last commit ")
883
+- select.add_option("", "--library", action="store_const",
884
+- const="library", dest="type",
885
+- help="List revisions in the revision library")
886
+- select.add_option("", "--ancestry", action="store_const",
887
+- const="ancestry", dest="type",
888
+- help="List revisions that are ancestors of the "
889
+- "current tree version")
891
+- select.add_option("", "--dependencies", action="store_const",
892
+- const="dependencies", dest="type",
893
+- help="List revisions that the given revision "
896
+- select.add_option("", "--non-dependencies", action="store_const",
897
+- const="non-dependencies", dest="type",
898
+- help="List revisions that the given revision "
899
+- "does not depend on")
901
+- select.add_option("--micro", action="store_const",
902
+- const="micro", dest="type",
903
+- help="List partner revisions aimed for this "
906
+- select.add_option("", "--modified", dest="modified",
907
+- help="List tree ancestor revisions that modified a "
908
+- "given file", metavar="FILE[:LINE]")
910
+ parser.add_option("", "--skip", dest="skip",
911
+ help="Skip revisions. Positive numbers skip from "
912
+ "beginning, negative skip from end.",
913
+@@ -1312,6 +1174,9 @@
914
+ format.add_option("--cacherev", action="store_const",
915
+ const=paths.determine_cacherev_path, dest="display",
916
+ help="Show location of cacherev file")
917
+ parser.add_option_group(format)
918
+ display = cmdutil.OptionGroup(parser, "Display format options",
919
+ "These control the display of data")
920
+@@ -1448,6 +1313,7 @@
921
+ if os.access(self.history_file, os.R_OK) and \
922
+ os.path.isfile(self.history_file):
923
+ readline.read_history_file(self.history_file)
925
+ def write_history(self):
926
+ readline.write_history_file(self.history_file)
927
+@@ -1470,16 +1336,21 @@
928
+ def set_prompt(self):
929
+ if self.tree is not None:
931
+- version = " "+self.tree.tree_version.nonarch
936
+- self.prompt = "Fai%s> " % version
938
+ def set_title(self, command=None):
940
+- version = self.tree.tree_version.nonarch
942
+ version = "[no version]"
943
+ if command is None:
944
+@@ -1489,8 +1360,15 @@
945
+ def do_cd(self, line):
949
+- os.chdir(os.path.expanduser(line))
950
+ except Exception, e:
953
+@@ -1523,7 +1401,7 @@
954
+ except cmdutil.CantDetermineRevision, e:
956
+ except Exception, e:
957
+- print "Unhandled error:\n%s" % cmdutil.exception_str(e)
959
+ elif suggestions.has_key(args[0]):
960
+ print suggestions[args[0]]
961
+@@ -1574,7 +1452,7 @@
962
+ arg = line.split()[-1]
965
+- iter = iter_munged_completions(iter, arg, text)
966
+ except Exception, e:
969
+@@ -1604,10 +1482,11 @@
972
+ if arg.startswith("-"):
973
+- return list(iter_munged_completions(iter, arg, text))
975
+- return list(iter_munged_completions(
976
+- iter_file_completions(arg), arg, text))
980
+@@ -1615,13 +1494,13 @@
981
+ arg = args.split()[-1]
984
+- iter = iter_dir_completions(arg)
985
+- iter = iter_munged_completions(iter, arg, text)
988
+ arg = args.split()[-1]
989
+- return list(iter_munged_completions(iter_file_completions(arg),
992
+ return self.completenames(text, line, begidx, endidx)
993
+ except Exception, e:
994
+@@ -1636,44 +1515,8 @@
998
+-def iter_file_completions(arg, only_dirs = False):
999
+- """Generate an iterator that iterates through filename completions.
1001
+- :param arg: The filename fragment to match
1003
+- :param only_dirs: If true, match only directories
1004
+- :type only_dirs: bool
1006
+- cwd = os.getcwd()
1008
+- extras = [".", ".."]
1011
+- (dir, file) = os.path.split(arg)
1013
+- listingdir = os.path.expanduser(dir)
1016
+- for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
1018
+- userfile = dir+'/'+file
1021
+- if userfile.startswith(arg):
1022
+- if os.path.isdir(listingdir+'/'+file):
1025
+- elif not only_dirs:
1028
+-def iter_munged_completions(iter, arg, text):
1029
+- for completion in iter:
1030
+- completion = str(completion)
1031
+- if completion.startswith(arg):
1032
+- yield completion[len(arg)-len(text):]
1034
+ def iter_source_file_completions(tree, arg):
1035
+- treepath = cmdutil.tree_cwd(tree)
1036
+ if len(treepath) > 0:
1039
+@@ -1701,7 +1544,7 @@
1040
+ :return: An iterator of all matching untagged files
1041
+ :rtype: iterator of str
1043
+- treepath = cmdutil.tree_cwd(tree)
1044
+ if len(treepath) > 0:
1047
+@@ -1743,8 +1586,8 @@
1048
+ :param arg: The prefix to match
1051
+- treepath = cmdutil.tree_cwd(tree)
1052
+- tmpdir = cmdutil.tmpdir()
1053
+ changeset = tmpdir+"/changeset"
1055
+ revision = cmdutil.determine_revision_tree(tree)
1056
+@@ -1756,14 +1599,6 @@
1057
+ shutil.rmtree(tmpdir)
1058
+ return completions
1060
+-def iter_dir_completions(arg):
1061
+- """Generate an iterator that iterates through directory name completions.
1063
+- :param arg: The directory name fragment to match
1066
+- return iter_file_completions(arg, True)
1068
+ class Shell(BaseCommand):
1069
+ def __init__(self):
1070
+ self.description = "Runs Fai as a shell"
1071
+@@ -1795,7 +1630,11 @@
1072
+ parser=self.get_parser()
1073
+ (options, args) = parser.parse_args(cmdargs)
1075
+- tree = arch.tree_root()
1077
+ if (len(args) == 0) == (options.untagged == False):
1078
+ raise cmdutil.GetHelp
1079
+@@ -1809,13 +1648,22 @@
1080
+ if options.id_type == "tagline":
1081
+ if method != "tagline":
1082
+ if not cmdutil.prompt("Tagline in other tree"):
1083
+- if method == "explicit":
1084
+- options.id_type == explicit
1086
+ print "add-id not supported for \"%s\" tagging method"\
1090
+ elif options.id_type == "explicit":
1091
+ if method != "tagline" and method != explicit:
1092
+ if not prompt("Explicit in other tree"):
1093
+@@ -1824,7 +1672,8 @@
1096
+ if options.id_type == "auto":
1097
+- if method != "tagline" and method != "explicit":
1098
+ print "add-id not supported for \"%s\" tagging method" % method
1101
+@@ -1852,10 +1701,12 @@
1102
+ previous_files.extend(files)
1103
+ if id_type == "explicit":
1104
+ cmdutil.add_id(files)
1105
+- elif id_type == "tagline":
1106
+ for file in files:
1108
+- cmdutil.add_tagline_or_explicit_id(file)
1109
+ except cmdutil.AlreadyTagged:
1110
+ print "\"%s\" already has a tagline." % file
1111
+ except cmdutil.NoCommentSyntax:
1112
+@@ -1888,6 +1739,9 @@
1113
+ parser.add_option("--tagline", action="store_const",
1114
+ const="tagline", dest="id_type",
1115
+ help="Use a tagline id")
1116
+ parser.add_option("--untagged", action="store_true",
1117
+ dest="untagged", default=False,
1118
+ help="tag all untagged files")
1119
+@@ -1926,27 +1780,7 @@
1120
+ def get_completer(self, arg, index):
1121
+ if self.tree is None:
1122
+ raise arch.errors.TreeRootError
1123
+- completions = list(ancillary.iter_partners(self.tree,
1124
+- self.tree.tree_version))
1125
+- if len(completions) == 0:
1126
+- completions = list(self.tree.iter_log_versions())
1130
+- for completion in completions:
1131
+- alias = ancillary.compact_alias(str(completion), self.tree)
1133
+- aliases.extend(alias)
1135
+- for completion in completions:
1136
+- if completion.archive == self.tree.tree_version.archive:
1137
+- aliases.append(completion.nonarch)
1139
+- except Exception, e:
1142
+- completions.extend(aliases)
1143
+- return completions
1145
+ def do_command(self, cmdargs):
1147
+@@ -1961,7 +1795,7 @@
1149
+ if self.tree is None:
1150
+ raise arch.errors.TreeRootError(os.getcwd())
1151
+- if cmdutil.has_changed(self.tree.tree_version):
1152
+ raise UncommittedChanges(self.tree)
1155
+@@ -2027,14 +1861,14 @@
1156
+ :type other_revision: `arch.Revision`
1157
+ :return: 0 if the merge was skipped, 1 if it was applied
1159
+- other_tree = cmdutil.find_or_make_local_revision(other_revision)
1161
+ if action == "native-merge":
1162
+- ancestor = cmdutil.merge_ancestor2(self.tree, other_tree,
1164
+ elif action == "update":
1165
+- ancestor = cmdutil.tree_latest(self.tree,
1166
+- other_revision.version)
1167
+ except CantDetermineRevision, e:
1168
+ raise CommandFailedWrapper(e)
1169
+ cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
1170
+@@ -2104,7 +1938,10 @@
1171
+ if self.tree is None:
1172
+ raise arch.errors.TreeRootError
1174
+- edit_log(self.tree)
1176
+ def get_parser(self):
1178
+@@ -2132,7 +1969,7 @@
1182
+-def edit_log(tree):
1183
++def edit_log(tree, version):
1184
+ """Makes and edits the log for a tree. Does all kinds of fancy things
1185
+ like log templates and merge summaries and log-for-merge
1187
+@@ -2141,28 +1978,29 @@
1189
+ #ensure we have an editor before preparing the log
1190
+ cmdutil.find_editor()
1191
+- log = tree.log_message(create=False)
1192
+ log_is_new = False
1193
+ if log is None or cmdutil.prompt("Overwrite log"):
1194
+ if log is not None:
1195
+ os.remove(log.name)
1196
+- log = tree.log_message(create=True)
1199
+- template = tree+"/{arch}/=log-template"
1200
+- if not os.path.exists(template):
1201
+- template = os.path.expanduser("~/.arch-params/=log-template")
1202
+- if not os.path.exists(template):
1205
+ shutil.copyfile(template, tmplog)
1207
+- new_merges = list(cmdutil.iter_new_merges(tree,
1208
+- tree.tree_version))
1209
+- log["Summary"] = merge_summary(new_merges, tree.tree_version)
1210
+ if len(new_merges) > 0:
1211
+ if cmdutil.prompt("Log for merge"):
1212
+- mergestuff = cmdutil.log_for_merge(tree)
1213
+ log.description += mergestuff
1216
+@@ -2172,29 +2010,6 @@
1217
+ os.remove(log.name)
1220
+-def merge_summary(new_merges, tree_version):
1221
+- if len(new_merges) == 0:
1223
+- if len(new_merges) == 1:
1224
+- summary = new_merges[0].summary
1226
+- summary = "Merge"
1229
+- for merge in new_merges:
1230
+- if arch.my_id() != merge.creator:
1231
+- name = re.sub("<.*>", "", merge.creator).rstrip(" ");
1232
+- if not name in credits:
1233
+- credits.append(name)
1235
+- version = merge.revision.version
1236
+- if version.archive == tree_version.archive:
1237
+- if not version.nonarch in credits:
1238
+- credits.append(version.nonarch)
1239
+- elif not str(version) in credits:
1240
+- credits.append(str(version))
1242
+- return ("%s (%s)") % (summary, ", ".join(credits))
1244
+ class MirrorArchive(BaseCommand):
1246
+@@ -2268,31 +2083,73 @@
1248
+ Use "alias" to list available (user and automatic) aliases."""
1252
++"The latest revision in the archive of the tree-version. You can specify \
1253
++a different version like so: acur:foo--bar--0 (aliases can be used)",
1255
++"""(tree current) The latest revision in the tree of the tree-version. \
1256
++You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
1259
++"""(tree previous) The previous revision in the tree of the tree-version. To \
1260
++specify an older revision, use a number, e.g. "tprev:4" """,
1262
++"""(tree ancestor) The ancestor revision of the tree To specify an older \
1263
++revision, use a number, e.g. "tanc:4".""",
1265
++"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
1267
++""" (tree modified) The latest revision to modify a given file, e.g. \
1268
++"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
1270
++"""(tree tag) The revision that was tagged into the current tree revision, \
1271
++according to the tree""",
1273
++"""(tag current) The latest revision of the version that the current tree \
1274
++was tagged from.""",
1276
++"""The common ancestor of the current tree and the specified revision. \
1277
++Defaults to the first partner-version's latest revision or to tagcur.""",
1281
++def is_auto_alias(name):
1285
++def display_def(iter, wrap = 80):
1289
+ def help_aliases(tree):
1290
+- print """Auto-generated aliases
1291
+- acur : The latest revision in the archive of the tree-version. You can specfy
1292
+- a different version like so: acur:foo--bar--0 (aliases can be used)
1293
+- tcur : (tree current) The latest revision in the tree of the tree-version.
1294
+- You can specify a different version like so: tcur:foo--bar--0 (aliases
1296
+-tprev : (tree previous) The previous revision in the tree of the tree-version.
1297
+- To specify an older revision, use a number, e.g. "tprev:4"
1298
+- tanc : (tree ancestor) The ancestor revision of the tree
1299
+- To specify an older revision, use a number, e.g. "tanc:4"
1300
+-tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
1301
+- tmod : (tree modified) The latest revision to modify a given file
1302
+- (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
1303
+- ttag : (tree tag) The revision that was tagged into the current tree revision,
1304
+- according to the tree.
1305
+-tagcur: (tag current) The latest revision of the version that the current tree
1307
+-mergeanc : The common ancestor of the current tree and the specified revision.
1308
+- Defaults to the first partner-version's latest revision or to tagcur.
1310
+ print "User aliases"
1311
+- for parts in ancillary.iter_all_alias(tree):
1312
+- print parts[0].rjust(10)+" : "+parts[1]
1315
+ class Inventory(BaseCommand):
1316
+ """List the status of files in the tree"""
1317
+@@ -2428,6 +2285,11 @@
1318
+ except cmdutil.ForbiddenAliasSyntax, e:
1319
+ raise CommandFailedWrapper(e)
1321
+ def arg_dispatch(self, args, options):
1322
+ """Add, modify, or list aliases, depending on number of arguments
1324
+@@ -2438,15 +2300,20 @@
1325
+ if len(args) == 0:
1326
+ help_aliases(self.tree)
1328
+- elif len(args) == 1:
1329
+- self.print_alias(args[0])
1330
+- elif (len(args)) == 2:
1331
+- self.add(args[0], args[1], options)
1333
+- raise cmdutil.GetHelp
1335
+ def print_alias(self, alias):
1337
+ for pair in ancillary.iter_all_alias(self.tree):
1338
+ if pair[0] == alias:
1340
+@@ -2464,6 +2331,8 @@
1341
+ :type expansion: str
1342
+ :param options: The commandline options
1346
+ new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
1347
+@@ -2490,14 +2359,17 @@
1349
+ if len(args) != 1:
1350
+ raise cmdutil.GetHelp
1352
+ for pair in self.get_iterator(options):
1353
+- if pair[0] != args[0]:
1354
+ newlist+="%s=%s\n" % (pair[0], pair[1])
1358
+- raise errors.NoSuchAlias(args[0])
1359
+ self.write_aliases(newlist, options)
1361
+ def get_alias_file(self, options):
1362
+@@ -2526,7 +2398,7 @@
1363
+ :param options: The commandline options
1365
+ filename = os.path.expanduser(self.get_alias_file(options))
1366
+- file = cmdutil.NewFileVersion(filename)
1367
+ file.write(newlist)
1370
+@@ -2588,10 +2460,13 @@
1371
+ :param cmdargs: The commandline arguments
1372
+ :type cmdargs: list of str
1374
+- cmdutil.find_editor()
1375
+ parser = self.get_parser()
1376
+ (options, args) = parser.parse_args(cmdargs)
1378
+ self.tree=arch.tree_root()
1381
+@@ -2655,7 +2530,7 @@
1382
+ target_revision = cmdutil.determine_revision_arch(self.tree,
1385
+- target_revision = cmdutil.tree_latest(self.tree)
1387
+ merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
1388
+ self.tree, f)) for f in args[1:] ]
1389
+@@ -2711,7 +2586,7 @@
1391
+ :param message: The message to send
1392
+ :type message: `email.Message`"""
1393
+- server = smtplib.SMTP()
1394
+ server.sendmail(message['From'], message['To'], message.as_string())
1397
+@@ -2763,6 +2638,22 @@
1399
+ 'request-merge': RequestMerge,
1402
++def my_import(mod_name):
1404
++def plugin(mod_name):
1406
++for file in os.listdir(sys.path[0]+"/command"):
1409
+ 'apply-delta' : "Try \"apply-changes\".",
1410
+ 'delta' : "To compare two revisions, use \"changes\".",
1411
+@@ -2784,6 +2675,7 @@
1412
+ 'tagline' : "Use add-id. It uses taglines in tagline trees",
1413
+ 'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
1414
+ 'library-revisions' : "Use revisions --library",
1415
+-'file-revert' : "Use revert FILE"
1416
++'file-revert' : "Use revert FILE",
1417
++'join-branch' : "Use replay --logs-only"
1419
+ # arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
1421
*** added file 'testdata/insert_top.patch'
1423
+++ testdata/insert_top.patch
1425
+--- orig/pylon/patches.py
1426
++++ mod/pylon/patches.py
1431
+ class PatchSyntax(Exception):
1433
*** added file 'testdata/mod'
1437
+# Copyright (C) 2004 Aaron Bentley
1438
+# <aaron.bentley@utoronto.ca>
1440
+# This program is free software; you can redistribute it and/or modify
1441
+# it under the terms of the GNU General Public License as published by
1442
+# the Free Software Foundation; either version 2 of the License, or
1443
+# (at your option) any later version.
1445
+# This program is distributed in the hope that it will be useful,
1446
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1447
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1448
+# GNU General Public License for more details.
1450
+# You should have received a copy of the GNU General Public License
1451
+# along with this program; if not, write to the Free Software
1452
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1459
+import pylon.errors
1460
+from pylon.errors import *
1461
+from pylon import errors
1462
+from pylon import util
1463
+from pylon import arch_core
1464
+from pylon import arch_compound
1465
+from pylon import ancillary
1466
+from pylon import misc
1467
+from pylon import paths
1484
+__docformat__ = "restructuredtext"
1485
+__doc__ = "Implementation of user (sub) commands"
1488
+def find_command(cmd):
1490
+ Return an instance of a command type. Return None if the type isn't
1493
+ :param cmd: the name of the command to look for
1494
+ :type cmd: the type of the command
1496
+ if commands.has_key(cmd):
1497
+ return commands[cmd]()
1502
+ def __call__(self, cmdline):
1504
+ self.do_command(cmdline.split())
1505
+ except cmdutil.GetHelp, e:
1507
+ except Exception, e:
1510
+ def get_completer(index):
1513
+ def complete(self, args, text):
1515
+ Returns a list of possible completions for the given text.
1517
+ :param args: The complete list of arguments
1518
+ :type args: List of str
1519
+ :param text: text to complete (may be shorter than args[-1])
1521
+ :rtype: list of str
1527
+ realtext = args[-1]
1532
+ parser=self.get_parser()
1533
+ if realtext.startswith('-'):
1534
+ candidates = parser.iter_options()
1536
+ (options, parsed_args) = parser.parse_args(args)
1538
+ if len (parsed_args) > 0:
1539
+ candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
1541
+ candidates = self.get_completer("", 0)
1544
+ if candidates is None:
1546
+ for candidate in candidates:
1547
+ candidate = str(candidate)
1548
+ if candidate.startswith(realtext):
1549
+ matches.append(candidate[len(realtext)- len(text):])
1553
+class Help(BaseCommand):
1555
+ Lists commands, prints help messages.
1557
+ def __init__(self):
1558
+ self.description="Prints help mesages"
1559
+ self.parser = None
1561
+ def do_command(self, cmdargs):
1563
+ Prints a help message.
1565
+ options, args = self.get_parser().parse_args(cmdargs)
1567
+ raise cmdutil.GetHelp
1569
+ if options.native or options.suggestions or options.external:
1570
+ native = options.native
1571
+ suggestions = options.suggestions
1572
+ external = options.external
1575
+ suggestions = False
1578
+ if len(args) == 0:
1579
+ self.list_commands(native, suggestions, external)
1581
+ elif len(args) == 1:
1582
+ command_help(args[0])
1586
+ self.get_parser().print_help()
1588
+If no command is specified, commands are listed. If a command is
1589
+specified, help for that command is listed.
1592
+ def get_parser(self):
1594
+ Returns the options parser to use for the "revision" command.
1596
+ :rtype: cmdutil.CmdOptionParser
1598
+ if self.parser is not None:
1599
+ return self.parser
1600
+ parser=cmdutil.CmdOptionParser("fai help [command]")
1601
+ parser.add_option("-n", "--native", action="store_true",
1602
+ dest="native", help="Show native commands")
1603
+ parser.add_option("-e", "--external", action="store_true",
1604
+ dest="external", help="Show external commands")
1605
+ parser.add_option("-s", "--suggest", action="store_true",
1606
+ dest="suggestions", help="Show suggestions")
1607
+ self.parser = parser
1610
+ def list_commands(self, native=True, suggest=False, external=True):
1612
+ Lists supported commands.
1614
+ :param native: list native, python-based commands
1615
+ :type native: bool
1616
+ :param external: list external aba-style commands
1617
+ :type external: bool
1620
+ print "Native Fai commands"
1621
+ keys=commands.keys()
1625
+ for i in range(28-len(k)):
1627
+ print space+k+" : "+commands[k]().description
1630
+ print "Unavailable commands and suggested alternatives"
1631
+ key_list = suggestions.keys()
1633
+ for key in key_list:
1634
+ print "%28s : %s" % (key, suggestions[key])
1637
+ fake_aba = abacmds.AbaCmds()
1638
+ if (fake_aba.abadir == ""):
1640
+ print "External commands"
1641
+ fake_aba.list_commands()
1644
+ print "Use help --suggest to list alternatives to tla and aba"\
1646
+ if options.tla_fallthrough and (native or external):
1647
+ print "Fai also supports tla commands."
1649
+def command_help(cmd):
1651
+ Prints help for a command.
1653
+ :param cmd: The name of the command to print help for
1656
+ fake_aba = abacmds.AbaCmds()
1657
+ cmdobj = find_command(cmd)
1658
+ if cmdobj != None:
1660
+ elif suggestions.has_key(cmd):
1661
+ print "Not available\n" + suggestions[cmd]
1663
+ abacmd = fake_aba.is_command(cmd)
1667
+ print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
1671
+class Changes(BaseCommand):
1673
+ the "changes" command: lists differences between trees/revisions:
1676
+ def __init__(self):
1677
+ self.description="Lists what files have changed in the project tree"
1679
+ def get_completer(self, arg, index):
1683
+ tree = arch.tree_root()
1686
+ return cmdutil.iter_revision_completions(arg, tree)
1688
+ def parse_commandline(self, cmdline):
1690
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
1692
+ :param cmdline: A list of arguments to parse
1693
+ :rtype: (options, Revision, Revision/WorkingTree)
1695
+ parser=self.get_parser()
1696
+ (options, args) = parser.parse_args(cmdline)
1698
+ raise cmdutil.GetHelp
1700
+ tree=arch.tree_root()
1701
+ if len(args) == 0:
1702
+ a_spec = ancillary.comp_revision(tree)
1704
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
1705
+ cmdutil.ensure_archive_registered(a_spec.archive)
1706
+ if len(args) == 2:
1707
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
1708
+ cmdutil.ensure_archive_registered(b_spec.archive)
1711
+ return options, a_spec, b_spec
1713
+ def do_command(self, cmdargs):
1715
+ Master function that perfoms the "changes" command.
1718
+ options, a_spec, b_spec = self.parse_commandline(cmdargs);
1719
+ except cmdutil.CantDetermineRevision, e:
1722
+ except arch.errors.TreeRootError, e:
1725
+ if options.changeset:
1726
+ changeset=options.changeset
1729
+ tmpdir=util.tmpdir()
1730
+ changeset=tmpdir+"/changeset"
1732
+ delta=arch.iter_delta(a_spec, b_spec, changeset)
1734
+ for line in delta:
1735
+ if cmdutil.chattermatch(line, "changeset:"):
1738
+ cmdutil.colorize(line, options.suppress_chatter)
1739
+ except arch.util.ExecProblem, e:
1740
+ if e.proc.error and e.proc.error.startswith(
1741
+ "missing explicit id for file"):
1742
+ raise MissingID(e)
1745
+ status=delta.status
1748
+ if (options.perform_diff):
1749
+ chan = arch_compound.ChangesetMunger(changeset)
1750
+ chan.read_indices()
1751
+ if options.diffopts is not None:
1752
+ if isinstance(b_spec, arch.Revision):
1753
+ b_dir = b_spec.library_find()
1756
+ a_dir = a_spec.library_find()
1757
+ diffopts = options.diffopts.split()
1758
+ cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
1760
+ cmdutil.show_diffs(delta.changeset)
1762
+ if tmpdir and (os.access(tmpdir, os.X_OK)):
1763
+ shutil.rmtree(tmpdir)
1765
+ def get_parser(self):
1767
+ Returns the options parser to use for the "changes" command.
1769
+ :rtype: cmdutil.CmdOptionParser
1771
+ parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
1773
+ parser.add_option("-d", "--diff", action="store_true",
1774
+ dest="perform_diff", default=False,
1775
+ help="Show diffs in summary")
1776
+ parser.add_option("-c", "--changeset", dest="changeset",
1777
+ help="Store a changeset in the given directory",
1778
+ metavar="DIRECTORY")
1779
+ parser.add_option("-s", "--silent", action="store_true",
1780
+ dest="suppress_chatter", default=False,
1781
+ help="Suppress chatter messages")
1782
+ parser.add_option("--diffopts", dest="diffopts",
1783
+ help="Use the specified diff options",
1784
+ metavar="OPTIONS")
1788
+ def help(self, parser=None):
1790
+ Prints a help message.
1792
+ :param parser: If supplied, the parser to use for generating help. If \
1793
+ not supplied, it is retrieved.
1794
+ :type parser: cmdutil.CmdOptionParser
1796
+ if parser is None:
1797
+ parser=self.get_parser()
1798
+ parser.print_help()
1800
+Performs source-tree comparisons
1802
+If no revision is specified, the current project tree is compared to the
1803
+last-committed revision. If one revision is specified, the current project
1804
+tree is compared to that revision. If two revisions are specified, they are
1805
+compared to each other.
1811
+class ApplyChanges(BaseCommand):
1813
+ Apply differences between two revisions to a tree
1816
+ def __init__(self):
1817
+ self.description="Applies changes to a project tree"
1819
+ def get_completer(self, arg, index):
1823
+ tree = arch.tree_root()
1826
+ return cmdutil.iter_revision_completions(arg, tree)
1828
+ def parse_commandline(self, cmdline, tree):
1830
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
1832
+ :param cmdline: A list of arguments to parse
1833
+ :rtype: (options, Revision, Revision/WorkingTree)
1835
+ parser=self.get_parser()
1836
+ (options, args) = parser.parse_args(cmdline)
1837
+ if len(args) != 2:
1838
+ raise cmdutil.GetHelp
1840
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
1841
+ cmdutil.ensure_archive_registered(a_spec.archive)
1842
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
1843
+ cmdutil.ensure_archive_registered(b_spec.archive)
1844
+ return options, a_spec, b_spec
1846
+ def do_command(self, cmdargs):
1848
+ Master function that performs "apply-changes".
1851
+ tree = arch.tree_root()
1852
+ options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
1853
+ except cmdutil.CantDetermineRevision, e:
1856
+ except arch.errors.TreeRootError, e:
1859
+ delta=cmdutil.apply_delta(a_spec, b_spec, tree)
1860
+ for line in cmdutil.iter_apply_delta_filter(delta):
1861
+ cmdutil.colorize(line, options.suppress_chatter)
1863
+ def get_parser(self):
1865
+ Returns the options parser to use for the "apply-changes" command.
1867
+ :rtype: cmdutil.CmdOptionParser
1869
+ parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
1871
+ parser.add_option("-d", "--diff", action="store_true",
1872
+ dest="perform_diff", default=False,
1873
+ help="Show diffs in summary")
1874
+ parser.add_option("-c", "--changeset", dest="changeset",
1875
+ help="Store a changeset in the given directory",
1876
+ metavar="DIRECTORY")
1877
+ parser.add_option("-s", "--silent", action="store_true",
1878
+ dest="suppress_chatter", default=False,
1879
+ help="Suppress chatter messages")
1882
+ def help(self, parser=None):
1884
+ Prints a help message.
1886
+ :param parser: If supplied, the parser to use for generating help. If \
1887
+ not supplied, it is retrieved.
1888
+ :type parser: cmdutil.CmdOptionParser
1890
+ if parser is None:
1891
+ parser=self.get_parser()
1892
+ parser.print_help()
1894
+Applies changes to a project tree
1896
+Compares two revisions and applies the difference between them to the current
1902
+class Update(BaseCommand):
1904
+ Updates a project tree to a given revision, preserving un-committed hanges.
1907
+ def __init__(self):
1908
+ self.description="Apply the latest changes to the current directory"
1910
+ def get_completer(self, arg, index):
1914
+ tree = arch.tree_root()
1917
+ return cmdutil.iter_revision_completions(arg, tree)
1919
+ def parse_commandline(self, cmdline, tree):
1921
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
1923
+ :param cmdline: A list of arguments to parse
1924
+ :rtype: (options, Revision, Revision/WorkingTree)
1926
+ parser=self.get_parser()
1927
+ (options, args) = parser.parse_args(cmdline)
1929
+ raise cmdutil.GetHelp
1934
+ revision=cmdutil.determine_revision_arch(tree, spec)
1935
+ cmdutil.ensure_archive_registered(revision.archive)
1937
+ mirror_source = cmdutil.get_mirror_source(revision.archive)
1938
+ if mirror_source != None:
1939
+ if cmdutil.prompt("Mirror update"):
1940
+ cmd=cmdutil.mirror_archive(mirror_source,
1941
+ revision.archive, arch.NameParser(revision).get_package_version())
1942
+ for line in arch.chatter_classifier(cmd):
1943
+ cmdutil.colorize(line, options.suppress_chatter)
1945
+ revision=cmdutil.determine_revision_arch(tree, spec)
1947
+ return options, revision
1949
+ def do_command(self, cmdargs):
1951
+ Master function that perfoms the "update" command.
1953
+ tree=arch.tree_root()
1955
+ options, to_revision = self.parse_commandline(cmdargs, tree);
1956
+ except cmdutil.CantDetermineRevision, e:
1959
+ except arch.errors.TreeRootError, e:
1962
+ from_revision = arch_compound.tree_latest(tree)
1963
+ if from_revision==to_revision:
1964
+ print "Tree is already up to date with:\n"+str(to_revision)+"."
1966
+ cmdutil.ensure_archive_registered(from_revision.archive)
1967
+ cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
1968
+ options.patch_forward)
1969
+ for line in cmdutil.iter_apply_delta_filter(cmd):
1970
+ cmdutil.colorize(line)
1971
+ if to_revision.version != tree.tree_version:
1972
+ if cmdutil.prompt("Update version"):
1973
+ tree.tree_version = to_revision.version
1975
+ def get_parser(self):
1977
+ Returns the options parser to use for the "update" command.
1979
+ :rtype: cmdutil.CmdOptionParser
1981
+ parser=cmdutil.CmdOptionParser("fai update [options]"
1982
+ " [revision/version]")
1983
+ parser.add_option("-f", "--forward", action="store_true",
1984
+ dest="patch_forward", default=False,
1985
+ help="pass the --forward option to 'patch'")
1986
+ parser.add_option("-s", "--silent", action="store_true",
1987
+ dest="suppress_chatter", default=False,
1988
+ help="Suppress chatter messages")
1991
+ def help(self, parser=None):
1993
+ Prints a help message.
1995
+ :param parser: If supplied, the parser to use for generating help. If \
1996
+ not supplied, it is retrieved.
1997
+ :type parser: cmdutil.CmdOptionParser
1999
+ if parser is None:
2000
+ parser=self.get_parser()
2001
+ parser.print_help()
2003
+Updates a working tree to the current archive revision
2005
+If a revision or version is specified, that is used instead
2011
+class Commit(BaseCommand):
2013
+ Create a revision based on the changes in the current tree.
2016
+ def __init__(self):
2017
+ self.description="Write local changes to the archive"
2019
+ def get_completer(self, arg, index):
2022
+ return iter_modified_file_completions(arch.tree_root(), arg)
2023
+# return iter_source_file_completions(arch.tree_root(), arg)
2025
+ def parse_commandline(self, cmdline, tree):
2027
+ Parse commandline arguments. Raise cmtutil.GetHelp if help is needed.
2029
+ :param cmdline: A list of arguments to parse
2030
+ :rtype: (options, Revision, Revision/WorkingTree)
2032
+ parser=self.get_parser()
2033
+ (options, args) = parser.parse_args(cmdline)
2035
+ if len(args) == 0:
2037
+ if options.version is None:
2038
+ return options, tree.tree_version, args
2040
+ revision=cmdutil.determine_revision_arch(tree, options.version)
2041
+ return options, revision.get_version(), args
2043
+ def do_command(self, cmdargs):
2045
+ Master function that perfoms the "commit" command.
2047
+ tree=arch.tree_root()
2048
+ options, version, files = self.parse_commandline(cmdargs, tree)
2050
+ if options.__dict__.has_key("base") and options.base:
2051
+ base = cmdutil.determine_revision_tree(tree, options.base)
2054
+ base = ancillary.submit_revision(tree)
2056
+ if ancestor is None:
2057
+ ancestor = arch_compound.tree_latest(tree, version)
2059
+ writeversion=version
2060
+ archive=version.archive
2061
+ source=cmdutil.get_mirror_source(archive)
2063
+ writethrough="implicit"
2066
+ if writethrough=="explicit" and \
2067
+ cmdutil.prompt("Writethrough"):
2068
+ writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
2069
+ elif writethrough=="none":
2070
+ raise CommitToMirror(archive)
2072
+ elif archive.is_mirror:
2073
+ raise CommitToMirror(archive)
2076
+ last_revision=tree.iter_logs(version, True).next().revision
2077
+ except StopIteration, e:
2078
+ last_revision = None
2079
+ if ancestor is None:
2080
+ if cmdutil.prompt("Import from commit"):
2081
+ return do_import(version)
2083
+ raise NoVersionLogs(version)
2085
+ arch_last_revision = version.iter_revisions(True).next()
2086
+ except StopIteration, e:
2087
+ arch_last_revision = None
2089
+ if last_revision != arch_last_revision:
2090
+ print "Tree is not up to date with %s" % str(version)
2091
+ if not cmdutil.prompt("Out of date"):
2097
+ if not cmdutil.has_changed(ancestor):
2098
+ if not cmdutil.prompt("Empty commit"):
2100
+ except arch.util.ExecProblem, e:
2101
+ if e.proc.error and e.proc.error.startswith(
2102
+ "missing explicit id for file"):
2103
+ raise MissingID(e)
2106
+ log = tree.log_message(create=False, version=version)
2109
+ if cmdutil.prompt("Create log"):
2110
+ edit_log(tree, version)
2112
+ except cmdutil.NoEditorSpecified, e:
2113
+ raise CommandFailed(e)
2114
+ log = tree.log_message(create=False, version=version)
2116
+ raise NoLogMessage
2117
+ if log["Summary"] is None or len(log["Summary"].strip()) == 0:
2118
+ if not cmdutil.prompt("Omit log summary"):
2119
+ raise errors.NoLogSummary
2121
+ for line in tree.iter_commit(version, seal=options.seal_version,
2122
+ base=base, out_of_date_ok=allow_old, file_list=files):
2123
+ cmdutil.colorize(line, options.suppress_chatter)
2125
+ except arch.util.ExecProblem, e:
2126
+ if e.proc.error and e.proc.error.startswith(
2127
+ "These files violate naming conventions:"):
2128
+ raise LintFailure(e.proc.error)
2132
+ def get_parser(self):
2134
+ Returns the options parser to use for the "commit" command.
2136
+ :rtype: cmdutil.CmdOptionParser
2139
+ parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
2141
+ parser.add_option("--seal", action="store_true",
2142
+ dest="seal_version", default=False,
2143
+ help="seal this version")
2144
+ parser.add_option("-v", "--version", dest="version",
2145
+ help="Use the specified version",
2146
+ metavar="VERSION")
2147
+ parser.add_option("-s", "--silent", action="store_true",
2148
+ dest="suppress_chatter", default=False,
2149
+ help="Suppress chatter messages")
2150
+ if cmdutil.supports_switch("commit", "--base"):
2151
+ parser.add_option("--base", dest="base", help="",
2152
+ metavar="REVISION")
2155
+ def help(self, parser=None):
2157
+ Prints a help message.
2159
+ :param parser: If supplied, the parser to use for generating help. If \
2160
+ not supplied, it is retrieved.
2161
+ :type parser: cmdutil.CmdOptionParser
2163
+ if parser is None:
2164
+ parser=self.get_parser()
2165
+ parser.print_help()
2167
+Updates a working tree to the current archive revision
2169
+If a version is specified, that is used instead
2176
+class CatLog(BaseCommand):
2178
+ Print the log of a given file (from current tree)
2180
+ def __init__(self):
2181
+ self.description="Prints the patch log for a revision"
2183
+ def get_completer(self, arg, index):
2187
+ tree = arch.tree_root()
2190
+ return cmdutil.iter_revision_completions(arg, tree)
2192
+ def do_command(self, cmdargs):
2194
+ Master function that perfoms the "cat-log" command.
2196
+ parser=self.get_parser()
2197
+ (options, args) = parser.parse_args(cmdargs)
2199
+ tree = arch.tree_root()
2200
+ except arch.errors.TreeRootError, e:
2206
+ raise cmdutil.GetHelp()
2209
+ revision = cmdutil.determine_revision_tree(tree, spec)
2211
+ revision = cmdutil.determine_revision_arch(tree, spec)
2212
+ except cmdutil.CantDetermineRevision, e:
2213
+ raise CommandFailedWrapper(e)
2216
+ use_tree = (options.source == "tree" or \
2217
+ (options.source == "any" and tree))
2218
+ use_arch = (options.source == "archive" or options.source == "any")
2222
+ for log in tree.iter_logs(revision.get_version()):
2223
+ if log.revision == revision:
2227
+ if log is None and use_arch:
2228
+ cmdutil.ensure_revision_exists(revision)
2229
+ log = arch.Patchlog(revision)
2230
+ if log is not None:
2231
+ for item in log.items():
2232
+ print "%s: %s" % item
2233
+ print log.description
2235
+ def get_parser(self):
2237
+ Returns the options parser to use for the "cat-log" command.
2239
+ :rtype: cmdutil.CmdOptionParser
2241
+ parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
2242
+ parser.add_option("--archive", action="store_const", dest="source",
2243
+ const="archive", default="any",
2244
+ help="Always get the log from the archive")
2245
+ parser.add_option("--tree", action="store_const", dest="source",
2246
+ const="tree", help="Always get the log from the tree")
2249
+ def help(self, parser=None):
2251
+ Prints a help message.
2253
+ :param parser: If supplied, the parser to use for generating help. If \
2254
+ not supplied, it is retrieved.
2255
+ :type parser: cmdutil.CmdOptionParser
2258
+ parser=self.get_parser()
2259
+ parser.print_help()
2261
+Prints the log for the specified revision
2266
+class Revert(BaseCommand):
2267
+ """ Reverts a tree (or aspects of it) to a revision
2269
+ def __init__(self):
2270
+ self.description="Reverts a tree (or aspects of it) to a revision "
2272
+ def get_completer(self, arg, index):
2276
+ tree = arch.tree_root()
2279
+ return iter_modified_file_completions(tree, arg)
2281
+ def do_command(self, cmdargs):
2283
+ Master function that perfoms the "revert" command.
2285
+ parser=self.get_parser()
2286
+ (options, args) = parser.parse_args(cmdargs)
2288
+ tree = arch.tree_root()
2289
+ except arch.errors.TreeRootError, e:
2290
+ raise CommandFailed(e)
2292
+ if options.revision is not None:
2293
+ spec=options.revision
2295
+ if spec is not None:
2296
+ revision = cmdutil.determine_revision_tree(tree, spec)
2298
+ revision = ancillary.comp_revision(tree)
2299
+ except cmdutil.CantDetermineRevision, e:
2300
+ raise CommandFailedWrapper(e)
2303
+ if options.file_contents or options.file_perms or options.deletions\
2304
+ or options.additions or options.renames or options.hunk_prompt:
2305
+ munger = arch_compound.MungeOpts()
2306
+ munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
2307
+ options.hunk_prompt)
2309
+ if len(args) > 0 or options.logs or options.pattern_files or \
2311
+ if munger is None:
2312
+ munger = cmdutil.arch_compound.MungeOpts(True)
2313
+ munger.all_types(True)
2315
+ t_cwd = arch_compound.tree_cwd(tree)
2317
+ if len(t_cwd) > 0:
2319
+ name = "./" + t_cwd + name
2320
+ munger.add_keep_file(name);
2322
+ if options.file_perms:
2323
+ munger.file_perms = True
2324
+ if options.file_contents:
2325
+ munger.file_contents = True
2326
+ if options.deletions:
2327
+ munger.deletions = True
2328
+ if options.additions:
2329
+ munger.additions = True
2330
+ if options.renames:
2331
+ munger.renames = True
2333
+ munger.add_keep_pattern('^\./\{arch\}/[^=].*')
2334
+ if options.control:
2335
+ munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
2336
+ "/\.arch-inventory$")
2337
+ if options.pattern_files:
2338
+ munger.add_keep_pattern(options.pattern_files)
2340
+ for line in arch_compound.revert(tree, revision, munger,
2341
+ not options.no_output):
2342
+ cmdutil.colorize(line)
2345
+ def get_parser(self):
2347
+ Returns the options parser to use for the "cat-log" command.
2349
+ :rtype: cmdutil.CmdOptionParser
2351
+ parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
2352
+ parser.add_option("", "--contents", action="store_true",
2353
+ dest="file_contents",
2354
+ help="Revert file content changes")
2355
+ parser.add_option("", "--permissions", action="store_true",
2356
+ dest="file_perms",
2357
+ help="Revert file permissions changes")
2358
+ parser.add_option("", "--deletions", action="store_true",
2360
+ help="Restore deleted files")
2361
+ parser.add_option("", "--additions", action="store_true",
2363
+ help="Remove added files")
2364
+ parser.add_option("", "--renames", action="store_true",
2366
+ help="Revert file names")
2367
+ parser.add_option("--hunks", action="store_true",
2368
+ dest="hunk_prompt", default=False,
2369
+ help="Prompt which hunks to revert")
2370
+ parser.add_option("--pattern-files", dest="pattern_files",
2371
+ help="Revert files that match this pattern",
2373
+ parser.add_option("--logs", action="store_true",
2374
+ dest="logs", default=False,
2375
+ help="Revert only logs")
2376
+ parser.add_option("--control-files", action="store_true",
2377
+ dest="control", default=False,
2378
+ help="Revert logs and other control files")
2379
+ parser.add_option("-n", "--no-output", action="store_true",
2381
+ help="Don't keep an undo changeset")
2382
+ parser.add_option("--revision", dest="revision",
2383
+ help="Revert to the specified revision",
2384
+ metavar="REVISION")
2387
+ def help(self, parser=None):
2389
+ Prints a help message.
2391
+ :param parser: If supplied, the parser to use for generating help. If \
2392
+ not supplied, it is retrieved.
2393
+ :type parser: cmdutil.CmdOptionParser
2396
+ parser=self.get_parser()
2397
+ parser.print_help()
2399
+Reverts changes in the current working tree. If no flags are specified, all
2400
+types of changes are reverted. Otherwise, only selected types of changes are
2403
+If a revision is specified on the commandline, differences between the current
2404
+tree and that revision are reverted. If a version is specified, the current
2405
+tree is used to determine the revision.
2407
+If files are specified, only those files listed will have any changes applied.
2408
+To specify a renamed file, you can use either the old or new name. (or both!)
2410
+Unless "-n" is specified, reversions can be undone with "redo".
2414
+class Revision(BaseCommand):
2416
+ Print a revision name based on a revision specifier
2418
+ def __init__(self):
2419
+ self.description="Prints the name of a revision"
2421
+ def get_completer(self, arg, index):
2425
+ tree = arch.tree_root()
2428
+ return cmdutil.iter_revision_completions(arg, tree)
2430
+ def do_command(self, cmdargs):
2432
+ Master function that perfoms the "revision" command.
2434
+ parser=self.get_parser()
2435
+ (options, args) = parser.parse_args(cmdargs)
2438
+ tree = arch.tree_root()
2439
+ except arch.errors.TreeRootError:
2446
+ raise cmdutil.GetHelp
2449
+ revision = cmdutil.determine_revision_tree(tree, spec)
2451
+ revision = cmdutil.determine_revision_arch(tree, spec)
2452
+ except cmdutil.CantDetermineRevision, e:
2455
+ print options.display(revision)
2457
+ def get_parser(self):
2459
+ Returns the options parser to use for the "revision" command.
2461
+ :rtype: cmdutil.CmdOptionParser
2463
+ parser=cmdutil.CmdOptionParser("fai revision [revision]")
2464
+ parser.add_option("", "--location", action="store_const",
2465
+ const=paths.determine_path, dest="display",
2466
+ help="Show location instead of name", default=str)
2467
+ parser.add_option("--import", action="store_const",
2468
+ const=paths.determine_import_path, dest="display",
2469
+ help="Show location of import file")
2470
+ parser.add_option("--log", action="store_const",
2471
+ const=paths.determine_log_path, dest="display",
2472
+ help="Show location of log file")
2473
+ parser.add_option("--patch", action="store_const",
2474
+ dest="display", const=paths.determine_patch_path,
2475
+ help="Show location of patchfile")
2476
+ parser.add_option("--continuation", action="store_const",
2477
+ const=paths.determine_continuation_path,
2479
+ help="Show location of continuation file")
2480
+ parser.add_option("--cacherev", action="store_const",
2481
+ const=paths.determine_cacherev_path, dest="display",
2482
+ help="Show location of cacherev file")
2485
+ def help(self, parser=None):
2487
+ Prints a help message.
2489
+ :param parser: If supplied, the parser to use for generating help. If \
2490
+ not supplied, it is retrieved.
2491
+ :type parser: cmdutil.CmdOptionParser
2494
+ parser=self.get_parser()
2495
+ parser.print_help()
2497
+Expands aliases and prints the name of the specified revision. Instead of
2498
+the name, several options can be used to print locations. If more than one is
2499
+specified, the last one is used.
2504
+class Revisions(BaseCommand):
2506
+ Print a revision name based on a revision specifier
2508
+ def __init__(self):
2509
+ self.description="Lists revisions"
2510
+ self.cl_revisions = []
2512
+ def do_command(self, cmdargs):
2514
+ Master function that perfoms the "revision" command.
2516
+ (options, args) = self.get_parser().parse_args(cmdargs)
2518
+ raise cmdutil.GetHelp
2520
+ self.tree = arch.tree_root()
2521
+ except arch.errors.TreeRootError:
2523
+ if options.type == "default":
2524
+ options.type = "archive"
2526
+ iter = cmdutil.revision_iterator(self.tree, options.type, args,
2527
+ options.reverse, options.modified,
2529
+ except cmdutil.CantDetermineRevision, e:
2530
+ raise CommandFailedWrapper(e)
2531
+ except cmdutil.CantDetermineVersion, e:
2532
+ raise CommandFailedWrapper(e)
2533
+ if options.skip is not None:
2534
+ iter = cmdutil.iter_skip(iter, int(options.skip))
2537
+ for revision in iter:
2539
+ if isinstance(revision, arch.Patchlog):
2541
+ revision=revision.revision
2542
+ out = options.display(revision)
2543
+ if out is not None:
2545
+ if log is None and (options.summary or options.creator or
2546
+ options.date or options.merges):
2547
+ log = revision.patchlog
2548
+ if options.creator:
2549
+ print " %s" % log.creator
2551
+ print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
2552
+ if options.summary:
2553
+ print " %s" % log.summary
2554
+ if options.merges:
2555
+ showed_title = False
2556
+ for revision in log.merged_patches:
2557
+ if not showed_title:
2559
+ showed_title = True
2560
+ print " %s" % revision
2561
+ if len(self.cl_revisions) > 0:
2562
+ print pylon.changelog_for_merge(self.cl_revisions)
2563
+ except pylon.errors.TreeRootNone:
2564
+ raise CommandFailedWrapper(
2565
+ Exception("This option can only be used in a project tree."))
2567
+ def changelog_append(self, revision):
2568
+ if isinstance(revision, arch.Revision):
2569
+ revision=arch.Patchlog(revision)
2570
+ self.cl_revisions.append(revision)
2572
+ def get_parser(self):
2574
+ Returns the options parser to use for the "revision" command.
2576
+ :rtype: cmdutil.CmdOptionParser
2578
+ parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
2579
+ select = cmdutil.OptionGroup(parser, "Selection options",
2580
+ "Control which revisions are listed. These options"
2581
+ " are mutually exclusive. If more than one is"
2582
+ " specified, the last is used.")
2584
+ cmdutil.add_revision_iter_options(select)
2585
+ parser.add_option("", "--skip", dest="skip",
2586
+ help="Skip revisions. Positive numbers skip from "
2587
+ "beginning, negative skip from end.",
2590
+ parser.add_option_group(select)
2592
+ format = cmdutil.OptionGroup(parser, "Revision format options",
2593
+ "These control the appearance of listed revisions")
2594
+ format.add_option("", "--location", action="store_const",
2595
+ const=paths.determine_path, dest="display",
2596
+ help="Show location instead of name", default=str)
2597
+ format.add_option("--import", action="store_const",
2598
+ const=paths.determine_import_path, dest="display",
2599
+ help="Show location of import file")
2600
+ format.add_option("--log", action="store_const",
2601
+ const=paths.determine_log_path, dest="display",
2602
+ help="Show location of log file")
2603
+ format.add_option("--patch", action="store_const",
2604
+ dest="display", const=paths.determine_patch_path,
2605
+ help="Show location of patchfile")
2606
+ format.add_option("--continuation", action="store_const",
2607
+ const=paths.determine_continuation_path,
2609
+ help="Show location of continuation file")
2610
+ format.add_option("--cacherev", action="store_const",
2611
+ const=paths.determine_cacherev_path, dest="display",
2612
+ help="Show location of cacherev file")
2613
+ format.add_option("--changelog", action="store_const",
2614
+ const=self.changelog_append, dest="display",
2615
+ help="Show location of cacherev file")
2616
+ parser.add_option_group(format)
2617
+ display = cmdutil.OptionGroup(parser, "Display format options",
2618
+ "These control the display of data")
2619
+ display.add_option("-r", "--reverse", action="store_true",
2620
+ dest="reverse", help="Sort from newest to oldest")
2621
+ display.add_option("-s", "--summary", action="store_true",
2622
+ dest="summary", help="Show patchlog summary")
2623
+ display.add_option("-D", "--date", action="store_true",
2624
+ dest="date", help="Show patchlog date")
2625
+ display.add_option("-c", "--creator", action="store_true",
2626
+ dest="creator", help="Show the id that committed the"
2628
+ display.add_option("-m", "--merges", action="store_true",
2629
+ dest="merges", help="Show the revisions that were"
2631
+ parser.add_option_group(display)
2633
+ def help(self, parser=None):
2634
+ """Attempt to explain the revisions command
2636
+ :param parser: If supplied, used to determine options
2639
+ parser=self.get_parser()
2640
+ parser.print_help()
2641
+ print """List revisions.
2646
+class Get(BaseCommand):
2648
+ Retrieve a revision from the archive
2650
+ def __init__(self):
2651
+ self.description="Retrieve a revision from the archive"
2652
+ self.parser=self.get_parser()
2655
+ def get_completer(self, arg, index):
2659
+ tree = arch.tree_root()
2662
+ return cmdutil.iter_revision_completions(arg, tree)
2665
+ def do_command(self, cmdargs):
2667
+ Master function that perfoms the "get" command.
2669
+ (options, args) = self.parser.parse_args(cmdargs)
2671
+ return self.help()
2673
+ tree = arch.tree_root()
2674
+ except arch.errors.TreeRootError:
2679
+ revision, arch_loc = paths.full_path_decode(args[0])
2680
+ except Exception, e:
2681
+ revision = cmdutil.determine_revision_arch(tree, args[0],
2682
+ check_existence=False, allow_package=True)
2684
+ directory = args[1]
2686
+ directory = str(revision.nonarch)
2687
+ if os.path.exists(directory):
2688
+ raise DirectoryExists(directory)
2689
+ cmdutil.ensure_archive_registered(revision.archive, arch_loc)
2691
+ cmdutil.ensure_revision_exists(revision)
2692
+ except cmdutil.NoSuchRevision, e:
2693
+ raise CommandFailedWrapper(e)
2695
+ link = cmdutil.prompt ("get link")
2696
+ for line in cmdutil.iter_get(revision, directory, link,
2697
+ options.no_pristine,
2698
+ options.no_greedy_add):
2699
+ cmdutil.colorize(line)
2701
+ def get_parser(self):
2703
+ Returns the options parser to use for the "get" command.
2705
+ :rtype: cmdutil.CmdOptionParser
2707
+ parser=cmdutil.CmdOptionParser("fai get revision [dir]")
2708
+ parser.add_option("--no-pristine", action="store_true",
2709
+ dest="no_pristine",
2710
+ help="Do not make pristine copy for reference")
2711
+ parser.add_option("--no-greedy-add", action="store_true",
2712
+ dest="no_greedy_add",
2713
+ help="Never add to greedy libraries")
2717
+ def help(self, parser=None):
2719
+ Prints a help message.
2721
+ :param parser: If supplied, the parser to use for generating help. If \
2722
+ not supplied, it is retrieved.
2723
+ :type parser: cmdutil.CmdOptionParser
2726
+ parser=self.get_parser()
2727
+ parser.print_help()
2729
+Expands aliases and constructs a project tree for a revision. If the optional
2730
+"dir" argument is provided, the project tree will be stored in this directory.
2735
+class PromptCmd(cmd.Cmd):
2736
+ def __init__(self):
2737
+ cmd.Cmd.__init__(self)
2738
+ self.prompt = "Fai> "
2740
+ self.tree = arch.tree_root()
2745
+ self.fake_aba = abacmds.AbaCmds()
2746
+ self.identchars += '-'
2747
+ self.history_file = os.path.expanduser("~/.fai-history")
2748
+ readline.set_completer_delims(string.whitespace)
2749
+ if os.access(self.history_file, os.R_OK) and \
2750
+ os.path.isfile(self.history_file):
2751
+ readline.read_history_file(self.history_file)
2752
+ self.cwd = os.getcwd()
2754
+ def write_history(self):
2755
+ readline.write_history_file(self.history_file)
2757
+ def do_quit(self, args):
2758
+ self.write_history()
2761
+ def do_exit(self, args):
2762
+ self.do_quit(args)
2764
+ def do_EOF(self, args):
2766
+ self.do_quit(args)
2768
+ def postcmd(self, line, bar):
2772
+ def set_prompt(self):
2773
+ if self.tree is not None:
2775
+ prompt = pylon.alias_or_version(self.tree.tree_version,
2778
+ if prompt is not None:
2779
+ prompt = " " + prompt
2784
+ self.prompt = "Fai%s> " % prompt
2786
+ def set_title(self, command=None):
2788
+ version = pylon.alias_or_version(self.tree.tree_version, self.tree,
2791
+ version = "[no version]"
2792
+ if command is None:
2794
+ sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
2796
+ def do_cd(self, line):
2799
+ line = os.path.expanduser(line)
2800
+ if os.path.isabs(line):
2803
+ newcwd = self.cwd+'/'+line
2804
+ newcwd = os.path.normpath(newcwd)
2808
+ except Exception, e:
2811
+ self.tree = arch.tree_root()
2815
+ def do_help(self, line):
2818
+ def default(self, line):
2819
+ args = line.split()
2820
+ if find_command(args[0]):
2822
+ find_command(args[0]).do_command(args[1:])
2823
+ except cmdutil.BadCommandOption, e:
2825
+ except cmdutil.GetHelp, e:
2826
+ find_command(args[0]).help()
2827
+ except CommandFailed, e:
2829
+ except arch.errors.ArchiveNotRegistered, e:
2831
+ except KeyboardInterrupt, e:
2832
+ print "Interrupted"
2833
+ except arch.util.ExecProblem, e:
2834
+ print e.proc.error.rstrip('\n')
2835
+ except cmdutil.CantDetermineVersion, e:
2837
+ except cmdutil.CantDetermineRevision, e:
2839
+ except Exception, e:
2840
+ print "Unhandled error:\n%s" % errors.exception_str(e)
2842
+ elif suggestions.has_key(args[0]):
2843
+ print suggestions[args[0]]
2845
+ elif self.fake_aba.is_command(args[0]):
2848
+ tree = arch.tree_root()
2849
+ except arch.errors.TreeRootError:
2851
+ cmd = self.fake_aba.is_command(args[0])
2853
+ cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
2854
+ except KeyboardInterrupt, e:
2855
+ print "Interrupted"
2857
+ elif options.tla_fallthrough and args[0] != "rm" and \
2858
+ cmdutil.is_tla_command(args[0]):
2862
+ tree = arch.tree_root()
2863
+ except arch.errors.TreeRootError:
2865
+ args = cmdutil.expand_prefix_alias(args, tree)
2866
+ arch.util.exec_safe('tla', args, stderr=sys.stderr,
2868
+ except arch.util.ExecProblem, e:
2870
+ except KeyboardInterrupt, e:
2871
+ print "Interrupted"
2875
+ tree = arch.tree_root()
2876
+ except arch.errors.TreeRootError:
2879
+ os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
2880
+ except KeyboardInterrupt, e:
2881
+ print "Interrupted"
2883
+ def completenames(self, text, line, begidx, endidx):
2885
+ iter = iter_command_names(self.fake_aba)
2888
+ arg = line.split()[-1]
2891
+ iter = cmdutil.iter_munged_completions(iter, arg, text)
2892
+ except Exception, e:
2896
+ def completedefault(self, text, line, begidx, endidx):
2897
+ """Perform completion for native commands.
2899
+ :param text: The text to complete
2901
+ :param line: The entire line to complete
2903
+ :param begidx: The start of the text in the line
2905
+ :param endidx: The end of the text in the line
2909
+ (cmd, args, foo) = self.parseline(line)
2910
+ command_obj=find_command(cmd)
2911
+ if command_obj is not None:
2912
+ return command_obj.complete(args.split(), text)
2913
+ elif not self.fake_aba.is_command(cmd) and \
2914
+ cmdutil.is_tla_command(cmd):
2915
+ iter = cmdutil.iter_supported_switches(cmd)
2917
+ arg = args.split()[-1]
2920
+ if arg.startswith("-"):
2921
+ return list(cmdutil.iter_munged_completions(iter, arg,
2924
+ return list(cmdutil.iter_munged_completions(
2925
+ cmdutil.iter_file_completions(arg), arg, text))
2930
+ arg = args.split()[-1]
2933
+ iter = cmdutil.iter_dir_completions(arg)
2934
+ iter = cmdutil.iter_munged_completions(iter, arg, text)
2937
+ arg = args.split()[-1]
2938
+ iter = cmdutil.iter_file_completions(arg)
2939
+ return list(cmdutil.iter_munged_completions(iter, arg, text))
2941
+ return self.completenames(text, line, begidx, endidx)
2942
+ except Exception, e:
2946
+def iter_command_names(fake_aba):
2947
+ for entry in cmdutil.iter_combine([commands.iterkeys(),
2948
+ fake_aba.get_commands(),
2949
+ cmdutil.iter_tla_commands(False)]):
2950
+ if not suggestions.has_key(str(entry)):
2954
+def iter_source_file_completions(tree, arg):
2955
+ treepath = arch_compound.tree_cwd(tree)
2956
+ if len(treepath) > 0:
2960
+ for file in tree.iter_inventory(dirs, source=True, both=True):
2961
+ file = file_completion_match(file, treepath, arg)
2962
+ if file is not None:
2966
+def iter_untagged(tree, dirs):
2967
+ for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False,
2968
+ categories=arch_core.non_root,
2969
+ control_files=True):
2973
+def iter_untagged_completions(tree, arg):
2974
+ """Generate an iterator for all visible untagged files that match arg.
2976
+ :param tree: The tree to look for untagged files in
2977
+ :type tree: `arch.WorkingTree`
2978
+ :param arg: The argument to match
2980
+ :return: An iterator of all matching untagged files
2981
+ :rtype: iterator of str
2983
+ treepath = arch_compound.tree_cwd(tree)
2984
+ if len(treepath) > 0:
2989
+ for file in iter_untagged(tree, dirs):
2990
+ file = file_completion_match(file, treepath, arg)
2991
+ if file is not None:
2995
+def file_completion_match(file, treepath, arg):
2996
+ """Determines whether a file within an arch tree matches the argument.
2998
+ :param file: The rooted filename
3000
+ :param treepath: The path to the cwd within the tree
3001
+ :type treepath: str
3002
+ :param arg: The prefix to match
3003
+ :return: The completion name, or None if not a match
3006
+ if not file.startswith(treepath):
3008
+ if treepath != "":
3009
+ file = file[len(treepath)+1:]
3011
+ if not file.startswith(arg):
3013
+ if os.path.isdir(file):
3017
+def iter_modified_file_completions(tree, arg):
3018
+ """Returns a list of modified files that match the specified prefix.
3020
+ :param tree: The current tree
3021
+ :type tree: `arch.WorkingTree`
3022
+ :param arg: The prefix to match
3025
+ treepath = arch_compound.tree_cwd(tree)
3026
+ tmpdir = util.tmpdir()
3027
+ changeset = tmpdir+"/changeset"
3029
+ revision = cmdutil.determine_revision_tree(tree)
3030
+ for line in arch.iter_delta(revision, tree, changeset):
3031
+ if isinstance(line, arch.FileModification):
3032
+ file = file_completion_match(line.name[1:], treepath, arg)
3033
+ if file is not None:
3034
+ completions.append(file)
3035
+ shutil.rmtree(tmpdir)
3036
+ return completions
3038
+class Shell(BaseCommand):
3039
+ def __init__(self):
3040
+ self.description = "Runs Fai as a shell"
3042
+ def do_command(self, cmdargs):
3043
+ if len(cmdargs)!=0:
3044
+ raise cmdutil.GetHelp
3045
+ prompt = PromptCmd()
3049
+ prompt.write_history()
3051
+class AddID(BaseCommand):
3053
+ Adds an inventory id for the given file
3055
+ def __init__(self):
3056
+ self.description="Add an inventory id for a given file"
3058
+ def get_completer(self, arg, index):
3059
+ tree = arch.tree_root()
3060
+ return iter_untagged_completions(tree, arg)
3062
+ def do_command(self, cmdargs):
3064
+ Master function that perfoms the "revision" command.
3066
+ parser=self.get_parser()
3067
+ (options, args) = parser.parse_args(cmdargs)
3070
+ tree = arch.tree_root()
3071
+ except arch.errors.TreeRootError, e:
3072
+ raise pylon.errors.CommandFailedWrapper(e)
3075
+ if (len(args) == 0) == (options.untagged == False):
3076
+ raise cmdutil.GetHelp
3078
+ #if options.id and len(args) != 1:
3079
+ # print "If --id is specified, only one file can be named."
3082
+ method = tree.tagging_method
3084
+ if options.id_type == "tagline":
3085
+ if method != "tagline":
3086
+ if not cmdutil.prompt("Tagline in other tree"):
3087
+ if method == "explicit" or method == "implicit":
3088
+ options.id_type == method
3090
+ print "add-id not supported for \"%s\" tagging method"\
3094
+ elif options.id_type == "implicit":
3095
+ if method != "implicit":
3096
+ if not cmdutil.prompt("Implicit in other tree"):
3097
+ if method == "explicit" or method == "tagline":
3098
+ options.id_type == method
3100
+ print "add-id not supported for \"%s\" tagging method"\
3103
+ elif options.id_type == "explicit":
3104
+ if method != "tagline" and method != explicit:
3105
+ if not prompt("Explicit in other tree"):
3106
+ print "add-id not supported for \"%s\" tagging method" % \
3110
+ if options.id_type == "auto":
3111
+ if method != "tagline" and method != "explicit" \
3112
+ and method !="implicit":
3113
+ print "add-id not supported for \"%s\" tagging method" % method
3116
+ options.id_type = method
3117
+ if options.untagged:
3119
+ self.add_ids(tree, options.id_type, args)
3121
+ def add_ids(self, tree, id_type, files=()):
3122
+ """Add inventory ids to files.
3124
+ :param tree: the tree the files are in
3125
+ :type tree: `arch.WorkingTree`
3126
+ :param id_type: the type of id to add: "explicit" or "tagline"
3127
+ :type id_type: str
3128
+ :param files: The list of files to add. If None do all untagged.
3129
+ :type files: tuple of str
3132
+ untagged = (files is None)
3134
+ files = list(iter_untagged(tree, None))
3135
+ previous_files = []
3136
+ while len(files) > 0:
3137
+ previous_files.extend(files)
3138
+ if id_type == "explicit":
3139
+ cmdutil.add_id(files)
3140
+ elif id_type == "tagline" or id_type == "implicit":
3141
+ for file in files:
3143
+ implicit = (id_type == "implicit")
3144
+ cmdutil.add_tagline_or_explicit_id(file, False,
3146
+ except cmdutil.AlreadyTagged:
3147
+ print "\"%s\" already has a tagline." % file
3148
+ except cmdutil.NoCommentSyntax:
3150
+ #do inventory after tagging until no untagged files are encountered
3153
+ for file in iter_untagged(tree, None):
3154
+ if not file in previous_files:
3155
+ files.append(file)
3160
+ def get_parser(self):
3162
+ Returns the options parser to use for the "revision" command.
3164
+ :rtype: cmdutil.CmdOptionParser
3166
+ parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
3167
+# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
3168
+# parser.add_option("-i", "--id", dest="id",
3169
+# help="Specify id for a single file", default=None)
3170
+ parser.add_option("--tltl", action="store_true",
3171
+ dest="lord_style", help="Use Tom Lord's style of id.")
3172
+ parser.add_option("--explicit", action="store_const",
3173
+ const="explicit", dest="id_type",
3174
+ help="Use an explicit id", default="auto")
3175
+ parser.add_option("--tagline", action="store_const",
3176
+ const="tagline", dest="id_type",
3177
+ help="Use a tagline id")
3178
+ parser.add_option("--implicit", action="store_const",
3179
+ const="implicit", dest="id_type",
3180
+ help="Use an implicit id (deprecated)")
3181
+ parser.add_option("--untagged", action="store_true",
3182
+ dest="untagged", default=False,
3183
+ help="tag all untagged files")
3186
+ def help(self, parser=None):
3188
+ Prints a help message.
3190
+ :param parser: If supplied, the parser to use for generating help. If \
3191
+ not supplied, it is retrieved.
3192
+ :type parser: cmdutil.CmdOptionParser
3195
+ parser=self.get_parser()
3196
+ parser.print_help()
3198
+Adds an inventory to the specified file(s) and directories. If --untagged is
3199
+specified, adds inventory to all untagged files and directories.
3204
+class Merge(BaseCommand):
3206
+ Merges changes from other versions into the current tree
3208
+ def __init__(self):
3209
+ self.description="Merges changes from other versions"
3211
+ self.tree = arch.tree_root()
3216
+ def get_completer(self, arg, index):
3217
+ if self.tree is None:
3218
+ raise arch.errors.TreeRootError
3219
+ return cmdutil.merge_completions(self.tree, arg, index)
3221
+ def do_command(self, cmdargs):
3223
+ Master function that perfoms the "merge" command.
3225
+ parser=self.get_parser()
3226
+ (options, args) = parser.parse_args(cmdargs)
3228
+ action="star-merge"
3230
+ action = options.action
3232
+ if self.tree is None:
3233
+ raise arch.errors.TreeRootError(os.getcwd())
3234
+ if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
3235
+ raise UncommittedChanges(self.tree)
3240
+ revisions.append(cmdutil.determine_revision_arch(self.tree,
3242
+ source = "from commandline"
3244
+ revisions = ancillary.iter_partner_revisions(self.tree,
3245
+ self.tree.tree_version)
3246
+ source = "from partner version"
3247
+ revisions = misc.rewind_iterator(revisions)
3250
+ revisions.rewind()
3251
+ except StopIteration, e:
3252
+ revision = cmdutil.tag_cur(self.tree)
3253
+ if revision is None:
3254
+ raise CantDetermineRevision("", "No version specified, no "
3255
+ "partner-versions, and no tag"
3257
+ revisions = [revision]
3258
+ source = "from tag source"
3259
+ for revision in revisions:
3260
+ cmdutil.ensure_archive_registered(revision.archive)
3261
+ cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
3262
+ (revision, source)))
3263
+ if action=="native-merge" or action=="update":
3264
+ if self.native_merge(revision, action) == 0:
3266
+ elif action=="star-merge":
3268
+ self.star_merge(revision, options.diff3)
3269
+ except errors.MergeProblem, e:
3271
+ if cmdutil.has_changed(self.tree.tree_version):
3274
+ def star_merge(self, revision, diff3):
3275
+ """Perform a star-merge on the current tree.
3277
+ :param revision: The revision to use for the merge
3278
+ :type revision: `arch.Revision`
3279
+ :param diff3: If true, do a diff3 merge
3283
+ for line in self.tree.iter_star_merge(revision, diff3=diff3):
3284
+ cmdutil.colorize(line)
3285
+ except arch.util.ExecProblem, e:
3286
+ if e.proc.status is not None and e.proc.status == 1:
3288
+ print e.proc.error
3289
+ raise MergeProblem
3293
+ def native_merge(self, other_revision, action):
3294
+ """Perform a native-merge on the current tree.
3296
+ :param other_revision: The revision to use for the merge
3297
+ :type other_revision: `arch.Revision`
3298
+ :return: 0 if the merge was skipped, 1 if it was applied
3300
+ other_tree = arch_compound.find_or_make_local_revision(other_revision)
3302
+ if action == "native-merge":
3303
+ ancestor = arch_compound.merge_ancestor2(self.tree, other_tree,
3305
+ elif action == "update":
3306
+ ancestor = arch_compound.tree_latest(self.tree,
3307
+ other_revision.version)
3308
+ except CantDetermineRevision, e:
3309
+ raise CommandFailedWrapper(e)
3310
+ cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
3311
+ if (ancestor == other_revision):
3312
+ cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
3315
+ delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
3316
+ for line in cmdutil.iter_apply_delta_filter(delta):
3317
+ cmdutil.colorize(line)
3322
+ def get_parser(self):
3324
+ Returns the options parser to use for the "merge" command.
3326
+ :rtype: cmdutil.CmdOptionParser
3328
+ parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
3329
+ parser.add_option("-s", "--star-merge", action="store_const",
3330
+ dest="action", help="Use star-merge",
3331
+ const="star-merge", default="native-merge")
3332
+ parser.add_option("--update", action="store_const",
3333
+ dest="action", help="Use update picker",
3335
+ parser.add_option("--diff3", action="store_true",
3337
+ help="Use diff3 for merge (implies star-merge)")
3340
+ def help(self, parser=None):
3342
+ Prints a help message.
3344
+ :param parser: If supplied, the parser to use for generating help. If \
3345
+ not supplied, it is retrieved.
3346
+ :type parser: cmdutil.CmdOptionParser
3349
+ parser=self.get_parser()
3350
+ parser.print_help()
3352
+Performs a merge operation using the specified version.
3356
+class ELog(BaseCommand):
3358
+ Produces a raw patchlog and invokes the user's editor
3360
+ def __init__(self):
3361
+ self.description="Edit a patchlog to commit"
3363
+ self.tree = arch.tree_root()
3368
+ def do_command(self, cmdargs):
3370
+ Master function that perfoms the "elog" command.
3372
+ parser=self.get_parser()
3373
+ (options, args) = parser.parse_args(cmdargs)
3374
+ if self.tree is None:
3375
+ raise arch.errors.TreeRootError
3378
+ edit_log(self.tree, self.tree.tree_version)
3379
+ except pylon.errors.NoEditorSpecified, e:
3380
+ raise pylon.errors.CommandFailedWrapper(e)
3382
+ def get_parser(self):
3384
+ Returns the options parser to use for the "merge" command.
3386
+ :rtype: cmdutil.CmdOptionParser
3388
+ parser=cmdutil.CmdOptionParser("fai elog")
3392
+ def help(self, parser=None):
3394
+ Invokes $EDITOR to produce a log for committing.
3396
+ :param parser: If supplied, the parser to use for generating help. If \
3397
+ not supplied, it is retrieved.
3398
+ :type parser: cmdutil.CmdOptionParser
3401
+ parser=self.get_parser()
3402
+ parser.print_help()
3404
+Invokes $EDITOR to produce a log for committing.
3408
+def edit_log(tree, version):
3409
+ """Makes and edits the log for a tree. Does all kinds of fancy things
3410
+ like log templates and merge summaries and log-for-merge
3412
+ :param tree: The tree to edit the log for
3413
+ :type tree: `arch.WorkingTree`
3415
+ #ensure we have an editor before preparing the log
3416
+ cmdutil.find_editor()
3417
+ log = tree.log_message(create=False, version=version)
3418
+ log_is_new = False
3419
+ if log is None or cmdutil.prompt("Overwrite log"):
3420
+ if log is not None:
3421
+ os.remove(log.name)
3422
+ log = tree.log_message(create=True, version=version)
3425
+ template = pylon.log_template_path(tree)
3427
+ shutil.copyfile(template, tmplog)
3428
+ comp_version = ancillary.comp_revision(tree).version
3429
+ new_merges = cmdutil.iter_new_merges(tree, comp_version)
3430
+ new_merges = cmdutil.direct_merges(new_merges)
3431
+ log["Summary"] = pylon.merge_summary(new_merges,
3433
+ if len(new_merges) > 0:
3434
+ if cmdutil.prompt("Log for merge"):
3435
+ if cmdutil.prompt("changelog for merge"):
3436
+ mergestuff = "Patches applied:\n"
3437
+ mergestuff += pylon.changelog_for_merge(new_merges)
3439
+ mergestuff = cmdutil.log_for_merge(tree, comp_version)
3440
+ log.description += mergestuff
3443
+ cmdutil.invoke_editor(log.name)
3446
+ os.remove(log.name)
3450
+class MirrorArchive(BaseCommand):
3452
+ Updates a mirror from an archive
3454
+ def __init__(self):
3455
+ self.description="Update a mirror from an archive"
3457
+ def do_command(self, cmdargs):
3459
+ Master function that perfoms the "revision" command.
3462
+ parser=self.get_parser()
3463
+ (options, args) = parser.parse_args(cmdargs)
3467
+ tree = arch.tree_root()
3471
+ if len(args) == 0:
3472
+ if tree is not None:
3473
+ name = tree.tree_version()
3475
+ name = cmdutil.expand_alias(args[0], tree)
3476
+ name = arch.NameParser(name)
3478
+ to_arch = name.get_archive()
3479
+ from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
3480
+ limit = name.get_nonarch()
3482
+ iter = arch_core.mirror_archive(from_arch,to_arch, limit)
3483
+ for line in arch.chatter_classifier(iter):
3484
+ cmdutil.colorize(line)
3486
+ def get_parser(self):
3488
+ Returns the options parser to use for the "revision" command.
3490
+ :rtype: cmdutil.CmdOptionParser
3492
+ parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
3495
+ def help(self, parser=None):
3497
+ Prints a help message.
3499
+ :param parser: If supplied, the parser to use for generating help. If \
3500
+ not supplied, it is retrieved.
3501
+ :type parser: cmdutil.CmdOptionParser
3504
+ parser=self.get_parser()
3505
+ parser.print_help()
3507
+Updates a mirror from an archive. If a branch, package, or version is
3508
+supplied, only changes under it are mirrored.
3512
+def help_tree_spec():
3513
+ print """Specifying revisions (default: tree)
3514
+Revisions may be specified by alias, revision, version or patchlevel.
3515
+Revisions or versions may be fully qualified. Unqualified revisions, versions,
3516
+or patchlevels use the archive of the current project tree. Versions will
3517
+use the latest patchlevel in the tree. Patchlevels will use the current tree-
3520
+Use "alias" to list available (user and automatic) aliases."""
3524
+"The latest revision in the archive of the tree-version. You can specify \
3525
+a different version like so: acur:foo--bar--0 (aliases can be used)",
3527
+"""(tree current) The latest revision in the tree of the tree-version. \
3528
+You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
3531
+"""(tree previous) The previous revision in the tree of the tree-version. To \
3532
+specify an older revision, use a number, e.g. "tprev:4" """,
3534
+"""(tree ancestor) The ancestor revision of the tree To specify an older \
3535
+revision, use a number, e.g. "tanc:4".""",
3537
+"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
3539
+""" (tree modified) The latest revision to modify a given file, e.g. \
3540
+"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
3542
+"""(tree tag) The revision that was tagged into the current tree revision, \
3543
+according to the tree""",
3545
+"""(tag current) The latest revision of the version that the current tree \
3546
+was tagged from.""",
3548
+"""The common ancestor of the current tree and the specified revision. \
3549
+Defaults to the first partner-version's latest revision or to tagcur.""",
3553
+def is_auto_alias(name):
3554
+ """Determine whether a name is an auto alias name
3556
+ :param name: the name to check
3558
+ :return: True if the name is an auto alias, false if not
3561
+ return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
3564
+def display_def(iter, wrap = 80):
3565
+ """Display a list of definitions
3567
+ :param iter: iter of name, definition pairs
3568
+ :type iter: iter of (str, str)
3569
+ :param wrap: The width for text wrapping
3574
+ for (key, value) in vals:
3575
+ if len(key) > maxlen:
3577
+ for (key, value) in vals:
3578
+ tw=textwrap.TextWrapper(width=wrap,
3579
+ initial_indent=key.rjust(maxlen)+" : ",
3580
+ subsequent_indent="".rjust(maxlen+3))
3581
+ print tw.fill(value)
3584
+def help_aliases(tree):
3585
+ print """Auto-generated aliases"""
3586
+ display_def(pylon.util.iter_pairs(auto_alias))
3587
+ print "User aliases"
3588
+ display_def(ancillary.iter_all_alias(tree))
3590
+class Inventory(BaseCommand):
3591
+ """List the status of files in the tree"""
3592
+ def __init__(self):
3593
+ self.description=self.__doc__
3595
+ def do_command(self, cmdargs):
3597
+ Master function that perfoms the "revision" command.
3600
+ parser=self.get_parser()
3601
+ (options, args) = parser.parse_args(cmdargs)
3602
+ tree = arch.tree_root()
3605
+ if (options.source):
3606
+ categories.append(arch_core.SourceFile)
3607
+ if (options.precious):
3608
+ categories.append(arch_core.PreciousFile)
3609
+ if (options.backup):
3610
+ categories.append(arch_core.BackupFile)
3611
+ if (options.junk):
3612
+ categories.append(arch_core.JunkFile)
3614
+ if len(categories) == 1:
3615
+ show_leading = False
3617
+ show_leading = True
3619
+ if len(categories) == 0:
3622
+ if options.untagged:
3623
+ categories = arch_core.non_root
3624
+ show_leading = False
3629
+ for file in arch_core.iter_inventory_filter(tree, None,
3630
+ control_files=options.control_files,
3631
+ categories = categories, tagged=tagged):
3632
+ print arch_core.file_line(file,
3633
+ category = show_leading,
3634
+ untagged = show_leading,
3637
+ def get_parser(self):
3639
+ Returns the options parser to use for the "revision" command.
3641
+ :rtype: cmdutil.CmdOptionParser
3643
+ parser=cmdutil.CmdOptionParser("fai inventory [options]")
3644
+ parser.add_option("--ids", action="store_true", dest="ids",
3645
+ help="Show file ids")
3646
+ parser.add_option("--control", action="store_true",
3647
+ dest="control_files", help="include control files")
3648
+ parser.add_option("--source", action="store_true", dest="source",
3649
+ help="List source files")
3650
+ parser.add_option("--backup", action="store_true", dest="backup",
3651
+ help="List backup files")
3652
+ parser.add_option("--precious", action="store_true", dest="precious",
3653
+ help="List precious files")
3654
+ parser.add_option("--junk", action="store_true", dest="junk",
3655
+ help="List junk files")
3656
+ parser.add_option("--unrecognized", action="store_true",
3657
+ dest="unrecognized", help="List unrecognized files")
3658
+ parser.add_option("--untagged", action="store_true",
3659
+ dest="untagged", help="List only untagged files")
3662
+ def help(self, parser=None):
3664
+ Prints a help message.
3666
+ :param parser: If supplied, the parser to use for generating help. If \
3667
+ not supplied, it is retrieved.
3668
+ :type parser: cmdutil.CmdOptionParser
3671
+ parser=self.get_parser()
3672
+ parser.print_help()
3674
+Lists the status of files in the archive:
3682
+Leading letter are not displayed if only one kind of file is shown
3687
+class Alias(BaseCommand):
3688
+ """List or adjust aliases"""
3689
+ def __init__(self):
3690
+ self.description=self.__doc__
3692
+ def get_completer(self, arg, index):
3696
+ self.tree = arch.tree_root()
3701
+ return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
3703
+ return cmdutil.iter_revision_completions(arg, self.tree)
3706
+ def do_command(self, cmdargs):
3708
+ Master function that perfoms the "revision" command.
3711
+ parser=self.get_parser()
3712
+ (options, args) = parser.parse_args(cmdargs)
3714
+ self.tree = arch.tree_root()
3720
+ options.action(args, options)
3721
+ except cmdutil.ForbiddenAliasSyntax, e:
3722
+ raise CommandFailedWrapper(e)
3724
+ def no_prefix(self, alias):
3725
+ if alias.startswith("^"):
3729
+ def arg_dispatch(self, args, options):
3730
+ """Add, modify, or list aliases, depending on number of arguments
3732
+ :param args: The list of commandline arguments
3733
+ :type args: list of str
3734
+ :param options: The commandline options
3736
+ if len(args) == 0:
3737
+ help_aliases(self.tree)
3740
+ alias = self.no_prefix(args[0])
3741
+ if len(args) == 1:
3742
+ self.print_alias(alias)
3743
+ elif (len(args)) == 2:
3744
+ self.add(alias, args[1], options)
3746
+ raise cmdutil.GetHelp
3748
+ def print_alias(self, alias):
3750
+ if is_auto_alias(alias):
3751
+ raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
3752
+ " Use \"revision\" to expand auto aliases." % alias)
3753
+ for pair in ancillary.iter_all_alias(self.tree):
3754
+ if pair[0] == alias:
3756
+ if answer is not None:
3759
+ print "The alias %s is not assigned." % alias
3761
+ def add(self, alias, expansion, options):
3762
+ """Add or modify aliases
3764
+ :param alias: The alias name to create/modify
3766
+ :param expansion: The expansion to assign to the alias name
3767
+ :type expansion: str
3768
+ :param options: The commandline options
3770
+ if is_auto_alias(alias):
3771
+ raise IsAutoAlias(alias)
3774
+ new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
3776
+ ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
3778
+ for pair in self.get_iterator(options):
3779
+ if pair[0] != alias:
3780
+ newlist+="%s=%s\n" % (pair[0], pair[1])
3786
+ self.write_aliases(newlist, options)
3788
+ def delete(self, args, options):
3789
+ """Delete the specified alias
3791
+ :param args: The list of arguments
3792
+ :type args: list of str
3793
+ :param options: The commandline options
3796
+ if len(args) != 1:
3797
+ raise cmdutil.GetHelp
3798
+ alias = self.no_prefix(args[0])
3799
+ if is_auto_alias(alias):
3800
+ raise IsAutoAlias(alias)
3802
+ for pair in self.get_iterator(options):
3803
+ if pair[0] != alias:
3804
+ newlist+="%s=%s\n" % (pair[0], pair[1])
3808
+ raise errors.NoSuchAlias(alias)
3809
+ self.write_aliases(newlist, options)
3811
+ def get_alias_file(self, options):
3812
+ """Return the name of the alias file to use
3814
+ :param options: The commandline options
3817
+ if self.tree is None:
3818
+ self.tree == arch.tree_root()
3819
+ return str(self.tree)+"/{arch}/+aliases"
3821
+ return "~/.aba/aliases"
3823
+ def get_iterator(self, options):
3824
+ """Return the alias iterator to use
3826
+ :param options: The commandline options
3828
+ return ancillary.iter_alias(self.get_alias_file(options))
3830
+ def write_aliases(self, newlist, options):
3831
+ """Safely rewrite the alias file
3832
+ :param newlist: The new list of aliases
3833
+ :type newlist: str
3834
+ :param options: The commandline options
3836
+ filename = os.path.expanduser(self.get_alias_file(options))
3837
+ file = util.NewFileVersion(filename)
3838
+ file.write(newlist)
3842
+ def get_parser(self):
3844
+ Returns the options parser to use for the "alias" command.
3846
+ :rtype: cmdutil.CmdOptionParser
3848
+ parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
3849
+ parser.add_option("-d", "--delete", action="store_const", dest="action",
3850
+ const=self.delete, default=self.arg_dispatch,
3851
+ help="Delete an alias")
3852
+ parser.add_option("--tree", action="store_true", dest="tree",
3853
+ help="Create a per-tree alias", default=False)
3856
+ def help(self, parser=None):
3858
+ Prints a help message.
3860
+ :param parser: If supplied, the parser to use for generating help. If \
3861
+ not supplied, it is retrieved.
3862
+ :type parser: cmdutil.CmdOptionParser
3865
+ parser=self.get_parser()
3866
+ parser.print_help()
3868
+Lists current aliases or modifies the list of aliases.
3870
+If no arguments are supplied, aliases will be listed. If two arguments are
3871
+supplied, the specified alias will be created or modified. If -d or --delete
3872
+is supplied, the specified alias will be deleted.
3874
+You can create aliases that refer to any fully-qualified part of the
3875
+Arch namespace, e.g.
3878
+archive/category--branch,
3879
+archive/category--branch--version (my favourite)
3880
+archive/category--branch--version--patchlevel
3882
+Aliases can be used automatically by native commands. To use them
3883
+with external or tla commands, prefix them with ^ (you can do this
3884
+with native commands, too).
3888
+class RequestMerge(BaseCommand):
3889
+ """Submit a merge request to Bug Goo"""
3890
+ def __init__(self):
3891
+ self.description=self.__doc__
3893
+ def do_command(self, cmdargs):
3894
+ """Submit a merge request
3896
+ :param cmdargs: The commandline arguments
3897
+ :type cmdargs: list of str
3899
+ parser = self.get_parser()
3900
+ (options, args) = parser.parse_args(cmdargs)
3902
+ cmdutil.find_editor()
3903
+ except pylon.errors.NoEditorSpecified, e:
3904
+ raise pylon.errors.CommandFailedWrapper(e)
3906
+ self.tree=arch.tree_root()
3909
+ base, revisions = self.revision_specs(args)
3910
+ message = self.make_headers(base, revisions)
3911
+ message += self.make_summary(revisions)
3912
+ path = self.edit_message(message)
3913
+ message = self.tidy_message(path)
3914
+ if cmdutil.prompt("Send merge"):
3915
+ self.send_message(message)
3916
+ print "Merge request sent"
3918
+ def make_headers(self, base, revisions):
3919
+ """Produce email and Bug Goo header strings
3921
+ :param base: The base revision to apply merges to
3922
+ :type base: `arch.Revision`
3923
+ :param revisions: The revisions to replay into the base
3924
+ :type revisions: list of `arch.Patchlog`
3925
+ :return: The headers
3928
+ headers = "To: gnu-arch-users@gnu.org\n"
3929
+ headers += "From: %s\n" % options.fromaddr
3930
+ if len(revisions) == 1:
3931
+ headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
3933
+ headers += "Subject: [MERGE REQUEST]\n"
3935
+ headers += "Base-Revision: %s\n" % base
3936
+ for revision in revisions:
3937
+ headers += "Revision: %s\n" % revision.revision
3938
+ headers += "Bug: \n\n"
3941
+ def make_summary(self, logs):
3942
+ """Generate a summary of merges
3944
+ :param logs: the patchlogs that were directly added by the merges
3945
+ :type logs: list of `arch.Patchlog`
3946
+ :return: the summary
3951
+ summary+=str(log.revision)+"\n"
3952
+ summary+=log.summary+"\n"
3953
+ if log.description.strip():
3954
+ summary+=log.description.strip('\n')+"\n\n"
3957
+ def revision_specs(self, args):
3958
+ """Determine the base and merge revisions from tree and arguments.
3960
+ :param args: The parsed arguments
3961
+ :type args: list of str
3962
+ :return: The base revision and merge revisions
3963
+ :rtype: `arch.Revision`, list of `arch.Patchlog`
3966
+ target_revision = cmdutil.determine_revision_arch(self.tree,
3969
+ target_revision = arch_compound.tree_latest(self.tree)
3971
+ merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
3972
+ self.tree, f)) for f in args[1:] ]
3974
+ if self.tree is None:
3975
+ raise CantDetermineRevision("", "Not in a project tree")
3976
+ merge_iter = cmdutil.iter_new_merges(self.tree,
3977
+ target_revision.version,
3979
+ merges = [f for f in cmdutil.direct_merges(merge_iter)]
3980
+ return (target_revision, merges)
3982
+ def edit_message(self, message):
3983
+ """Edit an email message in the user's standard editor
3985
+ :param message: The message to edit
3986
+ :type message: str
3987
+ :return: the path of the edited message
3990
+ if self.tree is None:
3991
+ path = os.get_cwd()
3994
+ path += "/,merge-request"
3995
+ file = open(path, 'w')
3996
+ file.write(message)
3998
+ cmdutil.invoke_editor(path)
4001
+ def tidy_message(self, path):
4002
+ """Validate and clean up message.
4004
+ :param path: The path to the message to clean up
4006
+ :return: The parsed message
4007
+ :rtype: `email.Message`
4009
+ mail = email.message_from_file(open(path))
4010
+ if mail["Subject"].strip() == "[MERGE REQUEST]":
4011
+ raise BlandSubject
4013
+ request = email.message_from_string(mail.get_payload())
4014
+ if request.has_key("Bug"):
4015
+ if request["Bug"].strip()=="":
4016
+ del request["Bug"]
4017
+ mail.set_payload(request.as_string())
4020
+ def send_message(self, message):
4021
+ """Send a message, using its headers to address it.
4023
+ :param message: The message to send
4024
+ :type message: `email.Message`"""
4025
+ server = smtplib.SMTP("localhost")
4026
+ server.sendmail(message['From'], message['To'], message.as_string())
4029
+ def help(self, parser=None):
4030
+ """Print a usage message
4032
+ :param parser: The options parser to use
4033
+ :type parser: `cmdutil.CmdOptionParser`
4035
+ if parser is None:
4036
+ parser = self.get_parser()
4037
+ parser.print_help()
4039
+Sends a merge request formatted for Bug Goo. Intended use: get the tree
4040
+you'd like to merge into. Apply the merges you want. Invoke request-merge.
4041
+The merge request will open in your $EDITOR.
4043
+When no TARGET is specified, it uses the current tree revision. When
4044
+no MERGE is specified, it uses the direct merges (as in "revisions
4045
+--direct-merges"). But you can specify just the TARGET, or all the MERGE
4049
+ def get_parser(self):
4050
+ """Produce a commandline parser for this command.
4052
+ :rtype: `cmdutil.CmdOptionParser`
4054
+ parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
4058
+'changes' : Changes,
4061
+'apply-changes':ApplyChanges,
4064
+'revision': Revision,
4065
+'revisions': Revisions,
4072
+'mirror-archive': MirrorArchive,
4073
+'ninventory': Inventory,
4075
+'request-merge': RequestMerge,
4078
+def my_import(mod_name):
4079
+ module = __import__(mod_name)
4080
+ components = mod_name.split('.')
4081
+ for comp in components[1:]:
4082
+ module = getattr(module, comp)
4085
+def plugin(mod_name):
4086
+ module = my_import(mod_name)
4087
+ module.add_command(commands)
4089
+for file in os.listdir(sys.path[0]+"/command"):
4090
+ if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
4091
+ plugin("command."+file[:-3])
4094
+'apply-delta' : "Try \"apply-changes\".",
4095
+'delta' : "To compare two revisions, use \"changes\".",
4096
+'diff-rev' : "To compare two revisions, use \"changes\".",
4097
+'undo' : "To undo local changes, use \"revert\".",
4098
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
4099
+'missing-from' : "Try \"revisions --missing-from\".",
4100
+'missing' : "Try \"revisions --missing\".",
4101
+'missing-merge' : "Try \"revisions --partner-missing\".",
4102
+'new-merges' : "Try \"revisions --new-merges\".",
4103
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
4104
+'logs' : "Try \"revisions --logs\"",
4105
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
4106
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
4107
+'change-version' : "Try \"update REVISION\"",
4108
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
4109
+'rev-depends' : "Use revisions --dependencies",
4110
+'auto-get' : "Plain get will do archive lookups",
4111
+'tagline' : "Use add-id. It uses taglines in tagline trees",
4112
+'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
4113
+'library-revisions' : "Use revisions --library",
4114
+'file-revert' : "Use revert FILE",
4115
+'join-branch' : "Use replay --logs-only"
4117
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
4119
*** added file 'testdata/orig'
4123
+# Copyright (C) 2004 Aaron Bentley
4124
+# <aaron.bentley@utoronto.ca>
4126
+# This program is free software; you can redistribute it and/or modify
4127
+# it under the terms of the GNU General Public License as published by
4128
+# the Free Software Foundation; either version 2 of the License, or
4129
+# (at your option) any later version.
4131
+# This program is distributed in the hope that it will be useful,
4132
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4133
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4134
+# GNU General Public License for more details.
4136
+# You should have received a copy of the GNU General Public License
4137
+# along with this program; if not, write to the Free Software
4138
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
4156
+from errors import *
4164
+__docformat__ = "restructuredtext"
4165
+__doc__ = "Implementation of user (sub) commands"
4168
+def find_command(cmd):
4170
+ Return an instance of a command type. Return None if the type isn't
4173
+ :param cmd: the name of the command to look for
4174
+ :type cmd: the type of the command
4176
+ if commands.has_key(cmd):
4177
+ return commands[cmd]()
4182
+ def __call__(self, cmdline):
4184
+ self.do_command(cmdline.split())
4185
+ except cmdutil.GetHelp, e:
4187
+ except Exception, e:
4190
+ def get_completer(index):
4193
+ def complete(self, args, text):
4195
+ Returns a list of possible completions for the given text.
4197
+ :param args: The complete list of arguments
4198
+ :type args: List of str
4199
+ :param text: text to complete (may be shorter than args[-1])
4201
+ :rtype: list of str
4207
+ realtext = args[-1]
4212
+ parser=self.get_parser()
4213
+ if realtext.startswith('-'):
4214
+ candidates = parser.iter_options()
4216
+ (options, parsed_args) = parser.parse_args(args)
4218
+ if len (parsed_args) > 0:
4219
+ candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
4221
+ candidates = self.get_completer("", 0)
4224
+ if candidates is None:
4226
+ for candidate in candidates:
4227
+ candidate = str(candidate)
4228
+ if candidate.startswith(realtext):
4229
+ matches.append(candidate[len(realtext)- len(text):])
4233
+class Help(BaseCommand):
4235
+ Lists commands, prints help messages.
4237
+ def __init__(self):
4238
+ self.description="Prints help mesages"
4239
+ self.parser = None
4241
+ def do_command(self, cmdargs):
4243
+ Prints a help message.
4245
+ options, args = self.get_parser().parse_args(cmdargs)
4247
+ raise cmdutil.GetHelp
4249
+ if options.native or options.suggestions or options.external:
4250
+ native = options.native
4251
+ suggestions = options.suggestions
4252
+ external = options.external
4255
+ suggestions = False
4258
+ if len(args) == 0:
4259
+ self.list_commands(native, suggestions, external)
4261
+ elif len(args) == 1:
4262
+ command_help(args[0])
4266
+ self.get_parser().print_help()
4268
+If no command is specified, commands are listed. If a command is
4269
+specified, help for that command is listed.
4272
+ def get_parser(self):
4274
+ Returns the options parser to use for the "revision" command.
4276
+ :rtype: cmdutil.CmdOptionParser
4278
+ if self.parser is not None:
4279
+ return self.parser
4280
+ parser=cmdutil.CmdOptionParser("fai help [command]")
4281
+ parser.add_option("-n", "--native", action="store_true",
4282
+ dest="native", help="Show native commands")
4283
+ parser.add_option("-e", "--external", action="store_true",
4284
+ dest="external", help="Show external commands")
4285
+ parser.add_option("-s", "--suggest", action="store_true",
4286
+ dest="suggestions", help="Show suggestions")
4287
+ self.parser = parser
4290
+ def list_commands(self, native=True, suggest=False, external=True):
4292
+ Lists supported commands.
4294
+ :param native: list native, python-based commands
4295
+ :type native: bool
4296
+ :param external: list external aba-style commands
4297
+ :type external: bool
4300
+ print "Native Fai commands"
4301
+ keys=commands.keys()
4305
+ for i in range(28-len(k)):
4307
+ print space+k+" : "+commands[k]().description
4310
+ print "Unavailable commands and suggested alternatives"
4311
+ key_list = suggestions.keys()
4313
+ for key in key_list:
4314
+ print "%28s : %s" % (key, suggestions[key])
4317
+ fake_aba = abacmds.AbaCmds()
4318
+ if (fake_aba.abadir == ""):
4320
+ print "External commands"
4321
+ fake_aba.list_commands()
4324
+ print "Use help --suggest to list alternatives to tla and aba"\
4326
+ if options.tla_fallthrough and (native or external):
4327
+ print "Fai also supports tla commands."
4329
+def command_help(cmd):
4331
+ Prints help for a command.
4333
+ :param cmd: The name of the command to print help for
4336
+ fake_aba = abacmds.AbaCmds()
4337
+ cmdobj = find_command(cmd)
4338
+ if cmdobj != None:
4340
+ elif suggestions.has_key(cmd):
4341
+ print "Not available\n" + suggestions[cmd]
4343
+ abacmd = fake_aba.is_command(cmd)
4347
+ print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
4351
+class Changes(BaseCommand):
4353
+ the "changes" command: lists differences between trees/revisions:
4356
+ def __init__(self):
4357
+ self.description="Lists what files have changed in the project tree"
4359
+ def get_completer(self, arg, index):
4363
+ tree = arch.tree_root()
4366
+ return cmdutil.iter_revision_completions(arg, tree)
4368
+ def parse_commandline(self, cmdline):
4370
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
4372
+ :param cmdline: A list of arguments to parse
4373
+ :rtype: (options, Revision, Revision/WorkingTree)
4375
+ parser=self.get_parser()
4376
+ (options, args) = parser.parse_args(cmdline)
4378
+ raise cmdutil.GetHelp
4380
+ tree=arch.tree_root()
4381
+ if len(args) == 0:
4382
+ a_spec = cmdutil.comp_revision(tree)
4384
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
4385
+ cmdutil.ensure_archive_registered(a_spec.archive)
4386
+ if len(args) == 2:
4387
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
4388
+ cmdutil.ensure_archive_registered(b_spec.archive)
4391
+ return options, a_spec, b_spec
4393
+ def do_command(self, cmdargs):
4395
+ Master function that perfoms the "changes" command.
4398
+ options, a_spec, b_spec = self.parse_commandline(cmdargs);
4399
+ except cmdutil.CantDetermineRevision, e:
4402
+ except arch.errors.TreeRootError, e:
4405
+ if options.changeset:
4406
+ changeset=options.changeset
4409
+ tmpdir=cmdutil.tmpdir()
4410
+ changeset=tmpdir+"/changeset"
4412
+ delta=arch.iter_delta(a_spec, b_spec, changeset)
4414
+ for line in delta:
4415
+ if cmdutil.chattermatch(line, "changeset:"):
4418
+ cmdutil.colorize(line, options.suppress_chatter)
4419
+ except arch.util.ExecProblem, e:
4420
+ if e.proc.error and e.proc.error.startswith(
4421
+ "missing explicit id for file"):
4422
+ raise MissingID(e)
4425
+ status=delta.status
4428
+ if (options.perform_diff):
4429
+ chan = cmdutil.ChangesetMunger(changeset)
4430
+ chan.read_indices()
4431
+ if isinstance(b_spec, arch.Revision):
4432
+ b_dir = b_spec.library_find()
4435
+ a_dir = a_spec.library_find()
4436
+ if options.diffopts is not None:
4437
+ diffopts = options.diffopts.split()
4438
+ cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
4440
+ cmdutil.show_diffs(delta.changeset)
4442
+ if tmpdir and (os.access(tmpdir, os.X_OK)):
4443
+ shutil.rmtree(tmpdir)
4445
+ def get_parser(self):
4447
+ Returns the options parser to use for the "changes" command.
4449
+ :rtype: cmdutil.CmdOptionParser
4451
+ parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
4453
+ parser.add_option("-d", "--diff", action="store_true",
4454
+ dest="perform_diff", default=False,
4455
+ help="Show diffs in summary")
4456
+ parser.add_option("-c", "--changeset", dest="changeset",
4457
+ help="Store a changeset in the given directory",
4458
+ metavar="DIRECTORY")
4459
+ parser.add_option("-s", "--silent", action="store_true",
4460
+ dest="suppress_chatter", default=False,
4461
+ help="Suppress chatter messages")
4462
+ parser.add_option("--diffopts", dest="diffopts",
4463
+ help="Use the specified diff options",
4464
+ metavar="OPTIONS")
4468
+ def help(self, parser=None):
4470
+ Prints a help message.
4472
+ :param parser: If supplied, the parser to use for generating help. If \
4473
+ not supplied, it is retrieved.
4474
+ :type parser: cmdutil.CmdOptionParser
4476
+ if parser is None:
4477
+ parser=self.get_parser()
4478
+ parser.print_help()
4480
+Performs source-tree comparisons
4482
+If no revision is specified, the current project tree is compared to the
4483
+last-committed revision. If one revision is specified, the current project
4484
+tree is compared to that revision. If two revisions are specified, they are
4485
+compared to each other.
4491
+class ApplyChanges(BaseCommand):
4493
+ Apply differences between two revisions to a tree
4496
+ def __init__(self):
4497
+ self.description="Applies changes to a project tree"
4499
+ def get_completer(self, arg, index):
4503
+ tree = arch.tree_root()
4506
+ return cmdutil.iter_revision_completions(arg, tree)
4508
+ def parse_commandline(self, cmdline, tree):
4510
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
4512
+ :param cmdline: A list of arguments to parse
4513
+ :rtype: (options, Revision, Revision/WorkingTree)
4515
+ parser=self.get_parser()
4516
+ (options, args) = parser.parse_args(cmdline)
4517
+ if len(args) != 2:
4518
+ raise cmdutil.GetHelp
4520
+ a_spec = cmdutil.determine_revision_tree(tree, args[0])
4521
+ cmdutil.ensure_archive_registered(a_spec.archive)
4522
+ b_spec = cmdutil.determine_revision_tree(tree, args[1])
4523
+ cmdutil.ensure_archive_registered(b_spec.archive)
4524
+ return options, a_spec, b_spec
4526
+ def do_command(self, cmdargs):
4528
+ Master function that performs "apply-changes".
4531
+ tree = arch.tree_root()
4532
+ options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
4533
+ except cmdutil.CantDetermineRevision, e:
4536
+ except arch.errors.TreeRootError, e:
4539
+ delta=cmdutil.apply_delta(a_spec, b_spec, tree)
4540
+ for line in cmdutil.iter_apply_delta_filter(delta):
4541
+ cmdutil.colorize(line, options.suppress_chatter)
4543
+ def get_parser(self):
4545
+ Returns the options parser to use for the "apply-changes" command.
4547
+ :rtype: cmdutil.CmdOptionParser
4549
+ parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
4551
+ parser.add_option("-d", "--diff", action="store_true",
4552
+ dest="perform_diff", default=False,
4553
+ help="Show diffs in summary")
4554
+ parser.add_option("-c", "--changeset", dest="changeset",
4555
+ help="Store a changeset in the given directory",
4556
+ metavar="DIRECTORY")
4557
+ parser.add_option("-s", "--silent", action="store_true",
4558
+ dest="suppress_chatter", default=False,
4559
+ help="Suppress chatter messages")
4562
+ def help(self, parser=None):
4564
+ Prints a help message.
4566
+ :param parser: If supplied, the parser to use for generating help. If \
4567
+ not supplied, it is retrieved.
4568
+ :type parser: cmdutil.CmdOptionParser
4570
+ if parser is None:
4571
+ parser=self.get_parser()
4572
+ parser.print_help()
4574
+Applies changes to a project tree
4576
+Compares two revisions and applies the difference between them to the current
4582
+class Update(BaseCommand):
4584
+ Updates a project tree to a given revision, preserving un-committed hanges.
4587
+ def __init__(self):
4588
+ self.description="Apply the latest changes to the current directory"
4590
+ def get_completer(self, arg, index):
4594
+ tree = arch.tree_root()
4597
+ return cmdutil.iter_revision_completions(arg, tree)
4599
+ def parse_commandline(self, cmdline, tree):
4601
+ Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
4603
+ :param cmdline: A list of arguments to parse
4604
+ :rtype: (options, Revision, Revision/WorkingTree)
4606
+ parser=self.get_parser()
4607
+ (options, args) = parser.parse_args(cmdline)
4609
+ raise cmdutil.GetHelp
4614
+ revision=cmdutil.determine_revision_arch(tree, spec)
4615
+ cmdutil.ensure_archive_registered(revision.archive)
4617
+ mirror_source = cmdutil.get_mirror_source(revision.archive)
4618
+ if mirror_source != None:
4619
+ if cmdutil.prompt("Mirror update"):
4620
+ cmd=cmdutil.mirror_archive(mirror_source,
4621
+ revision.archive, arch.NameParser(revision).get_package_version())
4622
+ for line in arch.chatter_classifier(cmd):
4623
+ cmdutil.colorize(line, options.suppress_chatter)
4625
+ revision=cmdutil.determine_revision_arch(tree, spec)
4627
+ return options, revision
4629
+ def do_command(self, cmdargs):
4631
+ Master function that perfoms the "update" command.
4633
+ tree=arch.tree_root()
4635
+ options, to_revision = self.parse_commandline(cmdargs, tree);
4636
+ except cmdutil.CantDetermineRevision, e:
4639
+ except arch.errors.TreeRootError, e:
4642
+ from_revision=cmdutil.tree_latest(tree)
4643
+ if from_revision==to_revision:
4644
+ print "Tree is already up to date with:\n"+str(to_revision)+"."
4646
+ cmdutil.ensure_archive_registered(from_revision.archive)
4647
+ cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
4648
+ options.patch_forward)
4649
+ for line in cmdutil.iter_apply_delta_filter(cmd):
4650
+ cmdutil.colorize(line)
4651
+ if to_revision.version != tree.tree_version:
4652
+ if cmdutil.prompt("Update version"):
4653
+ tree.tree_version = to_revision.version
4655
+ def get_parser(self):
4657
+ Returns the options parser to use for the "update" command.
4659
+ :rtype: cmdutil.CmdOptionParser
4661
+ parser=cmdutil.CmdOptionParser("fai update [options]"
4662
+ " [revision/version]")
4663
+ parser.add_option("-f", "--forward", action="store_true",
4664
+ dest="patch_forward", default=False,
4665
+ help="pass the --forward option to 'patch'")
4666
+ parser.add_option("-s", "--silent", action="store_true",
4667
+ dest="suppress_chatter", default=False,
4668
+ help="Suppress chatter messages")
4671
+ def help(self, parser=None):
4673
+ Prints a help message.
4675
+ :param parser: If supplied, the parser to use for generating help. If \
4676
+ not supplied, it is retrieved.
4677
+ :type parser: cmdutil.CmdOptionParser
4679
+ if parser is None:
4680
+ parser=self.get_parser()
4681
+ parser.print_help()
4683
+Updates a working tree to the current archive revision
4685
+If a revision or version is specified, that is used instead
4691
+class Commit(BaseCommand):
4693
+ Create a revision based on the changes in the current tree.
4696
+ def __init__(self):
4697
+ self.description="Write local changes to the archive"
4699
+ def get_completer(self, arg, index):
4702
+ return iter_modified_file_completions(arch.tree_root(), arg)
4703
+# return iter_source_file_completions(arch.tree_root(), arg)
4705
+ def parse_commandline(self, cmdline, tree):
4707
+ Parse commandline arguments. Raise cmtutil.GetHelp if help is needed.
4709
+ :param cmdline: A list of arguments to parse
4710
+ :rtype: (options, Revision, Revision/WorkingTree)
4712
+ parser=self.get_parser()
4713
+ (options, args) = parser.parse_args(cmdline)
4715
+ if len(args) == 0:
4717
+ revision=cmdutil.determine_revision_arch(tree, options.version)
4718
+ return options, revision.get_version(), args
4720
+ def do_command(self, cmdargs):
4722
+ Master function that perfoms the "commit" command.
4724
+ tree=arch.tree_root()
4725
+ options, version, files = self.parse_commandline(cmdargs, tree)
4726
+ if options.__dict__.has_key("base") and options.base:
4727
+ base = cmdutil.determine_revision_tree(tree, options.base)
4729
+ base = cmdutil.submit_revision(tree)
4731
+ writeversion=version
4732
+ archive=version.archive
4733
+ source=cmdutil.get_mirror_source(archive)
4735
+ writethrough="implicit"
4738
+ if writethrough=="explicit" and \
4739
+ cmdutil.prompt("Writethrough"):
4740
+ writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
4741
+ elif writethrough=="none":
4742
+ raise CommitToMirror(archive)
4744
+ elif archive.is_mirror:
4745
+ raise CommitToMirror(archive)
4748
+ last_revision=tree.iter_logs(version, True).next().revision
4749
+ except StopIteration, e:
4750
+ if cmdutil.prompt("Import from commit"):
4751
+ return do_import(version)
4753
+ raise NoVersionLogs(version)
4754
+ if last_revision!=version.iter_revisions(True).next():
4755
+ if not cmdutil.prompt("Out of date"):
4761
+ if not cmdutil.has_changed(version):
4762
+ if not cmdutil.prompt("Empty commit"):
4764
+ except arch.util.ExecProblem, e:
4765
+ if e.proc.error and e.proc.error.startswith(
4766
+ "missing explicit id for file"):
4767
+ raise MissingID(e)
4770
+ log = tree.log_message(create=False)
4773
+ if cmdutil.prompt("Create log"):
4776
+ except cmdutil.NoEditorSpecified, e:
4777
+ raise CommandFailed(e)
4778
+ log = tree.log_message(create=False)
4780
+ raise NoLogMessage
4781
+ if log["Summary"] is None or len(log["Summary"].strip()) == 0:
4782
+ if not cmdutil.prompt("Omit log summary"):
4783
+ raise errors.NoLogSummary
4785
+ for line in tree.iter_commit(version, seal=options.seal_version,
4786
+ base=base, out_of_date_ok=allow_old, file_list=files):
4787
+ cmdutil.colorize(line, options.suppress_chatter)
4789
+ except arch.util.ExecProblem, e:
4790
+ if e.proc.error and e.proc.error.startswith(
4791
+ "These files violate naming conventions:"):
4792
+ raise LintFailure(e.proc.error)
4796
+ def get_parser(self):
4798
+ Returns the options parser to use for the "commit" command.
4800
+ :rtype: cmdutil.CmdOptionParser
4803
+ parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
4805
+ parser.add_option("--seal", action="store_true",
4806
+ dest="seal_version", default=False,
4807
+ help="seal this version")
4808
+ parser.add_option("-v", "--version", dest="version",
4809
+ help="Use the specified version",
4810
+ metavar="VERSION")
4811
+ parser.add_option("-s", "--silent", action="store_true",
4812
+ dest="suppress_chatter", default=False,
4813
+ help="Suppress chatter messages")
4814
+ if cmdutil.supports_switch("commit", "--base"):
4815
+ parser.add_option("--base", dest="base", help="",
4816
+ metavar="REVISION")
4819
+ def help(self, parser=None):
4821
+ Prints a help message.
4823
+ :param parser: If supplied, the parser to use for generating help. If \
4824
+ not supplied, it is retrieved.
4825
+ :type parser: cmdutil.CmdOptionParser
4827
+ if parser is None:
4828
+ parser=self.get_parser()
4829
+ parser.print_help()
4831
+Updates a working tree to the current archive revision
4833
+If a version is specified, that is used instead
4840
+class CatLog(BaseCommand):
4842
+ Print the log of a given file (from current tree)
4844
+ def __init__(self):
4845
+ self.description="Prints the patch log for a revision"
4847
+ def get_completer(self, arg, index):
4851
+ tree = arch.tree_root()
4854
+ return cmdutil.iter_revision_completions(arg, tree)
4856
+ def do_command(self, cmdargs):
4858
+ Master function that perfoms the "cat-log" command.
4860
+ parser=self.get_parser()
4861
+ (options, args) = parser.parse_args(cmdargs)
4863
+ tree = arch.tree_root()
4864
+ except arch.errors.TreeRootError, e:
4870
+ raise cmdutil.GetHelp()
4873
+ revision = cmdutil.determine_revision_tree(tree, spec)
4875
+ revision = cmdutil.determine_revision_arch(tree, spec)
4876
+ except cmdutil.CantDetermineRevision, e:
4877
+ raise CommandFailedWrapper(e)
4880
+ use_tree = (options.source == "tree" or \
4881
+ (options.source == "any" and tree))
4882
+ use_arch = (options.source == "archive" or options.source == "any")
4886
+ for log in tree.iter_logs(revision.get_version()):
4887
+ if log.revision == revision:
4891
+ if log is None and use_arch:
4892
+ cmdutil.ensure_revision_exists(revision)
4893
+ log = arch.Patchlog(revision)
4894
+ if log is not None:
4895
+ for item in log.items():
4896
+ print "%s: %s" % item
4897
+ print log.description
4899
+ def get_parser(self):
4901
+ Returns the options parser to use for the "cat-log" command.
4903
+ :rtype: cmdutil.CmdOptionParser
4905
+ parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
4906
+ parser.add_option("--archive", action="store_const", dest="source",
4907
+ const="archive", default="any",
4908
+ help="Always get the log from the archive")
4909
+ parser.add_option("--tree", action="store_const", dest="source",
4910
+ const="tree", help="Always get the log from the tree")
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
4922
+ parser=self.get_parser()
4923
+ parser.print_help()
4925
+Prints the log for the specified revision
4930
+class Revert(BaseCommand):
4931
+ """ Reverts a tree (or aspects of it) to a revision
4933
+ def __init__(self):
4934
+ self.description="Reverts a tree (or aspects of it) to a revision "
4936
+ def get_completer(self, arg, index):
4940
+ tree = arch.tree_root()
4943
+ return iter_modified_file_completions(tree, arg)
4945
+ def do_command(self, cmdargs):
4947
+ Master function that perfoms the "revert" command.
4949
+ parser=self.get_parser()
4950
+ (options, args) = parser.parse_args(cmdargs)
4952
+ tree = arch.tree_root()
4953
+ except arch.errors.TreeRootError, e:
4954
+ raise CommandFailed(e)
4956
+ if options.revision is not None:
4957
+ spec=options.revision
4959
+ if spec is not None:
4960
+ revision = cmdutil.determine_revision_tree(tree, spec)
4962
+ revision = cmdutil.comp_revision(tree)
4963
+ except cmdutil.CantDetermineRevision, e:
4964
+ raise CommandFailedWrapper(e)
4967
+ if options.file_contents or options.file_perms or options.deletions\
4968
+ or options.additions or options.renames or options.hunk_prompt:
4969
+ munger = cmdutil.MungeOpts()
4970
+ munger.hunk_prompt = options.hunk_prompt
4972
+ if len(args) > 0 or options.logs or options.pattern_files or \
4974
+ if munger is None:
4975
+ munger = cmdutil.MungeOpts(True)
4976
+ munger.all_types(True)
4978
+ t_cwd = cmdutil.tree_cwd(tree)
4980
+ if len(t_cwd) > 0:
4982
+ name = "./" + t_cwd + name
4983
+ munger.add_keep_file(name);
4985
+ if options.file_perms:
4986
+ munger.file_perms = True
4987
+ if options.file_contents:
4988
+ munger.file_contents = True
4989
+ if options.deletions:
4990
+ munger.deletions = True
4991
+ if options.additions:
4992
+ munger.additions = True
4993
+ if options.renames:
4994
+ munger.renames = True
4996
+ munger.add_keep_pattern('^\./\{arch\}/[^=].*')
4997
+ if options.control:
4998
+ munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
4999
+ "/\.arch-inventory$")
5000
+ if options.pattern_files:
5001
+ munger.add_keep_pattern(options.pattern_files)
5003
+ for line in cmdutil.revert(tree, revision, munger,
5004
+ not options.no_output):
5005
+ cmdutil.colorize(line)
5008
+ def get_parser(self):
5010
+ Returns the options parser to use for the "cat-log" command.
5012
+ :rtype: cmdutil.CmdOptionParser
5014
+ parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
5015
+ parser.add_option("", "--contents", action="store_true",
5016
+ dest="file_contents",
5017
+ help="Revert file content changes")
5018
+ parser.add_option("", "--permissions", action="store_true",
5019
+ dest="file_perms",
5020
+ help="Revert file permissions changes")
5021
+ parser.add_option("", "--deletions", action="store_true",
5023
+ help="Restore deleted files")
5024
+ parser.add_option("", "--additions", action="store_true",
5026
+ help="Remove added files")
5027
+ parser.add_option("", "--renames", action="store_true",
5029
+ help="Revert file names")
5030
+ parser.add_option("--hunks", action="store_true",
5031
+ dest="hunk_prompt", default=False,
5032
+ help="Prompt which hunks to revert")
5033
+ parser.add_option("--pattern-files", dest="pattern_files",
5034
+ help="Revert files that match this pattern",
5036
+ parser.add_option("--logs", action="store_true",
5037
+ dest="logs", default=False,
5038
+ help="Revert only logs")
5039
+ parser.add_option("--control-files", action="store_true",
5040
+ dest="control", default=False,
5041
+ help="Revert logs and other control files")
5042
+ parser.add_option("-n", "--no-output", action="store_true",
5044
+ help="Don't keep an undo changeset")
5045
+ parser.add_option("--revision", dest="revision",
5046
+ help="Revert to the specified revision",
5047
+ metavar="REVISION")
5050
+ def help(self, parser=None):
5052
+ Prints a help message.
5054
+ :param parser: If supplied, the parser to use for generating help. If \
5055
+ not supplied, it is retrieved.
5056
+ :type parser: cmdutil.CmdOptionParser
5059
+ parser=self.get_parser()
5060
+ parser.print_help()
5062
+Reverts changes in the current working tree. If no flags are specified, all
5063
+types of changes are reverted. Otherwise, only selected types of changes are
5066
+If a revision is specified on the commandline, differences between the current
5067
+tree and that revision are reverted. If a version is specified, the current
5068
+tree is used to determine the revision.
5070
+If files are specified, only those files listed will have any changes applied.
5071
+To specify a renamed file, you can use either the old or new name. (or both!)
5073
+Unless "-n" is specified, reversions can be undone with "redo".
5077
+class Revision(BaseCommand):
5079
+ Print a revision name based on a revision specifier
5081
+ def __init__(self):
5082
+ self.description="Prints the name of a revision"
5084
+ def get_completer(self, arg, index):
5088
+ tree = arch.tree_root()
5091
+ return cmdutil.iter_revision_completions(arg, tree)
5093
+ def do_command(self, cmdargs):
5095
+ Master function that perfoms the "revision" command.
5097
+ parser=self.get_parser()
5098
+ (options, args) = parser.parse_args(cmdargs)
5101
+ tree = arch.tree_root()
5102
+ except arch.errors.TreeRootError:
5109
+ raise cmdutil.GetHelp
5112
+ revision = cmdutil.determine_revision_tree(tree, spec)
5114
+ revision = cmdutil.determine_revision_arch(tree, spec)
5115
+ except cmdutil.CantDetermineRevision, e:
5118
+ print options.display(revision)
5120
+ def get_parser(self):
5122
+ Returns the options parser to use for the "revision" command.
5124
+ :rtype: cmdutil.CmdOptionParser
5126
+ parser=cmdutil.CmdOptionParser("fai revision [revision]")
5127
+ parser.add_option("", "--location", action="store_const",
5128
+ const=paths.determine_path, dest="display",
5129
+ help="Show location instead of name", default=str)
5130
+ parser.add_option("--import", action="store_const",
5131
+ const=paths.determine_import_path, dest="display",
5132
+ help="Show location of import file")
5133
+ parser.add_option("--log", action="store_const",
5134
+ const=paths.determine_log_path, dest="display",
5135
+ help="Show location of log file")
5136
+ parser.add_option("--patch", action="store_const",
5137
+ dest="display", const=paths.determine_patch_path,
5138
+ help="Show location of patchfile")
5139
+ parser.add_option("--continuation", action="store_const",
5140
+ const=paths.determine_continuation_path,
5142
+ help="Show location of continuation file")
5143
+ parser.add_option("--cacherev", action="store_const",
5144
+ const=paths.determine_cacherev_path, dest="display",
5145
+ help="Show location of cacherev file")
5148
+ def help(self, parser=None):
5150
+ Prints a help message.
5152
+ :param parser: If supplied, the parser to use for generating help. If \
5153
+ not supplied, it is retrieved.
5154
+ :type parser: cmdutil.CmdOptionParser
5157
+ parser=self.get_parser()
5158
+ parser.print_help()
5160
+Expands aliases and prints the name of the specified revision. Instead of
5161
+the name, several options can be used to print locations. If more than one is
5162
+specified, the last one is used.
5167
+def require_version_exists(version, spec):
5168
+ if not version.exists():
5169
+ raise cmdutil.CantDetermineVersion(spec,
5170
+ "The version %s does not exist." \
5173
+class Revisions(BaseCommand):
5175
+ Print a revision name based on a revision specifier
5177
+ def __init__(self):
5178
+ self.description="Lists revisions"
5180
+ def do_command(self, cmdargs):
5182
+ Master function that perfoms the "revision" command.
5184
+ (options, args) = self.get_parser().parse_args(cmdargs)
5186
+ raise cmdutil.GetHelp
5188
+ self.tree = arch.tree_root()
5189
+ except arch.errors.TreeRootError:
5192
+ iter = self.get_iterator(options.type, args, options.reverse,
5194
+ except cmdutil.CantDetermineRevision, e:
5195
+ raise CommandFailedWrapper(e)
5197
+ if options.skip is not None:
5198
+ iter = cmdutil.iter_skip(iter, int(options.skip))
5200
+ for revision in iter:
5202
+ if isinstance(revision, arch.Patchlog):
5204
+ revision=revision.revision
5205
+ print options.display(revision)
5206
+ if log is None and (options.summary or options.creator or
5207
+ options.date or options.merges):
5208
+ log = revision.patchlog
5209
+ if options.creator:
5210
+ print " %s" % log.creator
5212
+ print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
5213
+ if options.summary:
5214
+ print " %s" % log.summary
5215
+ if options.merges:
5216
+ showed_title = False
5217
+ for revision in log.merged_patches:
5218
+ if not showed_title:
5220
+ showed_title = True
5221
+ print " %s" % revision
5223
+ def get_iterator(self, type, args, reverse, modified):
5228
+ if modified is not None:
5229
+ iter = cmdutil.modified_iter(modified, self.tree)
5233
+ return cmdutil.iter_reverse(iter)
5234
+ elif type == "archive":
5236
+ if self.tree is None:
5237
+ raise cmdutil.CantDetermineRevision("",
5238
+ "Not in a project tree")
5239
+ version = cmdutil.determine_version_tree(spec, self.tree)
5241
+ version = cmdutil.determine_version_arch(spec, self.tree)
5242
+ cmdutil.ensure_archive_registered(version.archive)
5243
+ require_version_exists(version, spec)
5244
+ return version.iter_revisions(reverse)
5245
+ elif type == "cacherevs":
5247
+ if self.tree is None:
5248
+ raise cmdutil.CantDetermineRevision("",
5249
+ "Not in a project tree")
5250
+ version = cmdutil.determine_version_tree(spec, self.tree)
5252
+ version = cmdutil.determine_version_arch(spec, self.tree)
5253
+ cmdutil.ensure_archive_registered(version.archive)
5254
+ require_version_exists(version, spec)
5255
+ return cmdutil.iter_cacherevs(version, reverse)
5256
+ elif type == "library":
5258
+ if self.tree is None:
5259
+ raise cmdutil.CantDetermineRevision("",
5260
+ "Not in a project tree")
5261
+ version = cmdutil.determine_version_tree(spec, self.tree)
5263
+ version = cmdutil.determine_version_arch(spec, self.tree)
5264
+ return version.iter_library_revisions(reverse)
5265
+ elif type == "logs":
5266
+ if self.tree is None:
5267
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5268
+ return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
5269
+ self.tree), reverse)
5270
+ elif type == "missing" or type == "skip-present":
5271
+ if self.tree is None:
5272
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5273
+ skip = (type == "skip-present")
5274
+ version = cmdutil.determine_version_tree(spec, self.tree)
5275
+ cmdutil.ensure_archive_registered(version.archive)
5276
+ require_version_exists(version, spec)
5277
+ return cmdutil.iter_missing(self.tree, version, reverse,
5278
+ skip_present=skip)
5280
+ elif type == "present":
5281
+ if self.tree is None:
5282
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5283
+ version = cmdutil.determine_version_tree(spec, self.tree)
5284
+ cmdutil.ensure_archive_registered(version.archive)
5285
+ require_version_exists(version, spec)
5286
+ return cmdutil.iter_present(self.tree, version, reverse)
5288
+ elif type == "new-merges" or type == "direct-merges":
5289
+ if self.tree is None:
5290
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5291
+ version = cmdutil.determine_version_tree(spec, self.tree)
5292
+ cmdutil.ensure_archive_registered(version.archive)
5293
+ require_version_exists(version, spec)
5294
+ iter = cmdutil.iter_new_merges(self.tree, version, reverse)
5295
+ if type == "new-merges":
5297
+ elif type == "direct-merges":
5298
+ return cmdutil.direct_merges(iter)
5300
+ elif type == "missing-from":
5301
+ if self.tree is None:
5302
+ raise cmdutil.CantDetermineRevision("", "Not in a project tree")
5303
+ revision = cmdutil.determine_revision_tree(self.tree, spec)
5304
+ libtree = cmdutil.find_or_make_local_revision(revision)
5305
+ return cmdutil.iter_missing(libtree, self.tree.tree_version,
5308
+ elif type == "partner-missing":
5309
+ return cmdutil.iter_partner_missing(self.tree, reverse)
5311
+ elif type == "ancestry":
5312
+ revision = cmdutil.determine_revision_tree(self.tree, spec)
5313
+ iter = cmdutil._iter_ancestry(self.tree, revision)
5317
+ return cmdutil.iter_reverse(iter)
5319
+ elif type == "dependencies" or type == "non-dependencies":
5320
+ nondeps = (type == "non-dependencies")
5321
+ revision = cmdutil.determine_revision_tree(self.tree, spec)
5322
+ anc_iter = cmdutil._iter_ancestry(self.tree, revision)
5323
+ iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
5325
+ return iter_depends
5327
+ return cmdutil.iter_reverse(iter_depends)
5328
+ elif type == "micro":
5329
+ return cmdutil.iter_micro(self.tree)
5332
+ def get_parser(self):
5334
+ Returns the options parser to use for the "revision" command.
5336
+ :rtype: cmdutil.CmdOptionParser
5338
+ parser=cmdutil.CmdOptionParser("fai revisions [revision]")
5339
+ select = cmdutil.OptionGroup(parser, "Selection options",
5340
+ "Control which revisions are listed. These options"
5341
+ " are mutually exclusive. If more than one is"
5342
+ " specified, the last is used.")
5343
+ select.add_option("", "--archive", action="store_const",
5344
+ const="archive", dest="type", default="archive",
5345
+ help="List all revisions in the archive")
5346
+ select.add_option("", "--cacherevs", action="store_const",
5347
+ const="cacherevs", dest="type",
5348
+ help="List all revisions stored in the archive as "
5349
+ "complete copies")
5350
+ select.add_option("", "--logs", action="store_const",
5351
+ const="logs", dest="type",
5352
+ help="List revisions that have a patchlog in the "
5354
+ select.add_option("", "--missing", action="store_const",
5355
+ const="missing", dest="type",
5356
+ help="List revisions from the specified version that"
5357
+ " have no patchlog in the tree")
5358
+ select.add_option("", "--skip-present", action="store_const",
5359
+ const="skip-present", dest="type",
5360
+ help="List revisions from the specified version that"
5361
+ " have no patchlogs at all in the tree")
5362
+ select.add_option("", "--present", action="store_const",
5363
+ const="present", dest="type",
5364
+ help="List revisions from the specified version that"
5365
+ " have no patchlog in the tree, but can't be merged")
5366
+ select.add_option("", "--missing-from", action="store_const",
5367
+ const="missing-from", dest="type",
5368
+ help="List revisions from the specified revision "
5369
+ "that have no patchlog for the tree version")
5370
+ select.add_option("", "--partner-missing", action="store_const",
5371
+ const="partner-missing", dest="type",
5372
+ help="List revisions in partner versions that are"
5374
+ select.add_option("", "--new-merges", action="store_const",
5375
+ const="new-merges", dest="type",
5376
+ help="List revisions that have had patchlogs added"
5377
+ " to the tree since the last commit")
5378
+ select.add_option("", "--direct-merges", action="store_const",
5379
+ const="direct-merges", dest="type",
5380
+ help="List revisions that have been directly added"
5381
+ " to tree since the last commit ")
5382
+ select.add_option("", "--library", action="store_const",
5383
+ const="library", dest="type",
5384
+ help="List revisions in the revision library")
5385
+ select.add_option("", "--ancestry", action="store_const",
5386
+ const="ancestry", dest="type",
5387
+ help="List revisions that are ancestors of the "
5388
+ "current tree version")
5390
+ select.add_option("", "--dependencies", action="store_const",
5391
+ const="dependencies", dest="type",
5392
+ help="List revisions that the given revision "
5395
+ select.add_option("", "--non-dependencies", action="store_const",
5396
+ const="non-dependencies", dest="type",
5397
+ help="List revisions that the given revision "
5398
+ "does not depend on")
5400
+ select.add_option("--micro", action="store_const",
5401
+ const="micro", dest="type",
5402
+ help="List partner revisions aimed for this "
5405
+ select.add_option("", "--modified", dest="modified",
5406
+ help="List tree ancestor revisions that modified a "
5407
+ "given file", metavar="FILE[:LINE]")
5409
+ parser.add_option("", "--skip", dest="skip",
5410
+ help="Skip revisions. Positive numbers skip from "
5411
+ "beginning, negative skip from end.",
5414
+ parser.add_option_group(select)
5416
+ format = cmdutil.OptionGroup(parser, "Revision format options",
5417
+ "These control the appearance of listed revisions")
5418
+ format.add_option("", "--location", action="store_const",
5419
+ const=paths.determine_path, dest="display",
5420
+ help="Show location instead of name", default=str)
5421
+ format.add_option("--import", action="store_const",
5422
+ const=paths.determine_import_path, dest="display",
5423
+ help="Show location of import file")
5424
+ format.add_option("--log", action="store_const",
5425
+ const=paths.determine_log_path, dest="display",
5426
+ help="Show location of log file")
5427
+ format.add_option("--patch", action="store_const",
5428
+ dest="display", const=paths.determine_patch_path,
5429
+ help="Show location of patchfile")
5430
+ format.add_option("--continuation", action="store_const",
5431
+ const=paths.determine_continuation_path,
5433
+ help="Show location of continuation file")
5434
+ format.add_option("--cacherev", action="store_const",
5435
+ const=paths.determine_cacherev_path, dest="display",
5436
+ help="Show location of cacherev file")
5437
+ parser.add_option_group(format)
5438
+ display = cmdutil.OptionGroup(parser, "Display format options",
5439
+ "These control the display of data")
5440
+ display.add_option("-r", "--reverse", action="store_true",
5441
+ dest="reverse", help="Sort from newest to oldest")
5442
+ display.add_option("-s", "--summary", action="store_true",
5443
+ dest="summary", help="Show patchlog summary")
5444
+ display.add_option("-D", "--date", action="store_true",
5445
+ dest="date", help="Show patchlog date")
5446
+ display.add_option("-c", "--creator", action="store_true",
5447
+ dest="creator", help="Show the id that committed the"
5449
+ display.add_option("-m", "--merges", action="store_true",
5450
+ dest="merges", help="Show the revisions that were"
5452
+ parser.add_option_group(display)
5454
+ def help(self, parser=None):
5455
+ """Attempt to explain the revisions command
5457
+ :param parser: If supplied, used to determine options
5460
+ parser=self.get_parser()
5461
+ parser.print_help()
5462
+ print """List revisions.
5467
+class Get(BaseCommand):
5469
+ Retrieve a revision from the archive
5471
+ def __init__(self):
5472
+ self.description="Retrieve a revision from the archive"
5473
+ self.parser=self.get_parser()
5476
+ def get_completer(self, arg, index):
5480
+ tree = arch.tree_root()
5483
+ return cmdutil.iter_revision_completions(arg, tree)
5486
+ def do_command(self, cmdargs):
5488
+ Master function that perfoms the "get" command.
5490
+ (options, args) = self.parser.parse_args(cmdargs)
5492
+ return self.help()
5494
+ tree = arch.tree_root()
5495
+ except arch.errors.TreeRootError:
5500
+ revision, arch_loc = paths.full_path_decode(args[0])
5501
+ except Exception, e:
5502
+ revision = cmdutil.determine_revision_arch(tree, args[0],
5503
+ check_existence=False, allow_package=True)
5505
+ directory = args[1]
5507
+ directory = str(revision.nonarch)
5508
+ if os.path.exists(directory):
5509
+ raise DirectoryExists(directory)
5510
+ cmdutil.ensure_archive_registered(revision.archive, arch_loc)
5512
+ cmdutil.ensure_revision_exists(revision)
5513
+ except cmdutil.NoSuchRevision, e:
5514
+ raise CommandFailedWrapper(e)
5516
+ link = cmdutil.prompt ("get link")
5517
+ for line in cmdutil.iter_get(revision, directory, link,
5518
+ options.no_pristine,
5519
+ options.no_greedy_add):
5520
+ cmdutil.colorize(line)
5522
+ def get_parser(self):
5524
+ Returns the options parser to use for the "get" command.
5526
+ :rtype: cmdutil.CmdOptionParser
5528
+ parser=cmdutil.CmdOptionParser("fai get revision [dir]")
5529
+ parser.add_option("--no-pristine", action="store_true",
5530
+ dest="no_pristine",
5531
+ help="Do not make pristine copy for reference")
5532
+ parser.add_option("--no-greedy-add", action="store_true",
5533
+ dest="no_greedy_add",
5534
+ help="Never add to greedy libraries")
5538
+ def help(self, parser=None):
5540
+ Prints a help message.
5542
+ :param parser: If supplied, the parser to use for generating help. If \
5543
+ not supplied, it is retrieved.
5544
+ :type parser: cmdutil.CmdOptionParser
5547
+ parser=self.get_parser()
5548
+ parser.print_help()
5550
+Expands aliases and constructs a project tree for a revision. If the optional
5551
+"dir" argument is provided, the project tree will be stored in this directory.
5556
+class PromptCmd(cmd.Cmd):
5557
+ def __init__(self):
5558
+ cmd.Cmd.__init__(self)
5559
+ self.prompt = "Fai> "
5561
+ self.tree = arch.tree_root()
5566
+ self.fake_aba = abacmds.AbaCmds()
5567
+ self.identchars += '-'
5568
+ self.history_file = os.path.expanduser("~/.fai-history")
5569
+ readline.set_completer_delims(string.whitespace)
5570
+ if os.access(self.history_file, os.R_OK) and \
5571
+ os.path.isfile(self.history_file):
5572
+ readline.read_history_file(self.history_file)
5574
+ def write_history(self):
5575
+ readline.write_history_file(self.history_file)
5577
+ def do_quit(self, args):
5578
+ self.write_history()
5581
+ def do_exit(self, args):
5582
+ self.do_quit(args)
5584
+ def do_EOF(self, args):
5586
+ self.do_quit(args)
5588
+ def postcmd(self, line, bar):
5592
+ def set_prompt(self):
5593
+ if self.tree is not None:
5595
+ version = " "+self.tree.tree_version.nonarch
5600
+ self.prompt = "Fai%s> " % version
5602
+ def set_title(self, command=None):
5604
+ version = self.tree.tree_version.nonarch
5606
+ version = "[no version]"
5607
+ if command is None:
5609
+ sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
5611
+ def do_cd(self, line):
5615
+ os.chdir(os.path.expanduser(line))
5616
+ except Exception, e:
5619
+ self.tree = arch.tree_root()
5623
+ def do_help(self, line):
5626
+ def default(self, line):
5627
+ args = line.split()
5628
+ if find_command(args[0]):
5630
+ find_command(args[0]).do_command(args[1:])
5631
+ except cmdutil.BadCommandOption, e:
5633
+ except cmdutil.GetHelp, e:
5634
+ find_command(args[0]).help()
5635
+ except CommandFailed, e:
5637
+ except arch.errors.ArchiveNotRegistered, e:
5639
+ except KeyboardInterrupt, e:
5640
+ print "Interrupted"
5641
+ except arch.util.ExecProblem, e:
5642
+ print e.proc.error.rstrip('\n')
5643
+ except cmdutil.CantDetermineVersion, e:
5645
+ except cmdutil.CantDetermineRevision, e:
5647
+ except Exception, e:
5648
+ print "Unhandled error:\n%s" % cmdutil.exception_str(e)
5650
+ elif suggestions.has_key(args[0]):
5651
+ print suggestions[args[0]]
5653
+ elif self.fake_aba.is_command(args[0]):
5656
+ tree = arch.tree_root()
5657
+ except arch.errors.TreeRootError:
5659
+ cmd = self.fake_aba.is_command(args[0])
5661
+ cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
5662
+ except KeyboardInterrupt, e:
5663
+ print "Interrupted"
5665
+ elif options.tla_fallthrough and args[0] != "rm" and \
5666
+ cmdutil.is_tla_command(args[0]):
5670
+ tree = arch.tree_root()
5671
+ except arch.errors.TreeRootError:
5673
+ args = cmdutil.expand_prefix_alias(args, tree)
5674
+ arch.util.exec_safe('tla', args, stderr=sys.stderr,
5676
+ except arch.util.ExecProblem, e:
5678
+ except KeyboardInterrupt, e:
5679
+ print "Interrupted"
5683
+ tree = arch.tree_root()
5684
+ except arch.errors.TreeRootError:
5687
+ os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
5688
+ except KeyboardInterrupt, e:
5689
+ print "Interrupted"
5691
+ def completenames(self, text, line, begidx, endidx):
5693
+ iter = iter_command_names(self.fake_aba)
5696
+ arg = line.split()[-1]
5699
+ iter = iter_munged_completions(iter, arg, text)
5700
+ except Exception, e:
5704
+ def completedefault(self, text, line, begidx, endidx):
5705
+ """Perform completion for native commands.
5707
+ :param text: The text to complete
5709
+ :param line: The entire line to complete
5711
+ :param begidx: The start of the text in the line
5713
+ :param endidx: The end of the text in the line
5717
+ (cmd, args, foo) = self.parseline(line)
5718
+ command_obj=find_command(cmd)
5719
+ if command_obj is not None:
5720
+ return command_obj.complete(args.split(), text)
5721
+ elif not self.fake_aba.is_command(cmd) and \
5722
+ cmdutil.is_tla_command(cmd):
5723
+ iter = cmdutil.iter_supported_switches(cmd)
5725
+ arg = args.split()[-1]
5728
+ if arg.startswith("-"):
5729
+ return list(iter_munged_completions(iter, arg, text))
5731
+ return list(iter_munged_completions(
5732
+ iter_file_completions(arg), arg, text))
5737
+ arg = args.split()[-1]
5740
+ iter = iter_dir_completions(arg)
5741
+ iter = iter_munged_completions(iter, arg, text)
5744
+ arg = args.split()[-1]
5745
+ return list(iter_munged_completions(iter_file_completions(arg),
5748
+ return self.completenames(text, line, begidx, endidx)
5749
+ except Exception, e:
5753
+def iter_command_names(fake_aba):
5754
+ for entry in cmdutil.iter_combine([commands.iterkeys(),
5755
+ fake_aba.get_commands(),
5756
+ cmdutil.iter_tla_commands(False)]):
5757
+ if not suggestions.has_key(str(entry)):
5761
+def iter_file_completions(arg, only_dirs = False):
5762
+ """Generate an iterator that iterates through filename completions.
5764
+ :param arg: The filename fragment to match
5766
+ :param only_dirs: If true, match only directories
5767
+ :type only_dirs: bool
5771
+ extras = [".", ".."]
5774
+ (dir, file) = os.path.split(arg)
5776
+ listingdir = os.path.expanduser(dir)
5779
+ for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
5781
+ userfile = dir+'/'+file
5784
+ if userfile.startswith(arg):
5785
+ if os.path.isdir(listingdir+'/'+file):
5788
+ elif not only_dirs:
5791
+def iter_munged_completions(iter, arg, text):
5792
+ for completion in iter:
5793
+ completion = str(completion)
5794
+ if completion.startswith(arg):
5795
+ yield completion[len(arg)-len(text):]
5797
+def iter_source_file_completions(tree, arg):
5798
+ treepath = cmdutil.tree_cwd(tree)
5799
+ if len(treepath) > 0:
5803
+ for file in tree.iter_inventory(dirs, source=True, both=True):
5804
+ file = file_completion_match(file, treepath, arg)
5805
+ if file is not None:
5809
+def iter_untagged(tree, dirs):
5810
+ for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False,
5811
+ categories=arch_core.non_root,
5812
+ control_files=True):
5816
+def iter_untagged_completions(tree, arg):
5817
+ """Generate an iterator for all visible untagged files that match arg.
5819
+ :param tree: The tree to look for untagged files in
5820
+ :type tree: `arch.WorkingTree`
5821
+ :param arg: The argument to match
5823
+ :return: An iterator of all matching untagged files
5824
+ :rtype: iterator of str
5826
+ treepath = cmdutil.tree_cwd(tree)
5827
+ if len(treepath) > 0:
5832
+ for file in iter_untagged(tree, dirs):
5833
+ file = file_completion_match(file, treepath, arg)
5834
+ if file is not None:
5838
+def file_completion_match(file, treepath, arg):
5839
+ """Determines whether a file within an arch tree matches the argument.
5841
+ :param file: The rooted filename
5843
+ :param treepath: The path to the cwd within the tree
5844
+ :type treepath: str
5845
+ :param arg: The prefix to match
5846
+ :return: The completion name, or None if not a match
5849
+ if not file.startswith(treepath):
5851
+ if treepath != "":
5852
+ file = file[len(treepath)+1:]
5854
+ if not file.startswith(arg):
5856
+ if os.path.isdir(file):
5860
+def iter_modified_file_completions(tree, arg):
5861
+ """Returns a list of modified files that match the specified prefix.
5863
+ :param tree: The current tree
5864
+ :type tree: `arch.WorkingTree`
5865
+ :param arg: The prefix to match
5868
+ treepath = cmdutil.tree_cwd(tree)
5869
+ tmpdir = cmdutil.tmpdir()
5870
+ changeset = tmpdir+"/changeset"
5872
+ revision = cmdutil.determine_revision_tree(tree)
5873
+ for line in arch.iter_delta(revision, tree, changeset):
5874
+ if isinstance(line, arch.FileModification):
5875
+ file = file_completion_match(line.name[1:], treepath, arg)
5876
+ if file is not None:
5877
+ completions.append(file)
5878
+ shutil.rmtree(tmpdir)
5879
+ return completions
5881
+def iter_dir_completions(arg):
5882
+ """Generate an iterator that iterates through directory name completions.
5884
+ :param arg: The directory name fragment to match
5887
+ return iter_file_completions(arg, True)
5889
+class Shell(BaseCommand):
5890
+ def __init__(self):
5891
+ self.description = "Runs Fai as a shell"
5893
+ def do_command(self, cmdargs):
5894
+ if len(cmdargs)!=0:
5895
+ raise cmdutil.GetHelp
5896
+ prompt = PromptCmd()
5900
+ prompt.write_history()
5902
+class AddID(BaseCommand):
5904
+ Adds an inventory id for the given file
5906
+ def __init__(self):
5907
+ self.description="Add an inventory id for a given file"
5909
+ def get_completer(self, arg, index):
5910
+ tree = arch.tree_root()
5911
+ return iter_untagged_completions(tree, arg)
5913
+ def do_command(self, cmdargs):
5915
+ Master function that perfoms the "revision" command.
5917
+ parser=self.get_parser()
5918
+ (options, args) = parser.parse_args(cmdargs)
5920
+ tree = arch.tree_root()
5922
+ if (len(args) == 0) == (options.untagged == False):
5923
+ raise cmdutil.GetHelp
5925
+ #if options.id and len(args) != 1:
5926
+ # print "If --id is specified, only one file can be named."
5929
+ method = tree.tagging_method
5931
+ if options.id_type == "tagline":
5932
+ if method != "tagline":
5933
+ if not cmdutil.prompt("Tagline in other tree"):
5934
+ if method == "explicit":
5935
+ options.id_type == explicit
5937
+ print "add-id not supported for \"%s\" tagging method"\
5941
+ elif options.id_type == "explicit":
5942
+ if method != "tagline" and method != explicit:
5943
+ if not prompt("Explicit in other tree"):
5944
+ print "add-id not supported for \"%s\" tagging method" % \
5948
+ if options.id_type == "auto":
5949
+ if method != "tagline" and method != "explicit":
5950
+ print "add-id not supported for \"%s\" tagging method" % method
5953
+ options.id_type = method
5954
+ if options.untagged:
5956
+ self.add_ids(tree, options.id_type, args)
5958
+ def add_ids(self, tree, id_type, files=()):
5959
+ """Add inventory ids to files.
5961
+ :param tree: the tree the files are in
5962
+ :type tree: `arch.WorkingTree`
5963
+ :param id_type: the type of id to add: "explicit" or "tagline"
5964
+ :type id_type: str
5965
+ :param files: The list of files to add. If None do all untagged.
5966
+ :type files: tuple of str
5969
+ untagged = (files is None)
5971
+ files = list(iter_untagged(tree, None))
5972
+ previous_files = []
5973
+ while len(files) > 0:
5974
+ previous_files.extend(files)
5975
+ if id_type == "explicit":
5976
+ cmdutil.add_id(files)
5977
+ elif id_type == "tagline":
5978
+ for file in files:
5980
+ cmdutil.add_tagline_or_explicit_id(file)
5981
+ except cmdutil.AlreadyTagged:
5982
+ print "\"%s\" already has a tagline." % file
5983
+ except cmdutil.NoCommentSyntax:
5985
+ #do inventory after tagging until no untagged files are encountered
5988
+ for file in iter_untagged(tree, None):
5989
+ if not file in previous_files:
5990
+ files.append(file)
5995
+ def get_parser(self):
5997
+ Returns the options parser to use for the "revision" command.
5999
+ :rtype: cmdutil.CmdOptionParser
6001
+ parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
6002
+# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
6003
+# parser.add_option("-i", "--id", dest="id",
6004
+# help="Specify id for a single file", default=None)
6005
+ parser.add_option("--tltl", action="store_true",
6006
+ dest="lord_style", help="Use Tom Lord's style of id.")
6007
+ parser.add_option("--explicit", action="store_const",
6008
+ const="explicit", dest="id_type",
6009
+ help="Use an explicit id", default="auto")
6010
+ parser.add_option("--tagline", action="store_const",
6011
+ const="tagline", dest="id_type",
6012
+ help="Use a tagline id")
6013
+ parser.add_option("--untagged", action="store_true",
6014
+ dest="untagged", default=False,
6015
+ help="tag all untagged files")
6018
+ def help(self, parser=None):
6020
+ Prints a help message.
6022
+ :param parser: If supplied, the parser to use for generating help. If \
6023
+ not supplied, it is retrieved.
6024
+ :type parser: cmdutil.CmdOptionParser
6027
+ parser=self.get_parser()
6028
+ parser.print_help()
6030
+Adds an inventory to the specified file(s) and directories. If --untagged is
6031
+specified, adds inventory to all untagged files and directories.
6036
+class Merge(BaseCommand):
6038
+ Merges changes from other versions into the current tree
6040
+ def __init__(self):
6041
+ self.description="Merges changes from other versions"
6043
+ self.tree = arch.tree_root()
6048
+ def get_completer(self, arg, index):
6049
+ if self.tree is None:
6050
+ raise arch.errors.TreeRootError
6051
+ completions = list(ancillary.iter_partners(self.tree,
6052
+ self.tree.tree_version))
6053
+ if len(completions) == 0:
6054
+ completions = list(self.tree.iter_log_versions())
6058
+ for completion in completions:
6059
+ alias = ancillary.compact_alias(str(completion), self.tree)
6061
+ aliases.extend(alias)
6063
+ for completion in completions:
6064
+ if completion.archive == self.tree.tree_version.archive:
6065
+ aliases.append(completion.nonarch)
6067
+ except Exception, e:
6070
+ completions.extend(aliases)
6071
+ return completions
6073
+ def do_command(self, cmdargs):
6075
+ Master function that perfoms the "merge" command.
6077
+ parser=self.get_parser()
6078
+ (options, args) = parser.parse_args(cmdargs)
6080
+ action="star-merge"
6082
+ action = options.action
6084
+ if self.tree is None:
6085
+ raise arch.errors.TreeRootError(os.getcwd())
6086
+ if cmdutil.has_changed(self.tree.tree_version):
6087
+ raise UncommittedChanges(self.tree)
6092
+ revisions.append(cmdutil.determine_revision_arch(self.tree,
6094
+ source = "from commandline"
6096
+ revisions = ancillary.iter_partner_revisions(self.tree,
6097
+ self.tree.tree_version)
6098
+ source = "from partner version"
6099
+ revisions = misc.rewind_iterator(revisions)
6102
+ revisions.rewind()
6103
+ except StopIteration, e:
6104
+ revision = cmdutil.tag_cur(self.tree)
6105
+ if revision is None:
6106
+ raise CantDetermineRevision("", "No version specified, no "
6107
+ "partner-versions, and no tag"
6109
+ revisions = [revision]
6110
+ source = "from tag source"
6111
+ for revision in revisions:
6112
+ cmdutil.ensure_archive_registered(revision.archive)
6113
+ cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
6114
+ (revision, source)))
6115
+ if action=="native-merge" or action=="update":
6116
+ if self.native_merge(revision, action) == 0:
6118
+ elif action=="star-merge":
6120
+ self.star_merge(revision, options.diff3)
6121
+ except errors.MergeProblem, e:
6123
+ if cmdutil.has_changed(self.tree.tree_version):
6126
+ def star_merge(self, revision, diff3):
6127
+ """Perform a star-merge on the current tree.
6129
+ :param revision: The revision to use for the merge
6130
+ :type revision: `arch.Revision`
6131
+ :param diff3: If true, do a diff3 merge
6135
+ for line in self.tree.iter_star_merge(revision, diff3=diff3):
6136
+ cmdutil.colorize(line)
6137
+ except arch.util.ExecProblem, e:
6138
+ if e.proc.status is not None and e.proc.status == 1:
6140
+ print e.proc.error
6141
+ raise MergeProblem
6145
+ def native_merge(self, other_revision, action):
6146
+ """Perform a native-merge on the current tree.
6148
+ :param other_revision: The revision to use for the merge
6149
+ :type other_revision: `arch.Revision`
6150
+ :return: 0 if the merge was skipped, 1 if it was applied
6152
+ other_tree = cmdutil.find_or_make_local_revision(other_revision)
6154
+ if action == "native-merge":
6155
+ ancestor = cmdutil.merge_ancestor2(self.tree, other_tree,
6157
+ elif action == "update":
6158
+ ancestor = cmdutil.tree_latest(self.tree,
6159
+ other_revision.version)
6160
+ except CantDetermineRevision, e:
6161
+ raise CommandFailedWrapper(e)
6162
+ cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
6163
+ if (ancestor == other_revision):
6164
+ cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
6167
+ delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
6168
+ for line in cmdutil.iter_apply_delta_filter(delta):
6169
+ cmdutil.colorize(line)
6174
+ def get_parser(self):
6176
+ Returns the options parser to use for the "merge" command.
6178
+ :rtype: cmdutil.CmdOptionParser
6180
+ parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
6181
+ parser.add_option("-s", "--star-merge", action="store_const",
6182
+ dest="action", help="Use star-merge",
6183
+ const="star-merge", default="native-merge")
6184
+ parser.add_option("--update", action="store_const",
6185
+ dest="action", help="Use update picker",
6187
+ parser.add_option("--diff3", action="store_true",
6189
+ help="Use diff3 for merge (implies star-merge)")
6192
+ def help(self, parser=None):
6194
+ Prints a help message.
6196
+ :param parser: If supplied, the parser to use for generating help. If \
6197
+ not supplied, it is retrieved.
6198
+ :type parser: cmdutil.CmdOptionParser
6201
+ parser=self.get_parser()
6202
+ parser.print_help()
6204
+Performs a merge operation using the specified version.
6208
+class ELog(BaseCommand):
6210
+ Produces a raw patchlog and invokes the user's editor
6212
+ def __init__(self):
6213
+ self.description="Edit a patchlog to commit"
6215
+ self.tree = arch.tree_root()
6220
+ def do_command(self, cmdargs):
6222
+ Master function that perfoms the "elog" command.
6224
+ parser=self.get_parser()
6225
+ (options, args) = parser.parse_args(cmdargs)
6226
+ if self.tree is None:
6227
+ raise arch.errors.TreeRootError
6229
+ edit_log(self.tree)
6231
+ def get_parser(self):
6233
+ Returns the options parser to use for the "merge" command.
6235
+ :rtype: cmdutil.CmdOptionParser
6237
+ parser=cmdutil.CmdOptionParser("fai elog")
6241
+ def help(self, parser=None):
6243
+ Invokes $EDITOR to produce a log for committing.
6245
+ :param parser: If supplied, the parser to use for generating help. If \
6246
+ not supplied, it is retrieved.
6247
+ :type parser: cmdutil.CmdOptionParser
6250
+ parser=self.get_parser()
6251
+ parser.print_help()
6253
+Invokes $EDITOR to produce a log for committing.
6257
+def edit_log(tree):
6258
+ """Makes and edits the log for a tree. Does all kinds of fancy things
6259
+ like log templates and merge summaries and log-for-merge
6261
+ :param tree: The tree to edit the log for
6262
+ :type tree: `arch.WorkingTree`
6264
+ #ensure we have an editor before preparing the log
6265
+ cmdutil.find_editor()
6266
+ log = tree.log_message(create=False)
6267
+ log_is_new = False
6268
+ if log is None or cmdutil.prompt("Overwrite log"):
6269
+ if log is not None:
6270
+ os.remove(log.name)
6271
+ log = tree.log_message(create=True)
6274
+ template = tree+"/{arch}/=log-template"
6275
+ if not os.path.exists(template):
6276
+ template = os.path.expanduser("~/.arch-params/=log-template")
6277
+ if not os.path.exists(template):
6280
+ shutil.copyfile(template, tmplog)
6282
+ new_merges = list(cmdutil.iter_new_merges(tree,
6283
+ tree.tree_version))
6284
+ log["Summary"] = merge_summary(new_merges, tree.tree_version)
6285
+ if len(new_merges) > 0:
6286
+ if cmdutil.prompt("Log for merge"):
6287
+ mergestuff = cmdutil.log_for_merge(tree)
6288
+ log.description += mergestuff
6291
+ cmdutil.invoke_editor(log.name)
6294
+ os.remove(log.name)
6297
+def merge_summary(new_merges, tree_version):
6298
+ if len(new_merges) == 0:
6300
+ if len(new_merges) == 1:
6301
+ summary = new_merges[0].summary
6306
+ for merge in new_merges:
6307
+ if arch.my_id() != merge.creator:
6308
+ name = re.sub("<.*>", "", merge.creator).rstrip(" ");
6309
+ if not name in credits:
6310
+ credits.append(name)
6312
+ version = merge.revision.version
6313
+ if version.archive == tree_version.archive:
6314
+ if not version.nonarch in credits:
6315
+ credits.append(version.nonarch)
6316
+ elif not str(version) in credits:
6317
+ credits.append(str(version))
6319
+ return ("%s (%s)") % (summary, ", ".join(credits))
6321
+class MirrorArchive(BaseCommand):
6323
+ Updates a mirror from an archive
6325
+ def __init__(self):
6326
+ self.description="Update a mirror from an archive"
6328
+ def do_command(self, cmdargs):
6330
+ Master function that perfoms the "revision" command.
6333
+ parser=self.get_parser()
6334
+ (options, args) = parser.parse_args(cmdargs)
6338
+ tree = arch.tree_root()
6342
+ if len(args) == 0:
6343
+ if tree is not None:
6344
+ name = tree.tree_version()
6346
+ name = cmdutil.expand_alias(args[0], tree)
6347
+ name = arch.NameParser(name)
6349
+ to_arch = name.get_archive()
6350
+ from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
6351
+ limit = name.get_nonarch()
6353
+ iter = arch_core.mirror_archive(from_arch,to_arch, limit)
6354
+ for line in arch.chatter_classifier(iter):
6355
+ cmdutil.colorize(line)
6357
+ def get_parser(self):
6359
+ Returns the options parser to use for the "revision" command.
6361
+ :rtype: cmdutil.CmdOptionParser
6363
+ parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
6366
+ def help(self, parser=None):
6368
+ Prints a help message.
6370
+ :param parser: If supplied, the parser to use for generating help. If \
6371
+ not supplied, it is retrieved.
6372
+ :type parser: cmdutil.CmdOptionParser
6375
+ parser=self.get_parser()
6376
+ parser.print_help()
6378
+Updates a mirror from an archive. If a branch, package, or version is
6379
+supplied, only changes under it are mirrored.
6383
+def help_tree_spec():
6384
+ print """Specifying revisions (default: tree)
6385
+Revisions may be specified by alias, revision, version or patchlevel.
6386
+Revisions or versions may be fully qualified. Unqualified revisions, versions,
6387
+or patchlevels use the archive of the current project tree. Versions will
6388
+use the latest patchlevel in the tree. Patchlevels will use the current tree-
6391
+Use "alias" to list available (user and automatic) aliases."""
6393
+def help_aliases(tree):
6394
+ print """Auto-generated aliases
6395
+ acur : The latest revision in the archive of the tree-version. You can specfy
6396
+ a different version like so: acur:foo--bar--0 (aliases can be used)
6397
+ tcur : (tree current) The latest revision in the tree of the tree-version.
6398
+ You can specify a different version like so: tcur:foo--bar--0 (aliases
6400
+tprev : (tree previous) The previous revision in the tree of the tree-version.
6401
+ To specify an older revision, use a number, e.g. "tprev:4"
6402
+ tanc : (tree ancestor) The ancestor revision of the tree
6403
+ To specify an older revision, use a number, e.g. "tanc:4"
6404
+tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
6405
+ tmod : (tree modified) The latest revision to modify a given file
6406
+ (e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
6407
+ ttag : (tree tag) The revision that was tagged into the current tree revision,
6408
+ according to the tree.
6409
+tagcur: (tag current) The latest revision of the version that the current tree
6411
+mergeanc : The common ancestor of the current tree and the specified revision.
6412
+ Defaults to the first partner-version's latest revision or to tagcur.
6414
+ print "User aliases"
6415
+ for parts in ancillary.iter_all_alias(tree):
6416
+ print parts[0].rjust(10)+" : "+parts[1]
6419
+class Inventory(BaseCommand):
6420
+ """List the status of files in the tree"""
6421
+ def __init__(self):
6422
+ self.description=self.__doc__
6424
+ def do_command(self, cmdargs):
6426
+ Master function that perfoms the "revision" command.
6429
+ parser=self.get_parser()
6430
+ (options, args) = parser.parse_args(cmdargs)
6431
+ tree = arch.tree_root()
6434
+ if (options.source):
6435
+ categories.append(arch_core.SourceFile)
6436
+ if (options.precious):
6437
+ categories.append(arch_core.PreciousFile)
6438
+ if (options.backup):
6439
+ categories.append(arch_core.BackupFile)
6440
+ if (options.junk):
6441
+ categories.append(arch_core.JunkFile)
6443
+ if len(categories) == 1:
6444
+ show_leading = False
6446
+ show_leading = True
6448
+ if len(categories) == 0:
6451
+ if options.untagged:
6452
+ categories = arch_core.non_root
6453
+ show_leading = False
6458
+ for file in arch_core.iter_inventory_filter(tree, None,
6459
+ control_files=options.control_files,
6460
+ categories = categories, tagged=tagged):
6461
+ print arch_core.file_line(file,
6462
+ category = show_leading,
6463
+ untagged = show_leading,
6466
+ def get_parser(self):
6468
+ Returns the options parser to use for the "revision" command.
6470
+ :rtype: cmdutil.CmdOptionParser
6472
+ parser=cmdutil.CmdOptionParser("fai inventory [options]")
6473
+ parser.add_option("--ids", action="store_true", dest="ids",
6474
+ help="Show file ids")
6475
+ parser.add_option("--control", action="store_true",
6476
+ dest="control_files", help="include control files")
6477
+ parser.add_option("--source", action="store_true", dest="source",
6478
+ help="List source files")
6479
+ parser.add_option("--backup", action="store_true", dest="backup",
6480
+ help="List backup files")
6481
+ parser.add_option("--precious", action="store_true", dest="precious",
6482
+ help="List precious files")
6483
+ parser.add_option("--junk", action="store_true", dest="junk",
6484
+ help="List junk files")
6485
+ parser.add_option("--unrecognized", action="store_true",
6486
+ dest="unrecognized", help="List unrecognized files")
6487
+ parser.add_option("--untagged", action="store_true",
6488
+ dest="untagged", help="List only untagged files")
6491
+ def help(self, parser=None):
6493
+ Prints a help message.
6495
+ :param parser: If supplied, the parser to use for generating help. If \
6496
+ not supplied, it is retrieved.
6497
+ :type parser: cmdutil.CmdOptionParser
6500
+ parser=self.get_parser()
6501
+ parser.print_help()
6503
+Lists the status of files in the archive:
6511
+Leading letter are not displayed if only one kind of file is shown
6516
+class Alias(BaseCommand):
6517
+ """List or adjust aliases"""
6518
+ def __init__(self):
6519
+ self.description=self.__doc__
6521
+ def get_completer(self, arg, index):
6525
+ self.tree = arch.tree_root()
6530
+ return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
6532
+ return cmdutil.iter_revision_completions(arg, self.tree)
6535
+ def do_command(self, cmdargs):
6537
+ Master function that perfoms the "revision" command.
6540
+ parser=self.get_parser()
6541
+ (options, args) = parser.parse_args(cmdargs)
6543
+ self.tree = arch.tree_root()
6549
+ options.action(args, options)
6550
+ except cmdutil.ForbiddenAliasSyntax, e:
6551
+ raise CommandFailedWrapper(e)
6553
+ def arg_dispatch(self, args, options):
6554
+ """Add, modify, or list aliases, depending on number of arguments
6556
+ :param args: The list of commandline arguments
6557
+ :type args: list of str
6558
+ :param options: The commandline options
6560
+ if len(args) == 0:
6561
+ help_aliases(self.tree)
6563
+ elif len(args) == 1:
6564
+ self.print_alias(args[0])
6565
+ elif (len(args)) == 2:
6566
+ self.add(args[0], args[1], options)
6568
+ raise cmdutil.GetHelp
6570
+ def print_alias(self, alias):
6572
+ for pair in ancillary.iter_all_alias(self.tree):
6573
+ if pair[0] == alias:
6575
+ if answer is not None:
6578
+ print "The alias %s is not assigned." % alias
6580
+ def add(self, alias, expansion, options):
6581
+ """Add or modify aliases
6583
+ :param alias: The alias name to create/modify
6585
+ :param expansion: The expansion to assign to the alias name
6586
+ :type expansion: str
6587
+ :param options: The commandline options
6591
+ new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
6593
+ ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
6595
+ for pair in self.get_iterator(options):
6596
+ if pair[0] != alias:
6597
+ newlist+="%s=%s\n" % (pair[0], pair[1])
6603
+ self.write_aliases(newlist, options)
6605
+ def delete(self, args, options):
6606
+ """Delete the specified alias
6608
+ :param args: The list of arguments
6609
+ :type args: list of str
6610
+ :param options: The commandline options
6613
+ if len(args) != 1:
6614
+ raise cmdutil.GetHelp
6616
+ for pair in self.get_iterator(options):
6617
+ if pair[0] != args[0]:
6618
+ newlist+="%s=%s\n" % (pair[0], pair[1])
6622
+ raise errors.NoSuchAlias(args[0])
6623
+ self.write_aliases(newlist, options)
6625
+ def get_alias_file(self, options):
6626
+ """Return the name of the alias file to use
6628
+ :param options: The commandline options
6631
+ if self.tree is None:
6632
+ self.tree == arch.tree_root()
6633
+ return str(self.tree)+"/{arch}/+aliases"
6635
+ return "~/.aba/aliases"
6637
+ def get_iterator(self, options):
6638
+ """Return the alias iterator to use
6640
+ :param options: The commandline options
6642
+ return ancillary.iter_alias(self.get_alias_file(options))
6644
+ def write_aliases(self, newlist, options):
6645
+ """Safely rewrite the alias file
6646
+ :param newlist: The new list of aliases
6647
+ :type newlist: str
6648
+ :param options: The commandline options
6650
+ filename = os.path.expanduser(self.get_alias_file(options))
6651
+ file = cmdutil.NewFileVersion(filename)
6652
+ file.write(newlist)
6656
+ def get_parser(self):
6658
+ Returns the options parser to use for the "alias" command.
6660
+ :rtype: cmdutil.CmdOptionParser
6662
+ parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
6663
+ parser.add_option("-d", "--delete", action="store_const", dest="action",
6664
+ const=self.delete, default=self.arg_dispatch,
6665
+ help="Delete an alias")
6666
+ parser.add_option("--tree", action="store_true", dest="tree",
6667
+ help="Create a per-tree alias", default=False)
6670
+ def help(self, parser=None):
6672
+ Prints a help message.
6674
+ :param parser: If supplied, the parser to use for generating help. If \
6675
+ not supplied, it is retrieved.
6676
+ :type parser: cmdutil.CmdOptionParser
6679
+ parser=self.get_parser()
6680
+ parser.print_help()
6682
+Lists current aliases or modifies the list of aliases.
6684
+If no arguments are supplied, aliases will be listed. If two arguments are
6685
+supplied, the specified alias will be created or modified. If -d or --delete
6686
+is supplied, the specified alias will be deleted.
6688
+You can create aliases that refer to any fully-qualified part of the
6689
+Arch namespace, e.g.
6692
+archive/category--branch,
6693
+archive/category--branch--version (my favourite)
6694
+archive/category--branch--version--patchlevel
6696
+Aliases can be used automatically by native commands. To use them
6697
+with external or tla commands, prefix them with ^ (you can do this
6698
+with native commands, too).
6702
+class RequestMerge(BaseCommand):
6703
+ """Submit a merge request to Bug Goo"""
6704
+ def __init__(self):
6705
+ self.description=self.__doc__
6707
+ def do_command(self, cmdargs):
6708
+ """Submit a merge request
6710
+ :param cmdargs: The commandline arguments
6711
+ :type cmdargs: list of str
6713
+ cmdutil.find_editor()
6714
+ parser = self.get_parser()
6715
+ (options, args) = parser.parse_args(cmdargs)
6717
+ self.tree=arch.tree_root()
6720
+ base, revisions = self.revision_specs(args)
6721
+ message = self.make_headers(base, revisions)
6722
+ message += self.make_summary(revisions)
6723
+ path = self.edit_message(message)
6724
+ message = self.tidy_message(path)
6725
+ if cmdutil.prompt("Send merge"):
6726
+ self.send_message(message)
6727
+ print "Merge request sent"
6729
+ def make_headers(self, base, revisions):
6730
+ """Produce email and Bug Goo header strings
6732
+ :param base: The base revision to apply merges to
6733
+ :type base: `arch.Revision`
6734
+ :param revisions: The revisions to replay into the base
6735
+ :type revisions: list of `arch.Patchlog`
6736
+ :return: The headers
6739
+ headers = "To: gnu-arch-users@gnu.org\n"
6740
+ headers += "From: %s\n" % options.fromaddr
6741
+ if len(revisions) == 1:
6742
+ headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
6744
+ headers += "Subject: [MERGE REQUEST]\n"
6746
+ headers += "Base-Revision: %s\n" % base
6747
+ for revision in revisions:
6748
+ headers += "Revision: %s\n" % revision.revision
6749
+ headers += "Bug: \n\n"
6752
+ def make_summary(self, logs):
6753
+ """Generate a summary of merges
6755
+ :param logs: the patchlogs that were directly added by the merges
6756
+ :type logs: list of `arch.Patchlog`
6757
+ :return: the summary
6762
+ summary+=str(log.revision)+"\n"
6763
+ summary+=log.summary+"\n"
6764
+ if log.description.strip():
6765
+ summary+=log.description.strip('\n')+"\n\n"
6768
+ def revision_specs(self, args):
6769
+ """Determine the base and merge revisions from tree and arguments.
6771
+ :param args: The parsed arguments
6772
+ :type args: list of str
6773
+ :return: The base revision and merge revisions
6774
+ :rtype: `arch.Revision`, list of `arch.Patchlog`
6777
+ target_revision = cmdutil.determine_revision_arch(self.tree,
6780
+ target_revision = cmdutil.tree_latest(self.tree)
6782
+ merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
6783
+ self.tree, f)) for f in args[1:] ]
6785
+ if self.tree is None:
6786
+ raise CantDetermineRevision("", "Not in a project tree")
6787
+ merge_iter = cmdutil.iter_new_merges(self.tree,
6788
+ target_revision.version,
6790
+ merges = [f for f in cmdutil.direct_merges(merge_iter)]
6791
+ return (target_revision, merges)
6793
+ def edit_message(self, message):
6794
+ """Edit an email message in the user's standard editor
6796
+ :param message: The message to edit
6797
+ :type message: str
6798
+ :return: the path of the edited message
6801
+ if self.tree is None:
6802
+ path = os.get_cwd()
6805
+ path += "/,merge-request"
6806
+ file = open(path, 'w')
6807
+ file.write(message)
6809
+ cmdutil.invoke_editor(path)
6812
+ def tidy_message(self, path):
6813
+ """Validate and clean up message.
6815
+ :param path: The path to the message to clean up
6817
+ :return: The parsed message
6818
+ :rtype: `email.Message`
6820
+ mail = email.message_from_file(open(path))
6821
+ if mail["Subject"].strip() == "[MERGE REQUEST]":
6822
+ raise BlandSubject
6824
+ request = email.message_from_string(mail.get_payload())
6825
+ if request.has_key("Bug"):
6826
+ if request["Bug"].strip()=="":
6827
+ del request["Bug"]
6828
+ mail.set_payload(request.as_string())
6831
+ def send_message(self, message):
6832
+ """Send a message, using its headers to address it.
6834
+ :param message: The message to send
6835
+ :type message: `email.Message`"""
6836
+ server = smtplib.SMTP()
6837
+ server.sendmail(message['From'], message['To'], message.as_string())
6840
+ def help(self, parser=None):
6841
+ """Print a usage message
6843
+ :param parser: The options parser to use
6844
+ :type parser: `cmdutil.CmdOptionParser`
6846
+ if parser is None:
6847
+ parser = self.get_parser()
6848
+ parser.print_help()
6850
+Sends a merge request formatted for Bug Goo. Intended use: get the tree
6851
+you'd like to merge into. Apply the merges you want. Invoke request-merge.
6852
+The merge request will open in your $EDITOR.
6854
+When no TARGET is specified, it uses the current tree revision. When
6855
+no MERGE is specified, it uses the direct merges (as in "revisions
6856
+--direct-merges"). But you can specify just the TARGET, or all the MERGE
6860
+ def get_parser(self):
6861
+ """Produce a commandline parser for this command.
6863
+ :rtype: `cmdutil.CmdOptionParser`
6865
+ parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
6869
+'changes' : Changes,
6872
+'apply-changes':ApplyChanges,
6875
+'revision': Revision,
6876
+'revisions': Revisions,
6883
+'mirror-archive': MirrorArchive,
6884
+'ninventory': Inventory,
6886
+'request-merge': RequestMerge,
6889
+'apply-delta' : "Try \"apply-changes\".",
6890
+'delta' : "To compare two revisions, use \"changes\".",
6891
+'diff-rev' : "To compare two revisions, use \"changes\".",
6892
+'undo' : "To undo local changes, use \"revert\".",
6893
+'undelete' : "To undo only deletions, use \"revert --deletions\"",
6894
+'missing-from' : "Try \"revisions --missing-from\".",
6895
+'missing' : "Try \"revisions --missing\".",
6896
+'missing-merge' : "Try \"revisions --partner-missing\".",
6897
+'new-merges' : "Try \"revisions --new-merges\".",
6898
+'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
6899
+'logs' : "Try \"revisions --logs\"",
6900
+'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
6901
+'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
6902
+'change-version' : "Try \"update REVISION\"",
6903
+'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
6904
+'rev-depends' : "Use revisions --dependencies",
6905
+'auto-get' : "Plain get will do archive lookups",
6906
+'tagline' : "Use add-id. It uses taglines in tagline trees",
6907
+'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
6908
+'library-revisions' : "Use revisions --library",
6909
+'file-revert' : "Use revert FILE"
6911
+# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7
6913
*** modified file 'bzrlib/branch.py'
6914
--- bzrlib/branch.py
6915
+++ bzrlib/branch.py
6917
from revision import Revision
6918
from errors import bailout, BzrError
6919
from textui import show_status
6921
+from bzrlib import progress
6923
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
6924
## TODO: Maybe include checks for common corruption of newlines, etc?
6925
@@ -864,3 +866,36 @@
6927
s = hexlify(rand_bytes(8))
6928
return '-'.join((name, compact_date(time.time()), s))
6931
+def iter_anno_data(branch, file_id):
6932
+ later_revision = branch.revno()
6933
+ q = range(branch.revno())
6935
+ later_text_id = branch.basis_tree().inventory[file_id].text_id
6939
+ cur_tree = branch.revision_tree(branch.lookup_revision(revno))
6940
+ if file_id not in cur_tree.inventory:
6943
+ text_id = cur_tree.inventory[file_id].text_id
6944
+ if text_id != later_text_id:
6945
+ patch = get_patch(branch, revno, later_revision, file_id)
6946
+ yield revno, patch.iter_inserted(), patch
6947
+ later_revision = revno
6948
+ later_text_id = text_id
6949
+ yield progress.Progress("revisions", i)
6951
+def get_patch(branch, old_revno, new_revno, file_id):
6952
+ old_tree = branch.revision_tree(branch.lookup_revision(old_revno))
6953
+ new_tree = branch.revision_tree(branch.lookup_revision(new_revno))
6954
+ if file_id in old_tree.inventory:
6955
+ old_file = old_tree.get_file(file_id).readlines()
6958
+ ud = difflib.unified_diff(old_file, new_tree.get_file(file_id).readlines())
6959
+ return patches.parse_patch(ud)
6963
*** modified file 'bzrlib/commands.py'
6964
--- bzrlib/commands.py
6965
+++ bzrlib/commands.py
6967
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
6969
from bzrlib import merge
6970
+from bzrlib.branch import iter_anno_data
6971
+from bzrlib import patches
6972
+from bzrlib import progress
6975
def _squish_command_name(cmd):
6976
@@ -882,7 +885,15 @@
6977
print '%3d FAILED!' % mf
6981
+ result = bzrlib.patches.test()
6982
+ resultFailed = len(result.errors) + len(result.failures)
6983
+ print '%-40s %3d tests' % ('bzrlib.patches', result.testsRun),
6985
+ print '%3d FAILED!' % resultFailed
6988
+ tests += result.testsRun
6989
+ failures += resultFailed
6990
print '%-40s %3d tests' % ('total', tests),
6992
print '%3d FAILED!' % failures
6993
@@ -897,6 +908,34 @@
6994
"""Show version of bzr"""
6998
+class cmd_annotate(Command):
6999
+ """Show which revision added each line in a file"""
7001
+ takes_args = ['filename']
7002
+ def run(self, filename):
7003
+ if not os.path.exists(filename):
7004
+ raise BzrCommandError("The file %s does not exist." % filename)
7005
+ branch = (Branch(filename))
7006
+ file_id = branch.working_tree().path2id(filename)
7007
+ if file_id is None:
7008
+ raise BzrCommandError("The file %s is not versioned." % filename)
7009
+ lines = branch.basis_tree().get_file(file_id)
7010
+ total = branch.revno()
7011
+ anno_d_iter = iter_anno_data(branch, file_id)
7012
+ progress_bar = progress.ProgressBar()
7014
+ for result in patches.iter_annotate_file(lines, anno_d_iter):
7015
+ if isinstance(result, progress.Progress):
7016
+ result.total = total
7017
+ progress_bar(result)
7019
+ anno_lines = result
7021
+ progress.clear_progress_bar()
7022
+ for line in anno_lines:
7023
+ sys.stdout.write("%4s:%s" % (str(line.log), line.text))
7027
print "bzr (bazaar-ng) %s" % bzrlib.__version__