1
# Copyright (C) 2004 Aaron Bentley
2
# <aaron.bentley@utoronto.ca>
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
from pylon.errors import *
25
from pylon import errors
26
from pylon import util
27
from pylon import arch_core
28
from pylon import arch_compound
29
from pylon import ancillary
30
from pylon import misc
31
from pylon import paths
48
__docformat__ = "restructuredtext"
49
__doc__ = "Implementation of user (sub) commands"
52
def find_command(cmd):
54
Return an instance of a command type. Return None if the type isn't
57
:param cmd: the name of the command to look for
58
:type cmd: the type of the command
60
if commands.has_key(cmd):
61
return commands[cmd]()
66
def __call__(self, cmdline):
68
self.do_command(cmdline.split())
69
except cmdutil.GetHelp, e:
74
def get_completer(index):
77
def complete(self, args, text):
79
Returns a list of possible completions for the given text.
81
:param args: The complete list of arguments
82
:type args: List of str
83
:param text: text to complete (may be shorter than args[-1])
96
parser=self.get_parser()
97
if realtext.startswith('-'):
98
candidates = parser.iter_options()
100
(options, parsed_args) = parser.parse_args(args)
102
if len (parsed_args) > 0:
103
candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
105
candidates = self.get_completer("", 0)
108
if candidates is None:
110
for candidate in candidates:
111
candidate = str(candidate)
112
if candidate.startswith(realtext):
113
matches.append(candidate[len(realtext)- len(text):])
117
class Help(BaseCommand):
119
Lists commands, prints help messages.
122
self.description="Prints help mesages"
125
def do_command(self, cmdargs):
127
Prints a help message.
129
options, args = self.get_parser().parse_args(cmdargs)
131
raise cmdutil.GetHelp
133
if options.native or options.suggestions or options.external:
134
native = options.native
135
suggestions = options.suggestions
136
external = options.external
143
self.list_commands(native, suggestions, external)
146
command_help(args[0])
150
self.get_parser().print_help()
152
If no command is specified, commands are listed. If a command is
153
specified, help for that command is listed.
156
def get_parser(self):
158
Returns the options parser to use for the "revision" command.
160
:rtype: cmdutil.CmdOptionParser
162
if self.parser is not None:
164
parser=cmdutil.CmdOptionParser("fai help [command]")
165
parser.add_option("-n", "--native", action="store_true",
166
dest="native", help="Show native commands")
167
parser.add_option("-e", "--external", action="store_true",
168
dest="external", help="Show external commands")
169
parser.add_option("-s", "--suggest", action="store_true",
170
dest="suggestions", help="Show suggestions")
174
def list_commands(self, native=True, suggest=False, external=True):
176
Lists supported commands.
178
:param native: list native, python-based commands
180
:param external: list external aba-style commands
184
print "Native Fai commands"
189
for i in range(28-len(k)):
191
print space+k+" : "+commands[k]().description
194
print "Unavailable commands and suggested alternatives"
195
key_list = suggestions.keys()
198
print "%28s : %s" % (key, suggestions[key])
201
fake_aba = abacmds.AbaCmds()
202
if (fake_aba.abadir == ""):
204
print "External commands"
205
fake_aba.list_commands()
208
print "Use help --suggest to list alternatives to tla and aba"\
210
if options.tla_fallthrough and (native or external):
211
print "Fai also supports tla commands."
213
def command_help(cmd):
215
Prints help for a command.
217
:param cmd: The name of the command to print help for
220
fake_aba = abacmds.AbaCmds()
221
cmdobj = find_command(cmd)
224
elif suggestions.has_key(cmd):
225
print "Not available\n" + suggestions[cmd]
227
abacmd = fake_aba.is_command(cmd)
231
print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
235
class Changes(BaseCommand):
237
the "changes" command: lists differences between trees/revisions:
241
self.description="Lists what files have changed in the project tree"
243
def get_completer(self, arg, index):
247
tree = arch.tree_root()
250
return cmdutil.iter_revision_completions(arg, tree)
252
def parse_commandline(self, cmdline):
254
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
256
:param cmdline: A list of arguments to parse
257
:rtype: (options, Revision, Revision/WorkingTree)
259
parser=self.get_parser()
260
(options, args) = parser.parse_args(cmdline)
262
raise cmdutil.GetHelp
264
tree=arch.tree_root()
266
a_spec = ancillary.comp_revision(tree)
268
a_spec = cmdutil.determine_revision_tree(tree, args[0])
269
cmdutil.ensure_archive_registered(a_spec.archive)
271
b_spec = cmdutil.determine_revision_tree(tree, args[1])
272
cmdutil.ensure_archive_registered(b_spec.archive)
275
return options, a_spec, b_spec
277
def do_command(self, cmdargs):
279
Master function that perfoms the "changes" command.
282
options, a_spec, b_spec = self.parse_commandline(cmdargs);
283
except cmdutil.CantDetermineRevision, e:
286
except arch.errors.TreeRootError, e:
289
if options.changeset:
290
changeset=options.changeset
294
changeset=tmpdir+"/changeset"
296
delta=arch.iter_delta(a_spec, b_spec, changeset)
299
if cmdutil.chattermatch(line, "changeset:"):
302
cmdutil.colorize(line, options.suppress_chatter)
303
except arch.util.ExecProblem, e:
304
if e.proc.error and e.proc.error.startswith(
305
"missing explicit id for file"):
312
if (options.perform_diff):
313
chan = arch_compound.ChangesetMunger(changeset)
315
if options.diffopts is not None:
316
if isinstance(b_spec, arch.Revision):
317
b_dir = b_spec.library_find()
320
a_dir = a_spec.library_find()
321
diffopts = options.diffopts.split()
322
cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
324
cmdutil.show_diffs(delta.changeset)
326
if tmpdir and (os.access(tmpdir, os.X_OK)):
327
shutil.rmtree(tmpdir)
329
def get_parser(self):
331
Returns the options parser to use for the "changes" command.
333
:rtype: cmdutil.CmdOptionParser
335
parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
337
parser.add_option("-d", "--diff", action="store_true",
338
dest="perform_diff", default=False,
339
help="Show diffs in summary")
340
parser.add_option("-c", "--changeset", dest="changeset",
341
help="Store a changeset in the given directory",
343
parser.add_option("-s", "--silent", action="store_true",
344
dest="suppress_chatter", default=False,
345
help="Suppress chatter messages")
346
parser.add_option("--diffopts", dest="diffopts",
347
help="Use the specified diff options",
352
def help(self, parser=None):
354
Prints a help message.
356
:param parser: If supplied, the parser to use for generating help. If \
357
not supplied, it is retrieved.
358
:type parser: cmdutil.CmdOptionParser
361
parser=self.get_parser()
364
Performs source-tree comparisons
366
If no revision is specified, the current project tree is compared to the
367
last-committed revision. If one revision is specified, the current project
368
tree is compared to that revision. If two revisions are specified, they are
369
compared to each other.
375
class ApplyChanges(BaseCommand):
377
Apply differences between two revisions to a tree
381
self.description="Applies changes to a project tree"
383
def get_completer(self, arg, index):
387
tree = arch.tree_root()
390
return cmdutil.iter_revision_completions(arg, tree)
392
def parse_commandline(self, cmdline, tree):
394
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
396
:param cmdline: A list of arguments to parse
397
:rtype: (options, Revision, Revision/WorkingTree)
399
parser=self.get_parser()
400
(options, args) = parser.parse_args(cmdline)
402
raise cmdutil.GetHelp
404
a_spec = cmdutil.determine_revision_tree(tree, args[0])
405
cmdutil.ensure_archive_registered(a_spec.archive)
406
b_spec = cmdutil.determine_revision_tree(tree, args[1])
407
cmdutil.ensure_archive_registered(b_spec.archive)
408
return options, a_spec, b_spec
410
def do_command(self, cmdargs):
412
Master function that performs "apply-changes".
415
tree = arch.tree_root()
416
options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
417
except cmdutil.CantDetermineRevision, e:
420
except arch.errors.TreeRootError, e:
423
delta=cmdutil.apply_delta(a_spec, b_spec, tree)
424
for line in cmdutil.iter_apply_delta_filter(delta):
425
cmdutil.colorize(line, options.suppress_chatter)
427
def get_parser(self):
429
Returns the options parser to use for the "apply-changes" command.
431
:rtype: cmdutil.CmdOptionParser
433
parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
435
parser.add_option("-d", "--diff", action="store_true",
436
dest="perform_diff", default=False,
437
help="Show diffs in summary")
438
parser.add_option("-c", "--changeset", dest="changeset",
439
help="Store a changeset in the given directory",
441
parser.add_option("-s", "--silent", action="store_true",
442
dest="suppress_chatter", default=False,
443
help="Suppress chatter messages")
446
def help(self, parser=None):
448
Prints a help message.
450
:param parser: If supplied, the parser to use for generating help. If \
451
not supplied, it is retrieved.
452
:type parser: cmdutil.CmdOptionParser
455
parser=self.get_parser()
458
Applies changes to a project tree
460
Compares two revisions and applies the difference between them to the current
466
class Update(BaseCommand):
468
Updates a project tree to a given revision, preserving un-committed hanges.
472
self.description="Apply the latest changes to the current directory"
474
def get_completer(self, arg, index):
478
tree = arch.tree_root()
481
return cmdutil.iter_revision_completions(arg, tree)
483
def parse_commandline(self, cmdline, tree):
485
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
487
:param cmdline: A list of arguments to parse
488
:rtype: (options, Revision, Revision/WorkingTree)
490
parser=self.get_parser()
491
(options, args) = parser.parse_args(cmdline)
493
raise cmdutil.GetHelp
498
revision=cmdutil.determine_revision_arch(tree, spec)
499
cmdutil.ensure_archive_registered(revision.archive)
501
mirror_source = cmdutil.get_mirror_source(revision.archive)
502
if mirror_source != None:
503
if cmdutil.prompt("Mirror update"):
504
cmd=cmdutil.mirror_archive(mirror_source,
505
revision.archive, arch.NameParser(revision).get_package_version())
506
for line in arch.chatter_classifier(cmd):
507
cmdutil.colorize(line, options.suppress_chatter)
509
revision=cmdutil.determine_revision_arch(tree, spec)
511
return options, revision
513
def do_command(self, cmdargs):
515
Master function that perfoms the "update" command.
517
tree=arch.tree_root()
519
options, to_revision = self.parse_commandline(cmdargs, tree);
520
except cmdutil.CantDetermineRevision, e:
523
except arch.errors.TreeRootError, e:
526
from_revision = arch_compound.tree_latest(tree)
527
if from_revision==to_revision:
528
print "Tree is already up to date with:\n"+str(to_revision)+"."
530
cmdutil.ensure_archive_registered(from_revision.archive)
531
cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
532
options.patch_forward)
533
for line in cmdutil.iter_apply_delta_filter(cmd):
534
cmdutil.colorize(line)
535
if to_revision.version != tree.tree_version:
536
if cmdutil.prompt("Update version"):
537
tree.tree_version = to_revision.version
539
def get_parser(self):
541
Returns the options parser to use for the "update" command.
543
:rtype: cmdutil.CmdOptionParser
545
parser=cmdutil.CmdOptionParser("fai update [options]"
546
" [revision/version]")
547
parser.add_option("-f", "--forward", action="store_true",
548
dest="patch_forward", default=False,
549
help="pass the --forward option to 'patch'")
550
parser.add_option("-s", "--silent", action="store_true",
551
dest="suppress_chatter", default=False,
552
help="Suppress chatter messages")
555
def help(self, parser=None):
557
Prints a help message.
559
:param parser: If supplied, the parser to use for generating help. If \
560
not supplied, it is retrieved.
561
:type parser: cmdutil.CmdOptionParser
564
parser=self.get_parser()
567
Updates a working tree to the current archive revision
569
If a revision or version is specified, that is used instead
575
class Commit(BaseCommand):
577
Create a revision based on the changes in the current tree.
581
self.description="Write local changes to the archive"
583
def get_completer(self, arg, index):
586
return iter_modified_file_completions(arch.tree_root(), arg)
587
# return iter_source_file_completions(arch.tree_root(), arg)
589
def parse_commandline(self, cmdline, tree):
591
Parse commandline arguments. Raise cmtutil.GetHelp if help is needed.
593
:param cmdline: A list of arguments to parse
594
:rtype: (options, Revision, Revision/WorkingTree)
596
parser=self.get_parser()
597
(options, args) = parser.parse_args(cmdline)
601
if options.version is None:
602
return options, tree.tree_version, args
604
revision=cmdutil.determine_revision_arch(tree, options.version)
605
return options, revision.get_version(), args
607
def do_command(self, cmdargs):
609
Master function that perfoms the "commit" command.
611
tree=arch.tree_root()
612
options, version, files = self.parse_commandline(cmdargs, tree)
614
if options.__dict__.has_key("base") and options.base:
615
base = cmdutil.determine_revision_tree(tree, options.base)
618
base = ancillary.submit_revision(tree)
621
ancestor = arch_compound.tree_latest(tree, version)
624
archive=version.archive
625
source=cmdutil.get_mirror_source(archive)
627
writethrough="implicit"
630
if writethrough=="explicit" and \
631
cmdutil.prompt("Writethrough"):
632
writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
633
elif writethrough=="none":
634
raise CommitToMirror(archive)
636
elif archive.is_mirror:
637
raise CommitToMirror(archive)
640
last_revision=tree.iter_logs(version, True).next().revision
641
except StopIteration, e:
644
if cmdutil.prompt("Import from commit"):
645
return do_import(version)
647
raise NoVersionLogs(version)
649
arch_last_revision = version.iter_revisions(True).next()
650
except StopIteration, e:
651
arch_last_revision = None
653
if last_revision != arch_last_revision:
654
print "Tree is not up to date with %s" % str(version)
655
if not cmdutil.prompt("Out of date"):
661
if not cmdutil.has_changed(ancestor):
662
if not cmdutil.prompt("Empty commit"):
664
except arch.util.ExecProblem, e:
665
if e.proc.error and e.proc.error.startswith(
666
"missing explicit id for file"):
670
log = tree.log_message(create=False, version=version)
673
if cmdutil.prompt("Create log"):
674
edit_log(tree, version)
676
except cmdutil.NoEditorSpecified, e:
677
raise CommandFailed(e)
678
log = tree.log_message(create=False, version=version)
681
if log["Summary"] is None or len(log["Summary"].strip()) == 0:
682
if not cmdutil.prompt("Omit log summary"):
683
raise errors.NoLogSummary
685
for line in tree.iter_commit(version, seal=options.seal_version,
686
base=base, out_of_date_ok=allow_old, file_list=files):
687
cmdutil.colorize(line, options.suppress_chatter)
689
except arch.util.ExecProblem, e:
690
if e.proc.error and e.proc.error.startswith(
691
"These files violate naming conventions:"):
692
raise LintFailure(e.proc.error)
696
def get_parser(self):
698
Returns the options parser to use for the "commit" command.
700
:rtype: cmdutil.CmdOptionParser
703
parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
705
parser.add_option("--seal", action="store_true",
706
dest="seal_version", default=False,
707
help="seal this version")
708
parser.add_option("-v", "--version", dest="version",
709
help="Use the specified version",
711
parser.add_option("-s", "--silent", action="store_true",
712
dest="suppress_chatter", default=False,
713
help="Suppress chatter messages")
714
if cmdutil.supports_switch("commit", "--base"):
715
parser.add_option("--base", dest="base", help="",
719
def help(self, parser=None):
721
Prints a help message.
723
:param parser: If supplied, the parser to use for generating help. If \
724
not supplied, it is retrieved.
725
:type parser: cmdutil.CmdOptionParser
728
parser=self.get_parser()
731
Updates a working tree to the current archive revision
733
If a version is specified, that is used instead
740
class CatLog(BaseCommand):
742
Print the log of a given file (from current tree)
745
self.description="Prints the patch log for a revision"
747
def get_completer(self, arg, index):
751
tree = arch.tree_root()
754
return cmdutil.iter_revision_completions(arg, tree)
756
def do_command(self, cmdargs):
758
Master function that perfoms the "cat-log" command.
760
parser=self.get_parser()
761
(options, args) = parser.parse_args(cmdargs)
763
tree = arch.tree_root()
764
except arch.errors.TreeRootError, e:
770
raise cmdutil.GetHelp()
773
revision = cmdutil.determine_revision_tree(tree, spec)
775
revision = cmdutil.determine_revision_arch(tree, spec)
776
except cmdutil.CantDetermineRevision, e:
777
raise CommandFailedWrapper(e)
780
use_tree = (options.source == "tree" or \
781
(options.source == "any" and tree))
782
use_arch = (options.source == "archive" or options.source == "any")
786
for log in tree.iter_logs(revision.get_version()):
787
if log.revision == revision:
791
if log is None and use_arch:
792
cmdutil.ensure_revision_exists(revision)
793
log = arch.Patchlog(revision)
795
for item in log.items():
796
print "%s: %s" % item
797
print log.description
799
def get_parser(self):
801
Returns the options parser to use for the "cat-log" command.
803
:rtype: cmdutil.CmdOptionParser
805
parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
806
parser.add_option("--archive", action="store_const", dest="source",
807
const="archive", default="any",
808
help="Always get the log from the archive")
809
parser.add_option("--tree", action="store_const", dest="source",
810
const="tree", help="Always get the log from the tree")
813
def help(self, parser=None):
815
Prints a help message.
817
:param parser: If supplied, the parser to use for generating help. If \
818
not supplied, it is retrieved.
819
:type parser: cmdutil.CmdOptionParser
822
parser=self.get_parser()
825
Prints the log for the specified revision
830
class Revert(BaseCommand):
831
""" Reverts a tree (or aspects of it) to a revision
834
self.description="Reverts a tree (or aspects of it) to a revision "
836
def get_completer(self, arg, index):
840
tree = arch.tree_root()
843
return iter_modified_file_completions(tree, arg)
845
def do_command(self, cmdargs):
847
Master function that perfoms the "revert" command.
849
parser=self.get_parser()
850
(options, args) = parser.parse_args(cmdargs)
852
tree = arch.tree_root()
853
except arch.errors.TreeRootError, e:
854
raise CommandFailed(e)
856
if options.revision is not None:
857
spec=options.revision
860
revision = cmdutil.determine_revision_tree(tree, spec)
862
revision = ancillary.comp_revision(tree)
863
except cmdutil.CantDetermineRevision, e:
864
raise CommandFailedWrapper(e)
867
if options.file_contents or options.file_perms or options.deletions\
868
or options.additions or options.renames or options.hunk_prompt:
869
munger = arch_compound.MungeOpts()
870
munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
873
if len(args) > 0 or options.logs or options.pattern_files or \
876
munger = cmdutil.arch_compound.MungeOpts(True)
877
munger.all_types(True)
879
t_cwd = arch_compound.tree_cwd(tree)
883
name = "./" + t_cwd + name
884
munger.add_keep_file(name);
886
if options.file_perms:
887
munger.file_perms = True
888
if options.file_contents:
889
munger.file_contents = True
890
if options.deletions:
891
munger.deletions = True
892
if options.additions:
893
munger.additions = True
895
munger.renames = True
897
munger.add_keep_pattern('^\./\{arch\}/[^=].*')
899
munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
900
"/\.arch-inventory$")
901
if options.pattern_files:
902
munger.add_keep_pattern(options.pattern_files)
904
for line in arch_compound.revert(tree, revision, munger,
905
not options.no_output):
906
cmdutil.colorize(line)
909
def get_parser(self):
911
Returns the options parser to use for the "cat-log" command.
913
:rtype: cmdutil.CmdOptionParser
915
parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
916
parser.add_option("", "--contents", action="store_true",
917
dest="file_contents",
918
help="Revert file content changes")
919
parser.add_option("", "--permissions", action="store_true",
921
help="Revert file permissions changes")
922
parser.add_option("", "--deletions", action="store_true",
924
help="Restore deleted files")
925
parser.add_option("", "--additions", action="store_true",
927
help="Remove added files")
928
parser.add_option("", "--renames", action="store_true",
930
help="Revert file names")
931
parser.add_option("--hunks", action="store_true",
932
dest="hunk_prompt", default=False,
933
help="Prompt which hunks to revert")
934
parser.add_option("--pattern-files", dest="pattern_files",
935
help="Revert files that match this pattern",
937
parser.add_option("--logs", action="store_true",
938
dest="logs", default=False,
939
help="Revert only logs")
940
parser.add_option("--control-files", action="store_true",
941
dest="control", default=False,
942
help="Revert logs and other control files")
943
parser.add_option("-n", "--no-output", action="store_true",
945
help="Don't keep an undo changeset")
946
parser.add_option("--revision", dest="revision",
947
help="Revert to the specified revision",
951
def help(self, parser=None):
953
Prints a help message.
955
:param parser: If supplied, the parser to use for generating help. If \
956
not supplied, it is retrieved.
957
:type parser: cmdutil.CmdOptionParser
960
parser=self.get_parser()
963
Reverts changes in the current working tree. If no flags are specified, all
964
types of changes are reverted. Otherwise, only selected types of changes are
967
If a revision is specified on the commandline, differences between the current
968
tree and that revision are reverted. If a version is specified, the current
969
tree is used to determine the revision.
971
If files are specified, only those files listed will have any changes applied.
972
To specify a renamed file, you can use either the old or new name. (or both!)
974
Unless "-n" is specified, reversions can be undone with "redo".
978
class Revision(BaseCommand):
980
Print a revision name based on a revision specifier
983
self.description="Prints the name of a revision"
985
def get_completer(self, arg, index):
989
tree = arch.tree_root()
992
return cmdutil.iter_revision_completions(arg, tree)
994
def do_command(self, cmdargs):
996
Master function that perfoms the "revision" command.
998
parser=self.get_parser()
999
(options, args) = parser.parse_args(cmdargs)
1002
tree = arch.tree_root()
1003
except arch.errors.TreeRootError:
1010
raise cmdutil.GetHelp
1013
revision = cmdutil.determine_revision_tree(tree, spec)
1015
revision = cmdutil.determine_revision_arch(tree, spec)
1016
except cmdutil.CantDetermineRevision, e:
1019
print options.display(revision)
1021
def get_parser(self):
1023
Returns the options parser to use for the "revision" command.
1025
:rtype: cmdutil.CmdOptionParser
1027
parser=cmdutil.CmdOptionParser("fai revision [revision]")
1028
parser.add_option("", "--location", action="store_const",
1029
const=paths.determine_path, dest="display",
1030
help="Show location instead of name", default=str)
1031
parser.add_option("--import", action="store_const",
1032
const=paths.determine_import_path, dest="display",
1033
help="Show location of import file")
1034
parser.add_option("--log", action="store_const",
1035
const=paths.determine_log_path, dest="display",
1036
help="Show location of log file")
1037
parser.add_option("--patch", action="store_const",
1038
dest="display", const=paths.determine_patch_path,
1039
help="Show location of patchfile")
1040
parser.add_option("--continuation", action="store_const",
1041
const=paths.determine_continuation_path,
1043
help="Show location of continuation file")
1044
parser.add_option("--cacherev", action="store_const",
1045
const=paths.determine_cacherev_path, dest="display",
1046
help="Show location of cacherev file")
1049
def help(self, parser=None):
1051
Prints a help message.
1053
:param parser: If supplied, the parser to use for generating help. If \
1054
not supplied, it is retrieved.
1055
:type parser: cmdutil.CmdOptionParser
1058
parser=self.get_parser()
1061
Expands aliases and prints the name of the specified revision. Instead of
1062
the name, several options can be used to print locations. If more than one is
1063
specified, the last one is used.
1068
class Revisions(BaseCommand):
1070
Print a revision name based on a revision specifier
1073
self.description="Lists revisions"
1074
self.cl_revisions = []
1076
def do_command(self, cmdargs):
1078
Master function that perfoms the "revision" command.
1080
(options, args) = self.get_parser().parse_args(cmdargs)
1082
raise cmdutil.GetHelp
1084
self.tree = arch.tree_root()
1085
except arch.errors.TreeRootError:
1087
if options.type == "default":
1088
options.type = "archive"
1090
iter = cmdutil.revision_iterator(self.tree, options.type, args,
1091
options.reverse, options.modified,
1093
except cmdutil.CantDetermineRevision, e:
1094
raise CommandFailedWrapper(e)
1095
except cmdutil.CantDetermineVersion, e:
1096
raise CommandFailedWrapper(e)
1097
if options.skip is not None:
1098
iter = cmdutil.iter_skip(iter, int(options.skip))
1101
for revision in iter:
1103
if isinstance(revision, arch.Patchlog):
1105
revision=revision.revision
1106
out = options.display(revision)
1109
if log is None and (options.summary or options.creator or
1110
options.date or options.merges):
1111
log = revision.patchlog
1113
print " %s" % log.creator
1115
print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
1117
print " %s" % log.summary
1119
showed_title = False
1120
for revision in log.merged_patches:
1121
if not showed_title:
1124
print " %s" % revision
1125
if len(self.cl_revisions) > 0:
1126
print pylon.changelog_for_merge(self.cl_revisions)
1127
except pylon.errors.TreeRootNone:
1128
raise CommandFailedWrapper(
1129
Exception("This option can only be used in a project tree."))
1131
def changelog_append(self, revision):
1132
if isinstance(revision, arch.Revision):
1133
revision=arch.Patchlog(revision)
1134
self.cl_revisions.append(revision)
1136
def get_parser(self):
1138
Returns the options parser to use for the "revision" command.
1140
:rtype: cmdutil.CmdOptionParser
1142
parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
1143
select = cmdutil.OptionGroup(parser, "Selection options",
1144
"Control which revisions are listed. These options"
1145
" are mutually exclusive. If more than one is"
1146
" specified, the last is used.")
1148
cmdutil.add_revision_iter_options(select)
1149
parser.add_option("", "--skip", dest="skip",
1150
help="Skip revisions. Positive numbers skip from "
1151
"beginning, negative skip from end.",
1154
parser.add_option_group(select)
1156
format = cmdutil.OptionGroup(parser, "Revision format options",
1157
"These control the appearance of listed revisions")
1158
format.add_option("", "--location", action="store_const",
1159
const=paths.determine_path, dest="display",
1160
help="Show location instead of name", default=str)
1161
format.add_option("--import", action="store_const",
1162
const=paths.determine_import_path, dest="display",
1163
help="Show location of import file")
1164
format.add_option("--log", action="store_const",
1165
const=paths.determine_log_path, dest="display",
1166
help="Show location of log file")
1167
format.add_option("--patch", action="store_const",
1168
dest="display", const=paths.determine_patch_path,
1169
help="Show location of patchfile")
1170
format.add_option("--continuation", action="store_const",
1171
const=paths.determine_continuation_path,
1173
help="Show location of continuation file")
1174
format.add_option("--cacherev", action="store_const",
1175
const=paths.determine_cacherev_path, dest="display",
1176
help="Show location of cacherev file")
1177
format.add_option("--changelog", action="store_const",
1178
const=self.changelog_append, dest="display",
1179
help="Show location of cacherev file")
1180
parser.add_option_group(format)
1181
display = cmdutil.OptionGroup(parser, "Display format options",
1182
"These control the display of data")
1183
display.add_option("-r", "--reverse", action="store_true",
1184
dest="reverse", help="Sort from newest to oldest")
1185
display.add_option("-s", "--summary", action="store_true",
1186
dest="summary", help="Show patchlog summary")
1187
display.add_option("-D", "--date", action="store_true",
1188
dest="date", help="Show patchlog date")
1189
display.add_option("-c", "--creator", action="store_true",
1190
dest="creator", help="Show the id that committed the"
1192
display.add_option("-m", "--merges", action="store_true",
1193
dest="merges", help="Show the revisions that were"
1195
parser.add_option_group(display)
1197
def help(self, parser=None):
1198
"""Attempt to explain the revisions command
1200
:param parser: If supplied, used to determine options
1203
parser=self.get_parser()
1205
print """List revisions.
1210
class Get(BaseCommand):
1212
Retrieve a revision from the archive
1215
self.description="Retrieve a revision from the archive"
1216
self.parser=self.get_parser()
1219
def get_completer(self, arg, index):
1223
tree = arch.tree_root()
1226
return cmdutil.iter_revision_completions(arg, tree)
1229
def do_command(self, cmdargs):
1231
Master function that perfoms the "get" command.
1233
(options, args) = self.parser.parse_args(cmdargs)
1237
tree = arch.tree_root()
1238
except arch.errors.TreeRootError:
1243
revision, arch_loc = paths.full_path_decode(args[0])
1244
except Exception, e:
1245
revision = cmdutil.determine_revision_arch(tree, args[0],
1246
check_existence=False, allow_package=True)
1250
directory = str(revision.nonarch)
1251
if os.path.exists(directory):
1252
raise DirectoryExists(directory)
1253
cmdutil.ensure_archive_registered(revision.archive, arch_loc)
1255
cmdutil.ensure_revision_exists(revision)
1256
except cmdutil.NoSuchRevision, e:
1257
raise CommandFailedWrapper(e)
1259
link = cmdutil.prompt ("get link")
1260
for line in cmdutil.iter_get(revision, directory, link,
1261
options.no_pristine,
1262
options.no_greedy_add):
1263
cmdutil.colorize(line)
1265
def get_parser(self):
1267
Returns the options parser to use for the "get" command.
1269
:rtype: cmdutil.CmdOptionParser
1271
parser=cmdutil.CmdOptionParser("fai get revision [dir]")
1272
parser.add_option("--no-pristine", action="store_true",
1274
help="Do not make pristine copy for reference")
1275
parser.add_option("--no-greedy-add", action="store_true",
1276
dest="no_greedy_add",
1277
help="Never add to greedy libraries")
1281
def help(self, parser=None):
1283
Prints a help message.
1285
:param parser: If supplied, the parser to use for generating help. If \
1286
not supplied, it is retrieved.
1287
:type parser: cmdutil.CmdOptionParser
1290
parser=self.get_parser()
1293
Expands aliases and constructs a project tree for a revision. If the optional
1294
"dir" argument is provided, the project tree will be stored in this directory.
1299
class PromptCmd(cmd.Cmd):
1301
cmd.Cmd.__init__(self)
1302
self.prompt = "Fai> "
1304
self.tree = arch.tree_root()
1309
self.fake_aba = abacmds.AbaCmds()
1310
self.identchars += '-'
1311
self.history_file = os.path.expanduser("~/.fai-history")
1312
readline.set_completer_delims(string.whitespace)
1313
if os.access(self.history_file, os.R_OK) and \
1314
os.path.isfile(self.history_file):
1315
readline.read_history_file(self.history_file)
1316
self.cwd = os.getcwd()
1318
def write_history(self):
1319
readline.write_history_file(self.history_file)
1321
def do_quit(self, args):
1322
self.write_history()
1325
def do_exit(self, args):
1328
def do_EOF(self, args):
1332
def postcmd(self, line, bar):
1336
def set_prompt(self):
1337
if self.tree is not None:
1339
prompt = pylon.alias_or_version(self.tree.tree_version,
1342
if prompt is not None:
1343
prompt = " " + prompt
1348
self.prompt = "Fai%s> " % prompt
1350
def set_title(self, command=None):
1352
version = pylon.alias_or_version(self.tree.tree_version, self.tree,
1355
version = "[no version]"
1358
sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
1360
def do_cd(self, line):
1363
line = os.path.expanduser(line)
1364
if os.path.isabs(line):
1367
newcwd = self.cwd+'/'+line
1368
newcwd = os.path.normpath(newcwd)
1372
except Exception, e:
1375
self.tree = arch.tree_root()
1379
def do_help(self, line):
1382
def default(self, line):
1384
if find_command(args[0]):
1386
find_command(args[0]).do_command(args[1:])
1387
except cmdutil.BadCommandOption, e:
1389
except cmdutil.GetHelp, e:
1390
find_command(args[0]).help()
1391
except CommandFailed, e:
1393
except arch.errors.ArchiveNotRegistered, e:
1395
except KeyboardInterrupt, e:
1397
except arch.util.ExecProblem, e:
1398
print e.proc.error.rstrip('\n')
1399
except cmdutil.CantDetermineVersion, e:
1401
except cmdutil.CantDetermineRevision, e:
1403
except Exception, e:
1404
print "Unhandled error:\n%s" % errors.exception_str(e)
1406
elif suggestions.has_key(args[0]):
1407
print suggestions[args[0]]
1409
elif self.fake_aba.is_command(args[0]):
1412
tree = arch.tree_root()
1413
except arch.errors.TreeRootError:
1415
cmd = self.fake_aba.is_command(args[0])
1417
cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
1418
except KeyboardInterrupt, e:
1421
elif options.tla_fallthrough and args[0] != "rm" and \
1422
cmdutil.is_tla_command(args[0]):
1426
tree = arch.tree_root()
1427
except arch.errors.TreeRootError:
1429
args = cmdutil.expand_prefix_alias(args, tree)
1430
arch.util.exec_safe('tla', args, stderr=sys.stderr,
1432
except arch.util.ExecProblem, e:
1434
except KeyboardInterrupt, e:
1439
tree = arch.tree_root()
1440
except arch.errors.TreeRootError:
1443
os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
1444
except KeyboardInterrupt, e:
1447
def completenames(self, text, line, begidx, endidx):
1449
iter = iter_command_names(self.fake_aba)
1452
arg = line.split()[-1]
1455
iter = cmdutil.iter_munged_completions(iter, arg, text)
1456
except Exception, e:
1460
def completedefault(self, text, line, begidx, endidx):
1461
"""Perform completion for native commands.
1463
:param text: The text to complete
1465
:param line: The entire line to complete
1467
:param begidx: The start of the text in the line
1469
:param endidx: The end of the text in the line
1473
(cmd, args, foo) = self.parseline(line)
1474
command_obj=find_command(cmd)
1475
if command_obj is not None:
1476
return command_obj.complete(args.split(), text)
1477
elif not self.fake_aba.is_command(cmd) and \
1478
cmdutil.is_tla_command(cmd):
1479
iter = cmdutil.iter_supported_switches(cmd)
1481
arg = args.split()[-1]
1484
if arg.startswith("-"):
1485
return list(cmdutil.iter_munged_completions(iter, arg,
1488
return list(cmdutil.iter_munged_completions(
1489
cmdutil.iter_file_completions(arg), arg, text))
1494
arg = args.split()[-1]
1497
iter = cmdutil.iter_dir_completions(arg)
1498
iter = cmdutil.iter_munged_completions(iter, arg, text)
1501
arg = args.split()[-1]
1502
iter = cmdutil.iter_file_completions(arg)
1503
return list(cmdutil.iter_munged_completions(iter, arg, text))
1505
return self.completenames(text, line, begidx, endidx)
1506
except Exception, e:
1510
def iter_command_names(fake_aba):
1511
for entry in cmdutil.iter_combine([commands.iterkeys(),
1512
fake_aba.get_commands(),
1513
cmdutil.iter_tla_commands(False)]):
1514
if not suggestions.has_key(str(entry)):
1518
def iter_source_file_completions(tree, arg):
1519
treepath = arch_compound.tree_cwd(tree)
1520
if len(treepath) > 0:
1524
for file in tree.iter_inventory(dirs, source=True, both=True):
1525
file = file_completion_match(file, treepath, arg)
1526
if file is not None:
1530
def iter_untagged(tree, dirs):
1531
for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False,
1532
categories=arch_core.non_root,
1533
control_files=True):
1537
def iter_untagged_completions(tree, arg):
1538
"""Generate an iterator for all visible untagged files that match arg.
1540
:param tree: The tree to look for untagged files in
1541
:type tree: `arch.WorkingTree`
1542
:param arg: The argument to match
1544
:return: An iterator of all matching untagged files
1545
:rtype: iterator of str
1547
treepath = arch_compound.tree_cwd(tree)
1548
if len(treepath) > 0:
1553
for file in iter_untagged(tree, dirs):
1554
file = file_completion_match(file, treepath, arg)
1555
if file is not None:
1559
def file_completion_match(file, treepath, arg):
1560
"""Determines whether a file within an arch tree matches the argument.
1562
:param file: The rooted filename
1564
:param treepath: The path to the cwd within the tree
1566
:param arg: The prefix to match
1567
:return: The completion name, or None if not a match
1570
if not file.startswith(treepath):
1573
file = file[len(treepath)+1:]
1575
if not file.startswith(arg):
1577
if os.path.isdir(file):
1581
def iter_modified_file_completions(tree, arg):
1582
"""Returns a list of modified files that match the specified prefix.
1584
:param tree: The current tree
1585
:type tree: `arch.WorkingTree`
1586
:param arg: The prefix to match
1589
treepath = arch_compound.tree_cwd(tree)
1590
tmpdir = util.tmpdir()
1591
changeset = tmpdir+"/changeset"
1593
revision = cmdutil.determine_revision_tree(tree)
1594
for line in arch.iter_delta(revision, tree, changeset):
1595
if isinstance(line, arch.FileModification):
1596
file = file_completion_match(line.name[1:], treepath, arg)
1597
if file is not None:
1598
completions.append(file)
1599
shutil.rmtree(tmpdir)
1602
class Shell(BaseCommand):
1604
self.description = "Runs Fai as a shell"
1606
def do_command(self, cmdargs):
1608
raise cmdutil.GetHelp
1609
prompt = PromptCmd()
1613
prompt.write_history()
1615
class AddID(BaseCommand):
1617
Adds an inventory id for the given file
1620
self.description="Add an inventory id for a given file"
1622
def get_completer(self, arg, index):
1623
tree = arch.tree_root()
1624
return iter_untagged_completions(tree, arg)
1626
def do_command(self, cmdargs):
1628
Master function that perfoms the "revision" command.
1630
parser=self.get_parser()
1631
(options, args) = parser.parse_args(cmdargs)
1634
tree = arch.tree_root()
1635
except arch.errors.TreeRootError, e:
1636
raise pylon.errors.CommandFailedWrapper(e)
1639
if (len(args) == 0) == (options.untagged == False):
1640
raise cmdutil.GetHelp
1642
#if options.id and len(args) != 1:
1643
# print "If --id is specified, only one file can be named."
1646
method = tree.tagging_method
1648
if options.id_type == "tagline":
1649
if method != "tagline":
1650
if not cmdutil.prompt("Tagline in other tree"):
1651
if method == "explicit" or method == "implicit":
1652
options.id_type == method
1654
print "add-id not supported for \"%s\" tagging method"\
1658
elif options.id_type == "implicit":
1659
if method != "implicit":
1660
if not cmdutil.prompt("Implicit in other tree"):
1661
if method == "explicit" or method == "tagline":
1662
options.id_type == method
1664
print "add-id not supported for \"%s\" tagging method"\
1667
elif options.id_type == "explicit":
1668
if method != "tagline" and method != explicit:
1669
if not prompt("Explicit in other tree"):
1670
print "add-id not supported for \"%s\" tagging method" % \
1674
if options.id_type == "auto":
1675
if method != "tagline" and method != "explicit" \
1676
and method !="implicit":
1677
print "add-id not supported for \"%s\" tagging method" % method
1680
options.id_type = method
1681
if options.untagged:
1683
self.add_ids(tree, options.id_type, args)
1685
def add_ids(self, tree, id_type, files=()):
1686
"""Add inventory ids to files.
1688
:param tree: the tree the files are in
1689
:type tree: `arch.WorkingTree`
1690
:param id_type: the type of id to add: "explicit" or "tagline"
1692
:param files: The list of files to add. If None do all untagged.
1693
:type files: tuple of str
1696
untagged = (files is None)
1698
files = list(iter_untagged(tree, None))
1700
while len(files) > 0:
1701
previous_files.extend(files)
1702
if id_type == "explicit":
1703
cmdutil.add_id(files)
1704
elif id_type == "tagline" or id_type == "implicit":
1707
implicit = (id_type == "implicit")
1708
cmdutil.add_tagline_or_explicit_id(file, False,
1710
except cmdutil.AlreadyTagged:
1711
print "\"%s\" already has a tagline." % file
1712
except cmdutil.NoCommentSyntax:
1714
#do inventory after tagging until no untagged files are encountered
1717
for file in iter_untagged(tree, None):
1718
if not file in previous_files:
1724
def get_parser(self):
1726
Returns the options parser to use for the "revision" command.
1728
:rtype: cmdutil.CmdOptionParser
1730
parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
1731
# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
1732
# parser.add_option("-i", "--id", dest="id",
1733
# help="Specify id for a single file", default=None)
1734
parser.add_option("--tltl", action="store_true",
1735
dest="lord_style", help="Use Tom Lord's style of id.")
1736
parser.add_option("--explicit", action="store_const",
1737
const="explicit", dest="id_type",
1738
help="Use an explicit id", default="auto")
1739
parser.add_option("--tagline", action="store_const",
1740
const="tagline", dest="id_type",
1741
help="Use a tagline id")
1742
parser.add_option("--implicit", action="store_const",
1743
const="implicit", dest="id_type",
1744
help="Use an implicit id (deprecated)")
1745
parser.add_option("--untagged", action="store_true",
1746
dest="untagged", default=False,
1747
help="tag all untagged files")
1750
def help(self, parser=None):
1752
Prints a help message.
1754
:param parser: If supplied, the parser to use for generating help. If \
1755
not supplied, it is retrieved.
1756
:type parser: cmdutil.CmdOptionParser
1759
parser=self.get_parser()
1762
Adds an inventory to the specified file(s) and directories. If --untagged is
1763
specified, adds inventory to all untagged files and directories.
1768
class Merge(BaseCommand):
1770
Merges changes from other versions into the current tree
1773
self.description="Merges changes from other versions"
1775
self.tree = arch.tree_root()
1780
def get_completer(self, arg, index):
1781
if self.tree is None:
1782
raise arch.errors.TreeRootError
1783
return cmdutil.merge_completions(self.tree, arg, index)
1785
def do_command(self, cmdargs):
1787
Master function that perfoms the "merge" command.
1789
parser=self.get_parser()
1790
(options, args) = parser.parse_args(cmdargs)
1794
action = options.action
1796
if self.tree is None:
1797
raise arch.errors.TreeRootError(os.getcwd())
1798
if cmdutil.has_changed(ancillary.comp_revision(self.tree)):
1799
raise UncommittedChanges(self.tree)
1804
revisions.append(cmdutil.determine_revision_arch(self.tree,
1806
source = "from commandline"
1808
revisions = ancillary.iter_partner_revisions(self.tree,
1809
self.tree.tree_version)
1810
source = "from partner version"
1811
revisions = misc.rewind_iterator(revisions)
1815
except StopIteration, e:
1816
revision = cmdutil.tag_cur(self.tree)
1817
if revision is None:
1818
raise CantDetermineRevision("", "No version specified, no "
1819
"partner-versions, and no tag"
1821
revisions = [revision]
1822
source = "from tag source"
1823
for revision in revisions:
1824
cmdutil.ensure_archive_registered(revision.archive)
1825
cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
1826
(revision, source)))
1827
if action=="native-merge" or action=="update":
1828
if self.native_merge(revision, action) == 0:
1830
elif action=="star-merge":
1832
self.star_merge(revision, options.diff3)
1833
except errors.MergeProblem, e:
1835
if cmdutil.has_changed(self.tree.tree_version):
1838
def star_merge(self, revision, diff3):
1839
"""Perform a star-merge on the current tree.
1841
:param revision: The revision to use for the merge
1842
:type revision: `arch.Revision`
1843
:param diff3: If true, do a diff3 merge
1847
for line in self.tree.iter_star_merge(revision, diff3=diff3):
1848
cmdutil.colorize(line)
1849
except arch.util.ExecProblem, e:
1850
if e.proc.status is not None and e.proc.status == 1:
1857
def native_merge(self, other_revision, action):
1858
"""Perform a native-merge on the current tree.
1860
:param other_revision: The revision to use for the merge
1861
:type other_revision: `arch.Revision`
1862
:return: 0 if the merge was skipped, 1 if it was applied
1864
other_tree = arch_compound.find_or_make_local_revision(other_revision)
1866
if action == "native-merge":
1867
ancestor = arch_compound.merge_ancestor2(self.tree, other_tree,
1869
elif action == "update":
1870
ancestor = arch_compound.tree_latest(self.tree,
1871
other_revision.version)
1872
except CantDetermineRevision, e:
1873
raise CommandFailedWrapper(e)
1874
cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
1875
if (ancestor == other_revision):
1876
cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
1879
delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
1880
for line in cmdutil.iter_apply_delta_filter(delta):
1881
cmdutil.colorize(line)
1886
def get_parser(self):
1888
Returns the options parser to use for the "merge" command.
1890
:rtype: cmdutil.CmdOptionParser
1892
parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
1893
parser.add_option("-s", "--star-merge", action="store_const",
1894
dest="action", help="Use star-merge",
1895
const="star-merge", default="native-merge")
1896
parser.add_option("--update", action="store_const",
1897
dest="action", help="Use update picker",
1899
parser.add_option("--diff3", action="store_true",
1901
help="Use diff3 for merge (implies star-merge)")
1904
def help(self, parser=None):
1906
Prints a help message.
1908
:param parser: If supplied, the parser to use for generating help. If \
1909
not supplied, it is retrieved.
1910
:type parser: cmdutil.CmdOptionParser
1913
parser=self.get_parser()
1916
Performs a merge operation using the specified version.
1920
class ELog(BaseCommand):
1922
Produces a raw patchlog and invokes the user's editor
1925
self.description="Edit a patchlog to commit"
1927
self.tree = arch.tree_root()
1932
def do_command(self, cmdargs):
1934
Master function that perfoms the "elog" command.
1936
parser=self.get_parser()
1937
(options, args) = parser.parse_args(cmdargs)
1938
if self.tree is None:
1939
raise arch.errors.TreeRootError
1942
edit_log(self.tree, self.tree.tree_version)
1943
except pylon.errors.NoEditorSpecified, e:
1944
raise pylon.errors.CommandFailedWrapper(e)
1946
def get_parser(self):
1948
Returns the options parser to use for the "merge" command.
1950
:rtype: cmdutil.CmdOptionParser
1952
parser=cmdutil.CmdOptionParser("fai elog")
1956
def help(self, parser=None):
1958
Invokes $EDITOR to produce a log for committing.
1960
:param parser: If supplied, the parser to use for generating help. If \
1961
not supplied, it is retrieved.
1962
:type parser: cmdutil.CmdOptionParser
1965
parser=self.get_parser()
1968
Invokes $EDITOR to produce a log for committing.
1972
def edit_log(tree, version):
1973
"""Makes and edits the log for a tree. Does all kinds of fancy things
1974
like log templates and merge summaries and log-for-merge
1976
:param tree: The tree to edit the log for
1977
:type tree: `arch.WorkingTree`
1979
#ensure we have an editor before preparing the log
1980
cmdutil.find_editor()
1981
log = tree.log_message(create=False, version=version)
1983
if log is None or cmdutil.prompt("Overwrite log"):
1986
log = tree.log_message(create=True, version=version)
1989
template = pylon.log_template_path(tree)
1991
shutil.copyfile(template, tmplog)
1992
comp_version = ancillary.comp_revision(tree).version
1993
new_merges = cmdutil.iter_new_merges(tree, comp_version)
1994
new_merges = cmdutil.direct_merges(new_merges)
1995
log["Summary"] = pylon.merge_summary(new_merges,
1997
if len(new_merges) > 0:
1998
if cmdutil.prompt("Log for merge"):
1999
if cmdutil.prompt("changelog for merge"):
2000
mergestuff = "Patches applied:\n"
2001
mergestuff += pylon.changelog_for_merge(new_merges)
2003
mergestuff = cmdutil.log_for_merge(tree, comp_version)
2004
log.description += mergestuff
2007
cmdutil.invoke_editor(log.name)
2014
class MirrorArchive(BaseCommand):
2016
Updates a mirror from an archive
2019
self.description="Update a mirror from an archive"
2021
def do_command(self, cmdargs):
2023
Master function that perfoms the "revision" command.
2026
parser=self.get_parser()
2027
(options, args) = parser.parse_args(cmdargs)
2031
tree = arch.tree_root()
2036
if tree is not None:
2037
name = tree.tree_version()
2039
name = cmdutil.expand_alias(args[0], tree)
2040
name = arch.NameParser(name)
2042
to_arch = name.get_archive()
2043
from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
2044
limit = name.get_nonarch()
2046
iter = arch_core.mirror_archive(from_arch,to_arch, limit)
2047
for line in arch.chatter_classifier(iter):
2048
cmdutil.colorize(line)
2050
def get_parser(self):
2052
Returns the options parser to use for the "revision" command.
2054
:rtype: cmdutil.CmdOptionParser
2056
parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
2059
def help(self, parser=None):
2061
Prints a help message.
2063
:param parser: If supplied, the parser to use for generating help. If \
2064
not supplied, it is retrieved.
2065
:type parser: cmdutil.CmdOptionParser
2068
parser=self.get_parser()
2071
Updates a mirror from an archive. If a branch, package, or version is
2072
supplied, only changes under it are mirrored.
2076
def help_tree_spec():
2077
print """Specifying revisions (default: tree)
2078
Revisions may be specified by alias, revision, version or patchlevel.
2079
Revisions or versions may be fully qualified. Unqualified revisions, versions,
2080
or patchlevels use the archive of the current project tree. Versions will
2081
use the latest patchlevel in the tree. Patchlevels will use the current tree-
2084
Use "alias" to list available (user and automatic) aliases."""
2088
"The latest revision in the archive of the tree-version. You can specify \
2089
a different version like so: acur:foo--bar--0 (aliases can be used)",
2091
"""(tree current) The latest revision in the tree of the tree-version. \
2092
You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
2095
"""(tree previous) The previous revision in the tree of the tree-version. To \
2096
specify an older revision, use a number, e.g. "tprev:4" """,
2098
"""(tree ancestor) The ancestor revision of the tree To specify an older \
2099
revision, use a number, e.g. "tanc:4".""",
2101
"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
2103
""" (tree modified) The latest revision to modify a given file, e.g. \
2104
"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
2106
"""(tree tag) The revision that was tagged into the current tree revision, \
2107
according to the tree""",
2109
"""(tag current) The latest revision of the version that the current tree \
2110
was tagged from.""",
2112
"""The common ancestor of the current tree and the specified revision. \
2113
Defaults to the first partner-version's latest revision or to tagcur.""",
2117
def is_auto_alias(name):
2118
"""Determine whether a name is an auto alias name
2120
:param name: the name to check
2122
:return: True if the name is an auto alias, false if not
2125
return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
2128
def display_def(iter, wrap = 80):
2129
"""Display a list of definitions
2131
:param iter: iter of name, definition pairs
2132
:type iter: iter of (str, str)
2133
:param wrap: The width for text wrapping
2138
for (key, value) in vals:
2139
if len(key) > maxlen:
2141
for (key, value) in vals:
2142
tw=textwrap.TextWrapper(width=wrap,
2143
initial_indent=key.rjust(maxlen)+" : ",
2144
subsequent_indent="".rjust(maxlen+3))
2145
print tw.fill(value)
2148
def help_aliases(tree):
2149
print """Auto-generated aliases"""
2150
display_def(pylon.util.iter_pairs(auto_alias))
2151
print "User aliases"
2152
display_def(ancillary.iter_all_alias(tree))
2154
class Inventory(BaseCommand):
2155
"""List the status of files in the tree"""
2157
self.description=self.__doc__
2159
def do_command(self, cmdargs):
2161
Master function that perfoms the "revision" command.
2164
parser=self.get_parser()
2165
(options, args) = parser.parse_args(cmdargs)
2166
tree = arch.tree_root()
2169
if (options.source):
2170
categories.append(arch_core.SourceFile)
2171
if (options.precious):
2172
categories.append(arch_core.PreciousFile)
2173
if (options.backup):
2174
categories.append(arch_core.BackupFile)
2176
categories.append(arch_core.JunkFile)
2178
if len(categories) == 1:
2179
show_leading = False
2183
if len(categories) == 0:
2186
if options.untagged:
2187
categories = arch_core.non_root
2188
show_leading = False
2193
for file in arch_core.iter_inventory_filter(tree, None,
2194
control_files=options.control_files,
2195
categories = categories, tagged=tagged):
2196
print arch_core.file_line(file,
2197
category = show_leading,
2198
untagged = show_leading,
2201
def get_parser(self):
2203
Returns the options parser to use for the "revision" command.
2205
:rtype: cmdutil.CmdOptionParser
2207
parser=cmdutil.CmdOptionParser("fai inventory [options]")
2208
parser.add_option("--ids", action="store_true", dest="ids",
2209
help="Show file ids")
2210
parser.add_option("--control", action="store_true",
2211
dest="control_files", help="include control files")
2212
parser.add_option("--source", action="store_true", dest="source",
2213
help="List source files")
2214
parser.add_option("--backup", action="store_true", dest="backup",
2215
help="List backup files")
2216
parser.add_option("--precious", action="store_true", dest="precious",
2217
help="List precious files")
2218
parser.add_option("--junk", action="store_true", dest="junk",
2219
help="List junk files")
2220
parser.add_option("--unrecognized", action="store_true",
2221
dest="unrecognized", help="List unrecognized files")
2222
parser.add_option("--untagged", action="store_true",
2223
dest="untagged", help="List only untagged files")
2226
def help(self, parser=None):
2228
Prints a help message.
2230
:param parser: If supplied, the parser to use for generating help. If \
2231
not supplied, it is retrieved.
2232
:type parser: cmdutil.CmdOptionParser
2235
parser=self.get_parser()
2238
Lists the status of files in the archive:
2246
Leading letter are not displayed if only one kind of file is shown
2251
class Alias(BaseCommand):
2252
"""List or adjust aliases"""
2254
self.description=self.__doc__
2256
def get_completer(self, arg, index):
2260
self.tree = arch.tree_root()
2265
return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
2267
return cmdutil.iter_revision_completions(arg, self.tree)
2270
def do_command(self, cmdargs):
2272
Master function that perfoms the "revision" command.
2275
parser=self.get_parser()
2276
(options, args) = parser.parse_args(cmdargs)
2278
self.tree = arch.tree_root()
2284
options.action(args, options)
2285
except cmdutil.ForbiddenAliasSyntax, e:
2286
raise CommandFailedWrapper(e)
2288
def no_prefix(self, alias):
2289
if alias.startswith("^"):
2293
def arg_dispatch(self, args, options):
2294
"""Add, modify, or list aliases, depending on number of arguments
2296
:param args: The list of commandline arguments
2297
:type args: list of str
2298
:param options: The commandline options
2301
help_aliases(self.tree)
2304
alias = self.no_prefix(args[0])
2306
self.print_alias(alias)
2307
elif (len(args)) == 2:
2308
self.add(alias, args[1], options)
2310
raise cmdutil.GetHelp
2312
def print_alias(self, alias):
2314
if is_auto_alias(alias):
2315
raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
2316
" Use \"revision\" to expand auto aliases." % alias)
2317
for pair in ancillary.iter_all_alias(self.tree):
2318
if pair[0] == alias:
2320
if answer is not None:
2323
print "The alias %s is not assigned." % alias
2325
def add(self, alias, expansion, options):
2326
"""Add or modify aliases
2328
:param alias: The alias name to create/modify
2330
:param expansion: The expansion to assign to the alias name
2331
:type expansion: str
2332
:param options: The commandline options
2334
if is_auto_alias(alias):
2335
raise IsAutoAlias(alias)
2338
new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
2340
ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
2342
for pair in self.get_iterator(options):
2343
if pair[0] != alias:
2344
newlist+="%s=%s\n" % (pair[0], pair[1])
2350
self.write_aliases(newlist, options)
2352
def delete(self, args, options):
2353
"""Delete the specified alias
2355
:param args: The list of arguments
2356
:type args: list of str
2357
:param options: The commandline options
2361
raise cmdutil.GetHelp
2362
alias = self.no_prefix(args[0])
2363
if is_auto_alias(alias):
2364
raise IsAutoAlias(alias)
2366
for pair in self.get_iterator(options):
2367
if pair[0] != alias:
2368
newlist+="%s=%s\n" % (pair[0], pair[1])
2372
raise errors.NoSuchAlias(alias)
2373
self.write_aliases(newlist, options)
2375
def get_alias_file(self, options):
2376
"""Return the name of the alias file to use
2378
:param options: The commandline options
2381
if self.tree is None:
2382
self.tree == arch.tree_root()
2383
return str(self.tree)+"/{arch}/+aliases"
2385
return "~/.aba/aliases"
2387
def get_iterator(self, options):
2388
"""Return the alias iterator to use
2390
:param options: The commandline options
2392
return ancillary.iter_alias(self.get_alias_file(options))
2394
def write_aliases(self, newlist, options):
2395
"""Safely rewrite the alias file
2396
:param newlist: The new list of aliases
2398
:param options: The commandline options
2400
filename = os.path.expanduser(self.get_alias_file(options))
2401
file = util.NewFileVersion(filename)
2406
def get_parser(self):
2408
Returns the options parser to use for the "alias" command.
2410
:rtype: cmdutil.CmdOptionParser
2412
parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
2413
parser.add_option("-d", "--delete", action="store_const", dest="action",
2414
const=self.delete, default=self.arg_dispatch,
2415
help="Delete an alias")
2416
parser.add_option("--tree", action="store_true", dest="tree",
2417
help="Create a per-tree alias", default=False)
2420
def help(self, parser=None):
2422
Prints a help message.
2424
:param parser: If supplied, the parser to use for generating help. If \
2425
not supplied, it is retrieved.
2426
:type parser: cmdutil.CmdOptionParser
2429
parser=self.get_parser()
2432
Lists current aliases or modifies the list of aliases.
2434
If no arguments are supplied, aliases will be listed. If two arguments are
2435
supplied, the specified alias will be created or modified. If -d or --delete
2436
is supplied, the specified alias will be deleted.
2438
You can create aliases that refer to any fully-qualified part of the
2439
Arch namespace, e.g.
2442
archive/category--branch,
2443
archive/category--branch--version (my favourite)
2444
archive/category--branch--version--patchlevel
2446
Aliases can be used automatically by native commands. To use them
2447
with external or tla commands, prefix them with ^ (you can do this
2448
with native commands, too).
2452
class RequestMerge(BaseCommand):
2453
"""Submit a merge request to Bug Goo"""
2455
self.description=self.__doc__
2457
def do_command(self, cmdargs):
2458
"""Submit a merge request
2460
:param cmdargs: The commandline arguments
2461
:type cmdargs: list of str
2463
parser = self.get_parser()
2464
(options, args) = parser.parse_args(cmdargs)
2466
cmdutil.find_editor()
2467
except pylon.errors.NoEditorSpecified, e:
2468
raise pylon.errors.CommandFailedWrapper(e)
2470
self.tree=arch.tree_root()
2473
base, revisions = self.revision_specs(args)
2474
message = self.make_headers(base, revisions)
2475
message += self.make_summary(revisions)
2476
path = self.edit_message(message)
2477
message = self.tidy_message(path)
2478
if cmdutil.prompt("Send merge"):
2479
self.send_message(message)
2480
print "Merge request sent"
2482
def make_headers(self, base, revisions):
2483
"""Produce email and Bug Goo header strings
2485
:param base: The base revision to apply merges to
2486
:type base: `arch.Revision`
2487
:param revisions: The revisions to replay into the base
2488
:type revisions: list of `arch.Patchlog`
2489
:return: The headers
2492
headers = "To: gnu-arch-users@gnu.org\n"
2493
headers += "From: %s\n" % options.fromaddr
2494
if len(revisions) == 1:
2495
headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
2497
headers += "Subject: [MERGE REQUEST]\n"
2499
headers += "Base-Revision: %s\n" % base
2500
for revision in revisions:
2501
headers += "Revision: %s\n" % revision.revision
2502
headers += "Bug: \n\n"
2505
def make_summary(self, logs):
2506
"""Generate a summary of merges
2508
:param logs: the patchlogs that were directly added by the merges
2509
:type logs: list of `arch.Patchlog`
2510
:return: the summary
2515
summary+=str(log.revision)+"\n"
2516
summary+=log.summary+"\n"
2517
if log.description.strip():
2518
summary+=log.description.strip('\n')+"\n\n"
2521
def revision_specs(self, args):
2522
"""Determine the base and merge revisions from tree and arguments.
2524
:param args: The parsed arguments
2525
:type args: list of str
2526
:return: The base revision and merge revisions
2527
:rtype: `arch.Revision`, list of `arch.Patchlog`
2530
target_revision = cmdutil.determine_revision_arch(self.tree,
2533
target_revision = arch_compound.tree_latest(self.tree)
2535
merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
2536
self.tree, f)) for f in args[1:] ]
2538
if self.tree is None:
2539
raise CantDetermineRevision("", "Not in a project tree")
2540
merge_iter = cmdutil.iter_new_merges(self.tree,
2541
target_revision.version,
2543
merges = [f for f in cmdutil.direct_merges(merge_iter)]
2544
return (target_revision, merges)
2546
def edit_message(self, message):
2547
"""Edit an email message in the user's standard editor
2549
:param message: The message to edit
2551
:return: the path of the edited message
2554
if self.tree is None:
2558
path += "/,merge-request"
2559
file = open(path, 'w')
2562
cmdutil.invoke_editor(path)
2565
def tidy_message(self, path):
2566
"""Validate and clean up message.
2568
:param path: The path to the message to clean up
2570
:return: The parsed message
2571
:rtype: `email.Message`
2573
mail = email.message_from_file(open(path))
2574
if mail["Subject"].strip() == "[MERGE REQUEST]":
2577
request = email.message_from_string(mail.get_payload())
2578
if request.has_key("Bug"):
2579
if request["Bug"].strip()=="":
2581
mail.set_payload(request.as_string())
2584
def send_message(self, message):
2585
"""Send a message, using its headers to address it.
2587
:param message: The message to send
2588
:type message: `email.Message`"""
2589
server = smtplib.SMTP("localhost")
2590
server.sendmail(message['From'], message['To'], message.as_string())
2593
def help(self, parser=None):
2594
"""Print a usage message
2596
:param parser: The options parser to use
2597
:type parser: `cmdutil.CmdOptionParser`
2600
parser = self.get_parser()
2603
Sends a merge request formatted for Bug Goo. Intended use: get the tree
2604
you'd like to merge into. Apply the merges you want. Invoke request-merge.
2605
The merge request will open in your $EDITOR.
2607
When no TARGET is specified, it uses the current tree revision. When
2608
no MERGE is specified, it uses the direct merges (as in "revisions
2609
--direct-merges"). But you can specify just the TARGET, or all the MERGE
2613
def get_parser(self):
2614
"""Produce a commandline parser for this command.
2616
:rtype: `cmdutil.CmdOptionParser`
2618
parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
2622
'changes' : Changes,
2625
'apply-changes':ApplyChanges,
2628
'revision': Revision,
2629
'revisions': Revisions,
2636
'mirror-archive': MirrorArchive,
2637
'ninventory': Inventory,
2639
'request-merge': RequestMerge,
2642
def my_import(mod_name):
2643
module = __import__(mod_name)
2644
components = mod_name.split('.')
2645
for comp in components[1:]:
2646
module = getattr(module, comp)
2649
def plugin(mod_name):
2650
module = my_import(mod_name)
2651
module.add_command(commands)
2653
for file in os.listdir(sys.path[0]+"/command"):
2654
if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
2655
plugin("command."+file[:-3])
2658
'apply-delta' : "Try \"apply-changes\".",
2659
'delta' : "To compare two revisions, use \"changes\".",
2660
'diff-rev' : "To compare two revisions, use \"changes\".",
2661
'undo' : "To undo local changes, use \"revert\".",
2662
'undelete' : "To undo only deletions, use \"revert --deletions\"",
2663
'missing-from' : "Try \"revisions --missing-from\".",
2664
'missing' : "Try \"revisions --missing\".",
2665
'missing-merge' : "Try \"revisions --partner-missing\".",
2666
'new-merges' : "Try \"revisions --new-merges\".",
2667
'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
2668
'logs' : "Try \"revisions --logs\"",
2669
'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
2670
'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
2671
'change-version' : "Try \"update REVISION\"",
2672
'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
2673
'rev-depends' : "Use revisions --dependencies",
2674
'auto-get' : "Plain get will do archive lookups",
2675
'tagline' : "Use add-id. It uses taglines in tagline trees",
2676
'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
2677
'library-revisions' : "Use revisions --library",
2678
'file-revert' : "Use revert FILE",
2679
'join-branch' : "Use replay --logs-only"
2681
# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7