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 GitRepository.checkout(remote, local, options, depth=1)
492 for (key,message,default) in Module.configKeys:
493 f.write("%s=%s\n"%(key,Module.config[key]))
496 print 'Stored',storage
497 Command("cat %s"%storage,options).run()
501 for line in f.readlines():
502 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
503 Module.config[key]=value
506 build_dir = os.path.join(options.workdir, cls.config['build'])
507 build = Repository(build_dir, options)
508 if not build.is_clean():
509 print "build module needs a revert"
514 if options.verbose and options.mode not in Main.silent_modes:
515 print '******** Using config'
516 for (key,message,default) in Module.configKeys:
517 print '\t',key,'=',Module.config[key]
519 def init_module_dir (self):
520 if self.options.verbose:
521 print 'Checking for',self.module_dir
523 if not os.path.isdir (self.module_dir):
524 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
525 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
529 remote = self.svn_selected_remote()
530 self.repository = SvnRepository.checkout(remote,
532 self.options, recursive=False)
534 self.repository = Repository(self.module_dir, self.options)
535 if self.repository.type == "svn":
536 # check if module has moved to git
537 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
538 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
539 self.init_module_dir()
540 # check if we have the required branch/tag
541 if self.repository.url() != self.svn_selected_remote():
542 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
543 self.init_module_dir()
545 elif self.repository.type == "git":
546 if hasattr(self,'branch'):
547 self.repository.to_branch(self.branch)
548 elif hasattr(self,'tagname'):
549 self.repository.to_tag(self.tagname)
552 raise Exception, 'Cannot find %s - check module name'%self.module_dir
555 def revert_module_dir (self):
556 if self.options.fast_checks:
557 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
559 if self.options.verbose:
560 print 'Checking whether', self.module_dir, 'needs being reverted'
562 if not self.repository.is_clean():
563 self.repository.revert()
565 def update_module_dir (self):
566 if self.options.fast_checks:
567 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
569 if self.options.verbose:
570 print 'Updating', self.module_dir
571 self.repository.update()
573 def main_specname (self):
574 attempt="%s/%s.spec"%(self.module_dir,self.name)
575 if os.path.isfile (attempt):
577 pattern1="%s/*.spec"%self.module_dir
578 level1=glob(pattern1)
581 pattern2="%s/*/*.spec"%self.module_dir
582 level2=glob(pattern2)
586 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
588 def all_specnames (self):
589 level1=glob("%s/*.spec" % self.module_dir)
590 if level1: return level1
591 level2=glob("%s/*/*.spec" % self.module_dir)
594 def parse_spec (self, specfile, varnames):
595 if self.options.verbose:
596 print 'Parsing',specfile,
602 for line in f.readlines():
603 attempt=Module.matcher_rpm_define.match(line)
605 (define,var,value)=attempt.groups()
609 if self.options.debug:
610 print 'found',len(result),'keys'
611 for (k,v) in result.iteritems():
615 # stores in self.module_name_varname the rpm variable to be used for the module's name
616 # and the list of these names in self.varnames
617 def spec_dict (self):
618 specfile=self.main_specname()
619 redirector_keys = [ varname for (varname,default) in Module.redirectors]
620 redirect_dict = self.parse_spec(specfile,redirector_keys)
621 if self.options.debug:
622 print '1st pass parsing done, redirect_dict=',redirect_dict
624 for (varname,default) in Module.redirectors:
625 if redirect_dict.has_key(varname):
626 setattr(self,varname,redirect_dict[varname])
627 varnames += [redirect_dict[varname]]
629 setattr(self,varname,default)
630 varnames += [ default ]
631 self.varnames = varnames
632 result = self.parse_spec (specfile,self.varnames)
633 if self.options.debug:
634 print '2st pass parsing done, varnames=',varnames,'result=',result
637 def patch_spec_var (self, patch_dict,define_missing=False):
638 for specfile in self.all_specnames():
639 # record the keys that were changed
640 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
641 newspecfile=specfile+".new"
642 if self.options.verbose:
643 print 'Patching',specfile,'for',patch_dict.keys()
645 new=open(newspecfile,"w")
647 for line in spec.readlines():
648 attempt=Module.matcher_rpm_define.match(line)
650 (define,var,value)=attempt.groups()
651 if var in patch_dict.keys():
652 if self.options.debug:
653 print 'rewriting %s as %s'%(var,patch_dict[var])
654 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
659 for (key,was_changed) in changed.iteritems():
661 if self.options.debug:
662 print 'rewriting missing %s as %s'%(key,patch_dict[key])
663 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
666 os.rename(newspecfile,specfile)
668 # returns all lines until the magic line
669 def unignored_lines (self, logfile):
671 white_line_matcher = re.compile("\A\s*\Z")
672 for logline in file(logfile).readlines():
673 if logline.strip() == Module.svn_magic_line:
675 elif white_line_matcher.match(logline):
678 result.append(logline.strip()+'\n')
681 # creates a copy of the input with only the unignored lines
682 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
684 f.write(self.setting_tag_format%new_tag_name + '\n')
685 for line in self.unignored_lines(filein):
689 def insert_changelog (self, logfile, oldtag, newtag):
690 for specfile in self.all_specnames():
691 newspecfile=specfile+".new"
692 if self.options.verbose:
693 print 'Inserting changelog from %s into %s'%(logfile,specfile)
695 new=open(newspecfile,"w")
696 for line in spec.readlines():
698 if re.compile('%changelog').match(line):
699 dateformat="* %a %b %d %Y"
700 datepart=time.strftime(dateformat)
701 logpart="%s <%s> - %s"%(Module.config['username'],
702 Module.config['email'],
704 new.write(datepart+" "+logpart+"\n")
705 for logline in self.unignored_lines(logfile):
706 new.write("- " + logline)
710 os.rename(newspecfile,specfile)
712 def show_dict (self, spec_dict):
713 if self.options.verbose:
714 for (k,v) in spec_dict.iteritems():
717 def last_tag (self, spec_dict):
719 return "%s-%s" % (spec_dict[self.module_version_varname],
720 spec_dict[self.module_taglevel_varname])
722 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
724 def tag_name (self, spec_dict, old_svn_name=False):
725 base_tag_name = self.name
727 base_tag_name = git_to_svn_name(self.name)
728 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
731 ##############################
732 # using fine_grain means replacing only those instances that currently refer to this tag
733 # otherwise, <module>-SVNPATH is replaced unconditionnally
734 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
735 newtagsfile=tagsfile+".new"
737 new=open(newtagsfile,"w")
740 # fine-grain : replace those lines that refer to oldname
742 if self.options.verbose:
743 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
744 matcher=re.compile("^(.*)%s(.*)"%oldname)
745 for line in tags.readlines():
746 if not matcher.match(line):
749 (begin,end)=matcher.match(line).groups()
750 new.write(begin+newname+end+"\n")
752 # brute-force : change uncommented lines that define <module>-SVNPATH
754 if self.options.verbose:
755 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
756 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
758 matcher_module=re.compile(pattern)
759 for line in tags.readlines():
760 attempt=matcher_module.match(line)
762 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
763 if self.options.verbose:
765 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
766 new.write(replacement)
772 os.rename(newtagsfile,tagsfile)
773 if self.options.verbose: print "%d changes"%matches
776 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
777 if self.options.verbose:
778 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
780 found_tagname = tagname
781 found = self.repository.tag_exists(tagname)
782 if not found and old_svn_tag_name:
783 if self.options.verbose:
785 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
786 found = self.repository.tag_exists(old_svn_tag_name)
788 found_tagname = old_svn_tag_name
790 if (found and need_it) or (not found and not need_it):
795 raise Exception, "tag (%s) is already there" % tagname
797 raise Exception, "can not find required tag (%s)" % tagname
803 self.init_module_dir()
804 self.revert_module_dir()
805 self.update_module_dir()
807 spec_dict = self.spec_dict()
808 self.show_dict(spec_dict)
811 old_tag_name = self.tag_name(spec_dict)
812 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
814 if (self.options.new_version):
815 # new version set on command line
816 spec_dict[self.module_version_varname] = self.options.new_version
817 spec_dict[self.module_taglevel_varname] = 0
820 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
821 spec_dict[self.module_taglevel_varname] = new_taglevel
823 new_tag_name = self.tag_name(spec_dict)
826 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
827 new_tag_name = self.check_tag(new_tag_name, need_it=False)
830 diff_output = self.repository.diff_with_tag(old_tag_name)
831 if len(diff_output) == 0:
832 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
835 # side effect in trunk's specfile
836 self.patch_spec_var(spec_dict)
838 # prepare changelog file
839 # we use the standard subversion magic string (see svn_magic_line)
840 # so we can provide useful information, such as version numbers and diff
842 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
843 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
844 setting_tag_line=Module.setting_tag_format%new_tag_name
845 file(changelog,"w").write("""
848 Please write a changelog for this new tag in the section above
849 """%(Module.svn_magic_line,setting_tag_line))
851 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
852 file(changelog,"a").write('DIFF=========\n' + diff_output)
854 if self.options.debug:
858 self.run("%s %s"%(self.options.editor,changelog))
859 # strip magic line in second file - looks like svn has changed its magic line with 1.6
860 # so we do the job ourselves
861 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
862 # insert changelog in spec
863 if self.options.changelog:
864 self.insert_changelog (changelog,old_tag_name,new_tag_name)
867 build_path = os.path.join(self.options.workdir,
868 Module.config['build'])
869 build = Repository(build_path, self.options)
870 if self.options.build_branch:
871 build.to_branch(self.options.build_branch)
872 if not build.is_clean():
875 tagsfiles=glob(build.path+"/*-tags*.mk")
876 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
880 for tagsfile in tagsfiles:
881 status=tagsdict[tagsfile]
882 basename=os.path.basename(tagsfile)
883 print ".................... Dealing with %s"%basename
884 while tagsdict[tagsfile] == 'todo' :
885 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
886 [ ('y','es'), ('n', 'ext'), ('f','orce'),
887 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
890 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
892 print 'Done with %s'%os.path.basename(tagsfile)
893 tagsdict[tagsfile]='done'
895 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
897 self.run("svn diff %s"%tagsfile)
899 self.run("svn revert %s"%tagsfile)
901 self.run("cat %s"%tagsfile)
904 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
905 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
906 d: show current diff for this tag file
907 r: revert that tag file
908 c: cat the current tag file
909 n: move to next file"""%locals()
911 if prompt("Want to review changes on tags files",False):
912 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
917 def diff_all_changes():
919 print self.repository.diff()
921 def commit_all_changes(log):
922 self.repository.commit(log)
925 self.run_prompt("Review module and build", diff_all_changes)
926 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
927 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
929 if self.options.debug:
930 print 'Preserving',changelog,'and stripped',changelog_svn
933 os.unlink(changelog_svn)
936 ##############################
939 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
941 module-tools : a set of tools to manage subversion tags and specfile
942 requires the specfile to either
943 * define *version* and *taglevel*
945 * define redirection variables module_version_varname / module_taglevel_varname
947 by default, the trunk of modules is taken into account
948 in this case, just mention the module name as <module_desc>
950 if you wish to work on a branch rather than on the trunk,
951 you can use something like e.g. Mom:2.1 as <module_desc>
953 release_usage="""Usage: %prog [options] tag1 .. tagn
954 Extract release notes from the changes in specfiles between several build tags, latest first
956 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
957 You can refer to a (build) branch by prepending a colon, like in
958 release-changelog :4.2 4.2-rc25
959 You can refer to the build trunk by just mentioning 'trunk', e.g.
960 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
962 common_usage="""More help:
963 see http://svn.planet-lab.org/wiki/ModuleTools"""
966 'list' : "displays a list of available tags or branches",
967 'version' : "check latest specfile and print out details",
968 'diff' : "show difference between module (trunk or branch) and latest tag",
969 'tag' : """increment taglevel in specfile, insert changelog in specfile,
970 create new tag and and monitor its adoption in build/*-tags*.mk""",
971 'branch' : """create a branch for this module, from the latest tag on the trunk,
972 and change trunk's version number to reflect the new branch name;
973 you can specify the new branch name by using module:branch""",
974 'sync' : """create a tag from the module
975 this is a last resort option, mostly for repairs""",
976 'changelog' : """extract changelog between build tags
977 expected arguments are a list of tags""",
980 silent_modes = ['list']
981 release_modes = ['changelog']
984 def optparse_list (option, opt, value, parser):
986 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
988 setattr(parser.values,option.dest,value.split())
993 for function in Main.modes.keys():
994 if sys.argv[0].find(function) >= 0:
998 print "Unsupported command",sys.argv[0]
999 print "Supported commands:" + " ".join(Main.modes.keys())
1002 if mode not in Main.release_modes:
1003 usage = Main.module_usage
1004 usage += Main.common_usage
1005 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1007 usage = Main.release_usage
1008 usage += Main.common_usage
1010 parser=OptionParser(usage=usage,version=subversion_id)
1012 if mode == "tag" or mode == 'branch':
1013 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1014 help="set new version and reset taglevel to 0")
1016 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1017 help="do not update changelog section in specfile when tagging")
1018 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1019 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1020 if mode == "tag" or mode == "sync" :
1021 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1022 help="specify editor")
1024 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1025 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1026 help="dry run - shell commands are only displayed")
1027 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1028 default=[], nargs=1,type="string",
1029 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1030 -- can be set multiple times, or use quotes""")
1032 parser.add_option("-w","--workdir", action="store", dest="workdir",
1033 default="%s/%s"%(os.getenv("HOME"),"modules"),
1034 help="""name for dedicated working dir - defaults to ~/modules
1035 ** THIS MUST NOT ** be your usual working directory""")
1036 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1037 help="skip safety checks, such as svn updates -- use with care")
1039 # default verbosity depending on function - temp
1040 verbose_modes= ['tag', 'sync', 'branch']
1042 if mode not in verbose_modes:
1043 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1044 help="run in verbose mode")
1046 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1047 help="run in quiet (non-verbose) mode")
1048 (options, args) = parser.parse_args()
1050 if not hasattr(options,'dry_run'):
1051 options.dry_run=False
1052 if not hasattr(options,'www'):
1060 Module.init_homedir(options)
1062 modules=[ Module(modname,options) for modname in args ]
1063 for module in modules:
1064 if len(args)>1 and mode not in Main.silent_modes:
1065 print '========================================',module.friendly_name()
1066 # call the method called do_<mode>
1067 method=Module.__dict__["do_%s"%mode]
1072 traceback.print_exc()
1073 print 'Skipping module %s: '%modname,e
1075 ####################
1076 if __name__ == "__main__" :
1079 except KeyboardInterrupt: