turn rvm-ruby back on for forensics
[build.git] / module-tools.py
index 6b41c35..58f7b85 100755 (executable)
@@ -1,8 +1,9 @@
 #!/usr/bin/python -u
 
 #!/usr/bin/python -u
 
-import sys, os
+import sys, os, os.path
 import re
 import time
 import re
 import time
+import tempfile
 from glob import glob
 from optparse import OptionParser
 
 from glob import glob
 from optparse import OptionParser
 
@@ -11,7 +12,15 @@ 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",
+    "MyPLC": "myplc",
+    "CoDemux": "codemux",
+    "NodeManager": "nodemanager",
+    "NodeUpdate": "nodeupdate",
+    "Monitor": "monitor",
     }
 
 def svn_to_git_name(module):
     }
 
 def svn_to_git_name(module):
@@ -102,6 +111,7 @@ class Command:
             print 'dry_run',self.command
             return 0
         if self.options.verbose:
             print 'dry_run',self.command
             return 0
         if self.options.verbose:
+            print '>',os.getcwd()
             print '+',self.command,' .. ',
             sys.stdout.flush()
         retcod=os.system(self.command + " &> " + self.tmp)
             print '+',self.command,' .. ',
             sys.stdout.flush()
         retcod=os.system(self.command + " &> " + self.tmp)
@@ -151,6 +161,11 @@ class SvnRepository:
     def name(self):
         return os.path.basename(self.path)
 
     def name(self):
         return os.path.basename(self.path)
 
+    def pathname(self):
+        # for svn modules pathname is just the name of the module as
+        # all modules are at the root
+        return self.name()
+
     def url(self):
         out = Command("svn info %s" % self.path, self.options).output_of()
         for line in out.split('\n'):
     def url(self):
         out = Command("svn info %s" % self.path, self.options).output_of()
         for line in out.split('\n'):
@@ -162,10 +177,10 @@ class SvnRepository:
         for line in out.split('\n'):
             if line.startswith("Repository Root:"):
                 root = line.split()[2].strip()
         for line in out.split('\n'):
             if line.startswith("Repository Root:"):
                 root = line.split()[2].strip()
-                return "%s/%s" % (root, self.name())
+                return "%s/%s" % (root, self.pathname())
 
     @classmethod
 
     @classmethod
-    def checkout(cls, remote, local, options, recursive=False):
+    def clone(cls, remote, local, options, recursive=False):
         if recursive:
             svncommand = "svn co %s %s" % (remote, local)
         else:
         if recursive:
             svncommand = "svn co %s %s" % (remote, local)
         else:
@@ -176,14 +191,14 @@ class SvnRepository:
         return SvnRepository(local, options)
 
     @classmethod
         return SvnRepository(local, options)
 
     @classmethod
-    def remote_exists(cls, remote):
-        return os.system("svn list %s &> /dev/null" % remote) == 0
+    def remote_exists(cls, remote, options):
+        return Command ("svn list %s &> /dev/null" % remote , options).run()==0
 
     def tag_exists(self, tagname):
         url = "%s/tags/%s" % (self.repo_root(), tagname)
 
     def tag_exists(self, tagname):
         url = "%s/tags/%s" % (self.repo_root(), tagname)
-        return SvnRepository.remote_exists(url)
+        return SvnRepository.remote_exists(url, self.options)
 
 
-    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,16 +209,16 @@ 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):
         remote = "%s/branches/%s" % (self.repo_root(), branch)
         Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
 
     def to_branch(self, branch):
         remote = "%s/branches/%s" % (self.repo_root(), branch)
-        SvnRepository.checkout(remote, self.path, self.options, recursive=True)
+        SvnRepository.clone(remote, self.path, self.options, recursive=True)
 
     def to_tag(self, tag):
         remote = "%s/tags/%s" % (self.repo_root(), branch)
 
     def to_tag(self, tag):
         remote = "%s/tags/%s" % (self.repo_root(), branch)
-        SvnRepository.checkout(remote, self.path, self.options, recursive=True)
+        SvnRepository.clone(remote, self.path, self.options, recursive=True)
 
     def tag(self, tagname, logfile):
         tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
 
     def tag(self, tagname, logfile):
         tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
@@ -250,24 +265,29 @@ class GitRepository:
         return os.path.basename(self.path)
 
     def url(self):
         return os.path.basename(self.path)
 
     def url(self):
-        self.repo_root()
+        return self.repo_root()
+
+    def gitweb(self):
+        c = Command("git show | grep commit | awk '{print $2;}'", self.options)
+        out = self.__run_in_repo(c.output_of).strip()
+        return "http://git.onelab.eu/?p=%s.git;a=commit;h=%s" % (self.name(), out)
 
     def repo_root(self):
         c = Command("git remote show origin", self.options)
         out = self.__run_in_repo(c.output_of)
         for line in out.split('\n'):
             if line.strip().startswith("Fetch URL:"):
 
     def repo_root(self):
         c = Command("git remote show origin", self.options)
         out = self.__run_in_repo(c.output_of)
         for line in out.split('\n'):
             if line.strip().startswith("Fetch URL:"):
-                repo = line.split()[2]
+                return line.split()[2]
 
     @classmethod
 
     @classmethod
-    def checkout(cls, remote, local, options, depth=1):
+    def clone(cls, remote, local, options, depth=0):
         Command("rm -rf %s" % local, options).run_silent()
         Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
         return GitRepository(local, options)
 
     @classmethod
         Command("rm -rf %s" % local, options).run_silent()
         Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
         return GitRepository(local, options)
 
     @classmethod
-    def remote_exists(cls, remote):
-        return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
+    def remote_exists(cls, remote, options):
+        return Command ("git --no-pager ls-remote %s &> /dev/null" % remote, options).run()==0
 
     def tag_exists(self, tagname):
         command = 'git tag -l | grep "^%s$"' % tagname
 
     def tag_exists(self, tagname):
         command = 'git tag -l | grep "^%s$"' % tagname
@@ -289,15 +309,34 @@ 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 __is_commit_id(self, id):
+        c = Command("git show %s | grep commit | awk '{print $2;}'" % id, self.options)
+        ret = self.__run_in_repo(c.output_of, with_stderr=False)
+        if ret.strip() == id:
+            return True
+        return False
+
+    def update(self, subdir=None, recursive=None, branch="master"):
+        if branch == "master":
+            self.__run_command_in_repo("git checkout %s" % branch)
+        else:
+            self.to_branch(branch, remote=True)
+        self.__run_command_in_repo("git fetch origin --tags")
+        self.__run_command_in_repo("git fetch origin")
+        if not self.__is_commit_id(branch):
+            # we don't need to merge anythign for commit ids.
+            self.__run_command_in_repo("git merge --ff origin/%s" % branch)
 
     def to_branch(self, branch, remote=True):
 
     def to_branch(self, branch, remote=True):
+        self.revert()
         if remote:
         if remote:
-            branch = "origin/%s" % branch
+            command = "git branch --track %s origin/%s" % (branch, branch)
+            c = Command(command, self.options)
+            self.__run_in_repo(c.output_of, with_stderr=True)
         return self.__run_command_in_repo("git checkout %s" % branch)
 
     def to_tag(self, tag):
         return self.__run_command_in_repo("git checkout %s" % branch)
 
     def to_tag(self, tag):
+        self.revert()
         return self.__run_command_in_repo("git checkout %s" % tag)
 
     def tag(self, tagname, logfile):
         return self.__run_command_in_repo("git checkout %s" % tag)
 
     def tag(self, tagname, logfile):
@@ -312,10 +351,14 @@ class GitRepository:
         c = Command("git diff %s" % tagname, self.options)
         return self.__run_in_repo(c.output_of, with_stderr=True)
 
         c = Command("git diff %s" % tagname, self.options)
         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)
+    def commit(self, logfile, branch="master"):
+        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 commit -F  %s" % logfile, ignore_errors=True)
-        self.__run_command_in_repo("git push")
+        if branch == "master" or self.__is_commit_id(branch):
+            self.__run_command_in_repo("git push")
+        else:
+            self.__run_command_in_repo("git push origin %s:%s" % (branch, branch))
         self.__run_command_in_repo("git push --tags")
 
     def revert(self, f=""):
         self.__run_command_in_repo("git push --tags")
 
     def revert(self, f=""):
@@ -350,14 +393,16 @@ class Repository:
                 break
 
     @classmethod
                 break
 
     @classmethod
-    def has_moved_to_git(cls, module, svnpath):
-        module = git_to_svn_name(module)
-        return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
+    def has_moved_to_git(cls, module, config,options):
+        module = svn_to_git_name(module)
+        # check if the module is already in Git
+        return GitRepository.remote_exists(Module.git_remote_dir(module),options)
+
 
     @classmethod
 
     @classmethod
-    def remote_exists(cls, remote):
+    def remote_exists(cls, remote, options):
         for repo in Repository.supported_repo_types:
         for repo in Repository.supported_repo_types:
-            if repo.remote_exists(remote):
+            if repo.remote_exists(remote, options):
                 return True
         return False
 
                 return True
         return False
 
@@ -369,7 +414,7 @@ class Repository:
 # support for tagged module is minimal, and is for the Build class only
 class Module:
 
 # support for tagged module is minimal, and is for the Build class only
 class Module:
 
-    svn_magic_line="--This line, and those below, will be ignored--"
+    edit_magic_line="--This line, and those below, will be ignored--"
     setting_tag_format = "Setting tag %s"
     
     redirectors=[ # ('module_name_varname','name'),
     setting_tag_format = "Setting tag %s"
     
     redirectors=[ # ('module_name_varname','name'),
@@ -385,7 +430,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", os.getlogin()),
+                 ('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",""),
@@ -402,33 +447,48 @@ class Module:
             while not cls.config[key]:
                 cls.prompt_config_option(key, message, default)
 
             while not cls.config[key]:
                 cls.prompt_config_option(key, message, default)
 
-
-    # for parsing module spec name:branch
-    matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
-    # special form for tagged module - for Build
-    matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
+    # for parsing module spec name:branch                                                                                                                                                                     
+    matcher_branch_spec=re.compile("\A(?P<name>[\w\.\-\/]+):(?P<branch>[\w\.\-]+)\Z")                                                                                                                         
+    # special form for tagged module - for Build                                                                                                                                                              
+    matcher_tag_spec=re.compile("\A(?P<name>[\w\.\-\/]+)@(?P<tagname>[\w\.\-]+)\Z")
     # parsing specfiles
     matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
 
     # parsing specfiles
     matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
 
-    def __init__ (self,module_spec,options):
-        # parse module spec
+    @classmethod
+    def parse_module_spec(cls, module_spec):
+        name = branch_or_tagname = module_type = ""
+
         attempt=Module.matcher_branch_spec.match(module_spec)
         if attempt:
         attempt=Module.matcher_branch_spec.match(module_spec)
         if attempt:
-            self.name=attempt.group('name')
-            self.branch=attempt.group('branch')
+            module_type = "branch"
+            name=attempt.group('name')
+            branch_or_tagname=attempt.group('branch')
         else:
             attempt=Module.matcher_tag_spec.match(module_spec)
             if attempt:
         else:
             attempt=Module.matcher_tag_spec.match(module_spec)
             if attempt:
-                self.name=attempt.group('name')
-                self.tagname=attempt.group('tagname')
+                module_type = "tag"
+                name=attempt.group('name')
+                branch_or_tagname=attempt.group('tagname')
             else:
             else:
-                self.name=module_spec
+                name=module_spec
+        return name, branch_or_tagname, module_type
+
+
+    def __init__ (self,module_spec,options):
+        # parse module spec
+        self.pathname, branch_or_tagname, module_type = self.parse_module_spec(module_spec)
+        self.name = os.path.basename(self.pathname)
+
+        if module_type == "branch":
+            self.branch=branch_or_tagname
+        elif module_type == "tag":
+            self.tagname=branch_or_tagname
 
         # when available prefer to use git module name internally
         self.name = svn_to_git_name(self.name)
 
         self.options=options
 
         # when available prefer to use git module name internally
         self.name = svn_to_git_name(self.name)
 
         self.options=options
-        self.module_dir="%s/%s"%(options.workdir,self.name)
+        self.module_dir="%s/%s"%(options.workdir,self.pathname)
         self.repository = None
         self.build = None
 
         self.repository = None
         self.build = None
 
@@ -453,11 +513,11 @@ class Module:
 
     def friendly_name (self):
         if hasattr(self,'branch'):
 
     def friendly_name (self):
         if hasattr(self,'branch'):
-            return "%s:%s"%(self.name,self.branch)
+            return "%s:%s"%(self.pathname,self.branch)
         elif hasattr(self,'tagname'):
         elif hasattr(self,'tagname'):
-            return "%s@%s"%(self.name,self.tagname)
+            return "%s@%s"%(self.pathname,self.tagname)
         else:
         else:
-            return self.name
+            return self.pathname
 
     @classmethod
     def git_remote_dir (cls, name):
 
     @classmethod
     def git_remote_dir (cls, name):
@@ -498,11 +558,11 @@ module-* commands need a fresh working dir. Make sure that you do not use
 that for other purposes than tagging""" % options.workdir
             sys.exit(1)
 
 that for other purposes than tagging""" % options.workdir
             sys.exit(1)
 
-        def checkout_build():
+        def clone_build():
             print "Checking out build module..."
             remote = cls.git_remote_dir(cls.config['build'])
             local = os.path.join(options.workdir, cls.config['build'])
             print "Checking out build module..."
             remote = cls.git_remote_dir(cls.config['build'])
             local = os.path.join(options.workdir, cls.config['build'])
-            GitRepository.checkout(remote, local, options, depth=1)
+            GitRepository.clone(remote, local, options, depth=1)
             print "OK"
 
         def store_config():
             print "OK"
 
         def store_config():
@@ -522,11 +582,15 @@ that for other purposes than tagging""" % options.workdir
                 Module.config[key]=value                
             f.close()
 
                 Module.config[key]=value                
             f.close()
 
+            # owerride config variables using options.
+            if options.build_module:
+                Module.config['build'] = options.build_module
+
         if not os.path.isdir (options.workdir):
             print "Cannot find",options.workdir,"let's create it"
             Command("mkdir -p %s" % options.workdir, options).run_silent()
             cls.prompt_config()
         if not os.path.isdir (options.workdir):
             print "Cannot find",options.workdir,"let's create it"
             Command("mkdir -p %s" % options.workdir, options).run_silent()
             cls.prompt_config()
-            checkout_build()
+            clone_build()
             store_config()
         else:
             read_config()
             store_config()
         else:
             read_config()
@@ -541,12 +605,12 @@ that for other purposes than tagging""" % options.workdir
             if old_layout:
                 Command("rm -rf %s" % options.workdir, options).run_silent()
                 Command("mkdir -p %s" % options.workdir, options).run_silent()
             if old_layout:
                 Command("rm -rf %s" % options.workdir, options).run_silent()
                 Command("mkdir -p %s" % options.workdir, options).run_silent()
-                checkout_build()
+                clone_build()
                 store_config()
 
             build_dir = os.path.join(options.workdir, cls.config['build'])
             if not os.path.isdir(build_dir):
                 store_config()
 
             build_dir = os.path.join(options.workdir, cls.config['build'])
             if not os.path.isdir(build_dir):
-                checkout_build()
+                clone_build()
             else:
                 build = Repository(build_dir, options)
                 if not build.is_clean():
             else:
                 build = Repository(build_dir, options)
                 if not build.is_clean():
@@ -565,20 +629,20 @@ 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']):
-                self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
-                                                         self.module_dir,
-                                                         self.options)
+            if Repository.has_moved_to_git(self.pathname, Module.config, self.options):
+                self.repository = GitRepository.clone(self.git_remote_dir(self.pathname),
+                                                      self.module_dir,
+                                                      self.options)
             else:
                 remote = self.svn_selected_remote()
             else:
                 remote = self.svn_selected_remote()
-                self.repository = SvnRepository.checkout(remote,
-                                                         self.module_dir,
-                                                         self.options, recursive=False)
+                self.repository = SvnRepository.clone(remote,
+                                                      self.module_dir,
+                                                      self.options, recursive=False)
 
         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.pathname, Module.config, self.options):
                 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
@@ -612,7 +676,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)
@@ -627,7 +697,7 @@ that for other purposes than tagging""" % options.workdir
 
         if level2:
             return level2[0]
 
         if level2:
             return level2[0]
-        raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
+        raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.pathname,pattern1,pattern2)
 
     def all_specnames (self):
         level1=glob("%s/*.spec" % self.module_dir)
 
     def all_specnames (self):
         level1=glob("%s/*.spec" % self.module_dir)
@@ -714,7 +784,7 @@ that for other purposes than tagging""" % options.workdir
         result=[]
         white_line_matcher = re.compile("\A\s*\Z")
         for logline in file(logfile).readlines():
         result=[]
         white_line_matcher = re.compile("\A\s*\Z")
         for logline in file(logfile).readlines():
-            if logline.strip() == Module.svn_magic_line:
+            if logline.strip() == Module.edit_magic_line:
                 break
             elif white_line_matcher.match(logline):
                 continue
                 break
             elif white_line_matcher.match(logline):
                 continue
@@ -723,14 +793,14 @@ that for other purposes than tagging""" % options.workdir
         return result
 
     # creates a copy of the input with only the unignored lines
         return result
 
     # creates a copy of the input with only the unignored lines
-    def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
+    def strip_magic_line_filename (self, filein, fileout ,new_tag_name):
        f=file(fileout,'w')
        f.write(self.setting_tag_format%new_tag_name + '\n')
        for line in self.unignored_lines(filein):
            f.write(line)
        f.close()
 
        f=file(fileout,'w')
        f.write(self.setting_tag_format%new_tag_name + '\n')
        for line in self.unignored_lines(filein):
            f.write(line)
        f.close()
 
-    def insert_changelog (self, logfile, oldtag, newtag):
+    def insert_changelog (self, logfile, newtag):
         for specfile in self.all_specnames():
             newspecfile=specfile+".new"
             if self.options.verbose:
         for specfile in self.all_specnames():
             newspecfile=specfile+".new"
             if self.options.verbose:
@@ -772,9 +842,20 @@ that for other purposes than tagging""" % options.workdir
         return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
     
 
         return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
     
 
+    pattern_format="\A\s*%(module)s-(SVNPATH|GITPATH)\s*(=|:=)\s*(?P<url_main>[^\s]+)/%(module)s[^\s]+"
+
+    def is_mentioned_in_tagsfile (self, tagsfile):
+        # so that %(module)s gets replaced from format
+        module=self.name
+        module_matcher = re.compile(Module.pattern_format % locals())
+        with open(tagsfile) as f:
+            for line in f.readlines():
+                if module_matcher.match(line): return True
+        return False
+
 ##############################
     # 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)
@@ -796,17 +877,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]+"\
-                                          %(self.name)
-            matcher_module=re.compile(pattern)
+                print 'Searching for -SVNPATH or -GITPATH lines referring to /%s/\n\tin %s .. '%(self.pathname,tagsfile),
+            # so that %(module)s gets replaced from format
+            module=self.name
+            module_matcher=re.compile(Module.pattern_format % locals())
             for line in tags.readlines():
             for line in tags.readlines():
-                attempt=matcher_module.match(line)
+                attempt=module_matcher.match(line)
                 if attempt:
                 if attempt:
-                    svnpath="%s-SVNPATH"%(attempt.group('make_name'))
+                    if line.find("-GITPATH") >= 0:
+                        modulepath = "%s-GITPATH"%self.name
+                        replacement = "%-32s:= %s/%s.git@%s\n"%(modulepath,attempt.group('url_main'),self.pathname,newname)
+                    else:
+                        modulepath = "%s-SVNPATH"%self.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:
@@ -856,9 +941,11 @@ that for other purposes than tagging""" % options.workdir
         spec_dict = self.spec_dict()
         self.show_dict(spec_dict)
         
         spec_dict = self.spec_dict()
         self.show_dict(spec_dict)
         
-        # side effects
-        old_tag_name = self.tag_name(spec_dict)
-        old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
+        # compute previous tag - if not bypassed
+        if not self.options.bypass:
+            old_tag_name = self.tag_name(spec_dict)
+            # sanity check
+            old_tag_name = self.check_tag(old_tag_name, need_it=True)
 
         if (self.options.new_version):
             # new version set on command line
 
         if (self.options.new_version):
             # new version set on command line
@@ -870,47 +957,48 @@ that for other purposes than tagging""" % options.workdir
             spec_dict[self.module_taglevel_varname] = new_taglevel
 
         new_tag_name = self.tag_name(spec_dict)
             spec_dict[self.module_taglevel_varname] = new_taglevel
 
         new_tag_name = self.tag_name(spec_dict)
-
         # sanity check
         # sanity check
-        old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
         new_tag_name = self.check_tag(new_tag_name, need_it=False)
 
         # checking for diffs
         new_tag_name = self.check_tag(new_tag_name, need_it=False)
 
         # checking for diffs
-        diff_output = self.repository.diff_with_tag(old_tag_name)
-        if len(diff_output) == 0:
-            if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
-                return
+        if not self.options.bypass:
+            diff_output = self.repository.diff_with_tag(old_tag_name)
+            if len(diff_output) == 0:
+                if not prompt ("No pending difference in module %s, want to tag anyway"%self.pathname,False):
+                    return
 
 
-        # side effect in trunk's specfile
+        # side effect in head's specfile
         self.patch_spec_var(spec_dict)
 
         # prepare changelog file 
         self.patch_spec_var(spec_dict)
 
         # prepare changelog file 
-        # we use the standard subversion magic string (see svn_magic_line)
+        # we use the standard subversion magic string (see edit_magic_line)
         # so we can provide useful information, such as version numbers and diff
         # in the same file
         # so we can provide useful information, such as version numbers and diff
         # in the same file
-        changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
-        changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
+        changelog_plain="/tmp/%s-%d.edit"%(self.name,os.getpid())
+        changelog_strip="/tmp/%s-%d.strip"%(self.name,os.getpid())
         setting_tag_line=Module.setting_tag_format%new_tag_name
         setting_tag_line=Module.setting_tag_format%new_tag_name
-        file(changelog,"w").write("""
+        file(changelog_plain,"w").write("""
 %s
 %s
 %s
 %s
-Please write a changelog for this new tag in the section above
-"""%(Module.svn_magic_line,setting_tag_line))
+Please write a changelog for this new tag in the section below
+"""%(Module.edit_magic_line,setting_tag_line))
 
 
-        if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
-            file(changelog,"a").write('DIFF=========\n' + diff_output)
+        if self.options.bypass: 
+            pass
+        elif prompt('Want to see diffs while writing changelog',True):
+            file(changelog_plain,"a").write('DIFF=========\n' + diff_output)
         
         if self.options.debug:
             prompt('Proceed ?')
 
         # edit it        
         
         if self.options.debug:
             prompt('Proceed ?')
 
         # edit it        
-        self.run("%s %s"%(self.options.editor,changelog))
+        self.run("%s %s"%(self.options.editor,changelog_plain))
         # strip magic line in second file - looks like svn has changed its magic line with 1.6
         # so we do the job ourselves
         # strip magic line in second file - looks like svn has changed its magic line with 1.6
         # so we do the job ourselves
-        self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
+        self.strip_magic_line_filename(changelog_plain,changelog_strip,new_tag_name)
         # insert changelog in spec
         if self.options.changelog:
         # insert changelog in spec
         if self.options.changelog:
-            self.insert_changelog (changelog,old_tag_name,new_tag_name)
+            self.insert_changelog (changelog_plain,new_tag_name)
 
         ## update build
         build_path = os.path.join(self.options.workdir,
 
         ## update build
         build_path = os.path.join(self.options.workdir,
@@ -921,12 +1009,17 @@ Please write a changelog for this new tag in the section above
         if not build.is_clean():
             build.revert()
 
         if not build.is_clean():
             build.revert()
 
-        tagsfiles=glob(build.path+"/*-tags*.mk")
+        tagsfiles=glob(build.path+"/*-tags.mk")
         tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
         default_answer = 'y'
         tagsfiles.sort()
         while True:
         tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
         default_answer = 'y'
         tagsfiles.sort()
         while True:
+            # do not bother if in bypass mode
+            if self.options.bypass: break
             for tagsfile in tagsfiles:
             for tagsfile in tagsfiles:
+                if not self.is_mentioned_in_tagsfile (tagsfile):
+                    if self.options.verbose: print "tagsfile %s does not mention %s - skipped"%(tagsfile,self.name)
+                    continue
                 status=tagsdict[tagsfile]
                 basename=os.path.basename(tagsfile)
                 print ".................... Dealing with %s"%basename
                 status=tagsdict[tagsfile]
                 basename=os.path.basename(tagsfile)
                 print ".................... Dealing with %s"%basename
@@ -943,14 +1036,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':
-                        print build.diff(f=tagsfile)
+                        print build.diff(f=os.path.basename(tagsfile))
                     elif choice == 'r':
                         build.revert(f=tagsfile)
                     elif choice == 'c':
                         self.run("cat %s"%tagsfile)
                     else:
                         name=self.name
                     elif choice == 'r':
                         build.revert(f=tagsfile)
                     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
@@ -968,18 +1061,21 @@ n: move to next file"""%locals()
             print self.repository.diff()
 
         def commit_all_changes(log):
             print self.repository.diff()
 
         def commit_all_changes(log):
-            self.repository.commit(log)
+            if hasattr(self,'branch'):
+                self.repository.commit(log, branch=self.branch)
+            else:
+                self.repository.commit(log)
             build.commit(log)
 
         self.run_prompt("Review module and build", diff_all_changes)
             build.commit(log)
 
         self.run_prompt("Review module and build", diff_all_changes)
-        self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
-        self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
+        self.run_prompt("Commit module and build", commit_all_changes, changelog_strip)
+        self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_strip)
 
         if self.options.debug:
 
         if self.options.debug:
-            print 'Preserving',changelog,'and stripped',changelog_svn
+            print 'Preserving',changelog_plain,'and stripped',changelog_strip
         else:
         else:
-            os.unlink(changelog)
-            os.unlink(changelog_svn)
+            os.unlink(changelog_plain)
+            os.unlink(changelog_strip)
 
 
 ##############################
 
 
 ##############################
@@ -997,6 +1093,7 @@ n: move to next file"""%locals()
                 return
             else:
                 self.html_print ("%-16s %s"%(varname,spec_dict[varname]))
                 return
             else:
                 self.html_print ("%-16s %s"%(varname,spec_dict[varname]))
+        self.html_print ("%-16s %s"%('url',self.repository.url()))
         if self.options.verbose:
             self.html_print ("%-16s %s"%('main specfile:',self.main_specname()))
             self.html_print ("%-16s %s"%('specfiles:',self.all_specnames()))
         if self.options.verbose:
             self.html_print ("%-16s %s"%('main specfile:',self.main_specname()))
             self.html_print ("%-16s %s"%('specfiles:',self.all_specnames()))
@@ -1024,7 +1121,7 @@ n: move to next file"""%locals()
 
         if self.options.list:
             if diff_output:
 
         if self.options.list:
             if diff_output:
-                print self.name
+                print self.pathname
         else:
             thename=self.friendly_name()
             do_print=False
         else:
             thename=self.friendly_name()
             do_print=False
@@ -1122,6 +1219,172 @@ span.error {text-weight:bold; color: red; }
             print '<p class="top">',self.html_href('#','Back to top'),'</p>'            
 
 
             print '<p class="top">',self.html_href('#','Back to top'),'</p>'            
 
 
+
+class Build(Module):
+    
+    def __get_modules(self, tagfile):
+        self.init_module_dir()
+        modules = {}
+
+        tagfile = os.path.join(self.module_dir, tagfile)
+        for line in open(tagfile):
+            try:
+                name, url = line.split(':=')
+                name, git_or_svn_path = name.rsplit('-', 1)
+                name = svn_to_git_name(name.strip())
+                modules[name] = (git_or_svn_path.strip(), url.strip())
+            except:
+                pass
+        return modules
+
+    def get_modules(self, tagfile):
+        modules = self.__get_modules(tagfile)
+        for module in modules:
+            module_type = tag_or_branch = ""
+
+            path_type, url = modules[module]
+            if path_type == "GITPATH":
+                module_spec = os.path.split(url)[-1].replace(".git","")
+                name, tag_or_branch, module_type = self.parse_module_spec(module_spec)
+            else:
+                tag_or_branch = os.path.split(url)[-1].strip()
+                if url.find('/tags/') >= 0:
+                    module_type = "tag"
+                elif url.find('/branches/') >= 0:
+                    module_type = "branch"
+            
+            modules[module] = {"module_type" : module_type,
+                               "path_type": path_type,
+                               "tag_or_branch": tag_or_branch,
+                               "url":url}
+        return modules
+                
+        
+
+def modules_diff(first, second):
+    diff = {}
+
+    for module in first:
+        if module not in second: 
+            print "=== module %s missing in right-hand side ==="%module
+            continue
+        if first[module]['tag_or_branch'] != second[module]['tag_or_branch']:
+            diff[module] = (first[module]['tag_or_branch'], second[module]['tag_or_branch'])
+
+    first_set = set(first.keys())
+    second_set = set(second.keys())
+
+    new_modules = list(second_set - first_set)
+    removed_modules = list(first_set - second_set)
+
+    return diff, new_modules, removed_modules
+
+def release_changelog(options, buildtag_old, buildtag_new):
+
+    # the command line expects new old, so we treat the tagfiles in the same order
+    nb_tags=len(options.distrotags)
+    if nb_tags==1:
+        tagfile_new=tagfile_old=options.distrotags[0]
+    elif nb_tags==2:
+        [tagfile_new,tagfile_old]=options.distrotags
+    else:
+        print "ERROR: provide one or two tagfile name (eg. onelab-k32-tags.mk)"
+        print "two tagfiles can be mentioned when a tagfile has been renamed"
+        return
+
+    if options.dry_run:
+        print "------------------------------ Computing Changelog from"
+        print "buildtag_old",buildtag_old,"tagfile_old",tagfile_old
+        print "buildtag_new",buildtag_new,"tagfile_new",tagfile_new
+        return
+
+    print '----'
+    print '----'
+    print '----'
+    print '= build tag %s to %s =' % (buildtag_old, buildtag_new)
+    print '== distro %s (%s to %s) ==' % (tagfile_new, buildtag_old, buildtag_new)
+
+    build = Build("build@%s" % buildtag_old, options)
+    build.init_module_dir()
+    first = build.get_modules(tagfile_old)
+
+    print ' * from', buildtag_old, build.repository.gitweb()
+
+    build = Build("build@%s" % buildtag_new, options)
+    build.init_module_dir()
+    second = build.get_modules(tagfile_new)
+
+    print ' * to', buildtag_new, build.repository.gitweb()
+
+    diff, new_modules, removed_modules = modules_diff(first, second)
+
+
+    def get_module(name, tag):
+        if not tag or  tag == "trunk":
+            return Module("%s" % (module), options)
+        else:
+            return Module("%s@%s" % (module, tag), options)
+
+
+    for module in diff:
+        print '=== %s - %s to %s : package %s ===' % (tagfile_new, buildtag_old, buildtag_new, module)
+
+        first, second = diff[module]
+        m = get_module(module, first)
+        os.system('rm -rf %s' % m.module_dir) # cleanup module dir
+        m.init_module_dir()
+
+        if m.repository.type == "svn":
+            print ' * from', first, m.repository.url()
+        else:
+            print ' * from', first, m.repository.gitweb()
+
+        specfile = m.main_specname()
+        (tmpfd, tmpfile) = tempfile.mkstemp()
+        os.system("cp -f /%s %s" % (specfile, tmpfile))
+        
+        m = get_module(module, second)
+        # patch for ipfw that, being managed in a separate repo, won't work for now
+        try:
+            m.init_module_dir()
+        except Exception,e:
+            print """Could not retrieve module %s - skipped
+{{{ %s }}}
+""" %( m.friendly_name(), e)
+            continue
+        specfile = m.main_specname()
+
+        if m.repository.type == "svn":
+            print ' * to', second, m.repository.url()
+        else:
+            print ' * to', second, m.repository.gitweb()
+
+        print '{{{'
+        os.system("diff -u %s %s | sed -e 's,%s,[[previous version]],'" % (tmpfile, specfile,tmpfile))
+        print '}}}'
+
+        os.unlink(tmpfile)
+
+    for module in new_modules:
+        print '=== %s : new package in build %s ===' % (tagfile_new, module)
+
+    for module in removed_modules:
+        print '=== %s : removed package from build %s ===' % (tagfile_new, module)
+
+
+def adopt_tag (options, args):
+    modules=[]
+    for module in options.modules:
+        modules += module.split()
+    for module in modules: 
+        modobj=Module(module,options)
+        for tags_file in args:
+            if options.verbose:
+                print 'adopting tag %s for %s in %s'%(options.tag,module,tags_file)
+            modobj.patch_tags_file(tags_file,'_unused_',options.tag,fine_grain=False)
+    if options.verbose:
+        Command("git diff %s"%" ".join(args),options).run()
+
 ##############################
 class Main:
 
 ##############################
 class Main:
 
@@ -1132,11 +1395,11 @@ module-tools : a set of tools to manage subversion tags and specfile
   * define *version* and *taglevel*
   OR alternatively 
   * define redirection variables module_version_varname / module_taglevel_varname
   * define *version* and *taglevel*
   OR alternatively 
   * define redirection variables module_version_varname / module_taglevel_varname
-Trunk:
-  by default, the trunk of modules is taken into account
+Master:
+  by default, the 'master' branch of modules is the target
   in this case, just mention the module name as <module_desc>
 Branches:
   in this case, just mention the module name as <module_desc>
 Branches:
-  if you wish to work on a branch rather than on the trunk
+  if you wish to work on another branch
   you can use something like e.g. Mom:2.1 as <module_desc>
 """
     release_usage="""Usage: %prog [options] tag1 .. tagn
   you can use something like e.g. Mom:2.1 as <module_desc>
 """
     release_usage="""Usage: %prog [options] tag1 .. tagn
@@ -1147,6 +1410,15 @@ Branches:
       release-changelog :4.2 4.2-rc25
   You can refer to the build trunk by just mentioning 'trunk', e.g.
       release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
       release-changelog :4.2 4.2-rc25
   You can refer to the build trunk by just mentioning 'trunk', e.g.
       release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
+  You can use 2 different tagfile names if that was renamed meanwhile
+      release-changelog -t onelab-tags.mk 5.0-rc29 -t onelab-k32-tags.mk 5.0-rc28
+"""
+    adopt_usage="""Usage: %prog [options] tag-file[s]
+  With this command you can adopt a specifi tag or branch in your tag files
+    This should be run in your daily build workdir; no call of git nor svn is done
+  Examples:
+    adopt-tag -m "plewww plcapi" -m Monitor onelab*tags.mk
+    adopt-tag -m sfa -t sfa-1.0-33 *tags.mk
 """
     common_usage="""More help:
   see http://svn.planet-lab.org/wiki/ModuleTools"""
 """
     common_usage="""More help:
   see http://svn.planet-lab.org/wiki/ModuleTools"""
@@ -1156,7 +1428,7 @@ Branches:
         'version' : "check latest specfile and print out details",
         'diff' : "show difference between module (trunk or branch) and latest tag",
         'tag'  : """increment taglevel in specfile, insert changelog in specfile,
         'version' : "check latest specfile and print out details",
         'diff' : "show difference between module (trunk or branch) and latest tag",
         'tag'  : """increment taglevel in specfile, insert changelog in specfile,
-                create new tag and and monitor its adoption in build/*-tags*.mk""",
+                create new tag and and monitor its adoption in build/*-tags.mk""",
         'branch' : """create a branch for this module, from the latest tag on the trunk, 
                   and change trunk's version number to reflect the new branch name;
                   you can specify the new branch name by using module:branch""",
         'branch' : """create a branch for this module, from the latest tag on the trunk, 
                   and change trunk's version number to reflect the new branch name;
                   you can specify the new branch name by using module:branch""",
@@ -1164,10 +1436,13 @@ Branches:
                 this is a last resort option, mostly for repairs""",
         'changelog' : """extract changelog between build tags
                 expected arguments are a list of tags""",
                 this is a last resort option, mostly for repairs""",
         'changelog' : """extract changelog between build tags
                 expected arguments are a list of tags""",
+        'adopt' : """locally adopt a specific tag""",
         }
 
     silent_modes = ['list']
         }
 
     silent_modes = ['list']
-    release_modes = ['changelog']
+    # 'changelog' is for release-changelog
+    # 'adopt' is for 'adopt-tag'
+    regular_modes = set(modes.keys()).difference(set(['changelog','adopt']))
 
     @staticmethod
     def optparse_list (option, opt, value, parser):
 
     @staticmethod
     def optparse_list (option, opt, value, parser):
@@ -1179,7 +1454,8 @@ Branches:
     def run(self):
 
         mode=None
     def run(self):
 
         mode=None
-        for function in Main.modes.keys():
+        # hack - need to check for adopt first as 'adopt-tag' contains tag..
+        for function in [ 'adopt' ] + Main.modes.keys():
             if sys.argv[0].find(function) >= 0:
                 mode = function
                 break
             if sys.argv[0].find(function) >= 0:
                 mode = function
                 break
@@ -1188,33 +1464,58 @@ Branches:
             print "Supported commands:" + " ".join(Main.modes.keys())
             sys.exit(1)
 
             print "Supported commands:" + " ".join(Main.modes.keys())
             sys.exit(1)
 
-        if mode not in Main.release_modes:
+        usage='undefined usage, mode=%s'%mode
+        if mode in Main.regular_modes:
             usage = Main.module_usage
             usage += Main.common_usage
             usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
             usage = Main.module_usage
             usage += Main.common_usage
             usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
-        else:
+        elif mode=='changelog':
             usage = Main.release_usage
             usage += Main.common_usage
             usage = Main.release_usage
             usage += Main.common_usage
+        elif mode=='adopt':
+            usage = Main.adopt_usage
+            usage += Main.common_usage
 
         parser=OptionParser(usage=usage)
         
 
         parser=OptionParser(usage=usage)
         
-        if mode == "tag" or mode == 'branch':
+        # the 'adopt' mode is really special and doesn't share any option
+        if mode == 'adopt':
+            parser.add_option("-m","--module",action="append",dest="modules",default=[],
+                              help="modules, can be used several times or with quotes")
+            parser.add_option("-t","--tag",action="store", dest="tag", default='master',
+                              help="specify the tag to adopt, default is 'master'")
+            parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False, 
+                              help="run in verbose mode")
+            (options, args) = parser.parse_args()
+            options.workdir='unused'
+            options.dry_run=False
+            options.mode='adopt'
+            if len(args)==0 or len(options.modules)==0:
+                parser.print_help()
+                sys.exit(1)
+            adopt_tag (options,args)
+            return 
+
+        # the other commands (module-* and release-changelog) share the same skeleton
+        if mode in [ 'tag', 'branch'] :
             parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
                               help="set new version and reset taglevel to 0")
             parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
                               help="set new version and reset taglevel to 0")
-        if mode == "tag" :
+            parser.add_option("-0","--bypass",action="store_true",dest="bypass",default=False,
+                              help="skip checks on existence of the previous tag")
+        if mode == 'tag' :
             parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
                               help="do not update changelog section in specfile when tagging")
             parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
             parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
                               help="do not update changelog section in specfile when tagging")
             parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
-                              help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
-        if mode == "tag" or mode == "sync" :
+                              help="specify a build branch; used for locating the *tags.mk files where adoption is to take place")
+        if mode in [ 'tag', 'sync' ] :
             parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
                               help="specify editor")
 
             parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
                               help="specify editor")
 
-        if mode in ["diff","version"] :
+        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")
 
             parser.add_option("-W","--www", action="store", dest="www", default=False,
                               help="export diff in html format, e.g. -W trunk")
 
-        if mode == "diff" :
+        if mode == 'diff' :
             parser.add_option("-l","--list", action="store_true", dest="list", default=False,
                               help="just list modules that exhibit differences")
             
             parser.add_option("-l","--list", action="store_true", dest="list", default=False,
                               help="just list modules that exhibit differences")
             
@@ -1236,6 +1537,8 @@ Branches:
 ** THIS MUST NOT ** be your usual working directory""")
         parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
                           help="skip safety checks, such as svn updates -- use with care")
 ** THIS MUST NOT ** be your usual working directory""")
         parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
                           help="skip safety checks, such as svn updates -- use with care")
+        parser.add_option("-B","--build-module",action="store",dest="build_module",default=None,
+                          help="specify a build module to owerride the one in the CONFIG")
 
         # default verbosity depending on function - temp
         verbose_modes= ['tag', 'sync', 'branch']
 
         # default verbosity depending on function - temp
         verbose_modes= ['tag', 'sync', 'branch']
@@ -1254,6 +1557,8 @@ Branches:
             options.www=False
         options.debug=False
 
             options.www=False
         options.debug=False
 
+        
+
         ########## module-*
         if len(args) == 0:
             if options.all_modules:
         ########## module-*
         if len(args) == 0:
             if options.all_modules:
@@ -1266,41 +1571,50 @@ Branches:
         Module.init_homedir(options)
         
 
         Module.init_homedir(options)
         
 
-        modules=[ Module(modname,options) for modname in args ]
-        # hack: create a dummy Module to store errors/warnings
-        error_module = Module('__errors__',options)
+        if mode in Main.regular_modes:
+            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:
-                print '========================================',module.friendly_name()
-            # call the method called do_<mode>
-            method=Module.__dict__["do_%s"%mode]
-            try:
-                method(module)
-            except Exception,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:
             for module in modules:
-                module.html_dump_body()
-            Module.html_dump_footer()
-
+                if len(args)>1 and mode not in Main.silent_modes:
+                    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:
+                    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()
+        else:
+            # if we provide, say a b c d, we want to build (a,b) (b,c) and (c,d)
+            # remember that the changelog in the twiki comes latest first, so
+            # we typically have here latest latest-1 latest-2
+            for (tag_new,tag_old) in zip ( args[:-1], args [1:]):
+                release_changelog(options, tag_old, tag_new)
+            
+    
 ####################
 if __name__ == "__main__" :
     try:
 ####################
 if __name__ == "__main__" :
     try: