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
42
__docformat__ = "restructuredtext"
43
__doc__ = "Implementation of user (sub) commands"
46
def find_command(cmd):
48
Return an instance of a command type. Return None if the type isn't
51
:param cmd: the name of the command to look for
52
:type cmd: the type of the command
54
if commands.has_key(cmd):
55
return commands[cmd]()
60
def __call__(self, cmdline):
62
self.do_command(cmdline.split())
63
except cmdutil.GetHelp, e:
68
def get_completer(index):
71
def complete(self, args, text):
73
Returns a list of possible completions for the given text.
75
:param args: The complete list of arguments
76
:type args: List of str
77
:param text: text to complete (may be shorter than args[-1])
90
parser=self.get_parser()
91
if realtext.startswith('-'):
92
candidates = parser.iter_options()
94
(options, parsed_args) = parser.parse_args(args)
96
if len (parsed_args) > 0:
97
candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1)
99
candidates = self.get_completer("", 0)
102
if candidates is None:
104
for candidate in candidates:
105
candidate = str(candidate)
106
if candidate.startswith(realtext):
107
matches.append(candidate[len(realtext)- len(text):])
111
class Help(BaseCommand):
113
Lists commands, prints help messages.
116
self.description="Prints help mesages"
119
def do_command(self, cmdargs):
121
Prints a help message.
123
options, args = self.get_parser().parse_args(cmdargs)
125
raise cmdutil.GetHelp
127
if options.native or options.suggestions or options.external:
128
native = options.native
129
suggestions = options.suggestions
130
external = options.external
137
self.list_commands(native, suggestions, external)
140
command_help(args[0])
144
self.get_parser().print_help()
146
If no command is specified, commands are listed. If a command is
147
specified, help for that command is listed.
150
def get_parser(self):
152
Returns the options parser to use for the "revision" command.
154
:rtype: cmdutil.CmdOptionParser
156
if self.parser is not None:
158
parser=cmdutil.CmdOptionParser("fai help [command]")
159
parser.add_option("-n", "--native", action="store_true",
160
dest="native", help="Show native commands")
161
parser.add_option("-e", "--external", action="store_true",
162
dest="external", help="Show external commands")
163
parser.add_option("-s", "--suggest", action="store_true",
164
dest="suggestions", help="Show suggestions")
168
def list_commands(self, native=True, suggest=False, external=True):
170
Lists supported commands.
172
:param native: list native, python-based commands
174
:param external: list external aba-style commands
178
print "Native Fai commands"
183
for i in range(28-len(k)):
185
print space+k+" : "+commands[k]().description
188
print "Unavailable commands and suggested alternatives"
189
key_list = suggestions.keys()
192
print "%28s : %s" % (key, suggestions[key])
195
fake_aba = abacmds.AbaCmds()
196
if (fake_aba.abadir == ""):
198
print "External commands"
199
fake_aba.list_commands()
202
print "Use help --suggest to list alternatives to tla and aba"\
204
if options.tla_fallthrough and (native or external):
205
print "Fai also supports tla commands."
207
def command_help(cmd):
209
Prints help for a command.
211
:param cmd: The name of the command to print help for
214
fake_aba = abacmds.AbaCmds()
215
cmdobj = find_command(cmd)
218
elif suggestions.has_key(cmd):
219
print "Not available\n" + suggestions[cmd]
221
abacmd = fake_aba.is_command(cmd)
225
print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?"
229
class Changes(BaseCommand):
231
the "changes" command: lists differences between trees/revisions:
235
self.description="Lists what files have changed in the project tree"
237
def get_completer(self, arg, index):
241
tree = arch.tree_root()
244
return cmdutil.iter_revision_completions(arg, tree)
246
def parse_commandline(self, cmdline):
248
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
250
:param cmdline: A list of arguments to parse
251
:rtype: (options, Revision, Revision/WorkingTree)
253
parser=self.get_parser()
254
(options, args) = parser.parse_args(cmdline)
256
raise cmdutil.GetHelp
258
tree=arch.tree_root()
260
a_spec = cmdutil.comp_revision(tree)
262
a_spec = cmdutil.determine_revision_tree(tree, args[0])
263
cmdutil.ensure_archive_registered(a_spec.archive)
265
b_spec = cmdutil.determine_revision_tree(tree, args[1])
266
cmdutil.ensure_archive_registered(b_spec.archive)
269
return options, a_spec, b_spec
271
def do_command(self, cmdargs):
273
Master function that perfoms the "changes" command.
276
options, a_spec, b_spec = self.parse_commandline(cmdargs);
277
except cmdutil.CantDetermineRevision, e:
280
except arch.errors.TreeRootError, e:
283
if options.changeset:
284
changeset=options.changeset
287
tmpdir=cmdutil.tmpdir()
288
changeset=tmpdir+"/changeset"
290
delta=arch.iter_delta(a_spec, b_spec, changeset)
293
if cmdutil.chattermatch(line, "changeset:"):
296
cmdutil.colorize(line, options.suppress_chatter)
297
except arch.util.ExecProblem, e:
298
if e.proc.error and e.proc.error.startswith(
299
"missing explicit id for file"):
306
if (options.perform_diff):
307
chan = cmdutil.ChangesetMunger(changeset)
309
if isinstance(b_spec, arch.Revision):
310
b_dir = b_spec.library_find()
313
a_dir = a_spec.library_find()
314
if options.diffopts is not None:
315
diffopts = options.diffopts.split()
316
cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir)
318
cmdutil.show_diffs(delta.changeset)
320
if tmpdir and (os.access(tmpdir, os.X_OK)):
321
shutil.rmtree(tmpdir)
323
def get_parser(self):
325
Returns the options parser to use for the "changes" command.
327
:rtype: cmdutil.CmdOptionParser
329
parser=cmdutil.CmdOptionParser("fai changes [options] [revision]"
331
parser.add_option("-d", "--diff", action="store_true",
332
dest="perform_diff", default=False,
333
help="Show diffs in summary")
334
parser.add_option("-c", "--changeset", dest="changeset",
335
help="Store a changeset in the given directory",
337
parser.add_option("-s", "--silent", action="store_true",
338
dest="suppress_chatter", default=False,
339
help="Suppress chatter messages")
340
parser.add_option("--diffopts", dest="diffopts",
341
help="Use the specified diff options",
346
def help(self, parser=None):
348
Prints a help message.
350
:param parser: If supplied, the parser to use for generating help. If \
351
not supplied, it is retrieved.
352
:type parser: cmdutil.CmdOptionParser
355
parser=self.get_parser()
358
Performs source-tree comparisons
360
If no revision is specified, the current project tree is compared to the
361
last-committed revision. If one revision is specified, the current project
362
tree is compared to that revision. If two revisions are specified, they are
363
compared to each other.
369
class ApplyChanges(BaseCommand):
371
Apply differences between two revisions to a tree
375
self.description="Applies changes to a project tree"
377
def get_completer(self, arg, index):
381
tree = arch.tree_root()
384
return cmdutil.iter_revision_completions(arg, tree)
386
def parse_commandline(self, cmdline, tree):
388
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
390
:param cmdline: A list of arguments to parse
391
:rtype: (options, Revision, Revision/WorkingTree)
393
parser=self.get_parser()
394
(options, args) = parser.parse_args(cmdline)
396
raise cmdutil.GetHelp
398
a_spec = cmdutil.determine_revision_tree(tree, args[0])
399
cmdutil.ensure_archive_registered(a_spec.archive)
400
b_spec = cmdutil.determine_revision_tree(tree, args[1])
401
cmdutil.ensure_archive_registered(b_spec.archive)
402
return options, a_spec, b_spec
404
def do_command(self, cmdargs):
406
Master function that performs "apply-changes".
409
tree = arch.tree_root()
410
options, a_spec, b_spec = self.parse_commandline(cmdargs, tree);
411
except cmdutil.CantDetermineRevision, e:
414
except arch.errors.TreeRootError, e:
417
delta=cmdutil.apply_delta(a_spec, b_spec, tree)
418
for line in cmdutil.iter_apply_delta_filter(delta):
419
cmdutil.colorize(line, options.suppress_chatter)
421
def get_parser(self):
423
Returns the options parser to use for the "apply-changes" command.
425
:rtype: cmdutil.CmdOptionParser
427
parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision"
429
parser.add_option("-d", "--diff", action="store_true",
430
dest="perform_diff", default=False,
431
help="Show diffs in summary")
432
parser.add_option("-c", "--changeset", dest="changeset",
433
help="Store a changeset in the given directory",
435
parser.add_option("-s", "--silent", action="store_true",
436
dest="suppress_chatter", default=False,
437
help="Suppress chatter messages")
440
def help(self, parser=None):
442
Prints a help message.
444
:param parser: If supplied, the parser to use for generating help. If \
445
not supplied, it is retrieved.
446
:type parser: cmdutil.CmdOptionParser
449
parser=self.get_parser()
452
Applies changes to a project tree
454
Compares two revisions and applies the difference between them to the current
460
class Update(BaseCommand):
462
Updates a project tree to a given revision, preserving un-committed hanges.
466
self.description="Apply the latest changes to the current directory"
468
def get_completer(self, arg, index):
472
tree = arch.tree_root()
475
return cmdutil.iter_revision_completions(arg, tree)
477
def parse_commandline(self, cmdline, tree):
479
Parse commandline arguments. Raises cmdutil.GetHelp if help is needed.
481
:param cmdline: A list of arguments to parse
482
:rtype: (options, Revision, Revision/WorkingTree)
484
parser=self.get_parser()
485
(options, args) = parser.parse_args(cmdline)
487
raise cmdutil.GetHelp
492
revision=cmdutil.determine_revision_arch(tree, spec)
493
cmdutil.ensure_archive_registered(revision.archive)
495
mirror_source = cmdutil.get_mirror_source(revision.archive)
496
if mirror_source != None:
497
if cmdutil.prompt("Mirror update"):
498
cmd=cmdutil.mirror_archive(mirror_source,
499
revision.archive, arch.NameParser(revision).get_package_version())
500
for line in arch.chatter_classifier(cmd):
501
cmdutil.colorize(line, options.suppress_chatter)
503
revision=cmdutil.determine_revision_arch(tree, spec)
505
return options, revision
507
def do_command(self, cmdargs):
509
Master function that perfoms the "update" command.
511
tree=arch.tree_root()
513
options, to_revision = self.parse_commandline(cmdargs, tree);
514
except cmdutil.CantDetermineRevision, e:
517
except arch.errors.TreeRootError, e:
520
from_revision=cmdutil.tree_latest(tree)
521
if from_revision==to_revision:
522
print "Tree is already up to date with:\n"+str(to_revision)+"."
524
cmdutil.ensure_archive_registered(from_revision.archive)
525
cmd=cmdutil.apply_delta(from_revision, to_revision, tree,
526
options.patch_forward)
527
for line in cmdutil.iter_apply_delta_filter(cmd):
528
cmdutil.colorize(line)
529
if to_revision.version != tree.tree_version:
530
if cmdutil.prompt("Update version"):
531
tree.tree_version = to_revision.version
533
def get_parser(self):
535
Returns the options parser to use for the "update" command.
537
:rtype: cmdutil.CmdOptionParser
539
parser=cmdutil.CmdOptionParser("fai update [options]"
540
" [revision/version]")
541
parser.add_option("-f", "--forward", action="store_true",
542
dest="patch_forward", default=False,
543
help="pass the --forward option to 'patch'")
544
parser.add_option("-s", "--silent", action="store_true",
545
dest="suppress_chatter", default=False,
546
help="Suppress chatter messages")
549
def help(self, parser=None):
551
Prints a help message.
553
:param parser: If supplied, the parser to use for generating help. If \
554
not supplied, it is retrieved.
555
:type parser: cmdutil.CmdOptionParser
558
parser=self.get_parser()
561
Updates a working tree to the current archive revision
563
If a revision or version is specified, that is used instead
569
class Commit(BaseCommand):
571
Create a revision based on the changes in the current tree.
575
self.description="Write local changes to the archive"
577
def get_completer(self, arg, index):
580
return iter_modified_file_completions(arch.tree_root(), arg)
581
# return iter_source_file_completions(arch.tree_root(), arg)
583
def parse_commandline(self, cmdline, tree):
585
Parse commandline arguments. Raise cmtutil.GetHelp if help is needed.
587
:param cmdline: A list of arguments to parse
588
:rtype: (options, Revision, Revision/WorkingTree)
590
parser=self.get_parser()
591
(options, args) = parser.parse_args(cmdline)
595
revision=cmdutil.determine_revision_arch(tree, options.version)
596
return options, revision.get_version(), args
598
def do_command(self, cmdargs):
600
Master function that perfoms the "commit" command.
602
tree=arch.tree_root()
603
options, version, files = self.parse_commandline(cmdargs, tree)
604
if options.__dict__.has_key("base") and options.base:
605
base = cmdutil.determine_revision_tree(tree, options.base)
607
base = cmdutil.submit_revision(tree)
610
archive=version.archive
611
source=cmdutil.get_mirror_source(archive)
613
writethrough="implicit"
616
if writethrough=="explicit" and \
617
cmdutil.prompt("Writethrough"):
618
writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch()))
619
elif writethrough=="none":
620
raise CommitToMirror(archive)
622
elif archive.is_mirror:
623
raise CommitToMirror(archive)
626
last_revision=tree.iter_logs(version, True).next().revision
627
except StopIteration, e:
628
if cmdutil.prompt("Import from commit"):
629
return do_import(version)
631
raise NoVersionLogs(version)
632
if last_revision!=version.iter_revisions(True).next():
633
if not cmdutil.prompt("Out of date"):
639
if not cmdutil.has_changed(version):
640
if not cmdutil.prompt("Empty commit"):
642
except arch.util.ExecProblem, e:
643
if e.proc.error and e.proc.error.startswith(
644
"missing explicit id for file"):
648
log = tree.log_message(create=False)
651
if cmdutil.prompt("Create log"):
654
except cmdutil.NoEditorSpecified, e:
655
raise CommandFailed(e)
656
log = tree.log_message(create=False)
659
if log["Summary"] is None or len(log["Summary"].strip()) == 0:
660
if not cmdutil.prompt("Omit log summary"):
661
raise errors.NoLogSummary
663
for line in tree.iter_commit(version, seal=options.seal_version,
664
base=base, out_of_date_ok=allow_old, file_list=files):
665
cmdutil.colorize(line, options.suppress_chatter)
667
except arch.util.ExecProblem, e:
668
if e.proc.error and e.proc.error.startswith(
669
"These files violate naming conventions:"):
670
raise LintFailure(e.proc.error)
674
def get_parser(self):
676
Returns the options parser to use for the "commit" command.
678
:rtype: cmdutil.CmdOptionParser
681
parser=cmdutil.CmdOptionParser("fai commit [options] [file1]"
683
parser.add_option("--seal", action="store_true",
684
dest="seal_version", default=False,
685
help="seal this version")
686
parser.add_option("-v", "--version", dest="version",
687
help="Use the specified version",
689
parser.add_option("-s", "--silent", action="store_true",
690
dest="suppress_chatter", default=False,
691
help="Suppress chatter messages")
692
if cmdutil.supports_switch("commit", "--base"):
693
parser.add_option("--base", dest="base", help="",
697
def help(self, parser=None):
699
Prints a help message.
701
:param parser: If supplied, the parser to use for generating help. If \
702
not supplied, it is retrieved.
703
:type parser: cmdutil.CmdOptionParser
706
parser=self.get_parser()
709
Updates a working tree to the current archive revision
711
If a version is specified, that is used instead
718
class CatLog(BaseCommand):
720
Print the log of a given file (from current tree)
723
self.description="Prints the patch log for a revision"
725
def get_completer(self, arg, index):
729
tree = arch.tree_root()
732
return cmdutil.iter_revision_completions(arg, tree)
734
def do_command(self, cmdargs):
736
Master function that perfoms the "cat-log" command.
738
parser=self.get_parser()
739
(options, args) = parser.parse_args(cmdargs)
741
tree = arch.tree_root()
742
except arch.errors.TreeRootError, e:
748
raise cmdutil.GetHelp()
751
revision = cmdutil.determine_revision_tree(tree, spec)
753
revision = cmdutil.determine_revision_arch(tree, spec)
754
except cmdutil.CantDetermineRevision, e:
755
raise CommandFailedWrapper(e)
758
use_tree = (options.source == "tree" or \
759
(options.source == "any" and tree))
760
use_arch = (options.source == "archive" or options.source == "any")
764
for log in tree.iter_logs(revision.get_version()):
765
if log.revision == revision:
769
if log is None and use_arch:
770
cmdutil.ensure_revision_exists(revision)
771
log = arch.Patchlog(revision)
773
for item in log.items():
774
print "%s: %s" % item
775
print log.description
777
def get_parser(self):
779
Returns the options parser to use for the "cat-log" command.
781
:rtype: cmdutil.CmdOptionParser
783
parser=cmdutil.CmdOptionParser("fai cat-log [revision]")
784
parser.add_option("--archive", action="store_const", dest="source",
785
const="archive", default="any",
786
help="Always get the log from the archive")
787
parser.add_option("--tree", action="store_const", dest="source",
788
const="tree", help="Always get the log from the tree")
791
def help(self, parser=None):
793
Prints a help message.
795
:param parser: If supplied, the parser to use for generating help. If \
796
not supplied, it is retrieved.
797
:type parser: cmdutil.CmdOptionParser
800
parser=self.get_parser()
803
Prints the log for the specified revision
808
class Revert(BaseCommand):
809
""" Reverts a tree (or aspects of it) to a revision
812
self.description="Reverts a tree (or aspects of it) to a revision "
814
def get_completer(self, arg, index):
818
tree = arch.tree_root()
821
return iter_modified_file_completions(tree, arg)
823
def do_command(self, cmdargs):
825
Master function that perfoms the "revert" command.
827
parser=self.get_parser()
828
(options, args) = parser.parse_args(cmdargs)
830
tree = arch.tree_root()
831
except arch.errors.TreeRootError, e:
832
raise CommandFailed(e)
834
if options.revision is not None:
835
spec=options.revision
838
revision = cmdutil.determine_revision_tree(tree, spec)
840
revision = cmdutil.comp_revision(tree)
841
except cmdutil.CantDetermineRevision, e:
842
raise CommandFailedWrapper(e)
845
if options.file_contents or options.file_perms or options.deletions\
846
or options.additions or options.renames or options.hunk_prompt:
847
munger = cmdutil.MungeOpts()
848
munger.hunk_prompt = options.hunk_prompt
850
if len(args) > 0 or options.logs or options.pattern_files or \
853
munger = cmdutil.MungeOpts(True)
854
munger.all_types(True)
856
t_cwd = cmdutil.tree_cwd(tree)
860
name = "./" + t_cwd + name
861
munger.add_keep_file(name);
863
if options.file_perms:
864
munger.file_perms = True
865
if options.file_contents:
866
munger.file_contents = True
867
if options.deletions:
868
munger.deletions = True
869
if options.additions:
870
munger.additions = True
872
munger.renames = True
874
munger.add_keep_pattern('^\./\{arch\}/[^=].*')
876
munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\
877
"/\.arch-inventory$")
878
if options.pattern_files:
879
munger.add_keep_pattern(options.pattern_files)
881
for line in cmdutil.revert(tree, revision, munger,
882
not options.no_output):
883
cmdutil.colorize(line)
886
def get_parser(self):
888
Returns the options parser to use for the "cat-log" command.
890
:rtype: cmdutil.CmdOptionParser
892
parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]")
893
parser.add_option("", "--contents", action="store_true",
894
dest="file_contents",
895
help="Revert file content changes")
896
parser.add_option("", "--permissions", action="store_true",
898
help="Revert file permissions changes")
899
parser.add_option("", "--deletions", action="store_true",
901
help="Restore deleted files")
902
parser.add_option("", "--additions", action="store_true",
904
help="Remove added files")
905
parser.add_option("", "--renames", action="store_true",
907
help="Revert file names")
908
parser.add_option("--hunks", action="store_true",
909
dest="hunk_prompt", default=False,
910
help="Prompt which hunks to revert")
911
parser.add_option("--pattern-files", dest="pattern_files",
912
help="Revert files that match this pattern",
914
parser.add_option("--logs", action="store_true",
915
dest="logs", default=False,
916
help="Revert only logs")
917
parser.add_option("--control-files", action="store_true",
918
dest="control", default=False,
919
help="Revert logs and other control files")
920
parser.add_option("-n", "--no-output", action="store_true",
922
help="Don't keep an undo changeset")
923
parser.add_option("--revision", dest="revision",
924
help="Revert to the specified revision",
928
def help(self, parser=None):
930
Prints a help message.
932
:param parser: If supplied, the parser to use for generating help. If \
933
not supplied, it is retrieved.
934
:type parser: cmdutil.CmdOptionParser
937
parser=self.get_parser()
940
Reverts changes in the current working tree. If no flags are specified, all
941
types of changes are reverted. Otherwise, only selected types of changes are
944
If a revision is specified on the commandline, differences between the current
945
tree and that revision are reverted. If a version is specified, the current
946
tree is used to determine the revision.
948
If files are specified, only those files listed will have any changes applied.
949
To specify a renamed file, you can use either the old or new name. (or both!)
951
Unless "-n" is specified, reversions can be undone with "redo".
955
class Revision(BaseCommand):
957
Print a revision name based on a revision specifier
960
self.description="Prints the name of a revision"
962
def get_completer(self, arg, index):
966
tree = arch.tree_root()
969
return cmdutil.iter_revision_completions(arg, tree)
971
def do_command(self, cmdargs):
973
Master function that perfoms the "revision" command.
975
parser=self.get_parser()
976
(options, args) = parser.parse_args(cmdargs)
979
tree = arch.tree_root()
980
except arch.errors.TreeRootError:
987
raise cmdutil.GetHelp
990
revision = cmdutil.determine_revision_tree(tree, spec)
992
revision = cmdutil.determine_revision_arch(tree, spec)
993
except cmdutil.CantDetermineRevision, e:
996
print options.display(revision)
998
def get_parser(self):
1000
Returns the options parser to use for the "revision" command.
1002
:rtype: cmdutil.CmdOptionParser
1004
parser=cmdutil.CmdOptionParser("fai revision [revision]")
1005
parser.add_option("", "--location", action="store_const",
1006
const=paths.determine_path, dest="display",
1007
help="Show location instead of name", default=str)
1008
parser.add_option("--import", action="store_const",
1009
const=paths.determine_import_path, dest="display",
1010
help="Show location of import file")
1011
parser.add_option("--log", action="store_const",
1012
const=paths.determine_log_path, dest="display",
1013
help="Show location of log file")
1014
parser.add_option("--patch", action="store_const",
1015
dest="display", const=paths.determine_patch_path,
1016
help="Show location of patchfile")
1017
parser.add_option("--continuation", action="store_const",
1018
const=paths.determine_continuation_path,
1020
help="Show location of continuation file")
1021
parser.add_option("--cacherev", action="store_const",
1022
const=paths.determine_cacherev_path, dest="display",
1023
help="Show location of cacherev file")
1026
def help(self, parser=None):
1028
Prints a help message.
1030
:param parser: If supplied, the parser to use for generating help. If \
1031
not supplied, it is retrieved.
1032
:type parser: cmdutil.CmdOptionParser
1035
parser=self.get_parser()
1038
Expands aliases and prints the name of the specified revision. Instead of
1039
the name, several options can be used to print locations. If more than one is
1040
specified, the last one is used.
1045
def require_version_exists(version, spec):
1046
if not version.exists():
1047
raise cmdutil.CantDetermineVersion(spec,
1048
"The version %s does not exist." \
1051
class Revisions(BaseCommand):
1053
Print a revision name based on a revision specifier
1056
self.description="Lists revisions"
1058
def do_command(self, cmdargs):
1060
Master function that perfoms the "revision" command.
1062
(options, args) = self.get_parser().parse_args(cmdargs)
1064
raise cmdutil.GetHelp
1066
self.tree = arch.tree_root()
1067
except arch.errors.TreeRootError:
1070
iter = self.get_iterator(options.type, args, options.reverse,
1072
except cmdutil.CantDetermineRevision, e:
1073
raise CommandFailedWrapper(e)
1075
if options.skip is not None:
1076
iter = cmdutil.iter_skip(iter, int(options.skip))
1078
for revision in iter:
1080
if isinstance(revision, arch.Patchlog):
1082
revision=revision.revision
1083
print options.display(revision)
1084
if log is None and (options.summary or options.creator or
1085
options.date or options.merges):
1086
log = revision.patchlog
1088
print " %s" % log.creator
1090
print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date)
1092
print " %s" % log.summary
1094
showed_title = False
1095
for revision in log.merged_patches:
1096
if not showed_title:
1099
print " %s" % revision
1101
def get_iterator(self, type, args, reverse, modified):
1106
if modified is not None:
1107
iter = cmdutil.modified_iter(modified, self.tree)
1111
return cmdutil.iter_reverse(iter)
1112
elif type == "archive":
1114
if self.tree is None:
1115
raise cmdutil.CantDetermineRevision("",
1116
"Not in a project tree")
1117
version = cmdutil.determine_version_tree(spec, self.tree)
1119
version = cmdutil.determine_version_arch(spec, self.tree)
1120
cmdutil.ensure_archive_registered(version.archive)
1121
require_version_exists(version, spec)
1122
return version.iter_revisions(reverse)
1123
elif type == "cacherevs":
1125
if self.tree is None:
1126
raise cmdutil.CantDetermineRevision("",
1127
"Not in a project tree")
1128
version = cmdutil.determine_version_tree(spec, self.tree)
1130
version = cmdutil.determine_version_arch(spec, self.tree)
1131
cmdutil.ensure_archive_registered(version.archive)
1132
require_version_exists(version, spec)
1133
return cmdutil.iter_cacherevs(version, reverse)
1134
elif type == "library":
1136
if self.tree is None:
1137
raise cmdutil.CantDetermineRevision("",
1138
"Not in a project tree")
1139
version = cmdutil.determine_version_tree(spec, self.tree)
1141
version = cmdutil.determine_version_arch(spec, self.tree)
1142
return version.iter_library_revisions(reverse)
1143
elif type == "logs":
1144
if self.tree is None:
1145
raise cmdutil.CantDetermineRevision("", "Not in a project tree")
1146
return self.tree.iter_logs(cmdutil.determine_version_tree(spec, \
1147
self.tree), reverse)
1148
elif type == "missing" or type == "skip-present":
1149
if self.tree is None:
1150
raise cmdutil.CantDetermineRevision("", "Not in a project tree")
1151
skip = (type == "skip-present")
1152
version = cmdutil.determine_version_tree(spec, self.tree)
1153
cmdutil.ensure_archive_registered(version.archive)
1154
require_version_exists(version, spec)
1155
return cmdutil.iter_missing(self.tree, version, reverse,
1158
elif type == "present":
1159
if self.tree is None:
1160
raise cmdutil.CantDetermineRevision("", "Not in a project tree")
1161
version = cmdutil.determine_version_tree(spec, self.tree)
1162
cmdutil.ensure_archive_registered(version.archive)
1163
require_version_exists(version, spec)
1164
return cmdutil.iter_present(self.tree, version, reverse)
1166
elif type == "new-merges" or type == "direct-merges":
1167
if self.tree is None:
1168
raise cmdutil.CantDetermineRevision("", "Not in a project tree")
1169
version = cmdutil.determine_version_tree(spec, self.tree)
1170
cmdutil.ensure_archive_registered(version.archive)
1171
require_version_exists(version, spec)
1172
iter = cmdutil.iter_new_merges(self.tree, version, reverse)
1173
if type == "new-merges":
1175
elif type == "direct-merges":
1176
return cmdutil.direct_merges(iter)
1178
elif type == "missing-from":
1179
if self.tree is None:
1180
raise cmdutil.CantDetermineRevision("", "Not in a project tree")
1181
revision = cmdutil.determine_revision_tree(self.tree, spec)
1182
libtree = cmdutil.find_or_make_local_revision(revision)
1183
return cmdutil.iter_missing(libtree, self.tree.tree_version,
1186
elif type == "partner-missing":
1187
return cmdutil.iter_partner_missing(self.tree, reverse)
1189
elif type == "ancestry":
1190
revision = cmdutil.determine_revision_tree(self.tree, spec)
1191
iter = cmdutil._iter_ancestry(self.tree, revision)
1195
return cmdutil.iter_reverse(iter)
1197
elif type == "dependencies" or type == "non-dependencies":
1198
nondeps = (type == "non-dependencies")
1199
revision = cmdutil.determine_revision_tree(self.tree, spec)
1200
anc_iter = cmdutil._iter_ancestry(self.tree, revision)
1201
iter_depends = cmdutil.iter_depends(anc_iter, nondeps)
1205
return cmdutil.iter_reverse(iter_depends)
1206
elif type == "micro":
1207
return cmdutil.iter_micro(self.tree)
1210
def get_parser(self):
1212
Returns the options parser to use for the "revision" command.
1214
:rtype: cmdutil.CmdOptionParser
1216
parser=cmdutil.CmdOptionParser("fai revisions [revision]")
1217
select = cmdutil.OptionGroup(parser, "Selection options",
1218
"Control which revisions are listed. These options"
1219
" are mutually exclusive. If more than one is"
1220
" specified, the last is used.")
1221
select.add_option("", "--archive", action="store_const",
1222
const="archive", dest="type", default="archive",
1223
help="List all revisions in the archive")
1224
select.add_option("", "--cacherevs", action="store_const",
1225
const="cacherevs", dest="type",
1226
help="List all revisions stored in the archive as "
1228
select.add_option("", "--logs", action="store_const",
1229
const="logs", dest="type",
1230
help="List revisions that have a patchlog in the "
1232
select.add_option("", "--missing", action="store_const",
1233
const="missing", dest="type",
1234
help="List revisions from the specified version that"
1235
" have no patchlog in the tree")
1236
select.add_option("", "--skip-present", action="store_const",
1237
const="skip-present", dest="type",
1238
help="List revisions from the specified version that"
1239
" have no patchlogs at all in the tree")
1240
select.add_option("", "--present", action="store_const",
1241
const="present", dest="type",
1242
help="List revisions from the specified version that"
1243
" have no patchlog in the tree, but can't be merged")
1244
select.add_option("", "--missing-from", action="store_const",
1245
const="missing-from", dest="type",
1246
help="List revisions from the specified revision "
1247
"that have no patchlog for the tree version")
1248
select.add_option("", "--partner-missing", action="store_const",
1249
const="partner-missing", dest="type",
1250
help="List revisions in partner versions that are"
1252
select.add_option("", "--new-merges", action="store_const",
1253
const="new-merges", dest="type",
1254
help="List revisions that have had patchlogs added"
1255
" to the tree since the last commit")
1256
select.add_option("", "--direct-merges", action="store_const",
1257
const="direct-merges", dest="type",
1258
help="List revisions that have been directly added"
1259
" to tree since the last commit ")
1260
select.add_option("", "--library", action="store_const",
1261
const="library", dest="type",
1262
help="List revisions in the revision library")
1263
select.add_option("", "--ancestry", action="store_const",
1264
const="ancestry", dest="type",
1265
help="List revisions that are ancestors of the "
1266
"current tree version")
1268
select.add_option("", "--dependencies", action="store_const",
1269
const="dependencies", dest="type",
1270
help="List revisions that the given revision "
1273
select.add_option("", "--non-dependencies", action="store_const",
1274
const="non-dependencies", dest="type",
1275
help="List revisions that the given revision "
1276
"does not depend on")
1278
select.add_option("--micro", action="store_const",
1279
const="micro", dest="type",
1280
help="List partner revisions aimed for this "
1283
select.add_option("", "--modified", dest="modified",
1284
help="List tree ancestor revisions that modified a "
1285
"given file", metavar="FILE[:LINE]")
1287
parser.add_option("", "--skip", dest="skip",
1288
help="Skip revisions. Positive numbers skip from "
1289
"beginning, negative skip from end.",
1292
parser.add_option_group(select)
1294
format = cmdutil.OptionGroup(parser, "Revision format options",
1295
"These control the appearance of listed revisions")
1296
format.add_option("", "--location", action="store_const",
1297
const=paths.determine_path, dest="display",
1298
help="Show location instead of name", default=str)
1299
format.add_option("--import", action="store_const",
1300
const=paths.determine_import_path, dest="display",
1301
help="Show location of import file")
1302
format.add_option("--log", action="store_const",
1303
const=paths.determine_log_path, dest="display",
1304
help="Show location of log file")
1305
format.add_option("--patch", action="store_const",
1306
dest="display", const=paths.determine_patch_path,
1307
help="Show location of patchfile")
1308
format.add_option("--continuation", action="store_const",
1309
const=paths.determine_continuation_path,
1311
help="Show location of continuation file")
1312
format.add_option("--cacherev", action="store_const",
1313
const=paths.determine_cacherev_path, dest="display",
1314
help="Show location of cacherev file")
1315
parser.add_option_group(format)
1316
display = cmdutil.OptionGroup(parser, "Display format options",
1317
"These control the display of data")
1318
display.add_option("-r", "--reverse", action="store_true",
1319
dest="reverse", help="Sort from newest to oldest")
1320
display.add_option("-s", "--summary", action="store_true",
1321
dest="summary", help="Show patchlog summary")
1322
display.add_option("-D", "--date", action="store_true",
1323
dest="date", help="Show patchlog date")
1324
display.add_option("-c", "--creator", action="store_true",
1325
dest="creator", help="Show the id that committed the"
1327
display.add_option("-m", "--merges", action="store_true",
1328
dest="merges", help="Show the revisions that were"
1330
parser.add_option_group(display)
1332
def help(self, parser=None):
1333
"""Attempt to explain the revisions command
1335
:param parser: If supplied, used to determine options
1338
parser=self.get_parser()
1340
print """List revisions.
1345
class Get(BaseCommand):
1347
Retrieve a revision from the archive
1350
self.description="Retrieve a revision from the archive"
1351
self.parser=self.get_parser()
1354
def get_completer(self, arg, index):
1358
tree = arch.tree_root()
1361
return cmdutil.iter_revision_completions(arg, tree)
1364
def do_command(self, cmdargs):
1366
Master function that perfoms the "get" command.
1368
(options, args) = self.parser.parse_args(cmdargs)
1372
tree = arch.tree_root()
1373
except arch.errors.TreeRootError:
1378
revision, arch_loc = paths.full_path_decode(args[0])
1379
except Exception, e:
1380
revision = cmdutil.determine_revision_arch(tree, args[0],
1381
check_existence=False, allow_package=True)
1385
directory = str(revision.nonarch)
1386
if os.path.exists(directory):
1387
raise DirectoryExists(directory)
1388
cmdutil.ensure_archive_registered(revision.archive, arch_loc)
1390
cmdutil.ensure_revision_exists(revision)
1391
except cmdutil.NoSuchRevision, e:
1392
raise CommandFailedWrapper(e)
1394
link = cmdutil.prompt ("get link")
1395
for line in cmdutil.iter_get(revision, directory, link,
1396
options.no_pristine,
1397
options.no_greedy_add):
1398
cmdutil.colorize(line)
1400
def get_parser(self):
1402
Returns the options parser to use for the "get" command.
1404
:rtype: cmdutil.CmdOptionParser
1406
parser=cmdutil.CmdOptionParser("fai get revision [dir]")
1407
parser.add_option("--no-pristine", action="store_true",
1409
help="Do not make pristine copy for reference")
1410
parser.add_option("--no-greedy-add", action="store_true",
1411
dest="no_greedy_add",
1412
help="Never add to greedy libraries")
1416
def help(self, parser=None):
1418
Prints a help message.
1420
:param parser: If supplied, the parser to use for generating help. If \
1421
not supplied, it is retrieved.
1422
:type parser: cmdutil.CmdOptionParser
1425
parser=self.get_parser()
1428
Expands aliases and constructs a project tree for a revision. If the optional
1429
"dir" argument is provided, the project tree will be stored in this directory.
1434
class PromptCmd(cmd.Cmd):
1436
cmd.Cmd.__init__(self)
1437
self.prompt = "Fai> "
1439
self.tree = arch.tree_root()
1444
self.fake_aba = abacmds.AbaCmds()
1445
self.identchars += '-'
1446
self.history_file = os.path.expanduser("~/.fai-history")
1447
readline.set_completer_delims(string.whitespace)
1448
if os.access(self.history_file, os.R_OK) and \
1449
os.path.isfile(self.history_file):
1450
readline.read_history_file(self.history_file)
1452
def write_history(self):
1453
readline.write_history_file(self.history_file)
1455
def do_quit(self, args):
1456
self.write_history()
1459
def do_exit(self, args):
1462
def do_EOF(self, args):
1466
def postcmd(self, line, bar):
1470
def set_prompt(self):
1471
if self.tree is not None:
1473
version = " "+self.tree.tree_version.nonarch
1478
self.prompt = "Fai%s> " % version
1480
def set_title(self, command=None):
1482
version = self.tree.tree_version.nonarch
1484
version = "[no version]"
1487
sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version)))
1489
def do_cd(self, line):
1493
os.chdir(os.path.expanduser(line))
1494
except Exception, e:
1497
self.tree = arch.tree_root()
1501
def do_help(self, line):
1504
def default(self, line):
1506
if find_command(args[0]):
1508
find_command(args[0]).do_command(args[1:])
1509
except cmdutil.BadCommandOption, e:
1511
except cmdutil.GetHelp, e:
1512
find_command(args[0]).help()
1513
except CommandFailed, e:
1515
except arch.errors.ArchiveNotRegistered, e:
1517
except KeyboardInterrupt, e:
1519
except arch.util.ExecProblem, e:
1520
print e.proc.error.rstrip('\n')
1521
except cmdutil.CantDetermineVersion, e:
1523
except cmdutil.CantDetermineRevision, e:
1525
except Exception, e:
1526
print "Unhandled error:\n%s" % cmdutil.exception_str(e)
1528
elif suggestions.has_key(args[0]):
1529
print suggestions[args[0]]
1531
elif self.fake_aba.is_command(args[0]):
1534
tree = arch.tree_root()
1535
except arch.errors.TreeRootError:
1537
cmd = self.fake_aba.is_command(args[0])
1539
cmd.run(cmdutil.expand_prefix_alias(args[1:], tree))
1540
except KeyboardInterrupt, e:
1543
elif options.tla_fallthrough and args[0] != "rm" and \
1544
cmdutil.is_tla_command(args[0]):
1548
tree = arch.tree_root()
1549
except arch.errors.TreeRootError:
1551
args = cmdutil.expand_prefix_alias(args, tree)
1552
arch.util.exec_safe('tla', args, stderr=sys.stderr,
1554
except arch.util.ExecProblem, e:
1556
except KeyboardInterrupt, e:
1561
tree = arch.tree_root()
1562
except arch.errors.TreeRootError:
1565
os.system(" ".join(cmdutil.expand_prefix_alias(args, tree)))
1566
except KeyboardInterrupt, e:
1569
def completenames(self, text, line, begidx, endidx):
1571
iter = iter_command_names(self.fake_aba)
1574
arg = line.split()[-1]
1577
iter = iter_munged_completions(iter, arg, text)
1578
except Exception, e:
1582
def completedefault(self, text, line, begidx, endidx):
1583
"""Perform completion for native commands.
1585
:param text: The text to complete
1587
:param line: The entire line to complete
1589
:param begidx: The start of the text in the line
1591
:param endidx: The end of the text in the line
1595
(cmd, args, foo) = self.parseline(line)
1596
command_obj=find_command(cmd)
1597
if command_obj is not None:
1598
return command_obj.complete(args.split(), text)
1599
elif not self.fake_aba.is_command(cmd) and \
1600
cmdutil.is_tla_command(cmd):
1601
iter = cmdutil.iter_supported_switches(cmd)
1603
arg = args.split()[-1]
1606
if arg.startswith("-"):
1607
return list(iter_munged_completions(iter, arg, text))
1609
return list(iter_munged_completions(
1610
iter_file_completions(arg), arg, text))
1615
arg = args.split()[-1]
1618
iter = iter_dir_completions(arg)
1619
iter = iter_munged_completions(iter, arg, text)
1622
arg = args.split()[-1]
1623
return list(iter_munged_completions(iter_file_completions(arg),
1626
return self.completenames(text, line, begidx, endidx)
1627
except Exception, e:
1631
def iter_command_names(fake_aba):
1632
for entry in cmdutil.iter_combine([commands.iterkeys(),
1633
fake_aba.get_commands(),
1634
cmdutil.iter_tla_commands(False)]):
1635
if not suggestions.has_key(str(entry)):
1639
def iter_file_completions(arg, only_dirs = False):
1640
"""Generate an iterator that iterates through filename completions.
1642
:param arg: The filename fragment to match
1644
:param only_dirs: If true, match only directories
1645
:type only_dirs: bool
1649
extras = [".", ".."]
1652
(dir, file) = os.path.split(arg)
1654
listingdir = os.path.expanduser(dir)
1657
for file in cmdutil.iter_combine([os.listdir(listingdir), extras]):
1659
userfile = dir+'/'+file
1662
if userfile.startswith(arg):
1663
if os.path.isdir(listingdir+'/'+file):
1669
def iter_munged_completions(iter, arg, text):
1670
for completion in iter:
1671
completion = str(completion)
1672
if completion.startswith(arg):
1673
yield completion[len(arg)-len(text):]
1675
def iter_source_file_completions(tree, arg):
1676
treepath = cmdutil.tree_cwd(tree)
1677
if len(treepath) > 0:
1681
for file in tree.iter_inventory(dirs, source=True, both=True):
1682
file = file_completion_match(file, treepath, arg)
1683
if file is not None:
1687
def iter_untagged(tree, dirs):
1688
for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False,
1689
categories=arch_core.non_root,
1690
control_files=True):
1694
def iter_untagged_completions(tree, arg):
1695
"""Generate an iterator for all visible untagged files that match arg.
1697
:param tree: The tree to look for untagged files in
1698
:type tree: `arch.WorkingTree`
1699
:param arg: The argument to match
1701
:return: An iterator of all matching untagged files
1702
:rtype: iterator of str
1704
treepath = cmdutil.tree_cwd(tree)
1705
if len(treepath) > 0:
1710
for file in iter_untagged(tree, dirs):
1711
file = file_completion_match(file, treepath, arg)
1712
if file is not None:
1716
def file_completion_match(file, treepath, arg):
1717
"""Determines whether a file within an arch tree matches the argument.
1719
:param file: The rooted filename
1721
:param treepath: The path to the cwd within the tree
1723
:param arg: The prefix to match
1724
:return: The completion name, or None if not a match
1727
if not file.startswith(treepath):
1730
file = file[len(treepath)+1:]
1732
if not file.startswith(arg):
1734
if os.path.isdir(file):
1738
def iter_modified_file_completions(tree, arg):
1739
"""Returns a list of modified files that match the specified prefix.
1741
:param tree: The current tree
1742
:type tree: `arch.WorkingTree`
1743
:param arg: The prefix to match
1746
treepath = cmdutil.tree_cwd(tree)
1747
tmpdir = cmdutil.tmpdir()
1748
changeset = tmpdir+"/changeset"
1750
revision = cmdutil.determine_revision_tree(tree)
1751
for line in arch.iter_delta(revision, tree, changeset):
1752
if isinstance(line, arch.FileModification):
1753
file = file_completion_match(line.name[1:], treepath, arg)
1754
if file is not None:
1755
completions.append(file)
1756
shutil.rmtree(tmpdir)
1759
def iter_dir_completions(arg):
1760
"""Generate an iterator that iterates through directory name completions.
1762
:param arg: The directory name fragment to match
1765
return iter_file_completions(arg, True)
1767
class Shell(BaseCommand):
1769
self.description = "Runs Fai as a shell"
1771
def do_command(self, cmdargs):
1773
raise cmdutil.GetHelp
1774
prompt = PromptCmd()
1778
prompt.write_history()
1780
class AddID(BaseCommand):
1782
Adds an inventory id for the given file
1785
self.description="Add an inventory id for a given file"
1787
def get_completer(self, arg, index):
1788
tree = arch.tree_root()
1789
return iter_untagged_completions(tree, arg)
1791
def do_command(self, cmdargs):
1793
Master function that perfoms the "revision" command.
1795
parser=self.get_parser()
1796
(options, args) = parser.parse_args(cmdargs)
1798
tree = arch.tree_root()
1800
if (len(args) == 0) == (options.untagged == False):
1801
raise cmdutil.GetHelp
1803
#if options.id and len(args) != 1:
1804
# print "If --id is specified, only one file can be named."
1807
method = tree.tagging_method
1809
if options.id_type == "tagline":
1810
if method != "tagline":
1811
if not cmdutil.prompt("Tagline in other tree"):
1812
if method == "explicit":
1813
options.id_type == explicit
1815
print "add-id not supported for \"%s\" tagging method"\
1819
elif options.id_type == "explicit":
1820
if method != "tagline" and method != explicit:
1821
if not prompt("Explicit in other tree"):
1822
print "add-id not supported for \"%s\" tagging method" % \
1826
if options.id_type == "auto":
1827
if method != "tagline" and method != "explicit":
1828
print "add-id not supported for \"%s\" tagging method" % method
1831
options.id_type = method
1832
if options.untagged:
1834
self.add_ids(tree, options.id_type, args)
1836
def add_ids(self, tree, id_type, files=()):
1837
"""Add inventory ids to files.
1839
:param tree: the tree the files are in
1840
:type tree: `arch.WorkingTree`
1841
:param id_type: the type of id to add: "explicit" or "tagline"
1843
:param files: The list of files to add. If None do all untagged.
1844
:type files: tuple of str
1847
untagged = (files is None)
1849
files = list(iter_untagged(tree, None))
1851
while len(files) > 0:
1852
previous_files.extend(files)
1853
if id_type == "explicit":
1854
cmdutil.add_id(files)
1855
elif id_type == "tagline":
1858
cmdutil.add_tagline_or_explicit_id(file)
1859
except cmdutil.AlreadyTagged:
1860
print "\"%s\" already has a tagline." % file
1861
except cmdutil.NoCommentSyntax:
1863
#do inventory after tagging until no untagged files are encountered
1866
for file in iter_untagged(tree, None):
1867
if not file in previous_files:
1873
def get_parser(self):
1875
Returns the options parser to use for the "revision" command.
1877
:rtype: cmdutil.CmdOptionParser
1879
parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...")
1880
# ddaa suggests removing this to promote GUIDs. Let's see who squalks.
1881
# parser.add_option("-i", "--id", dest="id",
1882
# help="Specify id for a single file", default=None)
1883
parser.add_option("--tltl", action="store_true",
1884
dest="lord_style", help="Use Tom Lord's style of id.")
1885
parser.add_option("--explicit", action="store_const",
1886
const="explicit", dest="id_type",
1887
help="Use an explicit id", default="auto")
1888
parser.add_option("--tagline", action="store_const",
1889
const="tagline", dest="id_type",
1890
help="Use a tagline id")
1891
parser.add_option("--untagged", action="store_true",
1892
dest="untagged", default=False,
1893
help="tag all untagged files")
1896
def help(self, parser=None):
1898
Prints a help message.
1900
:param parser: If supplied, the parser to use for generating help. If \
1901
not supplied, it is retrieved.
1902
:type parser: cmdutil.CmdOptionParser
1905
parser=self.get_parser()
1908
Adds an inventory to the specified file(s) and directories. If --untagged is
1909
specified, adds inventory to all untagged files and directories.
1914
class Merge(BaseCommand):
1916
Merges changes from other versions into the current tree
1919
self.description="Merges changes from other versions"
1921
self.tree = arch.tree_root()
1926
def get_completer(self, arg, index):
1927
if self.tree is None:
1928
raise arch.errors.TreeRootError
1929
completions = list(ancillary.iter_partners(self.tree,
1930
self.tree.tree_version))
1931
if len(completions) == 0:
1932
completions = list(self.tree.iter_log_versions())
1936
for completion in completions:
1937
alias = ancillary.compact_alias(str(completion), self.tree)
1939
aliases.extend(alias)
1941
for completion in completions:
1942
if completion.archive == self.tree.tree_version.archive:
1943
aliases.append(completion.nonarch)
1945
except Exception, e:
1948
completions.extend(aliases)
1951
def do_command(self, cmdargs):
1953
Master function that perfoms the "merge" command.
1955
parser=self.get_parser()
1956
(options, args) = parser.parse_args(cmdargs)
1960
action = options.action
1962
if self.tree is None:
1963
raise arch.errors.TreeRootError(os.getcwd())
1964
if cmdutil.has_changed(self.tree.tree_version):
1965
raise UncommittedChanges(self.tree)
1970
revisions.append(cmdutil.determine_revision_arch(self.tree,
1972
source = "from commandline"
1974
revisions = ancillary.iter_partner_revisions(self.tree,
1975
self.tree.tree_version)
1976
source = "from partner version"
1977
revisions = misc.rewind_iterator(revisions)
1981
except StopIteration, e:
1982
revision = cmdutil.tag_cur(self.tree)
1983
if revision is None:
1984
raise CantDetermineRevision("", "No version specified, no "
1985
"partner-versions, and no tag"
1987
revisions = [revision]
1988
source = "from tag source"
1989
for revision in revisions:
1990
cmdutil.ensure_archive_registered(revision.archive)
1991
cmdutil.colorize(arch.Chatter("* Merging %s [%s]" %
1992
(revision, source)))
1993
if action=="native-merge" or action=="update":
1994
if self.native_merge(revision, action) == 0:
1996
elif action=="star-merge":
1998
self.star_merge(revision, options.diff3)
1999
except errors.MergeProblem, e:
2001
if cmdutil.has_changed(self.tree.tree_version):
2004
def star_merge(self, revision, diff3):
2005
"""Perform a star-merge on the current tree.
2007
:param revision: The revision to use for the merge
2008
:type revision: `arch.Revision`
2009
:param diff3: If true, do a diff3 merge
2013
for line in self.tree.iter_star_merge(revision, diff3=diff3):
2014
cmdutil.colorize(line)
2015
except arch.util.ExecProblem, e:
2016
if e.proc.status is not None and e.proc.status == 1:
2023
def native_merge(self, other_revision, action):
2024
"""Perform a native-merge on the current tree.
2026
:param other_revision: The revision to use for the merge
2027
:type other_revision: `arch.Revision`
2028
:return: 0 if the merge was skipped, 1 if it was applied
2030
other_tree = cmdutil.find_or_make_local_revision(other_revision)
2032
if action == "native-merge":
2033
ancestor = cmdutil.merge_ancestor2(self.tree, other_tree,
2035
elif action == "update":
2036
ancestor = cmdutil.tree_latest(self.tree,
2037
other_revision.version)
2038
except CantDetermineRevision, e:
2039
raise CommandFailedWrapper(e)
2040
cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor))
2041
if (ancestor == other_revision):
2042
cmdutil.colorize(arch.Chatter("* Skipping redundant merge"
2045
delta = cmdutil.apply_delta(ancestor, other_tree, self.tree)
2046
for line in cmdutil.iter_apply_delta_filter(delta):
2047
cmdutil.colorize(line)
2052
def get_parser(self):
2054
Returns the options parser to use for the "merge" command.
2056
:rtype: cmdutil.CmdOptionParser
2058
parser=cmdutil.CmdOptionParser("fai merge [VERSION]")
2059
parser.add_option("-s", "--star-merge", action="store_const",
2060
dest="action", help="Use star-merge",
2061
const="star-merge", default="native-merge")
2062
parser.add_option("--update", action="store_const",
2063
dest="action", help="Use update picker",
2065
parser.add_option("--diff3", action="store_true",
2067
help="Use diff3 for merge (implies star-merge)")
2070
def help(self, parser=None):
2072
Prints a help message.
2074
:param parser: If supplied, the parser to use for generating help. If \
2075
not supplied, it is retrieved.
2076
:type parser: cmdutil.CmdOptionParser
2079
parser=self.get_parser()
2082
Performs a merge operation using the specified version.
2086
class ELog(BaseCommand):
2088
Produces a raw patchlog and invokes the user's editor
2091
self.description="Edit a patchlog to commit"
2093
self.tree = arch.tree_root()
2098
def do_command(self, cmdargs):
2100
Master function that perfoms the "elog" command.
2102
parser=self.get_parser()
2103
(options, args) = parser.parse_args(cmdargs)
2104
if self.tree is None:
2105
raise arch.errors.TreeRootError
2109
def get_parser(self):
2111
Returns the options parser to use for the "merge" command.
2113
:rtype: cmdutil.CmdOptionParser
2115
parser=cmdutil.CmdOptionParser("fai elog")
2119
def help(self, parser=None):
2121
Invokes $EDITOR to produce a log for committing.
2123
:param parser: If supplied, the parser to use for generating help. If \
2124
not supplied, it is retrieved.
2125
:type parser: cmdutil.CmdOptionParser
2128
parser=self.get_parser()
2131
Invokes $EDITOR to produce a log for committing.
2136
"""Makes and edits the log for a tree. Does all kinds of fancy things
2137
like log templates and merge summaries and log-for-merge
2139
:param tree: The tree to edit the log for
2140
:type tree: `arch.WorkingTree`
2142
#ensure we have an editor before preparing the log
2143
cmdutil.find_editor()
2144
log = tree.log_message(create=False)
2146
if log is None or cmdutil.prompt("Overwrite log"):
2149
log = tree.log_message(create=True)
2152
template = tree+"/{arch}/=log-template"
2153
if not os.path.exists(template):
2154
template = os.path.expanduser("~/.arch-params/=log-template")
2155
if not os.path.exists(template):
2158
shutil.copyfile(template, tmplog)
2160
new_merges = list(cmdutil.iter_new_merges(tree,
2162
log["Summary"] = merge_summary(new_merges, tree.tree_version)
2163
if len(new_merges) > 0:
2164
if cmdutil.prompt("Log for merge"):
2165
mergestuff = cmdutil.log_for_merge(tree)
2166
log.description += mergestuff
2169
cmdutil.invoke_editor(log.name)
2175
def merge_summary(new_merges, tree_version):
2176
if len(new_merges) == 0:
2178
if len(new_merges) == 1:
2179
summary = new_merges[0].summary
2184
for merge in new_merges:
2185
if arch.my_id() != merge.creator:
2186
name = re.sub("<.*>", "", merge.creator).rstrip(" ");
2187
if not name in credits:
2188
credits.append(name)
2190
version = merge.revision.version
2191
if version.archive == tree_version.archive:
2192
if not version.nonarch in credits:
2193
credits.append(version.nonarch)
2194
elif not str(version) in credits:
2195
credits.append(str(version))
2197
return ("%s (%s)") % (summary, ", ".join(credits))
2199
class MirrorArchive(BaseCommand):
2201
Updates a mirror from an archive
2204
self.description="Update a mirror from an archive"
2206
def do_command(self, cmdargs):
2208
Master function that perfoms the "revision" command.
2211
parser=self.get_parser()
2212
(options, args) = parser.parse_args(cmdargs)
2216
tree = arch.tree_root()
2221
if tree is not None:
2222
name = tree.tree_version()
2224
name = cmdutil.expand_alias(args[0], tree)
2225
name = arch.NameParser(name)
2227
to_arch = name.get_archive()
2228
from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch))
2229
limit = name.get_nonarch()
2231
iter = arch_core.mirror_archive(from_arch,to_arch, limit)
2232
for line in arch.chatter_classifier(iter):
2233
cmdutil.colorize(line)
2235
def get_parser(self):
2237
Returns the options parser to use for the "revision" command.
2239
:rtype: cmdutil.CmdOptionParser
2241
parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE")
2244
def help(self, parser=None):
2246
Prints a help message.
2248
:param parser: If supplied, the parser to use for generating help. If \
2249
not supplied, it is retrieved.
2250
:type parser: cmdutil.CmdOptionParser
2253
parser=self.get_parser()
2256
Updates a mirror from an archive. If a branch, package, or version is
2257
supplied, only changes under it are mirrored.
2261
def help_tree_spec():
2262
print """Specifying revisions (default: tree)
2263
Revisions may be specified by alias, revision, version or patchlevel.
2264
Revisions or versions may be fully qualified. Unqualified revisions, versions,
2265
or patchlevels use the archive of the current project tree. Versions will
2266
use the latest patchlevel in the tree. Patchlevels will use the current tree-
2269
Use "alias" to list available (user and automatic) aliases."""
2271
def help_aliases(tree):
2272
print """Auto-generated aliases
2273
acur : The latest revision in the archive of the tree-version. You can specfy
2274
a different version like so: acur:foo--bar--0 (aliases can be used)
2275
tcur : (tree current) The latest revision in the tree of the tree-version.
2276
You can specify a different version like so: tcur:foo--bar--0 (aliases
2278
tprev : (tree previous) The previous revision in the tree of the tree-version.
2279
To specify an older revision, use a number, e.g. "tprev:4"
2280
tanc : (tree ancestor) The ancestor revision of the tree
2281
To specify an older revision, use a number, e.g. "tanc:4"
2282
tdate : (tree date) The latest revision from a given date (e.g. "tdate:July 6")
2283
tmod : (tree modified) The latest revision to modify a given file
2284
(e.g. "tmod:engine.cpp" or "tmod:engine.cpp:16")
2285
ttag : (tree tag) The revision that was tagged into the current tree revision,
2286
according to the tree.
2287
tagcur: (tag current) The latest revision of the version that the current tree
2289
mergeanc : The common ancestor of the current tree and the specified revision.
2290
Defaults to the first partner-version's latest revision or to tagcur.
2292
print "User aliases"
2293
for parts in ancillary.iter_all_alias(tree):
2294
print parts[0].rjust(10)+" : "+parts[1]
2297
class Inventory(BaseCommand):
2298
"""List the status of files in the tree"""
2300
self.description=self.__doc__
2302
def do_command(self, cmdargs):
2304
Master function that perfoms the "revision" command.
2307
parser=self.get_parser()
2308
(options, args) = parser.parse_args(cmdargs)
2309
tree = arch.tree_root()
2312
if (options.source):
2313
categories.append(arch_core.SourceFile)
2314
if (options.precious):
2315
categories.append(arch_core.PreciousFile)
2316
if (options.backup):
2317
categories.append(arch_core.BackupFile)
2319
categories.append(arch_core.JunkFile)
2321
if len(categories) == 1:
2322
show_leading = False
2326
if len(categories) == 0:
2329
if options.untagged:
2330
categories = arch_core.non_root
2331
show_leading = False
2336
for file in arch_core.iter_inventory_filter(tree, None,
2337
control_files=options.control_files,
2338
categories = categories, tagged=tagged):
2339
print arch_core.file_line(file,
2340
category = show_leading,
2341
untagged = show_leading,
2344
def get_parser(self):
2346
Returns the options parser to use for the "revision" command.
2348
:rtype: cmdutil.CmdOptionParser
2350
parser=cmdutil.CmdOptionParser("fai inventory [options]")
2351
parser.add_option("--ids", action="store_true", dest="ids",
2352
help="Show file ids")
2353
parser.add_option("--control", action="store_true",
2354
dest="control_files", help="include control files")
2355
parser.add_option("--source", action="store_true", dest="source",
2356
help="List source files")
2357
parser.add_option("--backup", action="store_true", dest="backup",
2358
help="List backup files")
2359
parser.add_option("--precious", action="store_true", dest="precious",
2360
help="List precious files")
2361
parser.add_option("--junk", action="store_true", dest="junk",
2362
help="List junk files")
2363
parser.add_option("--unrecognized", action="store_true",
2364
dest="unrecognized", help="List unrecognized files")
2365
parser.add_option("--untagged", action="store_true",
2366
dest="untagged", help="List only untagged files")
2369
def help(self, parser=None):
2371
Prints a help message.
2373
:param parser: If supplied, the parser to use for generating help. If \
2374
not supplied, it is retrieved.
2375
:type parser: cmdutil.CmdOptionParser
2378
parser=self.get_parser()
2381
Lists the status of files in the archive:
2389
Leading letter are not displayed if only one kind of file is shown
2394
class Alias(BaseCommand):
2395
"""List or adjust aliases"""
2397
self.description=self.__doc__
2399
def get_completer(self, arg, index):
2403
self.tree = arch.tree_root()
2408
return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)]
2410
return cmdutil.iter_revision_completions(arg, self.tree)
2413
def do_command(self, cmdargs):
2415
Master function that perfoms the "revision" command.
2418
parser=self.get_parser()
2419
(options, args) = parser.parse_args(cmdargs)
2421
self.tree = arch.tree_root()
2427
options.action(args, options)
2428
except cmdutil.ForbiddenAliasSyntax, e:
2429
raise CommandFailedWrapper(e)
2431
def arg_dispatch(self, args, options):
2432
"""Add, modify, or list aliases, depending on number of arguments
2434
:param args: The list of commandline arguments
2435
:type args: list of str
2436
:param options: The commandline options
2439
help_aliases(self.tree)
2441
elif len(args) == 1:
2442
self.print_alias(args[0])
2443
elif (len(args)) == 2:
2444
self.add(args[0], args[1], options)
2446
raise cmdutil.GetHelp
2448
def print_alias(self, alias):
2450
for pair in ancillary.iter_all_alias(self.tree):
2451
if pair[0] == alias:
2453
if answer is not None:
2456
print "The alias %s is not assigned." % alias
2458
def add(self, alias, expansion, options):
2459
"""Add or modify aliases
2461
:param alias: The alias name to create/modify
2463
:param expansion: The expansion to assign to the alias name
2464
:type expansion: str
2465
:param options: The commandline options
2469
new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion,
2471
ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion])
2473
for pair in self.get_iterator(options):
2474
if pair[0] != alias:
2475
newlist+="%s=%s\n" % (pair[0], pair[1])
2481
self.write_aliases(newlist, options)
2483
def delete(self, args, options):
2484
"""Delete the specified alias
2486
:param args: The list of arguments
2487
:type args: list of str
2488
:param options: The commandline options
2492
raise cmdutil.GetHelp
2494
for pair in self.get_iterator(options):
2495
if pair[0] != args[0]:
2496
newlist+="%s=%s\n" % (pair[0], pair[1])
2500
raise errors.NoSuchAlias(args[0])
2501
self.write_aliases(newlist, options)
2503
def get_alias_file(self, options):
2504
"""Return the name of the alias file to use
2506
:param options: The commandline options
2509
if self.tree is None:
2510
self.tree == arch.tree_root()
2511
return str(self.tree)+"/{arch}/+aliases"
2513
return "~/.aba/aliases"
2515
def get_iterator(self, options):
2516
"""Return the alias iterator to use
2518
:param options: The commandline options
2520
return ancillary.iter_alias(self.get_alias_file(options))
2522
def write_aliases(self, newlist, options):
2523
"""Safely rewrite the alias file
2524
:param newlist: The new list of aliases
2526
:param options: The commandline options
2528
filename = os.path.expanduser(self.get_alias_file(options))
2529
file = cmdutil.NewFileVersion(filename)
2534
def get_parser(self):
2536
Returns the options parser to use for the "alias" command.
2538
:rtype: cmdutil.CmdOptionParser
2540
parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]")
2541
parser.add_option("-d", "--delete", action="store_const", dest="action",
2542
const=self.delete, default=self.arg_dispatch,
2543
help="Delete an alias")
2544
parser.add_option("--tree", action="store_true", dest="tree",
2545
help="Create a per-tree alias", default=False)
2548
def help(self, parser=None):
2550
Prints a help message.
2552
:param parser: If supplied, the parser to use for generating help. If \
2553
not supplied, it is retrieved.
2554
:type parser: cmdutil.CmdOptionParser
2557
parser=self.get_parser()
2560
Lists current aliases or modifies the list of aliases.
2562
If no arguments are supplied, aliases will be listed. If two arguments are
2563
supplied, the specified alias will be created or modified. If -d or --delete
2564
is supplied, the specified alias will be deleted.
2566
You can create aliases that refer to any fully-qualified part of the
2567
Arch namespace, e.g.
2570
archive/category--branch,
2571
archive/category--branch--version (my favourite)
2572
archive/category--branch--version--patchlevel
2574
Aliases can be used automatically by native commands. To use them
2575
with external or tla commands, prefix them with ^ (you can do this
2576
with native commands, too).
2580
class RequestMerge(BaseCommand):
2581
"""Submit a merge request to Bug Goo"""
2583
self.description=self.__doc__
2585
def do_command(self, cmdargs):
2586
"""Submit a merge request
2588
:param cmdargs: The commandline arguments
2589
:type cmdargs: list of str
2591
cmdutil.find_editor()
2592
parser = self.get_parser()
2593
(options, args) = parser.parse_args(cmdargs)
2595
self.tree=arch.tree_root()
2598
base, revisions = self.revision_specs(args)
2599
message = self.make_headers(base, revisions)
2600
message += self.make_summary(revisions)
2601
path = self.edit_message(message)
2602
message = self.tidy_message(path)
2603
if cmdutil.prompt("Send merge"):
2604
self.send_message(message)
2605
print "Merge request sent"
2607
def make_headers(self, base, revisions):
2608
"""Produce email and Bug Goo header strings
2610
:param base: The base revision to apply merges to
2611
:type base: `arch.Revision`
2612
:param revisions: The revisions to replay into the base
2613
:type revisions: list of `arch.Patchlog`
2614
:return: The headers
2617
headers = "To: gnu-arch-users@gnu.org\n"
2618
headers += "From: %s\n" % options.fromaddr
2619
if len(revisions) == 1:
2620
headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary
2622
headers += "Subject: [MERGE REQUEST]\n"
2624
headers += "Base-Revision: %s\n" % base
2625
for revision in revisions:
2626
headers += "Revision: %s\n" % revision.revision
2627
headers += "Bug: \n\n"
2630
def make_summary(self, logs):
2631
"""Generate a summary of merges
2633
:param logs: the patchlogs that were directly added by the merges
2634
:type logs: list of `arch.Patchlog`
2635
:return: the summary
2640
summary+=str(log.revision)+"\n"
2641
summary+=log.summary+"\n"
2642
if log.description.strip():
2643
summary+=log.description.strip('\n')+"\n\n"
2646
def revision_specs(self, args):
2647
"""Determine the base and merge revisions from tree and arguments.
2649
:param args: The parsed arguments
2650
:type args: list of str
2651
:return: The base revision and merge revisions
2652
:rtype: `arch.Revision`, list of `arch.Patchlog`
2655
target_revision = cmdutil.determine_revision_arch(self.tree,
2658
target_revision = cmdutil.tree_latest(self.tree)
2660
merges = [ arch.Patchlog(cmdutil.determine_revision_arch(
2661
self.tree, f)) for f in args[1:] ]
2663
if self.tree is None:
2664
raise CantDetermineRevision("", "Not in a project tree")
2665
merge_iter = cmdutil.iter_new_merges(self.tree,
2666
target_revision.version,
2668
merges = [f for f in cmdutil.direct_merges(merge_iter)]
2669
return (target_revision, merges)
2671
def edit_message(self, message):
2672
"""Edit an email message in the user's standard editor
2674
:param message: The message to edit
2676
:return: the path of the edited message
2679
if self.tree is None:
2683
path += "/,merge-request"
2684
file = open(path, 'w')
2687
cmdutil.invoke_editor(path)
2690
def tidy_message(self, path):
2691
"""Validate and clean up message.
2693
:param path: The path to the message to clean up
2695
:return: The parsed message
2696
:rtype: `email.Message`
2698
mail = email.message_from_file(open(path))
2699
if mail["Subject"].strip() == "[MERGE REQUEST]":
2702
request = email.message_from_string(mail.get_payload())
2703
if request.has_key("Bug"):
2704
if request["Bug"].strip()=="":
2706
mail.set_payload(request.as_string())
2709
def send_message(self, message):
2710
"""Send a message, using its headers to address it.
2712
:param message: The message to send
2713
:type message: `email.Message`"""
2714
server = smtplib.SMTP()
2715
server.sendmail(message['From'], message['To'], message.as_string())
2718
def help(self, parser=None):
2719
"""Print a usage message
2721
:param parser: The options parser to use
2722
:type parser: `cmdutil.CmdOptionParser`
2725
parser = self.get_parser()
2728
Sends a merge request formatted for Bug Goo. Intended use: get the tree
2729
you'd like to merge into. Apply the merges you want. Invoke request-merge.
2730
The merge request will open in your $EDITOR.
2732
When no TARGET is specified, it uses the current tree revision. When
2733
no MERGE is specified, it uses the direct merges (as in "revisions
2734
--direct-merges"). But you can specify just the TARGET, or all the MERGE
2738
def get_parser(self):
2739
"""Produce a commandline parser for this command.
2741
:rtype: `cmdutil.CmdOptionParser`
2743
parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]")
2747
'changes' : Changes,
2750
'apply-changes':ApplyChanges,
2753
'revision': Revision,
2754
'revisions': Revisions,
2761
'mirror-archive': MirrorArchive,
2762
'ninventory': Inventory,
2764
'request-merge': RequestMerge,
2767
'apply-delta' : "Try \"apply-changes\".",
2768
'delta' : "To compare two revisions, use \"changes\".",
2769
'diff-rev' : "To compare two revisions, use \"changes\".",
2770
'undo' : "To undo local changes, use \"revert\".",
2771
'undelete' : "To undo only deletions, use \"revert --deletions\"",
2772
'missing-from' : "Try \"revisions --missing-from\".",
2773
'missing' : "Try \"revisions --missing\".",
2774
'missing-merge' : "Try \"revisions --partner-missing\".",
2775
'new-merges' : "Try \"revisions --new-merges\".",
2776
'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')",
2777
'logs' : "Try \"revisions --logs\"",
2778
'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")",
2779
'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")",
2780
'change-version' : "Try \"update REVISION\"",
2781
'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")",
2782
'rev-depends' : "Use revisions --dependencies",
2783
'auto-get' : "Plain get will do archive lookups",
2784
'tagline' : "Use add-id. It uses taglines in tagline trees",
2785
'emlog' : "Use elog. It automatically adds log-for-merge text, if any",
2786
'library-revisions' : "Use revisions --library",
2787
'file-revert' : "Use revert FILE"
2789
# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7