7 from optparse import OptionParser
9 # HARDCODED NAME CHANGES
11 # Moving to git we decided to rename some of the repositories. Here is
12 # a map of name changes applied in git repositories.
13 RENAMED_SVN_MODULES = {
17 def svn_to_git_name(module):
18 if RENAMED_SVN_MODULES.has_key(module):
19 return RENAMED_SVN_MODULES[module]
22 def git_to_svn_name(module):
23 for key in RENAMED_SVN_MODULES:
24 if module == RENAMED_SVN_MODULES[key]:
29 # e.g. other_choices = [ ('d','iff') , ('g','uess') ] - lowercase
30 def prompt (question,default=True,other_choices=[],allow_outside=False):
31 if not isinstance (other_choices,list):
32 other_choices = [ other_choices ]
33 chars = [ c for (c,rest) in other_choices ]
37 if default is True: choices.append('[y]')
38 else : choices.append('y')
40 if default is False: choices.append('[n]')
41 else : choices.append('n')
43 for (char,choice) in other_choices:
45 choices.append("["+char+"]"+choice)
47 choices.append("<"+char+">"+choice)
49 answer=raw_input(question + " " + "/".join(choices) + " ? ")
52 answer=answer[0].lower()
54 if 'y' in chars: return 'y'
57 if 'n' in chars: return 'n'
60 for (char,choice) in other_choices:
65 return prompt(question,default,other_choices)
71 editor = os.environ['EDITOR']
79 def print_fold (line):
80 while len(line) >= fold_length:
81 print line[:fold_length],'\\'
82 line=line[fold_length:]
86 def __init__ (self,command,options):
89 self.tmp="/tmp/command-%d"%os.getpid()
92 if self.options.dry_run:
93 print 'dry_run',self.command
95 if self.options.verbose and self.options.mode not in Main.silent_modes:
96 print '+',self.command
98 return os.system(self.command)
100 def run_silent (self):
101 if self.options.dry_run:
102 print 'dry_run',self.command
104 if self.options.verbose:
105 print '+',self.command,' .. ',
107 retcod=os.system(self.command + " &> " + self.tmp)
109 print "FAILED ! -- out+err below (command was %s)"%self.command
110 os.system("cat " + self.tmp)
111 print "FAILED ! -- end of quoted output"
112 elif self.options.verbose:
118 if self.run_silent() !=0:
119 raise Exception,"Command %s failed"%self.command
121 # returns stdout, like bash's $(mycommand)
122 def output_of (self,with_stderr=False):
123 if self.options.dry_run:
124 print 'dry_run',self.command
125 return 'dry_run output'
126 tmp="/tmp/status-%d"%os.getpid()
127 if self.options.debug:
128 print '+',self.command,' .. ',
137 result=file(tmp).read()
139 if self.options.debug:
147 def __init__(self, path, options):
149 self.options = options
152 return os.path.basename(self.path)
155 out = Command("svn info %s" % self.path, self.options).output_of()
156 for line in out.split('\n'):
157 if line.startswith("URL:"):
158 return line.split()[1].strip()
161 out = Command("svn info %s" % self.path, self.options).output_of()
162 for line in out.split('\n'):
163 if line.startswith("Repository Root:"):
164 root = line.split()[2].strip()
165 return "%s/%s" % (root, self.name())
168 def checkout(cls, remote, local, options, recursive=False):
170 svncommand = "svn co %s %s" % (remote, local)
172 svncommand = "svn co -N %s %s" % (remote, local)
173 Command("rm -rf %s" % local, options).run_silent()
174 Command(svncommand, options).run_fatal()
176 return SvnRepository(local, options)
179 def remote_exists(cls, remote):
180 return os.system("svn list %s &> /dev/null" % remote) == 0
182 def tag_exists(self, tagname):
183 url = "%s/tags/%s" % (self.repo_root(), tagname)
184 return SvnRepository.remote_exists(url)
186 def update(self, subdir="", recursive=True):
187 path = os.path.join(self.path, subdir)
189 svncommand = "svn up %s" % path
191 svncommand = "svn up -N %s" % path
192 Command(svncommand, self.options).run_fatal()
194 def commit(self, logfile):
195 # add all new files to the repository
196 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
197 self.path, self.options).run_silent()
198 Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
200 def to_branch(self, branch):
201 remote = "%s/branches/%s" % (self.repo_root(), branch)
202 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
204 def to_tag(self, tag):
205 remote = "%s/tags/%s" % (self.repo_root(), branch)
206 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
208 def tag(self, tagname, logfile):
209 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
210 self_url = self.url()
211 Command("svn copy -F %s %s %s" % (logfile, self_url, tag_url), self.options).run_fatal()
213 def diff(self, f=""):
215 f = os.path.join(self.path, f)
218 return Command("svn diff %s" % f, self.options).output_of(True)
220 def diff_with_tag(self, tagname):
221 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
222 return Command("svn diff %s %s" % (tag_url, self.url()),
223 self.options).output_of(True)
225 def revert(self, f=""):
227 Command("svn revert %s" % os.path.join(self.path, f), self.options).run_fatal()
230 Command("svn revert %s -R" % self.path, self.options).run_fatal()
231 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
232 self.path, self.options).run_silent()
235 command="svn status %s" % self.path
236 return len(Command(command,self.options).output_of(True)) == 0
239 return os.path.exists(os.path.join(self.path, ".svn"))
245 def __init__(self, path, options):
247 self.options = options
250 return os.path.basename(self.path)
256 c = Command("git remote show origin", self.options)
257 out = self.__run_in_repo(c.output_of)
258 for line in out.split('\n'):
259 if line.strip().startswith("Fetch URL:"):
260 repo = line.split()[2]
263 def checkout(cls, remote, local, options, depth=1):
264 Command("rm -rf %s" % local, options).run_silent()
265 Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
266 return GitRepository(local, options)
269 def remote_exists(cls, remote):
270 return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
272 def tag_exists(self, tagname):
273 command = 'git tag -l | grep "^%s$"' % tagname
274 c = Command(command, self.options)
275 out = self.__run_in_repo(c.output_of, with_stderr=True)
278 def __run_in_repo(self, fun, *args, **kwargs):
281 ret = fun(*args, **kwargs)
285 def __run_command_in_repo(self, command, ignore_errors=False):
286 c = Command(command, self.options)
288 return self.__run_in_repo(c.output_of)
290 return self.__run_in_repo(c.run_fatal)
292 def update(self, subdir=None, recursive=None):
293 return self.__run_command_in_repo("git pull")
295 def to_branch(self, branch, remote=True):
297 branch = "origin/%s" % branch
298 return self.__run_command_in_repo("git checkout %s" % branch)
300 def to_tag(self, tag):
301 return self.__run_command_in_repo("git checkout %s" % tag)
303 def tag(self, tagname, logfile):
304 self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
307 def diff(self, f=""):
308 c = Command("git diff %s" % f, self.options)
309 return self.__run_in_repo(c.output_of, with_stderr=True)
311 def diff_with_tag(self, tagname):
312 c = Command("git diff %s" % tagname, self.options)
313 return self.__run_in_repo(c.output_of, with_stderr=True)
315 def commit(self, logfile):
316 self.__run_command_in_repo("git add -A", ignore_errors=True)
317 self.__run_command_in_repo("git commit -F %s" % logfile, ignore_errors=True)
318 self.__run_command_in_repo("git push")
319 self.__run_command_in_repo("git push --tags")
321 def revert(self, f=""):
323 self.__run_command_in_repo("git checkout %s" % f)
326 self.__run_command_in_repo("git --no-pager reset --hard")
327 self.__run_command_in_repo("git --no-pager clean -f")
332 s="nothing to commit (working directory clean)"
333 return Command(command, self.options).output_of(True).find(s) >= 0
334 return self.__run_in_repo(check_commit)
337 return os.path.exists(os.path.join(self.path, ".git"))
341 """ Generic repository """
342 supported_repo_types = [SvnRepository, GitRepository]
344 def __init__(self, path, options):
346 self.options = options
347 for repo in self.supported_repo_types:
348 self.repo = repo(self.path, self.options)
349 if self.repo.is_valid():
353 def has_moved_to_git(cls, module, svnpath):
354 module = git_to_svn_name(module)
355 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
358 def remote_exists(cls, remote):
359 for repo in Repository.supported_repo_types:
360 if repo.remote_exists(remote):
364 def __getattr__(self, attr):
365 return getattr(self.repo, attr)
369 # support for tagged module is minimal, and is for the Build class only
372 svn_magic_line="--This line, and those below, will be ignored--"
373 setting_tag_format = "Setting tag %s"
375 redirectors=[ # ('module_name_varname','name'),
376 ('module_version_varname','version'),
377 ('module_taglevel_varname','taglevel'), ]
379 # where to store user's config
380 config_storage="CONFIG"
385 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
386 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
387 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
388 ('gituser', "Enter your user name (login name) on git server", os.getlogin()),
389 ("build", "Enter the name of your build module","build"),
390 ('username',"Enter your firstname and lastname for changelogs",""),
391 ("email","Enter your email address for changelogs",""),
395 def prompt_config_option(cls, key, message, default):
396 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
399 def prompt_config (cls):
400 for (key,message,default) in cls.configKeys:
402 while not cls.config[key]:
403 cls.prompt_config_option(key, message, default)
406 # for parsing module spec name:branch
407 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
408 # special form for tagged module - for Build
409 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
411 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
413 def __init__ (self,module_spec,options):
415 attempt=Module.matcher_branch_spec.match(module_spec)
417 self.name=attempt.group('name')
418 self.branch=attempt.group('branch')
420 attempt=Module.matcher_tag_spec.match(module_spec)
422 self.name=attempt.group('name')
423 self.tagname=attempt.group('tagname')
425 self.name=module_spec
427 # when available prefer to use git module name internally
428 self.name = svn_to_git_name(self.name)
431 self.module_dir="%s/%s"%(options.workdir,self.name)
432 self.repository = None
435 def run (self,command):
436 return Command(command,self.options).run()
437 def run_fatal (self,command):
438 return Command(command,self.options).run_fatal()
439 def run_prompt (self,message,fun, *args):
440 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
441 if not self.options.verbose:
443 choice=prompt(message,True,('s','how'))
447 elif choice is False:
448 print 'About to run function:', fun_msg
450 question=message+" - want to run function: " + fun_msg
451 if prompt(question,True):
454 def friendly_name (self):
455 if hasattr(self,'branch'):
456 return "%s:%s"%(self.name,self.branch)
457 elif hasattr(self,'tagname'):
458 return "%s@%s"%(self.name,self.tagname)
463 def git_remote_dir (cls, name):
464 return "%s@%s:/git/%s.git" % (cls.config['gituser'], cls.config['gitserver'], name)
467 def svn_remote_dir (cls, name):
468 name = git_to_svn_name(name)
469 svn = cls.config['svnpath']
470 if svn.endswith('/'):
471 return "%s%s" % (svn, name)
472 return "%s/%s" % (svn, name)
474 def svn_selected_remote(self):
475 svn_name = git_to_svn_name(self.name)
476 remote = self.svn_remote_dir(svn_name)
477 if hasattr(self,'branch'):
478 remote = "%s/branches/%s" % (remote, self.branch)
479 elif hasattr(self,'tagname'):
480 remote = "%s/tags/%s" % (remote, self.tagname)
482 remote = "%s/trunk" % remote
487 def init_homedir (cls, options):
488 if options.verbose and options.mode not in Main.silent_modes:
489 print 'Checking for', options.workdir
490 storage="%s/%s"%(options.workdir, cls.config_storage)
491 # sanity check. Either the topdir exists AND we have a config/storage
492 # or topdir does not exist and we create it
493 # to avoid people use their own daily svn repo
494 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
495 print """The directory %s exists and has no CONFIG file
496 If this is your regular working directory, please provide another one as the
497 module-* commands need a fresh working dir. Make sure that you do not use
498 that for other purposes than tagging""" % options.workdir
501 def checkout_build():
502 print "Checking out build module..."
503 remote = cls.git_remote_dir(cls.config['build'])
504 local = os.path.join(options.workdir, cls.config['build'])
505 GitRepository.checkout(remote, local, options, depth=1)
510 for (key,message,default) in Module.configKeys:
511 f.write("%s=%s\n"%(key,Module.config[key]))
514 print 'Stored',storage
515 Command("cat %s"%storage,options).run()
520 for line in f.readlines():
521 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
522 Module.config[key]=value
525 if not os.path.isdir (options.workdir):
526 print "Cannot find",options.workdir,"let's create it"
527 Command("mkdir -p %s" % options.workdir, options).run_silent()
533 # check missing config options
535 for (key,message,default) in cls.configKeys:
536 if not Module.config.has_key(key):
537 print "Configuration changed for module-tools"
538 cls.prompt_config_option(key, message, default)
542 Command("rm -rf %s" % options.workdir, options).run_silent()
543 Command("mkdir -p %s" % options.workdir, options).run_silent()
547 build_dir = os.path.join(options.workdir, cls.config['build'])
548 if not os.path.isdir(build_dir):
551 build = Repository(build_dir, options)
552 if not build.is_clean():
553 print "build module needs a revert"
558 if options.verbose and options.mode not in Main.silent_modes:
559 print '******** Using config'
560 for (key,message,default) in Module.configKeys:
561 print '\t',key,'=',Module.config[key]
563 def init_module_dir (self):
564 if self.options.verbose:
565 print 'Checking for',self.module_dir
567 if not os.path.isdir (self.module_dir):
568 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
569 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
573 remote = self.svn_selected_remote()
574 self.repository = SvnRepository.checkout(remote,
576 self.options, recursive=False)
578 self.repository = Repository(self.module_dir, self.options)
579 if self.repository.type == "svn":
580 # check if module has moved to git
581 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
582 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
583 self.init_module_dir()
584 # check if we have the required branch/tag
585 if self.repository.url() != self.svn_selected_remote():
586 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
587 self.init_module_dir()
589 elif self.repository.type == "git":
590 if hasattr(self,'branch'):
591 self.repository.to_branch(self.branch)
592 elif hasattr(self,'tagname'):
593 self.repository.to_tag(self.tagname)
596 raise Exception, 'Cannot find %s - check module name'%self.module_dir
599 def revert_module_dir (self):
600 if self.options.fast_checks:
601 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
603 if self.options.verbose:
604 print 'Checking whether', self.module_dir, 'needs being reverted'
606 if not self.repository.is_clean():
607 self.repository.revert()
609 def update_module_dir (self):
610 if self.options.fast_checks:
611 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
613 if self.options.verbose:
614 print 'Updating', self.module_dir
615 self.repository.update()
617 def main_specname (self):
618 attempt="%s/%s.spec"%(self.module_dir,self.name)
619 if os.path.isfile (attempt):
621 pattern1="%s/*.spec"%self.module_dir
622 level1=glob(pattern1)
625 pattern2="%s/*/*.spec"%self.module_dir
626 level2=glob(pattern2)
630 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
632 def all_specnames (self):
633 level1=glob("%s/*.spec" % self.module_dir)
634 if level1: return level1
635 level2=glob("%s/*/*.spec" % self.module_dir)
638 def parse_spec (self, specfile, varnames):
639 if self.options.verbose:
640 print 'Parsing',specfile,
646 for line in f.readlines():
647 attempt=Module.matcher_rpm_define.match(line)
649 (define,var,value)=attempt.groups()
653 if self.options.debug:
654 print 'found',len(result),'keys'
655 for (k,v) in result.iteritems():
659 # stores in self.module_name_varname the rpm variable to be used for the module's name
660 # and the list of these names in self.varnames
661 def spec_dict (self):
662 specfile=self.main_specname()
663 redirector_keys = [ varname for (varname,default) in Module.redirectors]
664 redirect_dict = self.parse_spec(specfile,redirector_keys)
665 if self.options.debug:
666 print '1st pass parsing done, redirect_dict=',redirect_dict
668 for (varname,default) in Module.redirectors:
669 if redirect_dict.has_key(varname):
670 setattr(self,varname,redirect_dict[varname])
671 varnames += [redirect_dict[varname]]
673 setattr(self,varname,default)
674 varnames += [ default ]
675 self.varnames = varnames
676 result = self.parse_spec (specfile,self.varnames)
677 if self.options.debug:
678 print '2st pass parsing done, varnames=',varnames,'result=',result
681 def patch_spec_var (self, patch_dict,define_missing=False):
682 for specfile in self.all_specnames():
683 # record the keys that were changed
684 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
685 newspecfile=specfile+".new"
686 if self.options.verbose:
687 print 'Patching',specfile,'for',patch_dict.keys()
689 new=open(newspecfile,"w")
691 for line in spec.readlines():
692 attempt=Module.matcher_rpm_define.match(line)
694 (define,var,value)=attempt.groups()
695 if var in patch_dict.keys():
696 if self.options.debug:
697 print 'rewriting %s as %s'%(var,patch_dict[var])
698 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
703 for (key,was_changed) in changed.iteritems():
705 if self.options.debug:
706 print 'rewriting missing %s as %s'%(key,patch_dict[key])
707 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
710 os.rename(newspecfile,specfile)
712 # returns all lines until the magic line
713 def unignored_lines (self, logfile):
715 white_line_matcher = re.compile("\A\s*\Z")
716 for logline in file(logfile).readlines():
717 if logline.strip() == Module.svn_magic_line:
719 elif white_line_matcher.match(logline):
722 result.append(logline.strip()+'\n')
725 # creates a copy of the input with only the unignored lines
726 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
728 f.write(self.setting_tag_format%new_tag_name + '\n')
729 for line in self.unignored_lines(filein):
733 def insert_changelog (self, logfile, oldtag, newtag):
734 for specfile in self.all_specnames():
735 newspecfile=specfile+".new"
736 if self.options.verbose:
737 print 'Inserting changelog from %s into %s'%(logfile,specfile)
739 new=open(newspecfile,"w")
740 for line in spec.readlines():
742 if re.compile('%changelog').match(line):
743 dateformat="* %a %b %d %Y"
744 datepart=time.strftime(dateformat)
745 logpart="%s <%s> - %s"%(Module.config['username'],
746 Module.config['email'],
748 new.write(datepart+" "+logpart+"\n")
749 for logline in self.unignored_lines(logfile):
750 new.write("- " + logline)
754 os.rename(newspecfile,specfile)
756 def show_dict (self, spec_dict):
757 if self.options.verbose:
758 for (k,v) in spec_dict.iteritems():
761 def last_tag (self, spec_dict):
763 return "%s-%s" % (spec_dict[self.module_version_varname],
764 spec_dict[self.module_taglevel_varname])
766 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
768 def tag_name (self, spec_dict, old_svn_name=False):
769 base_tag_name = self.name
771 base_tag_name = git_to_svn_name(self.name)
772 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
775 ##############################
776 # using fine_grain means replacing only those instances that currently refer to this tag
777 # otherwise, <module>-SVNPATH is replaced unconditionnally
778 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
779 newtagsfile=tagsfile+".new"
781 new=open(newtagsfile,"w")
784 # fine-grain : replace those lines that refer to oldname
786 if self.options.verbose:
787 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
788 matcher=re.compile("^(.*)%s(.*)"%oldname)
789 for line in tags.readlines():
790 if not matcher.match(line):
793 (begin,end)=matcher.match(line).groups()
794 new.write(begin+newname+end+"\n")
796 # brute-force : change uncommented lines that define <module>-SVNPATH
798 if self.options.verbose:
799 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
800 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
802 matcher_module=re.compile(pattern)
803 for line in tags.readlines():
804 attempt=matcher_module.match(line)
806 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
807 if self.options.verbose:
809 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
810 new.write(replacement)
816 os.rename(newtagsfile,tagsfile)
817 if self.options.verbose: print "%d changes"%matches
820 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
821 if self.options.verbose:
822 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
824 found_tagname = tagname
825 found = self.repository.tag_exists(tagname)
826 if not found and old_svn_tag_name:
827 if self.options.verbose:
829 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
830 found = self.repository.tag_exists(old_svn_tag_name)
832 found_tagname = old_svn_tag_name
834 if (found and need_it) or (not found and not need_it):
839 raise Exception, "tag (%s) is already there" % tagname
841 raise Exception, "can not find required tag (%s)" % tagname
846 ##############################
848 self.init_module_dir()
849 self.revert_module_dir()
850 self.update_module_dir()
852 spec_dict = self.spec_dict()
853 self.show_dict(spec_dict)
856 old_tag_name = self.tag_name(spec_dict)
857 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
859 if (self.options.new_version):
860 # new version set on command line
861 spec_dict[self.module_version_varname] = self.options.new_version
862 spec_dict[self.module_taglevel_varname] = 0
865 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
866 spec_dict[self.module_taglevel_varname] = new_taglevel
868 new_tag_name = self.tag_name(spec_dict)
871 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
872 new_tag_name = self.check_tag(new_tag_name, need_it=False)
875 diff_output = self.repository.diff_with_tag(old_tag_name)
876 if len(diff_output) == 0:
877 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
880 # side effect in trunk's specfile
881 self.patch_spec_var(spec_dict)
883 # prepare changelog file
884 # we use the standard subversion magic string (see svn_magic_line)
885 # so we can provide useful information, such as version numbers and diff
887 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
888 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
889 setting_tag_line=Module.setting_tag_format%new_tag_name
890 file(changelog,"w").write("""
893 Please write a changelog for this new tag in the section above
894 """%(Module.svn_magic_line,setting_tag_line))
896 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
897 file(changelog,"a").write('DIFF=========\n' + diff_output)
899 if self.options.debug:
903 self.run("%s %s"%(self.options.editor,changelog))
904 # strip magic line in second file - looks like svn has changed its magic line with 1.6
905 # so we do the job ourselves
906 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
907 # insert changelog in spec
908 if self.options.changelog:
909 self.insert_changelog (changelog,old_tag_name,new_tag_name)
912 build_path = os.path.join(self.options.workdir,
913 Module.config['build'])
914 build = Repository(build_path, self.options)
915 if self.options.build_branch:
916 build.to_branch(self.options.build_branch)
917 if not build.is_clean():
920 tagsfiles=glob(build.path+"/*-tags*.mk")
921 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
925 for tagsfile in tagsfiles:
926 status=tagsdict[tagsfile]
927 basename=os.path.basename(tagsfile)
928 print ".................... Dealing with %s"%basename
929 while tagsdict[tagsfile] == 'todo' :
930 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
931 [ ('y','es'), ('n', 'ext'), ('f','orce'),
932 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
935 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
937 print 'Done with %s'%os.path.basename(tagsfile)
938 tagsdict[tagsfile]='done'
940 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
942 print build.diff(f=tagsfile)
944 build.revert(f=tagsfile)
946 self.run("cat %s"%tagsfile)
949 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
950 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
951 d: show current diff for this tag file
952 r: revert that tag file
953 c: cat the current tag file
954 n: move to next file"""%locals()
956 if prompt("Want to review changes on tags files",False):
957 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
962 def diff_all_changes():
964 print self.repository.diff()
966 def commit_all_changes(log):
967 self.repository.commit(log)
970 self.run_prompt("Review module and build", diff_all_changes)
971 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
972 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
974 if self.options.debug:
975 print 'Preserving',changelog,'and stripped',changelog_svn
978 os.unlink(changelog_svn)
981 ##############################
982 def do_version (self):
983 self.init_module_dir()
984 self.revert_module_dir()
985 self.update_module_dir()
986 spec_dict = self.spec_dict()
988 self.html_store_title('Version for module %s (%s)' % (self.friendly_name(),
989 self.last_tag(spec_dict)))
990 for varname in self.varnames:
991 if not spec_dict.has_key(varname):
992 self.html_print ('Could not find %%define for %s'%varname)
995 self.html_print ("%-16s %s"%(varname,spec_dict[varname]))
996 if self.options.verbose:
997 self.html_print ("%-16s %s"%('main specfile:',self.main_specname()))
998 self.html_print ("%-16s %s"%('specfiles:',self.all_specnames()))
999 self.html_print_end()
1003 ##############################
1004 # store and restitute html fragments
1006 def html_href (url,text): return '<a href="%s">%s</a>'%(url,text)
1009 def html_anchor (url,text): return '<a name="%s">%s</a>'%(url,text)
1012 def html_quote (text):
1013 return text.replace('&','&').replace('<','<').replace('>','>')
1015 # only the fake error module has multiple titles
1016 def html_store_title (self, title):
1017 if not hasattr(self,'titles'): self.titles=[]
1018 self.titles.append(title)
1020 def html_store_raw (self, html):
1021 if not hasattr(self,'body'): self.body=''
1024 def html_store_pre (self, text):
1025 if not hasattr(self,'body'): self.body=''
1026 self.body += '<pre>' + self.html_quote(text) + '</pre>'
1028 def html_print (self, txt):
1029 if not self.options.www:
1032 if not hasattr(self,'in_list') or not self.in_list:
1033 self.html_store_raw('<ul>')
1035 self.html_store_raw('<li>'+txt+'</li>')
1037 def html_print_end (self):
1038 if self.options.www:
1039 self.html_store_raw ('</ul>')
1042 def html_dump_header(title):
1043 nowdate=time.strftime("%Y-%m-%d")
1044 nowtime=time.strftime("%H:%M (%Z)")
1046 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1047 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
1050 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1051 <style type="text/css">
1052 body { font-family:georgia, serif; }
1053 h1 {font-size: large; }
1054 p.title {font-size: x-large; }
1055 span.error {text-weight:bold; color: red; }
1059 <p class='title'> %s - status on %s at %s</p>
1061 """%(title,title,nowdate,nowtime)
1064 def html_dump_middle():
1068 def html_dump_footer():
1069 print "</body></html"
1071 def html_dump_toc(self):
1072 if hasattr(self,'titles'):
1073 for title in self.titles:
1074 print '<li>',self.html_href ('#'+self.friendly_name(),title),'</li>'
1076 def html_dump_body(self):
1077 if hasattr(self,'titles'):
1078 for title in self.titles:
1079 print '<hr /><h1>',self.html_anchor(self.friendly_name(),title),'</h1>'
1080 if hasattr(self,'body'):
1082 print '<p class="top">',self.html_href('#','Back to top'),'</p>'
1085 ##############################
1088 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
1090 module-tools : a set of tools to manage subversion tags and specfile
1091 requires the specfile to either
1092 * define *version* and *taglevel*
1094 * define redirection variables module_version_varname / module_taglevel_varname
1096 by default, the trunk of modules is taken into account
1097 in this case, just mention the module name as <module_desc>
1099 if you wish to work on a branch rather than on the trunk,
1100 you can use something like e.g. Mom:2.1 as <module_desc>
1102 release_usage="""Usage: %prog [options] tag1 .. tagn
1103 Extract release notes from the changes in specfiles between several build tags, latest first
1105 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
1106 You can refer to a (build) branch by prepending a colon, like in
1107 release-changelog :4.2 4.2-rc25
1108 You can refer to the build trunk by just mentioning 'trunk', e.g.
1109 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
1111 common_usage="""More help:
1112 see http://svn.planet-lab.org/wiki/ModuleTools"""
1115 'list' : "displays a list of available tags or branches",
1116 'version' : "check latest specfile and print out details",
1117 'diff' : "show difference between module (trunk or branch) and latest tag",
1118 'tag' : """increment taglevel in specfile, insert changelog in specfile,
1119 create new tag and and monitor its adoption in build/*-tags*.mk""",
1120 'branch' : """create a branch for this module, from the latest tag on the trunk,
1121 and change trunk's version number to reflect the new branch name;
1122 you can specify the new branch name by using module:branch""",
1123 'sync' : """create a tag from the module
1124 this is a last resort option, mostly for repairs""",
1125 'changelog' : """extract changelog between build tags
1126 expected arguments are a list of tags""",
1129 silent_modes = ['list']
1130 release_modes = ['changelog']
1133 def optparse_list (option, opt, value, parser):
1135 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
1137 setattr(parser.values,option.dest,value.split())
1142 for function in Main.modes.keys():
1143 if sys.argv[0].find(function) >= 0:
1147 print "Unsupported command",sys.argv[0]
1148 print "Supported commands:" + " ".join(Main.modes.keys())
1151 if mode not in Main.release_modes:
1152 usage = Main.module_usage
1153 usage += Main.common_usage
1154 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1156 usage = Main.release_usage
1157 usage += Main.common_usage
1159 parser=OptionParser(usage=usage)
1161 if mode == "tag" or mode == 'branch':
1162 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1163 help="set new version and reset taglevel to 0")
1165 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1166 help="do not update changelog section in specfile when tagging")
1167 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1168 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1169 if mode == "tag" or mode == "sync" :
1170 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1171 help="specify editor")
1173 if mode in ["diff","version"] :
1174 parser.add_option("-W","--www", action="store", dest="www", default=False,
1175 help="export diff in html format, e.g. -W trunk")
1177 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1178 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1179 help="dry run - shell commands are only displayed")
1180 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1181 default=[], nargs=1,type="string",
1182 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1183 -- can be set multiple times, or use quotes""")
1185 parser.add_option("-w","--workdir", action="store", dest="workdir",
1186 default="%s/%s"%(os.getenv("HOME"),"modules"),
1187 help="""name for dedicated working dir - defaults to ~/modules
1188 ** THIS MUST NOT ** be your usual working directory""")
1189 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1190 help="skip safety checks, such as svn updates -- use with care")
1192 # default verbosity depending on function - temp
1193 verbose_modes= ['tag', 'sync', 'branch']
1195 if mode not in verbose_modes:
1196 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1197 help="run in verbose mode")
1199 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1200 help="run in quiet (non-verbose) mode")
1201 (options, args) = parser.parse_args()
1203 if not hasattr(options,'dry_run'):
1204 options.dry_run=False
1205 if not hasattr(options,'www'):
1213 Module.init_homedir(options)
1215 modules=[ Module(modname,options) for modname in args ]
1216 for module in modules:
1217 if len(args)>1 and mode not in Main.silent_modes:
1218 print '========================================',module.friendly_name()
1219 # call the method called do_<mode>
1220 method=Module.__dict__["do_%s"%mode]
1225 traceback.print_exc()
1226 print 'Skipping module %s: '%modname,e
1228 ####################
1229 if __name__ == "__main__" :
1232 except KeyboardInterrupt: