inittags: some updates
[git-dm.git] / firstlast
blob1a9879bfe168dea3f5b46ba6a839643006ffd119
1 #!/usr/bin/python3
2 # -*- python -*-
4 # Crank through the log looking at when developers did their first and
5 # last patches.
7 # git log | firstlast -v versiondb
9 import argparse, pickle
10 import sys
11 import gitlog
12 import database
13 import ConfigFile
14 from utils import accumulator
16 # Arg processing
18 def SetupArgs():
19     p = argparse.ArgumentParser()
20     p.add_argument('-v', '--versiondb', help = 'Version database file',
21                    required = False, default = 'committags.db')
22     p.add_argument('-c', '--config', help = 'Configuration file',
23                    required = True)
24     p.add_argument('-d', '--dbdir', help = 'Where to find the config database files',
25                    required = False, default = '')
26     p.add_argument('-f', '--first', help = 'First version for detailed tracking',
27                    required = False, default = '')
28     p.add_argument('-l', '--last', help = 'Last version for detailed tracking',
29                    required = False, default = '')
30     p.add_argument('-m', '--minversions', required = False, default = 1, type = int,
31                    help = 'How many versions an author contributes to for counting')
32     return p.parse_args()
35 # Try to track the first directory a new developer touches.
37 FirstDirs = { }
39 def TrackFirstDirs(patch):
40     dirs = [ ]
41     for file in patch.files:
42         split = file.split('/')
43         if split[0] in ['arch', 'drivers', 'fs']:
44             track = '/'.join(split[0:2])
45         else:
46             track = split[0]
47         if track not in dirs:
48             dirs.append(track)
49     for dir in dirs:
50         try:
51             FirstDirs[dir] += 1
52         except KeyError:
53             FirstDirs[dir] = 1
55 def dirkey(d):
56     return FirstDirs[d]
58 def PrintFirstDirs():
59     print('\nDirectories touched by first commits:')
60     dirs = list(FirstDirs.keys())
61     dirs.sort(key = dirkey, reverse = True)
62     for dir in dirs[:20]:
63         print('%5d: %s' % (FirstDirs[dir], dir))
66 # Let's also track who they worked for.
68 FirstEmpls = { }
70 def TrackFirstEmpl(name):
71     try:
72         FirstEmpls[name] += 1
73     except KeyError:
74         FirstEmpls[name] = 1
76 def emplkey(e):
77     return FirstEmpls[e]
79 def PrintFirstEmpls():
80     empls = list(FirstEmpls.keys())
81     empls.sort(key = emplkey, reverse = True)
82     print('\nEmployers:')
83     for e in empls[:30]:
84         print('%5d: %s' % (FirstEmpls[e], e))
85     #
86     # Make a quick sum of how many first timers were employed
87     #
88     companies = 0
89     for e in empls:
90         if e not in [ '(Unknown)', '(None)' ]:
91             companies += FirstEmpls[e]
92     print('Companies: %d' % (companies))
95 # Basic stats
97 DevsPerVersion = { }
98 EmplsPerVersion = { }
100 def TrackCounts(v, patch):
101     try:
102         DevsPerVersion[v].add(patch.author)
103     except KeyError:
104         DevsPerVersion[v] = set()
105         DevsPerVersion[v].add(patch.author)
106         
107     try:
108         empl = patch.author.emailemployer(patch.email, patch.date)
109     except AttributeError:
110         return
111     try:
112         EmplsPerVersion[v].add(empl)
113     except KeyError:
114         EmplsPerVersion[v] = set()
115         EmplsPerVersion[v].add(empl)
117 # Version comparison stuff.  Kernel-specific, obviously.
119 def die(gripe):
120     sys.stderr.write(gripe + '\n')
121     sys.exit(1)
123 def versionmap(vers):
124     split = vers.split('.')
125     if not (2 <= len(split) <= 5):
126         die('funky version %s' % (vers))
127     if split[0] in ['v2', '2']:
128         return int(split[2])
129     if split[0] in ['v3', '3']:
130         return 100 + int(split[1])
131     if split[0] in ['v4', '4']:
132         return 120 + int(split[1])
133     if split[0] in ['v5', '5']:
134         return 150 + int(split[1])
135     die('Funky version %s' % (vers))
137 T_First = 0
138 T_Last = 999999
140 def SetTrackingVersions(args):
141     global T_First, T_Last
142     if args.first:
143         T_First = versionmap(args.first)
144     if args.last:
145         T_Last = versionmap(args.last)
147 def TrackingVersion(vers):
148     return T_First <= versionmap(vers) <= T_Last
151 # Count the number of last-patch authors that had the minimum number
152 # of versions.
154 def CountLasts(hackers):
155     sum = 0
156     for h in hackers:
157         if len(Versions[h.id]) >= args.minversions:
158             sum += 1
159     return sum
161 # Main program.
163 args = SetupArgs()
164 VDB = pickle.load(open(args.versiondb, 'rb'))
165 ConfigFile.ConfigFile(args.config, args.dbdir)
166 SetTrackingVersions(args)
168 Firsts = accumulator()
169 Lasts = accumulator()
170 Singles = accumulator()
171 Versions = accumulator()
173 # Read through the full patch stream and collect the relevant info.
175 input = open(0, 'rb') # Get a bytes version of stdin
176 patch = gitlog.grabpatch(input)
177 while patch:
178     try:
179         v = VDB[patch.commit]
180     except KeyError:
181         print('Funky commit', patch.commit)
182         patch = gitlog.grabpatch(input)
183         continue
184     TrackCounts(v, patch)
185     #
186     # The first patch we see is the last they committed, since git
187     # lists things in backwards order.
188     #
189     # ... except, of course, that life is not so simple, and git can
190     # present patches in different orders at different times, so we
191     # just have to compare versions.
192     #
193     mapv = versionmap(v)
194     try:
195         if mapv < versionmap(patch.author.firstvers):
196             patch.author.firstvers = v
197     except AttributeError:
198         patch.author.firstvers = v
199     try:
200         if mapv > versionmap(patch.author.lastvers):
201             patch.author.lastvers = v
202     except AttributeError:
203         patch.author.lastvers = v
204     patch.author.addpatch(patch)
205     Versions.append(patch.author.id, v, unique = True)
206     patch = gitlog.grabpatch(input)
209 # Pass over all the hackers we saw and collate stuff.
211 for h in database.AllHackers():
212     if len(h.patches) > 0 and len(Versions[h.id]) >= args.minversions:
213         Firsts.append(h.firstvers, h)
214         Lasts.append(h.lastvers, h)
215         if h.firstvers == h.lastvers:
216             Singles.incr(h.firstvers)
217         #
218         # Track details, but only for versions we care about
219         #
220         if TrackingVersion(h.firstvers):
221             p = h.patches[-1]
222             TrackFirstDirs(p)
223             try:
224                 empl = h.emailemployer(p.email, p.date)
225             except AttributeError:
226                 print('No email on ', p.commit)
227                 continue
228 #            if empl.name == '(Unknown)':
229 #                print('UNK: %s %s' % (p.email, h.name))
230             TrackFirstEmpl(empl.name)
233 def vkey(vers):
234     return versionmap(vers)
236 versions = list(Lasts.keys())
237 versions.sort(key = vkey)
238 if args.minversions <= 1:
239     print('VERS\tFirst\tLast\tSingle\tTotal\tEmpls')
240 else:
241     print('VERS\tFirst\tLast\tTotal\tEmpls')
242 for v in versions:
243     if args.minversions <= 1:
244         print('%s\t%d\t%d\t%d' % (v, len(Firsts[v]), len(Lasts[v]), Singles[v]),
245               end = '')
246     else:
247         print('%s\t%d\t%d' % (v, len(Firsts.get(v, [])),
248                               CountLasts(Lasts.get(v, []))), end = '')
249     print('\t%d\t%d' % (len(DevsPerVersion[v]), len(EmplsPerVersion[v]) - 3))
250 PrintFirstDirs()
251 PrintFirstEmpls()