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
847 self.init_module_dir()
848 self.revert_module_dir()
849 self.update_module_dir()
851 spec_dict = self.spec_dict()
852 self.show_dict(spec_dict)
855 old_tag_name = self.tag_name(spec_dict)
856 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
858 if (self.options.new_version):
859 # new version set on command line
860 spec_dict[self.module_version_varname] = self.options.new_version
861 spec_dict[self.module_taglevel_varname] = 0
864 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
865 spec_dict[self.module_taglevel_varname] = new_taglevel
867 new_tag_name = self.tag_name(spec_dict)
870 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
871 new_tag_name = self.check_tag(new_tag_name, need_it=False)
874 diff_output = self.repository.diff_with_tag(old_tag_name)
875 if len(diff_output) == 0:
876 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
879 # side effect in trunk's specfile
880 self.patch_spec_var(spec_dict)
882 # prepare changelog file
883 # we use the standard subversion magic string (see svn_magic_line)
884 # so we can provide useful information, such as version numbers and diff
886 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
887 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
888 setting_tag_line=Module.setting_tag_format%new_tag_name
889 file(changelog,"w").write("""
892 Please write a changelog for this new tag in the section above
893 """%(Module.svn_magic_line,setting_tag_line))
895 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
896 file(changelog,"a").write('DIFF=========\n' + diff_output)
898 if self.options.debug:
902 self.run("%s %s"%(self.options.editor,changelog))
903 # strip magic line in second file - looks like svn has changed its magic line with 1.6
904 # so we do the job ourselves
905 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
906 # insert changelog in spec
907 if self.options.changelog:
908 self.insert_changelog (changelog,old_tag_name,new_tag_name)
911 build_path = os.path.join(self.options.workdir,
912 Module.config['build'])
913 build = Repository(build_path, self.options)
914 if self.options.build_branch:
915 build.to_branch(self.options.build_branch)
916 if not build.is_clean():
919 tagsfiles=glob(build.path+"/*-tags*.mk")
920 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
924 for tagsfile in tagsfiles:
925 status=tagsdict[tagsfile]
926 basename=os.path.basename(tagsfile)
927 print ".................... Dealing with %s"%basename
928 while tagsdict[tagsfile] == 'todo' :
929 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
930 [ ('y','es'), ('n', 'ext'), ('f','orce'),
931 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
934 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
936 print 'Done with %s'%os.path.basename(tagsfile)
937 tagsdict[tagsfile]='done'
939 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
941 print build.diff(f=tagsfile)
943 build.revert(f=tagsfile)
945 self.run("cat %s"%tagsfile)
948 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
949 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
950 d: show current diff for this tag file
951 r: revert that tag file
952 c: cat the current tag file
953 n: move to next file"""%locals()
955 if prompt("Want to review changes on tags files",False):
956 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
961 def diff_all_changes():
963 print self.repository.diff()
965 def commit_all_changes(log):
966 self.repository.commit(log)
969 self.run_prompt("Review module and build", diff_all_changes)
970 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
971 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
973 if self.options.debug:
974 print 'Preserving',changelog,'and stripped',changelog_svn
977 os.unlink(changelog_svn)
980 ##############################
983 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
985 module-tools : a set of tools to manage subversion tags and specfile
986 requires the specfile to either
987 * define *version* and *taglevel*
989 * define redirection variables module_version_varname / module_taglevel_varname
991 by default, the trunk of modules is taken into account
992 in this case, just mention the module name as <module_desc>
994 if you wish to work on a branch rather than on the trunk,
995 you can use something like e.g. Mom:2.1 as <module_desc>
997 release_usage="""Usage: %prog [options] tag1 .. tagn
998 Extract release notes from the changes in specfiles between several build tags, latest first
1000 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
1001 You can refer to a (build) branch by prepending a colon, like in
1002 release-changelog :4.2 4.2-rc25
1003 You can refer to the build trunk by just mentioning 'trunk', e.g.
1004 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
1006 common_usage="""More help:
1007 see http://svn.planet-lab.org/wiki/ModuleTools"""
1010 'list' : "displays a list of available tags or branches",
1011 'version' : "check latest specfile and print out details",
1012 'diff' : "show difference between module (trunk or branch) and latest tag",
1013 'tag' : """increment taglevel in specfile, insert changelog in specfile,
1014 create new tag and and monitor its adoption in build/*-tags*.mk""",
1015 'branch' : """create a branch for this module, from the latest tag on the trunk,
1016 and change trunk's version number to reflect the new branch name;
1017 you can specify the new branch name by using module:branch""",
1018 'sync' : """create a tag from the module
1019 this is a last resort option, mostly for repairs""",
1020 'changelog' : """extract changelog between build tags
1021 expected arguments are a list of tags""",
1024 silent_modes = ['list']
1025 release_modes = ['changelog']
1028 def optparse_list (option, opt, value, parser):
1030 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
1032 setattr(parser.values,option.dest,value.split())
1037 for function in Main.modes.keys():
1038 if sys.argv[0].find(function) >= 0:
1042 print "Unsupported command",sys.argv[0]
1043 print "Supported commands:" + " ".join(Main.modes.keys())
1046 if mode not in Main.release_modes:
1047 usage = Main.module_usage
1048 usage += Main.common_usage
1049 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1051 usage = Main.release_usage
1052 usage += Main.common_usage
1054 parser=OptionParser(usage=usage)
1056 if mode == "tag" or mode == 'branch':
1057 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1058 help="set new version and reset taglevel to 0")
1060 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1061 help="do not update changelog section in specfile when tagging")
1062 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1063 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1064 if mode == "tag" or mode == "sync" :
1065 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1066 help="specify editor")
1068 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1069 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1070 help="dry run - shell commands are only displayed")
1071 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1072 default=[], nargs=1,type="string",
1073 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1074 -- can be set multiple times, or use quotes""")
1076 parser.add_option("-w","--workdir", action="store", dest="workdir",
1077 default="%s/%s"%(os.getenv("HOME"),"modules"),
1078 help="""name for dedicated working dir - defaults to ~/modules
1079 ** THIS MUST NOT ** be your usual working directory""")
1080 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1081 help="skip safety checks, such as svn updates -- use with care")
1083 # default verbosity depending on function - temp
1084 verbose_modes= ['tag', 'sync', 'branch']
1086 if mode not in verbose_modes:
1087 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1088 help="run in verbose mode")
1090 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1091 help="run in quiet (non-verbose) mode")
1092 (options, args) = parser.parse_args()
1094 if not hasattr(options,'dry_run'):
1095 options.dry_run=False
1096 if not hasattr(options,'www'):
1104 Module.init_homedir(options)
1106 modules=[ Module(modname,options) for modname in args ]
1107 for module in modules:
1108 if len(args)>1 and mode not in Main.silent_modes:
1109 print '========================================',module.friendly_name()
1110 # call the method called do_<mode>
1111 method=Module.__dict__["do_%s"%mode]
1116 traceback.print_exc()
1117 print 'Skipping module %s: '%modname,e
1119 ####################
1120 if __name__ == "__main__" :
1123 except KeyboardInterrupt: