Skip to content

Commit 3db0ff1

Browse files
authored
feat: add repo facts, add email filtering for all hashers (#40)
* feat: add repo facts, add email filtering for all hashers * chore(CommitHasher): update filters * fix: merge
1 parent bd18eb5 commit 3db0ff1

File tree

10 files changed

+274
-158
lines changed

10 files changed

+274
-158
lines changed

src/main/kotlin/app/FactCodes.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ object FactCodes {
55
val COMMITS_DAY_TIME = 2
66
val LINE_LONGEVITY = 3
77
val LINE_LONGEVITY_REPO = 4
8+
val REPO_DATE_START = 5
9+
val REPO_DATE_END = 6
10+
val REPO_TEAM_SIZE = 7
811
}

src/main/kotlin/app/hashers/CodeLongevity.kt

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import app.FactCodes
77
import app.Logger
88
import app.api.Api
99
import app.model.Author
10-
import app.model.LocalRepo
1110
import app.model.Repo
1211
import app.model.Fact
1312
import app.utils.RepoHelper
@@ -106,9 +105,9 @@ class CodeLine(val repo: Repository,
106105
/**
107106
* Used to compute age of code lines in the repo.
108107
*/
109-
class CodeLongevity(private val localRepo: LocalRepo,
110-
private val serverRepo: Repo,
108+
class CodeLongevity(private val serverRepo: Repo,
111109
private val api: Api,
110+
private val emails: HashSet<String>,
112111
git: Git) {
113112
val repo: Repository = git.repository
114113
val head: RevCommit =
@@ -121,9 +120,6 @@ class CodeLongevity(private val localRepo: LocalRepo,
121120
}
122121

123122
fun update() {
124-
// TODO(anatoly): Add emails from server or hashAll.
125-
val emails = hashSetOf(localRepo.author.email)
126-
127123
val sums: MutableMap<String, Long> = emails.associate { Pair(it, 0L) }
128124
.toMutableMap()
129125
val totals: MutableMap<String, Int> = emails.associate { Pair(it, 0) }
@@ -150,7 +146,7 @@ class CodeLongevity(private val localRepo: LocalRepo,
150146
val stats = mutableListOf<Fact>()
151147
stats.add(Fact(repo = serverRepo,
152148
code = FactCodes.LINE_LONGEVITY_REPO,
153-
value = repoAvg.toDouble()))
149+
value = repoAvg.toString()))
154150
val repoAvgDays = repoAvg / secondsInDay
155151
Logger.info("Repo average code line age is $repoAvgDays days, "
156152
+ "lines total: $repoTotal")
@@ -160,13 +156,8 @@ class CodeLongevity(private val localRepo: LocalRepo,
160156
val avg = if (total > 0) { sums[email]!! / total } else 0
161157
stats.add(Fact(repo = serverRepo,
162158
code = FactCodes.LINE_LONGEVITY,
163-
value = avg.toDouble(),
159+
value = avg.toString(),
164160
author = Author(email = email)))
165-
if (email == localRepo.author.email) {
166-
val avgDays = avg / secondsInDay
167-
Logger.info("Your average code line age is $avgDays days, "
168-
+ "lines total: $total")
169-
}
170161
}
171162

172163
if (stats.size > 0) {

src/main/kotlin/app/hashers/CommitHasher.kt

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ import app.Logger
77
import app.api.Api
88
import app.extractors.Extractor
99
import app.model.Commit
10-
import app.model.LocalRepo
1110
import app.model.Repo
1211
import io.reactivex.Observable
1312
import java.util.concurrent.TimeUnit
1413

1514
/**
1615
* CommitHasher hashes repository and uploads stats to server.
1716
*/
18-
class CommitHasher(private val localRepo: LocalRepo,
19-
private val serverRepo: Repo = Repo(),
17+
class CommitHasher(private val serverRepo: Repo = Repo(),
2018
private val api: Api,
21-
private val rehashes: List<String>) {
19+
private val rehashes: List<String>,
20+
private val emails: HashSet<String>) {
2221

2322
init {
2423
// Delete locally missing commits from server. If found at least one
@@ -30,36 +29,6 @@ class CommitHasher(private val localRepo: LocalRepo,
3029
deleteCommitsOnServer(deletedCommits)
3130
}
3231

33-
private fun findFirstOverlappingCommitRehash(): String? {
34-
35-
val serverHistoryRehashes = serverRepo.commits
36-
.map { commit -> commit.rehash }
37-
.toHashSet()
38-
return rehashes.firstOrNull { rehash ->
39-
serverHistoryRehashes.contains(rehash)
40-
}
41-
}
42-
43-
private fun postCommitsToServer(commits: List<Commit>) {
44-
if (commits.isNotEmpty()) {
45-
api.postCommits(commits)
46-
Logger.debug("Sent ${commits.size} added commits to server")
47-
}
48-
}
49-
50-
private fun deleteCommitsOnServer(commits: List<Commit>) {
51-
if (commits.isNotEmpty()) {
52-
api.deleteCommits(commits)
53-
Logger.debug("Sent ${commits.size} deleted commits to server")
54-
}
55-
}
56-
57-
private val emailFilter: (Commit) -> Boolean = {
58-
val email = it.author.email
59-
localRepo.hashAllContributors || (email == localRepo.author.email ||
60-
serverRepo.emails.contains(email))
61-
}
62-
6332
// Hash added and missing server commits and send them to server.
6433
fun updateFromObservable(observable: Observable<Commit>,
6534
onError: (Throwable) -> Unit) {
@@ -70,10 +39,10 @@ class CommitHasher(private val localRepo: LocalRepo,
7039
.takeWhile { new -> // Hash until last known commit.
7140
new.rehash != lastKnownCommit?.rehash
7241
}
73-
.filter { commit -> // Don't hash known.
74-
knownCommits.isEmpty() || !knownCommits.contains(commit)
75-
}
76-
.filter { commit -> emailFilter(commit) } // Email filtering.
42+
// Don't hash known to server commits.
43+
.filter { commit -> !knownCommits.contains(commit) }
44+
// Hash only commits made by authors with specified emails.
45+
.filter { commit -> emails.contains(commit.author.email) }
7746
.map { commit ->
7847
// Mapping and stats extraction.
7948
commit.stats = Extractor().extract(commit.diffs)
@@ -95,4 +64,27 @@ class CommitHasher(private val localRepo: LocalRepo,
9564
postCommitsToServer(commitsBundle) // Send ready commits.
9665
}, onError)
9766
}
67+
68+
private fun findFirstOverlappingCommitRehash(): String? {
69+
val serverHistoryRehashes = serverRepo.commits
70+
.map { commit -> commit.rehash }
71+
.toHashSet()
72+
return rehashes.firstOrNull { rehash ->
73+
serverHistoryRehashes.contains(rehash)
74+
}
75+
}
76+
77+
private fun postCommitsToServer(commits: List<Commit>) {
78+
if (commits.isNotEmpty()) {
79+
api.postCommits(commits)
80+
Logger.debug("Sent ${commits.size} added commits to server")
81+
}
82+
}
83+
84+
private fun deleteCommitsOnServer(commits: List<Commit>) {
85+
if (commits.isNotEmpty()) {
86+
api.deleteCommits(commits)
87+
Logger.debug("Sent ${commits.size} deleted commits to server")
88+
}
89+
}
9890
}

src/main/kotlin/app/hashers/FactHasher.kt

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import app.api.Api
99
import app.model.Author
1010
import app.model.Commit
1111
import app.model.Fact
12-
import app.model.LocalRepo
1312
import app.model.Repo
1413
import io.reactivex.Observable
1514
import java.time.LocalDateTime
@@ -18,63 +17,92 @@ import java.time.ZoneOffset
1817
/**
1918
* CommitHasher hashes repository and uploads stats to server.
2019
*/
21-
class FactHasher(private val localRepo: LocalRepo,
22-
private val serverRepo: Repo = Repo(),
23-
private val api: Api) {
20+
class FactHasher(private val serverRepo: Repo = Repo(),
21+
private val api: Api,
22+
private val emails: HashSet<String>) {
23+
private val fsDayWeek = hashMapOf<String, Array<Int>>()
24+
private val fsDayTime = hashMapOf<String, Array<Int>>()
25+
private val fsRepoDateStart = hashMapOf<String, Long>()
26+
private val fsRepoDateEnd = hashMapOf<String, Long>()
27+
private val fsRepoTeamSize = hashSetOf<String>()
2428

25-
private fun postFactsToServer(facts: List<Fact>) {
26-
if (facts.isNotEmpty()) {
27-
api.postFacts(facts)
28-
Logger.debug("Sent ${facts.size} facts to server")
29+
init {
30+
for (author in emails) {
31+
fsDayWeek.put(author, Array(7) { 0 })
32+
fsDayTime.put(author, Array(24) { 0 })
33+
fsRepoDateStart.put(author, -1)
34+
fsRepoDateEnd.put(author, -1)
2935
}
3036
}
3137

3238
fun updateFromObservable(observable: Observable<Commit>,
3339
onError: (Throwable) -> Unit) {
34-
val factsDayWeek = hashMapOf<Author, Array<Int>>()
35-
val factsDayTime = hashMapOf<Author, Array<Int>>()
36-
37-
// TODO(anatoly): Filter hashing by email as in CommitHasher.
3840
observable
41+
.filter { commit -> emails.contains(commit.author.email) }
3942
.subscribe({ commit -> // OnNext.
4043
// Calculate facts.
41-
val author = commit.author
42-
val factDayWeek = factsDayWeek[author] ?: Array(7) { 0 }
43-
val factDayTime = factsDayTime[author] ?: Array(24) { 0 }
44+
val email = commit.author.email
4445
val timestamp = commit.dateTimestamp
4546
val dateTime = LocalDateTime.ofEpochSecond(timestamp, 0,
4647
ZoneOffset.ofTotalSeconds(commit.dateTimeZoneOffset * 60))
48+
49+
// DayWeek.
50+
val factDayWeek = fsDayWeek[email] ?: Array(7) { 0 }
4751
// The value is numbered from 1 (Monday) to 7 (Sunday).
4852
factDayWeek[dateTime.dayOfWeek.value - 1] += 1
53+
fsDayWeek[email] = factDayWeek
54+
55+
// DayTime.
56+
val factDayTime = fsDayTime[email] ?: Array(24) { 0 }
4957
// Hour from 0 to 23.
5058
factDayTime[dateTime.hour] += 1
51-
factsDayWeek[author] = factDayWeek
52-
factsDayTime[author] = factDayTime
59+
fsDayTime[email] = factDayTime
60+
61+
// RepoDateStart.
62+
fsRepoDateStart[email] = timestamp
63+
64+
// RepoDateEnd.
65+
if ((fsRepoDateEnd[email] ?: -1) == -1L) {
66+
fsRepoDateEnd[email] = timestamp
67+
}
68+
69+
// RepoTeamSize.
70+
fsRepoTeamSize.add(email)
5371
}, onError, { // OnComplete.
5472
try {
55-
val facts = mutableListOf<Fact>()
56-
factsDayTime.map { (author, list) ->
57-
list.forEachIndexed { hour, count ->
58-
if (count > 0) {
59-
facts.add(Fact(serverRepo,
60-
FactCodes.COMMITS_DAY_TIME, hour,
61-
count.toDouble(), author))
62-
}
63-
}
64-
}
65-
factsDayWeek.map { (author, list) ->
66-
list.forEachIndexed { day, count ->
67-
if (count > 0) {
68-
facts.add(Fact(serverRepo,
69-
FactCodes.COMMITS_DAY_WEEK, day,
70-
count.toDouble(), author))
71-
}
72-
}
73-
}
74-
postFactsToServer(facts)
73+
postFactsToServer(createFacts())
7574
} catch (e: Throwable) {
7675
onError(e)
7776
}
7877
})
7978
}
79+
80+
private fun createFacts(): List<Fact> {
81+
val fs = mutableListOf<Fact>()
82+
emails.forEach { email ->
83+
val author = Author(email = email)
84+
fsDayTime[email]?.forEachIndexed { hour, count -> if (count > 0) {
85+
fs.add(Fact(serverRepo, FactCodes.COMMITS_DAY_TIME, hour,
86+
count.toString(), author))
87+
}}
88+
fsDayWeek[email]?.forEachIndexed { day, count -> if (count > 0) {
89+
fs.add(Fact(serverRepo, FactCodes.COMMITS_DAY_WEEK, day,
90+
count.toString(), author))
91+
}}
92+
fs.add(Fact(serverRepo, FactCodes.REPO_DATE_START, 0,
93+
fsRepoDateStart[email].toString(), author))
94+
fs.add(Fact(serverRepo, FactCodes.REPO_DATE_END, 0,
95+
fsRepoDateEnd[email].toString(), author))
96+
}
97+
fs.add(Fact(serverRepo, FactCodes.REPO_TEAM_SIZE, 0,
98+
fsRepoTeamSize.size.toString()))
99+
return fs
100+
}
101+
102+
private fun postFactsToServer(facts: List<Fact>) {
103+
if (facts.isNotEmpty()) {
104+
api.postFacts(facts)
105+
Logger.debug("Sent ${facts.size} facts to server")
106+
}
107+
}
80108
}

src/main/kotlin/app/hashers/RepoHasher.kt

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.eclipse.jgit.revwalk.RevWalk
1818
import java.io.File
1919
import java.io.IOException
2020
import java.util.*
21+
import kotlin.collections.HashSet
2122

2223
class RepoHasher(private val localRepo: LocalRepo, private val api: Api,
2324
private val configurator: Configurator) {
@@ -33,13 +34,15 @@ class RepoHasher(private val localRepo: LocalRepo, private val api: Api,
3334
println("Hashing $localRepo...")
3435
val git = loadGit(localRepo.path)
3536
try {
36-
val (rehashes, _) = fetchRehashesAndAuthors(git)
37+
val (rehashes, emails) = fetchRehashesAndEmails(git)
3738

3839
localRepo.parseGitConfig(git.repository.config)
3940
if (localRepo.author.email.isBlank()) {
4041
throw IllegalStateException("Can't load email from Git config")
4142
}
4243

44+
val filteredEmails = filterEmails(emails)
45+
4346
initServerRepo(rehashes.last)
4447

4548
if (!isKnownRepo()) {
@@ -60,16 +63,17 @@ class RepoHasher(private val localRepo: LocalRepo, private val api: Api,
6063
// Hash by all plugins.
6164
val observable = CommitCrawler.getObservable(git, serverRepo)
6265
.publish()
63-
CommitHasher(localRepo, serverRepo, api, rehashes)
66+
CommitHasher(serverRepo, api, rehashes, filteredEmails)
6467
.updateFromObservable(observable, onError)
65-
FactHasher(localRepo, serverRepo, api)
68+
FactHasher(serverRepo, api, filteredEmails)
6669
.updateFromObservable(observable, onError)
70+
6771
// Start and synchronously wait until all subscribers complete.
6872
observable.connect()
6973

7074
// TODO(anatoly): CodeLongevity hash from observable.
7175
try {
72-
CodeLongevity(localRepo, serverRepo, api, git).update()
76+
CodeLongevity(serverRepo, api, filteredEmails, git).update()
7377
}
7478
catch (e: Throwable) {
7579
onError(e)
@@ -125,7 +129,7 @@ class RepoHasher(private val localRepo: LocalRepo, private val api: Api,
125129
serverRepo.initialCommitRehash, localRepo)
126130
}
127131

128-
private fun fetchRehashesAndAuthors(git: Git):
132+
private fun fetchRehashesAndEmails(git: Git):
129133
Pair<LinkedList<String>, HashSet<String>> {
130134
val head: RevCommit = RevWalk(git.repository)
131135
.parseCommit(git.repository.resolve(RepoHelper.MASTER_BRANCH))
@@ -134,17 +138,29 @@ class RepoHasher(private val localRepo: LocalRepo, private val api: Api,
134138
revWalk.markStart(head)
135139

136140
val commitsRehashes = LinkedList<String>()
137-
val authors = hashSetOf<String>()
141+
val emails = hashSetOf<String>()
138142

139143
var commit: RevCommit? = revWalk.next()
140144
while (commit != null) {
141145
commitsRehashes.add(DigestUtils.sha256Hex(commit.name))
142-
authors.add(commit.authorIdent.emailAddress)
146+
emails.add(commit.authorIdent.emailAddress)
143147
commit.disposeBody()
144148
commit = revWalk.next()
145149
}
146150
revWalk.dispose()
147151

148-
return Pair(commitsRehashes, authors)
152+
return Pair(commitsRehashes, emails)
153+
}
154+
155+
private fun filterEmails(emails: HashSet<String>): HashSet<String> {
156+
if (localRepo.hashAllContributors) {
157+
return emails
158+
}
159+
160+
val knownEmails = mutableListOf<String>()
161+
knownEmails.add(serverRepo.userEmail)
162+
knownEmails.addAll(serverRepo.emails)
163+
164+
return knownEmails.filter { emails.contains(it) }.toHashSet()
149165
}
150166
}

0 commit comments

Comments
 (0)