1 #!/usr/bin/env python3 -u
3 import sys, os, os.path
8 from optparse import OptionParser
10 # HARDCODED NAME CHANGES
12 # Moving to git we decided to rename some of the repositories. Here is
13 # a map of name changes applied in git repositories.
14 RENAMED_SVN_MODULES = {
17 "BootManager" : "bootmanager",
21 "NodeManager": "nodemanager",
22 "NodeUpdate": "nodeupdate",
26 def svn_to_git_name(module):
27 if module in RENAMED_SVN_MODULES:
28 return RENAMED_SVN_MODULES[module]
31 def git_to_svn_name(module):
32 for key in RENAMED_SVN_MODULES:
33 if module == RENAMED_SVN_MODULES[key]:
38 # e.g. other_choices = [ ('d','iff') , ('g','uess') ] - lowercase
39 def prompt (question, default=True, other_choices=[], allow_outside=False):
40 if not isinstance (other_choices, list):
41 other_choices = [ other_choices ]
42 chars = [ c for (c,rest) in other_choices ]
56 for char, choice in other_choices:
58 choices.append("[{}]{}".format(char, choice))
60 choices.append("<{}>{}>".format(char, choice))
62 answer = input(question + " " + "/".join(choices) + " ? ")
65 answer = answer[0].lower()
77 for (char,choice) in other_choices:
82 return prompt(question, default, other_choices)
88 editor = os.environ['EDITOR']
96 def print_fold (line):
97 while len(line) >= fold_length:
98 print(line[:fold_length],'\\')
99 line = line[fold_length:]
103 def __init__ (self, command, options):
104 self.command = command
105 self.options = options
106 self.tmp = "/tmp/command-%d"%os.getpid()
109 if self.options.dry_run:
110 print('dry_run', self.command)
112 if self.options.verbose and self.options.mode not in Main.silent_modes:
113 print('+', self.command)
115 return os.system(self.command)
117 def run_silent (self):
118 if self.options.dry_run:
119 print('dry_run', self.command)
121 if self.options.verbose:
122 print('>', os.getcwd())
123 print('+', self.command, ' .. ', end=' ')
125 retcod = os.system("{} &> {}".format(self.command, self.tmp))
127 print("FAILED ! -- out+err below (command was {})".format(self.command))
128 os.system("cat {}".format(self.tmp))
129 print("FAILED ! -- end of quoted output")
130 elif self.options.verbose:
136 if self.run_silent() != 0:
137 raise Exception("Command {} failed".format(self.command))
139 # returns stdout, like bash's $(mycommand)
140 def output_of (self, with_stderr=False):
141 if self.options.dry_run:
142 print('dry_run', self.command)
143 return 'dry_run output'
144 tmp="/tmp/status-{}".format(os.getpid())
145 if self.options.debug:
146 print('+',self.command,' .. ', end=' ')
148 command = self.command
158 if self.options.debug:
159 print('Done', end=' ')
165 def __init__(self, path, options):
167 self.options = options
170 return os.path.basename(self.path)
173 # for svn modules pathname is just the name of the module as
174 # all modules are at the root
178 out = Command("svn info {}".format(self.path), self.options).output_of()
179 for line in out.split('\n'):
180 if line.startswith("URL:"):
181 return line.split()[1].strip()
184 out = Command("svn info {}".format(self.path), self.options).output_of()
185 for line in out.split('\n'):
186 if line.startswith("Repository Root:"):
187 root = line.split()[2].strip()
188 return "{}/{}".format(root, self.pathname())
191 def clone(cls, remote, local, options, recursive=False):
193 svncommand = "svn co %s %s" % (remote, local)
195 svncommand = "svn co -N %s %s" % (remote, local)
196 Command("rm -rf %s" % local, options).run_silent()
197 Command(svncommand, options).run_fatal()
199 return SvnRepository(local, options)
202 def remote_exists(cls, remote, options):
203 return Command ("svn list %s &> /dev/null" % remote , options).run()==0
205 def tag_exists(self, tagname):
206 url = "%s/tags/%s" % (self.repo_root(), tagname)
207 return SvnRepository.remote_exists(url, self.options)
209 def update(self, subdir="", recursive=True, branch=None):
210 path = os.path.join(self.path, subdir)
212 svncommand = "svn up %s" % path
214 svncommand = "svn up -N %s" % path
215 Command(svncommand, self.options).run_fatal()
217 def commit(self, logfile):
218 # add all new files to the repository
219 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
220 self.path, self.options).output_of()
221 Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
223 def to_branch(self, branch):
224 remote = "%s/branches/%s" % (self.repo_root(), branch)
225 SvnRepository.clone(remote, self.path, self.options, recursive=True)
227 def to_tag(self, tag):
228 remote = "%s/tags/%s" % (self.repo_root(), branch)
229 SvnRepository.clone(remote, self.path, self.options, recursive=True)
231 def tag(self, tagname, logfile):
232 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
233 self_url = self.url()
234 Command("svn copy -F %s %s %s" % (logfile, self_url, tag_url), self.options).run_fatal()
236 def diff(self, f=""):
238 f = os.path.join(self.path, f)
241 return Command("svn diff %s" % f, self.options).output_of(True)
243 def diff_with_tag(self, tagname):
244 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
245 return Command("svn diff %s %s" % (tag_url, self.url()),
246 self.options).output_of(True)
248 def revert(self, f=""):
250 Command("svn revert %s" % os.path.join(self.path, f), self.options).run_fatal()
253 Command("svn revert %s -R" % self.path, self.options).run_fatal()
254 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
255 self.path, self.options).run_silent()
258 command="svn status %s" % self.path
259 return len(Command(command,self.options).output_of(True)) == 0
262 return os.path.exists(os.path.join(self.path, ".svn"))
268 def __init__(self, path, options):
270 self.options = options
273 return os.path.basename(self.path)
276 return self.repo_root()
279 c = Command("git show | grep commit | awk '{{print $2;}}'", self.options)
280 out = self.__run_in_repo(c.output_of).strip()
281 return "http://git.onelab.eu/?p={}.git;a=commit;h={}".format(self.name(), out)
284 c = Command("git remote show origin", self.options)
285 out = self.__run_in_repo(c.output_of)
286 for line in out.split('\n'):
287 if line.strip().startswith("Fetch URL:"):
288 return line.split()[2]
291 def clone(cls, remote, local, options, depth=None):
292 Command("rm -rf {}".format(local), options).run_silent()
293 depth_option = "" if depth is None else " --depth {}".format(depth)
294 Command("git clone{} {} {}".format(depth_option, remote, local), options).run_fatal()
295 return GitRepository(local, options)
298 def remote_exists(cls, remote, options):
299 return Command ("git --no-pager ls-remote {} &> /dev/null".format(remote), options).run()==0
301 def tag_exists(self, tagname):
302 command = 'git tag -l | grep "^{}$"'.format(tagname)
303 c = Command(command, self.options)
304 out = self.__run_in_repo(c.output_of, with_stderr=True)
307 def __run_in_repo(self, fun, *args, **kwargs):
310 ret = fun(*args, **kwargs)
314 def __run_command_in_repo(self, command, ignore_errors=False):
315 c = Command(command, self.options)
317 return self.__run_in_repo(c.output_of)
319 return self.__run_in_repo(c.run_fatal)
321 def __is_commit_id(self, id):
322 c = Command("git show {} | grep commit | awk '{{print $2;}}'".format(id), self.options)
323 ret = self.__run_in_repo(c.output_of, with_stderr=False)
324 if ret.strip() == id:
328 def update(self, subdir=None, recursive=None, branch="master"):
329 if branch == "master":
330 self.__run_command_in_repo("git checkout {}".format(branch))
332 self.to_branch(branch, remote=True)
333 self.__run_command_in_repo("git fetch origin --tags")
334 self.__run_command_in_repo("git fetch origin")
335 if not self.__is_commit_id(branch):
336 # we don't need to merge anythign for commit ids.
337 self.__run_command_in_repo("git merge --ff origin/{}".format(branch))
339 def to_branch(self, branch, remote=True):
342 command = "git branch --track {} origin/{}".format(branch, branch)
343 c = Command(command, self.options)
344 self.__run_in_repo(c.output_of, with_stderr=True)
345 return self.__run_command_in_repo("git checkout {}".format(branch))
347 def to_tag(self, tag):
349 return self.__run_command_in_repo("git checkout {}".format(tag))
351 def tag(self, tagname, logfile):
352 self.__run_command_in_repo("git tag {} -F {}".format(tagname, logfile))
355 def diff(self, f=""):
356 c = Command("git diff {}".format(f), self.options)
357 return self.__run_in_repo(c.output_of, with_stderr=True)
359 def diff_with_tag(self, tagname):
360 c = Command("git diff {}".format(tagname), self.options)
361 return self.__run_in_repo(c.output_of, with_stderr=True)
363 def commit(self, logfile, branch="master"):
364 self.__run_command_in_repo("git add .", ignore_errors=True)
365 self.__run_command_in_repo("git add -u", ignore_errors=True)
366 self.__run_command_in_repo("git commit -F {}".format(logfile), ignore_errors=True)
367 if branch == "master" or self.__is_commit_id(branch):
368 self.__run_command_in_repo("git push")
370 self.__run_command_in_repo("git push origin {}:{}".format(branch, branch))
371 self.__run_command_in_repo("git push --tags")
373 def revert(self, f=""):
375 self.__run_command_in_repo("git checkout {}".format(f))
378 self.__run_command_in_repo("git --no-pager reset --hard")
379 self.__run_command_in_repo("git --no-pager clean -f")
384 s="nothing to commit (working directory clean)"
385 return Command(command, self.options).output_of(True).find(s) >= 0
386 return self.__run_in_repo(check_commit)
389 return os.path.exists(os.path.join(self.path, ".git"))
393 """ Generic repository """
394 supported_repo_types = [SvnRepository, GitRepository]
396 def __init__(self, path, options):
398 self.options = options
399 for repo in self.supported_repo_types:
400 self.repo = repo(self.path, self.options)
401 if self.repo.is_valid():
405 def has_moved_to_git(cls, module, config,options):
406 module = svn_to_git_name(module)
407 # check if the module is already in Git
408 return GitRepository.remote_exists(Module.git_remote_dir(module), options)
412 def remote_exists(cls, remote, options):
413 for repo in Repository.supported_repo_types:
414 if repo.remote_exists(remote, options):
418 def __getattr__(self, attr):
419 return getattr(self.repo, attr)
423 # support for tagged module is minimal, and is for the Build class only
426 edit_magic_line="--This line, and those below, will be ignored--"
427 setting_tag_format = "Setting tag {}"
429 redirectors=[ # ('module_name_varname', 'name'),
430 ('module_version_varname', 'version'),
431 ('module_taglevel_varname', 'taglevel'), ]
433 # where to store user's config
434 config_storage = "CONFIG"
439 configKeys=[ ('svnpath', "Enter your toplevel svnpath",
440 "svn+ssh://%s@svn.planet-lab.org/svn/"%subprocess.getoutput("id -un")),
441 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
442 ('gituser', "Enter your user name (login name) on git server", subprocess.getoutput("id -un")),
443 ("build", "Enter the name of your build module", "build"),
444 ('username', "Enter your firstname and lastname for changelogs", ""),
445 ("email", "Enter your email address for changelogs", ""),
449 def prompt_config_option(cls, key, message, default):
450 cls.config[key]=input("{} [{}] : ".format(message,default)).strip() or default
453 def prompt_config (cls):
454 for (key,message,default) in cls.configKeys:
456 while not cls.config[key]:
457 cls.prompt_config_option(key, message, default)
459 # for parsing module spec name:branch
460 matcher_branch_spec=re.compile("\A(?P<name>[\w\.\-\/]+):(?P<branch>[\w\.\-]+)\Z")
461 # special form for tagged module - for Build
462 matcher_tag_spec=re.compile("\A(?P<name>[\w\.\-\/]+)@(?P<tagname>[\w\.\-]+)\Z")
464 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
467 def parse_module_spec(cls, module_spec):
468 name = branch_or_tagname = module_type = ""
470 attempt=Module.matcher_branch_spec.match(module_spec)
472 module_type = "branch"
473 name=attempt.group('name')
474 branch_or_tagname=attempt.group('branch')
476 attempt=Module.matcher_tag_spec.match(module_spec)
479 name=attempt.group('name')
480 branch_or_tagname=attempt.group('tagname')
483 return name, branch_or_tagname, module_type
486 def __init__ (self,module_spec,options):
488 self.pathname, branch_or_tagname, module_type = self.parse_module_spec(module_spec)
489 self.name = os.path.basename(self.pathname)
491 if module_type == "branch":
492 self.branch=branch_or_tagname
493 elif module_type == "tag":
494 self.tagname=branch_or_tagname
496 # when available prefer to use git module name internally
497 self.name = svn_to_git_name(self.name)
500 self.module_dir="{}/{}".format(options.workdir,self.pathname)
501 self.repository = None
504 def run (self,command):
505 return Command(command,self.options).run()
506 def run_fatal (self,command):
507 return Command(command,self.options).run_fatal()
508 def run_prompt (self,message,fun, *args):
509 fun_msg = "{}({})".format(fun.__name__, ",".join(args))
510 if not self.options.verbose:
512 choice = prompt(message, True, ('s','how'))
516 elif choice is False:
517 print('About to run function:', fun_msg)
519 question = "{} - want to run function: {}".format(message, fun_msg)
520 if prompt(question, True):
523 def friendly_name (self):
524 if hasattr(self, 'branch'):
525 return "{}:{}".format(self.pathname, self.branch)
526 elif hasattr(self, 'tagname'):
527 return "{}@{}".format(self.pathname, self.tagname)
532 def git_remote_dir (cls, name):
533 return "{}@{}:/git/{}.git".format(cls.config['gituser'], cls.config['gitserver'], name)
536 def svn_remote_dir (cls, name):
537 name = git_to_svn_name(name)
538 svn = cls.config['svnpath']
539 if svn.endswith('/'):
540 return "%s%s" % (svn, name)
541 return "%s/%s" % (svn, name)
543 def svn_selected_remote(self):
544 svn_name = git_to_svn_name(self.name)
545 remote = self.svn_remote_dir(svn_name)
546 if hasattr(self, 'branch'):
547 remote = "{}/branches/{}".format(remote, self.branch)
548 elif hasattr(self,'tagname'):
549 remote = "{}/tags/{}".format(remote, self.tagname)
551 remote = "{}/trunk".format(remote)
556 def init_homedir (cls, options):
557 if options.verbose and options.mode not in Main.silent_modes:
558 print('Checking for', options.workdir)
559 storage="{}/{}".format(options.workdir, cls.config_storage)
560 # sanity check. Either the topdir exists AND we have a config/storage
561 # or topdir does not exist and we create it
562 # to avoid people use their own daily svn repo
563 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
564 print("""The directory {} exists and has no CONFIG file
565 If this is your regular working directory, please provide another one as the
566 module-* commands need a fresh working dir. Make sure that you do not use
567 that for other purposes than tagging""".format(options.workdir))
571 print("Checking out build module...")
572 remote = cls.git_remote_dir(cls.config['build'])
573 local = os.path.join(options.workdir, cls.config['build'])
574 GitRepository.clone(remote, local, options, depth=1)
578 with open(storage, 'w') as f:
579 for (key, message, default) in Module.configKeys:
580 f.write("{}={}\n".format(key, Module.config[key]))
582 print('Stored', storage)
583 Command("cat {}".format(storage),options).run()
587 with open(storage) as f:
588 for line in f.readlines():
589 key, value = re.compile("^(.+)=(.+)$").match(line).groups()
590 Module.config[key] = value
592 # owerride config variables using options.
593 if options.build_module:
594 Module.config['build'] = options.build_module
596 if not os.path.isdir (options.workdir):
597 print("Cannot find",options.workdir,"let's create it")
598 Command("mkdir -p {}".format(options.workdir), options).run_silent()
604 # check missing config options
606 for key, message, default in cls.configKeys:
607 if key not in Module.config:
608 print("Configuration changed for module-tools")
609 cls.prompt_config_option(key, message, default)
613 Command("rm -rf {}".format(options.workdir), options).run_silent()
614 Command("mkdir -p {}".format(options.workdir), options).run_silent()
618 build_dir = os.path.join(options.workdir, cls.config['build'])
619 if not os.path.isdir(build_dir):
622 build = Repository(build_dir, options)
623 if not build.is_clean():
624 print("build module needs a revert")
629 if options.verbose and options.mode not in Main.silent_modes:
630 print('******** Using config')
631 for (key,message,default) in Module.configKeys:
632 print('\t{} = {}'.format(key,Module.config[key]))
634 def init_module_dir (self):
635 if self.options.verbose:
636 print('Checking for',self.module_dir)
638 if not os.path.isdir (self.module_dir):
639 if Repository.has_moved_to_git(self.pathname, Module.config, self.options):
640 self.repository = GitRepository.clone(self.git_remote_dir(self.pathname),
644 remote = self.svn_selected_remote()
645 self.repository = SvnRepository.clone(remote,
647 self.options, recursive=False)
649 self.repository = Repository(self.module_dir, self.options)
650 if self.repository.type == "svn":
651 # check if module has moved to git
652 if Repository.has_moved_to_git(self.pathname, Module.config, self.options):
653 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
654 self.init_module_dir()
655 # check if we have the required branch/tag
656 if self.repository.url() != self.svn_selected_remote():
657 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
658 self.init_module_dir()
660 elif self.repository.type == "git":
661 if hasattr(self, 'branch'):
662 self.repository.to_branch(self.branch)
663 elif hasattr(self, 'tagname'):
664 self.repository.to_tag(self.tagname)
667 raise Exception('Cannot find {} - check module name'.format(self.module_dir))
670 def revert_module_dir (self):
671 if self.options.fast_checks:
672 if self.options.verbose: print('Skipping revert of {}'.format(self.module_dir))
674 if self.options.verbose:
675 print('Checking whether', self.module_dir, 'needs being reverted')
677 if not self.repository.is_clean():
678 self.repository.revert()
680 def update_module_dir (self):
681 if self.options.fast_checks:
682 if self.options.verbose: print('Skipping update of {}'.format(self.module_dir))
684 if self.options.verbose:
685 print('Updating', self.module_dir)
687 if hasattr(self, 'branch'):
688 self.repository.update(branch=self.branch)
689 elif hasattr(self, 'tagname'):
690 self.repository.update(branch=self.tagname)
692 self.repository.update()
694 def main_specname (self):
695 attempt = "{}/{}.spec".format(self.module_dir, self.name)
696 if os.path.isfile (attempt):
698 pattern1 = "{}/*.spec".format(self.module_dir)
699 level1 = glob(pattern1)
702 pattern2 = "{}/*/*.spec".format(self.module_dir)
703 level2 = glob(pattern2)
707 raise Exception('Cannot guess specfile for module {} -- patterns were {} or {}'\
708 .format(self.pathname,pattern1,pattern2))
710 def all_specnames (self):
711 level1 = glob("{}/*.spec".format(self.module_dir))
714 level2 = glob("{}/*/*.spec".format(self.module_dir))
717 def parse_spec (self, specfile, varnames):
718 if self.options.verbose:
719 print('Parsing',specfile, end=' ')
721 print("[{}]".format(var), end=' ')
724 with open(specfile) as f:
725 for line in f.readlines():
726 attempt = Module.matcher_rpm_define.match(line)
728 define, var, value = attempt.groups()
731 if self.options.debug:
732 print('found {} keys'.format(len(result)))
733 for k, v in result.items():
734 print('{} = {}'.format(k, v))
737 # stores in self.module_name_varname the rpm variable to be used for the module's name
738 # and the list of these names in self.varnames
739 def spec_dict (self):
740 specfile = self.main_specname()
741 redirector_keys = [ varname for (varname, default) in Module.redirectors]
742 redirect_dict = self.parse_spec(specfile, redirector_keys)
743 if self.options.debug:
744 print('1st pass parsing done, redirect_dict=', redirect_dict)
746 for varname, default in Module.redirectors:
747 if varname in redirect_dict:
748 setattr(self, varname, redirect_dict[varname])
749 varnames += [redirect_dict[varname]]
751 setattr(self, varname, default)
752 varnames += [ default ]
753 self.varnames = varnames
754 result = self.parse_spec (specfile, self.varnames)
755 if self.options.debug:
756 print('2st pass parsing done, varnames={} result={}'.format(varnames, result))
759 def patch_spec_var (self, patch_dict,define_missing=False):
760 for specfile in self.all_specnames():
761 # record the keys that were changed
762 changed = dict ( [ (x,False) for x in list(patch_dict.keys()) ] )
763 newspecfile = "{}.new".format(specfile)
764 if self.options.verbose:
765 print('Patching', specfile, 'for', list(patch_dict.keys()))
767 with open (specfile) as spec:
768 with open(newspecfile, "w") as new:
769 for line in spec.readlines():
770 attempt = Module.matcher_rpm_define.match(line)
772 define, var, value = attempt.groups()
773 if var in list(patch_dict.keys()):
774 if self.options.debug:
775 print('rewriting {} as {}'.format(var, patch_dict[var]))
776 new.write('%{} {} {}\n'.format(define, var, patch_dict[var]))
781 for key, was_changed in changed.items():
783 if self.options.debug:
784 print('rewriting missing {} as {}'.format(key, patch_dict[key]))
785 new.write('\n%define {} {}\n'.format(key, patch_dict[key]))
786 os.rename(newspecfile, specfile)
788 # returns all lines until the magic line
789 def unignored_lines (self, logfile):
791 white_line_matcher = re.compile("\A\s*\Z")
792 with open(logfile) as f:
793 for logline in f.readlines():
794 if logline.strip() == Module.edit_magic_line:
796 elif white_line_matcher.match(logline):
799 result.append(logline.strip()+'\n')
802 # creates a copy of the input with only the unignored lines
803 def strip_magic_line_filename (self, filein, fileout ,new_tag_name):
804 with open(fileout,'w') as f:
805 f.write(self.setting_tag_format.format(new_tag_name) + '\n')
806 for line in self.unignored_lines(filein):
809 def insert_changelog (self, logfile, newtag):
810 for specfile in self.all_specnames():
811 newspecfile = "{}.new".format(specfile)
812 if self.options.verbose:
813 print('Inserting changelog from {} into {}'.format(logfile, specfile))
815 with open (specfile) as spec:
816 with open(newspecfile,"w") as new:
817 for line in spec.readlines():
819 if re.compile('%changelog').match(line):
820 dateformat="* %a %b %d %Y"
821 datepart=time.strftime(dateformat)
822 logpart="{} <{}> - {}".format(Module.config['username'],
823 Module.config['email'],
825 new.write("{} {}\n".format(datepart,logpart))
826 for logline in self.unignored_lines(logfile):
827 new.write("- " + logline)
829 os.rename(newspecfile,specfile)
831 def show_dict (self, spec_dict):
832 if self.options.verbose:
833 for k, v in spec_dict.items():
834 print('{} = {}'.format(k, v))
836 def last_tag (self, spec_dict):
838 return "{}-{}".format(spec_dict[self.module_version_varname],
839 spec_dict[self.module_taglevel_varname])
840 except KeyError as err:
841 raise Exception('Something is wrong with module {}, cannot determine {} - exiting'\
842 .format(self.name, err))
844 def tag_name (self, spec_dict, old_svn_name=False):
845 base_tag_name = self.name
847 base_tag_name = git_to_svn_name(self.name)
848 return "{}-{}".format(base_tag_name, self.last_tag(spec_dict))
851 pattern_format="\A\s*{module}-(SVNPATH|GITPATH)\s*(=|:=)\s*(?P<url_main>[^\s]+)/{module}[^\s]+"
853 def is_mentioned_in_tagsfile (self, tagsfile):
854 # so that %(module)s gets replaced from format
856 module_matcher = re.compile(Module.pattern_format.format(**locals()))
857 with open(tagsfile) as f:
858 for line in f.readlines():
859 if module_matcher.match(line):
863 ##############################
864 # using fine_grain means replacing only those instances that currently refer to this tag
865 # otherwise, <module>-{SVNPATH,GITPATH} is replaced unconditionnally
866 def patch_tags_file (self, tagsfile, oldname, newname, fine_grain=True):
867 newtagsfile = "{}.new".format(tagsfile)
869 with open(tagsfile) as tags:
870 with open(newtagsfile,"w") as new:
872 # fine-grain : replace those lines that refer to oldname
874 if self.options.verbose:
875 print('Replacing {} into {}\n\tin {} .. '.format(oldname, newname, tagsfile), end=' ')
876 matcher = re.compile("^(.*){}(.*)".format(oldname))
877 for line in tags.readlines():
878 if not matcher.match(line):
881 begin, end = matcher.match(line).groups()
882 new.write(begin+newname+end+"\n")
884 # brute-force : change uncommented lines that define <module>-SVNPATH
886 if self.options.verbose:
887 print('Searching for -SVNPATH or -GITPATH lines referring to /{}/\n\tin {} .. '\
888 .format(self.pathname, tagsfile), end=' ')
889 # so that {module} gets replaced from format
891 module_matcher = re.compile(Module.pattern_format.format(**locals()))
892 for line in tags.readlines():
893 attempt = module_matcher.match(line)
895 if line.find("-GITPATH") >= 0:
896 modulepath = "{}-GITPATH".format(self.name)
897 replacement = "{:<32}:= {}/{}.git@{}\n"\
898 .format(modulepath, attempt.group('url_main'), self.pathname, newname)
900 modulepath = "{}-SVNPATH".format(self.name)
901 replacement = "{:-32}:= {}/{}/tags/{}\n"\
902 .format(modulepath, attempt.group('url_main'), self.name, newname)
903 if self.options.verbose:
904 print(' ' + modulepath, end=' ')
905 new.write(replacement)
910 os.rename(newtagsfile,tagsfile)
911 if self.options.verbose:
912 print("{} changes".format(matches))
915 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
916 if self.options.verbose:
917 print("Checking {} repository tag: {} - ".format(self.repository.type, tagname), end=' ')
919 found_tagname = tagname
920 found = self.repository.tag_exists(tagname)
921 if not found and old_svn_tag_name:
922 if self.options.verbose:
924 print("Checking {} repository tag: {} - ".format(self.repository.type, old_svn_tag_name), end=' ')
925 found = self.repository.tag_exists(old_svn_tag_name)
927 found_tagname = old_svn_tag_name
929 if (found and need_it) or (not found and not need_it):
930 if self.options.verbose:
932 print("- found" if found else "- not found")
934 if self.options.verbose:
936 exception_format = "tag ({}) is already there" if found else "can not find required tag ({})"
937 raise Exception(exception_format.format(tagname))
942 ##############################
944 self.init_module_dir()
945 self.revert_module_dir()
946 self.update_module_dir()
948 spec_dict = self.spec_dict()
949 self.show_dict(spec_dict)
951 # compute previous tag - if not bypassed
952 if not self.options.bypass:
953 old_tag_name = self.tag_name(spec_dict)
955 old_tag_name = self.check_tag(old_tag_name, need_it=True)
957 if (self.options.new_version):
958 # new version set on command line
959 spec_dict[self.module_version_varname] = self.options.new_version
960 spec_dict[self.module_taglevel_varname] = 0
963 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
964 spec_dict[self.module_taglevel_varname] = new_taglevel
966 new_tag_name = self.tag_name(spec_dict)
968 new_tag_name = self.check_tag(new_tag_name, need_it=False)
971 if not self.options.bypass:
972 diff_output = self.repository.diff_with_tag(old_tag_name)
973 if len(diff_output) == 0:
974 if not prompt ("No pending difference in module %s, want to tag anyway"%self.pathname,False):
977 # side effect in head's specfile
978 self.patch_spec_var(spec_dict)
980 # prepare changelog file
981 # we use the standard subversion magic string (see edit_magic_line)
982 # so we can provide useful information, such as version numbers and diff
984 changelog_plain = "/tmp/{}-{}.edit".format(self.name, os.getpid())
985 changelog_strip = "/tmp/{}-{}.strip".format(self.name, os.getpid())
986 setting_tag_line = Module.setting_tag_format.format(new_tag_name)
987 with open(changelog_plain, 'w') as f:
991 Please write a changelog for this new tag in the section below
992 """.format(Module.edit_magic_line, setting_tag_line))
994 if self.options.bypass:
996 elif prompt('Want to see diffs while writing changelog', True):
997 with open(changelog_plain, "a") as f:
998 f.write('DIFF=========\n' + diff_output)
1000 if self.options.debug:
1004 self.run("{} {}".format(self.options.editor, changelog_plain))
1005 # strip magic line in second file - looks like svn has changed its magic line with 1.6
1006 # so we do the job ourselves
1007 self.strip_magic_line_filename(changelog_plain, changelog_strip, new_tag_name)
1008 # insert changelog in spec
1009 if self.options.changelog:
1010 self.insert_changelog (changelog_plain, new_tag_name)
1013 build_path = os.path.join(self.options.workdir, Module.config['build'])
1014 build = Repository(build_path, self.options)
1015 if self.options.build_branch:
1016 build.to_branch(self.options.build_branch)
1017 if not build.is_clean():
1020 tagsfiles = glob(build.path+"/*-tags.mk")
1021 tagsdict = dict( [ (x,'todo') for x in tagsfiles ] )
1022 default_answer = 'y'
1025 # do not bother if in bypass mode
1026 if self.options.bypass:
1028 for tagsfile in tagsfiles:
1029 if not self.is_mentioned_in_tagsfile (tagsfile):
1030 if self.options.verbose:
1031 print("tagsfile {} does not mention {} - skipped".format(tagsfile, self.name))
1033 status = tagsdict[tagsfile]
1034 basename = os.path.basename(tagsfile)
1035 print(".................... Dealing with {}".format(basename))
1036 while tagsdict[tagsfile] == 'todo' :
1037 choice = prompt ("insert {} in {} ".format(new_tag_name, basename),
1039 [ ('y','es'), ('n', 'ext'), ('f','orce'),
1040 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
1043 self.patch_tags_file(tagsfile, old_tag_name, new_tag_name, fine_grain=True)
1045 print('Done with {}'.format(os.path.basename(tagsfile)))
1046 tagsdict[tagsfile] = 'done'
1048 self.patch_tags_file(tagsfile, old_tag_name, new_tag_name, fine_grain=False)
1050 print(build.diff(f=os.path.basename(tagsfile)))
1052 build.revert(f=tagsfile)
1054 self.run("cat {}".format(tagsfile))
1058 """y: change {name}-{{SVNPATH,GITPATH}} only if it currently refers to {old_tag_name}
1059 f: unconditionnally change any line that assigns {name}-SVNPATH to using {new_tag_name}
1060 d: show current diff for this tag file
1061 r: revert that tag file
1062 c: cat the current tag file
1063 n: move to next file""".format(**locals()))
1065 if prompt("Want to review changes on tags files", False):
1066 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
1067 default_answer = 'd'
1071 def diff_all_changes():
1073 print(self.repository.diff())
1075 def commit_all_changes(log):
1076 if hasattr(self, 'branch'):
1077 self.repository.commit(log, branch=self.branch)
1079 self.repository.commit(log)
1082 self.run_prompt("Review module and build", diff_all_changes)
1083 self.run_prompt("Commit module and build", commit_all_changes, changelog_strip)
1084 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_strip)
1086 if self.options.debug:
1087 print('Preserving {} and stripped {}', changelog_plain, changelog_strip)
1089 os.unlink(changelog_plain)
1090 os.unlink(changelog_strip)
1093 ##############################
1094 def do_version (self):
1095 self.init_module_dir()
1096 self.revert_module_dir()
1097 self.update_module_dir()
1098 spec_dict = self.spec_dict()
1099 if self.options.www:
1100 self.html_store_title('Version for module {} ({})'\
1101 .format(self.friendly_name(), self.last_tag(spec_dict)))
1102 for varname in self.varnames:
1103 if varname not in spec_dict:
1104 self.html_print ('Could not find %define for {}'.format(varname))
1107 self.html_print ("{:<16} {}".format(varname, spec_dict[varname]))
1108 self.html_print ("{:<16} {}".format('url', self.repository.url()))
1109 if self.options.verbose:
1110 self.html_print ("{:<16} {}".format('main specfile:', self.main_specname()))
1111 self.html_print ("{:<16} {}".format('specfiles:', self.all_specnames()))
1112 self.html_print_end()
1115 ##############################
1117 self.init_module_dir()
1118 self.revert_module_dir()
1119 self.update_module_dir()
1120 spec_dict = self.spec_dict()
1121 self.show_dict(spec_dict)
1124 tag_name = self.tag_name(spec_dict)
1125 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
1128 tag_name = self.check_tag(tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
1130 if self.options.verbose:
1131 print('Getting diff')
1132 diff_output = self.repository.diff_with_tag(tag_name)
1134 if self.options.list:
1136 print(self.pathname)
1138 thename = self.friendly_name()
1140 if self.options.www and diff_output:
1141 self.html_store_title("Diffs in module {} ({}) : {} chars"\
1142 .format(thename, self.last_tag(spec_dict), len(diff_output)))
1144 self.html_store_raw ('<p> < (left) {} </p>'"{:<16} {}".format(tag_name))
1145 self.html_store_raw ('<p> > (right) {} </p>'"{:<16} {}".format(thename))
1146 self.html_store_pre (diff_output)
1147 elif not self.options.www:
1148 print('x'*30, 'module', thename)
1149 print('x'*20, '<', tag_name)
1150 print('x'*20, '>', thename)
1153 ##############################
1154 # store and restitute html fragments
1156 def html_href (url,text):
1157 return '<a href="{}">{}</a>'.format(url, text)
1160 def html_anchor (url,text):
1161 return '<a name="{}">{}</a>'.format(url,text)
1164 def html_quote (text):
1165 return text.replace('&', '&').replace('<', '<').replace('>', '>')
1167 # only the fake error module has multiple titles
1168 def html_store_title (self, title):
1169 if not hasattr(self,'titles'):
1171 self.titles.append(title)
1173 def html_store_raw (self, html):
1174 if not hasattr(self,'body'):
1178 def html_store_pre (self, text):
1179 if not hasattr(self,'body'):
1181 self.body += '<pre>{}</pre>'.format(self.html_quote(text))
1183 def html_print (self, txt):
1184 if not self.options.www:
1187 if not hasattr(self, 'in_list') or not self.in_list:
1188 self.html_store_raw('<ul>')
1190 self.html_store_raw('<li>{}</li>'.format(txt))
1192 def html_print_end (self):
1193 if self.options.www:
1194 self.html_store_raw ('</ul>')
1197 def html_dump_header(title):
1198 nowdate = time.strftime("%Y-%m-%d")
1199 nowtime = time.strftime("%H:%M (%Z)")
1200 print("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1201 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
1204 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1205 <style type="text/css">
1206 body {{ font-family:georgia, serif; }}
1207 h1 {{font-size: large; }}
1208 p.title {{font-size: x-large; }}
1209 span.error {{text-weight:bold; color: red; }}
1213 <p class='title'> {} - status on {} at {}</p>
1215 """.format(title, title, nowdate, nowtime))
1218 def html_dump_middle():
1222 def html_dump_footer():
1223 print("</body></html")
1225 def html_dump_toc(self):
1226 if hasattr(self,'titles'):
1227 for title in self.titles:
1228 print('<li>', self.html_href('#'+self.friendly_name(),title), '</li>')
1230 def html_dump_body(self):
1231 if hasattr(self,'titles'):
1232 for title in self.titles:
1233 print('<hr /><h1>', self.html_anchor(self.friendly_name(),title), '</h1>')
1234 if hasattr(self,'body'):
1236 print('<p class="top">', self.html_href('#','Back to top'), '</p>')
1240 class Build(Module):
1242 def __get_modules(self, tagfile):
1243 self.init_module_dir()
1246 tagfile = os.path.join(self.module_dir, tagfile)
1247 for line in open(tagfile):
1249 name, url = line.split(':=')
1250 name, git_or_svn_path = name.rsplit('-', 1)
1251 name = svn_to_git_name(name.strip())
1252 modules[name] = (git_or_svn_path.strip(), url.strip())
1257 def get_modules(self, tagfile):
1258 modules = self.__get_modules(tagfile)
1259 for module in modules:
1260 module_type = tag_or_branch = ""
1262 path_type, url = modules[module]
1263 if path_type == "GITPATH":
1264 module_spec = os.path.split(url)[-1].replace(".git","")
1265 name, tag_or_branch, module_type = self.parse_module_spec(module_spec)
1267 tag_or_branch = os.path.split(url)[-1].strip()
1268 if url.find('/tags/') >= 0:
1270 elif url.find('/branches/') >= 0:
1271 module_type = "branch"
1273 modules[module] = {"module_type" : module_type,
1274 "path_type": path_type,
1275 "tag_or_branch": tag_or_branch,
1281 def modules_diff(first, second):
1284 for module in first:
1285 if module not in second:
1286 print("=== module {} missing in right-hand side ===".format(module))
1288 if first[module]['tag_or_branch'] != second[module]['tag_or_branch']:
1289 diff[module] = (first[module]['tag_or_branch'], second[module]['tag_or_branch'])
1291 first_set = set(first.keys())
1292 second_set = set(second.keys())
1294 new_modules = list(second_set - first_set)
1295 removed_modules = list(first_set - second_set)
1297 return diff, new_modules, removed_modules
1299 def release_changelog(options, buildtag_old, buildtag_new):
1301 # the command line expects new old, so we treat the tagfiles in the same order
1302 nb_tags = len(options.distrotags)
1304 tagfile_new = tagfile_old = options.distrotags[0]
1306 tagfile_new, tagfile_old = options.distrotags
1308 print("ERROR: provide one or two tagfile name (eg. onelab-k32-tags.mk)")
1309 print("two tagfiles can be mentioned when a tagfile has been renamed")
1313 print("------------------------------ Computing Changelog from")
1314 print("buildtag_old", buildtag_old, "tagfile_old", tagfile_old)
1315 print("buildtag_new", buildtag_new, "tagfile_new", tagfile_new)
1321 print('= build tag {} to {} ='.format(buildtag_old, buildtag_new))
1322 print('== distro {} ({} to {}) =='.format(tagfile_new, buildtag_old, buildtag_new))
1324 build = Build("build@{}".format(buildtag_old), options)
1325 build.init_module_dir()
1326 first = build.get_modules(tagfile_old)
1328 print(' * from', buildtag_old, build.repository.gitweb())
1330 build = Build("build@{}".format(buildtag_new), options)
1331 build.init_module_dir()
1332 second = build.get_modules(tagfile_new)
1334 print(' * to', buildtag_new, build.repository.gitweb())
1336 diff, new_modules, removed_modules = modules_diff(first, second)
1339 def get_module(name, tag):
1340 if not tag or tag == "trunk":
1341 return Module("{}".format(module), options)
1343 return Module("{}@{}".format(module, tag), options)
1347 print('=== {} - {} to {} : package {} ==='.format(tagfile_new, buildtag_old, buildtag_new, module))
1349 first, second = diff[module]
1350 m = get_module(module, first)
1351 os.system('rm -rf {}'.format(m.module_dir)) # cleanup module dir
1354 if m.repository.type == "svn":
1355 print(' * from', first, m.repository.url())
1357 print(' * from', first, m.repository.gitweb())
1359 specfile = m.main_specname()
1360 (tmpfd, tmpfile) = tempfile.mkstemp()
1361 os.system("cp -f /{} {}".format(specfile, tmpfile))
1363 m = get_module(module, second)
1364 # patch for ipfw that, being managed in a separate repo, won't work for now
1367 except Exception as e:
1368 print("""Could not retrieve module {} - skipped
1370 """.format( m.friendly_name(), e))
1372 specfile = m.main_specname()
1374 if m.repository.type == "svn":
1375 print(' * to', second, m.repository.url())
1377 print(' * to', second, m.repository.gitweb())
1380 os.system("diff -u {} {} | sed -e 's,{},[[previous version]],'"\
1381 .format(tmpfile, specfile, tmpfile))
1386 for module in new_modules:
1387 print('=== {} : new package in build {} ==='.format(tagfile_new, module))
1389 for module in removed_modules:
1390 print('=== {} : removed package from build {} ==='.format(tagfile_new, module))
1393 def adopt_tag (options, args):
1395 for module in options.modules:
1396 modules += module.split()
1397 for module in modules:
1398 modobj=Module(module,options)
1399 for tags_file in args:
1401 print('adopting tag {} for {} in {}'.format(options.tag, module, tags_file))
1402 modobj.patch_tags_file(tags_file, '_unused_', options.tag, fine_grain=False)
1404 Command("git diff {}".format(" ".join(args)), options).run()
1406 ##############################
1409 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
1411 module-tools : a set of tools to manage subversion tags and specfile
1412 requires the specfile to either
1413 * define *version* and *taglevel*
1415 * define redirection variables module_version_varname / module_taglevel_varname
1417 by default, the 'master' branch of modules is the target
1418 in this case, just mention the module name as <module_desc>
1420 if you wish to work on another branch,
1421 you can use something like e.g. Mom:2.1 as <module_desc>
1423 release_usage="""Usage: %prog [options] tag1 .. tagn
1424 Extract release notes from the changes in specfiles between several build tags, latest first
1426 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
1427 You can refer to a (build) branch by prepending a colon, like in
1428 release-changelog :4.2 4.2-rc25
1429 You can refer to the build trunk by just mentioning 'trunk', e.g.
1430 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
1431 You can use 2 different tagfile names if that was renamed meanwhile
1432 release-changelog -t onelab-tags.mk 5.0-rc29 -t onelab-k32-tags.mk 5.0-rc28
1434 adopt_usage="""Usage: %prog [options] tag-file[s]
1435 With this command you can adopt a specifi tag or branch in your tag files
1436 This should be run in your daily build workdir; no call of git nor svn is done
1438 adopt-tag -m "plewww plcapi" -m Monitor onelab*tags.mk
1439 adopt-tag -m sfa -t sfa-1.0-33 *tags.mk
1441 common_usage="""More help:
1442 see http://svn.planet-lab.org/wiki/ModuleTools"""
1445 'list' : "displays a list of available tags or branches",
1446 'version' : "check latest specfile and print out details",
1447 'diff' : "show difference between module (trunk or branch) and latest tag",
1448 'tag' : """increment taglevel in specfile, insert changelog in specfile,
1449 create new tag and and monitor its adoption in build/*-tags.mk""",
1450 'branch' : """create a branch for this module, from the latest tag on the trunk,
1451 and change trunk's version number to reflect the new branch name;
1452 you can specify the new branch name by using module:branch""",
1453 'sync' : """create a tag from the module
1454 this is a last resort option, mostly for repairs""",
1455 'changelog' : """extract changelog between build tags
1456 expected arguments are a list of tags""",
1457 'adopt' : """locally adopt a specific tag""",
1460 silent_modes = ['list']
1461 # 'changelog' is for release-changelog
1462 # 'adopt' is for 'adopt-tag'
1463 regular_modes = set(modes.keys()).difference(set(['changelog','adopt']))
1466 def optparse_list (option, opt, value, parser):
1468 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
1470 setattr(parser.values,option.dest,value.split())
1475 # hack - need to check for adopt first as 'adopt-tag' contains tag..
1476 for function in [ 'adopt' ] + list(Main.modes.keys()):
1477 if sys.argv[0].find(function) >= 0:
1481 print("Unsupported command",sys.argv[0])
1482 print("Supported commands:" + " ".join(list(Main.modes.keys())))
1485 usage='undefined usage, mode={}'.format(mode)
1486 if mode in Main.regular_modes:
1487 usage = Main.module_usage
1488 usage += Main.common_usage
1489 usage += "\nmodule-{} : {}".format(mode, Main.modes[mode])
1490 elif mode == 'changelog':
1491 usage = Main.release_usage
1492 usage += Main.common_usage
1493 elif mode == 'adopt':
1494 usage = Main.adopt_usage
1495 usage += Main.common_usage
1497 parser=OptionParser(usage=usage)
1499 # the 'adopt' mode is really special and doesn't share any option
1501 parser.add_option("-m","--module",action="append",dest="modules",default=[],
1502 help="modules, can be used several times or with quotes")
1503 parser.add_option("-t","--tag",action="store", dest="tag", default='master',
1504 help="specify the tag to adopt, default is 'master'")
1505 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1506 help="run in verbose mode")
1507 (options, args) = parser.parse_args()
1508 options.workdir='unused'
1509 options.dry_run=False
1510 options.mode='adopt'
1511 if len(args)==0 or len(options.modules)==0:
1514 adopt_tag (options,args)
1517 # the other commands (module-* and release-changelog) share the same skeleton
1518 if mode in [ 'tag', 'branch'] :
1519 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1520 help="set new version and reset taglevel to 0")
1521 parser.add_option("-0","--bypass",action="store_true",dest="bypass",default=False,
1522 help="skip checks on existence of the previous tag")
1524 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1525 help="do not update changelog section in specfile when tagging")
1526 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1527 help="specify a build branch; used for locating the *tags.mk files where adoption is to take place")
1528 if mode in [ 'tag', 'sync' ] :
1529 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1530 help="specify editor")
1532 if mode in ['diff','version'] :
1533 parser.add_option("-W","--www", action="store", dest="www", default=False,
1534 help="export diff in html format, e.g. -W trunk")
1537 parser.add_option("-l","--list", action="store_true", dest="list", default=False,
1538 help="just list modules that exhibit differences")
1540 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1541 parser.add_option("-a","--all",action="store_true",dest="all_modules",default=False,
1542 help="run on all modules as found in {}".format(default_modules_list))
1543 parser.add_option("-f","--file",action="store",dest="modules_list",default=None,
1544 help="run on all modules found in specified file")
1545 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1546 help="dry run - shell commands are only displayed")
1547 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1548 default=[], nargs=1,type="string",
1549 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1550 -- can be set multiple times, or use quotes""")
1552 parser.add_option("-w","--workdir", action="store", dest="workdir",
1553 default="{}/{}".format(os.getenv("HOME"),"modules"),
1554 help="""name for dedicated working dir - defaults to ~/modules
1555 ** THIS MUST NOT ** be your usual working directory""")
1556 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1557 help="skip safety checks, such as svn updates -- use with care")
1558 parser.add_option("-B","--build-module",action="store",dest="build_module",default=None,
1559 help="specify a build module to owerride the one in the CONFIG")
1561 # default verbosity depending on function - temp
1562 verbose_modes= ['tag', 'sync', 'branch']
1564 if mode not in verbose_modes:
1565 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1566 help="run in verbose mode")
1568 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1569 help="run in quiet (non-verbose) mode")
1570 (options, args) = parser.parse_args()
1572 if not hasattr(options,'dry_run'):
1573 options.dry_run=False
1574 if not hasattr(options,'www'):
1582 if options.all_modules:
1583 options.modules_list=default_modules_list
1584 if options.modules_list:
1585 args=Command("grep -v '#' {}".format(options.modules_list), options).output_of().split()
1589 Module.init_homedir(options)
1592 if mode in Main.regular_modes:
1593 modules = [ Module(modname,options) for modname in args ]
1594 # hack: create a dummy Module to store errors/warnings
1595 error_module = Module('__errors__',options)
1597 for module in modules:
1598 if len(args)>1 and mode not in Main.silent_modes:
1600 print('========================================', module.friendly_name())
1601 # call the method called do_<mode>
1602 method = Module.__dict__["do_{}".format(mode)]
1605 except Exception as e:
1607 title='<span class="error"> Skipping module {} - failure: {} </span>'\
1608 .format(module.friendly_name(), str(e))
1609 error_module.html_store_title(title)
1612 traceback.print_exc()
1613 print('Skipping module {}: {}'.format(modname,e))
1617 modetitle="Changes to tag in {}".format(options.www)
1618 elif mode == "version":
1619 modetitle="Latest tags in {}".format(options.www)
1620 modules.append(error_module)
1621 error_module.html_dump_header(modetitle)
1622 for module in modules:
1623 module.html_dump_toc()
1624 Module.html_dump_middle()
1625 for module in modules:
1626 module.html_dump_body()
1627 Module.html_dump_footer()
1629 # if we provide, say a b c d, we want to build (a,b) (b,c) and (c,d)
1630 # remember that the changelog in the twiki comes latest first, so
1631 # we typically have here latest latest-1 latest-2
1632 for (tag_new,tag_old) in zip ( args[:-1], args [1:]):
1633 release_changelog(options, tag_old, tag_new)
1636 ####################
1637 if __name__ == "__main__" :
1640 except KeyboardInterrupt: