1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
4c0630 tucker
4c0630 tucker
4c0630 tucker
4c0630 tucker
4c0630 tucker
4c0630 tucker
4c0630 tucker
4c0630 tucker
4c0630 tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
b8a3bb tucker
import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText} from '../../helpers.ts'
export const getFileList = async (repoName: string, branchName: string, repoLocation: string) => {
const command = `git ls-tree -r --name-only ${branchName}`
const result = await exec(`(cd ${repoLocation} && ${command})`)
let files = result.stdout.split("\n").filter(item => item.length > 0 && item != ".")
// TODO: this could be better. This is adding each sub-path of a file to a set, so that
// we don't wind up with repeats, and then converting that back into an array. E.g. if
// we have two files:
// - posts/blog/one.md
// - posts/blog/two.md
// this will add the following to the set:
// posts, posts/blog, posts/blog/one.md, posts, posts/blog, posts/blog/two.md
// The repeats will be omitted because it's a Set, and the resulting array will
// be [posts, posts/blog, posts/blog/one.md, posts/blog/two.md].
// This is because it's convenient to have the directories show up as their own "file"
// in the file list, even though git doesn't treat them that way.
const fileSet: Set<string> = new Set()
files.forEach((file) => {
const fileParts = file.split("/")
const allPathsInFile = fileParts.reduce((accumulator, currentValue, index) => {
// Skip the first iteration, we have already added it as the initialValue
if (index === 0) { return accumulator }
accumulator.push(accumulator[accumulator.length - 1] + '/' + currentValue)
return accumulator
}, [fileParts[0]])
allPathsInFile.forEach((path) => {
fileSet.add(path)
})
})
return Array.from(fileSet)
}
export const getBranchInfo = async (repoName: string, branchName: string, repoLocation: string) => {
const patches = new Map()
const totalPatchesCountRes = await exec(`(cd ${repoLocation} && git rev-list --count ${branchName})`)
const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
for (let i = 0; i < totalPatchesCount; i = i + 10) {
const gitLogSubsetRes = await exec(`(cd ${repoLocation} && git log ${branchName} -p -n 10 --skip ${i})`)
let gitLogSubset = gitLogSubsetRes.stdout.split("\n")
do {
const nextPatchStart = gitLogSubset.findIndex((line, index) => {
return (index > 0 && line.startsWith("commit ")) || index === gitLogSubset.length - 1
})
const isEndOfFile = nextPatchStart === gitLogSubset.length - 1
const currentPatch = gitLogSubset.slice(0, isEndOfFile ? nextPatchStart : nextPatchStart - 1)
gitLogSubset = gitLogSubset.slice(nextPatchStart)
const hash = currentPatch[0].replace("commit ", "").trim()
let author, date
[1, 2, 3].forEach((lineNumber) => {
if (currentPatch[lineNumber].startsWith("Author")) {
author = currentPatch[lineNumber].replace("Author: ", "").trim()
}
else if (currentPatch[lineNumber].startsWith("Date")) {
date = currentPatch[lineNumber].replace("Date: ", "").trim()
}
})
const diffStart = currentPatch.findIndex((line) => {
return line.startsWith("diff ")
})
// Git log is indent four spaces by default -- remove those.
const commitMessage = currentPatch.slice(4, diffStart).map(str => str.replace(" ", ""))
const name = commitMessage[0].trim()
const description = commitMessage.slice(1, commitMessage.length - 1).filter((line) => {
// git log --porcelain output adds these "Ignore-this:" lines and I'm not sure what they are
return !line.startsWith('Ignore-this: ')
}).join("\n").trim()
const diffs = getGitDiffsFromPatchText(currentPatch.slice(diffStart).join("\n"))
patches.set(hash, {
name,
description,
author,
date,
hash,
diffs,
})
} while (gitLogSubset.length > 1)
}
return Array.from(patches.values())
}
export const getFileLastTouchInfo = async (repo, branch, filename, repoLocation) => {
const regex = RegExp(".* [0-9]+ [0-9]+")
const command = `git blame --porcelain ${branch} ${filename}`
const res = await exec(`(cd ${repoLocation} && ${command})`)
const output = res.stdout
const outputLines = output.split("\n")
const initialValue: Array<Array<string>> = [[outputLines[0]]]
const chunked = outputLines.reduce((accumulator, currentLine, currentIndex) => {
// skip the first iteration since we already gave it initialValue
if (currentIndex == 0) { return accumulator }
if (currentLine.match(regex)) {
accumulator.push([currentLine])
return accumulator
}
else {
accumulator[accumulator.length - 1].push(currentLine)
return accumulator
}
}, initialValue)
let currentAuthor = ''
let authorsAndShasByLine = []
chunked.forEach((chunk) => {
let shaAndLineNumberParts = chunk[0].split(' ')
let line = parseInt(shaAndLineNumberParts[2])
let sha = shaAndLineNumberParts[0]
if (chunk[1] && chunk[1].startsWith('author ')) {
currentAuthor = chunk[1].replace('author ', '')
}
authorsAndShasByLine[line - 1] = {sha, author: currentAuthor}
})
return authorsAndShasByLine
}