@gitkraken/core-gitlens 0.9.6 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/dist/git/cache.d.ts +8 -0
- package/dist/git/cache.js +14 -0
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +2 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/statusFile.d.ts +3 -2
- package/dist/git/models/statusFile.js +4 -1
- package/dist/git/models/statusFile.js.map +1 -1
- package/dist/git/providers/commits.d.ts +36 -0
- package/dist/git-cli/exec/exec.d.ts +2 -2
- package/dist/git-cli/exec/exec.js +2 -2
- package/dist/git-cli/exec/git.js +2 -2
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/parsers/statusParser.d.ts +2 -1
- package/dist/git-cli/parsers/statusParser.js +53 -27
- package/dist/git-cli/parsers/statusParser.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts +30 -0
- package/dist/git-cli/providers/commits.js +203 -0
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/status.js +3 -5
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- package/dist/utils/env/node/exec.d.ts +8 -0
- package/dist/utils/env/node/exec.js +15 -6
- package/dist/utils/env/node/exec.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusParser.js","names":[],"sources":["../../../../git-cli/src/parsers/statusParser.ts"],"sourcesContent":["import { GitStatus } from '@gitlens/git/models/status.js';\nimport { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport { normalizePath } from '@gitlens/utils/path.js';\nimport { maybeStopWatch } from '@gitlens/utils/stopwatch.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\n\nconst aheadStatusV1Regex = /(?:ahead ([0-9]+))/;\nconst behindStatusV1Regex = /(?:behind ([0-9]+))/;\nconst quoteRegex = /\"/g;\n\nexport function parseGitStatus(\n\tdata: string,\n\trepoPath: string,\n\tporcelainVersion: number,\n\tgetUri: (path: string) => Uri,\n): GitStatus | undefined {\n\tusing sw = maybeStopWatch(`Git.parseStatus(${repoPath}, v=${porcelainVersion})`, {\n\t\tlog: { onlyExit: true, level: 'debug' },\n\t});\n\tif (!data) {\n\t\tsw?.stop({ suffix: ` no data` });\n\t\treturn undefined;\n\t}\n\n\tconst lines = data.split('\\n').filter(<T>(i?: T): i is T => Boolean(i));\n\tif (lines.length === 0) {\n\t\tsw?.stop({ suffix: ` parsed no files` });\n\t\treturn undefined;\n\t}\n\n\tconst status =\n\t\tporcelainVersion < 2 ? parseStatusV1(lines, repoPath, getUri) : parseStatusV2(lines, repoPath, getUri);\n\n\tsw?.stop({ suffix: ` parsed ${status.files.length} files` });\n\n\treturn status;\n}\n\nfunction parseStatusV1(lines: string[], repoPath: string, getUri: (path: string) => Uri): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet upstream;\n\tlet missing = false;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Header\n\t\tif (line.startsWith('##')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\t[branch, upstream] = lineParts[1].split('...');\n\t\t\tif (lineParts.length > 2) {\n\t\t\t\tconst upstreamStatus = lineParts.slice(2).join(' ');\n\t\t\t\tif (upstreamStatus === '[gone]') {\n\t\t\t\t\tmissing = true;\n\t\t\t\t\tstate.ahead = 0;\n\t\t\t\t\tstate.behind = 0;\n\t\t\t\t} else {\n\t\t\t\t\tconst aheadStatus = aheadStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.ahead = aheadStatus == null ? 0 : Number(aheadStatus[1]) || 0;\n\n\t\t\t\t\tconst behindStatus = behindStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.behind = behindStatus == null ? 0 : Number(behindStatus[1]) || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst rawStatus = line.substring(0, 2);\n\t\t\tconst fileName = line.substring(3);\n\t\t\tif (rawStatus.startsWith('R') || rawStatus.startsWith('C')) {\n\t\t\t\tconst [file1, file2] = fileName.replace(quoteRegex, '').split(' -> ');\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, file2.trim(), getUri, file1.trim()));\n\t\t\t} else {\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, fileName, getUri));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\t'',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusV2(lines: string[], repoPath: string, getUri: (path: string) => Uri): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tlet sha: string | undefined;\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet missing = true;\n\tlet upstream;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Headers\n\t\tif (line.startsWith('#')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\tswitch (lineParts[1]) {\n\t\t\t\tcase 'branch.oid':\n\t\t\t\t\tsha = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.head':\n\t\t\t\t\tbranch = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.upstream':\n\t\t\t\t\tupstream = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.ab':\n\t\t\t\t\tmissing = false;\n\t\t\t\t\tstate.ahead = Number(lineParts[2].substring(1));\n\t\t\t\t\tstate.behind = Number(lineParts[3].substring(1));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\tswitch (lineParts[0][0]) {\n\t\t\t\tcase '1': {\n\t\t\t\t\t// normal: 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>\n\t\t\t\t\t// <sub> starts with 'S' if submodule, 'N' if not\n\t\t\t\t\tconst submodule = lineParts[2]?.startsWith('S')\n\t\t\t\t\t\t? { oid: lineParts[7], previousOid: lineParts[6] }\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tlineParts[1],\n\t\t\t\t\t\t\tlineParts.slice(8).join(' '),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '2': {\n\t\t\t\t\t// rename: 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path>\\t<origPath>\n\t\t\t\t\tconst submodule = lineParts[2]?.startsWith('S')\n\t\t\t\t\t\t? { oid: lineParts[7], previousOid: lineParts[6] }\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst file = lineParts.slice(9).join(' ').split('\\t');\n\t\t\t\t\tfiles.push(parseStatusFile(repoPath, lineParts[1], file[0], getUri, file[1], submodule));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'u': {\n\t\t\t\t\t// unmerged: u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>\n\t\t\t\t\tconst submodule = lineParts[2]?.startsWith('S')\n\t\t\t\t\t\t? { oid: lineParts[9], previousOid: lineParts[7] }\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tlineParts[1],\n\t\t\t\t\t\t\tlineParts.slice(10).join(' '),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '?': // untracked\n\t\t\t\t\tfiles.push(parseStatusFile(repoPath, '??', lineParts.slice(1).join(' '), getUri));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\tsha ?? '',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusFile(\n\trepoPath: string,\n\trawStatus: string,\n\tfileName: string,\n\tgetUri: (path: string) => Uri,\n\toriginalFileName?: string,\n\tsubmodule?: { readonly oid: string; readonly previousOid?: string },\n): GitStatusFile {\n\tlet x = !rawStatus.startsWith('.') ? rawStatus[0].trim() : undefined;\n\tif (x == null || x.length === 0) {\n\t\tx = undefined;\n\t}\n\n\tlet y = undefined;\n\tif (rawStatus.length > 1) {\n\t\ty = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined;\n\t\tif (y == null || y.length === 0) {\n\t\t\ty = undefined;\n\t\t}\n\t}\n\n\treturn new GitStatusFile(repoPath, x, y, fileName, getUri(fileName), originalFileName, submodule);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"statusParser.js","names":[],"sources":["../../../../git-cli/src/parsers/statusParser.ts"],"sourcesContent":["import { GitStatus } from '@gitlens/git/models/status.js';\nimport { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport { normalizePath } from '@gitlens/utils/path.js';\nimport { maybeStopWatch } from '@gitlens/utils/stopwatch.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\n\nconst aheadStatusV1Regex = /(?:ahead ([0-9]+))/;\nconst behindStatusV1Regex = /(?:behind ([0-9]+))/;\nconst quoteRegex = /\"/g;\nconst submoduleCharCode = 'S'.charCodeAt(0);\n\n/** Returns the index just after the `n`th space, or -1 if the line has fewer than `n` spaces. */\nfunction indexAfterNthSpace(line: string, n: number): number {\n\tlet index = -1;\n\tfor (let i = 0; i < n; i++) {\n\t\tindex = line.indexOf(' ', index + 1);\n\t\tif (index === -1) return -1;\n\t}\n\n\treturn index + 1;\n}\n\n/** Without `getUri`, each file builds its uri on first read rather than during the parse */\nexport function parseGitStatus(\n\tdata: string,\n\trepoPath: string,\n\tporcelainVersion: number,\n\tgetUri?: (path: string) => Uri,\n): GitStatus | undefined {\n\tusing sw = maybeStopWatch(`Git.parseStatus(${repoPath}, v=${porcelainVersion})`, {\n\t\tlog: { onlyExit: true, level: 'debug' },\n\t});\n\tif (!data) {\n\t\tsw?.stop({ suffix: ` no data` });\n\t\treturn undefined;\n\t}\n\n\tconst lines = data.split('\\n').filter(<T>(i?: T): i is T => Boolean(i));\n\tif (lines.length === 0) {\n\t\tsw?.stop({ suffix: ` parsed no files` });\n\t\treturn undefined;\n\t}\n\n\tconst status =\n\t\tporcelainVersion < 2 ? parseStatusV1(lines, repoPath, getUri) : parseStatusV2(lines, repoPath, getUri);\n\n\tsw?.stop({ suffix: ` parsed ${status.files.length} files` });\n\n\treturn status;\n}\n\nfunction parseStatusV1(lines: string[], repoPath: string, getUri: ((path: string) => Uri) | undefined): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet upstream;\n\tlet missing = false;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Header\n\t\tif (line.startsWith('##')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\t[branch, upstream] = lineParts[1].split('...');\n\t\t\tif (lineParts.length > 2) {\n\t\t\t\tconst upstreamStatus = lineParts.slice(2).join(' ');\n\t\t\t\tif (upstreamStatus === '[gone]') {\n\t\t\t\t\tmissing = true;\n\t\t\t\t\tstate.ahead = 0;\n\t\t\t\t\tstate.behind = 0;\n\t\t\t\t} else {\n\t\t\t\t\tconst aheadStatus = aheadStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.ahead = aheadStatus == null ? 0 : Number(aheadStatus[1]) || 0;\n\n\t\t\t\t\tconst behindStatus = behindStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.behind = behindStatus == null ? 0 : Number(behindStatus[1]) || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst rawStatus = line.substring(0, 2);\n\t\t\tconst fileName = line.substring(3);\n\t\t\tif (rawStatus.startsWith('R') || rawStatus.startsWith('C')) {\n\t\t\t\tconst [file1, file2] = fileName.replace(quoteRegex, '').split(' -> ');\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, file2.trim(), getUri, file1.trim()));\n\t\t\t} else {\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, fileName, getUri));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\t'',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusV2(lines: string[], repoPath: string, getUri: ((path: string) => Uri) | undefined): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tlet sha: string | undefined;\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet missing = true;\n\tlet upstream;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Headers\n\t\tif (line.startsWith('#')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\tswitch (lineParts[1]) {\n\t\t\t\tcase 'branch.oid':\n\t\t\t\t\tsha = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.head':\n\t\t\t\t\tbranch = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.upstream':\n\t\t\t\t\tupstream = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.ab':\n\t\t\t\t\tmissing = false;\n\t\t\t\t\tstate.ahead = Number(lineParts[2].substring(1));\n\t\t\t\t\tstate.behind = Number(lineParts[3].substring(1));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tswitch (line[0]) {\n\t\t\t\tcase '1': {\n\t\t\t\t\t// normal: 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>\n\t\t\t\t\tconst pathStart = indexAfterNthSpace(line, 8);\n\t\t\t\t\tif (pathStart === -1) break;\n\n\t\t\t\t\t// <sub> starts with 'S' if submodule, 'N' if not\n\t\t\t\t\tlet submodule;\n\t\t\t\t\tif (line.charCodeAt(5) === submoduleCharCode) {\n\t\t\t\t\t\tconst parts = line.split(' ');\n\t\t\t\t\t\tsubmodule = { oid: parts[7], previousOid: parts[6] };\n\t\t\t\t\t}\n\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tline.substring(2, 4),\n\t\t\t\t\t\t\tline.substring(pathStart),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '2': {\n\t\t\t\t\t// rename: 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path>\\t<origPath>\n\t\t\t\t\tconst pathStart = indexAfterNthSpace(line, 9);\n\t\t\t\t\tif (pathStart === -1) break;\n\n\t\t\t\t\tlet submodule;\n\t\t\t\t\tif (line.charCodeAt(5) === submoduleCharCode) {\n\t\t\t\t\t\tconst parts = line.split(' ');\n\t\t\t\t\t\tsubmodule = { oid: parts[7], previousOid: parts[6] };\n\t\t\t\t\t}\n\n\t\t\t\t\tconst tab = line.indexOf('\\t', pathStart);\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tline.substring(2, 4),\n\t\t\t\t\t\t\ttab === -1 ? line.substring(pathStart) : line.substring(pathStart, tab),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\ttab === -1 ? undefined : line.substring(tab + 1),\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'u': {\n\t\t\t\t\t// unmerged: u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>\n\t\t\t\t\tconst pathStart = indexAfterNthSpace(line, 10);\n\t\t\t\t\tif (pathStart === -1) break;\n\n\t\t\t\t\tlet submodule;\n\t\t\t\t\tif (line.charCodeAt(5) === submoduleCharCode) {\n\t\t\t\t\t\tconst parts = line.split(' ');\n\t\t\t\t\t\tsubmodule = { oid: parts[9], previousOid: parts[7] };\n\t\t\t\t\t}\n\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tline.substring(2, 4),\n\t\t\t\t\t\t\tline.substring(pathStart),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '?': // untracked\n\t\t\t\t\tfiles.push(parseStatusFile(repoPath, '??', line.substring(2), getUri));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\tsha ?? '',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusFile(\n\trepoPath: string,\n\trawStatus: string,\n\tfileName: string,\n\tgetUri: ((path: string) => Uri) | undefined,\n\toriginalFileName?: string,\n\tsubmodule?: { readonly oid: string; readonly previousOid?: string },\n): GitStatusFile {\n\tlet x = !rawStatus.startsWith('.') ? rawStatus[0].trim() : undefined;\n\tif (x == null || x.length === 0) {\n\t\tx = undefined;\n\t}\n\n\tlet y = undefined;\n\tif (rawStatus.length > 1) {\n\t\ty = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined;\n\t\tif (y == null || y.length === 0) {\n\t\t\ty = undefined;\n\t\t}\n\t}\n\n\treturn new GitStatusFile(repoPath, x, y, fileName, getUri?.(fileName), originalFileName, submodule);\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,aAAa;AACnB,MAAM,oBAAoB,IAAI,WAAW,CAAC;;AAG1C,SAAS,mBAAmB,MAAc,GAAmB;CAC5D,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC3B,QAAQ,KAAK,QAAQ,KAAK,QAAQ,CAAC;EACnC,IAAI,UAAU,IAAI,OAAO;CAC1B;CAEA,OAAO,QAAQ;AAChB;;AAGA,SAAgB,eACf,MACA,UACA,kBACA,QACwB;;;EACxB,MAAM,KAAA,YAAA,EAAK,eAAe,mBAAmB,SAAS,MAAM,iBAAiB,IAAI,EAChF,KAAK;GAAE,UAAU;GAAM,OAAO;EAAQ,EACvC,CAAC,CAAA;EACD,IAAI,CAAC,MAAM;GACV,IAAI,KAAK,EAAE,QAAQ,WAAW,CAAC;GAC/B;EACD;EAEA,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAW,MAAkB,QAAQ,CAAC,CAAC;EACtE,IAAI,MAAM,WAAW,GAAG;GACvB,IAAI,KAAK,EAAE,QAAQ,mBAAmB,CAAC;GACvC;EACD;EAEA,MAAM,SACL,mBAAmB,IAAI,cAAc,OAAO,UAAU,MAAM,IAAI,cAAc,OAAO,UAAU,MAAM;EAEtG,IAAI,KAAK,EAAE,QAAQ,WAAW,OAAO,MAAM,OAAO,QAAQ,CAAC;EAE3D,OAAO;;;;;;AACR;AAEA,SAAS,cAAc,OAAiB,UAAkB,QAAwD;CACjH,IAAI;CACJ,MAAM,QAAQ,CAAC;CACf,MAAM,QAAQ;EACb,OAAO;EACP,QAAQ;CACT;CACA,IAAI;CACJ,IAAI,UAAU;CAEd,IAAI,WAAW;CACf,OAAO,EAAE,WAAW,MAAM,QAAQ;EACjC,MAAM,OAAO,MAAM;EAEnB,IAAI,KAAK,WAAW,IAAI,GAAG;GAC1B,MAAM,YAAY,KAAK,MAAM,GAAG;GAChC,CAAC,QAAQ,YAAY,UAAU,EAAE,CAAC,MAAM,KAAK;GAC7C,IAAI,UAAU,SAAS,GAAG;IACzB,MAAM,iBAAiB,UAAU,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;IAClD,IAAI,mBAAmB,UAAU;KAChC,UAAU;KACV,MAAM,QAAQ;KACd,MAAM,SAAS;IAChB,OAAO;KACN,MAAM,cAAc,mBAAmB,KAAK,cAAc;KAC1D,MAAM,QAAQ,eAAe,OAAO,IAAI,OAAO,YAAY,EAAE,KAAK;KAElE,MAAM,eAAe,oBAAoB,KAAK,cAAc;KAC5D,MAAM,SAAS,gBAAgB,OAAO,IAAI,OAAO,aAAa,EAAE,KAAK;IACtE;GACD;EACD,OAAO;GACN,MAAM,YAAY,KAAK,UAAU,GAAG,CAAC;GACrC,MAAM,WAAW,KAAK,UAAU,CAAC;GACjC,IAAI,UAAU,WAAW,GAAG,KAAK,UAAU,WAAW,GAAG,GAAG;IAC3D,MAAM,CAAC,OAAO,SAAS,SAAS,QAAQ,YAAY,EAAE,CAAC,CAAC,MAAM,MAAM;IACpE,MAAM,KAAK,gBAAgB,UAAU,WAAW,MAAM,KAAK,GAAG,QAAQ,MAAM,KAAK,CAAC,CAAC;GACpF,OACC,MAAM,KAAK,gBAAgB,UAAU,WAAW,UAAU,MAAM,CAAC;EAEnE;CACD;CAEA,OAAO,IAAI,UACV,cAAc,QAAQ,GACtB,UAAU,IACV,IACA,OACA,WAAW;EAAE,MAAM;EAAmB;EAAgB;CAAM,IAAI,KAAA,CACjE;AACD;AAEA,SAAS,cAAc,OAAiB,UAAkB,QAAwD;CACjH,IAAI;CACJ,MAAM,QAAQ,CAAC;CACf,IAAI;CACJ,MAAM,QAAQ;EACb,OAAO;EACP,QAAQ;CACT;CACA,IAAI,UAAU;CACd,IAAI;CAEJ,IAAI,WAAW;CACf,OAAO,EAAE,WAAW,MAAM,QAAQ;EACjC,MAAM,OAAO,MAAM;EAEnB,IAAI,KAAK,WAAW,GAAG,GAAG;GACzB,MAAM,YAAY,KAAK,MAAM,GAAG;GAChC,QAAQ,UAAU,IAAlB;IACC,KAAK;KACJ,MAAM,UAAU;KAChB;IACD,KAAK;KACJ,SAAS,UAAU;KACnB;IACD,KAAK;KACJ,WAAW,UAAU;KACrB;IACD,KAAK;KACJ,UAAU;KACV,MAAM,QAAQ,OAAO,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;KAC9C,MAAM,SAAS,OAAO,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;GAEjD;EACD,OACC,QAAQ,KAAK,IAAb;GACC,KAAK,KAAK;IAET,MAAM,YAAY,mBAAmB,MAAM,CAAC;IAC5C,IAAI,cAAc,IAAI;IAGtB,IAAI;IACJ,IAAI,KAAK,WAAW,CAAC,MAAM,mBAAmB;KAC7C,MAAM,QAAQ,KAAK,MAAM,GAAG;KAC5B,YAAY;MAAE,KAAK,MAAM;MAAI,aAAa,MAAM;KAAG;IACpD;IAEA,MAAM,KACL,gBACC,UACA,KAAK,UAAU,GAAG,CAAC,GACnB,KAAK,UAAU,SAAS,GACxB,QACA,KAAA,GACA,SACD,CACD;IACA;GACD;GACA,KAAK,KAAK;IAET,MAAM,YAAY,mBAAmB,MAAM,CAAC;IAC5C,IAAI,cAAc,IAAI;IAEtB,IAAI;IACJ,IAAI,KAAK,WAAW,CAAC,MAAM,mBAAmB;KAC7C,MAAM,QAAQ,KAAK,MAAM,GAAG;KAC5B,YAAY;MAAE,KAAK,MAAM;MAAI,aAAa,MAAM;KAAG;IACpD;IAEA,MAAM,MAAM,KAAK,QAAQ,KAAM,SAAS;IACxC,MAAM,KACL,gBACC,UACA,KAAK,UAAU,GAAG,CAAC,GACnB,QAAQ,KAAK,KAAK,UAAU,SAAS,IAAI,KAAK,UAAU,WAAW,GAAG,GACtE,QACA,QAAQ,KAAK,KAAA,IAAY,KAAK,UAAU,MAAM,CAAC,GAC/C,SACD,CACD;IACA;GACD;GACA,KAAK,KAAK;IAET,MAAM,YAAY,mBAAmB,MAAM,EAAE;IAC7C,IAAI,cAAc,IAAI;IAEtB,IAAI;IACJ,IAAI,KAAK,WAAW,CAAC,MAAM,mBAAmB;KAC7C,MAAM,QAAQ,KAAK,MAAM,GAAG;KAC5B,YAAY;MAAE,KAAK,MAAM;MAAI,aAAa,MAAM;KAAG;IACpD;IAEA,MAAM,KACL,gBACC,UACA,KAAK,UAAU,GAAG,CAAC,GACnB,KAAK,UAAU,SAAS,GACxB,QACA,KAAA,GACA,SACD,CACD;IACA;GACD;GACA,KAAK,KACJ,MAAM,KAAK,gBAAgB,UAAU,MAAM,KAAK,UAAU,CAAC,GAAG,MAAM,CAAC;EAEvE;CAEF;CAEA,OAAO,IAAI,UACV,cAAc,QAAQ,GACtB,UAAU,IACV,OAAO,IACP,OACA,WAAW;EAAE,MAAM;EAAmB;EAAgB;CAAM,IAAI,KAAA,CACjE;AACD;AAEA,SAAS,gBACR,UACA,WACA,UACA,QACA,kBACA,WACgB;CAChB,IAAI,IAAI,CAAC,UAAU,WAAW,GAAG,IAAI,UAAU,EAAE,CAAC,KAAK,IAAI,KAAA;CAC3D,IAAI,KAAK,QAAQ,EAAE,WAAW,GAC7B,IAAI,KAAA;CAGL,IAAI,IAAI,KAAA;CACR,IAAI,UAAU,SAAS,GAAG;EACzB,IAAI,UAAU,OAAO,MAAM,UAAU,EAAE,CAAC,KAAK,IAAI,KAAA;EACjD,IAAI,KAAK,QAAQ,EAAE,WAAW,GAC7B,IAAI,KAAA;CAEN;CAEA,OAAO,IAAI,cAAc,UAAU,GAAG,GAAG,UAAU,SAAS,QAAQ,GAAG,kBAAkB,SAAS;AACnG"}
|
|
@@ -55,6 +55,36 @@ export declare class CommitsGitSubProvider implements GitCommitsSubProvider {
|
|
|
55
55
|
authorDate: Date;
|
|
56
56
|
committerDate: Date;
|
|
57
57
|
} | undefined>;
|
|
58
|
+
getCommitPatchIds(repoPath: string, revs: readonly string[] | GitRevisionRange, options?: {
|
|
59
|
+
paths?: readonly string[];
|
|
60
|
+
verbatim?: boolean;
|
|
61
|
+
limit?: number;
|
|
62
|
+
}, cancellation?: AbortSignal): Promise<Map<string, string> | undefined>;
|
|
63
|
+
/** The commits a shallow clone was cut at, read from the repository's common git directory so linked worktrees work. */
|
|
64
|
+
private getShallowBoundary;
|
|
65
|
+
/**
|
|
66
|
+
* Lists the full shas of the commits {@link getCommitPatchIds} reads diffs of, since `diff-tree --stdin` does not
|
|
67
|
+
* accept abbreviated ones. A list of revisions that are all full shas is taken as is, and a merge commit among them
|
|
68
|
+
* is dropped later by `diff-tree`. Resolves to `undefined` when more than `options.limit` commits match, never a
|
|
69
|
+
* truncated list.
|
|
70
|
+
*/
|
|
71
|
+
private resolvePatchIdCommitShas;
|
|
72
|
+
getDiffPatchId(repoPath: string, from: string, to: string, options?: {
|
|
73
|
+
paths?: readonly string[];
|
|
74
|
+
verbatim?: boolean;
|
|
75
|
+
}, cancellation?: AbortSignal): Promise<string | undefined>;
|
|
76
|
+
/**
|
|
77
|
+
* A `patch-id` older than 2.39 hashes a binary diff's text rather than its blob ids, so only there is the binary
|
|
78
|
+
* data worth reading: it is all that tells two changes to one binary file apart. Elsewhere it is ignored, and
|
|
79
|
+
* would only bloat the diff held in memory and piped to `patch-id`.
|
|
80
|
+
*/
|
|
81
|
+
private getPatchIdDiffArgs;
|
|
82
|
+
/**
|
|
83
|
+
* Pipes `diff` through `patch-id`, answering patch id by the commit id on the diff's header line (all zeros when it
|
|
84
|
+
* has none). Throws when a commit is answered twice: `patch-id` older than 2.39 splits a patch at a binary diff and
|
|
85
|
+
* answers the rest separately, so neither id covers the whole change.
|
|
86
|
+
*/
|
|
87
|
+
private computePatchIds;
|
|
58
88
|
getCommitFiles(repoPath: string, rev: string, cancellation?: AbortSignal): Promise<GitFileChange[]>;
|
|
59
89
|
getCommitForFile(repoPath: string, pathOrUri: string | Uri, rev?: string | undefined, options?: {
|
|
60
90
|
firstIfNotFound?: boolean;
|
|
@@ -25,11 +25,52 @@ import { parseSignatureOutput, signatureFormat } from "../parsers/signatureParse
|
|
|
25
25
|
import { extractCommitterFromCommitObject, extractSshPublicKeyFromCommitObject } from "../parsers/sshSignatureParser.js";
|
|
26
26
|
import { createCommitFileset } from "./commitFilesetUtils.js";
|
|
27
27
|
import { convertStashesToStdin } from "./stash.js";
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
28
29
|
//#region ../git-cli/src/providers/commits.ts
|
|
29
30
|
const emptyPromise = Promise.resolve(void 0);
|
|
30
31
|
const reflogCommands = ["merge", "pull"];
|
|
31
32
|
/** Keeps `filterUnpublishedShas` well inside Windows' ~32K command-line cap (40 hex chars + separator each). */
|
|
32
33
|
const maxShasPerRevListSpawn = 500;
|
|
34
|
+
const fullShaRegex = /^[0-9a-f]{40}$/;
|
|
35
|
+
/** Bounds the diff text held in memory, and piped to `patch-id`, per `diff-tree` spawn. */
|
|
36
|
+
const maxCommitsPerPatchIdSpawn = 250;
|
|
37
|
+
/**
|
|
38
|
+
* Plumbing diff with every output knob pinned, so no user diff config can change what a patch id hashes.
|
|
39
|
+
* `--full-index` is load-bearing: a binary change is hashed through its blob oids, which must be unabbreviated.
|
|
40
|
+
* Plumbing still reads `diff.indentHeuristic`, which can slide a hunk, so the heuristic is named rather than defaulted.
|
|
41
|
+
*/
|
|
42
|
+
const patchIdDiffArgs = [
|
|
43
|
+
"diff-tree",
|
|
44
|
+
"-p",
|
|
45
|
+
"--full-index",
|
|
46
|
+
"--indent-heuristic",
|
|
47
|
+
"--no-color",
|
|
48
|
+
"--no-ext-diff",
|
|
49
|
+
"--no-textconv",
|
|
50
|
+
"--no-renames"
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* The other diff setting plumbing reads: a blank context line printed without its leading space is counted by
|
|
54
|
+
* `patch-id` as neither side of the hunk, so the hunk never ends and the next file's header is hashed into it.
|
|
55
|
+
*/
|
|
56
|
+
const patchIdDiffConfigs = ["-c", "diff.suppressBlankEmpty=false"];
|
|
57
|
+
/**
|
|
58
|
+
* `GIT_DIFF_OPTS` overrides even an explicit `-U<n>`, changing the context lines a patch id hashes, so it is
|
|
59
|
+
* unset. Literal pathspecs keep a caller's path from being read as a glob or pathspec magic.
|
|
60
|
+
*/
|
|
61
|
+
const patchIdEnv = {
|
|
62
|
+
GIT_DIFF_OPTS: void 0,
|
|
63
|
+
GIT_LITERAL_PATHSPECS: "1"
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The part of a patch id's cache key after its sha. Order and duplicates in `paths` never change the diff, so they
|
|
67
|
+
* must not split the key, and the paths are hashed so a long list isn't held again by every cached commit.
|
|
68
|
+
*/
|
|
69
|
+
function getPatchIdCacheKeySuffix(paths, verbatim) {
|
|
70
|
+
const mode = verbatim ? "v" : "s";
|
|
71
|
+
if (!paths?.length) return `\0${mode}`;
|
|
72
|
+
return `\0${mode}\0${createHash("sha256").update([...new Set(paths)].sort().join("\0")).digest("hex")}`;
|
|
73
|
+
}
|
|
33
74
|
const refExclusionNamespaces = [
|
|
34
75
|
[
|
|
35
76
|
"branches",
|
|
@@ -218,6 +259,159 @@ var CommitsGitSubProvider = class {
|
|
|
218
259
|
};
|
|
219
260
|
}
|
|
220
261
|
}
|
|
262
|
+
async getCommitPatchIds(repoPath, revs, options, cancellation) {
|
|
263
|
+
const scope = getScopedLogger();
|
|
264
|
+
if (options?.verbatim && !await this.git.supports("git:patch-id:verbatim")) return void 0;
|
|
265
|
+
try {
|
|
266
|
+
const shas = await this.resolvePatchIdCommitShas(repoPath, revs, options, cancellation);
|
|
267
|
+
if (shas == null) return void 0;
|
|
268
|
+
const keySuffix = getPatchIdCacheKeySuffix(options?.paths, options?.verbatim);
|
|
269
|
+
const ids = /* @__PURE__ */ new Map();
|
|
270
|
+
const uncached = [];
|
|
271
|
+
for (const sha of shas) {
|
|
272
|
+
const cached = this.cache.patchIds.get(repoPath, `${sha}${keySuffix}`);
|
|
273
|
+
if (cached == null) {
|
|
274
|
+
uncached.push(sha);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const id = await cached;
|
|
278
|
+
if (id != null) ids.set(sha, id);
|
|
279
|
+
}
|
|
280
|
+
const boundary = uncached.length ? await this.getShallowBoundary(repoPath, cancellation) : void 0;
|
|
281
|
+
const readable = boundary?.size ? uncached.filter((sha) => !boundary.has(sha)) : uncached;
|
|
282
|
+
const diffArgs = await this.getPatchIdDiffArgs();
|
|
283
|
+
for (const batch of chunk(readable, maxCommitsPerPatchIdSpawn)) {
|
|
284
|
+
const diff = await this.git.run({
|
|
285
|
+
cwd: repoPath,
|
|
286
|
+
cancellation,
|
|
287
|
+
configs: patchIdDiffConfigs,
|
|
288
|
+
errors: "throw",
|
|
289
|
+
encoding: "buffer",
|
|
290
|
+
env: patchIdEnv,
|
|
291
|
+
stdin: `${batch.join("\n")}\n`
|
|
292
|
+
}, ...diffArgs, "--root", "--stdin", ...options?.paths?.length ? ["--", ...options.paths] : []);
|
|
293
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
294
|
+
const batchIds = diff.stdout.length > 0 ? await this.computePatchIds(repoPath, diff.stdout, options?.verbatim, cancellation) : void 0;
|
|
295
|
+
if (batchIds != null) {
|
|
296
|
+
const batchShas = new Set(batch);
|
|
297
|
+
if (some(batchIds.keys(), (commitId) => !batchShas.has(commitId))) throw new Error("patch-id answered for a commit it was not given");
|
|
298
|
+
}
|
|
299
|
+
for (const sha of batch) {
|
|
300
|
+
const id = batchIds?.get(sha);
|
|
301
|
+
this.cache.patchIds.getOrCreate(repoPath, `${sha}${keySuffix}`, () => Promise.resolve(id));
|
|
302
|
+
if (id != null) ids.set(sha, id);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return ids;
|
|
306
|
+
} catch (ex) {
|
|
307
|
+
scope?.error(ex);
|
|
308
|
+
if (isCancellationError(ex)) throw ex;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** The commits a shallow clone was cut at, read from the repository's common git directory so linked worktrees work. */
|
|
313
|
+
async getShallowBoundary(repoPath, cancellation) {
|
|
314
|
+
const path = (await this.git.run({
|
|
315
|
+
cwd: repoPath,
|
|
316
|
+
cancellation,
|
|
317
|
+
errors: "throw"
|
|
318
|
+
}, "rev-parse", "--git-path", "shallow")).stdout.trim();
|
|
319
|
+
if (!path) return /* @__PURE__ */ new Set();
|
|
320
|
+
const contents = await this.context.fs.readFile(this.provider.getAbsoluteUri(path, repoPath)).catch((ex) => {
|
|
321
|
+
const code = ex.code;
|
|
322
|
+
if (code === "ENOENT" || code === "FileNotFound") return void 0;
|
|
323
|
+
throw ex;
|
|
324
|
+
});
|
|
325
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
326
|
+
return new Set(contents == null ? [] : new TextDecoder().decode(contents).split(/\s+/).filter((s) => s.length > 0));
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Lists the full shas of the commits {@link getCommitPatchIds} reads diffs of, since `diff-tree --stdin` does not
|
|
330
|
+
* accept abbreviated ones. A list of revisions that are all full shas is taken as is, and a merge commit among them
|
|
331
|
+
* is dropped later by `diff-tree`. Resolves to `undefined` when more than `options.limit` commits match, never a
|
|
332
|
+
* truncated list.
|
|
333
|
+
*/
|
|
334
|
+
async resolvePatchIdCommitShas(repoPath, revs, options, cancellation) {
|
|
335
|
+
let result;
|
|
336
|
+
if (typeof revs === "string") {
|
|
337
|
+
if (!revs.includes("..") || revs.startsWith("-")) return void 0;
|
|
338
|
+
result = await this.git.run({
|
|
339
|
+
cwd: repoPath,
|
|
340
|
+
cancellation,
|
|
341
|
+
errors: "throw",
|
|
342
|
+
env: patchIdEnv
|
|
343
|
+
}, "rev-list", "--no-merges", "--full-history", options?.limit != null ? `--max-count=${options.limit + 1}` : void 0, revs, "--", ...options?.paths ?? []);
|
|
344
|
+
} else {
|
|
345
|
+
if (!revs.length) return [];
|
|
346
|
+
if (revs.some((r) => !r || r.startsWith("-") || r.startsWith("^") || r.includes("..") || r.includes("\n"))) return;
|
|
347
|
+
if (revs.every((r) => fullShaRegex.test(r))) {
|
|
348
|
+
const shas = [...new Set(revs)];
|
|
349
|
+
return options?.limit != null && shas.length > options.limit ? void 0 : shas;
|
|
350
|
+
}
|
|
351
|
+
result = await this.git.run({
|
|
352
|
+
cwd: repoPath,
|
|
353
|
+
cancellation,
|
|
354
|
+
errors: "throw",
|
|
355
|
+
env: patchIdEnv,
|
|
356
|
+
stdin: `${revs.join("\n")}\n--\n`
|
|
357
|
+
}, "rev-list", "--no-merges", "--no-walk=unsorted", "--stdin");
|
|
358
|
+
}
|
|
359
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
360
|
+
const shas = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
361
|
+
return options?.limit != null && shas.length > options.limit ? void 0 : shas;
|
|
362
|
+
}
|
|
363
|
+
async getDiffPatchId(repoPath, from, to, options, cancellation) {
|
|
364
|
+
const scope = getScopedLogger();
|
|
365
|
+
if (!from || !to || from.startsWith("-") || to.startsWith("-")) return void 0;
|
|
366
|
+
if (options?.verbatim && !await this.git.supports("git:patch-id:verbatim")) return void 0;
|
|
367
|
+
try {
|
|
368
|
+
const diff = await this.git.run({
|
|
369
|
+
cwd: repoPath,
|
|
370
|
+
cancellation,
|
|
371
|
+
configs: patchIdDiffConfigs,
|
|
372
|
+
errors: "throw",
|
|
373
|
+
encoding: "buffer",
|
|
374
|
+
env: patchIdEnv
|
|
375
|
+
}, ...await this.getPatchIdDiffArgs(), from, to, "--", ...options?.paths ?? []);
|
|
376
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
377
|
+
if (!diff.stdout.length) return void 0;
|
|
378
|
+
return first((await this.computePatchIds(repoPath, diff.stdout, options?.verbatim, cancellation)).values());
|
|
379
|
+
} catch (ex) {
|
|
380
|
+
scope?.error(ex);
|
|
381
|
+
if (isCancellationError(ex)) throw ex;
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* A `patch-id` older than 2.39 hashes a binary diff's text rather than its blob ids, so only there is the binary
|
|
387
|
+
* data worth reading: it is all that tells two changes to one binary file apart. Elsewhere it is ignored, and
|
|
388
|
+
* would only bloat the diff held in memory and piped to `patch-id`.
|
|
389
|
+
*/
|
|
390
|
+
async getPatchIdDiffArgs() {
|
|
391
|
+
return await this.git.supports("git:patch-id:binary-oids") ? patchIdDiffArgs : [...patchIdDiffArgs, "--binary"];
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Pipes `diff` through `patch-id`, answering patch id by the commit id on the diff's header line (all zeros when it
|
|
395
|
+
* has none). Throws when a commit is answered twice: `patch-id` older than 2.39 splits a patch at a binary diff and
|
|
396
|
+
* answers the rest separately, so neither id covers the whole change.
|
|
397
|
+
*/
|
|
398
|
+
async computePatchIds(repoPath, diff, verbatim, cancellation) {
|
|
399
|
+
const result = await this.git.run({
|
|
400
|
+
cwd: repoPath,
|
|
401
|
+
cancellation,
|
|
402
|
+
errors: "throw",
|
|
403
|
+
stdin: diff
|
|
404
|
+
}, "patch-id", verbatim ? "--verbatim" : "--stable");
|
|
405
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
406
|
+
const ids = /* @__PURE__ */ new Map();
|
|
407
|
+
for (const line of result.stdout.split("\n")) {
|
|
408
|
+
const [patchId, commitId] = line.trim().split(" ");
|
|
409
|
+
if (!patchId || !commitId) continue;
|
|
410
|
+
if (ids.has(commitId)) throw new Error(`patch-id answered more than once for ${commitId}`);
|
|
411
|
+
ids.set(commitId, patchId);
|
|
412
|
+
}
|
|
413
|
+
return ids;
|
|
414
|
+
}
|
|
221
415
|
async getCommitFiles(repoPath, rev, cancellation) {
|
|
222
416
|
const parser = getShaAndFilesAndStatsLogParser();
|
|
223
417
|
const result = await this.git.run({
|
|
@@ -1028,6 +1222,15 @@ __decorate([debug({
|
|
|
1028
1222
|
exit: (r) => `${r.size} unpublished`
|
|
1029
1223
|
})], CommitsGitSubProvider.prototype, "filterUnpublishedShas", null);
|
|
1030
1224
|
__decorate([debug({ exit: true })], CommitsGitSubProvider.prototype, "getCommitDates", null);
|
|
1225
|
+
__decorate([debug({
|
|
1226
|
+
args: (repoPath, revs, options) => ({
|
|
1227
|
+
repoPath,
|
|
1228
|
+
revs: typeof revs === "string" ? revs : `${revs.length} value(s)`,
|
|
1229
|
+
options
|
|
1230
|
+
}),
|
|
1231
|
+
exit: (r) => r != null ? `${r.size} patch id(s)` : "unavailable"
|
|
1232
|
+
})], CommitsGitSubProvider.prototype, "getCommitPatchIds", null);
|
|
1233
|
+
__decorate([debug({ exit: true })], CommitsGitSubProvider.prototype, "getDiffPatchId", null);
|
|
1031
1234
|
__decorate([debug()], CommitsGitSubProvider.prototype, "getCommitFiles", null);
|
|
1032
1235
|
__decorate([debug()], CommitsGitSubProvider.prototype, "getCommitForFile", null);
|
|
1033
1236
|
__decorate([debug()], CommitsGitSubProvider.prototype, "getCommitReachability", null);
|