7 from optparse import OptionParser
9 # HARDCODED NAME CHANGES
11 # Moving to git we decided to rename some of the repositories. Here is
12 # a map of name changes applied in git repositories.
13 RENAMED_SVN_MODULES = {
17 def svn_to_git_name(module):
18 if RENAMED_SVN_MODULES.has_key(module):
19 return RENAMED_SVN_MODULES[module]
22 def git_to_svn_name(module):
23 for key in RENAMED_SVN_MODULES:
24 if module == RENAMED_SVN_MODULES[key]:
29 # e.g. other_choices = [ ('d','iff') , ('g','uess') ] - lowercase
30 def prompt (question,default=True,other_choices=[],allow_outside=False):
31 if not isinstance (other_choices,list):
32 other_choices = [ other_choices ]
33 chars = [ c for (c,rest) in other_choices ]
37 if default is True: choices.append('[y]')
38 else : choices.append('y')
40 if default is False: choices.append('[n]')
41 else : choices.append('n')
43 for (char,choice) in other_choices:
45 choices.append("["+char+"]"+choice)
47 choices.append("<"+char+">"+choice)
49 answer=raw_input(question + " " + "/".join(choices) + " ? ")
52 answer=answer[0].lower()
54 if 'y' in chars: return 'y'
57 if 'n' in chars: return 'n'
60 for (char,choice) in other_choices:
65 return prompt(question,default,other_choices)
71 editor = os.environ['EDITOR']
79 def print_fold (line):
80 while len(line) >= fold_length:
81 print line[:fold_length],'\\'
82 line=line[fold_length:]
86 def __init__ (self,command,options):
89 self.tmp="/tmp/command-%d"%os.getpid()
92 if self.options.dry_run:
93 print 'dry_run',self.command
95 if self.options.verbose and self.options.mode not in Main.silent_modes:
96 print '+',self.command
98 return os.system(self.command)
100 def run_silent (self):
101 if self.options.dry_run:
102 print 'dry_run',self.command
104 if self.options.verbose:
105 print '+',self.command,' .. ',
107 retcod=os.system(self.command + " &> " + self.tmp)
109 print "FAILED ! -- out+err below (command was %s)"%self.command
110 os.system("cat " + self.tmp)
111 print "FAILED ! -- end of quoted output"
112 elif self.options.verbose:
118 if self.run_silent() !=0:
119 raise Exception,"Command %s failed"%self.command
121 # returns stdout, like bash's $(mycommand)
122 def output_of (self,with_stderr=False):
123 if self.options.dry_run:
124 print 'dry_run',self.command
125 return 'dry_run output'
126 tmp="/tmp/status-%d"%os.getpid()
127 if self.options.debug:
128 print '+',self.command,' .. ',
137 result=file(tmp).read()
139 if self.options.debug:
147 def __init__(self, path, options):
149 self.options = options
152 return os.path.basename(self.path)
155 out = Command("svn info %s" % self.path, self.options).output_of()
156 for line in out.split('\n'):
157 if line.startswith("URL:"):
158 return line.split()[1].strip()
161 out = Command("svn info %s" % self.path, self.options).output_of()
162 for line in out.split('\n'):
163 if line.startswith("Repository Root:"):
164 repo_root = line.split()[2].strip()
165 return "%s/svn/%s" (repo_root, self.name)
168 def checkout(cls, remote, local, options, recursive=False):
170 svncommand = "svn co %s %s" % (remote, local)
172 svncommand = "svn co -N %s %s" % (remote, local)
173 Command("rm -rf %s" % local, options).run_silent()
174 Command(svncommand, options).run_fatal()
176 return SvnRepository(local, options)
179 def remote_exists(cls, remote):
180 return os.system("svn list %s &> /dev/null" % remote) == 0
182 def tag_exists(self, tagname):
183 url = "%s/tags/%s" % (self.repo_root(), tagname)
184 return SvnRepository.remote_exists(url)
186 def update(self, subdir="", recursive=True):
187 path = os.path.join(self.path, subdir)
189 svncommand = "svn up %s" % path
191 svncommand = "svn up -N %s" % path
192 Command(svncommand, self.options).run_fatal()
194 def commit(self, logfile):
195 # add all new files to the repository
196 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs svn add" %
197 self.path, self.options).run_silent()
198 Command("svn commit -F %s %s" % (logfile, self.path), self.options).run_fatal()
200 def to_branch(self, branch):
201 remote = "%s/branches/%s" % (self.repo_root(), branch)
202 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
204 def to_tag(self, tag):
205 remote = "%s/tags/%s" % (self.repo_root(), branch)
206 SvnRepository.checkout(remote, self.path, self.options, recursive=True)
208 def tag(self, tagname, logfile):
209 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
210 self_url = self.url()
211 Command("svn copy -F %s %s %s" % (logfile, self_url, tag_url), self.options).run_fatal()
214 return Command("svn diff %s" % self.path, self.options).output_of(True)
216 def diff_with_tag(self, tagname):
217 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
218 return Command("svn diff %s %s" % (tag_url, self.url()),
219 self.options).output_of(True)
222 Command("svn revert %s -R" % self.path, self.options).run_fatal()
223 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
224 self.path, self.options).run_silent()
227 command="svn status %s" % self.path
228 return len(Command(command,self.options).output_of(True)) == 0
231 return os.path.exists(os.path.join(self.path, ".svn"))
237 def __init__(self, path, options):
239 self.options = options
242 return os.path.basename(self.path)
248 c = Command("git remote show origin", self.options)
249 out = self.__run_in_repo(c.output_of)
250 for line in out.split('\n'):
251 if line.strip().startswith("Fetch URL:"):
252 repo = line.split()[2]
255 def checkout(cls, remote, local, options, depth=1):
256 Command("rm -rf %s" % local, options).run_silent()
257 Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
258 return GitRepository(local, options)
261 def remote_exists(cls, remote):
262 return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
264 def tag_exists(self, tagname):
265 command = 'git tag -l | grep "^%s$"' % tagname
266 c = Command(command, self.options)
267 out = self.__run_in_repo(c.output_of, with_stderr=True)
270 def __run_in_repo(self, fun, *args, **kwargs):
273 ret = fun(*args, **kwargs)
277 def __run_command_in_repo(self, command, ignore_errors=False):
278 c = Command(command, self.options)
280 return self.__run_in_repo(c.output_of)
282 return self.__run_in_repo(c.run_fatal)
284 def update(self, subdir=None, recursive=None):
285 return self.__run_command_in_repo("git pull")
287 def to_branch(self, branch, remote=True):
289 branch = "origin/%s" % branch
290 return self.__run_command_in_repo("git checkout %s" % branch)
292 def to_tag(self, tag):
293 return self.__run_command_in_repo("git checkout %s" % tag)
295 def tag(self, tagname, logfile):
296 self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
300 c = Command("git diff", self.options)
301 return self.__run_in_repo(c.output_of, with_stderr=True)
303 def diff_with_tag(self, tagname):
304 c = Command("git diff %s" % tagname, self.options)
305 return self.__run_in_repo(c.output_of, with_stderr=True)
307 def commit(self, logfile):
308 self.__run_command_in_repo("git add -A", ignore_errors=True)
309 self.__run_command_in_repo("git commit -F %s" % logfile, ignore_errors=True)
310 self.__run_command_in_repo("git push")
311 self.__run_command_in_repo("git push --tags")
314 self.__run_command_in_repo("git --no-pager reset --hard")
315 self.__run_command_in_repo("git --no-pager clean -f")
320 s="nothing to commit (working directory clean)"
321 return Command(command, self.options).output_of(True).find(s) >= 0
322 return self.__run_in_repo(check_commit)
325 return os.path.exists(os.path.join(self.path, ".git"))
329 """ Generic repository """
330 supported_repo_types = [SvnRepository, GitRepository]
332 def __init__(self, path, options):
334 self.options = options
335 for repo in self.supported_repo_types:
336 self.repo = repo(self.path, self.options)
337 if self.repo.is_valid():
341 def has_moved_to_git(cls, module, svnpath):
342 module = git_to_svn_name(module)
343 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
346 def remote_exists(cls, remote):
347 for repo in Repository.supported_repo_types:
348 if repo.remote_exists(remote):
352 def __getattr__(self, attr):
353 return getattr(self.repo, attr)
357 # support for tagged module is minimal, and is for the Build class only
360 svn_magic_line="--This line, and those below, will be ignored--"
361 setting_tag_format = "Setting tag %s"
363 redirectors=[ # ('module_name_varname','name'),
364 ('module_version_varname','version'),
365 ('module_taglevel_varname','taglevel'), ]
367 # where to store user's config
368 config_storage="CONFIG"
373 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
374 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
375 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
376 ("build", "Enter the name of your build module","build"),
377 ('username',"Enter your firstname and lastname for changelogs",""),
378 ("email","Enter your email address for changelogs",""),
382 def prompt_config_option(cls, key, message, default):
383 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
386 def prompt_config (cls):
387 for (key,message,default) in cls.configKeys:
389 while not cls.config[key]:
390 cls.prompt_config_option(key, message, default)
393 # for parsing module spec name:branch
394 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
395 # special form for tagged module - for Build
396 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
398 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
400 def __init__ (self,module_spec,options):
402 attempt=Module.matcher_branch_spec.match(module_spec)
404 self.name=attempt.group('name')
405 self.branch=attempt.group('branch')
407 attempt=Module.matcher_tag_spec.match(module_spec)
409 self.name=attempt.group('name')
410 self.tagname=attempt.group('tagname')
412 self.name=module_spec
414 # when available prefer to use git module name internally
415 self.name = svn_to_git_name(self.name)
418 self.module_dir="%s/%s"%(options.workdir,self.name)
419 self.repository = None
422 def run (self,command):
423 return Command(command,self.options).run()
424 def run_fatal (self,command):
425 return Command(command,self.options).run_fatal()
426 def run_prompt (self,message,fun, *args):
427 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
428 if not self.options.verbose:
430 choice=prompt(message,True,('s','how'))
434 elif choice is False:
435 print 'About to run function:', fun_msg
437 question=message+" - want to run function: " + fun_msg
438 if prompt(question,True):
441 def friendly_name (self):
442 if hasattr(self,'branch'):
443 return "%s:%s"%(self.name,self.branch)
444 elif hasattr(self,'tagname'):
445 return "%s@%s"%(self.name,self.tagname)
450 def git_remote_dir (cls, name):
451 return "%s:/git/%s.git" % (cls.config['gitserver'], name)
454 def svn_remote_dir (cls, name):
455 name = git_to_svn_name(name)
456 svn = cls.config['svnpath']
457 if svn.endswith('/'):
458 return "%s%s" % (svn, name)
459 return "%s/%s" % (svn, name)
461 def svn_selected_remote(self):
462 svn_name = git_to_svn_name(self.name)
463 remote = self.svn_remote_dir(svn_name)
464 if hasattr(self,'branch'):
465 remote = "%s/branches/%s" % (remote, self.branch)
466 elif hasattr(self,'tagname'):
467 remote = "%s/tags/%s" % (remote, self.tagname)
469 remote = "%s/trunk" % remote
474 def init_homedir (cls, options):
475 if options.verbose and options.mode not in Main.silent_modes:
476 print 'Checking for', options.workdir
477 storage="%s/%s"%(options.workdir, cls.config_storage)
478 # sanity check. Either the topdir exists AND we have a config/storage
479 # or topdir does not exist and we create it
480 # to avoid people use their own daily svn repo
481 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
482 print """The directory %s exists and has no CONFIG file
483 If this is your regular working directory, please provide another one as the
484 module-* commands need a fresh working dir. Make sure that you do not use
485 that for other purposes than tagging""" % options.workdir
488 def checkout_build():
489 print "Checking out build module..."
490 remote = cls.git_remote_dir(cls.config['build'])
491 local = os.path.join(options.workdir, cls.config['build'])
492 GitRepository.checkout(remote, local, options, depth=1)
497 for (key,message,default) in Module.configKeys:
498 f.write("%s=%s\n"%(key,Module.config[key]))
501 print 'Stored',storage
502 Command("cat %s"%storage,options).run()
507 for line in f.readlines():
508 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
509 Module.config[key]=value
512 if not os.path.isdir (options.workdir):
513 print "Cannot find",options.workdir,"let's create it"
514 Command("mkdir -p %s" % options.workdir, options).run_silent()
520 # check missing config options
522 for (key,message,default) in cls.configKeys:
523 if not Module.config.has_key(key):
524 print "Configuration changed for module-tools"
525 cls.prompt_config_option(key, message, default)
529 Command("rm -rf %s" % options.workdir, options).run_silent()
530 Command("mkdir -p %s" % options.workdir, options).run_silent()
534 build_dir = os.path.join(options.workdir, cls.config['build'])
535 build = Repository(build_dir, options)
536 if not build.is_clean():
537 print "build module needs a revert"
542 if options.verbose and options.mode not in Main.silent_modes:
543 print '******** Using config'
544 for (key,message,default) in Module.configKeys:
545 print '\t',key,'=',Module.config[key]
547 def init_module_dir (self):
548 if self.options.verbose:
549 print 'Checking for',self.module_dir
551 if not os.path.isdir (self.module_dir):
552 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
553 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
557 remote = self.svn_selected_remote()
558 self.repository = SvnRepository.checkout(remote,
560 self.options, recursive=False)
562 self.repository = Repository(self.module_dir, self.options)
563 if self.repository.type == "svn":
564 # check if module has moved to git
565 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
566 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
567 self.init_module_dir()
568 # check if we have the required branch/tag
569 if self.repository.url() != self.svn_selected_remote():
570 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
571 self.init_module_dir()
573 elif self.repository.type == "git":
574 if hasattr(self,'branch'):
575 self.repository.to_branch(self.branch)
576 elif hasattr(self,'tagname'):
577 self.repository.to_tag(self.tagname)
580 raise Exception, 'Cannot find %s - check module name'%self.module_dir
583 def revert_module_dir (self):
584 if self.options.fast_checks:
585 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
587 if self.options.verbose:
588 print 'Checking whether', self.module_dir, 'needs being reverted'
590 if not self.repository.is_clean():
591 self.repository.revert()
593 def update_module_dir (self):
594 if self.options.fast_checks:
595 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
597 if self.options.verbose:
598 print 'Updating', self.module_dir
599 self.repository.update()
601 def main_specname (self):
602 attempt="%s/%s.spec"%(self.module_dir,self.name)
603 if os.path.isfile (attempt):
605 pattern1="%s/*.spec"%self.module_dir
606 level1=glob(pattern1)
609 pattern2="%s/*/*.spec"%self.module_dir
610 level2=glob(pattern2)
614 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
616 def all_specnames (self):
617 level1=glob("%s/*.spec" % self.module_dir)
618 if level1: return level1
619 level2=glob("%s/*/*.spec" % self.module_dir)
622 def parse_spec (self, specfile, varnames):
623 if self.options.verbose:
624 print 'Parsing',specfile,
630 for line in f.readlines():
631 attempt=Module.matcher_rpm_define.match(line)
633 (define,var,value)=attempt.groups()
637 if self.options.debug:
638 print 'found',len(result),'keys'
639 for (k,v) in result.iteritems():
643 # stores in self.module_name_varname the rpm variable to be used for the module's name
644 # and the list of these names in self.varnames
645 def spec_dict (self):
646 specfile=self.main_specname()
647 redirector_keys = [ varname for (varname,default) in Module.redirectors]
648 redirect_dict = self.parse_spec(specfile,redirector_keys)
649 if self.options.debug:
650 print '1st pass parsing done, redirect_dict=',redirect_dict
652 for (varname,default) in Module.redirectors:
653 if redirect_dict.has_key(varname):
654 setattr(self,varname,redirect_dict[varname])
655 varnames += [redirect_dict[varname]]
657 setattr(self,varname,default)
658 varnames += [ default ]
659 self.varnames = varnames
660 result = self.parse_spec (specfile,self.varnames)
661 if self.options.debug:
662 print '2st pass parsing done, varnames=',varnames,'result=',result
665 def patch_spec_var (self, patch_dict,define_missing=False):
666 for specfile in self.all_specnames():
667 # record the keys that were changed
668 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
669 newspecfile=specfile+".new"
670 if self.options.verbose:
671 print 'Patching',specfile,'for',patch_dict.keys()
673 new=open(newspecfile,"w")
675 for line in spec.readlines():
676 attempt=Module.matcher_rpm_define.match(line)
678 (define,var,value)=attempt.groups()
679 if var in patch_dict.keys():
680 if self.options.debug:
681 print 'rewriting %s as %s'%(var,patch_dict[var])
682 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
687 for (key,was_changed) in changed.iteritems():
689 if self.options.debug:
690 print 'rewriting missing %s as %s'%(key,patch_dict[key])
691 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
694 os.rename(newspecfile,specfile)
696 # returns all lines until the magic line
697 def unignored_lines (self, logfile):
699 white_line_matcher = re.compile("\A\s*\Z")
700 for logline in file(logfile).readlines():
701 if logline.strip() == Module.svn_magic_line:
703 elif white_line_matcher.match(logline):
706 result.append(logline.strip()+'\n')
709 # creates a copy of the input with only the unignored lines
710 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
712 f.write(self.setting_tag_format%new_tag_name + '\n')
713 for line in self.unignored_lines(filein):
717 def insert_changelog (self, logfile, oldtag, newtag):
718 for specfile in self.all_specnames():
719 newspecfile=specfile+".new"
720 if self.options.verbose:
721 print 'Inserting changelog from %s into %s'%(logfile,specfile)
723 new=open(newspecfile,"w")
724 for line in spec.readlines():
726 if re.compile('%changelog').match(line):
727 dateformat="* %a %b %d %Y"
728 datepart=time.strftime(dateformat)
729 logpart="%s <%s> - %s"%(Module.config['username'],
730 Module.config['email'],
732 new.write(datepart+" "+logpart+"\n")
733 for logline in self.unignored_lines(logfile):
734 new.write("- " + logline)
738 os.rename(newspecfile,specfile)
740 def show_dict (self, spec_dict):
741 if self.options.verbose:
742 for (k,v) in spec_dict.iteritems():
745 def last_tag (self, spec_dict):
747 return "%s-%s" % (spec_dict[self.module_version_varname],
748 spec_dict[self.module_taglevel_varname])
750 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
752 def tag_name (self, spec_dict, old_svn_name=False):
753 base_tag_name = self.name
755 base_tag_name = git_to_svn_name(self.name)
756 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
759 ##############################
760 # using fine_grain means replacing only those instances that currently refer to this tag
761 # otherwise, <module>-SVNPATH is replaced unconditionnally
762 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
763 newtagsfile=tagsfile+".new"
765 new=open(newtagsfile,"w")
768 # fine-grain : replace those lines that refer to oldname
770 if self.options.verbose:
771 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
772 matcher=re.compile("^(.*)%s(.*)"%oldname)
773 for line in tags.readlines():
774 if not matcher.match(line):
777 (begin,end)=matcher.match(line).groups()
778 new.write(begin+newname+end+"\n")
780 # brute-force : change uncommented lines that define <module>-SVNPATH
782 if self.options.verbose:
783 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
784 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
786 matcher_module=re.compile(pattern)
787 for line in tags.readlines():
788 attempt=matcher_module.match(line)
790 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
791 if self.options.verbose:
793 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
794 new.write(replacement)
800 os.rename(newtagsfile,tagsfile)
801 if self.options.verbose: print "%d changes"%matches
804 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
805 if self.options.verbose:
806 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
808 found_tagname = tagname
809 found = self.repository.tag_exists(tagname)
810 if not found and old_svn_tag_name:
811 if self.options.verbose:
813 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
814 found = self.repository.tag_exists(old_svn_tag_name)
816 found_tagname = old_svn_tag_name
818 if (found and need_it) or (not found and not need_it):
823 raise Exception, "tag (%s) is already there" % tagname
825 raise Exception, "can not find required tag (%s)" % tagname
831 self.init_module_dir()
832 self.revert_module_dir()
833 self.update_module_dir()
835 spec_dict = self.spec_dict()
836 self.show_dict(spec_dict)
839 old_tag_name = self.tag_name(spec_dict)
840 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
842 if (self.options.new_version):
843 # new version set on command line
844 spec_dict[self.module_version_varname] = self.options.new_version
845 spec_dict[self.module_taglevel_varname] = 0
848 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
849 spec_dict[self.module_taglevel_varname] = new_taglevel
851 new_tag_name = self.tag_name(spec_dict)
854 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
855 new_tag_name = self.check_tag(new_tag_name, need_it=False)
858 diff_output = self.repository.diff_with_tag(old_tag_name)
859 if len(diff_output) == 0:
860 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
863 # side effect in trunk's specfile
864 self.patch_spec_var(spec_dict)
866 # prepare changelog file
867 # we use the standard subversion magic string (see svn_magic_line)
868 # so we can provide useful information, such as version numbers and diff
870 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
871 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
872 setting_tag_line=Module.setting_tag_format%new_tag_name
873 file(changelog,"w").write("""
876 Please write a changelog for this new tag in the section above
877 """%(Module.svn_magic_line,setting_tag_line))
879 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
880 file(changelog,"a").write('DIFF=========\n' + diff_output)
882 if self.options.debug:
886 self.run("%s %s"%(self.options.editor,changelog))
887 # strip magic line in second file - looks like svn has changed its magic line with 1.6
888 # so we do the job ourselves
889 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
890 # insert changelog in spec
891 if self.options.changelog:
892 self.insert_changelog (changelog,old_tag_name,new_tag_name)
895 build_path = os.path.join(self.options.workdir,
896 Module.config['build'])
897 build = Repository(build_path, self.options)
898 if self.options.build_branch:
899 build.to_branch(self.options.build_branch)
900 if not build.is_clean():
903 tagsfiles=glob(build.path+"/*-tags*.mk")
904 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
908 for tagsfile in tagsfiles:
909 status=tagsdict[tagsfile]
910 basename=os.path.basename(tagsfile)
911 print ".................... Dealing with %s"%basename
912 while tagsdict[tagsfile] == 'todo' :
913 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
914 [ ('y','es'), ('n', 'ext'), ('f','orce'),
915 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
918 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
920 print 'Done with %s'%os.path.basename(tagsfile)
921 tagsdict[tagsfile]='done'
923 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
925 self.run("svn diff %s"%tagsfile)
927 self.run("svn revert %s"%tagsfile)
929 self.run("cat %s"%tagsfile)
932 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
933 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
934 d: show current diff for this tag file
935 r: revert that tag file
936 c: cat the current tag file
937 n: move to next file"""%locals()
939 if prompt("Want to review changes on tags files",False):
940 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
945 def diff_all_changes():
947 print self.repository.diff()
949 def commit_all_changes(log):
950 self.repository.commit(log)
953 self.run_prompt("Review module and build", diff_all_changes)
954 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
955 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
957 if self.options.debug:
958 print 'Preserving',changelog,'and stripped',changelog_svn
961 os.unlink(changelog_svn)
964 ##############################
967 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
969 module-tools : a set of tools to manage subversion tags and specfile
970 requires the specfile to either
971 * define *version* and *taglevel*
973 * define redirection variables module_version_varname / module_taglevel_varname
975 by default, the trunk of modules is taken into account
976 in this case, just mention the module name as <module_desc>
978 if you wish to work on a branch rather than on the trunk,
979 you can use something like e.g. Mom:2.1 as <module_desc>
981 release_usage="""Usage: %prog [options] tag1 .. tagn
982 Extract release notes from the changes in specfiles between several build tags, latest first
984 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
985 You can refer to a (build) branch by prepending a colon, like in
986 release-changelog :4.2 4.2-rc25
987 You can refer to the build trunk by just mentioning 'trunk', e.g.
988 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
990 common_usage="""More help:
991 see http://svn.planet-lab.org/wiki/ModuleTools"""
994 'list' : "displays a list of available tags or branches",
995 'version' : "check latest specfile and print out details",
996 'diff' : "show difference between module (trunk or branch) and latest tag",
997 'tag' : """increment taglevel in specfile, insert changelog in specfile,
998 create new tag and and monitor its adoption in build/*-tags*.mk""",
999 'branch' : """create a branch for this module, from the latest tag on the trunk,
1000 and change trunk's version number to reflect the new branch name;
1001 you can specify the new branch name by using module:branch""",
1002 'sync' : """create a tag from the module
1003 this is a last resort option, mostly for repairs""",
1004 'changelog' : """extract changelog between build tags
1005 expected arguments are a list of tags""",
1008 silent_modes = ['list']
1009 release_modes = ['changelog']
1012 def optparse_list (option, opt, value, parser):
1014 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
1016 setattr(parser.values,option.dest,value.split())
1021 for function in Main.modes.keys():
1022 if sys.argv[0].find(function) >= 0:
1026 print "Unsupported command",sys.argv[0]
1027 print "Supported commands:" + " ".join(Main.modes.keys())
1030 if mode not in Main.release_modes:
1031 usage = Main.module_usage
1032 usage += Main.common_usage
1033 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1035 usage = Main.release_usage
1036 usage += Main.common_usage
1038 parser=OptionParser(usage=usage)
1040 if mode == "tag" or mode == 'branch':
1041 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1042 help="set new version and reset taglevel to 0")
1044 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1045 help="do not update changelog section in specfile when tagging")
1046 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1047 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1048 if mode == "tag" or mode == "sync" :
1049 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1050 help="specify editor")
1052 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1053 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1054 help="dry run - shell commands are only displayed")
1055 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1056 default=[], nargs=1,type="string",
1057 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1058 -- can be set multiple times, or use quotes""")
1060 parser.add_option("-w","--workdir", action="store", dest="workdir",
1061 default="%s/%s"%(os.getenv("HOME"),"modules"),
1062 help="""name for dedicated working dir - defaults to ~/modules
1063 ** THIS MUST NOT ** be your usual working directory""")
1064 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1065 help="skip safety checks, such as svn updates -- use with care")
1067 # default verbosity depending on function - temp
1068 verbose_modes= ['tag', 'sync', 'branch']
1070 if mode not in verbose_modes:
1071 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1072 help="run in verbose mode")
1074 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1075 help="run in quiet (non-verbose) mode")
1076 (options, args) = parser.parse_args()
1078 if not hasattr(options,'dry_run'):
1079 options.dry_run=False
1080 if not hasattr(options,'www'):
1088 Module.init_homedir(options)
1090 modules=[ Module(modname,options) for modname in args ]
1091 for module in modules:
1092 if len(args)>1 and mode not in Main.silent_modes:
1093 print '========================================',module.friendly_name()
1094 # call the method called do_<mode>
1095 method=Module.__dict__["do_%s"%mode]
1100 traceback.print_exc()
1101 print 'Skipping module %s: '%modname,e
1103 ####################
1104 if __name__ == "__main__" :
1107 except KeyboardInterrupt: