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 return os.path.basename(self.path)
137 out = Command("svn info %s" % self.path, self.options).output_of()
138 for line in out.split('\n'):
139 if line.startswith("URL:"):
140 return line.split()[1].strip()
143 out = Command("svn info %s" % self.path, self.options).output_of()
144 for line in out.split('\n'):
145 if line.startswith("Repository Root:"):
146 repo_root = line.split()[2].strip()
147 return "%s/svn/%s" (repo_root, self.name)
150 def checkout(cls, remote, local, options, recursive=False):
152 svncommand = "svn co %s %s" % (remote, local)
154 svncommand = "svn co -N %s %s" % (remote, local)
155 Command("rm -rf %s" % local, options).run_silent()
156 Command(svncommand, options).run_fatal()
158 return SvnRepository(local, options)
161 def remote_exists(cls, remote):
162 return os.system("svn list %s &> /dev/null" % remote) == 0
164 def update(self, subdir="", recursive=True):
165 path = os.path.join(self.path, subdir)
167 svncommand = "svn up %s" % path
169 svncommand = "svn up -N %s" % path
170 Command(svncommand, self.options).run_fatal()
172 def commit(self, logfile):
173 # add all new files to the repository
174 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
175 self.path, self.options).run_silent()
176 Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
178 def to_branch(self, branch):
179 remote = "%s/branches/%s" % (self.repo_root(), branch)
180 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
182 def to_tag(self, tag):
183 remote = "%s/tags/%s" % (self.repo_root(), branch)
184 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
186 def tag(self, from_url, to_url, logfile):
187 # TODO: get svn url from svn info output
188 # and only require tagname and logfile parameters
189 Command("svn copy -F %s %s %s" % (logfile, from_url, to_url), self.options).run_fatal()
192 return Command("svn diff %s" % self.path, self.options).output_of(True)
195 Command("svn revert %s -R" % self.path, self.options).run_fatal()
196 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
197 self.path, self.options).run_silent()
200 command="svn status %s" % self.path
201 return len(Command(command,self.options).output_of(True)) == 0
204 return os.path.exists(os.path.join(self.path, ".svn"))
210 def __init__(self, path, options):
212 self.options = options
215 return os.path.basename(self.path)
221 c = Command("git remote show origin", self.options)
222 out = self.__run_in_repo(c.output_of)
223 for line in out.split('\n'):
224 if line.strip().startswith("Fetch URL:"):
225 repo = line.split()[2]
228 def checkout(cls, remote, local, options, depth=1):
229 Command("rm -rf %s" % local, options).run_silent()
230 Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
231 return GitRepository(local, options)
234 def remote_exists(cls, remote):
235 return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
237 def __run_in_repo(self, fun, *args, **kwargs):
240 ret = fun(*args, **kwargs)
244 def __run_command_in_repo(self, command):
245 c = Command(command, self.options)
246 return self.__run_in_repo(c.run_fatal)
248 def update(self, subdir=None, recursive=None):
249 return self.__run_command_in_repo("git pull")
251 def to_branch(self, branch, remote=True):
253 branch = "origin/%s" % branch
254 return self.__run_command_in_repo("git checkout %s" % branch)
256 def to_tag(self, tag):
257 return self.__run_command_in_repo("git checkout %s" % tag)
259 def tag(self, tagname, logfile):
260 return self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
263 c = Command("git diff", self.options)
264 return self.__run_in_repo(c.output_of, with_stderr=True)
266 def commit(self, logfile):
267 self.__run_command_in_repo("git add -A")
268 self.__run_command_in_repo("git commit -F %s" % logfile)
269 self.__run_command_in_repo("git push --tags")
272 self.__run_command_in_repo("git --no-pager reset --hard")
273 self.__run_command_in_repo("git --no-pager clean -f")
278 s="nothing to commit (working directory clean)"
279 return Command(command, self.options).output_of(True).find(s) >= 0
280 return self.__run_in_repo(check_commit)
283 return os.path.exists(os.path.join(self.path, ".git"))
287 """ Generic repository """
288 supported_repo_types = [SvnRepository, GitRepository]
290 def __init__(self, path, options):
292 self.options = options
293 for repo in self.supported_repo_types:
294 self.repo = repo(self.path, self.options)
295 if self.repo.is_valid():
299 def has_moved_to_git(cls, module, svnpath):
300 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
303 def remote_exists(cls, remote):
304 for repo in Repository.supported_repo_types:
305 if repo.remote_exists(remote):
309 def __getattr__(self, attr):
310 return getattr(self.repo, attr)
314 # support for tagged module is minimal, and is for the Build class only
317 svn_magic_line="--This line, and those below, will be ignored--"
318 setting_tag_format = "Setting tag %s"
320 redirectors=[ # ('module_name_varname','name'),
321 ('module_version_varname','version'),
322 ('module_taglevel_varname','taglevel'), ]
324 # where to store user's config
325 config_storage="CONFIG"
330 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
331 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
332 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
333 ("build", "Enter the name of your build module","build"),
334 ('username',"Enter your firstname and lastname for changelogs",""),
335 ("email","Enter your email address for changelogs",""),
339 def prompt_config (cls):
340 for (key,message,default) in cls.configKeys:
342 while not cls.config[key]:
343 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
346 # for parsing module spec name:branch
347 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
348 # special form for tagged module - for Build
349 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
351 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
353 def __init__ (self,module_spec,options):
355 attempt=Module.matcher_branch_spec.match(module_spec)
357 self.name=attempt.group('name')
358 self.branch=attempt.group('branch')
360 attempt=Module.matcher_tag_spec.match(module_spec)
362 self.name=attempt.group('name')
363 self.tagname=attempt.group('tagname')
365 self.name=module_spec
368 self.module_dir="%s/%s"%(options.workdir,self.name)
369 self.repository = None
372 def run (self,command):
373 return Command(command,self.options).run()
374 def run_fatal (self,command):
375 return Command(command,self.options).run_fatal()
376 def run_prompt (self,message,fun, *args):
377 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
378 if not self.options.verbose:
380 choice=prompt(message,True,('s','how'))
384 elif choice is False:
385 print 'About to run function:', fun_msg
387 question=message+" - want to run function: " + fun_msg
388 if prompt(question,True):
391 def friendly_name (self):
392 if hasattr(self,'branch'):
393 return "%s:%s"%(self.name,self.branch)
394 elif hasattr(self,'tagname'):
395 return "%s@%s"%(self.name,self.tagname)
400 def git_remote_dir (cls, name):
401 return "%s:/git/%s.git" % (cls.config['gitserver'], name)
404 def svn_remote_dir (cls, name):
405 svn = cls.config['svnpath']
406 if svn.endswith('/'):
407 return "%s%s" % (svn, name)
408 return "%s/%s" % (svn, name)
412 def init_homedir (cls, options):
413 if options.verbose and options.mode not in Main.silent_modes:
414 print 'Checking for', options.workdir
415 storage="%s/%s"%(options.workdir, cls.config_storage)
416 # sanity check. Either the topdir exists AND we have a config/storage
417 # or topdir does not exist and we create it
418 # to avoid people use their own daily svn repo
419 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
420 print """The directory %s exists and has no CONFIG file
421 If this is your regular working directory, please provide another one as the
422 module-* commands need a fresh working dir. Make sure that you do not use
423 that for other purposes than tagging""" % options.workdir
425 if not os.path.isdir (options.workdir):
426 print "Cannot find",options.workdir,"let's create it"
428 print "Checking ...",
429 remote = cls.git_remote_dir(cls.config['build'])
430 local = os.path.join(options.workdir, cls.config['build'])
431 GitRepository.checkout(remote, local, options, depth=1)
436 for (key,message,default) in Module.configKeys:
437 f.write("%s=%s\n"%(key,Module.config[key]))
440 print 'Stored',storage
441 Command("cat %s"%storage,options).run()
445 for line in f.readlines():
446 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
447 Module.config[key]=value
449 if options.verbose and options.mode not in Main.silent_modes:
450 print '******** Using config'
451 for (key,message,default) in Module.configKeys:
452 print '\t',key,'=',Module.config[key]
454 def svn_selected_remote(self):
455 remote = self.svn_remote_dir(self.name)
456 if hasattr(self,'branch'):
457 remote = "%s/branches/%s" % (remote, self.branch)
458 elif hasattr(self,'tagname'):
459 remote = "%s/tags/%s" % (remote, self.tagname)
461 remote = "%s/trunk" % remote
464 def init_module_dir (self):
465 if self.options.verbose:
466 print 'Checking for',self.module_dir
468 if not os.path.isdir (self.module_dir):
469 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
470 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
474 remote = self.svn_selected_remote()
475 self.repository = SvnRepository.checkout(remote,
477 self.options, recursive=False)
479 self.repository = Repository(self.module_dir, self.options)
480 if self.repository.type == "svn":
481 # check if module has moved to git
482 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
483 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
484 self.init_module_dir()
485 # check if we have the required branch/tag
486 if self.repository.url() != self.svn_selected_remote():
487 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
488 self.init_module_dir()
490 elif self.repository.type == "git":
491 if hasattr(self,'branch'):
492 self.repository.to_branch(self.branch)
493 elif hasattr(self,'tagname'):
494 self.repository.to_tag(self.tagname)
497 raise Exception, 'Cannot find %s - check module name'%self.module_dir
500 def revert_module_dir (self):
501 if self.options.fast_checks:
502 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
504 if self.options.verbose:
505 print 'Checking whether', self.module_dir, 'needs being reverted'
507 if not self.repository.is_clean():
508 self.repository.revert()
510 def update_module_dir (self):
511 if self.options.fast_checks:
512 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
514 if self.options.verbose:
515 print 'Updating', self.module_dir
516 self.repository.update()
518 def main_specname (self):
519 attempt="%s/%s.spec"%(self.module_dir,self.name)
520 if os.path.isfile (attempt):
522 pattern1="%s/*.spec"%self.module_dir
523 level1=glob(pattern1)
526 pattern2="%s/*/*.spec"%self.module_dir
527 level2=glob(pattern2)
531 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
533 def all_specnames (self):
534 level1=glob("%s/*.spec" % self.module_dir)
535 if level1: return level1
536 level2=glob("%s/*/*.spec" % self.module_dir)
539 def parse_spec (self, specfile, varnames):
540 if self.options.verbose:
541 print 'Parsing',specfile,
547 for line in f.readlines():
548 attempt=Module.matcher_rpm_define.match(line)
550 (define,var,value)=attempt.groups()
554 if self.options.debug:
555 print 'found',len(result),'keys'
556 for (k,v) in result.iteritems():
560 # stores in self.module_name_varname the rpm variable to be used for the module's name
561 # and the list of these names in self.varnames
562 def spec_dict (self):
563 specfile=self.main_specname()
564 redirector_keys = [ varname for (varname,default) in Module.redirectors]
565 redirect_dict = self.parse_spec(specfile,redirector_keys)
566 if self.options.debug:
567 print '1st pass parsing done, redirect_dict=',redirect_dict
569 for (varname,default) in Module.redirectors:
570 if redirect_dict.has_key(varname):
571 setattr(self,varname,redirect_dict[varname])
572 varnames += [redirect_dict[varname]]
574 setattr(self,varname,default)
575 varnames += [ default ]
576 self.varnames = varnames
577 result = self.parse_spec (specfile,self.varnames)
578 if self.options.debug:
579 print '2st pass parsing done, varnames=',varnames,'result=',result
582 def patch_spec_var (self, patch_dict,define_missing=False):
583 for specfile in self.all_specnames():
584 # record the keys that were changed
585 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
586 newspecfile=specfile+".new"
587 if self.options.verbose:
588 print 'Patching',specfile,'for',patch_dict.keys()
590 new=open(newspecfile,"w")
592 for line in spec.readlines():
593 attempt=Module.matcher_rpm_define.match(line)
595 (define,var,value)=attempt.groups()
596 if var in patch_dict.keys():
597 if self.options.debug:
598 print 'rewriting %s as %s'%(var,patch_dict[var])
599 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
604 for (key,was_changed) in changed.iteritems():
606 if self.options.debug:
607 print 'rewriting missing %s as %s'%(key,patch_dict[key])
608 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
611 os.rename(newspecfile,specfile)
613 # returns all lines until the magic line
614 def unignored_lines (self, logfile):
616 white_line_matcher = re.compile("\A\s*\Z")
617 for logline in file(logfile).readlines():
618 if logline.strip() == Module.svn_magic_line:
620 elif white_line_matcher.match(logline):
623 result.append(logline.strip()+'\n')
626 # creates a copy of the input with only the unignored lines
627 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
629 f.write(self.setting_tag_format%new_tag_name + '\n')
630 for line in self.unignored_lines(filein):
634 def insert_changelog (self, logfile, oldtag, newtag):
635 for specfile in self.all_specnames():
636 newspecfile=specfile+".new"
637 if self.options.verbose:
638 print 'Inserting changelog from %s into %s'%(logfile,specfile)
640 new=open(newspecfile,"w")
641 for line in spec.readlines():
643 if re.compile('%changelog').match(line):
644 dateformat="* %a %b %d %Y"
645 datepart=time.strftime(dateformat)
646 logpart="%s <%s> - %s"%(Module.config['username'],
647 Module.config['email'],
649 new.write(datepart+" "+logpart+"\n")
650 for logline in self.unignored_lines(logfile):
651 new.write("- " + logline)
655 os.rename(newspecfile,specfile)
657 def show_dict (self, spec_dict):
658 if self.options.verbose:
659 for (k,v) in spec_dict.iteritems():
662 def mod_svn_url (self):
663 return "%s/%s"%(Module.config['svnpath'],self.name)
666 if hasattr(self,'branch'):
667 return "%s/branches/%s"%(self.mod_svn_url(),self.branch)
668 elif hasattr(self,'tagname'):
669 return "%s/tags/%s"%(self.mod_svn_url(),self.tagname)
671 return "%s/trunk"%(self.mod_svn_url())
673 def last_tag (self, spec_dict):
674 return "%s-%s"%(spec_dict[self.module_version_varname],spec_dict[self.module_taglevel_varname])
676 def tag_name (self, spec_dict):
678 return "%s-%s"%(self.name,
679 self.last_tag(spec_dict))
681 raise Exception, 'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
683 def tag_url (self, spec_dict):
684 return "%s/tags/%s"%(self.mod_svn_url(),self.tag_name(spec_dict))
686 def check_svnpath_exists (self, url, message):
687 if self.options.fast_checks:
689 if self.options.verbose:
690 print 'Checking url (%s) %s'%(url,message),
692 if SvnRepository.remote_exists(url):
693 if self.options.verbose: print 'exists - OK'
695 if self.options.verbose: print 'KO'
696 raise Exception, 'Could not find %s URL %s'%(message,url)
698 def check_svnpath_not_exists (self, url, message):
699 if self.options.fast_checks:
701 if self.options.verbose:
702 print 'Checking url (%s) %s'%(url,message),
704 if not SvnRepository.remote_exists(url):
705 if self.options.verbose: print 'does not exist - OK'
707 if self.options.verbose: print 'KO'
708 raise Exception, '%s URL %s already exists - exiting'%(message,url)
710 # locate specfile, parse it, check it and show values
713 ##############################
714 # using fine_grain means replacing only those instances that currently refer to this tag
715 # otherwise, <module>-SVNPATH is replaced unconditionnally
716 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
717 newtagsfile=tagsfile+".new"
719 new=open(newtagsfile,"w")
722 # fine-grain : replace those lines that refer to oldname
724 if self.options.verbose:
725 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
726 matcher=re.compile("^(.*)%s(.*)"%oldname)
727 for line in tags.readlines():
728 if not matcher.match(line):
731 (begin,end)=matcher.match(line).groups()
732 new.write(begin+newname+end+"\n")
734 # brute-force : change uncommented lines that define <module>-SVNPATH
736 if self.options.verbose:
737 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
738 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
740 matcher_module=re.compile(pattern)
741 for line in tags.readlines():
742 attempt=matcher_module.match(line)
744 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
745 if self.options.verbose:
747 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
748 new.write(replacement)
754 os.rename(newtagsfile,tagsfile)
755 if self.options.verbose: print "%d changes"%matches
759 self.init_module_dir()
760 self.revert_module_dir()
761 self.update_module_dir()
763 spec_dict = self.spec_dict()
764 self.show_dict(spec_dict)
767 edge_url=self.edge_url()
768 old_tag_name = self.tag_name(spec_dict)
769 old_tag_url=self.tag_url(spec_dict)
770 if (self.options.new_version):
771 # new version set on command line
772 spec_dict[self.module_version_varname] = self.options.new_version
773 spec_dict[self.module_taglevel_varname] = 0
776 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
777 spec_dict[self.module_taglevel_varname] = new_taglevel
780 new_tag_name = self.tag_name(spec_dict)
781 new_tag_url=self.tag_url(spec_dict)
782 self.check_svnpath_exists (edge_url,"edge track")
783 self.check_svnpath_exists (old_tag_url,"previous tag")
784 self.check_svnpath_not_exists (new_tag_url,"new tag")
787 diff_output=Command("svn diff %s %s"%(old_tag_url,edge_url),
788 self.options).output_of()
789 if len(diff_output) == 0:
790 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
793 # side effect in trunk's specfile
794 self.patch_spec_var(spec_dict)
796 # prepare changelog file
797 # we use the standard subversion magic string (see svn_magic_line)
798 # so we can provide useful information, such as version numbers and diff
800 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
801 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
802 setting_tag_line=Module.setting_tag_format%new_tag_name
803 file(changelog,"w").write("""
806 Please write a changelog for this new tag in the section above
807 """%(Module.svn_magic_line,setting_tag_line))
809 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
810 file(changelog,"a").write('DIFF=========\n' + diff_output)
812 if self.options.debug:
816 self.run("%s %s"%(self.options.editor,changelog))
817 # strip magic line in second file - looks like svn has changed its magic line with 1.6
818 # so we do the job ourselves
819 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
820 # insert changelog in spec
821 if self.options.changelog:
822 self.insert_changelog (changelog,old_tag_name,new_tag_name)
825 build_path = os.path.join(self.options.workdir,
826 Module.config['build'])
827 build = Repository(build_path, self.options)
828 if self.options.build_branch:
829 build.to_branch(self.options.build_branch)
831 tagsfiles=glob(build.path+"/*-tags*.mk")
832 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
836 for tagsfile in tagsfiles:
837 status=tagsdict[tagsfile]
838 basename=os.path.basename(tagsfile)
839 print ".................... Dealing with %s"%basename
840 while tagsdict[tagsfile] == 'todo' :
841 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
842 [ ('y','es'), ('n', 'ext'), ('f','orce'),
843 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
846 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
848 print 'Done with %s'%os.path.basename(tagsfile)
849 tagsdict[tagsfile]='done'
851 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
853 self.run("svn diff %s"%tagsfile)
855 self.run("svn revert %s"%tagsfile)
857 self.run("cat %s"%tagsfile)
860 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
861 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
862 d: show current diff for this tag file
863 r: revert that tag file
864 c: cat the current tag file
865 n: move to next file"""%locals()
867 if prompt("Want to review changes on tags files",False):
868 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
873 def diff_all_changes():
875 print self.repository.diff()
877 def commit_all_changes(log):
878 self.repository.commit(log)
881 self.run_prompt("Review module and build", diff_all_changes)
882 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
884 if self.repository.type == 'svn':
885 self.run_prompt("Create tag", self.repository.tag, edge_url, new_tag_url, changelog_svn)
886 elif self.repository.type == 'git':
887 tagname = os.path.basename(new_tag_url)
888 self.run_prompt("Create tag", self.repository.tag, tagname, changelog_svn)
890 if self.options.debug:
891 print 'Preserving',changelog,'and stripped',changelog_svn
894 os.unlink(changelog_svn)
897 ##############################
900 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
902 module-tools : a set of tools to manage subversion tags and specfile
903 requires the specfile to either
904 * define *version* and *taglevel*
906 * define redirection variables module_version_varname / module_taglevel_varname
908 by default, the trunk of modules is taken into account
909 in this case, just mention the module name as <module_desc>
911 if you wish to work on a branch rather than on the trunk,
912 you can use something like e.g. Mom:2.1 as <module_desc>
914 release_usage="""Usage: %prog [options] tag1 .. tagn
915 Extract release notes from the changes in specfiles between several build tags, latest first
917 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
918 You can refer to a (build) branch by prepending a colon, like in
919 release-changelog :4.2 4.2-rc25
920 You can refer to the build trunk by just mentioning 'trunk', e.g.
921 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
923 common_usage="""More help:
924 see http://svn.planet-lab.org/wiki/ModuleTools"""
927 'list' : "displays a list of available tags or branches",
928 'version' : "check latest specfile and print out details",
929 'diff' : "show difference between module (trunk or branch) and latest tag",
930 'tag' : """increment taglevel in specfile, insert changelog in specfile,
931 create new tag and and monitor its adoption in build/*-tags*.mk""",
932 'branch' : """create a branch for this module, from the latest tag on the trunk,
933 and change trunk's version number to reflect the new branch name;
934 you can specify the new branch name by using module:branch""",
935 'sync' : """create a tag from the module
936 this is a last resort option, mostly for repairs""",
937 'changelog' : """extract changelog between build tags
938 expected arguments are a list of tags""",
941 silent_modes = ['list']
942 release_modes = ['changelog']
945 def optparse_list (option, opt, value, parser):
947 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
949 setattr(parser.values,option.dest,value.split())
954 for function in Main.modes.keys():
955 if sys.argv[0].find(function) >= 0:
959 print "Unsupported command",sys.argv[0]
960 print "Supported commands:" + " ".join(Main.modes.keys())
963 if mode not in Main.release_modes:
964 usage = Main.module_usage
965 usage += Main.common_usage
966 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
968 usage = Main.release_usage
969 usage += Main.common_usage
971 parser=OptionParser(usage=usage,version=subversion_id)
973 if mode == "tag" or mode == 'branch':
974 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
975 help="set new version and reset taglevel to 0")
977 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
978 help="do not update changelog section in specfile when tagging")
979 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
980 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
981 if mode == "tag" or mode == "sync" :
982 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
983 help="specify editor")
985 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
986 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
987 help="dry run - shell commands are only displayed")
988 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
989 default=[], nargs=1,type="string",
990 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
991 -- can be set multiple times, or use quotes""")
993 parser.add_option("-w","--workdir", action="store", dest="workdir",
994 default="%s/%s"%(os.getenv("HOME"),"modules"),
995 help="""name for dedicated working dir - defaults to ~/modules
996 ** THIS MUST NOT ** be your usual working directory""")
997 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
998 help="skip safety checks, such as svn updates -- use with care")
1000 # default verbosity depending on function - temp
1001 verbose_modes= ['tag', 'sync', 'branch']
1003 if mode not in verbose_modes:
1004 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1005 help="run in verbose mode")
1007 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1008 help="run in quiet (non-verbose) mode")
1009 (options, args) = parser.parse_args()
1011 if not hasattr(options,'dry_run'):
1012 options.dry_run=False
1013 if not hasattr(options,'www'):
1021 Module.init_homedir(options)
1023 modules=[ Module(modname,options) for modname in args ]
1024 for module in modules:
1025 if len(args)>1 and mode not in Main.silent_modes:
1026 print '========================================',module.friendly_name()
1027 # call the method called do_<mode>
1028 method=Module.__dict__["do_%s"%mode]
1033 traceback.print_exc()
1034 print 'Skipping module %s: '%modname,e
1036 ####################
1037 if __name__ == "__main__" :
1040 except KeyboardInterrupt: