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 root = line.split()[2].strip()
165 return "%s/%s" % (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()
213 def diff(self, f=""):
215 f = os.path.join(self.path, f)
218 return Command("svn diff %s" % f, self.options).output_of(True)
220 def diff_with_tag(self, tagname):
221 tag_url = "%s/tags/%s" % (self.repo_root(), tagname)
222 return Command("svn diff %s %s" % (tag_url, self.url()),
223 self.options).output_of(True)
225 def revert(self, f=""):
227 Command("svn revert %s" % os.path.join(self.path, f), self.options).run_fatal()
230 Command("svn revert %s -R" % self.path, self.options).run_fatal()
231 Command("svn status %s | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\\ /g' | xargs rm -rf " %
232 self.path, self.options).run_silent()
235 command="svn status %s" % self.path
236 return len(Command(command,self.options).output_of(True)) == 0
239 return os.path.exists(os.path.join(self.path, ".svn"))
245 def __init__(self, path, options):
247 self.options = options
250 return os.path.basename(self.path)
256 c = Command("git remote show origin", self.options)
257 out = self.__run_in_repo(c.output_of)
258 for line in out.split('\n'):
259 if line.strip().startswith("Fetch URL:"):
260 repo = line.split()[2]
263 def checkout(cls, remote, local, options, depth=1):
264 Command("rm -rf %s" % local, options).run_silent()
265 Command("git clone --depth %d %s %s" % (depth, remote, local), options).run_fatal()
266 return GitRepository(local, options)
269 def remote_exists(cls, remote):
270 return os.system("git --no-pager ls-remote %s &> /dev/null" % remote) == 0
272 def tag_exists(self, tagname):
273 command = 'git tag -l | grep "^%s$"' % tagname
274 c = Command(command, self.options)
275 out = self.__run_in_repo(c.output_of, with_stderr=True)
278 def __run_in_repo(self, fun, *args, **kwargs):
281 ret = fun(*args, **kwargs)
285 def __run_command_in_repo(self, command, ignore_errors=False):
286 c = Command(command, self.options)
288 return self.__run_in_repo(c.output_of)
290 return self.__run_in_repo(c.run_fatal)
292 def update(self, subdir=None, recursive=None):
293 self.__run_command_in_repo("git pull")
294 self.__run_command_in_repo("git pull --tags")
296 def to_branch(self, branch, remote=True):
298 branch = "origin/%s" % branch
299 return self.__run_command_in_repo("git checkout %s" % branch)
301 def to_tag(self, tag):
302 return self.__run_command_in_repo("git checkout %s" % tag)
304 def tag(self, tagname, logfile):
305 self.__run_command_in_repo("git tag %s -F %s" % (tagname, logfile))
308 def diff(self, f=""):
309 c = Command("git diff %s" % f, self.options)
310 return self.__run_in_repo(c.output_of, with_stderr=True)
312 def diff_with_tag(self, tagname):
313 c = Command("git diff %s" % tagname, self.options)
314 return self.__run_in_repo(c.output_of, with_stderr=True)
316 def commit(self, logfile):
317 self.__run_command_in_repo("git add -A", ignore_errors=True)
318 self.__run_command_in_repo("git commit -F %s" % logfile, ignore_errors=True)
319 self.__run_command_in_repo("git push")
320 self.__run_command_in_repo("git push --tags")
322 def revert(self, f=""):
324 self.__run_command_in_repo("git checkout %s" % f)
327 self.__run_command_in_repo("git --no-pager reset --hard")
328 self.__run_command_in_repo("git --no-pager clean -f")
333 s="nothing to commit (working directory clean)"
334 return Command(command, self.options).output_of(True).find(s) >= 0
335 return self.__run_in_repo(check_commit)
338 return os.path.exists(os.path.join(self.path, ".git"))
342 """ Generic repository """
343 supported_repo_types = [SvnRepository, GitRepository]
345 def __init__(self, path, options):
347 self.options = options
348 for repo in self.supported_repo_types:
349 self.repo = repo(self.path, self.options)
350 if self.repo.is_valid():
354 def has_moved_to_git(cls, module, svnpath):
355 module = git_to_svn_name(module)
356 return SvnRepository.remote_exists("%s/%s/aaaa-has-moved-to-git" % (svnpath, module))
359 def remote_exists(cls, remote):
360 for repo in Repository.supported_repo_types:
361 if repo.remote_exists(remote):
365 def __getattr__(self, attr):
366 return getattr(self.repo, attr)
370 # support for tagged module is minimal, and is for the Build class only
373 svn_magic_line="--This line, and those below, will be ignored--"
374 setting_tag_format = "Setting tag %s"
376 redirectors=[ # ('module_name_varname','name'),
377 ('module_version_varname','version'),
378 ('module_taglevel_varname','taglevel'), ]
380 # where to store user's config
381 config_storage="CONFIG"
386 configKeys=[ ('svnpath',"Enter your toplevel svnpath",
387 "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
388 ('gitserver', "Enter your git server's hostname", "git.onelab.eu"),
389 ('gituser', "Enter your user name (login name) on git server", os.getlogin()),
390 ("build", "Enter the name of your build module","build"),
391 ('username',"Enter your firstname and lastname for changelogs",""),
392 ("email","Enter your email address for changelogs",""),
396 def prompt_config_option(cls, key, message, default):
397 cls.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
400 def prompt_config (cls):
401 for (key,message,default) in cls.configKeys:
403 while not cls.config[key]:
404 cls.prompt_config_option(key, message, default)
407 # for parsing module spec name:branch
408 matcher_branch_spec=re.compile("\A(?P<name>[\w\.-]+):(?P<branch>[\w\.-]+)\Z")
409 # special form for tagged module - for Build
410 matcher_tag_spec=re.compile("\A(?P<name>[\w-]+)@(?P<tagname>[\w\.-]+)\Z")
412 matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
414 def __init__ (self,module_spec,options):
416 attempt=Module.matcher_branch_spec.match(module_spec)
418 self.name=attempt.group('name')
419 self.branch=attempt.group('branch')
421 attempt=Module.matcher_tag_spec.match(module_spec)
423 self.name=attempt.group('name')
424 self.tagname=attempt.group('tagname')
426 self.name=module_spec
428 # when available prefer to use git module name internally
429 self.name = svn_to_git_name(self.name)
432 self.module_dir="%s/%s"%(options.workdir,self.name)
433 self.repository = None
436 def run (self,command):
437 return Command(command,self.options).run()
438 def run_fatal (self,command):
439 return Command(command,self.options).run_fatal()
440 def run_prompt (self,message,fun, *args):
441 fun_msg = "%s(%s)" % (fun.func_name, ",".join(args))
442 if not self.options.verbose:
444 choice=prompt(message,True,('s','how'))
448 elif choice is False:
449 print 'About to run function:', fun_msg
451 question=message+" - want to run function: " + fun_msg
452 if prompt(question,True):
455 def friendly_name (self):
456 if hasattr(self,'branch'):
457 return "%s:%s"%(self.name,self.branch)
458 elif hasattr(self,'tagname'):
459 return "%s@%s"%(self.name,self.tagname)
464 def git_remote_dir (cls, name):
465 return "%s@%s:/git/%s.git" % (cls.config['gituser'], cls.config['gitserver'], name)
468 def svn_remote_dir (cls, name):
469 name = git_to_svn_name(name)
470 svn = cls.config['svnpath']
471 if svn.endswith('/'):
472 return "%s%s" % (svn, name)
473 return "%s/%s" % (svn, name)
475 def svn_selected_remote(self):
476 svn_name = git_to_svn_name(self.name)
477 remote = self.svn_remote_dir(svn_name)
478 if hasattr(self,'branch'):
479 remote = "%s/branches/%s" % (remote, self.branch)
480 elif hasattr(self,'tagname'):
481 remote = "%s/tags/%s" % (remote, self.tagname)
483 remote = "%s/trunk" % remote
488 def init_homedir (cls, options):
489 if options.verbose and options.mode not in Main.silent_modes:
490 print 'Checking for', options.workdir
491 storage="%s/%s"%(options.workdir, cls.config_storage)
492 # sanity check. Either the topdir exists AND we have a config/storage
493 # or topdir does not exist and we create it
494 # to avoid people use their own daily svn repo
495 if os.path.isdir(options.workdir) and not os.path.isfile(storage):
496 print """The directory %s exists and has no CONFIG file
497 If this is your regular working directory, please provide another one as the
498 module-* commands need a fresh working dir. Make sure that you do not use
499 that for other purposes than tagging""" % options.workdir
502 def checkout_build():
503 print "Checking out build module..."
504 remote = cls.git_remote_dir(cls.config['build'])
505 local = os.path.join(options.workdir, cls.config['build'])
506 GitRepository.checkout(remote, local, options, depth=1)
511 for (key,message,default) in Module.configKeys:
512 f.write("%s=%s\n"%(key,Module.config[key]))
515 print 'Stored',storage
516 Command("cat %s"%storage,options).run()
521 for line in f.readlines():
522 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
523 Module.config[key]=value
526 if not os.path.isdir (options.workdir):
527 print "Cannot find",options.workdir,"let's create it"
528 Command("mkdir -p %s" % options.workdir, options).run_silent()
534 # check missing config options
536 for (key,message,default) in cls.configKeys:
537 if not Module.config.has_key(key):
538 print "Configuration changed for module-tools"
539 cls.prompt_config_option(key, message, default)
543 Command("rm -rf %s" % options.workdir, options).run_silent()
544 Command("mkdir -p %s" % options.workdir, options).run_silent()
548 build_dir = os.path.join(options.workdir, cls.config['build'])
549 if not os.path.isdir(build_dir):
552 build = Repository(build_dir, options)
553 if not build.is_clean():
554 print "build module needs a revert"
559 if options.verbose and options.mode not in Main.silent_modes:
560 print '******** Using config'
561 for (key,message,default) in Module.configKeys:
562 print '\t',key,'=',Module.config[key]
564 def init_module_dir (self):
565 if self.options.verbose:
566 print 'Checking for',self.module_dir
568 if not os.path.isdir (self.module_dir):
569 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
570 self.repository = GitRepository.checkout(self.git_remote_dir(self.name),
574 remote = self.svn_selected_remote()
575 self.repository = SvnRepository.checkout(remote,
577 self.options, recursive=False)
579 self.repository = Repository(self.module_dir, self.options)
580 if self.repository.type == "svn":
581 # check if module has moved to git
582 if Repository.has_moved_to_git(self.name, Module.config['svnpath']):
583 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
584 self.init_module_dir()
585 # check if we have the required branch/tag
586 if self.repository.url() != self.svn_selected_remote():
587 Command("rm -rf %s" % self.module_dir, self.options).run_silent()
588 self.init_module_dir()
590 elif self.repository.type == "git":
591 if hasattr(self,'branch'):
592 self.repository.to_branch(self.branch)
593 elif hasattr(self,'tagname'):
594 self.repository.to_tag(self.tagname)
597 raise Exception, 'Cannot find %s - check module name'%self.module_dir
600 def revert_module_dir (self):
601 if self.options.fast_checks:
602 if self.options.verbose: print 'Skipping revert of %s' % self.module_dir
604 if self.options.verbose:
605 print 'Checking whether', self.module_dir, 'needs being reverted'
607 if not self.repository.is_clean():
608 self.repository.revert()
610 def update_module_dir (self):
611 if self.options.fast_checks:
612 if self.options.verbose: print 'Skipping update of %s' % self.module_dir
614 if self.options.verbose:
615 print 'Updating', self.module_dir
616 self.repository.update()
618 def main_specname (self):
619 attempt="%s/%s.spec"%(self.module_dir,self.name)
620 if os.path.isfile (attempt):
622 pattern1="%s/*.spec"%self.module_dir
623 level1=glob(pattern1)
626 pattern2="%s/*/*.spec"%self.module_dir
627 level2=glob(pattern2)
631 raise Exception, 'Cannot guess specfile for module %s -- patterns were %s or %s'%(self.name,pattern1,pattern2)
633 def all_specnames (self):
634 level1=glob("%s/*.spec" % self.module_dir)
635 if level1: return level1
636 level2=glob("%s/*/*.spec" % self.module_dir)
639 def parse_spec (self, specfile, varnames):
640 if self.options.verbose:
641 print 'Parsing',specfile,
647 for line in f.readlines():
648 attempt=Module.matcher_rpm_define.match(line)
650 (define,var,value)=attempt.groups()
654 if self.options.debug:
655 print 'found',len(result),'keys'
656 for (k,v) in result.iteritems():
660 # stores in self.module_name_varname the rpm variable to be used for the module's name
661 # and the list of these names in self.varnames
662 def spec_dict (self):
663 specfile=self.main_specname()
664 redirector_keys = [ varname for (varname,default) in Module.redirectors]
665 redirect_dict = self.parse_spec(specfile,redirector_keys)
666 if self.options.debug:
667 print '1st pass parsing done, redirect_dict=',redirect_dict
669 for (varname,default) in Module.redirectors:
670 if redirect_dict.has_key(varname):
671 setattr(self,varname,redirect_dict[varname])
672 varnames += [redirect_dict[varname]]
674 setattr(self,varname,default)
675 varnames += [ default ]
676 self.varnames = varnames
677 result = self.parse_spec (specfile,self.varnames)
678 if self.options.debug:
679 print '2st pass parsing done, varnames=',varnames,'result=',result
682 def patch_spec_var (self, patch_dict,define_missing=False):
683 for specfile in self.all_specnames():
684 # record the keys that were changed
685 changed = dict ( [ (x,False) for x in patch_dict.keys() ] )
686 newspecfile=specfile+".new"
687 if self.options.verbose:
688 print 'Patching',specfile,'for',patch_dict.keys()
690 new=open(newspecfile,"w")
692 for line in spec.readlines():
693 attempt=Module.matcher_rpm_define.match(line)
695 (define,var,value)=attempt.groups()
696 if var in patch_dict.keys():
697 if self.options.debug:
698 print 'rewriting %s as %s'%(var,patch_dict[var])
699 new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
704 for (key,was_changed) in changed.iteritems():
706 if self.options.debug:
707 print 'rewriting missing %s as %s'%(key,patch_dict[key])
708 new.write('\n%%define %s %s\n'%(key,patch_dict[key]))
711 os.rename(newspecfile,specfile)
713 # returns all lines until the magic line
714 def unignored_lines (self, logfile):
716 white_line_matcher = re.compile("\A\s*\Z")
717 for logline in file(logfile).readlines():
718 if logline.strip() == Module.svn_magic_line:
720 elif white_line_matcher.match(logline):
723 result.append(logline.strip()+'\n')
726 # creates a copy of the input with only the unignored lines
727 def stripped_magic_line_filename (self, filein, fileout ,new_tag_name):
729 f.write(self.setting_tag_format%new_tag_name + '\n')
730 for line in self.unignored_lines(filein):
734 def insert_changelog (self, logfile, oldtag, newtag):
735 for specfile in self.all_specnames():
736 newspecfile=specfile+".new"
737 if self.options.verbose:
738 print 'Inserting changelog from %s into %s'%(logfile,specfile)
740 new=open(newspecfile,"w")
741 for line in spec.readlines():
743 if re.compile('%changelog').match(line):
744 dateformat="* %a %b %d %Y"
745 datepart=time.strftime(dateformat)
746 logpart="%s <%s> - %s"%(Module.config['username'],
747 Module.config['email'],
749 new.write(datepart+" "+logpart+"\n")
750 for logline in self.unignored_lines(logfile):
751 new.write("- " + logline)
755 os.rename(newspecfile,specfile)
757 def show_dict (self, spec_dict):
758 if self.options.verbose:
759 for (k,v) in spec_dict.iteritems():
762 def last_tag (self, spec_dict):
764 return "%s-%s" % (spec_dict[self.module_version_varname],
765 spec_dict[self.module_taglevel_varname])
767 raise Exception,'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
769 def tag_name (self, spec_dict, old_svn_name=False):
770 base_tag_name = self.name
772 base_tag_name = git_to_svn_name(self.name)
773 return "%s-%s" % (base_tag_name, self.last_tag(spec_dict))
776 ##############################
777 # using fine_grain means replacing only those instances that currently refer to this tag
778 # otherwise, <module>-SVNPATH is replaced unconditionnally
779 def patch_tags_file (self, tagsfile, oldname, newname,fine_grain=True):
780 newtagsfile=tagsfile+".new"
782 new=open(newtagsfile,"w")
785 # fine-grain : replace those lines that refer to oldname
787 if self.options.verbose:
788 print 'Replacing %s into %s\n\tin %s .. '%(oldname,newname,tagsfile),
789 matcher=re.compile("^(.*)%s(.*)"%oldname)
790 for line in tags.readlines():
791 if not matcher.match(line):
794 (begin,end)=matcher.match(line).groups()
795 new.write(begin+newname+end+"\n")
797 # brute-force : change uncommented lines that define <module>-SVNPATH
799 if self.options.verbose:
800 print 'Searching for -SVNPATH lines referring to /%s/\n\tin %s .. '%(self.name,tagsfile),
801 pattern="\A\s*(?P<make_name>[^\s]+)-SVNPATH\s*(=|:=)\s*(?P<url_main>[^\s]+)/%s/[^\s]+"\
803 matcher_module=re.compile(pattern)
804 for line in tags.readlines():
805 attempt=matcher_module.match(line)
807 svnpath="%s-SVNPATH"%(attempt.group('make_name'))
808 if self.options.verbose:
810 replacement = "%-32s:= %s/%s/tags/%s\n"%(svnpath,attempt.group('url_main'),self.name,newname)
811 new.write(replacement)
817 os.rename(newtagsfile,tagsfile)
818 if self.options.verbose: print "%d changes"%matches
821 def check_tag(self, tagname, need_it=False, old_svn_tag_name=None):
822 if self.options.verbose:
823 print "Checking %s repository tag: %s - " % (self.repository.type, tagname),
825 found_tagname = tagname
826 found = self.repository.tag_exists(tagname)
827 if not found and old_svn_tag_name:
828 if self.options.verbose:
830 print "Checking %s repository tag: %s - " % (self.repository.type, old_svn_tag_name),
831 found = self.repository.tag_exists(old_svn_tag_name)
833 found_tagname = old_svn_tag_name
835 if (found and need_it) or (not found and not need_it):
836 if self.options.verbose:
838 if found: print "- found"
839 else: print "- not found"
841 if self.options.verbose:
844 raise Exception, "tag (%s) is already there" % tagname
846 raise Exception, "can not find required tag (%s)" % tagname
851 ##############################
853 self.init_module_dir()
854 self.revert_module_dir()
855 self.update_module_dir()
857 spec_dict = self.spec_dict()
858 self.show_dict(spec_dict)
861 old_tag_name = self.tag_name(spec_dict)
862 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
864 if (self.options.new_version):
865 # new version set on command line
866 spec_dict[self.module_version_varname] = self.options.new_version
867 spec_dict[self.module_taglevel_varname] = 0
870 new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
871 spec_dict[self.module_taglevel_varname] = new_taglevel
873 new_tag_name = self.tag_name(spec_dict)
876 old_tag_name = self.check_tag(old_tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
877 new_tag_name = self.check_tag(new_tag_name, need_it=False)
880 diff_output = self.repository.diff_with_tag(old_tag_name)
881 if len(diff_output) == 0:
882 if not prompt ("No pending difference in module %s, want to tag anyway"%self.name,False):
885 # side effect in trunk's specfile
886 self.patch_spec_var(spec_dict)
888 # prepare changelog file
889 # we use the standard subversion magic string (see svn_magic_line)
890 # so we can provide useful information, such as version numbers and diff
892 changelog="/tmp/%s-%d.edit"%(self.name,os.getpid())
893 changelog_svn="/tmp/%s-%d.svn"%(self.name,os.getpid())
894 setting_tag_line=Module.setting_tag_format%new_tag_name
895 file(changelog,"w").write("""
898 Please write a changelog for this new tag in the section above
899 """%(Module.svn_magic_line,setting_tag_line))
901 if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
902 file(changelog,"a").write('DIFF=========\n' + diff_output)
904 if self.options.debug:
908 self.run("%s %s"%(self.options.editor,changelog))
909 # strip magic line in second file - looks like svn has changed its magic line with 1.6
910 # so we do the job ourselves
911 self.stripped_magic_line_filename(changelog,changelog_svn,new_tag_name)
912 # insert changelog in spec
913 if self.options.changelog:
914 self.insert_changelog (changelog,old_tag_name,new_tag_name)
917 build_path = os.path.join(self.options.workdir,
918 Module.config['build'])
919 build = Repository(build_path, self.options)
920 if self.options.build_branch:
921 build.to_branch(self.options.build_branch)
922 if not build.is_clean():
925 tagsfiles=glob(build.path+"/*-tags*.mk")
926 tagsdict=dict( [ (x,'todo') for x in tagsfiles ] )
930 for tagsfile in tagsfiles:
931 status=tagsdict[tagsfile]
932 basename=os.path.basename(tagsfile)
933 print ".................... Dealing with %s"%basename
934 while tagsdict[tagsfile] == 'todo' :
935 choice = prompt ("insert %s in %s "%(new_tag_name,basename),default_answer,
936 [ ('y','es'), ('n', 'ext'), ('f','orce'),
937 ('d','iff'), ('r','evert'), ('c', 'at'), ('h','elp') ] ,
940 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=True)
942 print 'Done with %s'%os.path.basename(tagsfile)
943 tagsdict[tagsfile]='done'
945 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name,fine_grain=False)
947 print build.diff(f=tagsfile)
949 build.revert(f=tagsfile)
951 self.run("cat %s"%tagsfile)
954 print """y: change %(name)s-SVNPATH only if it currently refers to %(old_tag_name)s
955 f: unconditionnally change any line that assigns %(name)s-SVNPATH to using %(new_tag_name)s
956 d: show current diff for this tag file
957 r: revert that tag file
958 c: cat the current tag file
959 n: move to next file"""%locals()
961 if prompt("Want to review changes on tags files",False):
962 tagsdict = dict ( [ (x, 'todo') for x in tagsfiles ] )
967 def diff_all_changes():
969 print self.repository.diff()
971 def commit_all_changes(log):
972 self.repository.commit(log)
975 self.run_prompt("Review module and build", diff_all_changes)
976 self.run_prompt("Commit module and build", commit_all_changes, changelog_svn)
977 self.run_prompt("Create tag", self.repository.tag, new_tag_name, changelog_svn)
979 if self.options.debug:
980 print 'Preserving',changelog,'and stripped',changelog_svn
983 os.unlink(changelog_svn)
986 ##############################
987 def do_version (self):
988 self.init_module_dir()
989 self.revert_module_dir()
990 self.update_module_dir()
991 spec_dict = self.spec_dict()
993 self.html_store_title('Version for module %s (%s)' % (self.friendly_name(),
994 self.last_tag(spec_dict)))
995 for varname in self.varnames:
996 if not spec_dict.has_key(varname):
997 self.html_print ('Could not find %%define for %s'%varname)
1000 self.html_print ("%-16s %s"%(varname,spec_dict[varname]))
1001 if self.options.verbose:
1002 self.html_print ("%-16s %s"%('main specfile:',self.main_specname()))
1003 self.html_print ("%-16s %s"%('specfiles:',self.all_specnames()))
1004 self.html_print_end()
1007 ##############################
1009 self.init_module_dir()
1010 self.revert_module_dir()
1011 self.update_module_dir()
1012 spec_dict = self.spec_dict()
1013 self.show_dict(spec_dict)
1016 tag_name = self.tag_name(spec_dict)
1017 old_svn_tag_name = self.tag_name(spec_dict, old_svn_name=True)
1020 tag_name = self.check_tag(tag_name, need_it=True, old_svn_tag_name=old_svn_tag_name)
1022 if self.options.verbose:
1023 print 'Getting diff'
1024 diff_output = self.repository.diff_with_tag(tag_name)
1026 if self.options.list:
1030 thename=self.friendly_name()
1032 if self.options.www and diff_output:
1033 self.html_store_title("Diffs in module %s (%s) : %d chars"%(\
1034 thename,self.last_tag(spec_dict),len(diff_output)))
1036 self.html_store_raw ('<p> < (left) %s </p>' % tag_name)
1037 self.html_store_raw ('<p> > (right) %s </p>' % thename)
1038 self.html_store_pre (diff_output)
1039 elif not self.options.www:
1040 print 'x'*30,'module',thename
1041 print 'x'*20,'<',tag_name
1042 print 'x'*20,'>',thename
1045 ##############################
1046 # store and restitute html fragments
1048 def html_href (url,text): return '<a href="%s">%s</a>'%(url,text)
1051 def html_anchor (url,text): return '<a name="%s">%s</a>'%(url,text)
1054 def html_quote (text):
1055 return text.replace('&','&').replace('<','<').replace('>','>')
1057 # only the fake error module has multiple titles
1058 def html_store_title (self, title):
1059 if not hasattr(self,'titles'): self.titles=[]
1060 self.titles.append(title)
1062 def html_store_raw (self, html):
1063 if not hasattr(self,'body'): self.body=''
1066 def html_store_pre (self, text):
1067 if not hasattr(self,'body'): self.body=''
1068 self.body += '<pre>' + self.html_quote(text) + '</pre>'
1070 def html_print (self, txt):
1071 if not self.options.www:
1074 if not hasattr(self,'in_list') or not self.in_list:
1075 self.html_store_raw('<ul>')
1077 self.html_store_raw('<li>'+txt+'</li>')
1079 def html_print_end (self):
1080 if self.options.www:
1081 self.html_store_raw ('</ul>')
1084 def html_dump_header(title):
1085 nowdate=time.strftime("%Y-%m-%d")
1086 nowtime=time.strftime("%H:%M (%Z)")
1087 print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1088 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
1091 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1092 <style type="text/css">
1093 body { font-family:georgia, serif; }
1094 h1 {font-size: large; }
1095 p.title {font-size: x-large; }
1096 span.error {text-weight:bold; color: red; }
1100 <p class='title'> %s - status on %s at %s</p>
1102 """%(title,title,nowdate,nowtime)
1105 def html_dump_middle():
1109 def html_dump_footer():
1110 print "</body></html"
1112 def html_dump_toc(self):
1113 if hasattr(self,'titles'):
1114 for title in self.titles:
1115 print '<li>',self.html_href ('#'+self.friendly_name(),title),'</li>'
1117 def html_dump_body(self):
1118 if hasattr(self,'titles'):
1119 for title in self.titles:
1120 print '<hr /><h1>',self.html_anchor(self.friendly_name(),title),'</h1>'
1121 if hasattr(self,'body'):
1123 print '<p class="top">',self.html_href('#','Back to top'),'</p>'
1126 ##############################
1129 module_usage="""Usage: %prog [options] module_desc [ .. module_desc ]
1131 module-tools : a set of tools to manage subversion tags and specfile
1132 requires the specfile to either
1133 * define *version* and *taglevel*
1135 * define redirection variables module_version_varname / module_taglevel_varname
1137 by default, the trunk of modules is taken into account
1138 in this case, just mention the module name as <module_desc>
1140 if you wish to work on a branch rather than on the trunk,
1141 you can use something like e.g. Mom:2.1 as <module_desc>
1143 release_usage="""Usage: %prog [options] tag1 .. tagn
1144 Extract release notes from the changes in specfiles between several build tags, latest first
1146 release-changelog 4.2-rc25 4.2-rc24 4.2-rc23 4.2-rc22
1147 You can refer to a (build) branch by prepending a colon, like in
1148 release-changelog :4.2 4.2-rc25
1149 You can refer to the build trunk by just mentioning 'trunk', e.g.
1150 release-changelog -t coblitz-tags.mk coblitz-2.01-rc6 trunk
1152 common_usage="""More help:
1153 see http://svn.planet-lab.org/wiki/ModuleTools"""
1156 'list' : "displays a list of available tags or branches",
1157 'version' : "check latest specfile and print out details",
1158 'diff' : "show difference between module (trunk or branch) and latest tag",
1159 'tag' : """increment taglevel in specfile, insert changelog in specfile,
1160 create new tag and and monitor its adoption in build/*-tags*.mk""",
1161 'branch' : """create a branch for this module, from the latest tag on the trunk,
1162 and change trunk's version number to reflect the new branch name;
1163 you can specify the new branch name by using module:branch""",
1164 'sync' : """create a tag from the module
1165 this is a last resort option, mostly for repairs""",
1166 'changelog' : """extract changelog between build tags
1167 expected arguments are a list of tags""",
1170 silent_modes = ['list']
1171 release_modes = ['changelog']
1174 def optparse_list (option, opt, value, parser):
1176 setattr(parser.values,option.dest,getattr(parser.values,option.dest)+value.split())
1178 setattr(parser.values,option.dest,value.split())
1183 for function in Main.modes.keys():
1184 if sys.argv[0].find(function) >= 0:
1188 print "Unsupported command",sys.argv[0]
1189 print "Supported commands:" + " ".join(Main.modes.keys())
1192 if mode not in Main.release_modes:
1193 usage = Main.module_usage
1194 usage += Main.common_usage
1195 usage += "\nmodule-%s : %s"%(mode,Main.modes[mode])
1197 usage = Main.release_usage
1198 usage += Main.common_usage
1200 parser=OptionParser(usage=usage)
1202 if mode == "tag" or mode == 'branch':
1203 parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
1204 help="set new version and reset taglevel to 0")
1206 parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
1207 help="do not update changelog section in specfile when tagging")
1208 parser.add_option("-b","--build-branch", action="store", dest="build_branch", default=None,
1209 help="specify a build branch; used for locating the *tags*.mk files where adoption is to take place")
1210 if mode == "tag" or mode == "sync" :
1211 parser.add_option("-e","--editor", action="store", dest="editor", default=default_editor(),
1212 help="specify editor")
1214 if mode in ["diff","version"] :
1215 parser.add_option("-W","--www", action="store", dest="www", default=False,
1216 help="export diff in html format, e.g. -W trunk")
1219 parser.add_option("-l","--list", action="store_true", dest="list", default=False,
1220 help="just list modules that exhibit differences")
1222 default_modules_list=os.path.dirname(sys.argv[0])+"/modules.list"
1223 parser.add_option("-a","--all",action="store_true",dest="all_modules",default=False,
1224 help="run on all modules as found in %s"%default_modules_list)
1225 parser.add_option("-f","--file",action="store",dest="modules_list",default=None,
1226 help="run on all modules found in specified file")
1227 parser.add_option("-n","--dry-run",action="store_true",dest="dry_run",default=False,
1228 help="dry run - shell commands are only displayed")
1229 parser.add_option("-t","--distrotags",action="callback",callback=Main.optparse_list, dest="distrotags",
1230 default=[], nargs=1,type="string",
1231 help="""specify distro-tags files, e.g. onelab-tags-4.2.mk
1232 -- can be set multiple times, or use quotes""")
1234 parser.add_option("-w","--workdir", action="store", dest="workdir",
1235 default="%s/%s"%(os.getenv("HOME"),"modules"),
1236 help="""name for dedicated working dir - defaults to ~/modules
1237 ** THIS MUST NOT ** be your usual working directory""")
1238 parser.add_option("-F","--fast-checks",action="store_true",dest="fast_checks",default=False,
1239 help="skip safety checks, such as svn updates -- use with care")
1241 # default verbosity depending on function - temp
1242 verbose_modes= ['tag', 'sync', 'branch']
1244 if mode not in verbose_modes:
1245 parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False,
1246 help="run in verbose mode")
1248 parser.add_option("-q","--quiet", action="store_false", dest="verbose", default=True,
1249 help="run in quiet (non-verbose) mode")
1250 (options, args) = parser.parse_args()
1252 if not hasattr(options,'dry_run'):
1253 options.dry_run=False
1254 if not hasattr(options,'www'):
1260 if options.all_modules:
1261 options.modules_list=default_modules_list
1262 if options.modules_list:
1263 args=Command("grep -v '#' %s"%options.modules_list,options).output_of().split()
1267 Module.init_homedir(options)
1270 modules=[ Module(modname,options) for modname in args ]
1271 # hack: create a dummy Module to store errors/warnings
1272 error_module = Module('__errors__',options)
1274 for module in modules:
1275 if len(args)>1 and mode not in Main.silent_modes:
1277 print '========================================',module.friendly_name()
1278 # call the method called do_<mode>
1279 method=Module.__dict__["do_%s"%mode]
1284 title='<span class="error"> Skipping module %s - failure: %s </span>'%\
1285 (module.friendly_name(), str(e))
1286 error_module.html_store_title(title)
1289 traceback.print_exc()
1290 print 'Skipping module %s: '%modname,e
1294 modetitle="Changes to tag in %s"%options.www
1295 elif mode == "version":
1296 modetitle="Latest tags in %s"%options.www
1297 modules.append(error_module)
1298 error_module.html_dump_header(modetitle)
1299 for module in modules:
1300 module.html_dump_toc()
1301 Module.html_dump_middle()
1302 for module in modules:
1303 module.html_dump_body()
1304 Module.html_dump_footer()
1306 ####################
1307 if __name__ == "__main__" :
1310 except KeyboardInterrupt: