Files snapshot from file-caching-stash-mistaken-branch-idea

./src/vcses/git/operations.ts

0
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
bcdc64 Tucker McKnight
b8a3bb Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
c945a9 Tucker McKnight
b8a3bb Tucker McKnight
c945a9 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
cdb797 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
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
2a54fe Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
cdb797 Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
bcdc64 Tucker McKnight
c945a9 Tucker McKnight
b8a3bb Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
b8a3bb Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
b8a3bb Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
7e40e5 Tucker McKnight
2a54fe Tucker McKnight
fb82fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
2a54fe Tucker McKnight
7e40e5 Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
f0856f Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
bcdc64 Tucker McKnight
2a54fe Tucker McKnight
2a54fe Tucker McKnight
bcdc64 Tucker McKnight
b8a3bb Tucker McKnight
b8a3bb Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
cdb797 Tucker McKnight
b8a3bb Tucker McKnight
c945a9 Tucker McKnight
c945a9 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
cdb797 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
import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText} from '../../helpers.ts'
import { type Repository, type FileInfo, type Commit } from '../../dataTypes.ts'

export const getFileList = async (
  branchName: string, repoLocation: string
) : Promise<Map<string, {fileInfo: FileInfo}>> => {
  const command = `git -C ${repoLocation} ls-tree -r --name-only ${branchName}`

  const result = await exec(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 filesMap: Map<string, {fileInfo: FileInfo}> = new Map()
  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) => {
      filesMap.set(path, {
        get fileInfo() {
          return {
            contents: "", // TODO: implement this part
            lastModified: new Date(),
            blameLines: []
          }
        }
      })
    })
  })
  return filesMap
}

export const addBranchToCommitsMap = async(branchName: string, repoLocation: string, commits: Repository['commits']): Promise<Array<Commit>> => {
  const totalPatchesCountRes = await exec(`git -C ${repoLocation} rev-list --count ${branchName}`)
  const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
  let gitLogSubsetCmd = null
  let gitLogSubset = []
  return addBranchToCommitsMapWithSkip(branchName, repoLocation, commits, totalPatchesCount)
}

export const addBranchToCommitsMapWithSkip = async(branchName: string, repoLocation: string, commits: Repository['commits'], totalPatchesCount: number, skip = 0): Promise<Array<Commit>> => {
  return new Promise((resolve) => {
    if (skip >= totalPatchesCount) {
      return resolve([])
    }

    const gitLogSubsetCmd = childProcess.spawn('git', ['-C', repoLocation, 'log', branchName, '-p', '-n', '10', '--skip', skip.toString()], {
      stdio: [0, "pipe", "inherit"],
    })
    let gitLogSubset = []
    gitLogSubsetCmd.stdout.on('data', (data) => {
      gitLogSubset.push(data)
    })

    gitLogSubsetCmd.on('close', () => {
      const myChunkOfCommits = addGitLogSubsetToMap(gitLogSubset.join('').split('\n'), commits)
      return addBranchToCommitsMapWithSkip(branchName, repoLocation, commits, totalPatchesCount, skip + 10).then((laterChunksOfCommits) => {
        return resolve(myChunkOfCommits.concat(laterChunksOfCommits))
      })
    })
  })
}

const addGitLogSubsetToMap = (gitLogSubset: string[], commits: Repository['commits']) => {
  const orderedCommits: Array<Commit> = []

  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()

    // exit early if the commits map already includes this hash
    if (commits.has(hash)) {
      continue
    }

    let author: string, date: string, isMerge: boolean
    [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()
      }
      else if (currentPatch[lineNumber].startsWith("Merge")) {
        isMerge = true
      }
    })
    let diffStart = currentPatch.findIndex((line) => {
      return line.startsWith("diff ")
    })
    // If no line starts with "diff", this
    // is probably a mege commit. Use the last
    // line of the patch + 1, in that case, to just get the full
    // text of the commit
    let messageStart = 4
    if (diffStart === -1) {
      messageStart = 5
      diffStart = currentPatch.length
    }
    // Git log is indent four spaces by default -- remove those.
    const commitMessage = currentPatch.slice(messageStart, diffStart).map(str => str.replace("    ", "")).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"))

    let commit = {
      hash,
      message: commitMessage,
      isMerge: isMerge || false,
      author,
      date: new Date(date),
      diffs,
      parent: null,
      cachedFiles: new Map(),
    }

    orderedCommits.push(commit)
    commits.set(hash, commit)
  } while (gitLogSubset.length > 1)

  return orderedCommits
}

export type BlameInfo = {
  sha: string,
  author: string,
}

export const getFileLastTouchInfo = async (
  branch: string, filename: string, repoLocation: string
) : Promise<Array<BlameInfo>> => {
  const regex = RegExp(".* [0-9]+ [0-9]+")
  const command = `git -C ${repoLocation} blame --porcelain ${branch} ${filename}`
  const res = await exec(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: Array<BlameInfo> = []
  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
}