9 from optparse import OptionParser
11 # e.g. other_choices = [ ('d','iff') , ('g','uess') ] - lowercase
12 def prompt (question,default=True,other_choices=[],allow_outside=False):
13 if not isinstance (other_choices,list):
14 other_choices = [ other_choices ]
15 chars = [ c for (c,rest) in other_choices ]
19 if default is True: choices.append('[y]')
20 else : choices.append('y')
22 if default is False: choices.append('[n]')
23 else : choices.append('n')
25 for (char,choice) in other_choices:
27 choices.append("["+char+"]"+choice)
29 choices.append("<"+char+">"+choice)
31 answer=raw_input(question + " " + "/".join(choices) + " ? ")
34 answer=answer[0].lower()
36 if 'y' in chars: return 'y'
39 if 'n' in chars: return 'n'
42 for (char,choice) in other_choices:
47 return prompt(question,default,other_choices)
53 editor = os.environ['EDITOR']
61 def print_fold (line):
62 while len(line) >= fold_length:
63 print line[:fold_length],'\\'
64 line=line[fold_length:]
68 def __init__ (self,command,options):
71 self.tmp="/tmp/command-%d"%os.getpid()
74 if self.options.dry_run:
75 print 'dry_run',self.command
77 if self.options.verbose and self.options.mode not in Main.silent_modes:
78 print '+',self.command
80 return os.system(self.command)
82 def run_silent (self):
83 if self.options.dry_run:
84 print 'dry_run',self.command
86 if self.options.verbose:
87 print '+',self.command,' .. ',
89 retcod=os.system(self.command + " &> " + self.tmp)
91 print "FAILED ! -- out+err below (command was %s)"%self.command
92 os.system("cat " + self.tmp)
93 print "FAILED ! -- end of quoted output"
94 elif self.options.verbose:
100 if self.run_silent() !=0:
101 raise Exception,"Command %s failed"%self.command
103 # returns stdout, like bash's $(mycommand)
104 def output_of (self,with_stderr=False):
105 if self.options.dry_run:
106 print 'dry_run',self.command
107 return 'dry_run output'
108 tmp="/tmp/status-%d"%os.getpid()
109 if self.options.debug:
110 print '+',self.command,' .. ',
119 result=file(tmp).read()
121 if self.options.debug:
129 def __init__(self, path, options):
131 self.options = options
134 def checkout(cls, module, config, options, recursive=False):
135 remote = "%s/%s" % (config['svnpath'], module)
136 local = os.path.join(options.workdir, module)
139 svncommand = "svn co %s %s" % (remote, local)
141 svncommand = "svn co -N %s %s" % (remote, local)
142 Command("rm -rf %s" % local, options).run_silent()
143 Command(svncommand, options).run_fatal()
145 return SvnRepository(local, options)
148 def remote_exists(cls, remote):
149 return os.system("svn list %s &> /dev/null" % remote) == 0
151 def update(self, subdir="", recursive=True):
152 path = os.path.join(self.path, subdir)
154 svncommand = "svn up %s" % path
156 svncommand = "svn up -N %s" % path
157 Command(svncommand, self.options).run_fatal()
159 def commit(self, logfile):
160 # add all new files to the repository
161 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
162 self.path, self.options).run_silent()
163 Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
165 def tag(self, from_url, to_url, logfile):
166 # TODO: get svn url from svn info output
167 # and only require tagname and logfile parameters
168 Command("svn copy -F %s %s %s" % (logfile, from_url, to_url), self.options).run_fatal()
171 return Command("svn diff %s" % self.path, self.options).output_of(True)
174 Command("svn revert %s -R" % self.path, self.options).run_fatal()
177 command="svn status %s" % self.path
178 return len(Command(command,self.options).output_of(True)) == 0
181 return os.path.exists(os.path.join(self.path, ".svn"))
188 def __init__(self, path, options):
190 self.options = options
193 def checkout(cls, module, config, options, depth=1):
194 remote = "%s:/git/%s.git" % (config['gitserver'], module)
195 local = os.path.join(options.workdir, module)
197 Command("rm -rf %s" % local, options).run_silent()
198 Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
200 return GitRepository(local, options)
203 def remote_exists(cls, remote):
204 return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
206 def __run_in_repo(self, fun, *args, **kwargs):
209 ret = fun(*args, **kwargs)
213 def __run_command_in_repo(self, command):
214 c = Command(command, self.options)
215 return self.__run_in_repo(c.run_fatal)
217 def update(self, subdir=None, recursive=None):
218 return self.__run_command_in_repo("git pull")
220 def to_branch(self, branch, remote=True):
222 branch = "origin/%s" % branch
223 self.__run_command_in_repo("git checkout %s" % branch)
226 c = Command("git diff", self.options)
227 return self.__run_in_repo(c.output_of, with_stderr=True)
229 def commit(self, logfile):
230 self.__run_command_in_repo("git add -A")
231 self.__run_command_in_repo("git commit -F %s" % logfile)
232 self.__run_command_in_repo("git push")
235 self.__run_command_in_repo("git --no-pager reset --hard")
236 self.__run_command_in_repo("git --no-pager clean -f")
241 s="nothing to commit (working directory clean)"
242 return Command(command, self.options).output_of(True).find(s) >= 0
243 return self.__run_in_repo(check_commit)
246 return os.path.exists(os.path.join(self.path, ".git"))
250 """ Generic repository """
251 supported_repo_types = [SvnRepository, GitRepository]
253 def __init__(self, path, options):
255 self.options = options
256 for repo in self.supported_repo_types:
257 self.repo = repo(self.path, self.options)
258 if self.repo.is_valid():
262 def has_moved_to_git(cls, module, config):
263 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (config['svnpath'], module))
266 def remote_exists(cls, remote):
267 for repo in Repository.supported_repo_types:
268 if repo.remote_exists(remote):
272 def __getattr__(self, attr):
273 return getattr(self.repo, attr)
277 # support for tagged module is minimal, and is for the Build class only
280 svn_magic_line="--This line, and those below, will be ignored--"
281 setting_tag_format = "Setting tag %s"
283 redirectors=[ # ('module_name_varname','name'),
284 ('module_version_varname','version'),
285 ('module_taglevel_varname','taglevel'), ]
287 # where to store user's config
288 config_storage="CONFIG"
293 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
294 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
295 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
296 ("build", "Enter the name of your build module","build"),
297 ('username',"Enter your firstname and lastname for changelogs",""),
298 ("email","Enter your email address for changelogs",""),
302 def prompt_config ():
303 for (key,message,default) in Module.configKeys:
304 Module.config[key]=""
305 while not Module.config[key]:
306 Module.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
309 # for parsing module spec name:branch
310 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
311 # special form for tagged module - for Build
312 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
314 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
316 def __init__ (self,module_spec,options):
318 attempt=Module.matcher_branch_spec.match(module_spec)
320 self.name=attempt.group('name')
321 self.branch=attempt.group('branch')
323 attempt=Module.matcher_tag_spec.match(module_spec)
325 self.name=attempt.group('name')
326 self.tagname=attempt.group('tagname')
328 self.name=module_spec
331 self.module_dir="%s/%s"%(options.workdir,self.name)
332 self.repository = None
335 def friendly_name (self):
336 if hasattr(self,'branch'):
337 return "%s:%s"%(self.name,self.branch)
338 elif hasattr(self,'tagname'):
339 return "%s@%s"%(self.name,self.tagname)
344 if hasattr(self,'branch'):
345 return "%s/branches/%s"%(self.module_dir,self.branch)
346 elif hasattr(self,'tagname'):
347 return "%s/tags/%s"%(self.module_dir,self.tagname)
349 return "%s/trunk"%(self.module_dir)
352 return "%s/tags"%(self.module_dir)
354 def run (self,command):
355 return Command(command,self.options).run()
356 def run_fatal (self,command):
357 return Command(command,self.options).run_fatal()
358 def run_prompt (self,message,fun, *args):
359 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
360 if not self.options.verbose:
362 choice=prompt(message,True,('s','how'))
366 elif choice is False:
367 print 'About to run function:', fun_msg
369 question=message+" - want to run function: " + fun_msg
370 if prompt(question,True):
376 def init_homedir (options):
377 topdir=options.workdir
378 if options.verbose and options.mode not in Main.silent_modes:
379 print 'Checking for',topdir
380 storage="%s/%s"%(topdir,Module.config_storage)
381 # sanity check. Either the topdir exists AND we have a config/storage
382 # or topdir does not exist and we create it
383 # to avoid people use their own daily svn repo
384 if os.path.isdir(topdir) and not os.path.isfile(storage):
385 print """The directory %s exists and has no CONFIG file
386 If this is your regular working directory, please provide another one as the
387 module-* commands need a fresh working dir. Make sure that you do not use
388 that for other purposes than tagging"""%topdir
390 if not os.path.isdir (topdir):
391 print "Cannot find",topdir,"let's create it"
392 Module.prompt_config()
393 print "Checking ...",
394 GitRepository.checkout(Module.config['build'], Module.config,
400 for (key,message,default) in Module.configKeys:
401 f.write("%s=%s\n"%(key,Module.config[key]))
404 print 'Stored',storage
405 Command("cat %s"%storage,options).run()
409 for line in f.readlines():
410 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
411 Module.config[key]=value
413 if options.verbose and options.mode not in Main.silent_modes:
414 print '******** Using config'
415 for (key,message,default) in Module.configKeys:
416 print '\t',key,'=',Module.config[key]
418 def init_module_dir (self):
419 if self.options.verbose:
420 print 'Checking for',self.module_dir
422 if not os.path.isdir (self.module_dir):
423 if Repository.has_moved_to_git(self.name, Module.config):
424 self.repository = GitRepository.checkout(self.name, Module.config,
425 self.options, depth=1)
427 self.repository = SvnRepository.checkout(self.name, Module.config,
428 self.options, recursive=False)
431 self.repository = Repository(self.module_dir, self.options)
433 raise Exception, 'Cannot find %s - check module name'%self.module_dir
435 def init_subdir (self,subdir, recursive=True):
436 path = os.path.join(self.repository.path, subdir)
437 if self.options.verbose:
438 print 'Checking for', path
439 if not os.path.isdir(path):
440 self.repository.update(recursive=recursive, subdir=subdir)
442 def revert_subdir (self,fullpath):
443 if self.options.fast_checks:
444 if self.options.verbose: print 'Skipping revert of %s'%fullpath
446 if self.options.verbose:
447 print 'Checking whether',fullpath,'needs being reverted'
449 repo=Repository(fullpath, self.options)
450 if not repo.is_clean():
453 def update_subdir (self,fullpath):
454 if self.options.fast_checks:
455 if self.options.verbose: print 'Skipping update of %s'%fullpath
457 if self.options.verbose:
458 print 'Updating',fullpath
459 Repository(fullpath, self.options).update()
461 def init_edge_dir (self):
462 # if branch, edge_dir is two steps down
463 if hasattr(self,'branch'):
464 self.init_subdir("%s/branches"%self.module_dir,recursive=False)
465 elif hasattr(self,'tagname'):
466 self.init_subdir("%s/tags"%self.module_dir,recursive=False)
467 self.init_subdir(self.edge_dir(),recursive=True)
469 def revert_edge_dir (self):
470 self.revert_subdir(self.edge_dir())
472 def update_edge_dir (self):
473 self.update_subdir(self.edge_dir())
475 def main_specname (self):
476 attempt="%s/%s.spec"%(self.edge_dir(),self.name)
477 if os.path.isfile (attempt):
479 pattern1="%s/*.spec"%self.edge_dir()
480 level1=glob(pattern1)
483 pattern2="%s/*/*.spec"%self.edge_dir()
484 level2=glob(pattern2)
487 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
489 def all_specnames (self):
490 level1=glob("%s/*.spec"%self.edge_dir())
491 if level1: return level1
492 level2=glob("%s/*/*.spec"%self.edge_dir())
495 def parse_spec (self, specfile, varnames):
496 if self.options.verbose:
497 print 'Parsing',specfile,
503 for line in f.readlines():
504 attempt=Module.matcher_rpm_define.match(line)
506 (define,var,value)=attempt.groups()
510 if self.options.debug:
511 print 'found',len(result),'keys'
512 for (k,v) in result.iteritems():
516 # stores in self.module_name_varname the rpm variable to be used for the module's name
517 # and the list of these names in self.varnames
518 def spec_dict (self):
519 specfile=self.main_specname()
520 redirector_keys = [ varname for (varname,default) in Module.redirectors]
521 redirect_dict = self.parse_spec(specfile,redirector_keys)
522 if self.options.debug:
523 print '1st pass parsing done, redirect_dict=',redirect_dict
525 for (varname,default) in Module.redirectors:
526 if redirect_dict.has_key(varname):
527 setattr(self,varname,redirect_dict[varname])
528 varnames += [redirect_dict[varname]]
530 setattr(self,varname,default)
531 varnames += [ default ]
532 self.varnames = varnames
533 result = self.parse_spec (specfile,self.varnames)
534 if self.options.debug:
535 print '2st pass parsing done, varnames=',varnames,'result=',result
538 def patch_spec_var (self, patch_dict,define_missing=False):
539 for specfile in self.all_specnames():
540 # record the keys that were changed
541 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
542 newspecfile=specfile+".new"
543 if self.options.verbose:
544 print 'Patching',specfile,'for',patch_dict.keys()
546 new=open(newspecfile,"w")
548 for line in spec.readlines():
549 attempt=Module.matcher_rpm_define.match(line)
551 (define,var,value)=attempt.groups()
552 if var in patch_dict.keys():
553 if self.options.debug:
554 print 'rewriting %s as %s'%(var,patch_dict[var])
555 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
560 for (key,was_changed) in changed.iteritems():
562 if self.options.debug:
563 print 'rewriting missing %s as %s'%(key,patch_dict[key])
564 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
567 os.rename(newspecfile,specfile)
569 # returns all lines until the magic line
570 def unignored_lines (self, logfile):
572 white_line_matcher = re.compile("\A\s*\Z")
573 for logline in file(logfile).readlines():
574 if logline.strip() == Module.svn_magic_line:
576 elif white_line_matcher.match(logline):
579 result.append(logline.strip()+'\n')
582 # creates a copy of the input with only the unignored lines
583 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
585 f.write(self.setting_tag_format%new_tag_name + '\n')
586 for line in self.unignored_lines(filein):
590 def insert_changelog (self, logfile, oldtag, newtag):
591 for specfile in self.all_specnames():
592 newspecfile=specfile+".new"
593 if self.options.verbose:
594 print 'Inserting changelog from %s into %s'%(logfile,specfile)
596 new=open(newspecfile,"w")
597 for line in spec.readlines():
599 if re.compile('%changelog').match(line):
600 dateformat="* %a %b %d %Y"
601 datepart=time.strftime(dateformat)
602 logpart="%s <%s> - %s"%(Module.config['username'],
603 Module.config['email'],
605 new.write(datepart+" "+logpart+"\n")
606 for logline in self.unignored_lines(logfile):
607 new.write("- " + logline)
611 os.rename(newspecfile,specfile)
613 def show_dict (self, spec_dict):
614 if self.options.verbose:
615 for (k,v) in spec_dict.iteritems():
618 def mod_svn_url (self):
619 return "%s/%s"%(Module.config['svnpath'],self.name)
622 if hasattr(self,'branch'):
623 return "%s/branches/%s"%(self.mod_svn_url(),self.branch)
624 elif hasattr(self,'tagname'):
625 return "%s/tags/%s"%(self.mod_svn_url(),self.tagname)
627 return "%s/trunk"%(self.mod_svn_url())
629 def last_tag (self, spec_dict):
630 return "%s-%s"%(spec_dict[self.module_version_varname],spec_dict[self.module_taglevel_varname])
632 def tag_name (self, spec_dict):
634 return "%s-%s"%(self.name,
635 self.last_tag(spec_dict))
637 raise Exception, 'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
639 def tag_url (self, spec_dict):
640 return "%s/tags/%s"%(self.mod_svn_url(),self.tag_name(spec_dict))
642 def check_svnpath_exists (self, url, message):
643 if self.options.fast_checks:
645 if self.options.verbose:
646 print 'Checking url (%s) %s'%(url,message),
648 if SvnRepository.remote_exists(url):
649 if self.options.verbose: print 'exists - OK'
651 if self.options.verbose: print 'KO'
652 raise Exception, 'Could not find %s URL %s'%(message,url)
654 def check_svnpath_not_exists (self, url, message):
655 if self.options.fast_checks:
657 if self.options.verbose:
658 print 'Checking url (%s) %s'%(url,message),
660 if not SvnRepository.remote_exists(url):
661 if self.options.verbose: print 'does not exist - OK'
663 if self.options.verbose: print 'KO'
664 raise Exception, '%s URL %s already exists - exiting'%(message,url)
666 # locate specfile, parse it, check it and show values
669 ##############################
670 # using fine_grain means replacing only those instances that currently refer to this tag
671 # otherwise, <module>-SVNPATH is replaced unconditionnally
672 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
673 newtagsfile=tagsfile+".new"
675 new=open(newtagsfile,"w")
678 # fine-grain : replace those lines that refer to oldname
680 if self.options.verbose:
681 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
682 matcher=re.compile("^(.*)%s(.*)"%oldname)
683 for line in tags.readlines():
684 if not matcher.match(line):
687 (begin,end)=matcher.match(line).groups()
688 new.write(begin+newname+end+"\n")
690 # brute-force : change uncommented lines that define <module>-SVNPATH
692 if self.options.verbose:
693 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
694 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
696 matcher_module=re.compile(pattern)
697 for line in tags.readlines():
698 attempt=matcher_module.match(line)
700 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
701 if self.options.verbose:
703 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
704 new.write(replacement)
710 os.rename(newtagsfile,tagsfile)
711 if self.options.verbose: print "%d changes"%matches
715 self.init_module_dir()
717 self.revert_edge_dir()
718 self.update_edge_dir()
720 spec_dict = self.spec_dict()
721 self.show_dict(spec_dict)
724 edge_url=self.edge_url()
725 old_tag_name = self.tag_name(spec_dict)
726 old_tag_url=self.tag_url(spec_dict)
727 if (self.options.new_version):
728 # new version set on command line
729 spec_dict[self.module_version_varname] = self.options.new_version
730 spec_dict[self.module_taglevel_varname] = 0
733 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
734 spec_dict[self.module_taglevel_varname] = new_taglevel
737 new_tag_name = self.tag_name(spec_dict)
738 new_tag_url=self.tag_url(spec_dict)
739 self.check_svnpath_exists (edge_url,"edge track")
740 self.check_svnpath_exists (old_tag_url,"previous tag")
741 self.check_svnpath_not_exists (new_tag_url,"new tag")
744 diff_output=Command("svn diff %s %s"%(old_tag_url,edge_url),
745 self.options).output_of()
746 if len(diff_output) == 0:
747 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
750 # side effect in trunk's specfile
751 self.patch_spec_var(spec_dict)
753 # prepare changelog file
754 # we use the standard subversion magic string (see svn_magic_line)
755 # so we can provide useful information, such as version numbers and diff
757 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
758 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
759 setting_tag_line=Module.setting_tag_format%new_tag_name
760 file(changelog,"w").write("""
763 Please write a changelog for this new tag in the section above
764 """%(Module.svn_magic_line,setting_tag_line))
766 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
767 file(changelog,"a").write('DIFF=========\n' + diff_output)
769 if self.options.debug:
773 self.run("%s %s"%(self.options.editor,changelog))
774 # strip magic line in second file - looks like svn has changed its magic line with 1.6
775 # so we do the job ourselves
776 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
777 # insert changelog in spec
778 if self.options.changelog:
779 self.insert_changelog (changelog,old_tag_name,new_tag_name)
782 build_path = os.path.join(self.options.workdir,
783 Module.config['build'])
784 build = Repository(build_path, self.options)
785 if self.options.build_branch:
786 build.to_branch(self.options.build_branch)
788 tagsfiles=glob(build.path+"/*-tags*.mk")
789 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
793 for tagsfile in tagsfiles:
794 status=tagsdict[tagsfile]
795 basename=os.path.basename(tagsfile)
796 print ".................... Dealing with %s"%basename
797 while tagsdict[tagsfile] == 'todo' :
798 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
799 [ ('y','es'), ('n', 'ext'), ('f','orce'),
800 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
803 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
805 print 'Done with %s'%os.path.basename(tagsfile)
806 tagsdict[tagsfile]='done'
808 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
810 self.run("svn diff %s"%tagsfile)
812 self.run("svn revert %s"%tagsfile)
814 self.run("cat %s"%tagsfile)
817 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
818 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
819 d: show current diff for this tag file
820 r: revert that tag file
821 c: cat the current tag file
822 n: move to next file"""%locals()
824 if prompt("Want to review changes on tags files",False):
825 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
830 def diff_all_changes():
832 print self.repository.diff()
834 def commit_all_changes(log):
835 self.repository.commit(log)
838 self.run_prompt("Review module and build", diff_all_changes)
839 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
840 # TODO: implement repository.tag() for git
841 self.run_prompt("Create tag", self.repository.tag, edge_url, new_tag_url, changelog_svn)
843 if self.options.debug:
844 print 'Preserving',changelog,'and stripped',changelog_svn
847 os.unlink(changelog_svn)
850 ##############################
853 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
855 module-tools : a set of tools to manage subversion tags and specfile
856 requires the specfile to either
857 * define *version* and *taglevel*
859 * define redirection variables module_version_varname / module_taglevel_varname
861 by default, the trunk of modules is taken into account
862 in this case, just mention the module name as <module_desc>
864 if you wish to work on a branch rather than on the trunk,
865 you can use something like e.g. Mom:2.1 as <module_desc>
867 release_usage="""Usage: %prog [options] tag1 .. tagn
868 Extract release notes from the changes in specfiles between several build tags, latest first
870 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
871 You can refer to a (build) branch by prepending a colon, like in
872 release-changelog :4.2 4.2-rc25
873 You can refer to the build trunk by just mentioning 'trunk', e.g.
874 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
876 common_usage="""More help:
877 see http://svn.planet-lab.org/wiki/ModuleTools"""
880 'list' : "displays a list of available tags or branches",
881 'version' : "check latest specfile and print out details",
882 'diff' : "show difference between module (trunk or branch) and latest tag",
883 'tag' : """increment taglevel in specfile, insert changelog in specfile,
884 create new tag and and monitor its adoption in build/*-tags*.mk""",
885 'branch' : """create a branch for this module, from the latest tag on the trunk,
886 and change trunk's version number to reflect the new branch name;
887 you can specify the new branch name by using module:branch""",
888 'sync' : """create a tag from the module
889 this is a last resort option, mostly for repairs""",
890 'changelog' : """extract changelog between build tags
891 expected arguments are a list of tags""",
894 silent_modes = ['list']
895 release_modes = ['changelog']
898 def optparse_list (option, opt, value, parser):
900 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
902 setattr(parser.values,option.dest,value.split())
907 for function in Main.modes.keys():
908 if sys.argv[0].find(function) >= 0:
912 print "Unsupported command",sys.argv[0]
913 print "Supported commands:" + " ".join(Main.modes.keys())
916 if mode not in Main.release_modes:
917 usage = Main.module_usage
918 usage += Main.common_usage
919 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
921 usage = Main.release_usage
922 usage += Main.common_usage
924 parser=OptionParser(usage=usage,version=subversion_id)
926 if mode == "tag" or mode == 'branch':
927 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
928 help="set new version and reset taglevel to 0")
930 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
931 help="do not update changelog section in specfile when tagging")
932 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
933 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
934 if mode == "tag" or mode == "sync" :
935 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
936 help="specify editor")
938 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
939 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
940 help="dry run - shell commands are only displayed")
941 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
942 default=[], nargs=1,type="string",
943 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
944 -- can be set multiple times, or use quotes""")
946 parser.add_option("-w","--workdir", action="store", dest="workdir",
947 default="%s/%s"%(os.getenv("HOME"),"modules"),
948 help="""name for dedicated working dir - defaults to ~/modules
949 ** THIS MUST NOT ** be your usual working directory""")
950 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
951 help="skip safety checks, such as svn updates -- use with care")
953 # default verbosity depending on function - temp
954 verbose_modes= ['tag', 'sync', 'branch']
956 if mode not in verbose_modes:
957 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
958 help="run in verbose mode")
960 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
961 help="run in quiet (non-verbose) mode")
962 (options, args) = parser.parse_args()
964 if not hasattr(options,'dry_run'):
965 options.dry_run=False
966 if not hasattr(options,'www'):
974 Module.init_homedir(options)
976 modules=[ Module(modname,options) for modname in args ]
977 for module in modules:
978 if len(args)>1 and mode not in Main.silent_modes:
979 print '========================================',module.friendly_name()
980 # call the method called do_<mode>
981 method=Module.__dict__["do_%s"%mode]
986 traceback.print_exc()
987 print 'Skipping module %s: '%modname,e
990 if __name__ == "__main__" :
993 except KeyboardInterrupt: