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):
835 if self.options.verbose:
837 if found: print "- found"
838 else: print "- not found"
840 if self.options.verbose:
843 raise Exception, "tag (%s) is already there" % tagname
845 raise Exception, "can not find required tag (%s)" % tagname
850 ##############################
852 self.init_module_dir()
853 self.revert_module_dir()
854 self.update_module_dir()
856 spec_dict = self.spec_dict()
857 self.show_dict(spec_dict)
860 old_tag_name = self.tag_name(spec_dict)
861 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
863 if (self.options.new_version):
864 # new version set on command line
865 spec_dict[self.module_version_varname] = self.options.new_version
866 spec_dict[self.module_taglevel_varname] = 0
869 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
870 spec_dict[self.module_taglevel_varname] = new_taglevel
872 new_tag_name = self.tag_name(spec_dict)
875 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
876 new_tag_name = self.check_tag(new_tag_name, need_it=False)
879 diff_output = self.repository.diff_with_tag(old_tag_name)
880 if len(diff_output) == 0:
881 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
884 # side effect in trunk's specfile
885 self.patch_spec_var(spec_dict)
887 # prepare changelog file
888 # we use the standard subversion magic string (see svn_magic_line)
889 # so we can provide useful information, such as version numbers and diff
891 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
892 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
893 setting_tag_line=Module.setting_tag_format%new_tag_name
894 file(changelog,"w").write("""
897 Please write a changelog for this new tag in the section above
898 """%(Module.svn_magic_line,setting_tag_line))
900 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
901 file(changelog,"a").write('DIFF=========\n' + diff_output)
903 if self.options.debug:
907 self.run("%s %s"%(self.options.editor,changelog))
908 # strip magic line in second file - looks like svn has changed its magic line with 1.6
909 # so we do the job ourselves
910 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
911 # insert changelog in spec
912 if self.options.changelog:
913 self.insert_changelog (changelog,old_tag_name,new_tag_name)
916 build_path = os.path.join(self.options.workdir,
917 Module.config['build'])
918 build = Repository(build_path, self.options)
919 if self.options.build_branch:
920 build.to_branch(self.options.build_branch)
921 if not build.is_clean():
924 tagsfiles=glob(build.path+"/*-tags*.mk")
925 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
929 for tagsfile in tagsfiles:
930 status=tagsdict[tagsfile]
931 basename=os.path.basename(tagsfile)
932 print ".................... Dealing with %s"%basename
933 while tagsdict[tagsfile] == 'todo' :
934 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
935 [ ('y','es'), ('n', 'ext'), ('f','orce'),
936 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
939 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
941 print 'Done with %s'%os.path.basename(tagsfile)
942 tagsdict[tagsfile]='done'
944 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
946 print build.diff(f=tagsfile)
948 build.revert(f=tagsfile)
950 self.run("cat %s"%tagsfile)
953 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
954 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
955 d: show current diff for this tag file
956 r: revert that tag file
957 c: cat the current tag file
958 n: move to next file"""%locals()
960 if prompt("Want to review changes on tags files",False):
961 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
966 def diff_all_changes():
968 print self.repository.diff()
970 def commit_all_changes(log):
971 self.repository.commit(log)
974 self.run_prompt("Review module and build", diff_all_changes)
975 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
976 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
978 if self.options.debug:
979 print 'Preserving',changelog,'and stripped',changelog_svn
982 os.unlink(changelog_svn)
985 ##############################
986 def do_version (self):
987 self.init_module_dir()
988 self.revert_module_dir()
989 self.update_module_dir()
990 spec_dict = self.spec_dict()
992 self.html_store_title('Version for module %s (%s)' % (self.friendly_name(),
993 self.last_tag(spec_dict)))
994 for varname in self.varnames:
995 if not spec_dict.has_key(varname):
996 self.html_print ('Could not find %%define for %s'%varname)
999 self.html_print ("%-16s %s"%(varname,spec_dict[varname]))
1000 if self.options.verbose:
1001 self.html_print ("%-16s %s"%('main specfile:',self.main_specname()))
1002 self.html_print ("%-16s %s"%('specfiles:',self.all_specnames()))
1003 self.html_print_end()
1006 ##############################
1008 self.init_module_dir()
1009 self.revert_module_dir()
1010 self.update_module_dir()
1011 spec_dict = self.spec_dict()
1012 self.show_dict(spec_dict)
1015 tag_name = self.tag_name(spec_dict)
1016 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
1019 tag_name = self.check_tag(tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
1021 if self.options.verbose:
1022 print 'Getting diff'
1023 diff_output = self.repository.diff_with_tag(tag_name)
1025 if self.options.list:
1029 thename=self.friendly_name()
1031 if self.options.www and diff_output:
1032 self.html_store_title("Diffs in module %s (%s) : %d chars"%(\
1033 thename,self.last_tag(spec_dict),len(diff_output)))
1035 self.html_store_raw ('<p> < (left) %s </p>' % tag_name)
1036 self.html_store_raw ('<p> > (right) %s </p>' % thename)
1037 self.html_store_pre (diff_output)
1038 elif not self.options.www:
1039 print 'x'*30,'module',thename
1040 print 'x'*20,'<',tag_name
1041 print 'x'*20,'>',thename
1044 ##############################
1045 # store and restitute html fragments
1047 def html_href (url,text): return '<a href="%s">%s</a>'%(url,text)
1050 def html_anchor (url,text): return '<a name="%s">%s</a>'%(url,text)
1053 def html_quote (text):
1054 return text.replace('&','&').replace('<','<').replace('>','>')
1056 # only the fake error module has multiple titles
1057 def html_store_title (self, title):
1058 if not hasattr(self,'titles'): self.titles=[]
1059 self.titles.append(title)
1061 def html_store_raw (self, html):
1062 if not hasattr(self,'body'): self.body=''
1065 def html_store_pre (self, text):
1066 if not hasattr(self,'body'): self.body=''
1067 self.body += '<pre>' + self.html_quote(text) + '</pre>'
1069 def html_print (self, txt):
1070 if not self.options.www:
1073 if not hasattr(self,'in_list') or not self.in_list:
1074 self.html_store_raw('<ul>')
1076 self.html_store_raw('<li>'+txt+'</li>')
1078 def html_print_end (self):
1079 if self.options.www:
1080 self.html_store_raw ('</ul>')
1083 def html_dump_header(title):
1084 nowdate=time.strftime("%Y-%m-%d")
1085 nowtime=time.strftime("%H:%M (%Z)")
1086 print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1087 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
1090 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1091 <style type="text/css">
1092 body { font-family:georgia, serif; }
1093 h1 {font-size: large; }
1094 p.title {font-size: x-large; }
1095 span.error {text-weight:bold; color: red; }
1099 <p class='title'> %s - status on %s at %s</p>
1101 """%(title,title,nowdate,nowtime)
1104 def html_dump_middle():
1108 def html_dump_footer():
1109 print "</body></html"
1111 def html_dump_toc(self):
1112 if hasattr(self,'titles'):
1113 for title in self.titles:
1114 print '<li>',self.html_href ('#'+self.friendly_name(),title),'</li>'
1116 def html_dump_body(self):
1117 if hasattr(self,'titles'):
1118 for title in self.titles:
1119 print '<hr /><h1>',self.html_anchor(self.friendly_name(),title),'</h1>'
1120 if hasattr(self,'body'):
1122 print '<p class="top">',self.html_href('#','Back to top'),'</p>'
1125 ##############################
1128 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
1130 module-tools : a set of tools to manage subversion tags and specfile
1131 requires the specfile to either
1132 * define *version* and *taglevel*
1134 * define redirection variables module_version_varname / module_taglevel_varname
1136 by default, the trunk of modules is taken into account
1137 in this case, just mention the module name as <module_desc>
1139 if you wish to work on a branch rather than on the trunk,
1140 you can use something like e.g. Mom:2.1 as <module_desc>
1142 release_usage="""Usage: %prog [options] tag1 .. tagn
1143 Extract release notes from the changes in specfiles between several build tags, latest first
1145 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
1146 You can refer to a (build) branch by prepending a colon, like in
1147 release-changelog :4.2 4.2-rc25
1148 You can refer to the build trunk by just mentioning 'trunk', e.g.
1149 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
1151 common_usage="""More help:
1152 see http://svn.planet-lab.org/wiki/ModuleTools"""
1155 'list' : "displays a list of available tags or branches",
1156 'version' : "check latest specfile and print out details",
1157 'diff' : "show difference between module (trunk or branch) and latest tag",
1158 'tag' : """increment taglevel in specfile, insert changelog in specfile,
1159 create new tag and and monitor its adoption in build/*-tags*.mk""",
1160 'branch' : """create a branch for this module, from the latest tag on the trunk,
1161 and change trunk's version number to reflect the new branch name;
1162 you can specify the new branch name by using module:branch""",
1163 'sync' : """create a tag from the module
1164 this is a last resort option, mostly for repairs""",
1165 'changelog' : """extract changelog between build tags
1166 expected arguments are a list of tags""",
1169 silent_modes = ['list']
1170 release_modes = ['changelog']
1173 def optparse_list (option, opt, value, parser):
1175 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
1177 setattr(parser.values,option.dest,value.split())
1182 for function in Main.modes.keys():
1183 if sys.argv[0].find(function) >= 0:
1187 print "Unsupported command",sys.argv[0]
1188 print "Supported commands:" + " ".join(Main.modes.keys())
1191 if mode not in Main.release_modes:
1192 usage = Main.module_usage
1193 usage += Main.common_usage
1194 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1196 usage = Main.release_usage
1197 usage += Main.common_usage
1199 parser=OptionParser(usage=usage)
1201 if mode == "tag" or mode == 'branch':
1202 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1203 help="set new version and reset taglevel to 0")
1205 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1206 help="do not update changelog section in specfile when tagging")
1207 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1208 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1209 if mode == "tag" or mode == "sync" :
1210 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1211 help="specify editor")
1213 if mode in ["diff","version"] :
1214 parser.add_option("-W","--www", action="store", dest="www", default=False,
1215 help="export diff in html format, e.g. -W trunk")
1218 parser.add_option("-l","--list", action="store_true", dest="list", default=False,
1219 help="just list modules that exhibit differences")
1221 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1222 parser.add_option("-a","--all",action="store_true",dest="all_modules",default=False,
1223 help="run on all modules as found in %s"%default_modules_list)
1224 parser.add_option("-f","--file",action="store",dest="modules_list",default=None,
1225 help="run on all modules found in specified file")
1226 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1227 help="dry run - shell commands are only displayed")
1228 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1229 default=[], nargs=1,type="string",
1230 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1231 -- can be set multiple times, or use quotes""")
1233 parser.add_option("-w","--workdir", action="store", dest="workdir",
1234 default="%s/%s"%(os.getenv("HOME"),"modules"),
1235 help="""name for dedicated working dir - defaults to ~/modules
1236 ** THIS MUST NOT ** be your usual working directory""")
1237 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1238 help="skip safety checks, such as svn updates -- use with care")
1240 # default verbosity depending on function - temp
1241 verbose_modes= ['tag', 'sync', 'branch']
1243 if mode not in verbose_modes:
1244 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1245 help="run in verbose mode")
1247 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1248 help="run in quiet (non-verbose) mode")
1249 (options, args) = parser.parse_args()
1251 if not hasattr(options,'dry_run'):
1252 options.dry_run=False
1253 if not hasattr(options,'www'):
1259 if options.all_modules:
1260 options.modules_list=default_modules_list
1261 if options.modules_list:
1262 args=Command("grep -v '#' %s"%options.modules_list,options).output_of().split()
1266 Module.init_homedir(options)
1269 modules=[ Module(modname,options) for modname in args ]
1270 # hack: create a dummy Module to store errors/warnings
1271 error_module = Module('__errors__',options)
1273 for module in modules:
1274 if len(args)>1 and mode not in Main.silent_modes:
1275 print '========================================',module.friendly_name()
1276 # call the method called do_<mode>
1277 method=Module.__dict__["do_%s"%mode]
1282 title='<span class="error"> Skipping module %s - failure: %s </span>'%\
1283 (module.friendly_name(), str(e))
1284 error_module.html_store_title(title)
1287 traceback.print_exc()
1288 print 'Skipping module %s: '%modname,e
1292 modetitle="Changes to tag in %s"%options.www
1293 elif mode == "version":
1294 modetitle="Latest tags in %s"%options.www
1295 modules.append(error_module)
1296 error_module.html_dump_header(modetitle)
1297 for module in modules:
1298 module.html_dump_toc()
1299 Module.html_dump_middle()
1300 for module in modules:
1301 module.html_dump_body()
1302 Module.html_dump_footer()
1304 ####################
1305 if __name__ == "__main__" :
1308 except KeyboardInterrupt: