vserverreference renamed to vserver-reference
[build.git] / module-tools.py
index 425e044..e3aaba7 100755 (executable)
@@ -11,7 +11,14 @@ from optparse import OptionParser
 # Moving to git we decided to rename some of the repositories. Here is
 # a map of name changes applied in git repositories.
 RENAMED_SVN_MODULES = {
 # Moving to git we decided to rename some of the repositories. Here is
 # a map of name changes applied in git repositories.
 RENAMED_SVN_MODULES = {
-    "PLEWWW": "plewww"
+    "PLEWWW": "plewww",
+    "PLCAPI": "plcapi",
+    "BootManager": "bootmanager",
+    "BootCD": "bootcd",
+    "VserverReference": "vserver-reference",
+    "BootstrapFS": "bootstrapfs",
+    "MyPLC": "myplc",
+    "NodeManager": "nodemanager"
     }
 
 def svn_to_git_name(module):
     }
 
 def svn_to_git_name(module):
@@ -161,8 +168,8 @@ class SvnRepository:
         out = Command("svn info %s" % self.path, self.options).output_of()
         for line in out.split('\n'):
             if line.startswith("Repository Root:"):
         out = Command("svn info %s" % self.path, self.options).output_of()
         for line in out.split('\n'):
             if line.startswith("Repository Root:"):
-                repo_root = line.split()[2].strip()
-                return "%s/svn/%s" (repo_root, self.name)
+                root = line.split()[2].strip()
+                return "%s/%s" % (root, self.name())
 
     @classmethod
     def checkout(cls, remote, local, options, recursive=False):
 
     @classmethod
     def checkout(cls, remote, local, options, recursive=False):
@@ -183,7 +190,7 @@ class SvnRepository:
         url = "%s/tags/%s" % (self.repo_root(), tagname)
         return SvnRepository.remote_exists(url)
 
         url = "%s/tags/%s" % (self.repo_root(), tagname)
         return SvnRepository.remote_exists(url)
 
-    def update(self, subdir="", recursive=True):
+    def update(self, subdir="", recursive=True, branch=None):
         path = os.path.join(self.path, subdir)
         if recursive:
             svncommand = "svn up %s" % path
         path = os.path.join(self.path, subdir)
         if recursive:
             svncommand = "svn up %s" % path
@@ -194,7 +201,7 @@ class SvnRepository:
     def commit(self, logfile):
         # add all new files to the repository
         Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
     def commit(self, logfile):
         # add all new files to the repository
         Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
-                self.path, self.options).run_silent()
+                self.path, self.options).output_of()
         Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
 
     def to_branch(self, branch):
         Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
 
     def to_branch(self, branch):
@@ -210,18 +217,26 @@ class SvnRepository:
         self_url = self.url()
         Command("svn copy -F %s %s %s" % (logfile, self_url, tag_url), self.options).run_fatal()
 
         self_url = self.url()
         Command("svn copy -F %s %s %s" % (logfile, self_url, tag_url), self.options).run_fatal()
 
-    def diff(self):
-        return Command("svn diff %s" % self.path, self.options).output_of(True)
+    def diff(self, f=""):
+        if f:
+            f = os.path.join(self.path, f)
+        else:
+            f = self.path
+        return Command("svn diff %s" % f, self.options).output_of(True)
 
     def diff_with_tag(self, tagname):
         tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
         return Command("svn diff %s %s" % (tag_url, self.url()),
                        self.options).output_of(True)
 
 
     def diff_with_tag(self, tagname):
         tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
         return Command("svn diff %s %s" % (tag_url, self.url()),
                        self.options).output_of(True)
 
-    def revert(self):
-        Command("svn revert %s -R" % self.path, self.options).run_fatal()
-        Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
-                self.path, self.options).run_silent()
+    def revert(self, f=""):
+        if f:
+            Command("svn revert %s" % os.path.join(self.path, f), self.options).run_fatal()
+        else:
+            # revert all
+            Command("svn revert %s -R" % self.path, self.options).run_fatal()
+            Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
+                    self.path, self.options).run_silent()
 
     def is_clean(self):
         command="svn status %s" % self.path
 
     def is_clean(self):
         command="svn status %s" % self.path
@@ -281,8 +296,14 @@ class GitRepository:
         else:
             return self.__run_in_repo(c.run_fatal)
 
         else:
             return self.__run_in_repo(c.run_fatal)
 
-    def update(self, subdir=None, recursive=None):
-        return self.__run_command_in_repo("git pull")
+    def update(self, subdir=None, recursive=None, branch="master"):
+        if branch == "master":
+            self.__run_command_in_repo("git checkout %s" % branch)
+        else:
+            self.__run_command_in_repo("git checkout origin/%s" % branch)
+        self.__run_command_in_repo("git fetch origin --tags")
+        self.__run_command_in_repo("git fetch origin")
+        self.__run_command_in_repo("git merge --ff origin/%s" % branch)
 
     def to_branch(self, branch, remote=True):
         if remote:
 
     def to_branch(self, branch, remote=True):
         if remote:
@@ -296,8 +317,8 @@ class GitRepository:
         self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
         self.commit(logfile)
 
         self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
         self.commit(logfile)
 
-    def diff(self):
-        c = Command("git diff", self.options)
+    def diff(self, f=""):
+        c = Command("git diff %s" % f, self.options)
         return self.__run_in_repo(c.output_of, with_stderr=True)
 
     def diff_with_tag(self, tagname):
         return self.__run_in_repo(c.output_of, with_stderr=True)
 
     def diff_with_tag(self, tagname):
@@ -305,14 +326,19 @@ class GitRepository:
         return self.__run_in_repo(c.output_of, with_stderr=True)
 
     def commit(self, logfile):
         return self.__run_in_repo(c.output_of, with_stderr=True)
 
     def commit(self, logfile):
-        self.__run_command_in_repo("git add -A", ignore_errors=True)
+        self.__run_command_in_repo("git add .", ignore_errors=True)
+        self.__run_command_in_repo("git add -u", ignore_errors=True)
         self.__run_command_in_repo("git commit -F  %s" % logfile, ignore_errors=True)
         self.__run_command_in_repo("git push")
         self.__run_command_in_repo("git push --tags")
 
         self.__run_command_in_repo("git commit -F  %s" % logfile, ignore_errors=True)
         self.__run_command_in_repo("git push")
         self.__run_command_in_repo("git push --tags")
 
-    def revert(self):
-        self.__run_command_in_repo("git --no-pager reset --hard")
-        self.__run_command_in_repo("git --no-pager clean -f")
+    def revert(self, f=""):
+        if f:
+            self.__run_command_in_repo("git checkout %s" % f)
+        else:
+            # revert all
+            self.__run_command_in_repo("git --no-pager reset --hard")
+            self.__run_command_in_repo("git --no-pager clean -f")
 
     def is_clean(self):
         def check_commit():
 
     def is_clean(self):
         def check_commit():
@@ -338,9 +364,12 @@ class Repository:
                 break
 
     @classmethod
                 break
 
     @classmethod
-    def has_moved_to_git(cls, module, svnpath):
+    def has_moved_to_git(cls, module, config):
         module = git_to_svn_name(module)
         module = git_to_svn_name(module)
-        return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
+        # check if the module is already in Git
+#        return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (config['svnpath'], module))
+        return GitRepository.remote_exists(Module.git_remote_dir(module))
+
 
     @classmethod
     def remote_exists(cls, remote):
 
     @classmethod
     def remote_exists(cls, remote):
@@ -373,6 +402,7 @@ class Module:
     configKeys=[ ('svnpath',"Enter your toplevel svnpath",
                   "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
                  ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
     configKeys=[ ('svnpath',"Enter your toplevel svnpath",
                   "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
                  ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
+                 ('gituser', "Enter your user name (login name) on git server", commands.getoutput("id -un")),
                  ("build", "Enter the name of your build module","build"),
                  ('username',"Enter your firstname and lastname for changelogs",""),
                  ("email","Enter your email address for changelogs",""),
                  ("build", "Enter the name of your build module","build"),
                  ('username',"Enter your firstname and lastname for changelogs",""),
                  ("email","Enter your email address for changelogs",""),
@@ -448,7 +478,7 @@ class Module:
 
     @classmethod
     def git_remote_dir (cls, name):
 
     @classmethod
     def git_remote_dir (cls, name):
-        return "%s:/git/%s.git" % (cls.config['gitserver'], name)
+        return "%s@%s:/git/%s.git" % (cls.config['gituser'], cls.config['gitserver'], name)
 
     @classmethod
     def svn_remote_dir (cls, name):
 
     @classmethod
     def svn_remote_dir (cls, name):
@@ -532,12 +562,15 @@ that for other purposes than tagging""" % options.workdir
                 store_config()
 
             build_dir = os.path.join(options.workdir, cls.config['build'])
                 store_config()
 
             build_dir = os.path.join(options.workdir, cls.config['build'])
-            build = Repository(build_dir, options)
-            if not build.is_clean():
-                print "build module needs a revert"
-                build.revert()
-                print "OK"
-            build.update()
+            if not os.path.isdir(build_dir):
+                checkout_build()
+            else:
+                build = Repository(build_dir, options)
+                if not build.is_clean():
+                    print "build module needs a revert"
+                    build.revert()
+                    print "OK"
+                build.update()
 
         if options.verbose and options.mode not in Main.silent_modes:
             print '******** Using config'
 
         if options.verbose and options.mode not in Main.silent_modes:
             print '******** Using config'
@@ -549,7 +582,7 @@ that for other purposes than tagging""" % options.workdir
             print 'Checking for',self.module_dir
 
         if not os.path.isdir (self.module_dir):
             print 'Checking for',self.module_dir
 
         if not os.path.isdir (self.module_dir):
-            if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
+            if Repository.has_moved_to_git(self.name, Module.config):
                 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
                                                          self.module_dir,
                                                          self.options)
                 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
                                                          self.module_dir,
                                                          self.options)
@@ -562,7 +595,7 @@ that for other purposes than tagging""" % options.workdir
         self.repository = Repository(self.module_dir, self.options)
         if self.repository.type == "svn":
             # check if module has moved to git    
         self.repository = Repository(self.module_dir, self.options)
         if self.repository.type == "svn":
             # check if module has moved to git    
-            if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
+            if Repository.has_moved_to_git(self.name, Module.config):
                 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
                 self.init_module_dir()
             # check if we have the required branch/tag
                 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
                 self.init_module_dir()
             # check if we have the required branch/tag
@@ -596,7 +629,13 @@ that for other purposes than tagging""" % options.workdir
             return
         if self.options.verbose:
             print 'Updating', self.module_dir
             return
         if self.options.verbose:
             print 'Updating', self.module_dir
-        self.repository.update()
+
+        if hasattr(self,'branch'):
+            self.repository.update(branch=self.branch)
+        elif hasattr(self,'tagname'):
+            self.repository.update(branch=self.tagname)
+        else:
+            self.repository.update()
 
     def main_specname (self):
         attempt="%s/%s.spec"%(self.module_dir,self.name)
 
     def main_specname (self):
         attempt="%s/%s.spec"%(self.module_dir,self.name)
@@ -758,7 +797,7 @@ that for other purposes than tagging""" % options.workdir
 
 ##############################
     # using fine_grain means replacing only those instances that currently refer to this tag
 
 ##############################
     # using fine_grain means replacing only those instances that currently refer to this tag
-    # otherwise, <module>-SVNPATH is replaced unconditionnally
+    # otherwise, <module>-{SVNPATH,GITPATH} is replaced unconditionnally
     def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
         newtagsfile=tagsfile+".new"
         tags=open (tagsfile)
     def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
         newtagsfile=tagsfile+".new"
         tags=open (tagsfile)
@@ -780,17 +819,21 @@ that for other purposes than tagging""" % options.workdir
         # brute-force : change uncommented lines that define <module>-SVNPATH
         else:
             if self.options.verbose:
         # brute-force : change uncommented lines that define <module>-SVNPATH
         else:
             if self.options.verbose:
-                print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
-            pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
+                print 'Searching for -SVNPATH or -GITPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
+            pattern="\A\s*(?P<make_name>[^\s]+)-(SVNPATH|GITPATH)\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s[^\s]+"\
                                           %(self.name)
             matcher_module=re.compile(pattern)
             for line in tags.readlines():
                 attempt=matcher_module.match(line)
                 if attempt:
                                           %(self.name)
             matcher_module=re.compile(pattern)
             for line in tags.readlines():
                 attempt=matcher_module.match(line)
                 if attempt:
-                    svnpath="%s-SVNPATH"%(attempt.group('make_name'))
+                    if line.find("-GITPATH") >= 0:
+                        modulepath = "%s-GITPATH"%(attempt.group('make_name'))
+                        replacement = "%-32s:= %s/%s.git@%s\n"%(modulepath,attempt.group('url_main'),self.name,newname)
+                    else:
+                        modulepath = "%s-SVNPATH"%(attempt.group('make_name'))
+                        replacement = "%-32s:= %s/%s/tags/%s\n"%(modulepath,attempt.group('url_main'),self.name,newname)
                     if self.options.verbose:
                     if self.options.verbose:
-                        print ' '+svnpath, 
-                    replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
+                        print ' ' + modulepath, 
                     new.write(replacement)
                     matches += 1
                 else:
                     new.write(replacement)
                     matches += 1
                 else:
@@ -816,9 +859,13 @@ that for other purposes than tagging""" % options.workdir
                 found_tagname = old_svn_tag_name
 
         if (found and need_it) or (not found and not need_it):
                 found_tagname = old_svn_tag_name
 
         if (found and need_it) or (not found and not need_it):
-            print "OK "
+            if self.options.verbose:
+                print "OK",
+                if found: print "- found"
+                else: print "- not found"
         else:
         else:
-            print "KO"
+            if self.options.verbose:
+                print "KO"
             if found:
                 raise Exception, "tag (%s) is already there" % tagname
             else:
             if found:
                 raise Exception, "tag (%s) is already there" % tagname
             else:
@@ -827,6 +874,7 @@ that for other purposes than tagging""" % options.workdir
         return found_tagname
 
 
         return found_tagname
 
 
+##############################
     def do_tag (self):
         self.init_module_dir()
         self.revert_module_dir()
     def do_tag (self):
         self.init_module_dir()
         self.revert_module_dir()
@@ -922,14 +970,14 @@ Please write a changelog for this new tag in the section above
                     elif choice == 'f':
                         self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
                     elif choice == 'd':
                     elif choice == 'f':
                         self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
                     elif choice == 'd':
-                        self.run("svn diff %s"%tagsfile)
+                        print build.diff(f=tagsfile)
                     elif choice == 'r':
                     elif choice == 'r':
-                        self.run("svn revert %s"%tagsfile)
+                        build.revert(f=tagsfile)
                     elif choice == 'c':
                         self.run("cat %s"%tagsfile)
                     else:
                         name=self.name
                     elif choice == 'c':
                         self.run("cat %s"%tagsfile)
                     else:
                         name=self.name
-                        print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
+                        print """y: change %(name)s-{SVNPATH,GITPATH} only if it currently refers to %(old_tag_name)s
 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
 d: show current diff for this tag file
 r: revert that tag file
 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
 d: show current diff for this tag file
 r: revert that tag file
@@ -959,7 +1007,147 @@ n: move to next file"""%locals()
         else:
             os.unlink(changelog)
             os.unlink(changelog_svn)
         else:
             os.unlink(changelog)
             os.unlink(changelog_svn)
-            
+
+
+##############################
+    def do_version (self):
+        self.init_module_dir()
+        self.revert_module_dir()
+        self.update_module_dir()
+        spec_dict = self.spec_dict()
+        if self.options.www:
+            self.html_store_title('Version for module %s (%s)' % (self.friendly_name(),
+                                                                  self.last_tag(spec_dict)))
+        for varname in self.varnames:
+            if not spec_dict.has_key(varname):
+                self.html_print ('Could not find %%define for %s'%varname)
+                return
+            else:
+                self.html_print ("%-16s %s"%(varname,spec_dict[varname]))
+        if self.options.verbose:
+            self.html_print ("%-16s %s"%('main specfile:',self.main_specname()))
+            self.html_print ("%-16s %s"%('specfiles:',self.all_specnames()))
+        self.html_print_end()
+
+
+##############################
+    def do_diff (self):
+        self.init_module_dir()
+        self.revert_module_dir()
+        self.update_module_dir()
+        spec_dict = self.spec_dict()
+        self.show_dict(spec_dict)
+
+        # side effects
+        tag_name = self.tag_name(spec_dict)
+        old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
+
+        # sanity check
+        tag_name = self.check_tag(tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
+
+        if self.options.verbose:
+            print 'Getting diff'
+        diff_output = self.repository.diff_with_tag(tag_name)
+
+        if self.options.list:
+            if diff_output:
+                print self.name
+        else:
+            thename=self.friendly_name()
+            do_print=False
+            if self.options.www and diff_output:
+                self.html_store_title("Diffs in module %s (%s) : %d chars"%(\
+                        thename,self.last_tag(spec_dict),len(diff_output)))
+
+                self.html_store_raw ('<p> &lt; (left) %s </p>' % tag_name)
+                self.html_store_raw ('<p> &gt; (right) %s </p>' % thename)
+                self.html_store_pre (diff_output)
+            elif not self.options.www:
+                print 'x'*30,'module',thename
+                print 'x'*20,'<',tag_name
+                print 'x'*20,'>',thename
+                print diff_output
+
+##############################
+    # store and restitute html fragments
+    @staticmethod 
+    def html_href (url,text): return '<a href="%s">%s</a>'%(url,text)
+
+    @staticmethod 
+    def html_anchor (url,text): return '<a name="%s">%s</a>'%(url,text)
+
+    @staticmethod
+    def html_quote (text):
+        return text.replace('&','&#38;').replace('<','&lt;').replace('>','&gt;')
+
+    # only the fake error module has multiple titles
+    def html_store_title (self, title):
+        if not hasattr(self,'titles'): self.titles=[]
+        self.titles.append(title)
+
+    def html_store_raw (self, html):
+        if not hasattr(self,'body'): self.body=''
+        self.body += html
+
+    def html_store_pre (self, text):
+        if not hasattr(self,'body'): self.body=''
+        self.body += '<pre>' + self.html_quote(text) + '</pre>'
+
+    def html_print (self, txt):
+        if not self.options.www:
+            print txt
+        else:
+            if not hasattr(self,'in_list') or not self.in_list:
+                self.html_store_raw('<ul>')
+                self.in_list=True
+            self.html_store_raw('<li>'+txt+'</li>')
+
+    def html_print_end (self):
+        if self.options.www:
+            self.html_store_raw ('</ul>')
+
+    @staticmethod
+    def html_dump_header(title):
+        nowdate=time.strftime("%Y-%m-%d")
+        nowtime=time.strftime("%H:%M (%Z)")
+        print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<title> %s </title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<style type="text/css">
+body { font-family:georgia, serif; }
+h1 {font-size: large; }
+p.title {font-size: x-large; }
+span.error {text-weight:bold; color: red; }
+</style>
+</head>
+<body>
+<p class='title'> %s - status on %s at %s</p>
+<ul>
+"""%(title,title,nowdate,nowtime)
+
+    @staticmethod
+    def html_dump_middle():
+        print "</ul>"
+
+    @staticmethod
+    def html_dump_footer():
+        print "</body></html"
+
+    def html_dump_toc(self):
+        if hasattr(self,'titles'):
+            for title in self.titles:
+                print '<li>',self.html_href ('#'+self.friendly_name(),title),'</li>'
+
+    def html_dump_body(self):
+        if hasattr(self,'titles'):
+            for title in self.titles:
+                print '<hr /><h1>',self.html_anchor(self.friendly_name(),title),'</h1>'
+        if hasattr(self,'body'):
+            print self.body
+            print '<p class="top">',self.html_href('#','Back to top'),'</p>'            
+
 
 ##############################
 class Main:
 
 ##############################
 class Main:
@@ -1048,8 +1236,20 @@ Branches:
         if mode == "tag" or mode == "sync" :
             parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
                               help="specify editor")
         if mode == "tag" or mode == "sync" :
             parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
                               help="specify editor")
+
+        if mode in ["diff","version"] :
+            parser.add_option("-W","--www", action="store", dest="www", default=False,
+                              help="export diff in html format, e.g. -W trunk")
+
+        if mode == "diff" :
+            parser.add_option("-l","--list", action="store_true", dest="list", default=False,
+                              help="just list modules that exhibit differences")
             
         default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
             
         default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
+        parser.add_option("-a","--all",action="store_true",dest="all_modules",default=False,
+                          help="run on all modules as found in %s"%default_modules_list)
+        parser.add_option("-f","--file",action="store",dest="modules_list",default=None,
+                          help="run on all modules found in specified file")
         parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
                           help="dry run - shell commands are only displayed")
         parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
         parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
                           help="dry run - shell commands are only displayed")
         parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
@@ -1083,22 +1283,51 @@ Branches:
 
         ########## module-*
         if len(args) == 0:
 
         ########## module-*
         if len(args) == 0:
-            parser.print_help()
-            sys.exit(1)
+            if options.all_modules:
+                options.modules_list=default_modules_list
+            if options.modules_list:
+                args=Command("grep -v '#' %s"%options.modules_list,options).output_of().split()
+            else:
+                parser.print_help()
+                sys.exit(1)
         Module.init_homedir(options)
         Module.init_homedir(options)
+        
 
         modules=[ Module(modname,options) for modname in args ]
 
         modules=[ Module(modname,options) for modname in args ]
+        # hack: create a dummy Module to store errors/warnings
+        error_module = Module('__errors__',options)
+
         for module in modules:
             if len(args)>1 and mode not in Main.silent_modes:
         for module in modules:
             if len(args)>1 and mode not in Main.silent_modes:
-                print '========================================',module.friendly_name()
+                if not options.www:
+                    print '========================================',module.friendly_name()
             # call the method called do_<mode>
             method=Module.__dict__["do_%s"%mode]
             try:
                 method(module)
             except Exception,e:
             # call the method called do_<mode>
             method=Module.__dict__["do_%s"%mode]
             try:
                 method(module)
             except Exception,e:
-                import traceback
-                traceback.print_exc()
-                print 'Skipping module %s: '%modname,e
+                if options.www:
+                    title='<span class="error"> Skipping module %s - failure: %s </span>'%\
+                        (module.friendly_name(), str(e))
+                    error_module.html_store_title(title)
+                else:
+                    import traceback
+                    traceback.print_exc()
+                    print 'Skipping module %s: '%modname,e
+
+        if options.www:
+            if mode == "diff":
+                modetitle="Changes to tag in %s"%options.www
+            elif mode == "version":
+                modetitle="Latest tags in %s"%options.www
+            modules.append(error_module)
+            error_module.html_dump_header(modetitle)
+            for module in modules:
+                module.html_dump_toc()
+            Module.html_dump_middle()
+            for module in modules:
+                module.html_dump_body()
+            Module.html_dump_footer()
 
 ####################
 if __name__ == "__main__" :
 
 ####################
 if __name__ == "__main__" :