for dealing with the util-vserver module, specifically the scholz branch
[build.git] / module-tag.py
1 #!/usr/bin/python -u
2
3 subversion_id = "$Id$"
4
5 import sys, os, os.path
6 import re
7 import time
8 from glob import glob
9 from optparse import OptionParser
10
11 def prompt (question,default=True):
12     if default:
13         question += " [y]/n ? "
14     else:
15         question += " y/[n] ? "
16     try:
17         answer=raw_input(question)
18         if not answer:
19             return default
20         elif answer[0] in [ 'y','Y']:
21             return True
22         elif answer[0] in [ 'n','N']:
23             return False
24         else:
25             return prompt(question,default)
26     except KeyboardInterrupt:
27         print "Aborted"
28         return False
29     except:
30         raise
31
32 class Command:
33     def __init__ (self,command,options):
34         self.command=command
35         self.options=options
36         self.tmp="/tmp/command-%d"%os.getpid()
37
38     def run (self):
39         if self.options.verbose:
40             print '+',self.command
41             sys.stdout.flush()
42         return os.system(self.command)
43
44     def run_silent (self):
45         if self.options.verbose:
46             print '+',self.command,' .. ',
47             sys.stdout.flush()
48         retcod=os.system(self.command + " &> " + self.tmp)
49         if retcod != 0:
50             print "FAILED ! -- output quoted below "
51             os.system("cat " + self.tmp)
52             print "FAILED ! -- end of quoted output"
53         elif self.options.verbose:
54             print "OK"
55         os.unlink(self.tmp)
56         return retcod
57
58     def run_fatal(self):
59         if self.run_silent() !=0:
60             raise Exception,"Command %s failed"%self.command
61
62     # returns stdout, like bash's $(mycommand)
63     def output_of (self,with_stderr=False):
64         tmp="/tmp/status-%d"%os.getpid()
65         if self.options.debug:
66             print '+',self.command,' .. ',
67             sys.stdout.flush()
68         command=self.command
69         if with_stderr:
70             command += " &> "
71         else:
72             command += " > "
73         command += tmp
74         os.system(command)
75         result=file(tmp).read()
76         os.unlink(tmp)
77         if self.options.debug:
78             print 'Done',
79         return result
80
81 class Svnpath:
82     def __init__(self,path,options):
83         self.path=path
84         self.options=options
85
86     def url_exists (self):
87         return os.system("svn list %s &> /dev/null"%self.path) == 0
88
89     def dir_needs_revert (self):
90         command="svn status %s"%self.path
91         return len(Command(command,self.options).output_of(True)) != 0
92     # turns out it's the same implem.
93     def file_needs_commit (self):
94         command="svn status %s"%self.path
95         return len(Command(command,self.options).output_of(True)) != 0
96
97 class Module:
98
99     svn_magic_line="--This line, and those below, will be ignored--"
100     
101     redirectors=[ # ('module_name_varname','name'),
102                   ('module_version_varname','version'),
103                   ('module_taglevel_varname','taglevel'), ]
104
105     # where to store user's config
106     config_storage="CONFIG"
107     # 
108     config={}
109
110     import commands
111     configKeys=[ ('svnpath',"Enter your toplevel svnpath",
112                   "svn+ssh://%s@svn.planet-lab.org/svn/"%commands.getoutput("id -un")),
113                  ("build", "Enter the name of your build module","build"),
114                  ('username',"Enter your firstname and lastname for changelogs",""),
115                  ("email","Enter your email address for changelogs",""),
116                  ]
117
118     @staticmethod
119     def prompt_config ():
120         for (key,message,default) in Module.configKeys:
121             Module.config[key]=""
122             while not Module.config[key]:
123                 Module.config[key]=raw_input("%s [%s] : "%(message,default)).strip() or default
124
125
126     # for parsing module spec name:branch
127     matcher_branch_spec=re.compile("\A(?P<name>[\w-]+):(?P<branch>[\w\.]+)\Z")
128     matcher_rpm_define=re.compile("%(define|global)\s+(\S+)\s+(\S*)\s*")
129
130     def __init__ (self,module_spec,options):
131         # parse module spec
132         attempt=Module.matcher_branch_spec.match(module_spec)
133         if attempt:
134             self.name=attempt.group('name')
135             self.branch=attempt.group('branch')
136         else:
137             self.name=module_spec
138             self.branch=None
139
140         self.options=options
141         self.moddir="%s/%s"%(options.workdir,self.name)
142
143     def edge_dir (self):
144         if not self.branch:
145             return "%s/trunk"%(self.moddir)
146         else:
147             return "%s/branches/%s"%(self.moddir,self.branch)
148
149     def tags_dir (self):
150         return "%s/tags"%(self.moddir)
151
152     def run (self,command):
153         return Command(command,self.options).run()
154     def run_fatal (self,command):
155         return Command(command,self.options).run_fatal()
156     def run_prompt (self,message,command):
157         if not self.options.verbose:
158             question=message
159         else:
160             question="Want to run " + command
161         if prompt(question,True):
162             self.run(command)            
163
164     @staticmethod
165     def init_homedir (options):
166         topdir=options.workdir
167         if options.verbose:
168             print 'Checking for',topdir
169         storage="%s/%s"%(topdir,Module.config_storage)
170         # sanity check. Either the topdir exists AND we have a config/storage
171         # or topdir does not exist and we create it
172         # to avoid people use their own daily svn repo
173         if os.path.isdir(topdir) and not os.path.isfile(storage):
174             print """The directory %s exists and has no CONFIG file
175 If this is your regular working directory, please provide another one as the
176 module-* commands need a fresh working dir. Make sure that you do not use 
177 that for other purposes than tagging"""%topdir
178             sys.exit(1)
179         if not os.path.isdir (topdir):
180             print "Cannot find",topdir,"let's create it"
181             Module.prompt_config()
182             print "Checking ...",
183             Command("svn co -N %s %s"%(Module.config['svnpath'],topdir),options).run_fatal()
184             Command("svn co -N %s/%s %s/%s"%(Module.config['svnpath'],
185                                              Module.config['build'],
186                                              topdir,
187                                              Module.config['build']),options).run_fatal()
188             print "OK"
189             
190             # store config
191             f=file(storage,"w")
192             for (key,message,default) in Module.configKeys:
193                 f.write("%s=%s\n"%(key,Module.config[key]))
194             f.close()
195             if options.debug:
196                 print 'Stored',storage
197                 Command("cat %s"%storage,options).run()
198         else:
199             # read config
200             f=open(storage)
201             for line in f.readlines():
202                 (key,value)=re.compile("^(.+)=(.+)$").match(line).groups()
203                 Module.config[key]=value                
204             f.close()
205         if options.verbose:
206             print '******** Using config'
207             for (key,message,default) in Module.configKeys:
208                 print '\t',key,'=',Module.config[key]
209
210     def init_moddir (self):
211         if self.options.verbose:
212             print 'Checking for',self.moddir
213         if not os.path.isdir (self.moddir):
214             self.run_fatal("svn up -N %s"%self.moddir)
215         if not os.path.isdir (self.moddir):
216             print 'Cannot find %s - check module name'%self.moddir
217             sys.exit(1)
218
219     def init_subdir (self,fullpath):
220         if self.options.verbose:
221             print 'Checking for',fullpath
222         if not os.path.isdir (fullpath):
223             self.run_fatal("svn up -N %s"%fullpath)
224
225     def revert_subdir (self,fullpath):
226         if self.options.fast_checks:
227             if self.options.verbose: print 'Skipping revert of %s'%fullpath
228             return
229         if self.options.verbose:
230             print 'Checking whether',fullpath,'needs being reverted'
231         if Svnpath(fullpath,self.options).dir_needs_revert():
232             self.run_fatal("svn revert -R %s"%fullpath)
233
234     def update_subdir (self,fullpath):
235         if self.options.fast_checks:
236             if self.options.verbose: print 'Skipping update of %s'%fullpath
237             return
238         if self.options.verbose:
239             print 'Updating',fullpath
240         self.run_fatal("svn update -N %s"%fullpath)
241
242     def init_edge_dir (self):
243         # if branch, edge_dir is two steps down
244         if self.branch:
245             self.init_subdir("%s/branches"%self.moddir)
246         self.init_subdir(self.edge_dir())
247
248     def revert_edge_dir (self):
249         self.revert_subdir(self.edge_dir())
250
251     def update_edge_dir (self):
252         self.update_subdir(self.edge_dir())
253
254     def main_specname (self):
255         attempt="%s/%s.spec"%(self.edge_dir(),self.name)
256         if os.path.isfile (attempt):
257             return attempt
258         else:
259             try:
260                 return glob("%s/*.spec"%self.edge_dir())[0]
261             except:
262                 print 'Cannot guess specfile for module %s'%self.name
263                 sys.exit(1)
264
265     def all_specnames (self):
266         return glob("%s/*.spec"%self.edge_dir())
267
268     def parse_spec (self, specfile, varnames):
269         if self.options.verbose:
270             print 'Parsing',specfile,
271             for var in varnames:
272                 print "[%s]"%var,
273             print ""
274         result={}
275         f=open(specfile)
276         for line in f.readlines():
277             attempt=Module.matcher_rpm_define.match(line)
278             if attempt:
279                 (define,var,value)=attempt.groups()
280                 if var in varnames:
281                     result[var]=value
282         f.close()
283         if self.options.debug:
284             print 'found',len(result),'keys'
285             for (k,v) in result.iteritems():
286                 print k,'=',v
287         return result
288                 
289     # stores in self.module_name_varname the rpm variable to be used for the module's name
290     # and the list of these names in self.varnames
291     def spec_dict (self):
292         specfile=self.main_specname()
293         redirector_keys = [ varname for (varname,default) in Module.redirectors]
294         redirect_dict = self.parse_spec(specfile,redirector_keys)
295         if self.options.debug:
296             print '1st pass parsing done, redirect_dict=',redirect_dict
297         varnames=[]
298         for (varname,default) in Module.redirectors:
299             if redirect_dict.has_key(varname):
300                 setattr(self,varname,redirect_dict[varname])
301                 varnames += [redirect_dict[varname]]
302             else:
303                 setattr(self,varname,default)
304                 varnames += [ default ] 
305         self.varnames = varnames
306         result = self.parse_spec (specfile,self.varnames)
307         if self.options.debug:
308             print '2st pass parsing done, varnames=',varnames,'result=',result
309         return result
310
311     def patch_spec_var (self, patch_dict):
312         for specfile in self.all_specnames():
313             newspecfile=specfile+".new"
314             if self.options.verbose:
315                 print 'Patching',specfile,'for',patch_dict.keys()
316             spec=open (specfile)
317             new=open(newspecfile,"w")
318
319             for line in spec.readlines():
320                 attempt=Module.matcher_rpm_define.match(line)
321                 if attempt:
322                     (define,var,value)=attempt.groups()
323                     if var in patch_dict.keys():
324                         new.write('%%%s %s %s\n'%(define,var,patch_dict[var]))
325                         continue
326                 new.write(line)
327             spec.close()
328             new.close()
329             os.rename(newspecfile,specfile)
330
331     def unignored_lines (self, logfile):
332         result=[]
333         exclude="Tagging module %s"%self.name
334         for logline in file(logfile).readlines():
335             if logline.strip() == Module.svn_magic_line:
336                 break
337             if logline.find(exclude) < 0:
338                 result += [ logline ]
339         return result
340
341     def insert_changelog (self, logfile, oldtag, newtag):
342         for specfile in self.all_specnames():
343             newspecfile=specfile+".new"
344             if self.options.verbose:
345                 print 'Inserting changelog from %s into %s'%(logfile,specfile)
346             spec=open (specfile)
347             new=open(newspecfile,"w")
348             for line in spec.readlines():
349                 new.write(line)
350                 if re.compile('%changelog').match(line):
351                     dateformat="* %a %b %d %Y"
352                     datepart=time.strftime(dateformat)
353                     logpart="%s <%s> - %s %s"%(Module.config['username'],
354                                                  Module.config['email'],
355                                                  oldtag,newtag)
356                     new.write(datepart+" "+logpart+"\n")
357                     for logline in self.unignored_lines(logfile):
358                         new.write("- " + logline)
359                     new.write("\n")
360             spec.close()
361             new.close()
362             os.rename(newspecfile,specfile)
363             
364     def show_dict (self, spec_dict):
365         if self.options.verbose:
366             for (k,v) in spec_dict.iteritems():
367                 print k,'=',v
368
369     def mod_url (self):
370         return "%s/%s"%(Module.config['svnpath'],self.name)
371
372     def edge_url (self):
373         if not self.branch:
374             return "%s/trunk"%(self.mod_url())
375         else:
376             return "%s/branches/%s"%(self.mod_url(),self.branch)
377
378     def tag_name (self, spec_dict):
379         try:
380             return "%s-%s-%s"%(#spec_dict[self.module_name_varname],
381                 self.name,
382                 spec_dict[self.module_version_varname],
383                 spec_dict[self.module_taglevel_varname])
384         except KeyError,err:
385             print 'Something is wrong with module %s, cannot determine %s - exiting'%(self.name,err)
386             sys.exit(1)
387
388     def tag_url (self, spec_dict):
389         return "%s/tags/%s"%(self.mod_url(),self.tag_name(spec_dict))
390
391     def check_svnpath_exists (self, url, message):
392         if self.options.fast_checks:
393             return
394         if self.options.verbose:
395             print 'Checking url (%s) %s'%(url,message),
396         ok=Svnpath(url,self.options).url_exists()
397         if ok:
398             if self.options.verbose: print 'exists - OK'
399         else:
400             if self.options.verbose: print 'KO'
401             print 'Could not find %s URL %s'%(message,url)
402             sys.exit(1)
403     def check_svnpath_not_exists (self, url, message):
404         if self.options.fast_checks:
405             return
406         if self.options.verbose:
407             print 'Checking url (%s) %s'%(url,message),
408         ok=not Svnpath(url,self.options).url_exists()
409         if ok:
410             if self.options.verbose: print 'does not exist - OK'
411         else:
412             if self.options.verbose: print 'KO'
413             print '%s URL %s already exists - exiting'%(message,url)
414             sys.exit(1)
415
416     # locate specfile, parse it, check it and show values
417 ##############################
418     def do_version (self):
419         self.init_moddir()
420         self.init_edge_dir()
421         self.revert_edge_dir()
422         self.update_edge_dir()
423         spec_dict = self.spec_dict()
424         for varname in self.varnames:
425             if not spec_dict.has_key(varname):
426                 print 'Could not find %%define for %s'%varname
427                 return
428             else:
429                 print varname+":",spec_dict[varname]
430         print 'edge url',self.edge_url()
431         print 'latest tag url',self.tag_url(spec_dict)
432         if self.options.verbose:
433             print 'main specfile:',self.main_specname()
434             print 'specfiles:',self.all_specnames()
435
436     init_warning="""WARNING
437 The module-init function has the following limitations
438 * it does not handle changelogs
439 * it does not scan the -tags*.mk files to adopt the new tags"""
440 ##############################
441     def do_init(self):
442         if self.options.verbose:
443             print Module.init_warning
444             if not prompt('Want to proceed anyway'):
445                 return
446
447         self.init_moddir()
448         self.init_edge_dir()
449         self.revert_edge_dir()
450         self.update_edge_dir()
451         spec_dict = self.spec_dict()
452
453         edge_url=self.edge_url()
454         tag_name=self.tag_name(spec_dict)
455         tag_url=self.tag_url(spec_dict)
456         # check the tag does not exist yet
457         self.check_svnpath_not_exists(tag_url,"new tag")
458
459         if self.options.message:
460             svnopt='--message "%s"'%self.options.message
461         else:
462             svnopt='--editor-cmd=%s'%self.options.editor
463         self.run_prompt("Create initial tag",
464                         "svn copy %s %s %s"%(svnopt,edge_url,tag_url))
465
466 ##############################
467     def do_diff (self):
468         self.init_moddir()
469         self.init_edge_dir()
470         self.revert_edge_dir()
471         self.update_edge_dir()
472         spec_dict = self.spec_dict()
473         self.show_dict(spec_dict)
474
475         edge_url=self.edge_url()
476         tag_url=self.tag_url(spec_dict)
477         self.check_svnpath_exists(edge_url,"edge track")
478         self.check_svnpath_exists(tag_url,"latest tag")
479         diff_output = Command("svn diff %s %s"%(tag_url,edge_url),self.options).output_of()
480         if self.options.list:
481             if diff_output:
482                 print self.name
483         else:
484             if not self.options.only or diff_output:
485                 print 'x'*40,'module',self.name
486                 print 'x'*20,'<',tag_url
487                 print 'x'*20,'>',edge_url
488                 print diff_output
489
490 ##############################
491     def patch_tags_file (self, tagsfile, oldname, newname):
492         newtagsfile=tagsfile+".new"
493         if self.options.verbose:
494             print 'Replacing %s into %s in %s'%(oldname,newname,tagsfile)
495         tags=open (tagsfile)
496         new=open(newtagsfile,"w")
497         matcher=re.compile("^(.*)%s(.*)"%oldname)
498         for line in tags.readlines():
499             if not matcher.match(line):
500                 new.write(line)
501             else:
502                 (begin,end)=matcher.match(line).groups()
503                 new.write(begin+newname+end+"\n")
504         tags.close()
505         new.close()
506         os.rename(newtagsfile,tagsfile)
507
508     def do_tag (self):
509         self.init_moddir()
510         self.init_edge_dir()
511         self.revert_edge_dir()
512         self.update_edge_dir()
513         # parse specfile
514         spec_dict = self.spec_dict()
515         self.show_dict(spec_dict)
516         
517         # side effects
518         edge_url=self.edge_url()
519         old_tag_name = self.tag_name(spec_dict)
520         old_tag_url=self.tag_url(spec_dict)
521         if (self.options.new_version):
522             # new version set on command line
523             spec_dict[self.module_version_varname] = self.options.new_version
524             spec_dict[self.module_taglevel_varname] = 0
525         else:
526             # increment taglevel
527             new_taglevel = str ( int (spec_dict[self.module_taglevel_varname]) + 1)
528             spec_dict[self.module_taglevel_varname] = new_taglevel
529
530         # sanity check
531         new_tag_name = self.tag_name(spec_dict)
532         new_tag_url=self.tag_url(spec_dict)
533         self.check_svnpath_exists (edge_url,"edge track")
534         self.check_svnpath_exists (old_tag_url,"previous tag")
535         self.check_svnpath_not_exists (new_tag_url,"new tag")
536
537         # checking for diffs
538         diff_output=Command("svn diff %s %s"%(old_tag_url,edge_url),
539                             self.options).output_of()
540         if len(diff_output) == 0:
541             if not prompt ("No difference in trunk for module %s, want to tag anyway"%self.name,False):
542                 return
543
544         # side effect in trunk's specfile
545         self.patch_spec_var(spec_dict)
546
547         # prepare changelog file 
548         # we use the standard subversion magic string (see svn_magic_line)
549         # so we can provide useful information, such as version numbers and diff
550         # in the same file
551         changelog="/tmp/%s-%d.txt"%(self.name,os.getpid())
552         file(changelog,"w").write("""Tagging module %s - %s
553
554 %s
555 Please write a changelog for this new tag in the section above
556 """%(self.name,new_tag_name,Module.svn_magic_line))
557
558         if not self.options.verbose or prompt('Want to see diffs while writing changelog',True):
559             file(changelog,"a").write('DIFF=========\n' + diff_output)
560         
561         if self.options.debug:
562             prompt('Proceed ?')
563
564         # edit it        
565         self.run("%s %s"%(self.options.editor,changelog))
566         # insert changelog in spec
567         if self.options.changelog:
568             self.insert_changelog (changelog,old_tag_name,new_tag_name)
569
570         ## update build
571         try:
572             buildname=Module.config['build']
573         except:
574             buildname="build"
575         build = Module(buildname,self.options)
576         build.init_moddir()
577         build.init_edge_dir()
578         build.revert_edge_dir()
579         build.update_edge_dir()
580         
581         for tagsfile in glob(build.edge_dir()+"/*-tags*.mk"):
582             if prompt("Want to adopt new tag in %s"%tagsfile):
583                 self.patch_tags_file(tagsfile,old_tag_name,new_tag_name)
584
585         paths=""
586         paths += self.edge_dir() + " "
587         paths += build.edge_dir() + " "
588         self.run_prompt("Check","svn diff " + paths)
589         self.run_prompt("Commit","svn commit --file %s %s"%(changelog,paths))
590         self.run_prompt("Create tag","svn copy --file %s %s %s"%(changelog,edge_url,new_tag_url))
591
592         if self.options.debug:
593             print 'Preserving',changelog
594         else:
595             os.unlink(changelog)
596             
597 ##############################
598     def do_branch (self):
599
600         print 'module-branch is experimental - exiting'
601         sys.exit(1)
602
603         if self.branch:
604             print 'Cannot create a branch from another branch - exiting'
605             sys.exit(1)
606         self.init_moddir()
607         
608         # xxx - tmp
609         import readline
610         answer = raw_input ("enter tag name [trunk]").strip()
611         if answer == "" or answer == "trunk":
612             ref="/trunk"
613             from_trunk=True
614         else:
615             ref="/tags/%s-%s"%(self.name,answer)
616             from_trunk=False
617
618         ref_url = "%s/%s"%(self.mod_url(),ref)
619         self.check_svnpath_exists (ref_url,"branch creation point")
620         print "Using starting point %s"%ref_url
621         
622         spec=self.main_specname()
623         if not from_trunk:
624             self.init_subdir(self.tags_dir())
625             workdir="%s/%s"%(self.moddir,ref)
626         else:
627             workdir=self.edge_dir()
628
629         self.init_subdir(workdir)
630         self.revert_subdir(workdir)
631         self.update_subdir(workdir)
632
633         print 'got spec',spec
634         if not os.path.isfile(spec):
635             print 'cannot find spec'
636         
637         # read version & taglevel from the origin specfile
638         print 'parsing',spec
639         origin=self.spec_dict()
640         self.show_dict(origin)
641
642         default_branch=self.options.new_version
643         if not default_branch:
644 #            try:
645                 match=re.compile("\A(?P<main>.*[\.-_])(?P<subid>[0-9]+)\Z").match(origin['version'])
646                 new_subid=int(match.group('subid'))+1
647                 default_branch="%s%d"%(match.group('main'),new_subid)
648 #            except:
649 #                default_branch="not found"
650         new_branch_name=raw_input("Enter branch name [%s] "%default_branch) or default_branch
651         
652         new_branch_url="%s/branches/%s"%(self.mod_url(),new_branch_name)
653         self.check_svnpath_not_exists(new_branch_url,"new branch")
654         print new_branch_name
655         
656
657 ##############################
658 usage="""Usage: %prog options module_desc [ .. module_desc ]
659 Purpose:
660   manage subversion tags and specfile
661   requires the specfile to define *version* and *taglevel*
662   OR alternatively 
663   redirection variables module_version_varname / module_taglevel_varname
664 Trunk:
665   by default, the trunk of modules is taken into account
666   in this case, just mention the module name as <module_desc>
667 Branches:
668   if you wish to work on a branch rather than on the trunk, 
669   you can use the following syntax for <module_desc>
670   Mom:2.1
671       works on Mom/branches/2.1 
672 """
673 # unsupported yet
674 #"""
675 #  branch:Mom
676 #      the branch_id is deduced from the current *version* in the trunk's specfile
677 #      e.g. if Mom/trunk/Mom.spec specifies %define version 2.3, then this script
678 #      would use Mom/branches/2.2
679 #      if if stated %define version 3.0, then the script fails
680 #"""
681
682 functions={ 
683     'diff' : "show difference between trunk and latest tag",
684     'tag'  : """increment taglevel in specfile, insert changelog in specfile,
685                 create new tag and and adopt it in build/*-tags*.mk""",
686     'init' : "create initial tag",
687     'version' : "only check specfile and print out details",
688     'branch' : """create a branch for this module. 
689                 either from trunk, or from a tag""",
690 }
691
692 def main():
693
694     mode=None
695     for function in functions.keys():
696         if sys.argv[0].find(function) >= 0:
697             mode = function
698             break
699     if not mode:
700         print "Unsupported command",sys.argv[0]
701         sys.exit(1)
702
703     global usage
704     usage += "module-%s.py : %s"%(mode,functions[mode])
705     all_modules=os.path.dirname(sys.argv[0])+"/modules.list"
706
707     parser=OptionParser(usage=usage,version=subversion_id)
708     parser.add_option("-a","--all",action="store_true",dest="all_modules",default=False,
709                       help="run on all modules as found in %s"%all_modules)
710     parser.add_option("-f","--fast-checks",action="store_true",dest="fast_checks",default=False,
711                       help="skip safety checks, such as svn updates -- use with care")
712     if mode == "tag" or mode == 'branch':
713         parser.add_option("-s","--set-version",action="store",dest="new_version",default=None,
714                           help="set new version and reset taglevel to 0")
715     if mode == "tag" :
716         parser.add_option("-c","--no-changelog", action="store_false", dest="changelog", default=True,
717                           help="do not update changelog section in specfile when tagging")
718     if mode == "tag" or mode == "init" :
719         parser.add_option("-e","--editor", action="store", dest="editor", default="emacs",
720                           help="specify editor")
721     if mode == "init" :
722         parser.add_option("-m","--message", action="store", dest="message", default=None,
723                           help="specify log message")
724     if mode == "diff" :
725         parser.add_option("-o","--only", action="store_true", dest="only", default=False,
726                           help="report diff only for modules that exhibit differences")
727     if mode == "diff" :
728         parser.add_option("-l","--list", action="store_true", dest="list", default=False,
729                           help="just list modules that exhibit differences")
730     parser.add_option("-w","--workdir", action="store", dest="workdir", 
731                       default="%s/%s"%(os.getenv("HOME"),"modules"),
732                       help="""name for dedicated working dir - defaults to ~/modules
733 ** THIS MUST NOT ** be your usual working directory""")
734     parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=True, 
735                       help="run in verbose mode")
736     parser.add_option("-q","--quiet", action="store_false", dest="verbose", 
737                       help="run in quiet (non-verbose) mode")
738     parser.add_option("-d","--debug", action="store_true", dest="debug", default=False, 
739                       help="debug mode - mostly more verbose")
740     (options, args) = parser.parse_args()
741
742     if len(args) == 0:
743         if options.all_modules:
744             args=Command("grep -v '#' %s"%all_modules,options).output_of().split()
745         else:
746             parser.print_help()
747             sys.exit(1)
748     Module.init_homedir(options)
749     for modname in args:
750         module=Module(modname,options)
751         print '==============================',module.name
752         # call the method called do_<mode>
753         method=Module.__dict__["do_%s"%mode]
754         method(module)
755
756 # basically, we exit if anything goes wrong
757 if __name__ == "__main__" :
758     main()