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 --tags")
315 self.__run_command_in_repo("git --no-pager reset --hard")
316 self.__run_command_in_repo("git --no-pager clean -f")
321 s="nothing to commit (working directory clean)"
322 return Command(command, self.options).output_of(True).find(s) >= 0
323 return self.__run_in_repo(check_commit)
326 return os.path.exists(os.path.join(self.path, ".git"))
330 """ Generic repository """
331 supported_repo_types = [SvnRepository, GitRepository]
333 def __init__(self, path, options):
335 self.options = options
336 for repo in self.supported_repo_types:
337 self.repo = repo(self.path, self.options)
338 if self.repo.is_valid():
342 def has_moved_to_git(cls, module, svnpath):
343 module = git_to_svn_name(module)
344 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
347 def remote_exists(cls, remote):
348 for repo in Repository.supported_repo_types:
349 if repo.remote_exists(remote):
353 def __getattr__(self, attr):
354 return getattr(self.repo, attr)
358 # support for tagged module is minimal, and is for the Build class only
361 svn_magic_line="--This line, and those below, will be ignored--"
362 setting_tag_format = "Setting tag %s"
364 redirectors=[ # ('module_name_varname','name'),
365 ('module_version_varname','version'),
366 ('module_taglevel_varname','taglevel'), ]
368 # where to store user's config
369 config_storage="CONFIG"
374 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
375 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
376 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
377 ("build", "Enter the name of your build module","build"),
378 ('username',"Enter your firstname and lastname for changelogs",""),
379 ("email","Enter your email address for changelogs",""),
383 def prompt_config (cls):
384 for (key,message,default) in cls.configKeys:
386 while not cls.config[key]:
387 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
390 # for parsing module spec name:branch
391 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
392 # special form for tagged module - for Build
393 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
395 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
397 def __init__ (self,module_spec,options):
399 attempt=Module.matcher_branch_spec.match(module_spec)
401 self.name=attempt.group('name')
402 self.branch=attempt.group('branch')
404 attempt=Module.matcher_tag_spec.match(module_spec)
406 self.name=attempt.group('name')
407 self.tagname=attempt.group('tagname')
409 self.name=module_spec
411 # when available prefer to use git module name internally
412 self.name = svn_to_git_name(self.name)
415 self.module_dir="%s/%s"%(options.workdir,self.name)
416 self.repository = None
419 def run (self,command):
420 return Command(command,self.options).run()
421 def run_fatal (self,command):
422 return Command(command,self.options).run_fatal()
423 def run_prompt (self,message,fun, *args):
424 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
425 if not self.options.verbose:
427 choice=prompt(message,True,('s','how'))
431 elif choice is False:
432 print 'About to run function:', fun_msg
434 question=message+" - want to run function: " + fun_msg
435 if prompt(question,True):
438 def friendly_name (self):
439 if hasattr(self,'branch'):
440 return "%s:%s"%(self.name,self.branch)
441 elif hasattr(self,'tagname'):
442 return "%s@%s"%(self.name,self.tagname)
447 def git_remote_dir (cls, name):
448 return "%s:/git/%s.git" % (cls.config['gitserver'], name)
451 def svn_remote_dir (cls, name):
452 name = git_to_svn_name(name)
453 svn = cls.config['svnpath']
454 if svn.endswith('/'):
455 return "%s%s" % (svn, name)
456 return "%s/%s" % (svn, name)
458 def svn_selected_remote(self):
459 svn_name = git_to_svn_name(self.name)
460 remote = self.svn_remote_dir(svn_name)
461 if hasattr(self,'branch'):
462 remote = "%s/branches/%s" % (remote, self.branch)
463 elif hasattr(self,'tagname'):
464 remote = "%s/tags/%s" % (remote, self.tagname)
466 remote = "%s/trunk" % remote
471 def init_homedir (cls, options):
472 if options.verbose and options.mode not in Main.silent_modes:
473 print 'Checking for', options.workdir
474 storage="%s/%s"%(options.workdir, cls.config_storage)
475 # sanity check. Either the topdir exists AND we have a config/storage
476 # or topdir does not exist and we create it
477 # to avoid people use their own daily svn repo
478 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
479 print """The directory %s exists and has no CONFIG file
480 If this is your regular working directory, please provide another one as the
481 module-* commands need a fresh working dir. Make sure that you do not use
482 that for other purposes than tagging""" % options.workdir
484 if not os.path.isdir (options.workdir):
485 print "Cannot find",options.workdir,"let's create it"
487 print "Checking ...",
488 remote = cls.git_remote_dir(cls.config['build'])
489 local = os.path.join(options.workdir, cls.config['build'])
490 GitRepository.checkout(remote, local, options, depth=1)
495 for (key,message,default) in Module.configKeys:
496 f.write("%s=%s\n"%(key,Module.config[key]))
499 print 'Stored',storage
500 Command("cat %s"%storage,options).run()
504 for line in f.readlines():
505 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
506 Module.config[key]=value
509 build_dir = os.path.join(options.workdir, cls.config['build'])
510 build = Repository(build_dir, options)
511 if not build.is_clean():
512 print "build module needs a revert"
517 if options.verbose and options.mode not in Main.silent_modes:
518 print '******** Using config'
519 for (key,message,default) in Module.configKeys:
520 print '\t',key,'=',Module.config[key]
522 def init_module_dir (self):
523 if self.options.verbose:
524 print 'Checking for',self.module_dir
526 if not os.path.isdir (self.module_dir):
527 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
528 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
532 remote = self.svn_selected_remote()
533 self.repository = SvnRepository.checkout(remote,
535 self.options, recursive=False)
537 self.repository = Repository(self.module_dir, self.options)
538 if self.repository.type == "svn":
539 # check if module has moved to git
540 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
541 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
542 self.init_module_dir()
543 # check if we have the required branch/tag
544 if self.repository.url() != self.svn_selected_remote():
545 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
546 self.init_module_dir()
548 elif self.repository.type == "git":
549 if hasattr(self,'branch'):
550 self.repository.to_branch(self.branch)
551 elif hasattr(self,'tagname'):
552 self.repository.to_tag(self.tagname)
555 raise Exception, 'Cannot find %s - check module name'%self.module_dir
558 def revert_module_dir (self):
559 if self.options.fast_checks:
560 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
562 if self.options.verbose:
563 print 'Checking whether', self.module_dir, 'needs being reverted'
565 if not self.repository.is_clean():
566 self.repository.revert()
568 def update_module_dir (self):
569 if self.options.fast_checks:
570 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
572 if self.options.verbose:
573 print 'Updating', self.module_dir
574 self.repository.update()
576 def main_specname (self):
577 attempt="%s/%s.spec"%(self.module_dir,self.name)
578 if os.path.isfile (attempt):
580 pattern1="%s/*.spec"%self.module_dir
581 level1=glob(pattern1)
584 pattern2="%s/*/*.spec"%self.module_dir
585 level2=glob(pattern2)
589 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
591 def all_specnames (self):
592 level1=glob("%s/*.spec" % self.module_dir)
593 if level1: return level1
594 level2=glob("%s/*/*.spec" % self.module_dir)
597 def parse_spec (self, specfile, varnames):
598 if self.options.verbose:
599 print 'Parsing',specfile,
605 for line in f.readlines():
606 attempt=Module.matcher_rpm_define.match(line)
608 (define,var,value)=attempt.groups()
612 if self.options.debug:
613 print 'found',len(result),'keys'
614 for (k,v) in result.iteritems():
618 # stores in self.module_name_varname the rpm variable to be used for the module's name
619 # and the list of these names in self.varnames
620 def spec_dict (self):
621 specfile=self.main_specname()
622 redirector_keys = [ varname for (varname,default) in Module.redirectors]
623 redirect_dict = self.parse_spec(specfile,redirector_keys)
624 if self.options.debug:
625 print '1st pass parsing done, redirect_dict=',redirect_dict
627 for (varname,default) in Module.redirectors:
628 if redirect_dict.has_key(varname):
629 setattr(self,varname,redirect_dict[varname])
630 varnames += [redirect_dict[varname]]
632 setattr(self,varname,default)
633 varnames += [ default ]
634 self.varnames = varnames
635 result = self.parse_spec (specfile,self.varnames)
636 if self.options.debug:
637 print '2st pass parsing done, varnames=',varnames,'result=',result
640 def patch_spec_var (self, patch_dict,define_missing=False):
641 for specfile in self.all_specnames():
642 # record the keys that were changed
643 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
644 newspecfile=specfile+".new"
645 if self.options.verbose:
646 print 'Patching',specfile,'for',patch_dict.keys()
648 new=open(newspecfile,"w")
650 for line in spec.readlines():
651 attempt=Module.matcher_rpm_define.match(line)
653 (define,var,value)=attempt.groups()
654 if var in patch_dict.keys():
655 if self.options.debug:
656 print 'rewriting %s as %s'%(var,patch_dict[var])
657 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
662 for (key,was_changed) in changed.iteritems():
664 if self.options.debug:
665 print 'rewriting missing %s as %s'%(key,patch_dict[key])
666 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
669 os.rename(newspecfile,specfile)
671 # returns all lines until the magic line
672 def unignored_lines (self, logfile):
674 white_line_matcher = re.compile("\A\s*\Z")
675 for logline in file(logfile).readlines():
676 if logline.strip() == Module.svn_magic_line:
678 elif white_line_matcher.match(logline):
681 result.append(logline.strip()+'\n')
684 # creates a copy of the input with only the unignored lines
685 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
687 f.write(self.setting_tag_format%new_tag_name + '\n')
688 for line in self.unignored_lines(filein):
692 def insert_changelog (self, logfile, oldtag, newtag):
693 for specfile in self.all_specnames():
694 newspecfile=specfile+".new"
695 if self.options.verbose:
696 print 'Inserting changelog from %s into %s'%(logfile,specfile)
698 new=open(newspecfile,"w")
699 for line in spec.readlines():
701 if re.compile('%changelog').match(line):
702 dateformat="* %a %b %d %Y"
703 datepart=time.strftime(dateformat)
704 logpart="%s <%s> - %s"%(Module.config['username'],
705 Module.config['email'],
707 new.write(datepart+" "+logpart+"\n")
708 for logline in self.unignored_lines(logfile):
709 new.write("- " + logline)
713 os.rename(newspecfile,specfile)
715 def show_dict (self, spec_dict):
716 if self.options.verbose:
717 for (k,v) in spec_dict.iteritems():
720 def last_tag (self, spec_dict):
722 return "%s-%s" % (spec_dict[self.module_version_varname],
723 spec_dict[self.module_taglevel_varname])
725 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
727 def tag_name (self, spec_dict, old_svn_name=False):
728 base_tag_name = self.name
730 base_tag_name = git_to_svn_name(self.name)
731 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
734 ##############################
735 # using fine_grain means replacing only those instances that currently refer to this tag
736 # otherwise, <module>-SVNPATH is replaced unconditionnally
737 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
738 newtagsfile=tagsfile+".new"
740 new=open(newtagsfile,"w")
743 # fine-grain : replace those lines that refer to oldname
745 if self.options.verbose:
746 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
747 matcher=re.compile("^(.*)%s(.*)"%oldname)
748 for line in tags.readlines():
749 if not matcher.match(line):
752 (begin,end)=matcher.match(line).groups()
753 new.write(begin+newname+end+"\n")
755 # brute-force : change uncommented lines that define <module>-SVNPATH
757 if self.options.verbose:
758 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
759 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
761 matcher_module=re.compile(pattern)
762 for line in tags.readlines():
763 attempt=matcher_module.match(line)
765 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
766 if self.options.verbose:
768 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
769 new.write(replacement)
775 os.rename(newtagsfile,tagsfile)
776 if self.options.verbose: print "%d changes"%matches
779 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
780 if self.options.verbose:
781 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
783 found_tagname = tagname
784 found = self.repository.tag_exists(tagname)
785 if not found and old_svn_tag_name:
786 if self.options.verbose:
788 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
789 found = self.repository.tag_exists(old_svn_tag_name)
791 found_tagname = old_svn_tag_name
793 if (found and need_it) or (not found and not need_it):
798 raise Exception, "tag (%s) is already there" % tagname
800 raise Exception, "can not find required tag (%s)" % tagname
806 self.init_module_dir()
807 self.revert_module_dir()
808 self.update_module_dir()
810 spec_dict = self.spec_dict()
811 self.show_dict(spec_dict)
814 old_tag_name = self.tag_name(spec_dict)
815 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
817 if (self.options.new_version):
818 # new version set on command line
819 spec_dict[self.module_version_varname] = self.options.new_version
820 spec_dict[self.module_taglevel_varname] = 0
823 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
824 spec_dict[self.module_taglevel_varname] = new_taglevel
826 new_tag_name = self.tag_name(spec_dict)
829 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
830 new_tag_name = self.check_tag(new_tag_name, need_it=False)
833 diff_output = self.repository.diff_with_tag(old_tag_name)
834 if len(diff_output) == 0:
835 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
838 # side effect in trunk's specfile
839 self.patch_spec_var(spec_dict)
841 # prepare changelog file
842 # we use the standard subversion magic string (see svn_magic_line)
843 # so we can provide useful information, such as version numbers and diff
845 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
846 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
847 setting_tag_line=Module.setting_tag_format%new_tag_name
848 file(changelog,"w").write("""
851 Please write a changelog for this new tag in the section above
852 """%(Module.svn_magic_line,setting_tag_line))
854 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
855 file(changelog,"a").write('DIFF=========\n' + diff_output)
857 if self.options.debug:
861 self.run("%s %s"%(self.options.editor,changelog))
862 # strip magic line in second file - looks like svn has changed its magic line with 1.6
863 # so we do the job ourselves
864 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
865 # insert changelog in spec
866 if self.options.changelog:
867 self.insert_changelog (changelog,old_tag_name,new_tag_name)
870 build_path = os.path.join(self.options.workdir,
871 Module.config['build'])
872 build = Repository(build_path, self.options)
873 if self.options.build_branch:
874 build.to_branch(self.options.build_branch)
875 if not build.is_clean():
878 tagsfiles=glob(build.path+"/*-tags*.mk")
879 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
883 for tagsfile in tagsfiles:
884 status=tagsdict[tagsfile]
885 basename=os.path.basename(tagsfile)
886 print ".................... Dealing with %s"%basename
887 while tagsdict[tagsfile] == 'todo' :
888 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
889 [ ('y','es'), ('n', 'ext'), ('f','orce'),
890 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
893 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
895 print 'Done with %s'%os.path.basename(tagsfile)
896 tagsdict[tagsfile]='done'
898 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
900 self.run("svn diff %s"%tagsfile)
902 self.run("svn revert %s"%tagsfile)
904 self.run("cat %s"%tagsfile)
907 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
908 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
909 d: show current diff for this tag file
910 r: revert that tag file
911 c: cat the current tag file
912 n: move to next file"""%locals()
914 if prompt("Want to review changes on tags files",False):
915 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
920 def diff_all_changes():
922 print self.repository.diff()
924 def commit_all_changes(log):
925 self.repository.commit(log)
928 self.run_prompt("Review module and build", diff_all_changes)
929 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
930 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
932 if self.options.debug:
933 print 'Preserving',changelog,'and stripped',changelog_svn
936 os.unlink(changelog_svn)
939 ##############################
942 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
944 module-tools : a set of tools to manage subversion tags and specfile
945 requires the specfile to either
946 * define *version* and *taglevel*
948 * define redirection variables module_version_varname / module_taglevel_varname
950 by default, the trunk of modules is taken into account
951 in this case, just mention the module name as <module_desc>
953 if you wish to work on a branch rather than on the trunk,
954 you can use something like e.g. Mom:2.1 as <module_desc>
956 release_usage="""Usage: %prog [options] tag1 .. tagn
957 Extract release notes from the changes in specfiles between several build tags, latest first
959 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
960 You can refer to a (build) branch by prepending a colon, like in
961 release-changelog :4.2 4.2-rc25
962 You can refer to the build trunk by just mentioning 'trunk', e.g.
963 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
965 common_usage="""More help:
966 see http://svn.planet-lab.org/wiki/ModuleTools"""
969 'list' : "displays a list of available tags or branches",
970 'version' : "check latest specfile and print out details",
971 'diff' : "show difference between module (trunk or branch) and latest tag",
972 'tag' : """increment taglevel in specfile, insert changelog in specfile,
973 create new tag and and monitor its adoption in build/*-tags*.mk""",
974 'branch' : """create a branch for this module, from the latest tag on the trunk,
975 and change trunk's version number to reflect the new branch name;
976 you can specify the new branch name by using module:branch""",
977 'sync' : """create a tag from the module
978 this is a last resort option, mostly for repairs""",
979 'changelog' : """extract changelog between build tags
980 expected arguments are a list of tags""",
983 silent_modes = ['list']
984 release_modes = ['changelog']
987 def optparse_list (option, opt, value, parser):
989 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
991 setattr(parser.values,option.dest,value.split())
996 for function in Main.modes.keys():
997 if sys.argv[0].find(function) >= 0:
1001 print "Unsupported command",sys.argv[0]
1002 print "Supported commands:" + " ".join(Main.modes.keys())
1005 if mode not in Main.release_modes:
1006 usage = Main.module_usage
1007 usage += Main.common_usage
1008 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1010 usage = Main.release_usage
1011 usage += Main.common_usage
1013 parser=OptionParser(usage=usage,version=subversion_id)
1015 if mode == "tag" or mode == 'branch':
1016 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1017 help="set new version and reset taglevel to 0")
1019 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1020 help="do not update changelog section in specfile when tagging")
1021 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1022 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1023 if mode == "tag" or mode == "sync" :
1024 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1025 help="specify editor")
1027 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1028 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1029 help="dry run - shell commands are only displayed")
1030 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1031 default=[], nargs=1,type="string",
1032 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1033 -- can be set multiple times, or use quotes""")
1035 parser.add_option("-w","--workdir", action="store", dest="workdir",
1036 default="%s/%s"%(os.getenv("HOME"),"modules"),
1037 help="""name for dedicated working dir - defaults to ~/modules
1038 ** THIS MUST NOT ** be your usual working directory""")
1039 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1040 help="skip safety checks, such as svn updates -- use with care")
1042 # default verbosity depending on function - temp
1043 verbose_modes= ['tag', 'sync', 'branch']
1045 if mode not in verbose_modes:
1046 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1047 help="run in verbose mode")
1049 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1050 help="run in quiet (non-verbose) mode")
1051 (options, args) = parser.parse_args()
1053 if not hasattr(options,'dry_run'):
1054 options.dry_run=False
1055 if not hasattr(options,'www'):
1063 Module.init_homedir(options)
1065 modules=[ Module(modname,options) for modname in args ]
1066 for module in modules:
1067 if len(args)>1 and mode not in Main.silent_modes:
1068 print '========================================',module.friendly_name()
1069 # call the method called do_<mode>
1070 method=Module.__dict__["do_%s"%mode]
1075 traceback.print_exc()
1076 print 'Skipping module %s: '%modname,e
1078 ####################
1079 if __name__ == "__main__" :
1082 except KeyboardInterrupt: