@@ -73,8 +73,14 @@ class CodeLine(val repo: Repository,
73
73
/* *
74
74
* The code line's age in seconds.
75
75
*/
76
- val age : Long
77
- get() = (to.commit.commitTime - from.commit.commitTime).toLong()
76
+ var age : Long = 0
77
+ get() {
78
+ if (field == 0L ) {
79
+ field = (to.commit.commitTime - from.commit.commitTime).toLong()
80
+ }
81
+ return field
82
+ }
83
+ set(v) { field = v }
78
84
79
85
/* *
80
86
* The code line text.
@@ -85,9 +91,21 @@ class CodeLine(val repo: Repository,
85
91
/* *
86
92
* Email address of the line's author.
87
93
*/
88
- val email : String
94
+ val authorEmail : String
89
95
get() = from.commit.authorIdent.emailAddress
90
96
97
+ /* *
98
+ * Email address of the line's changer.
99
+ */
100
+ val editorEmail : String?
101
+ get() = if (isDeleted) to.commit.authorIdent.emailAddress else null
102
+
103
+ /* *
104
+ * A date when the line was changed.
105
+ */
106
+ val editDate : Date
107
+ get() = Date (to.commit.getCommitTime().toLong() * 1000 )
108
+
91
109
/* *
92
110
* True if the line is deleted.
93
111
*/
@@ -107,7 +125,71 @@ class CodeLine(val repo: Repository,
107
125
val state = if (isDeleted) " deleted" else " alive"
108
126
return " Line '$text ' - '${from.file} :${from.line} ' added in $fc $fd \n " +
109
127
" ${revState} '${to.file} :${to.line} ' in $tc $td ,\n " +
110
- " age: ${age} ms - $state "
128
+ " age: ${age} s - $state "
129
+ }
130
+ }
131
+
132
+ /* *
133
+ * Detects colleagues and their 'work vicinity' from commits.
134
+ */
135
+ class Colleagues {
136
+ // A map of <colleague_email1, colleague_email2> pairs to pairs of
137
+ // <month, time>, which indicates to a minimum time in ms between all line
138
+ // changes for these two colleagues in a given month (yyyy-mm).
139
+ private val map: HashMap <Pair <String , String >,
140
+ HashMap <String , Long >> = hashMapOf()
141
+
142
+ fun collect (line : CodeLine ) {
143
+ // TODO(alex): ignore same user emails
144
+ val authorEmail = line.authorEmail
145
+ val editorEmail = line.editorEmail
146
+ if (editorEmail == null || authorEmail == editorEmail) {
147
+ return
148
+ }
149
+ val emails =
150
+ if (editorEmail < authorEmail) Pair (editorEmail, authorEmail)
151
+ else Pair (authorEmail, editorEmail)
152
+
153
+ val dates = map.getOrPut(emails, { hashMapOf() })
154
+ val month = SimpleDateFormat (" yyyy-MM" ).format(line.editDate)
155
+
156
+ Logger .trace { " collected colleague, age: ${line.age} " }
157
+ var vicinity = dates.getOrPut(month, { line.age })
158
+ if (vicinity > line.age) {
159
+ dates.put(month, line.age)
160
+ }
161
+ }
162
+
163
+ fun updateStats () {
164
+ // TODO(alex): report the stats to the server
165
+ if (Logger .isDebug) {
166
+ map.forEach( { (email1, email2), dates ->
167
+ Logger .debug { " <$email1 > <$email2 >" }
168
+ dates.forEach { month, vicinity ->
169
+ Logger .debug { " $month : $vicinity ms" }
170
+ }
171
+ } )
172
+ }
173
+ }
174
+
175
+ /* *
176
+ * Return colleagues in a form of <email, month, time> for the given
177
+ * email, where time indicates a minimal time in ms between all line edits
178
+ * by these colleagues in a given month (yyyy-mm).
179
+ */
180
+ fun get (email : String ) : List <Triple <String , String , Long >> {
181
+ return map
182
+ .filter { (pair, _) -> pair.first == email || pair.second == email }
183
+ .flatMap { (pair, dates) ->
184
+ val colleagueEmail =
185
+ if (email == pair.first) pair.second else pair.first
186
+
187
+ var list = mutableListOf<Triple <String , String , Long >>()
188
+ dates.forEach { month, vicinity ->
189
+ list.add(Triple (colleagueEmail, month, vicinity))
190
+ }
191
+ return list
192
+ }
111
193
}
112
194
}
113
195
@@ -152,6 +234,7 @@ class CodeLongevity(private val serverRepo: Repo,
152
234
153
235
val df = DiffFormatter (DisabledOutputStream .INSTANCE )
154
236
val dataPath = FileHelper .getPath(serverRepo.rehash, " longevity" )
237
+ val colleagues = Colleagues ()
155
238
156
239
init {
157
240
df.setRepository(repo)
@@ -212,6 +295,8 @@ class CodeLongevity(private val serverRepo: Repo,
212
295
api.postFacts(stats).onErrorThrow()
213
296
Logger .info { " Sent ${stats.size} facts to server" }
214
297
}
298
+
299
+ colleagues.updateStats();
215
300
}
216
301
217
302
/* *
@@ -244,24 +329,23 @@ class CodeLongevity(private val serverRepo: Repo,
244
329
245
330
// Update ages.
246
331
getLinesObservable(storedHead).blockingSubscribe { line ->
247
- Logger .trace { " Scanning: ${line} " }
248
- if (line.to.isDeleted) {
249
- var age = line.age
332
+ if (line.isDeleted) {
250
333
if (ageData.lastingLines.contains(line.oldId)) {
251
- age + = ageData.lastingLines.remove(line.oldId)!! .age
334
+ line. age + = ageData.lastingLines.remove(line.oldId)!! .age
252
335
}
253
- val aggrAge = ageData.aggrAges.getOrPut(line.email ,
336
+ val aggrAge = ageData.aggrAges.getOrPut(line.authorEmail ,
254
337
{ CodeLineAges .AggrAge () } )
255
- aggrAge.sum + = age
338
+ aggrAge.sum + = line. age
256
339
aggrAge.count + = 1
257
340
341
+ colleagues.collect(line);
258
342
} else {
259
343
var age = line.age
260
344
if (ageData.lastingLines.contains(line.oldId)) {
261
345
age + = ageData.lastingLines.remove(line.oldId)!! .age
262
346
}
263
347
ageData.lastingLines.put(line.newId,
264
- CodeLineAges .LineInfo (age, line.email ))
348
+ CodeLineAges .LineInfo (age, line.authorEmail ))
265
349
}
266
350
}
267
351
0 commit comments