9 from optparse import OptionParser
11 # HARDCODED NAME CHANGES
13 # Moving to git we decided to rename some of the repositories. Here is
14 # a map of name changes applied in git repositories.
15 RENAMED_SVN_MODULES = {
19 def svn_to_git_name(module):
20 if RENAMED_SVN_MODULES.has_key(module):
21 return RENAMED_SVN_MODULES[module]
24 def git_to_svn_name(module):
25 for key in RENAMED_SVN_MODULES:
26 if module == RENAMED_SVN_MODULES[key]:
31 # e.g. other_choices = [ ('d','iff') , ('g','uess') ] - lowercase
32 def prompt (question,default=True,other_choices=[],allow_outside=False):
33 if not isinstance (other_choices,list):
34 other_choices = [ other_choices ]
35 chars = [ c for (c,rest) in other_choices ]
39 if default is True: choices.append('[y]')
40 else : choices.append('y')
42 if default is False: choices.append('[n]')
43 else : choices.append('n')
45 for (char,choice) in other_choices:
47 choices.append("["+char+"]"+choice)
49 choices.append("<"+char+">"+choice)
51 answer=raw_input(question + " " + "/".join(choices) + " ? ")
54 answer=answer[0].lower()
56 if 'y' in chars: return 'y'
59 if 'n' in chars: return 'n'
62 for (char,choice) in other_choices:
67 return prompt(question,default,other_choices)
73 editor = os.environ['EDITOR']
81 def print_fold (line):
82 while len(line) >= fold_length:
83 print line[:fold_length],'\\'
84 line=line[fold_length:]
88 def __init__ (self,command,options):
91 self.tmp="/tmp/command-%d"%os.getpid()
94 if self.options.dry_run:
95 print 'dry_run',self.command
97 if self.options.verbose and self.options.mode not in Main.silent_modes:
98 print '+',self.command
100 return os.system(self.command)
102 def run_silent (self):
103 if self.options.dry_run:
104 print 'dry_run',self.command
106 if self.options.verbose:
107 print '+',self.command,' .. ',
109 retcod=os.system(self.command + " &> " + self.tmp)
111 print "FAILED ! -- out+err below (command was %s)"%self.command
112 os.system("cat " + self.tmp)
113 print "FAILED ! -- end of quoted output"
114 elif self.options.verbose:
120 if self.run_silent() !=0:
121 raise Exception,"Command %s failed"%self.command
123 # returns stdout, like bash's $(mycommand)
124 def output_of (self,with_stderr=False):
125 if self.options.dry_run:
126 print 'dry_run',self.command
127 return 'dry_run output'
128 tmp="/tmp/status-%d"%os.getpid()
129 if self.options.debug:
130 print '+',self.command,' .. ',
139 result=file(tmp).read()
141 if self.options.debug:
149 def __init__(self, path, options):
151 self.options = options
154 return os.path.basename(self.path)
157 out = Command("svn info %s" % self.path, self.options).output_of()
158 for line in out.split('\n'):
159 if line.startswith("URL:"):
160 return line.split()[1].strip()
163 out = Command("svn info %s" % self.path, self.options).output_of()
164 for line in out.split('\n'):
165 if line.startswith("Repository Root:"):
166 repo_root = line.split()[2].strip()
167 return "%s/svn/%s" (repo_root, self.name)
170 def checkout(cls, remote, local, options, recursive=False):
172 svncommand = "svn co %s %s" % (remote, local)
174 svncommand = "svn co -N %s %s" % (remote, local)
175 Command("rm -rf %s" % local, options).run_silent()
176 Command(svncommand, options).run_fatal()
178 return SvnRepository(local, options)
181 def remote_exists(cls, remote):
182 return os.system("svn list %s &> /dev/null" % remote) == 0
184 def tag_exists(self, tagname):
185 url = "%s/tags/%s" % (self.repo_root(), tagname)
186 return SvnRepository.remote_exists(url)
188 def update(self, subdir="", recursive=True):
189 path = os.path.join(self.path, subdir)
191 svncommand = "svn up %s" % path
193 svncommand = "svn up -N %s" % path
194 Command(svncommand, self.options).run_fatal()
196 def commit(self, logfile):
197 # add all new files to the repository
198 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
199 self.path, self.options).run_silent()
200 Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
202 def to_branch(self, branch):
203 remote = "%s/branches/%s" % (self.repo_root(), branch)
204 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
206 def to_tag(self, tag):
207 remote = "%s/tags/%s" % (self.repo_root(), branch)
208 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
210 def tag(self, tagname, logfile):
211 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
212 self_url = self.url()
213 Command("svn copy -F %s %s %s" % (logfile, self_url, tag_url), self.options).run_fatal()
216 return Command("svn diff %s" % self.path, self.options).output_of(True)
218 def diff_with_tag(self, tagname):
219 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
220 return Command("svn diff %s %s" % (tag_url, self.url()),
221 self.options).output_of(True)
224 Command("svn revert %s -R" % self.path, self.options).run_fatal()
225 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
226 self.path, self.options).run_silent()
229 command="svn status %s" % self.path
230 return len(Command(command,self.options).output_of(True)) == 0
233 return os.path.exists(os.path.join(self.path, ".svn"))
239 def __init__(self, path, options):
241 self.options = options
244 return os.path.basename(self.path)
250 c = Command("git remote show origin", self.options)
251 out = self.__run_in_repo(c.output_of)
252 for line in out.split('\n'):
253 if line.strip().startswith("Fetch URL:"):
254 repo = line.split()[2]
257 def checkout(cls, remote, local, options, depth=1):
258 Command("rm -rf %s" % local, options).run_silent()
259 Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
260 return GitRepository(local, options)
263 def remote_exists(cls, remote):
264 return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
266 def tag_exists(self, tagname):
267 command = 'git tag -l | grep "^%s$"' % tagname
268 c = Command(command, self.options)
269 out = self.__run_in_repo(c.output_of, with_stderr=True)
272 def __run_in_repo(self, fun, *args, **kwargs):
275 ret = fun(*args, **kwargs)
279 def __run_command_in_repo(self, command, ignore_errors=False):
280 c = Command(command, self.options)
282 return self.__run_in_repo(c.output_of)
284 return self.__run_in_repo(c.run_fatal)
286 def update(self, subdir=None, recursive=None):
287 return self.__run_command_in_repo("git pull")
289 def to_branch(self, branch, remote=True):
291 branch = "origin/%s" % branch
292 return self.__run_command_in_repo("git checkout %s" % branch)
294 def to_tag(self, tag):
295 return self.__run_command_in_repo("git checkout %s" % tag)
297 def tag(self, tagname, logfile):
298 self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
302 c = Command("git diff", self.options)
303 return self.__run_in_repo(c.output_of, with_stderr=True)
305 def diff_with_tag(self, tagname):
306 c = Command("git diff %s" % tagname, self.options)
307 return self.__run_in_repo(c.output_of, with_stderr=True)
309 def commit(self, logfile):
310 self.__run_command_in_repo("git add -A", ignore_errors=True)
311 self.__run_command_in_repo("git commit -F %s" % logfile, ignore_errors=True)
312 self.__run_command_in_repo("git push")
313 self.__run_command_in_repo("git push --tags")
316 self.__run_command_in_repo("git --no-pager reset --hard")
317 self.__run_command_in_repo("git --no-pager clean -f")
322 s="nothing to commit (working directory clean)"
323 return Command(command, self.options).output_of(True).find(s) >= 0
324 return self.__run_in_repo(check_commit)
327 return os.path.exists(os.path.join(self.path, ".git"))
331 """ Generic repository """
332 supported_repo_types = [SvnRepository, GitRepository]
334 def __init__(self, path, options):
336 self.options = options
337 for repo in self.supported_repo_types:
338 self.repo = repo(self.path, self.options)
339 if self.repo.is_valid():
343 def has_moved_to_git(cls, module, svnpath):
344 module = git_to_svn_name(module)
345 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
348 def remote_exists(cls, remote):
349 for repo in Repository.supported_repo_types:
350 if repo.remote_exists(remote):
354 def __getattr__(self, attr):
355 return getattr(self.repo, attr)
359 # support for tagged module is minimal, and is for the Build class only
362 svn_magic_line="--This line, and those below, will be ignored--"
363 setting_tag_format = "Setting tag %s"
365 redirectors=[ # ('module_name_varname','name'),
366 ('module_version_varname','version'),
367 ('module_taglevel_varname','taglevel'), ]
369 # where to store user's config
370 config_storage="CONFIG"
375 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
376 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
377 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
378 ("build", "Enter the name of your build module","build"),
379 ('username',"Enter your firstname and lastname for changelogs",""),
380 ("email","Enter your email address for changelogs",""),
384 def prompt_config (cls):
385 for (key,message,default) in cls.configKeys:
387 while not cls.config[key]:
388 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
391 # for parsing module spec name:branch
392 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
393 # special form for tagged module - for Build
394 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
396 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
398 def __init__ (self,module_spec,options):
400 attempt=Module.matcher_branch_spec.match(module_spec)
402 self.name=attempt.group('name')
403 self.branch=attempt.group('branch')
405 attempt=Module.matcher_tag_spec.match(module_spec)
407 self.name=attempt.group('name')
408 self.tagname=attempt.group('tagname')
410 self.name=module_spec
412 # when available prefer to use git module name internally
413 self.name = svn_to_git_name(self.name)
416 self.module_dir="%s/%s"%(options.workdir,self.name)
417 self.repository = None
420 def run (self,command):
421 return Command(command,self.options).run()
422 def run_fatal (self,command):
423 return Command(command,self.options).run_fatal()
424 def run_prompt (self,message,fun, *args):
425 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
426 if not self.options.verbose:
428 choice=prompt(message,True,('s','how'))
432 elif choice is False:
433 print 'About to run function:', fun_msg
435 question=message+" - want to run function: " + fun_msg
436 if prompt(question,True):
439 def friendly_name (self):
440 if hasattr(self,'branch'):
441 return "%s:%s"%(self.name,self.branch)
442 elif hasattr(self,'tagname'):
443 return "%s@%s"%(self.name,self.tagname)
448 def git_remote_dir (cls, name):
449 return "%s:/git/%s.git" % (cls.config['gitserver'], name)
452 def svn_remote_dir (cls, name):
453 name = git_to_svn_name(name)
454 svn = cls.config['svnpath']
455 if svn.endswith('/'):
456 return "%s%s" % (svn, name)
457 return "%s/%s" % (svn, name)
459 def svn_selected_remote(self):
460 svn_name = git_to_svn_name(self.name)
461 remote = self.svn_remote_dir(svn_name)
462 if hasattr(self,'branch'):
463 remote = "%s/branches/%s" % (remote, self.branch)
464 elif hasattr(self,'tagname'):
465 remote = "%s/tags/%s" % (remote, self.tagname)
467 remote = "%s/trunk" % remote
472 def init_homedir (cls, options):
473 if options.verbose and options.mode not in Main.silent_modes:
474 print 'Checking for', options.workdir
475 storage="%s/%s"%(options.workdir, cls.config_storage)
476 # sanity check. Either the topdir exists AND we have a config/storage
477 # or topdir does not exist and we create it
478 # to avoid people use their own daily svn repo
479 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
480 print """The directory %s exists and has no CONFIG file
481 If this is your regular working directory, please provide another one as the
482 module-* commands need a fresh working dir. Make sure that you do not use
483 that for other purposes than tagging""" % options.workdir
485 if not os.path.isdir (options.workdir):
486 print "Cannot find",options.workdir,"let's create it"
488 print "Checking ...",
489 remote = cls.git_remote_dir(cls.config['build'])
490 local = os.path.join(options.workdir, cls.config['build'])
491 GitRepository.checkout(remote, local, options, depth=1)
496 for (key,message,default) in Module.configKeys:
497 f.write("%s=%s\n"%(key,Module.config[key]))
500 print 'Stored',storage
501 Command("cat %s"%storage,options).run()
505 for line in f.readlines():
506 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
507 Module.config[key]=value
510 build_dir = os.path.join(options.workdir, cls.config['build'])
511 build = Repository(build_dir, options)
512 if not build.is_clean():
513 print "build module needs a revert"
518 if options.verbose and options.mode not in Main.silent_modes:
519 print '******** Using config'
520 for (key,message,default) in Module.configKeys:
521 print '\t',key,'=',Module.config[key]
523 def init_module_dir (self):
524 if self.options.verbose:
525 print 'Checking for',self.module_dir
527 if not os.path.isdir (self.module_dir):
528 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
529 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
533 remote = self.svn_selected_remote()
534 self.repository = SvnRepository.checkout(remote,
536 self.options, recursive=False)
538 self.repository = Repository(self.module_dir, self.options)
539 if self.repository.type == "svn":
540 # check if module has moved to git
541 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
542 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
543 self.init_module_dir()
544 # check if we have the required branch/tag
545 if self.repository.url() != self.svn_selected_remote():
546 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
547 self.init_module_dir()
549 elif self.repository.type == "git":
550 if hasattr(self,'branch'):
551 self.repository.to_branch(self.branch)
552 elif hasattr(self,'tagname'):
553 self.repository.to_tag(self.tagname)
556 raise Exception, 'Cannot find %s - check module name'%self.module_dir
559 def revert_module_dir (self):
560 if self.options.fast_checks:
561 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
563 if self.options.verbose:
564 print 'Checking whether', self.module_dir, 'needs being reverted'
566 if not self.repository.is_clean():
567 self.repository.revert()
569 def update_module_dir (self):
570 if self.options.fast_checks:
571 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
573 if self.options.verbose:
574 print 'Updating', self.module_dir
575 self.repository.update()
577 def main_specname (self):
578 attempt="%s/%s.spec"%(self.module_dir,self.name)
579 if os.path.isfile (attempt):
581 pattern1="%s/*.spec"%self.module_dir
582 level1=glob(pattern1)
585 pattern2="%s/*/*.spec"%self.module_dir
586 level2=glob(pattern2)
590 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
592 def all_specnames (self):
593 level1=glob("%s/*.spec" % self.module_dir)
594 if level1: return level1
595 level2=glob("%s/*/*.spec" % self.module_dir)
598 def parse_spec (self, specfile, varnames):
599 if self.options.verbose:
600 print 'Parsing',specfile,
606 for line in f.readlines():
607 attempt=Module.matcher_rpm_define.match(line)
609 (define,var,value)=attempt.groups()
613 if self.options.debug:
614 print 'found',len(result),'keys'
615 for (k,v) in result.iteritems():
619 # stores in self.module_name_varname the rpm variable to be used for the module's name
620 # and the list of these names in self.varnames
621 def spec_dict (self):
622 specfile=self.main_specname()
623 redirector_keys = [ varname for (varname,default) in Module.redirectors]
624 redirect_dict = self.parse_spec(specfile,redirector_keys)
625 if self.options.debug:
626 print '1st pass parsing done, redirect_dict=',redirect_dict
628 for (varname,default) in Module.redirectors:
629 if redirect_dict.has_key(varname):
630 setattr(self,varname,redirect_dict[varname])
631 varnames += [redirect_dict[varname]]
633 setattr(self,varname,default)
634 varnames += [ default ]
635 self.varnames = varnames
636 result = self.parse_spec (specfile,self.varnames)
637 if self.options.debug:
638 print '2st pass parsing done, varnames=',varnames,'result=',result
641 def patch_spec_var (self, patch_dict,define_missing=False):
642 for specfile in self.all_specnames():
643 # record the keys that were changed
644 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
645 newspecfile=specfile+".new"
646 if self.options.verbose:
647 print 'Patching',specfile,'for',patch_dict.keys()
649 new=open(newspecfile,"w")
651 for line in spec.readlines():
652 attempt=Module.matcher_rpm_define.match(line)
654 (define,var,value)=attempt.groups()
655 if var in patch_dict.keys():
656 if self.options.debug:
657 print 'rewriting %s as %s'%(var,patch_dict[var])
658 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
663 for (key,was_changed) in changed.iteritems():
665 if self.options.debug:
666 print 'rewriting missing %s as %s'%(key,patch_dict[key])
667 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
670 os.rename(newspecfile,specfile)
672 # returns all lines until the magic line
673 def unignored_lines (self, logfile):
675 white_line_matcher = re.compile("\A\s*\Z")
676 for logline in file(logfile).readlines():
677 if logline.strip() == Module.svn_magic_line:
679 elif white_line_matcher.match(logline):
682 result.append(logline.strip()+'\n')
685 # creates a copy of the input with only the unignored lines
686 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
688 f.write(self.setting_tag_format%new_tag_name + '\n')
689 for line in self.unignored_lines(filein):
693 def insert_changelog (self, logfile, oldtag, newtag):
694 for specfile in self.all_specnames():
695 newspecfile=specfile+".new"
696 if self.options.verbose:
697 print 'Inserting changelog from %s into %s'%(logfile,specfile)
699 new=open(newspecfile,"w")
700 for line in spec.readlines():
702 if re.compile('%changelog').match(line):
703 dateformat="* %a %b %d %Y"
704 datepart=time.strftime(dateformat)
705 logpart="%s <%s> - %s"%(Module.config['username'],
706 Module.config['email'],
708 new.write(datepart+" "+logpart+"\n")
709 for logline in self.unignored_lines(logfile):
710 new.write("- " + logline)
714 os.rename(newspecfile,specfile)
716 def show_dict (self, spec_dict):
717 if self.options.verbose:
718 for (k,v) in spec_dict.iteritems():
721 def last_tag (self, spec_dict):
723 return "%s-%s" % (spec_dict[self.module_version_varname],
724 spec_dict[self.module_taglevel_varname])
726 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
728 def tag_name (self, spec_dict, old_svn_name=False):
729 base_tag_name = self.name
731 base_tag_name = git_to_svn_name(self.name)
732 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
735 ##############################
736 # using fine_grain means replacing only those instances that currently refer to this tag
737 # otherwise, <module>-SVNPATH is replaced unconditionnally
738 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
739 newtagsfile=tagsfile+".new"
741 new=open(newtagsfile,"w")
744 # fine-grain : replace those lines that refer to oldname
746 if self.options.verbose:
747 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
748 matcher=re.compile("^(.*)%s(.*)"%oldname)
749 for line in tags.readlines():
750 if not matcher.match(line):
753 (begin,end)=matcher.match(line).groups()
754 new.write(begin+newname+end+"\n")
756 # brute-force : change uncommented lines that define <module>-SVNPATH
758 if self.options.verbose:
759 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
760 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
762 matcher_module=re.compile(pattern)
763 for line in tags.readlines():
764 attempt=matcher_module.match(line)
766 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
767 if self.options.verbose:
769 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
770 new.write(replacement)
776 os.rename(newtagsfile,tagsfile)
777 if self.options.verbose: print "%d changes"%matches
780 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
781 if self.options.verbose:
782 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
784 found_tagname = tagname
785 found = self.repository.tag_exists(tagname)
786 if not found and old_svn_tag_name:
787 if self.options.verbose:
789 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
790 found = self.repository.tag_exists(old_svn_tag_name)
792 found_tagname = old_svn_tag_name
794 if (found and need_it) or (not found and not need_it):
799 raise Exception, "tag (%s) is already there" % tagname
801 raise Exception, "can not find required tag (%s)" % tagname
807 self.init_module_dir()
808 self.revert_module_dir()
809 self.update_module_dir()
811 spec_dict = self.spec_dict()
812 self.show_dict(spec_dict)
815 old_tag_name = self.tag_name(spec_dict)
816 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
818 if (self.options.new_version):
819 # new version set on command line
820 spec_dict[self.module_version_varname] = self.options.new_version
821 spec_dict[self.module_taglevel_varname] = 0
824 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
825 spec_dict[self.module_taglevel_varname] = new_taglevel
827 new_tag_name = self.tag_name(spec_dict)
830 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
831 new_tag_name = self.check_tag(new_tag_name, need_it=False)
834 diff_output = self.repository.diff_with_tag(old_tag_name)
835 if len(diff_output) == 0:
836 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
839 # side effect in trunk's specfile
840 self.patch_spec_var(spec_dict)
842 # prepare changelog file
843 # we use the standard subversion magic string (see svn_magic_line)
844 # so we can provide useful information, such as version numbers and diff
846 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
847 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
848 setting_tag_line=Module.setting_tag_format%new_tag_name
849 file(changelog,"w").write("""
852 Please write a changelog for this new tag in the section above
853 """%(Module.svn_magic_line,setting_tag_line))
855 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
856 file(changelog,"a").write('DIFF=========\n' + diff_output)
858 if self.options.debug:
862 self.run("%s %s"%(self.options.editor,changelog))
863 # strip magic line in second file - looks like svn has changed its magic line with 1.6
864 # so we do the job ourselves
865 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
866 # insert changelog in spec
867 if self.options.changelog:
868 self.insert_changelog (changelog,old_tag_name,new_tag_name)
871 build_path = os.path.join(self.options.workdir,
872 Module.config['build'])
873 build = Repository(build_path, self.options)
874 if self.options.build_branch:
875 build.to_branch(self.options.build_branch)
876 if not build.is_clean():
879 tagsfiles=glob(build.path+"/*-tags*.mk")
880 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
884 for tagsfile in tagsfiles:
885 status=tagsdict[tagsfile]
886 basename=os.path.basename(tagsfile)
887 print ".................... Dealing with %s"%basename
888 while tagsdict[tagsfile] == 'todo' :
889 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
890 [ ('y','es'), ('n', 'ext'), ('f','orce'),
891 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
894 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
896 print 'Done with %s'%os.path.basename(tagsfile)
897 tagsdict[tagsfile]='done'
899 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
901 self.run("svn diff %s"%tagsfile)
903 self.run("svn revert %s"%tagsfile)
905 self.run("cat %s"%tagsfile)
908 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
909 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
910 d: show current diff for this tag file
911 r: revert that tag file
912 c: cat the current tag file
913 n: move to next file"""%locals()
915 if prompt("Want to review changes on tags files",False):
916 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
921 def diff_all_changes():
923 print self.repository.diff()
925 def commit_all_changes(log):
926 self.repository.commit(log)
929 self.run_prompt("Review module and build", diff_all_changes)
930 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
931 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
933 if self.options.debug:
934 print 'Preserving',changelog,'and stripped',changelog_svn
937 os.unlink(changelog_svn)
940 ##############################
943 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
945 module-tools : a set of tools to manage subversion tags and specfile
946 requires the specfile to either
947 * define *version* and *taglevel*
949 * define redirection variables module_version_varname / module_taglevel_varname
951 by default, the trunk of modules is taken into account
952 in this case, just mention the module name as <module_desc>
954 if you wish to work on a branch rather than on the trunk,
955 you can use something like e.g. Mom:2.1 as <module_desc>
957 release_usage="""Usage: %prog [options] tag1 .. tagn
958 Extract release notes from the changes in specfiles between several build tags, latest first
960 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
961 You can refer to a (build) branch by prepending a colon, like in
962 release-changelog :4.2 4.2-rc25
963 You can refer to the build trunk by just mentioning 'trunk', e.g.
964 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
966 common_usage="""More help:
967 see http://svn.planet-lab.org/wiki/ModuleTools"""
970 'list' : "displays a list of available tags or branches",
971 'version' : "check latest specfile and print out details",
972 'diff' : "show difference between module (trunk or branch) and latest tag",
973 'tag' : """increment taglevel in specfile, insert changelog in specfile,
974 create new tag and and monitor its adoption in build/*-tags*.mk""",
975 'branch' : """create a branch for this module, from the latest tag on the trunk,
976 and change trunk's version number to reflect the new branch name;
977 you can specify the new branch name by using module:branch""",
978 'sync' : """create a tag from the module
979 this is a last resort option, mostly for repairs""",
980 'changelog' : """extract changelog between build tags
981 expected arguments are a list of tags""",
984 silent_modes = ['list']
985 release_modes = ['changelog']
988 def optparse_list (option, opt, value, parser):
990 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
992 setattr(parser.values,option.dest,value.split())
997 for function in Main.modes.keys():
998 if sys.argv[0].find(function) >= 0:
1002 print "Unsupported command",sys.argv[0]
1003 print "Supported commands:" + " ".join(Main.modes.keys())
1006 if mode not in Main.release_modes:
1007 usage = Main.module_usage
1008 usage += Main.common_usage
1009 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1011 usage = Main.release_usage
1012 usage += Main.common_usage
1014 parser=OptionParser(usage=usage,version=subversion_id)
1016 if mode == "tag" or mode == 'branch':
1017 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1018 help="set new version and reset taglevel to 0")
1020 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1021 help="do not update changelog section in specfile when tagging")
1022 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1023 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1024 if mode == "tag" or mode == "sync" :
1025 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1026 help="specify editor")
1028 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1029 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1030 help="dry run - shell commands are only displayed")
1031 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1032 default=[], nargs=1,type="string",
1033 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1034 -- can be set multiple times, or use quotes""")
1036 parser.add_option("-w","--workdir", action="store", dest="workdir",
1037 default="%s/%s"%(os.getenv("HOME"),"modules"),
1038 help="""name for dedicated working dir - defaults to ~/modules
1039 ** THIS MUST NOT ** be your usual working directory""")
1040 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1041 help="skip safety checks, such as svn updates -- use with care")
1043 # default verbosity depending on function - temp
1044 verbose_modes= ['tag', 'sync', 'branch']
1046 if mode not in verbose_modes:
1047 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1048 help="run in verbose mode")
1050 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1051 help="run in quiet (non-verbose) mode")
1052 (options, args) = parser.parse_args()
1054 if not hasattr(options,'dry_run'):
1055 options.dry_run=False
1056 if not hasattr(options,'www'):
1064 Module.init_homedir(options)
1066 modules=[ Module(modname,options) for modname in args ]
1067 for module in modules:
1068 if len(args)>1 and mode not in Main.silent_modes:
1069 print '========================================',module.friendly_name()
1070 # call the method called do_<mode>
1071 method=Module.__dict__["do_%s"%mode]
1076 traceback.print_exc()
1077 print 'Skipping module %s: '%modname,e
1079 ####################
1080 if __name__ == "__main__" :
1083 except KeyboardInterrupt: