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):
280 c = Command(command, self.options)
281 return self.__run_in_repo(c.run_fatal)
283 def update(self, subdir=None, recursive=None):
284 return self.__run_command_in_repo("git pull")
286 def to_branch(self, branch, remote=True):
288 branch = "origin/%s" % branch
289 return self.__run_command_in_repo("git checkout %s" % branch)
291 def to_tag(self, tag):
292 return self.__run_command_in_repo("git checkout %s" % tag)
294 def tag(self, tagname, logfile):
295 self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
299 c = Command("git diff", self.options)
300 return self.__run_in_repo(c.output_of, with_stderr=True)
302 def diff_with_tag(self, tagname):
303 c = Command("git diff %s" % tagname, self.options)
304 return self.__run_in_repo(c.output_of, with_stderr=True)
306 def commit(self, logfile):
307 self.__run_command_in_repo("git add -A")
308 self.__run_command_in_repo("git commit -F %s" % logfile)
309 self.__run_command_in_repo("git push --tags")
312 self.__run_command_in_repo("git --no-pager reset --hard")
313 self.__run_command_in_repo("git --no-pager clean -f")
318 s="nothing to commit (working directory clean)"
319 return Command(command, self.options).output_of(True).find(s) >= 0
320 return self.__run_in_repo(check_commit)
323 return os.path.exists(os.path.join(self.path, ".git"))
327 """ Generic repository """
328 supported_repo_types = [SvnRepository, GitRepository]
330 def __init__(self, path, options):
332 self.options = options
333 for repo in self.supported_repo_types:
334 self.repo = repo(self.path, self.options)
335 if self.repo.is_valid():
339 def has_moved_to_git(cls, module, svnpath):
340 module = git_to_svn_name(module)
341 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
344 def remote_exists(cls, remote):
345 for repo in Repository.supported_repo_types:
346 if repo.remote_exists(remote):
350 def __getattr__(self, attr):
351 return getattr(self.repo, attr)
355 # support for tagged module is minimal, and is for the Build class only
358 svn_magic_line="--This line, and those below, will be ignored--"
359 setting_tag_format = "Setting tag %s"
361 redirectors=[ # ('module_name_varname','name'),
362 ('module_version_varname','version'),
363 ('module_taglevel_varname','taglevel'), ]
365 # where to store user's config
366 config_storage="CONFIG"
371 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
372 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
373 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
374 ("build", "Enter the name of your build module","build"),
375 ('username',"Enter your firstname and lastname for changelogs",""),
376 ("email","Enter your email address for changelogs",""),
380 def prompt_config (cls):
381 for (key,message,default) in cls.configKeys:
383 while not cls.config[key]:
384 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
387 # for parsing module spec name:branch
388 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
389 # special form for tagged module - for Build
390 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
392 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
394 def __init__ (self,module_spec,options):
396 attempt=Module.matcher_branch_spec.match(module_spec)
398 self.name=attempt.group('name')
399 self.branch=attempt.group('branch')
401 attempt=Module.matcher_tag_spec.match(module_spec)
403 self.name=attempt.group('name')
404 self.tagname=attempt.group('tagname')
406 self.name=module_spec
408 # when available prefer to use git module name internally
409 self.name = svn_to_git_name(self.name)
412 self.module_dir="%s/%s"%(options.workdir,self.name)
413 self.repository = None
416 def run (self,command):
417 return Command(command,self.options).run()
418 def run_fatal (self,command):
419 return Command(command,self.options).run_fatal()
420 def run_prompt (self,message,fun, *args):
421 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
422 if not self.options.verbose:
424 choice=prompt(message,True,('s','how'))
428 elif choice is False:
429 print 'About to run function:', fun_msg
431 question=message+" - want to run function: " + fun_msg
432 if prompt(question,True):
435 def friendly_name (self):
436 if hasattr(self,'branch'):
437 return "%s:%s"%(self.name,self.branch)
438 elif hasattr(self,'tagname'):
439 return "%s@%s"%(self.name,self.tagname)
444 def git_remote_dir (cls, name):
445 return "%s:/git/%s.git" % (cls.config['gitserver'], name)
448 def svn_remote_dir (cls, name):
449 name = git_to_svn_name(name)
450 svn = cls.config['svnpath']
451 if svn.endswith('/'):
452 return "%s%s" % (svn, name)
453 return "%s/%s" % (svn, name)
455 def svn_selected_remote(self):
456 svn_name = git_to_svn_name(self.name)
457 remote = self.svn_remote_dir(svn_name)
458 if hasattr(self,'branch'):
459 remote = "%s/branches/%s" % (remote, self.branch)
460 elif hasattr(self,'tagname'):
461 remote = "%s/tags/%s" % (remote, self.tagname)
463 remote = "%s/trunk" % remote
468 def init_homedir (cls, options):
469 if options.verbose and options.mode not in Main.silent_modes:
470 print 'Checking for', options.workdir
471 storage="%s/%s"%(options.workdir, cls.config_storage)
472 # sanity check. Either the topdir exists AND we have a config/storage
473 # or topdir does not exist and we create it
474 # to avoid people use their own daily svn repo
475 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
476 print """The directory %s exists and has no CONFIG file
477 If this is your regular working directory, please provide another one as the
478 module-* commands need a fresh working dir. Make sure that you do not use
479 that for other purposes than tagging""" % options.workdir
481 if not os.path.isdir (options.workdir):
482 print "Cannot find",options.workdir,"let's create it"
484 print "Checking ...",
485 remote = cls.git_remote_dir(cls.config['build'])
486 local = os.path.join(options.workdir, cls.config['build'])
487 build = GitRepository.checkout(remote, local, options, depth=1)
488 if not build.is_clean():
494 for (key,message,default) in Module.configKeys:
495 f.write("%s=%s\n"%(key,Module.config[key]))
498 print 'Stored',storage
499 Command("cat %s"%storage,options).run()
503 for line in f.readlines():
504 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
505 Module.config[key]=value
508 build_dir = os.path.join(options.workdir, cls.config['build'])
509 build = Repository(build_dir, options)
510 if not build.is_clean():
511 print "build module needs a revert"
515 if options.verbose and options.mode not in Main.silent_modes:
516 print '******** Using config'
517 for (key,message,default) in Module.configKeys:
518 print '\t',key,'=',Module.config[key]
520 def init_module_dir (self):
521 if self.options.verbose:
522 print 'Checking for',self.module_dir
524 if not os.path.isdir (self.module_dir):
525 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
526 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
530 remote = self.svn_selected_remote()
531 self.repository = SvnRepository.checkout(remote,
533 self.options, recursive=False)
535 self.repository = Repository(self.module_dir, self.options)
536 if self.repository.type == "svn":
537 # check if module has moved to git
538 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
539 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
540 self.init_module_dir()
541 # check if we have the required branch/tag
542 if self.repository.url() != self.svn_selected_remote():
543 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
544 self.init_module_dir()
546 elif self.repository.type == "git":
547 if hasattr(self,'branch'):
548 self.repository.to_branch(self.branch)
549 elif hasattr(self,'tagname'):
550 self.repository.to_tag(self.tagname)
553 raise Exception, 'Cannot find %s - check module name'%self.module_dir
556 def revert_module_dir (self):
557 if self.options.fast_checks:
558 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
560 if self.options.verbose:
561 print 'Checking whether', self.module_dir, 'needs being reverted'
563 if not self.repository.is_clean():
564 self.repository.revert()
566 def update_module_dir (self):
567 if self.options.fast_checks:
568 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
570 if self.options.verbose:
571 print 'Updating', self.module_dir
572 self.repository.update()
574 def main_specname (self):
575 attempt="%s/%s.spec"%(self.module_dir,self.name)
576 if os.path.isfile (attempt):
578 pattern1="%s/*.spec"%self.module_dir
579 level1=glob(pattern1)
582 pattern2="%s/*/*.spec"%self.module_dir
583 level2=glob(pattern2)
587 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
589 def all_specnames (self):
590 level1=glob("%s/*.spec" % self.module_dir)
591 if level1: return level1
592 level2=glob("%s/*/*.spec" % self.module_dir)
595 def parse_spec (self, specfile, varnames):
596 if self.options.verbose:
597 print 'Parsing',specfile,
603 for line in f.readlines():
604 attempt=Module.matcher_rpm_define.match(line)
606 (define,var,value)=attempt.groups()
610 if self.options.debug:
611 print 'found',len(result),'keys'
612 for (k,v) in result.iteritems():
616 # stores in self.module_name_varname the rpm variable to be used for the module's name
617 # and the list of these names in self.varnames
618 def spec_dict (self):
619 specfile=self.main_specname()
620 redirector_keys = [ varname for (varname,default) in Module.redirectors]
621 redirect_dict = self.parse_spec(specfile,redirector_keys)
622 if self.options.debug:
623 print '1st pass parsing done, redirect_dict=',redirect_dict
625 for (varname,default) in Module.redirectors:
626 if redirect_dict.has_key(varname):
627 setattr(self,varname,redirect_dict[varname])
628 varnames += [redirect_dict[varname]]
630 setattr(self,varname,default)
631 varnames += [ default ]
632 self.varnames = varnames
633 result = self.parse_spec (specfile,self.varnames)
634 if self.options.debug:
635 print '2st pass parsing done, varnames=',varnames,'result=',result
638 def patch_spec_var (self, patch_dict,define_missing=False):
639 for specfile in self.all_specnames():
640 # record the keys that were changed
641 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
642 newspecfile=specfile+".new"
643 if self.options.verbose:
644 print 'Patching',specfile,'for',patch_dict.keys()
646 new=open(newspecfile,"w")
648 for line in spec.readlines():
649 attempt=Module.matcher_rpm_define.match(line)
651 (define,var,value)=attempt.groups()
652 if var in patch_dict.keys():
653 if self.options.debug:
654 print 'rewriting %s as %s'%(var,patch_dict[var])
655 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
660 for (key,was_changed) in changed.iteritems():
662 if self.options.debug:
663 print 'rewriting missing %s as %s'%(key,patch_dict[key])
664 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
667 os.rename(newspecfile,specfile)
669 # returns all lines until the magic line
670 def unignored_lines (self, logfile):
672 white_line_matcher = re.compile("\A\s*\Z")
673 for logline in file(logfile).readlines():
674 if logline.strip() == Module.svn_magic_line:
676 elif white_line_matcher.match(logline):
679 result.append(logline.strip()+'\n')
682 # creates a copy of the input with only the unignored lines
683 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
685 f.write(self.setting_tag_format%new_tag_name + '\n')
686 for line in self.unignored_lines(filein):
690 def insert_changelog (self, logfile, oldtag, newtag):
691 for specfile in self.all_specnames():
692 newspecfile=specfile+".new"
693 if self.options.verbose:
694 print 'Inserting changelog from %s into %s'%(logfile,specfile)
696 new=open(newspecfile,"w")
697 for line in spec.readlines():
699 if re.compile('%changelog').match(line):
700 dateformat="* %a %b %d %Y"
701 datepart=time.strftime(dateformat)
702 logpart="%s <%s> - %s"%(Module.config['username'],
703 Module.config['email'],
705 new.write(datepart+" "+logpart+"\n")
706 for logline in self.unignored_lines(logfile):
707 new.write("- " + logline)
711 os.rename(newspecfile,specfile)
713 def show_dict (self, spec_dict):
714 if self.options.verbose:
715 for (k,v) in spec_dict.iteritems():
718 def last_tag (self, spec_dict):
720 return "%s-%s" % (spec_dict[self.module_version_varname],
721 spec_dict[self.module_taglevel_varname])
723 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
725 def tag_name (self, spec_dict, old_svn_name=False):
726 base_tag_name = self.name
728 base_tag_name = git_to_svn_name(self.name)
729 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
732 ##############################
733 # using fine_grain means replacing only those instances that currently refer to this tag
734 # otherwise, <module>-SVNPATH is replaced unconditionnally
735 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
736 newtagsfile=tagsfile+".new"
738 new=open(newtagsfile,"w")
741 # fine-grain : replace those lines that refer to oldname
743 if self.options.verbose:
744 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
745 matcher=re.compile("^(.*)%s(.*)"%oldname)
746 for line in tags.readlines():
747 if not matcher.match(line):
750 (begin,end)=matcher.match(line).groups()
751 new.write(begin+newname+end+"\n")
753 # brute-force : change uncommented lines that define <module>-SVNPATH
755 if self.options.verbose:
756 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
757 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
759 matcher_module=re.compile(pattern)
760 for line in tags.readlines():
761 attempt=matcher_module.match(line)
763 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
764 if self.options.verbose:
766 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
767 new.write(replacement)
773 os.rename(newtagsfile,tagsfile)
774 if self.options.verbose: print "%d changes"%matches
777 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
778 if self.options.verbose:
779 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
781 found_tagname = tagname
782 found = self.repository.tag_exists(tagname)
783 if not found and old_svn_tag_name:
784 if self.options.verbose:
786 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
787 found = self.repository.tag_exists(old_svn_tag_name)
789 found_tagname = old_svn_tag_name
791 if (found and need_it) or (not found and not need_it):
796 raise Exception, "tag (%s) is already there" % tagname
798 raise Exception, "can not find required tag (%s)" % tagname
804 self.init_module_dir()
805 self.revert_module_dir()
806 self.update_module_dir()
808 spec_dict = self.spec_dict()
809 self.show_dict(spec_dict)
812 old_tag_name = self.tag_name(spec_dict)
813 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
815 if (self.options.new_version):
816 # new version set on command line
817 spec_dict[self.module_version_varname] = self.options.new_version
818 spec_dict[self.module_taglevel_varname] = 0
821 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
822 spec_dict[self.module_taglevel_varname] = new_taglevel
824 new_tag_name = self.tag_name(spec_dict)
827 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
828 new_tag_name = self.check_tag(new_tag_name, need_it=False)
831 diff_output = self.repository.diff_with_tag(old_tag_name)
832 if len(diff_output) == 0:
833 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
836 # side effect in trunk's specfile
837 self.patch_spec_var(spec_dict)
839 # prepare changelog file
840 # we use the standard subversion magic string (see svn_magic_line)
841 # so we can provide useful information, such as version numbers and diff
843 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
844 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
845 setting_tag_line=Module.setting_tag_format%new_tag_name
846 file(changelog,"w").write("""
849 Please write a changelog for this new tag in the section above
850 """%(Module.svn_magic_line,setting_tag_line))
852 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
853 file(changelog,"a").write('DIFF=========\n' + diff_output)
855 if self.options.debug:
859 self.run("%s %s"%(self.options.editor,changelog))
860 # strip magic line in second file - looks like svn has changed its magic line with 1.6
861 # so we do the job ourselves
862 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
863 # insert changelog in spec
864 if self.options.changelog:
865 self.insert_changelog (changelog,old_tag_name,new_tag_name)
868 build_path = os.path.join(self.options.workdir,
869 Module.config['build'])
870 build = Repository(build_path, self.options)
871 if self.options.build_branch:
872 build.to_branch(self.options.build_branch)
873 if not build.is_clean():
876 tagsfiles=glob(build.path+"/*-tags*.mk")
877 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
881 for tagsfile in tagsfiles:
882 status=tagsdict[tagsfile]
883 basename=os.path.basename(tagsfile)
884 print ".................... Dealing with %s"%basename
885 while tagsdict[tagsfile] == 'todo' :
886 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
887 [ ('y','es'), ('n', 'ext'), ('f','orce'),
888 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
891 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
893 print 'Done with %s'%os.path.basename(tagsfile)
894 tagsdict[tagsfile]='done'
896 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
898 self.run("svn diff %s"%tagsfile)
900 self.run("svn revert %s"%tagsfile)
902 self.run("cat %s"%tagsfile)
905 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
906 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
907 d: show current diff for this tag file
908 r: revert that tag file
909 c: cat the current tag file
910 n: move to next file"""%locals()
912 if prompt("Want to review changes on tags files",False):
913 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
918 def diff_all_changes():
920 print self.repository.diff()
922 def commit_all_changes(log):
923 self.repository.commit(log)
926 self.run_prompt("Review module and build", diff_all_changes)
927 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
928 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
930 if self.options.debug:
931 print 'Preserving',changelog,'and stripped',changelog_svn
934 os.unlink(changelog_svn)
937 ##############################
940 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
942 module-tools : a set of tools to manage subversion tags and specfile
943 requires the specfile to either
944 * define *version* and *taglevel*
946 * define redirection variables module_version_varname / module_taglevel_varname
948 by default, the trunk of modules is taken into account
949 in this case, just mention the module name as <module_desc>
951 if you wish to work on a branch rather than on the trunk,
952 you can use something like e.g. Mom:2.1 as <module_desc>
954 release_usage="""Usage: %prog [options] tag1 .. tagn
955 Extract release notes from the changes in specfiles between several build tags, latest first
957 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
958 You can refer to a (build) branch by prepending a colon, like in
959 release-changelog :4.2 4.2-rc25
960 You can refer to the build trunk by just mentioning 'trunk', e.g.
961 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
963 common_usage="""More help:
964 see http://svn.planet-lab.org/wiki/ModuleTools"""
967 'list' : "displays a list of available tags or branches",
968 'version' : "check latest specfile and print out details",
969 'diff' : "show difference between module (trunk or branch) and latest tag",
970 'tag' : """increment taglevel in specfile, insert changelog in specfile,
971 create new tag and and monitor its adoption in build/*-tags*.mk""",
972 'branch' : """create a branch for this module, from the latest tag on the trunk,
973 and change trunk's version number to reflect the new branch name;
974 you can specify the new branch name by using module:branch""",
975 'sync' : """create a tag from the module
976 this is a last resort option, mostly for repairs""",
977 'changelog' : """extract changelog between build tags
978 expected arguments are a list of tags""",
981 silent_modes = ['list']
982 release_modes = ['changelog']
985 def optparse_list (option, opt, value, parser):
987 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
989 setattr(parser.values,option.dest,value.split())
994 for function in Main.modes.keys():
995 if sys.argv[0].find(function) >= 0:
999 print "Unsupported command",sys.argv[0]
1000 print "Supported commands:" + " ".join(Main.modes.keys())
1003 if mode not in Main.release_modes:
1004 usage = Main.module_usage
1005 usage += Main.common_usage
1006 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1008 usage = Main.release_usage
1009 usage += Main.common_usage
1011 parser=OptionParser(usage=usage,version=subversion_id)
1013 if mode == "tag" or mode == 'branch':
1014 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1015 help="set new version and reset taglevel to 0")
1017 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1018 help="do not update changelog section in specfile when tagging")
1019 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1020 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1021 if mode == "tag" or mode == "sync" :
1022 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1023 help="specify editor")
1025 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1026 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1027 help="dry run - shell commands are only displayed")
1028 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1029 default=[], nargs=1,type="string",
1030 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1031 -- can be set multiple times, or use quotes""")
1033 parser.add_option("-w","--workdir", action="store", dest="workdir",
1034 default="%s/%s"%(os.getenv("HOME"),"modules"),
1035 help="""name for dedicated working dir - defaults to ~/modules
1036 ** THIS MUST NOT ** be your usual working directory""")
1037 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1038 help="skip safety checks, such as svn updates -- use with care")
1040 # default verbosity depending on function - temp
1041 verbose_modes= ['tag', 'sync', 'branch']
1043 if mode not in verbose_modes:
1044 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1045 help="run in verbose mode")
1047 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1048 help="run in quiet (non-verbose) mode")
1049 (options, args) = parser.parse_args()
1051 if not hasattr(options,'dry_run'):
1052 options.dry_run=False
1053 if not hasattr(options,'www'):
1061 Module.init_homedir(options)
1063 modules=[ Module(modname,options) for modname in args ]
1064 for module in modules:
1065 if len(args)>1 and mode not in Main.silent_modes:
1066 print '========================================',module.friendly_name()
1067 # call the method called do_<mode>
1068 method=Module.__dict__["do_%s"%mode]
1073 traceback.print_exc()
1074 print 'Skipping module %s: '%modname,e
1076 ####################
1077 if __name__ == "__main__" :
1080 except KeyboardInterrupt: