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
25
from pylon.errors import *
26
from pylon import errors
44
__docformat__ = "restructuredtext"
45
__doc__ = "Implementation of user (sub) commands"
48
def find_command(cmd):
50
Return an instance of a command type. Return None if the type isn't
53
:param cmd: the name of the command to look for
54
:type cmd: the type of the command
56
if commands.has_key(cmd):
57
return commands[cmd]()
62
def __call__(self, cmdline):
64
self.do_command(cmdline.split())
65
except cmdutil.GetHelp, e:
70
def get_completer(index):
73
def complete(self, args, text):
75
Returns a list of possible completions for the given text.
77
:param args: The complete list of arguments
78
:type args: List of str
79
:param text: text to complete (may be shorter than args[-1])
92
parser=self.get_parser()
93
if realtext.startswith('-'):
94
candidates = parser.iter_options()
96
(options, parsed_args) = parser.parse_args(args)
98
if len (parsed_args) > 0:
99
candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
101
candidates = self.get_completer("", 0)
104
if candidates is None:
106
for candidate in candidates:
107
candidate = str(candidate)
108
if candidate.startswith(realtext):
109
matches.append(candidate[len(realtext)- len(text):])
113
class Help(BaseCommand):
115
Lists commands, prints help messages.
118
self.description="Prints help mesages"
121
def do_command(self, cmdargs):
123
Prints a help message.
125
options, args = self.get_parser().parse_args(cmdargs)
127
raise cmdutil.GetHelp
129
if options.native or options.suggestions or options.external:
130
native = options.native
131
suggestions = options.suggestions
132
external = options.external
139
self.list_commands(native, suggestions, external)
142
command_help(args[0])
146
self.get_parser().print_help()
148
If no command is specified, commands are listed. If a command is
149
specified, help for that command is listed.
152
def get_parser(self):
154
Returns the options parser to use for the "revision" command.
156
:rtype: cmdutil.CmdOptionParser
158
if self.parser is not None:
160
parser=cmdutil.CmdOptionParser("fai help [command]")
161
parser.add_option("-n", "--native", action="store_true",
162
dest="native", help="Show native commands")
163
parser.add_option("-e", "--external", action="store_true",
164
dest="external", help="Show external commands")
165
parser.add_option("-s", "--suggest", action="store_true",
166
dest="suggestions", help="Show suggestions")
170
def list_commands(self, native=True, suggest=False, external=True):
172
Lists supported commands.
174
:param native: list native, python-based commands
176
:param external: list external aba-style commands
180
print "Native Fai commands"
185
for i in range(28-len(k)):
187
print space+k+" : "+commands[k]().description
190
print "Unavailable commands and suggested alternatives"
191
key_list = suggestions.keys()
194
print "%28s : %s" % (key, suggestions[key])
197
fake_aba = abacmds.AbaCmds()
198
if (fake_aba.abadir == ""):
200
print "External commands"
201
fake_aba.list_commands()
204
print "Use help --suggest to list alternatives to tla and aba"\
206
if options.tla_fallthrough and (native or external):
207
print "Fai also supports tla commands."
209
def command_help(cmd):
211
Prints help for a command.
213
:param cmd: The name of the command to print help for
216
fake_aba = abacmds.AbaCmds()
217
cmdobj = find_command(cmd)
220
elif suggestions.has_key(cmd):
221
print "Not available\n" + suggestions[cmd]
223
abacmd = fake_aba.is_command(cmd)
227
print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
231
class Changes(BaseCommand):
233
the "changes" command: lists differences between trees/revisions:
237
self.description="Lists what files have changed in the project tree"
239
def get_completer(self, arg, index):
243
tree = arch.tree_root(".")
246
return cmdutil.iter_revision_completions(arg, tree)
248
def parse_commandline(self, cmdline):
250
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
252
:param cmdline: A list of arguments to parse
253
:rtype: (options, Revision, Revision/WorkingTree)
255
parser=self.get_parser()
256
(options, args) = parser.parse_args(cmdline)
258
raise cmdutil.GetHelp
260
tree=arch.tree_root(".")
262
a_spec = pylon.comp_revision(tree)
264
a_spec = cmdutil.determine_revision_tree(tree, args[0])
265
cmdutil.ensure_archive_registered(a_spec.archive)
267
b_spec = cmdutil.determine_revision_tree(tree, args[1])
268
cmdutil.ensure_archive_registered(b_spec.archive)
271
return options, a_spec, b_spec
273
def do_command(self, cmdargs):
275
Master function that perfoms the "changes" command.
278
options, a_spec, b_spec = self.parse_commandline(cmdargs);
279
except cmdutil.CantDetermineRevision, e:
282
except arch.errors.TreeRootError, e:
285
if options.changeset:
286
changeset=options.changeset
289
tmpdir=pylon.util.tmpdir()
290
changeset=tmpdir+"/changeset"
292
delta=arch.iter_delta(a_spec, b_spec, changeset)
295
if pylon.chattermatch(line, "changeset:"):
298
cmdutil.colorize(line, options.suppress_chatter)
299
except arch.util.ExecProblem, e:
300
if e.proc.error and e.proc.error.startswith(
301
"missing explicit id for file"):
305
if (options.perform_diff):
306
chan = pylon.ChangesetMunger(changeset)
308
if options.diffopts is not None:
309
if isinstance(b_spec, arch.Revision):
310
b_dir = b_spec.library_find()
313
a_dir = a_spec.library_find()
314
diffopts = options.diffopts.split()
315
cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
317
cmdutil.show_diffs(delta.changeset)
319
if tmpdir and (os.access(tmpdir, os.X_OK)):
320
shutil.rmtree(tmpdir)
322
def get_parser(self):
324
Returns the options parser to use for the "changes" command.
326
:rtype: cmdutil.CmdOptionParser
328
parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
330
parser.add_option("-d", "--diff", action="store_true",
331
dest="perform_diff", default=False,
332
help="Show diffs in summary")
333
parser.add_option("-c", "--changeset", dest="changeset",
334
help="Store a changeset in the given directory",
336
parser.add_option("-s", "--silent", action="store_true",
337
dest="suppress_chatter", default=False,
338
help="Suppress chatter messages")
339
parser.add_option("--diffopts", dest="diffopts",
340
help="Use the specified diff options",
345
def help(self, parser=None):
347
Prints a help message.
349
:param parser: If supplied, the parser to use for generating help. If \
350
not supplied, it is retrieved.
351
:type parser: cmdutil.CmdOptionParser
354
parser=self.get_parser()
357
Performs source-tree comparisons
359
If no revision is specified, the current project tree is compared to the
360
last-committed revision. If one revision is specified, the current project
361
tree is compared to that revision. If two revisions are specified, they are
362
compared to each other.
368
class ApplyChanges(BaseCommand):
370
Apply differences between two revisions to a tree
374
self.description="Applies changes to a project tree"
376
def get_completer(self, arg, index):
380
tree = arch.tree_root(".")
383
return cmdutil.iter_revision_completions(arg, tree)
385
def parse_commandline(self, cmdline, tree):
387
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
389
:param cmdline: A list of arguments to parse
390
:rtype: (options, Revision, Revision/WorkingTree)
392
parser=self.get_parser()
393
(options, args) = parser.parse_args(cmdline)
395
raise cmdutil.GetHelp
397
a_spec = cmdutil.determine_revision_tree(tree, args[0])
398
cmdutil.ensure_archive_registered(a_spec.archive)
399
b_spec = cmdutil.determine_revision_tree(tree, args[1])
400
cmdutil.ensure_archive_registered(b_spec.archive)
401
return options, a_spec, b_spec
403
def do_command(self, cmdargs):
405
Master function that performs "apply-changes".
408
tree = arch.tree_root(".")
409
options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
410
except cmdutil.CantDetermineRevision, e:
413
except arch.errors.TreeRootError, e:
417
delta=pylon.apply_delta_custom(a_spec, b_spec, tree,
418
apply_type=pylon.Diff3Handler)
420
delta=cmdutil.apply_delta(a_spec, b_spec, tree)
421
for line in cmdutil.iter_apply_delta_filter(delta):
422
cmdutil.colorize(line, options.suppress_chatter)
424
def get_parser(self):
426
Returns the options parser to use for the "apply-changes" command.
428
:rtype: cmdutil.CmdOptionParser
430
parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
432
parser.add_option("-d", "--diff", action="store_true",
433
dest="perform_diff", default=False,
434
help="Show diffs in summary")
435
parser.add_option("-c", "--changeset", dest="changeset",
436
help="Store a changeset in the given directory",
438
parser.add_option("-s", "--silent", action="store_true",
439
dest="suppress_chatter", default=False,
440
help="Suppress chatter messages")
441
parser.add_option("-t", "--three-way", action="store_true",
442
dest="diff3", default=False,
443
help="[experimental] Use diff3 to merge files")
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=pylon.mirror_archive(mirror_source,
505
revision.archive, arch.NameParser(revision).get_package_version())
506
for line in arch.classify_chatter(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 = pylon.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, files) = parser.parse_args(cmdline)
602
files = [pylon.tree_file_path(tree, f) for f in files]
603
return options, tree.tree_version, files
606
def do_command(self, cmdargs):
608
Master function that perfoms the "commit" command.
610
tree=arch.tree_root(".")
611
options, version, files = self.parse_commandline(cmdargs, tree)
613
if options.__dict__.has_key("base") and options.base:
614
base = cmdutil.determine_revision_tree(tree, options.base)
617
base = pylon.submit_revision(tree)
620
ancestor = pylon.tree_latest(tree, version)
623
archive=version.archive
624
source=cmdutil.get_mirror_source(archive)
626
writethrough="implicit"
629
if writethrough=="explicit" and \
630
cmdutil.prompt("Writethrough"):
631
writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
632
elif writethrough=="none":
633
raise CommitToMirror(archive)
635
elif archive.is_mirror:
636
raise CommitToMirror(archive)
639
last_revision=tree.iter_logs(version, True).next().revision
640
except StopIteration, e:
643
if cmdutil.prompt("Import from commit"):
644
return do_import(version)
646
raise NoVersionLogs(version)
648
arch_last_revision = version.iter_revisions(True).next()
649
except StopIteration, e:
650
arch_last_revision = None
652
if last_revision != arch_last_revision:
653
print "Tree is not up to date with %s" % str(version)
654
if not cmdutil.prompt("Out of date"):
660
if not pylon.has_changed(ancestor):
661
if not cmdutil.prompt("Empty commit"):
663
except arch.util.ExecProblem, e:
664
if e.proc.error and e.proc.error.startswith(
665
"missing explicit id for file"):
669
log = tree.log_message(create=False)
672
if cmdutil.prompt("Create log"):
675
except cmdutil.NoEditorSpecified, e:
676
raise CommandFailed(e)
677
log = tree.log_message(create=False)
680
if log["Summary"] is None or len(log["Summary"].strip()) == 0:
681
if not cmdutil.prompt("Omit log summary"):
682
raise errors.NoLogSummary
684
for line in tree.iter_commit(seal=options.seal_version,
685
base=base, version=version, out_of_date_ok=allow_old,
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("-s", "--silent", action="store_true",
709
dest="suppress_chatter", default=False,
710
help="Suppress chatter messages")
711
if cmdutil.supports_switch("commit", "--base"):
712
parser.add_option("--base", dest="base", help="",
716
def help(self, parser=None):
718
Prints a help message.
720
:param parser: If supplied, the parser to use for generating help. If \
721
not supplied, it is retrieved.
722
:type parser: cmdutil.CmdOptionParser
725
parser=self.get_parser()
728
Updates a working tree to the current archive revision
730
If a version is specified, that is used instead
737
class CatLog(BaseCommand):
739
Print the log of a given file (from current tree)
742
self.description="Prints the patch log for a revision"
744
def get_completer(self, arg, index):
748
tree = arch.tree_root(".")
751
return cmdutil.iter_revision_completions(arg, tree)
753
def do_command(self, cmdargs):
755
Master function that perfoms the "cat-log" command.
757
parser=self.get_parser()
758
(options, args) = parser.parse_args(cmdargs)
760
tree = arch.tree_root(".")
761
except arch.errors.TreeRootError, e:
767
raise cmdutil.GetHelp()
770
revision = cmdutil.determine_revision_tree(tree, spec)
772
revision = cmdutil.determine_revision_arch(tree, spec)
773
except cmdutil.CantDetermineRevision, e:
774
raise CommandFailedWrapper(e)
777
use_tree = (options.source == "tree" or \
778
(options.source == "any" and tree))
779
use_arch = (options.source == "archive" or options.source == "any")
783
for log in tree.iter_logs(revision.version):
784
if log.revision == revision:
788
if log is None and use_arch:
789
cmdutil.ensure_revision_exists(revision)
790
log = arch.Patchlog(revision)
792
for item in log.items():
793
print "%s: %s" % item
794
print log.description
796
def get_parser(self):
798
Returns the options parser to use for the "cat-log" command.
800
:rtype: cmdutil.CmdOptionParser
802
parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
803
parser.add_option("--archive", action="store_const", dest="source",
804
const="archive", default="any",
805
help="Always get the log from the archive")
806
parser.add_option("--tree", action="store_const", dest="source",
807
const="tree", help="Always get the log from the tree")
810
def help(self, parser=None):
812
Prints a help message.
814
:param parser: If supplied, the parser to use for generating help. If \
815
not supplied, it is retrieved.
816
:type parser: cmdutil.CmdOptionParser
819
parser=self.get_parser()
822
Prints the log for the specified revision
827
class Revert(BaseCommand):
828
""" Reverts a tree (or aspects of it) to a revision
831
self.description="Reverts a tree (or aspects of it) to a revision "
833
def get_completer(self, arg, index):
837
tree = arch.tree_root(".")
840
return iter_modified_file_completions(tree, arg)
842
def do_command(self, cmdargs):
844
Master function that perfoms the "revert" command.
846
parser=self.get_parser()
847
(options, args) = parser.parse_args(cmdargs)
849
tree = arch.tree_root(".")
850
except arch.errors.TreeRootError, e:
851
raise CommandFailed(e)
853
if options.revision is not None:
854
spec=options.revision
857
revision = cmdutil.determine_revision_tree(tree, spec)
859
revision = pylon.comp_revision(tree)
860
except cmdutil.CantDetermineRevision, e:
861
raise CommandFailedWrapper(e)
864
if options.file_contents or options.file_perms or options.deletions\
865
or options.additions or options.renames or options.hunk_prompt:
866
munger = pylon.MungeOpts()
867
munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm,
870
if len(args) > 0 or options.logs or options.pattern_files or \
873
munger = pylon.MungeOpts(True)
874
munger.all_types(True)
876
t_cwd = pylon.tree_cwd(tree)
880
name = os.path.normpath(os.path.join(t_cwd, name))
881
name = os.path.join("./", name)
882
munger.add_keep_file(name);
884
if options.file_perms:
885
munger.file_perms = True
886
if options.file_contents:
887
munger.file_contents = True
888
if options.deletions:
889
munger.deletions = True
890
if options.additions:
891
munger.additions = True
893
munger.renames = True
895
munger.add_keep_pattern('^\./\{arch\}/[^=].*')
897
munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
898
"/\.arch-inventory$")
899
if options.pattern_files:
900
munger.add_keep_pattern(options.pattern_files)
902
for line in pylon.revert(tree, revision, munger,
903
not options.no_output):
904
cmdutil.colorize(line)
907
def get_parser(self):
909
Returns the options parser to use for the "cat-log" command.
911
:rtype: cmdutil.CmdOptionParser
913
parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
914
parser.add_option("", "--contents", action="store_true",
915
dest="file_contents",
916
help="Revert file content changes")
917
parser.add_option("", "--permissions", action="store_true",
919
help="Revert file permissions changes")
920
parser.add_option("", "--deletions", action="store_true",
922
help="Restore deleted files")
923
parser.add_option("", "--additions", action="store_true",
925
help="Remove added files")
926
parser.add_option("", "--renames", action="store_true",
928
help="Revert file names")
929
parser.add_option("--hunks", action="store_true",
930
dest="hunk_prompt", default=False,
931
help="Prompt which hunks to revert")
932
parser.add_option("--pattern-files", dest="pattern_files",
933
help="Revert files that match this pattern",
935
parser.add_option("--logs", action="store_true",
936
dest="logs", default=False,
937
help="Revert only logs")
938
parser.add_option("--control-files", action="store_true",
939
dest="control", default=False,
940
help="Revert logs and other control files")
941
parser.add_option("-n", "--no-output", action="store_true",
943
help="Don't keep an undo changeset")
944
parser.add_option("--revision", dest="revision",
945
help="Revert to the specified revision",
949
def help(self, parser=None):
951
Prints a help message.
953
:param parser: If supplied, the parser to use for generating help. If \
954
not supplied, it is retrieved.
955
:type parser: cmdutil.CmdOptionParser
958
parser=self.get_parser()
961
Reverts changes in the current working tree. If no flags are specified, all
962
types of changes are reverted. Otherwise, only selected types of changes are
965
If a revision is specified on the commandline, differences between the current
966
tree and that revision are reverted. If a version is specified, the current
967
tree is used to determine the revision.
969
If files are specified, only those files listed will have any changes applied.
970
To specify a renamed file, you can use either the old or new name. (or both!)
972
Unless "-n" is specified, reversions can be undone with "redo".
976
class Revision(BaseCommand):
978
Print a revision name based on a revision specifier
981
self.description="Prints the name of a revision"
983
def get_completer(self, arg, index):
987
tree = arch.tree_root(".")
990
return cmdutil.iter_revision_completions(arg, tree)
992
def do_command(self, cmdargs):
994
Master function that perfoms the "revision" command.
996
parser=self.get_parser()
997
(options, args) = parser.parse_args(cmdargs)
1000
tree = arch.tree_root(".")
1001
except arch.errors.TreeRootError:
1008
raise cmdutil.GetHelp
1011
revision = cmdutil.determine_revision_tree(tree, spec)
1013
revision = cmdutil.determine_revision_arch(tree, spec)
1014
except cmdutil.CantDetermineRevision, e:
1017
print options.display(revision)
1019
def get_parser(self):
1021
Returns the options parser to use for the "revision" command.
1023
:rtype: cmdutil.CmdOptionParser
1025
parser=cmdutil.CmdOptionParser("fai revision [revision]")
1026
parser.add_option("", "--location", action="store_const",
1027
const=pylon.paths.determine_path, dest="display",
1028
help="Show location instead of name", default=str)
1029
parser.add_option("--import", action="store_const",
1030
const=pylon.paths.determine_import_path,
1031
dest="display", help="Show location of import file")
1032
parser.add_option("--log", action="store_const",
1033
const=pylon.paths.determine_log_path, dest="display",
1034
help="Show location of log file")
1035
parser.add_option("--patch", action="store_const",
1036
dest="display", const=pylon.paths.determine_patch_path,
1037
help="Show location of patchfile")
1038
parser.add_option("--continuation", action="store_const",
1039
const=pylon.paths.determine_continuation_path,
1041
help="Show location of continuation file")
1042
parser.add_option("--cacherev", action="store_const",
1043
const=pylon.paths.determine_cacherev_path,
1044
dest="display", help="Show location of cacherev file")
1047
def help(self, parser=None):
1049
Prints a help message.
1051
:param parser: If supplied, the parser to use for generating help. If \
1052
not supplied, it is retrieved.
1053
:type parser: cmdutil.CmdOptionParser
1056
parser=self.get_parser()
1059
Expands aliases and prints the name of the specified revision. Instead of
1060
the name, several options can be used to print locations. If more than one is
1061
specified, the last one is used.
1066
class Revisions(BaseCommand):
1068
Print a revision name based on a revision specifier
1071
self.description="Lists revisions"
1072
self.cl_revisions = []
1074
def do_command(self, cmdargs):
1076
Master function that perfoms the "revision" command.
1078
(options, args) = self.get_parser().parse_args(cmdargs)
1080
raise cmdutil.GetHelp
1082
self.tree = arch.tree_root(".")
1083
except arch.errors.TreeRootError:
1085
if options.type == "default":
1086
options.type = "archive"
1088
iter = cmdutil.revision_iterator(self.tree, options.type, args,
1089
options.reverse, options.modified,
1091
except cmdutil.CantDetermineRevision, e:
1092
raise CommandFailedWrapper(e)
1093
except cmdutil.CantDetermineVersion, e:
1094
raise CommandFailedWrapper(e)
1095
if options.skip is not None:
1096
iter = cmdutil.iter_skip(iter, int(options.skip))
1099
for revision in iter:
1101
if isinstance(revision, arch.Patchlog):
1103
revision=revision.revision
1104
out = options.display(revision)
1107
if log is None and (options.summary or options.creator or
1108
options.date or options.merges):
1109
log = revision.patchlog
1111
print " %s" % log.creator
1113
print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
1115
print " %s" % log.summary
1117
showed_title = False
1118
for revision in log.merged_patches:
1119
if not showed_title:
1122
print " %s" % revision
1123
if len(self.cl_revisions) > 0:
1124
print pylon.changelog_for_merge(self.cl_revisions, None)
1125
except pylon.errors.TreeRootNone:
1126
raise CommandFailedWrapper(
1127
Exception("This option can only be used in a project tree."))
1129
def changelog_append(self, revision):
1130
if isinstance(revision, arch.Revision):
1131
revision=arch.Patchlog(revision)
1132
self.cl_revisions.append(revision)
1134
def get_parser(self):
1136
Returns the options parser to use for the "revision" command.
1138
:rtype: cmdutil.CmdOptionParser
1140
parser=cmdutil.CmdOptionParser("fai revisions [version/revision]")
1141
select = cmdutil.OptionGroup(parser, "Selection options",
1142
"Control which revisions are listed. These options"
1143
" are mutually exclusive. If more than one is"
1144
" specified, the last is used.")
1146
cmdutil.add_revision_iter_options(select)
1147
parser.add_option("", "--skip", dest="skip",
1148
help="Skip revisions. Positive numbers skip from "
1149
"beginning, negative skip from end.",
1152
parser.add_option_group(select)
1154
format = cmdutil.OptionGroup(parser, "Revision format options",
1155
"These control the appearance of listed revisions")
1156
format.add_option("", "--location", action="store_const",
1157
const=pylon.paths.determine_path, dest="display",
1158
help="Show location instead of name", default=str)
1159
format.add_option("--import", action="store_const",
1160
const=pylon.paths.determine_import_path,
1161
dest="display", help="Show location of import file")
1162
format.add_option("--log", action="store_const",
1163
const=pylon.paths.determine_log_path, dest="display",
1164
help="Show location of log file")
1165
format.add_option("--patch", action="store_const",
1166
dest="display", const=pylon.paths.determine_patch_path,
1167
help="Show location of patchfile")
1168
format.add_option("--continuation", action="store_const",
1169
const=pylon.paths.determine_continuation_path,
1171
help="Show location of continuation file")
1172
format.add_option("--cacherev", action="store_const",
1173
const=pylon.paths.determine_cacherev_path,
1174
dest="display", help="Show location of cacherev file")
1175
format.add_option("--changelog", action="store_const",
1176
const=self.changelog_append, dest="display",
1177
help="Show location of cacherev file")
1178
parser.add_option_group(format)
1179
display = cmdutil.OptionGroup(parser, "Display format options",
1180
"These control the display of data")
1181
display.add_option("-r", "--reverse", action="store_true",
1182
dest="reverse", help="Sort from newest to oldest")
1183
display.add_option("-s", "--summary", action="store_true",
1184
dest="summary", help="Show patchlog summary")
1185
display.add_option("-D", "--date", action="store_true",
1186
dest="date", help="Show patchlog date")
1187
display.add_option("-c", "--creator", action="store_true",
1188
dest="creator", help="Show the id that committed the"
1190
display.add_option("-m", "--merges", action="store_true",
1191
dest="merges", help="Show the revisions that were"
1193
parser.add_option_group(display)
1195
def help(self, parser=None):
1196
"""Attempt to explain the revisions command
1198
:param parser: If supplied, used to determine options
1201
parser=self.get_parser()
1203
print """List revisions.
1208
class Get(BaseCommand):
1210
Retrieve a revision from the archive
1213
self.description="Create a working tree for a revision"
1214
self.parser=self.get_parser()
1217
def get_completer(self, arg, index):
1221
tree = arch.tree_root(".")
1224
return cmdutil.iter_revision_completions(arg, tree)
1227
def do_command(self, cmdargs):
1229
Master function that perfoms the "get" command.
1231
(options, args) = self.parser.parse_args(cmdargs)
1235
tree = arch.tree_root(".")
1236
except arch.errors.TreeRootError:
1241
revision, arch_loc = pylon.paths.full_path_decode(args[0])
1242
except Exception, e:
1243
revision = cmdutil.determine_revision_arch(tree, args[0],
1244
check_existence=False, allow_package=True)
1248
directory = str(revision.nonarch)
1249
if os.path.exists(directory):
1250
raise DirectoryExists(directory)
1251
cmdutil.ensure_archive_registered(revision.archive, arch_loc)
1253
cmdutil.ensure_revision_exists(revision)
1254
except cmdutil.NoSuchRevision, e:
1255
raise CommandFailedWrapper(e)
1257
link = cmdutil.prompt ("get link")
1258
for line in pylon.iter_get(revision, directory, link,
1259
options.no_pristine,
1260
options.no_greedy_add):
1261
cmdutil.colorize(line)
1263
def get_parser(self):
1265
Returns the options parser to use for the "get" command.
1267
:rtype: cmdutil.CmdOptionParser
1269
parser=cmdutil.CmdOptionParser("fai get revision [dir]")
1270
parser.add_option("--no-pristine", action="store_true",
1272
help="Do not make pristine copy for reference")
1273
parser.add_option("--no-greedy-add", action="store_true",
1274
dest="no_greedy_add",
1275
help="Never add to greedy libraries")
1279
def help(self, parser=None):
1281
Prints a help message.
1283
:param parser: If supplied, the parser to use for generating help. If \
1284
not supplied, it is retrieved.
1285
:type parser: cmdutil.CmdOptionParser
1288
parser=self.get_parser()
1291
Expands aliases and constructs a project tree for a revision. If the optional
1292
"dir" argument is provided, the project tree will be stored in this directory.
1297
class PromptCmd(cmd.Cmd):
1299
cmd.Cmd.__init__(self)
1300
self.prompt = "Fai> "
1302
self.tree = arch.tree_root(".")
1307
self.fake_aba = abacmds.AbaCmds()
1308
self.identchars += '-'
1309
self.history_file = os.path.expanduser("~/.fai-history")
1310
readline.set_completer_delims(string.whitespace)
1311
if os.access(self.history_file, os.R_OK) and \
1312
os.path.isfile(self.history_file):
1313
readline.read_history_file(self.history_file)
1314
self.cwd = os.getcwd()
1316
def write_history(self):
1317
readline.write_history_file(self.history_file)
1319
def do_quit(self, args):
1320
self.write_history()
1323
def do_exit(self, args):
1326
def do_EOF(self, args):
1330
def postcmd(self, line, bar):
1334
def set_prompt(self):
1335
if self.tree is not None:
1337
prompt = pylon.alias_or_version(self.tree.tree_version,
1340
if prompt is not None:
1341
prompt = " " + prompt +":"+ pylon.tree_cwd(self.tree)
1346
self.prompt = "Fai%s> " % prompt
1348
def set_title(self, command=None):
1350
version = pylon.alias_or_version(self.tree.tree_version, self.tree,
1353
version = "[no version]"
1356
sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
1358
def do_cd(self, line):
1361
line = os.path.expanduser(line)
1362
if os.path.isabs(line):
1365
newcwd = self.cwd+'/'+line
1366
newcwd = os.path.normpath(newcwd)
1370
except Exception, e:
1373
self.tree = arch.tree_root(".")
1377
def do_help(self, line):
1380
def default(self, line):
1382
if find_command(args[0]):
1384
find_command(args[0]).do_command(args[1:])
1385
except cmdutil.BadCommandOption, e:
1387
except cmdutil.GetHelp, e:
1388
find_command(args[0]).help()
1389
except CommandFailed, e:
1391
except arch.errors.ArchiveNotRegistered, e:
1393
except KeyboardInterrupt, e:
1395
except arch.util.ExecProblem, e:
1396
print e.proc.error.rstrip('\n')
1397
except cmdutil.CantDetermineVersion, e:
1399
except cmdutil.CantDetermineRevision, e:
1401
except Exception, e:
1402
print "Unhandled error:\n%s" % errors.exception_str(e)
1404
elif suggestions.has_key(args[0]):
1405
print suggestions[args[0]]
1407
elif self.fake_aba.is_command(args[0]):
1410
tree = arch.tree_root(".")
1411
except arch.errors.TreeRootError:
1413
cmd = self.fake_aba.is_command(args[0])
1415
cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
1416
except KeyboardInterrupt, e:
1419
elif options.tla_fallthrough and args[0] != "rm" and \
1420
cmdutil.is_tla_command(args[0]):
1424
tree = arch.tree_root(".")
1425
except arch.errors.TreeRootError:
1427
args = cmdutil.expand_prefix_alias(args, tree)
1428
arch.util.exec_safe('tla', args, stderr=sys.stderr,
1429
stdout=sys.stdout, expected=(0, 1))
1430
except arch.util.ExecProblem, e:
1432
except KeyboardInterrupt, e:
1437
tree = arch.tree_root(".")
1438
except arch.errors.TreeRootError:
1441
os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
1442
except KeyboardInterrupt, e:
1445
def completenames(self, text, line, begidx, endidx):
1447
iter = iter_command_names(self.fake_aba)
1450
arg = line.split()[-1]
1453
iter = cmdutil.iter_munged_completions(iter, arg, text)
1454
except Exception, e:
1458
def completedefault(self, text, line, begidx, endidx):
1459
"""Perform completion for native commands.
1461
:param text: The text to complete
1463
:param line: The entire line to complete
1465
:param begidx: The start of the text in the line
1467
:param endidx: The end of the text in the line
1471
(cmd, args, foo) = self.parseline(line)
1472
command_obj=find_command(cmd)
1473
if command_obj is not None:
1474
return command_obj.complete(args.split(), text)
1475
elif not self.fake_aba.is_command(cmd) and \
1476
cmdutil.is_tla_command(cmd):
1477
iter = cmdutil.iter_supported_switches(cmd)
1479
arg = args.split()[-1]
1482
if arg.startswith("-"):
1483
return list(cmdutil.iter_munged_completions(iter, arg,
1486
return list(cmdutil.iter_munged_completions(
1487
cmdutil.iter_file_completions(arg), arg, text))
1492
arg = args.split()[-1]
1495
iter = cmdutil.iter_dir_completions(arg)
1496
iter = cmdutil.iter_munged_completions(iter, arg, text)
1499
arg = args.split()[-1]
1500
iter = cmdutil.iter_file_completions(arg)
1501
return list(cmdutil.iter_munged_completions(iter, arg, text))
1503
return self.completenames(text, line, begidx, endidx)
1504
except Exception, e:
1508
def iter_command_names(fake_aba):
1509
for entry in cmdutil.iter_combine([commands.iterkeys(),
1510
fake_aba.get_commands(),
1511
pylon.iter_tla_commands(False)]):
1512
if not suggestions.has_key(str(entry)):
1516
def iter_source_file_completions(tree, arg):
1517
treepath = pylon.tree_cwd(tree)
1518
if len(treepath) > 0:
1522
for file in tree.iter_inventory(dirs, source=True, both=True):
1523
file = file_completion_match(file, treepath, arg)
1524
if file is not None:
1528
def iter_untagged(tree, dirs):
1529
for file in pylon.iter_inventory_filter(tree, dirs, tagged=False,
1530
categories=pylon.non_root,
1531
control_files=True):
1535
def iter_untagged_completions(tree, arg):
1536
"""Generate an iterator for all visible untagged files that match arg.
1538
:param tree: The tree to look for untagged files in
1539
:type tree: `arch.WorkingTree`
1540
:param arg: The argument to match
1542
:return: An iterator of all matching untagged files
1543
:rtype: iterator of str
1545
treepath = pylon.tree_cwd(tree)
1547
for file in iter_untagged(tree, None):
1548
file = file_completion_match(file, "", arg)
1549
if file is not None:
1553
def file_completion_match(file, treepath, arg):
1554
"""Determines whether a file within an arch tree matches the argument.
1556
:param file: The rooted filename
1558
:param treepath: The path to the cwd within the tree
1560
:param arg: The prefix to match
1561
:return: The completion name, or None if not a match
1564
if not file.startswith(treepath):
1567
file = file[len(treepath)+1:]
1569
if not file.startswith(arg):
1571
if os.path.isdir(file):
1575
def iter_modified_file_completions(tree, arg):
1576
"""Returns a list of modified files that match the specified prefix.
1578
:param tree: The current tree
1579
:type tree: `arch.WorkingTree`
1580
:param arg: The prefix to match
1583
treepath = pylon.tree_cwd(tree)
1584
tmpdir = util.tmpdir()
1585
changeset = tmpdir+"/changeset"
1587
revision = cmdutil.determine_revision_tree(tree)
1588
for line in arch.iter_delta(revision, tree, changeset):
1589
if isinstance(line, arch.FileModification):
1590
file = file_completion_match(line.name[1:], treepath, arg)
1591
if file is not None:
1592
completions.append(file)
1593
shutil.rmtree(tmpdir)
1596
class Shell(BaseCommand):
1598
self.description = "Runs Fai as a shell"
1600
def do_command(self, cmdargs):
1602
raise cmdutil.GetHelp
1603
prompt = PromptCmd()
1607
prompt.write_history()
1609
class AddID(BaseCommand):
1611
Adds an inventory id for the given file
1614
self.description="Add an inventory id for a given file"
1616
def get_completer(self, arg, index):
1617
tree = arch.tree_root(".")
1618
return iter_untagged_completions(tree, arg)
1620
def do_command(self, cmdargs):
1622
Master function that perfoms the "revision" command.
1624
parser=self.get_parser()
1625
(options, args) = parser.parse_args(cmdargs)
1628
tree = arch.tree_root(".")
1629
except arch.errors.TreeRootError, e:
1630
raise pylon.errors.CommandFailedWrapper(e)
1633
if (len(args) == 0) == (options.untagged == False):
1634
raise cmdutil.GetHelp
1636
#if options.id and len(args) != 1:
1637
# print "If --id is specified, only one file can be named."
1640
method = tree.tagging_method
1642
if options.id_type == "tagline":
1643
if method != "tagline":
1644
if not cmdutil.prompt("Tagline in other tree"):
1645
if method == "explicit" or method == "implicit":
1646
options.id_type == method
1648
print "add-id not supported for \"%s\" tagging method"\
1652
elif options.id_type == "implicit":
1653
if method != "implicit":
1654
if not cmdutil.prompt("Implicit in other tree"):
1655
if method == "explicit" or method == "tagline":
1656
options.id_type == method
1658
print "add-id not supported for \"%s\" tagging method"\
1661
elif options.id_type == "explicit":
1662
if method != "tagline" and method != "explicit" and \
1663
method != "implicit":
1664
if not cmdutil.prompt("Explicit in other tree"):
1665
print "add-id not supported for \"%s\" tagging method" % \
1669
if options.id_type == "auto":
1670
if method != "tagline" and method != "explicit" \
1671
and method !="implicit":
1672
print "add-id not supported for \"%s\" tagging method" % method
1675
options.id_type = method
1676
if options.untagged:
1678
self.add_ids(tree, options.id_type, args)
1680
def add_ids(self, tree, id_type, files=()):
1681
"""Add inventory ids to files.
1683
:param tree: the tree the files are in
1684
:type tree: `arch.WorkingTree`
1685
:param id_type: the type of id to add: "explicit" or "tagline"
1687
:param files: The list of files to add. If None do all untagged.
1688
:type files: tuple of str
1691
untagged = (files is None)
1693
files = [os.path.join(tree, f) for f in iter_untagged(tree, None)]
1695
while len(files) > 0:
1696
previous_files.extend(files)
1697
if id_type == "explicit":
1699
elif id_type == "tagline" or id_type == "implicit":
1702
implicit = (id_type == "implicit")
1703
cmdutil.add_tagline_or_explicit_id(file, False,
1705
except cmdutil.AlreadyTagged:
1706
print "\"%s\" already has a tagline." % file
1707
except cmdutil.NoCommentSyntax:
1709
#do inventory after tagging until no untagged files are encountered
1712
for file in iter_untagged(tree, None):
1713
if not file in previous_files:
1719
def get_parser(self):
1721
Returns the options parser to use for the "revision" command.
1723
:rtype: cmdutil.CmdOptionParser
1725
parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
1726
# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
1727
# parser.add_option("-i", "--id", dest="id",
1728
# help="Specify id for a single file", default=None)
1729
parser.add_option("--tltl", action="store_true",
1730
dest="lord_style", help="Use Tom Lord's style of id.")
1731
parser.add_option("--explicit", action="store_const",
1732
const="explicit", dest="id_type",
1733
help="Use an explicit id", default="auto")
1734
parser.add_option("--tagline", action="store_const",
1735
const="tagline", dest="id_type",
1736
help="Use a tagline id")
1737
parser.add_option("--implicit", action="store_const",
1738
const="implicit", dest="id_type",
1739
help="Use an implicit id (deprecated)")
1740
parser.add_option("--untagged", action="store_true",
1741
dest="untagged", default=False,
1742
help="tag all untagged files")
1745
def help(self, parser=None):
1747
Prints a help message.
1749
:param parser: If supplied, the parser to use for generating help. If \
1750
not supplied, it is retrieved.
1751
:type parser: cmdutil.CmdOptionParser
1754
parser=self.get_parser()
1757
Adds an inventory to the specified file(s) and directories. If --untagged is
1758
specified, adds inventory to all untagged files and directories.
1763
class Merge(BaseCommand):
1765
Merges changes from other versions into the current tree
1768
self.description="Merges changes from other versions"
1770
self.tree = arch.tree_root(".")
1775
def get_completer(self, arg, index):
1776
if self.tree is None:
1777
raise arch.errors.TreeRootError
1778
return cmdutil.merge_completions(self.tree, arg, index)
1780
def do_command(self, cmdargs):
1782
Master function that perfoms the "merge" command.
1784
parser=self.get_parser()
1785
(options, args) = parser.parse_args(cmdargs)
1786
action = options.action
1788
if self.tree is None:
1789
raise arch.errors.TreeRootError(os.getcwd())
1790
if pylon.has_changed(pylon.comp_revision(self.tree)):
1791
raise UncommittedChanges(self.tree)
1796
revisions.append(cmdutil.determine_revision_arch(self.tree,
1798
source = "from commandline"
1800
revisions = pylon.iter_partner_revisions(self.tree,
1801
self.tree.tree_version)
1802
source = "from partner version"
1803
revisions = pylon.misc.rewind_iterator(revisions)
1807
except StopIteration, e:
1808
revision = cmdutil.tag_cur(self.tree)
1809
if revision is None:
1810
raise CantDetermineRevision("", "No version specified, no "
1811
"partner-versions, and no tag"
1813
revisions = [revision]
1814
source = "from tag source"
1815
for revision in revisions:
1816
cmdutil.ensure_archive_registered(revision.archive)
1817
cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
1818
(revision, source)))
1819
if action=="native-merge" or action=="update":
1820
if self.native_merge(revision, action,
1821
options.apply_type) == 0:
1823
elif action=="star-merge":
1825
self.star_merge(revision, options.diff3)
1826
except errors.MergeProblem, e:
1828
if pylon.has_changed(self.tree.tree_version):
1831
def star_merge(self, revision, diff3):
1832
"""Perform a star-merge on the current tree.
1834
:param revision: The revision to use for the merge
1835
:type revision: `arch.Revision`
1836
:param diff3: If true, do a diff3 merge
1840
for line in self.tree.iter_star_merge(revision, diff3=diff3):
1841
cmdutil.colorize(line)
1842
except arch.util.ExecProblem, e:
1843
if e.proc.status is not None and e.proc.status == 1:
1850
def native_merge(self, other_revision, action, apply_type):
1851
"""Perform a native-merge on the current tree.
1853
:param other_revision: The revision to use for the merge
1854
:type other_revision: `arch.Revision`
1855
:return: 0 if the merge was skipped, 1 if it was applied
1857
other_tree = pylon.find_or_make_local_revision(other_revision)
1859
if action == "native-merge":
1860
ancestor = pylon.merge_ancestor2(self.tree, other_tree,
1862
elif action == "update":
1863
ancestor = pylon.tree_latest(self.tree,
1864
other_revision.version)
1865
except CantDetermineRevision, e:
1866
raise CommandFailedWrapper(e)
1867
cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
1868
if (ancestor == other_revision):
1869
cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
1872
if apply_type == "diff3":
1873
delta=pylon.apply_delta_custom(ancestor, other_tree,
1875
apply_type=pylon.Diff3Handler)
1876
merge_iter = cmdutil.iter_apply_delta_filter(delta)
1877
elif apply_type == "normal":
1878
delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
1879
merge_iter = cmdutil.iter_apply_delta_filter(delta)
1880
elif apply_type == "python":
1881
base_tree = pylon.find_or_make_local_revision(ancestor)
1882
merge_iter = pylon.iter_arch_merge(self.tree, base_tree,
1883
other_tree, ancestor,
1887
for line in merge_iter:
1888
cmdutil.colorize(line)
1893
def get_parser(self):
1895
Returns the options parser to use for the "merge" command.
1897
:rtype: cmdutil.CmdOptionParser
1899
parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
1900
parser.add_option("-s", "--star-merge", action="store_const",
1901
dest="action", help="Use star-merge",
1902
const="star-merge", default="native-merge")
1903
parser.add_option("--update", action="store_const",
1904
dest="action", help="Use update picker",
1906
parser.add_option("--diff3", action="store_const",
1907
dest="apply_type", const="diff3", default="normal",
1908
help="Use diff3 for merge (implies star-merge)")
1909
if "iter_arch_merge" in dir(pylon):
1910
parser.add_option("--python", action="store_const",
1911
dest="apply_type", const="python",
1912
default="normal", help="Do native merging")
1915
def help(self, parser=None):
1917
Prints a help message.
1919
:param parser: If supplied, the parser to use for generating help. If \
1920
not supplied, it is retrieved.
1921
:type parser: cmdutil.CmdOptionParser
1924
parser=self.get_parser()
1927
Performs a merge operation using the specified version.
1931
class ELog(BaseCommand):
1933
Produces a raw patchlog and invokes the user's editor
1936
self.description="Edit a patchlog to commit"
1938
self.tree = arch.tree_root(".")
1943
def do_command(self, cmdargs):
1945
Master function that perfoms the "elog" command.
1947
parser=self.get_parser()
1948
(options, args) = parser.parse_args(cmdargs)
1949
if self.tree is None:
1950
raise arch.errors.TreeRootError
1954
except pylon.errors.NoEditorSpecified, e:
1955
raise pylon.errors.CommandFailedWrapper(e)
1957
def get_parser(self):
1959
Returns the options parser to use for the "merge" command.
1961
:rtype: cmdutil.CmdOptionParser
1963
parser=cmdutil.CmdOptionParser("fai elog")
1967
def help(self, parser=None):
1969
Invokes $EDITOR to produce a log for committing.
1971
:param parser: If supplied, the parser to use for generating help. If \
1972
not supplied, it is retrieved.
1973
:type parser: cmdutil.CmdOptionParser
1976
parser=self.get_parser()
1979
Invokes $EDITOR to produce a log for committing.
1984
"""Makes and edits the log for a tree. Does all kinds of fancy things
1985
like log templates and merge summaries and log-for-merge
1987
:param tree: The tree to edit the log for
1988
:type tree: `arch.WorkingTree`
1990
#ensure we have an editor before preparing the log
1991
cmdutil.find_editor()
1992
log = tree.log_message(create=False)
1994
if log is None or cmdutil.prompt("Overwrite log"):
1997
log = tree.log_message(create=True)
2000
template = pylon.log_template_path(tree)
2002
shutil.copyfile(template, tmplog)
2003
comp_version = pylon.comp_revision(tree).version
2004
new_merges = list(pylon.iter_new_merges(tree, comp_version))
2005
direct_merges = cmdutil.direct_merges(new_merges)
2006
log["Summary"] = pylon.merge_summary(direct_merges, tree.tree_version)
2007
if len(direct_merges) > 0:
2008
if cmdutil.prompt("Log for merge"):
2009
if cmdutil.prompt("changelog for merge"):
2010
mergestuff = "Patches applied:\n"
2011
mergestuff += pylon.changelog_for_merge(direct_merges,
2014
mergestuff = cmdutil.log_for_merge(tree, comp_version)
2015
log.description += mergestuff
2018
cmdutil.invoke_editor(log.name)
2025
class MirrorArchive(BaseCommand):
2027
Updates a mirror from an archive
2030
self.description="Update a mirror from an archive"
2032
def do_command(self, cmdargs):
2034
Master function that perfoms the "revision" command.
2037
parser=self.get_parser()
2038
(options, args) = parser.parse_args(cmdargs)
2042
tree = arch.tree_root(".")
2047
if tree is not None:
2048
name = tree.tree_version
2050
raise errors.CommandFailedWrapper(\
2051
errors.CantDetermineArchive(None, "Not in a project tree"))
2053
name = cmdutil.expand_alias(args[0], tree)
2054
name = arch.NameParser(name)
2056
to_arch = name.get_archive()
2057
from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
2058
limit = name.get_nonarch()
2060
for line in pylon.iter_mirror_archive(from_arch,to_arch, limit):
2061
cmdutil.colorize(line)
2063
def get_parser(self):
2065
Returns the options parser to use for the "revision" command.
2067
:rtype: cmdutil.CmdOptionParser
2069
parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
2072
def help(self, parser=None):
2074
Prints a help message.
2076
:param parser: If supplied, the parser to use for generating help. If \
2077
not supplied, it is retrieved.
2078
:type parser: cmdutil.CmdOptionParser
2081
parser=self.get_parser()
2084
Updates a mirror from an archive. If a branch, package, or version is
2085
supplied, only changes under it are mirrored.
2089
def help_tree_spec():
2090
print """Specifying revisions (default: tree)
2091
Revisions may be specified by alias, revision, version or patchlevel.
2092
Revisions or versions may be fully qualified. Unqualified revisions, versions,
2093
or patchlevels use the archive of the current project tree. Versions will
2094
use the latest patchlevel in the tree. Patchlevels will use the current tree-
2097
Use "alias" to list available (user and automatic) aliases."""
2101
"The latest revision in the archive of the tree-version. You can specify \
2102
a different version like so: acur:foo--bar--0 (aliases can be used)",
2104
"""(tree current) The latest revision in the tree of the tree-version. \
2105
You can specify a different version like so: tcur:foo--bar--0 (aliases can be \
2108
"""(tree previous) The previous revision in the tree of the tree-version. To \
2109
specify an older revision, use a number, e.g. "tprev:4" """,
2111
"""(tree ancestor) The ancestor revision of the tree To specify an older \
2112
revision, use a number, e.g. "tanc:4".""",
2114
"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""",
2116
""" (tree modified) The latest revision to modify a given file, e.g. \
2117
"tmod:engine.cpp" or "tmod:engine.cpp:16".""",
2119
"""(tree tag) The revision that was tagged into the current tree revision, \
2120
according to the tree""",
2122
"""(tag current) The latest revision of the version that the current tree \
2123
was tagged from.""",
2125
"""The common ancestor of the current tree and the specified revision. \
2126
Defaults to the first partner-version's latest revision or to tagcur.""",
2130
def is_auto_alias(name):
2131
"""Determine whether a name is an auto alias name
2133
:param name: the name to check
2135
:return: True if the name is an auto alias, false if not
2138
return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)]
2141
def display_def(iter, wrap = 80):
2142
"""Display a list of definitions
2144
:param iter: iter of name, definition pairs
2145
:type iter: iter of (str, str)
2146
:param wrap: The width for text wrapping
2151
for (key, value) in vals:
2152
if len(key) > maxlen:
2154
for (key, value) in vals:
2155
tw=textwrap.TextWrapper(width=wrap,
2156
initial_indent=key.rjust(maxlen)+" : ",
2157
subsequent_indent="".rjust(maxlen+3))
2158
print tw.fill(value)
2161
def help_aliases(tree):
2162
print """Auto-generated aliases"""
2163
display_def(pylon.util.iter_pairs(auto_alias))
2164
print "User aliases"
2165
display_def(pylon.iter_all_alias(tree))
2167
class Inventory(BaseCommand):
2168
"""List the status of files in the tree"""
2170
self.description=self.__doc__
2172
def do_command(self, cmdargs):
2174
Master function that perfoms the "revision" command.
2177
parser=self.get_parser()
2178
(options, args) = parser.parse_args(cmdargs)
2179
tree = arch.tree_root(".")
2182
if (options.source):
2183
categories.append(pylon.SourceFile)
2184
if (options.precious):
2185
categories.append(pylon.PreciousFile)
2186
if (options.backup):
2187
categories.append(pylon.BackupFile)
2189
categories.append(pylon.JunkFile)
2191
if len(categories) == 1:
2192
show_leading = False
2196
if len(categories) == 0:
2199
if options.untagged:
2200
categories = pylon.non_root
2201
show_leading = False
2206
for file in pylon.iter_inventory_filter(tree, None,
2207
control_files=options.control_files,
2208
categories = categories, tagged=tagged):
2209
print pylon.file_line(file,
2210
category = show_leading,
2211
untagged = show_leading,
2214
def get_parser(self):
2216
Returns the options parser to use for the "revision" command.
2218
:rtype: cmdutil.CmdOptionParser
2220
parser=cmdutil.CmdOptionParser("fai inventory [options]")
2221
parser.add_option("--ids", action="store_true", dest="ids",
2222
help="Show file ids")
2223
parser.add_option("--control", action="store_true",
2224
dest="control_files", help="include control files")
2225
parser.add_option("--source", action="store_true", dest="source",
2226
help="List source files")
2227
parser.add_option("--backup", action="store_true", dest="backup",
2228
help="List backup files")
2229
parser.add_option("--precious", action="store_true", dest="precious",
2230
help="List precious files")
2231
parser.add_option("--junk", action="store_true", dest="junk",
2232
help="List junk files")
2233
parser.add_option("--unrecognized", action="store_true",
2234
dest="unrecognized", help="List unrecognized files")
2235
parser.add_option("--untagged", action="store_true",
2236
dest="untagged", help="List only untagged files")
2239
def help(self, parser=None):
2241
Prints a help message.
2243
:param parser: If supplied, the parser to use for generating help. If \
2244
not supplied, it is retrieved.
2245
:type parser: cmdutil.CmdOptionParser
2248
parser=self.get_parser()
2251
Lists the status of files in the archive:
2259
Leading letter are not displayed if only one kind of file is shown
2264
class Alias(BaseCommand):
2265
"""List or adjust aliases"""
2267
self.description=self.__doc__
2269
def get_completer(self, arg, index):
2273
self.tree = arch.tree_root(".")
2278
return [part[0]+" " for part in pylon.iter_all_alias(self.tree)]
2280
return cmdutil.iter_revision_completions(arg, self.tree)
2283
def do_command(self, cmdargs):
2285
Master function that perfoms the "revision" command.
2288
parser=self.get_parser()
2289
(options, args) = parser.parse_args(cmdargs)
2291
self.tree = arch.tree_root(".")
2297
options.action(args, options)
2298
except cmdutil.ForbiddenAliasSyntax, e:
2299
raise CommandFailedWrapper(e)
2301
def no_prefix(self, alias):
2302
if alias.startswith("^"):
2306
def arg_dispatch(self, args, options):
2307
"""Add, modify, or list aliases, depending on number of arguments
2309
:param args: The list of commandline arguments
2310
:type args: list of str
2311
:param options: The commandline options
2314
help_aliases(self.tree)
2317
alias = self.no_prefix(args[0])
2319
self.print_alias(alias)
2320
elif (len(args)) == 2:
2321
self.add(alias, args[1], options)
2323
raise cmdutil.GetHelp
2325
def print_alias(self, alias):
2327
if is_auto_alias(alias):
2328
raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias."
2329
" Use \"revision\" to expand auto aliases." % alias)
2330
for pair in pylon.iter_all_alias(self.tree):
2331
if pair[0] == alias:
2333
if answer is not None:
2336
print "The alias %s is not assigned." % alias
2338
def add(self, alias, expansion, options):
2339
"""Add or modify aliases
2341
:param alias: The alias name to create/modify
2343
:param expansion: The expansion to assign to the alias name
2344
:type expansion: str
2345
:param options: The commandline options
2347
if is_auto_alias(alias):
2348
raise IsAutoAlias(alias)
2351
new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
2353
pylon.check_alias(new_line.rstrip("\n"), [alias, expansion])
2355
for pair in self.get_iterator(options):
2356
if pair[0] != alias:
2357
newlist+="%s=%s\n" % (pair[0], pair[1])
2363
self.write_aliases(newlist, options)
2365
def delete(self, args, options):
2366
"""Delete the specified alias
2368
:param args: The list of arguments
2369
:type args: list of str
2370
:param options: The commandline options
2374
raise cmdutil.GetHelp
2375
alias = self.no_prefix(args[0])
2376
if is_auto_alias(alias):
2377
raise IsAutoAlias(alias)
2379
for pair in self.get_iterator(options):
2380
if pair[0] != alias:
2381
newlist+="%s=%s\n" % (pair[0], pair[1])
2385
raise errors.NoSuchAlias(alias)
2386
self.write_aliases(newlist, options)
2388
def get_alias_file(self, options):
2389
"""Return the name of the alias file to use
2391
:param options: The commandline options
2394
if self.tree is None:
2395
self.tree == arch.tree_root(".")
2396
return str(self.tree)+"/{arch}/+aliases"
2398
return "~/.aba/aliases"
2400
def get_iterator(self, options):
2401
"""Return the alias iterator to use
2403
:param options: The commandline options
2405
return pylon.iter_alias(self.get_alias_file(options))
2407
def write_aliases(self, newlist, options):
2408
"""Safely rewrite the alias file
2409
:param newlist: The new list of aliases
2411
:param options: The commandline options
2413
filename = os.path.expanduser(self.get_alias_file(options))
2414
file = pylon.util.NewFileVersion(filename)
2419
def get_parser(self):
2421
Returns the options parser to use for the "alias" command.
2423
:rtype: cmdutil.CmdOptionParser
2425
parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
2426
parser.add_option("-d", "--delete", action="store_const", dest="action",
2427
const=self.delete, default=self.arg_dispatch,
2428
help="Delete an alias")
2429
parser.add_option("--tree", action="store_true", dest="tree",
2430
help="Create a per-tree alias", default=False)
2433
def help(self, parser=None):
2435
Prints a help message.
2437
:param parser: If supplied, the parser to use for generating help. If \
2438
not supplied, it is retrieved.
2439
:type parser: cmdutil.CmdOptionParser
2442
parser=self.get_parser()
2445
Lists current aliases or modifies the list of aliases.
2447
If no arguments are supplied, aliases will be listed. If two arguments are
2448
supplied, the specified alias will be created or modified. If -d or --delete
2449
is supplied, the specified alias will be deleted.
2451
You can create aliases that refer to any fully-qualified part of the
2452
Arch namespace, e.g.
2455
archive/category--branch,
2456
archive/category--branch--version (my favourite)
2457
archive/category--branch--version--patchlevel
2459
Aliases can be used automatically by native commands. To use them
2460
with external or tla commands, prefix them with ^ (you can do this
2461
with native commands, too).
2465
class RequestMerge(BaseCommand):
2466
"""Submit a merge request to Bug Goo"""
2468
self.description=self.__doc__
2470
def do_command(self, cmdargs):
2471
"""Submit a merge request
2473
:param cmdargs: The commandline arguments
2474
:type cmdargs: list of str
2476
parser = self.get_parser()
2477
(options, args) = parser.parse_args(cmdargs)
2479
cmdutil.find_editor()
2480
except pylon.errors.NoEditorSpecified, e:
2481
raise pylon.errors.CommandFailedWrapper(e)
2483
self.tree=arch.tree_root(".")
2486
base, revisions = self.revision_specs(args)
2487
message = self.make_headers(base, revisions)
2488
message += self.make_summary(revisions)
2489
path = self.edit_message(message)
2490
message = self.tidy_message(path)
2491
if cmdutil.prompt("Send merge"):
2492
self.send_message(message)
2493
print "Merge request sent"
2495
def make_headers(self, base, revisions):
2496
"""Produce email and Bug Goo header strings
2498
:param base: The base revision to apply merges to
2499
:type base: `arch.Revision`
2500
:param revisions: The revisions to replay into the base
2501
:type revisions: list of `arch.Patchlog`
2502
:return: The headers
2505
headers = "To: gnu-arch-users@gnu.org\n"
2506
headers += "From: %s\n" % options.fromaddr
2507
if len(revisions) == 1:
2508
headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
2510
headers += "Subject: [MERGE REQUEST]\n"
2512
headers += "Base-Revision: %s\n" % base
2513
for revision in revisions:
2514
headers += "Revision: %s\n" % revision.revision
2515
headers += "Bug: \n\n"
2518
def make_summary(self, logs):
2519
"""Generate a summary of merges
2521
:param logs: the patchlogs that were directly added by the merges
2522
:type logs: list of `arch.Patchlog`
2523
:return: the summary
2528
summary+=str(log.revision)+"\n"
2529
summary+=log.summary+"\n"
2530
if log.description.strip():
2531
summary+=log.description.strip('\n')+"\n\n"
2534
def revision_specs(self, args):
2535
"""Determine the base and merge revisions from tree and arguments.
2537
:param args: The parsed arguments
2538
:type args: list of str
2539
:return: The base revision and merge revisions
2540
:rtype: `arch.Revision`, list of `arch.Patchlog`
2543
target_revision = cmdutil.determine_revision_arch(self.tree,
2546
target_revision = pylon.tree_latest(self.tree)
2548
merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
2549
self.tree, f)) for f in args[1:] ]
2551
if self.tree is None:
2552
raise CantDetermineRevision("", "Not in a project tree")
2553
merge_iter = pylon.iter_new_merges(self.tree,
2554
target_revision.version,
2556
merges = [f for f in cmdutil.direct_merges(merge_iter)]
2557
return (target_revision, merges)
2559
def edit_message(self, message):
2560
"""Edit an email message in the user's standard editor
2562
:param message: The message to edit
2564
:return: the path of the edited message
2567
if self.tree is None:
2571
path += "/,merge-request"
2572
file = open(path, 'w')
2575
cmdutil.invoke_editor(path)
2578
def tidy_message(self, path):
2579
"""Validate and clean up message.
2581
:param path: The path to the message to clean up
2583
:return: The parsed message
2584
:rtype: `email.Message`
2586
mail = email.message_from_file(open(path))
2587
if mail["Subject"].strip() == "[MERGE REQUEST]":
2590
request = email.message_from_string(mail.get_payload())
2591
if request.has_key("Bug"):
2592
if request["Bug"].strip()=="":
2594
mail.set_payload(request.as_string())
2597
def send_message(self, message):
2598
"""Send a message, using its headers to address it.
2600
:param message: The message to send
2601
:type message: `email.Message`"""
2602
server = smtplib.SMTP("localhost")
2603
server.sendmail(message['From'], message['To'], message.as_string())
2606
def help(self, parser=None):
2607
"""Print a usage message
2609
:param parser: The options parser to use
2610
:type parser: `cmdutil.CmdOptionParser`
2613
parser = self.get_parser()
2616
Sends a merge request formatted for Bug Goo. Intended use: get the tree
2617
you'd like to merge into. Apply the merges you want. Invoke request-merge.
2618
The merge request will open in your $EDITOR.
2620
When no TARGET is specified, it uses the current tree revision. When
2621
no MERGE is specified, it uses the direct merges (as in "revisions
2622
--direct-merges"). But you can specify just the TARGET, or all the MERGE
2626
def get_parser(self):
2627
"""Produce a commandline parser for this command.
2629
:rtype: `cmdutil.CmdOptionParser`
2631
parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
2635
'changes' : Changes,
2638
'apply-changes':ApplyChanges,
2641
'revision': Revision,
2642
'revisions': Revisions,
2649
'mirror-archive': MirrorArchive,
2650
'ninventory': Inventory,
2652
'request-merge': RequestMerge,
2655
def my_import(mod_name):
2656
module = __import__(mod_name)
2657
components = mod_name.split('.')
2658
for comp in components[1:]:
2659
module = getattr(module, comp)
2662
def plugin(mod_name):
2663
module = my_import(mod_name)
2664
module.add_command(commands)
2666
for file in os.listdir(sys.path[0]+"/command"):
2667
if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py":
2668
plugin("command."+file[:-3])
2671
'apply-delta' : "Try \"apply-changes\".",
2672
'delta' : "To compare two revisions, use \"changes\".",
2673
'diff-rev' : "To compare two revisions, use \"changes\".",
2674
'undo' : "To undo local changes, use \"revert\".",
2675
'undelete' : "To undo only deletions, use \"revert --deletions\"",
2676
'missing-from' : "Try \"revisions --missing-from\".",
2677
'missing' : "Try \"revisions --missing\".",
2678
'missing-merge' : "Try \"revisions --partner-missing\".",
2679
'new-merges' : "Try \"revisions --new-merges\".",
2680
'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
2681
'logs' : "Try \"revisions --logs\"",
2682
'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
2683
'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
2684
'change-version' : "Try \"update REVISION\"",
2685
'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
2686
'rev-depends' : "Use revisions --dependencies",
2687
'auto-get' : "Plain get will do archive lookups",
2688
'tagline' : "Use add-id. It uses taglines in tagline trees",
2689
'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
2690
'library-revisions' : "Use revisions --library",
2691
'file-revert' : "Use revert FILE",
2692
'join-branch' : "Use replay --logs-only",
2693
'show-rev' : "Use show-changeset REVISION",
2694
'replay-micro' : "Use replay --micro"
2696
# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7