Update master of plcapi.
[build.git] / module-log.py
1 #!/usr/bin/python
2 # -*- mode:python; var: python-guess-indent:false; python-indent:4; -*-
3
4 import os
5 import sys
6 import re
7 from optparse import OptionParser
8
9 modules_map = {
10     'general' : [ 'build', 'tests', ],
11     'server' : ['Monitor', 'MyPLC', 'PLCAPI', 'PLCRT', 'PLCWWW', 'PLEWWW', 'www-register-wizard', 
12                 'PXEService', 'drupal', 'plcmdline',],
13     'node': [ 'linux-2.6', 'util-vserver', 'util-vserver-pl', 'chopstix-L0', 
14               'BootCD', 'BootManager', 'BootstrapFS', 'VserverReference', 
15               'DistributedRateLimiting', 'Mom', 'PingOfDeath', 
16               'NodeManager', 'NodeManager-optin', 'NodeManager-topo', 
17               'NodeUpdate', 'CoDemux', 
18               'nodeconfig', 'pl_sshd', 
19               'libnl', 'pypcilib', 'pyplnet',],
20     'wifi' : ['madwifi', 'PlanetBridge', 'hostapd',],
21     'emulation': ['dummynet_image', 'ipfw', ],
22     'netflow' : [ 'fprobe-ulog', 'pf2gui', 'pf2monitor', 'pf2slice', 'iproute2', 'iptables', 'silk',],
23     'sfa' : [ 'sfa', 'xmlrspecs', 'pyopenssl', ],
24     'vsys' : ['vsys', 'vsys-scripts', 'vsys-wrappers', 'inotify-tools'],
25     'deprecated' : [ 'proper', 'libhttpd++', 'oombailout', 'ulogd', 'patchdep', 'pdelta', 
26                      'sandbox', 'playground', 'infrastructure', 'util-python', 'vnetspec', 
27                      ],
28     }
29
30 epoch='{2007-07-01}'
31
32 class ModuleHistory:
33
34     def __init__ (self, name, options):
35         self.name=name
36         self.options=options
37         self.user_commits=[]
38         self.user_revs={}
39         self.current_rev=None
40         self.current_user=None
41         
42     valid=re.compile('\Ar[0-9]+ \|')
43     tagging=re.compile('\ATagging|\ASetting tag')
44
45     @staticmethod
46     def sort ( (u1,c1), (u2,c2) ): return c2-c1
47
48     def record(self,user,rev):
49         try:
50             self.user_revs[user].append(rev)
51         except:
52             self.user_revs[user] = [rev]
53         self.current_rev=rev
54         self.current_user=user
55
56     def ignore(self):
57         if (not self.current_user) or (not self.current_rev): return
58         user_list=self.user_revs[self.current_user]
59         if len(user_list) >= 1 and user_list[-1] == self.current_rev:
60             user_list.pop()
61
62     def scan (self):
63         cmd =  "svn log -r %s:%s http://svn.planet-lab.org/svn/%s " % (self.options.fromv,self.options.tov,self.name)
64         if self.options.verbose:
65             print 'running',cmd
66         f = os.popen(cmd)
67         for line in f:
68             if not self.valid.match(line): 
69                 # mostly ignore commit body, except for ignoring the current commit if -i is set
70                 if self.options.ignore_tags and self.tagging.match(line):
71                     # roll back these changes
72                     self.ignore()
73                 continue
74             fields = line.split('|')
75             fields = [field.strip() for field in fields]
76             [rev,user,ctime,size] = fields[:4]
77             self.record(user,rev)
78         # translate into a list of tuples
79         user_commits = [ (user,len(revs)) for (user,revs) in self.user_revs.items() ]
80         user_commits.sort(self.sort)
81         self.user_commits=user_commits
82
83     def show(self):
84         if len(self.user_commits) ==0: return
85         print '%s [%s-%s]'%(self.name,self.options.fromv,self.options.tov),
86         if self.options.ignore_tags:
87             print ' - Ignored tag commits'
88         else:
89             print ''
90         for (u,c) in self.user_commits:
91             print "\t",u,c
92
93 class Aggregate:
94
95     def __init__ (self,options):
96         # key=user, value=commits
97         self.options=options
98         self.user_commits_dict={}
99         self.user_commits=[]
100
101     def merge (self, modulehistory):
102         for (u,c) in modulehistory.user_commits:
103             try:
104                 self.user_commits_dict[u] += c
105             except:
106                 self.user_commits_dict[u] = c
107
108     def sort (self):
109         user_commits = [ (u,c) for (u,c) in self.user_commits_dict.items() ]
110         user_commits.sort(ModuleHistory.sort)
111         self.user_commits=user_commits
112         
113     def show(self):
114         print 'Overall',
115         if self.options.ignore_tags:
116             print ' - Ignored tag commits'
117         else:
118             print ''
119         for (u,c) in self.user_commits:
120             print "\t",u,c
121
122 class Modules:
123     
124     def __init__ (self,map):
125         self.map=map
126
127     def categories(self):
128         return self.map.keys()
129
130     def all_modules(self,categories=None):
131         if not categories: categories=self.categories()
132         elif not isinstance(categories,list): categories=[categories]
133         result=[]
134         for category in categories:
135             result += self.map[category]
136         return result
137
138     def locate (self,keywords):
139         result=[]
140         for kw in keywords:
141             if self.map.has_key(kw):
142                 result += self.map[kw]
143             else:
144                 result += [kw]
145         return result
146
147     def list(self,scope):
148         for (cat,mod_list) in self.map.items():
149             for mod in mod_list:
150                 if mod in scope:
151                     print cat,mod
152
153 def main ():
154     usage="%prog [module_or_category ...]"
155     parser = OptionParser(usage=usage)
156     parser.add_option("-f", "--from", action = "store", dest='fromv',
157                       default = epoch, help = "The revision to start from, default %s"%epoch)
158     parser.add_option("-t", "--to", action = "store", dest='tov',
159                       default = 'HEAD', help = "The revision to end with, default HEAD")
160     parser.add_option("-n","--no-aggregate", action='store_false',dest='aggregate',default=True,
161                       help='Do not aggregate over modules')
162     parser.add_option("-v","--verbose", action='store_true',dest='verbose',default=False,
163                       help='Run in verbose/debug mode')
164     parser.add_option("-i","--ignore-tags",action='store_true',dest='ignore_tags',
165                       help='ignore commits related to tagging')
166     parser.add_option("-l","--list",action='store_true',dest='list_modules',
167                       help='list available modules and categories')
168     # pass this to the invoked shell if any
169     (options, args) = parser.parse_args()
170
171     map=Modules(modules_map)
172     if not args:
173         modules=map.all_modules()
174     else:
175         modules=map.locate(args)
176
177     if options.list_modules:
178         map.list(modules)
179         return
180
181     if len(modules) <=1:
182         options.aggregate=False
183
184     aggregate = Aggregate(options)
185     for module in modules:
186         history=ModuleHistory(module,options)
187         history.scan()
188         history.show()
189         aggregate.merge(history)
190
191     if options.aggregate:
192         aggregate.sort()
193         aggregate.show()
194
195 if __name__ == '__main__':
196     main()