Skip to content

Commit 9c1e6a9

Browse files
authored
fix(longevity): incremental line age collection (APP-103) (#36)
1 parent 6b55411 commit 9c1e6a9

File tree

2 files changed

+232
-74
lines changed

2 files changed

+232
-74
lines changed

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

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.eclipse.jgit.diff.DiffFormatter
1717
import org.eclipse.jgit.diff.DiffEntry
1818
import org.eclipse.jgit.diff.RawText
1919
import org.eclipse.jgit.api.Git
20+
import org.eclipse.jgit.lib.AnyObjectId
2021
import org.eclipse.jgit.lib.Repository
2122
import org.eclipse.jgit.revwalk.RevCommit
2223
import org.eclipse.jgit.revwalk.RevWalk
@@ -29,15 +30,21 @@ import java.util.Date
2930
/**
3031
* Represents a code line in a file revision.
3132
*/
32-
class RevCommitLine(val commit: RevCommit, val file: String, val line: Int)
33+
class RevCommitLine(val commit: RevCommit, val fileId: AnyObjectId,
34+
val file: String, val line: Int,
35+
val isDeleted: Boolean) {
36+
37+
val id : String
38+
get() = "${fileId.getName()}:$line"
39+
}
3340

3441
/**
3542
* Represents a code line in repo's history.
3643
*
3744
* TODO(Alex): the text arg is solely for testing proposes (remove it)
3845
*/
39-
class CodeLine(val from: RevCommitLine, val to: RevCommitLine,
40-
val text: String) {
46+
class CodeLine(val repo: Repository,
47+
val from: RevCommitLine, val to: RevCommitLine) {
4148

4249
// TODO(alex): oldId and newId may be computed as a hash built from commit,
4350
// file name and line number, if we are going to send the data outside a
@@ -48,18 +55,39 @@ class CodeLine(val from: RevCommitLine, val to: RevCommitLine,
4855
* identify a line and update its lifetime computed at the previous
4956
* iteration.
5057
*/
51-
val oldId: String = ""
58+
val oldId : String
59+
get() = from.id
5260

5361
/**
5462
* Id of the code line in a revision, where the line was deleted, or a head
5563
* revision, if the line is alive.
5664
*/
57-
val newId: String = ""
65+
val newId : String
66+
get() = to.id
5867

5968
/**
6069
* The code line's age in seconds.
6170
*/
62-
val age = to.commit.getCommitTime() - from.commit.getCommitTime()
71+
val age : Long
72+
get() = (to.commit.getCommitTime() - from.commit.getCommitTime()).toLong()
73+
74+
/**
75+
* The code line text.
76+
*/
77+
val text : String
78+
get() = RawText(repo.open(from.fileId).getBytes()).getString(from.line)
79+
80+
/**
81+
* Email address of the line's author.
82+
*/
83+
val email : String
84+
get() = to.commit.authorIdent.emailAddress
85+
86+
/**
87+
* True if the line is deleted.
88+
*/
89+
val isDeleted : Boolean
90+
get() = to.isDeleted
6391

6492
/**
6593
* A pretty print of a code line; debugging.
@@ -70,8 +98,9 @@ class CodeLine(val from: RevCommitLine, val to: RevCommitLine,
7098
val td = df.format(Date(to.commit.getCommitTime().toLong() * 1000))
7199
val fc = "${from.commit.getName()} '${from.commit.getShortMessage()}'"
72100
val tc = "${to.commit.getName()} '${to.commit.getShortMessage()}'"
101+
val state = if (isDeleted) "deleted in" else "last known as"
73102
return "Line '$text' - '${from.file}:${from.line}' added in $fc $fd\n" +
74-
" last known as '${to.file}:${to.line}' in $tc $td"
103+
" ${state} '${to.file}:${to.line}' in $tc $td"
75104
}
76105
}
77106

@@ -81,13 +110,10 @@ class CodeLine(val from: RevCommitLine, val to: RevCommitLine,
81110
class CodeLongevity(private val localRepo: LocalRepo,
82111
private val serverRepo: Repo,
83112
private val api: Api,
84-
private val git: Git, tailRev: String = "") {
113+
private val git: Git) {
85114
val repo: Repository = git.repository
86115
val head: RevCommit =
87116
RevWalk(repo).parseCommit(repo.resolve(RepoHelper.MASTER_BRANCH))
88-
val tail: RevCommit? =
89-
if (tailRev != "") RevWalk(repo).parseCommit(repo.resolve(tailRev))
90-
else null
91117
val df = DiffFormatter(DisabledOutputStream.INSTANCE)
92118

93119
init {
@@ -154,9 +180,9 @@ class CodeLongevity(private val localRepo: LocalRepo,
154180
* Returns a list of code lines, both alive and deleted, between
155181
* the revisions of the repo.
156182
*/
157-
fun getLinesList() : List<CodeLine> {
183+
fun getLinesList(tail : RevCommit? = null) : List<CodeLine> {
158184
val codeLines: MutableList<CodeLine> = mutableListOf()
159-
getLinesObservable().blockingSubscribe { line ->
185+
getLinesObservable(tail).blockingSubscribe { line ->
160186
codeLines.add(line)
161187
}
162188
return codeLines
@@ -166,30 +192,31 @@ class CodeLongevity(private val localRepo: LocalRepo,
166192
* Returns an observable for for code lines, both alive and deleted, between
167193
* the revisions of the repo.
168194
*/
169-
private fun getLinesObservable(): Observable<CodeLine> =
195+
fun getLinesObservable(tail : RevCommit? = null) : Observable<CodeLine> =
170196
Observable.create { subscriber ->
171197

172-
val treeWalk = TreeWalk(repo)
173-
treeWalk.setRecursive(true)
174-
treeWalk.addTree(head.getTree())
198+
val headWalk = TreeWalk(repo)
199+
headWalk.setRecursive(true)
200+
headWalk.addTree(head.getTree())
175201

176202
val files: MutableMap<String, ArrayList<RevCommitLine>> = mutableMapOf()
177203

178204
// Build a map of file names and their code lines.
179-
while (treeWalk.next()) {
180-
val path = treeWalk.getPathString()
181-
val fileLoader = repo.open(treeWalk.getObjectId(0))
205+
while (headWalk.next()) {
206+
val path = headWalk.getPathString()
207+
val fileId = headWalk.getObjectId(0)
208+
val fileLoader = repo.open(fileId)
182209
if (!RawText.isBinary(fileLoader.openStream())) {
183210
val fileText = RawText(fileLoader.getBytes())
184211
var lines = ArrayList<RevCommitLine>(fileText.size())
185212
for (idx in 0 .. fileText.size() - 1) {
186-
lines.add(RevCommitLine(head, path, idx))
213+
lines.add(RevCommitLine(head, fileId, path, idx, false))
187214
}
188215
files.put(path, lines)
189216
}
190217
}
191-
192-
getDiffsObservable().blockingSubscribe { (commit, diffs) ->
218+
219+
getDiffsObservable(tail).blockingSubscribe { (commit, diffs) ->
193220
// A step back in commits history. Update the files map according
194221
// to the diff.
195222
for (diff in diffs) {
@@ -210,12 +237,12 @@ class CodeLongevity(private val localRepo: LocalRepo,
210237
continue
211238
}
212239

213-
// File was deleted, put its lines into the files map.
240+
// File was deleted, initialize the line array in the files map.
214241
if (diff.changeType == DiffEntry.ChangeType.DELETE) {
215242
val fileLoader = repo.open(oldId)
216243
val fileText = RawText(fileLoader.getBytes())
217-
val lines = ArrayList<RevCommitLine>(fileText.size())
218-
files.put(oldPath, lines)
244+
files.put(oldPath,
245+
ArrayList<RevCommitLine>(fileText.size()))
219246
}
220247

221248
// If a file was deleted, then the new path is /dev/null.
@@ -238,15 +265,13 @@ class CodeLongevity(private val localRepo: LocalRepo,
238265
var insEnd = edit.getEndB()
239266
Logger.debug("ins ($insStart, $insEnd)")
240267

241-
val fileLoader = repo.open(newId)
242-
val fileText = RawText(fileLoader.getBytes())
243-
244268
for (idx in insStart .. insEnd - 1) {
245-
val from = RevCommitLine(commit, newPath, idx)
269+
val from = RevCommitLine(commit, newId,
270+
newPath, idx, false)
246271
var to = lines.get(idx)
247-
val cl = CodeLine(from, to, fileText.getString(idx))
248-
subscriber.onNext(cl)
272+
val cl = CodeLine(repo, from, to)
249273
Logger.debug("Collected: ${cl.toString()}")
274+
subscriber.onNext(cl)
250275
}
251276
lines.subList(insStart, insEnd).clear()
252277
}
@@ -264,7 +289,8 @@ class CodeLongevity(private val localRepo: LocalRepo,
264289

265290
var tmpLines = ArrayList<RevCommitLine>(delCount)
266291
for (idx in delStart .. delEnd - 1) {
267-
tmpLines.add(RevCommitLine(commit, oldPath, idx))
292+
tmpLines.add(RevCommitLine(commit, oldId,
293+
oldPath, idx, true))
268294
}
269295
lines.addAll(delStart, tmpLines)
270296
}
@@ -282,12 +308,22 @@ class CodeLongevity(private val localRepo: LocalRepo,
282308
// them all into the result lines list, so the caller can update their
283309
// ages properly.
284310
if (tail != null) {
285-
for ((file, lines) in files) {
286-
for (idx in 0 .. lines.size - 1) {
287-
val from = RevCommitLine(tail, file, idx)
288-
val cl = CodeLine(from, lines[idx],
289-
"no data (too lazy to compute)")
290-
subscriber.onNext(cl)
311+
val tailWalk = TreeWalk(repo)
312+
tailWalk.setRecursive(true)
313+
tailWalk.addTree(tail.getTree())
314+
315+
while (tailWalk.next()) {
316+
val filePath = tailWalk.getPathString()
317+
val lines = files.get(filePath)
318+
if (lines != null) {
319+
val fileId = tailWalk.getObjectId(0)
320+
for (idx in 0 .. lines.size - 1) {
321+
val from = RevCommitLine(tail, fileId,
322+
filePath, idx, false)
323+
val cl = CodeLine(repo, from, lines[idx])
324+
Logger.debug("Collected (tail): ${cl.toString()}")
325+
subscriber.onNext(cl)
326+
}
291327
}
292328
}
293329
}
@@ -298,7 +334,8 @@ class CodeLongevity(private val localRepo: LocalRepo,
298334
/**
299335
* Iterates over the diffs between commits in the repo's history.
300336
*/
301-
private fun getDiffsObservable(): Observable<Pair<RevCommit, List<DiffEntry>>> =
337+
private fun getDiffsObservable(tail : RevCommit?) :
338+
Observable<Pair<RevCommit, List<DiffEntry>>> =
302339
Observable.create { subscriber ->
303340

304341
val revWalk = RevWalk(repo)

0 commit comments

Comments
 (0)