@shipwithai/loopnote 0.0.0-stage → 2.0.0-bootstrap.0
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/LICENSE +21 -0
- package/README.md +202 -2
- package/THIRD_PARTY_NOTICES.md +580 -0
- package/dist/bin/loopnote.mjs +5 -0
- package/dist/cli/confirm.js +38 -0
- package/dist/cli/picker.js +342 -0
- package/dist/cli/request.js +120 -0
- package/dist/cli/run.js +970 -0
- package/dist/contracts/api.js +640 -0
- package/dist/contracts/comparison.js +178 -0
- package/dist/contracts/draft.js +606 -0
- package/dist/contracts/request.js +124 -0
- package/dist/domain/anchor.js +122 -0
- package/dist/domain/comparison-key.js +51 -0
- package/dist/domain/errors.js +43 -0
- package/dist/domain/path-bytes.js +82 -0
- package/dist/domain/product-identity.js +66 -0
- package/dist/domain/source.js +4 -0
- package/dist/draft/mutate-draft.js +87 -0
- package/dist/export/render-review-markdown.js +163 -0
- package/dist/export/review-export.js +273 -0
- package/dist/git/availability.js +92 -0
- package/dist/git/candidates.js +413 -0
- package/dist/git/comparison.js +263 -0
- package/dist/git/exact-patch.js +702 -0
- package/dist/git/ignore-status.js +23 -0
- package/dist/git/inventory.js +160 -0
- package/dist/git/numstat.js +111 -0
- package/dist/git/objects.js +95 -0
- package/dist/git/raw-diff.js +84 -0
- package/dist/git/repository.js +144 -0
- package/dist/git/runner.js +145 -0
- package/dist/git/selector-drift.js +169 -0
- package/dist/git/standalone-resolution.js +174 -0
- package/dist/git/worktree-content.js +97 -0
- package/dist/git/worktree-inventory.js +391 -0
- package/dist/native/directory_exchange.node +0 -0
- package/dist/server/app.js +171 -0
- package/dist/server/attached-completion.js +85 -0
- package/dist/server/capabilities.js +1572 -0
- package/dist/server/draft-loader.js +134 -0
- package/dist/server/draft-recovery.js +5 -0
- package/dist/server/draft-store.js +380 -0
- package/dist/server/export-store.js +291 -0
- package/dist/server/gitignore-capability.js +197 -0
- package/dist/server/image-content.js +101 -0
- package/dist/server/lifecycle.js +72 -0
- package/dist/server/native-exchange-capability.js +45 -0
- package/dist/server/patch-snapshot.js +500 -0
- package/dist/server/routes.js +418 -0
- package/dist/server/security.js +123 -0
- package/dist/server/snapshot-fs.js +58 -0
- package/dist/server/support-client.js +76 -0
- package/dist/server/support-store.js +194 -0
- package/dist/server/worktree-snapshot.js +553 -0
- package/dist/web/assets/abap-08VXUWAP.js +1 -0
- package/dist/web/assets/apex-BWPQTe0t.js +1 -0
- package/dist/web/assets/azcli-Bc_sGQ0U.js +1 -0
- package/dist/web/assets/bat-i0X4ZdIN.js +1 -0
- package/dist/web/assets/bicep-B5-_aFwp.js +2 -0
- package/dist/web/assets/cameligo-DMUM7wLl.js +1 -0
- package/dist/web/assets/clojure-Cm7r79vr.js +1 -0
- package/dist/web/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/web/assets/coffee-Ba7i2nA0.js +1 -0
- package/dist/web/assets/cpp-C7h46wYY.js +1 -0
- package/dist/web/assets/csharp-BKxtCVv1.js +1 -0
- package/dist/web/assets/csp-bTuwJoIa.js +1 -0
- package/dist/web/assets/css-DIMkf-bt.js +3 -0
- package/dist/web/assets/css.worker-yKcq13f2.js +89 -0
- package/dist/web/assets/cssMode-CpBTfhMg.js +1 -0
- package/dist/web/assets/cypher-CVaqCwHa.js +1 -0
- package/dist/web/assets/dart-onAF5SnQ.js +1 -0
- package/dist/web/assets/dockerfile-DZFCIeNp.js +1 -0
- package/dist/web/assets/ecl-D05T4iGw.js +1 -0
- package/dist/web/assets/editor.worker-tytyn86m.js +26 -0
- package/dist/web/assets/elixir-6RTg0lbw.js +1 -0
- package/dist/web/assets/flow9-C5_-GSwl.js +1 -0
- package/dist/web/assets/freemarker2-skQ63wb7.js +3 -0
- package/dist/web/assets/fsharp-C8Ef5oNN.js +1 -0
- package/dist/web/assets/go-C-y9NEjX.js +1 -0
- package/dist/web/assets/graphql-fmXr3nnJ.js +1 -0
- package/dist/web/assets/handlebars-CqvPAZY8.js +1 -0
- package/dist/web/assets/hcl-CpzslTdj.js +1 -0
- package/dist/web/assets/html-BDAqstpo.js +1 -0
- package/dist/web/assets/html.worker-DSJ9zmd7.js +502 -0
- package/dist/web/assets/htmlMode-BRYA3pnT.js +1 -0
- package/dist/web/assets/index-CCeYBqPo.js +1111 -0
- package/dist/web/assets/index-CRj9ojWJ.css +1 -0
- package/dist/web/assets/ini-sBoK_t0W.js +1 -0
- package/dist/web/assets/java-BEtHBSE6.js +1 -0
- package/dist/web/assets/javascript-1Ywytw67.js +1 -0
- package/dist/web/assets/json.worker-Oa9UrkKE.js +58 -0
- package/dist/web/assets/jsonMode-AO1C_0kX.js +7 -0
- package/dist/web/assets/julia-Bri6UV-V.js +1 -0
- package/dist/web/assets/kotlin-BOotOW0E.js +1 -0
- package/dist/web/assets/less-B9JPFI3C.js +2 -0
- package/dist/web/assets/lexon-CfSJPG6W.js +1 -0
- package/dist/web/assets/liquid-CkkqyyGc.js +1 -0
- package/dist/web/assets/lspLanguageFeatures-CYOFEMZp.js +4 -0
- package/dist/web/assets/lua-CsQS60Ue.js +1 -0
- package/dist/web/assets/m3-D-oSqn_W.js +1 -0
- package/dist/web/assets/markdown-Cimd5fb3.js +1 -0
- package/dist/web/assets/mdx-Bd3Wlfqw.js +1 -0
- package/dist/web/assets/mips-CIPQ_RoX.js +1 -0
- package/dist/web/assets/msdax-DauUninz.js +1 -0
- package/dist/web/assets/mysql-SOo6toE5.js +1 -0
- package/dist/web/assets/objective-c-FvmIjYaQ.js +1 -0
- package/dist/web/assets/pascal-DrH0SRf2.js +1 -0
- package/dist/web/assets/pascaligo-D-ptJ9y-.js +1 -0
- package/dist/web/assets/perl-oz_6vUea.js +1 -0
- package/dist/web/assets/pgsql-DTj74zXo.js +1 -0
- package/dist/web/assets/php-nr791fC2.js +1 -0
- package/dist/web/assets/pla-CopQ2nXW.js +1 -0
- package/dist/web/assets/postiats-43DmfD33.js +1 -0
- package/dist/web/assets/powerquery-D3hlyOfw.js +1 -0
- package/dist/web/assets/powershell-DmHpPYUd.js +1 -0
- package/dist/web/assets/protobuf-C531GsRP.js +2 -0
- package/dist/web/assets/pug-Z5eAx3Zn.js +1 -0
- package/dist/web/assets/python-CET4IumD.js +1 -0
- package/dist/web/assets/qsharp-DkqhCAOL.js +1 -0
- package/dist/web/assets/r-BwWrilGY.js +1 -0
- package/dist/web/assets/razor-d-reaHeT.js +1 -0
- package/dist/web/assets/redis-ClamHrr6.js +1 -0
- package/dist/web/assets/redshift-DT7zqm-g.js +1 -0
- package/dist/web/assets/restructuredtext-BYgofb2h.js +1 -0
- package/dist/web/assets/ruby-DezsRK8O.js +1 -0
- package/dist/web/assets/rust-DdL9SqIa.js +1 -0
- package/dist/web/assets/sb-CcwsVR0C.js +1 -0
- package/dist/web/assets/scala-DHpiXF5c.js +1 -0
- package/dist/web/assets/scheme-BeGwcela.js +1 -0
- package/dist/web/assets/scss-gp-XZpBa.js +3 -0
- package/dist/web/assets/shell-CC2rA5mh.js +1 -0
- package/dist/web/assets/solidity-BEEn4gHE.js +1 -0
- package/dist/web/assets/sophia-CRfGWb83.js +1 -0
- package/dist/web/assets/sparql-D_Lu-MrJ.js +1 -0
- package/dist/web/assets/sql-NEE52Syq.js +1 -0
- package/dist/web/assets/st-DbInun42.js +1 -0
- package/dist/web/assets/swift-Bxkupp3x.js +1 -0
- package/dist/web/assets/systemverilog-Bz4Y3fRF.js +1 -0
- package/dist/web/assets/tcl-DISqw1ZD.js +1 -0
- package/dist/web/assets/ts.worker-BtjBFNHX.js +67734 -0
- package/dist/web/assets/tsMode-DLxSAtkD.js +11 -0
- package/dist/web/assets/twig-De2hgUGE.js +1 -0
- package/dist/web/assets/typescript-DprEVA-S.js +1 -0
- package/dist/web/assets/typespec-B8J7ngcE.js +1 -0
- package/dist/web/assets/vb-DV3o63ZY.js +1 -0
- package/dist/web/assets/wgsl-DpFanUEy.js +298 -0
- package/dist/web/assets/xml-B7ZwKOhJ.js +1 -0
- package/dist/web/assets/yaml-OQ9Asg7B.js +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +87 -4
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { inflateSync } from 'node:zlib';
|
|
4
|
+
import { lstat, readFile, readlink } from 'node:fs/promises';
|
|
5
|
+
import { createExactPath } from '../domain/path-bytes.js';
|
|
6
|
+
import { RENAME_MAP } from '../domain/product-identity.js';
|
|
7
|
+
import { MAX_INLINE_TEXT_BYTES } from './availability.js';
|
|
8
|
+
import { createObjectReader } from './objects.js';
|
|
9
|
+
import { discoverGitRepository } from './repository.js';
|
|
10
|
+
import { createGitRunner } from './runner.js';
|
|
11
|
+
const newStateDirSegment = RENAME_MAP.repoStateDir.replace(/\/$/, '');
|
|
12
|
+
const modePattern = /^[0-7]{6}$/;
|
|
13
|
+
const oidPattern = /^[0-9a-f]+$/;
|
|
14
|
+
const MAX_PATCH_LINES = 100_000;
|
|
15
|
+
const MAX_PATCH_ENTRIES = 10_000;
|
|
16
|
+
const MAX_PATCH_HUNKS = 10_000;
|
|
17
|
+
const MAX_GROUNDED_OBJECT_BYTES = 2 * MAX_INLINE_TEXT_BYTES;
|
|
18
|
+
const MAX_GROUNDED_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
19
|
+
const MAX_REPOSITORY_TREE_BYTES = 16 * 1024 * 1024;
|
|
20
|
+
const BASE85_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~';
|
|
21
|
+
export class ExactPatchGroundingError extends Error {
|
|
22
|
+
constructor(message = 'Invalid exact patch.') {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'ExactPatchGroundingError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function fail(message) {
|
|
28
|
+
throw new ExactPatchGroundingError(message);
|
|
29
|
+
}
|
|
30
|
+
function validUnicode(value) {
|
|
31
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
32
|
+
const codeUnit = value.charCodeAt(index);
|
|
33
|
+
if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
|
|
34
|
+
const next = value.charCodeAt(index + 1);
|
|
35
|
+
if (next < 0xdc00 || next > 0xdfff)
|
|
36
|
+
return false;
|
|
37
|
+
index += 1;
|
|
38
|
+
}
|
|
39
|
+
else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function decodeQuotedPath(value) {
|
|
46
|
+
if (!value.startsWith('"') || !value.endsWith('"'))
|
|
47
|
+
return Buffer.from(value, 'utf8');
|
|
48
|
+
const bytes = [];
|
|
49
|
+
for (let index = 1; index < value.length - 1; index += 1) {
|
|
50
|
+
const character = value[index];
|
|
51
|
+
if (character !== '\\') {
|
|
52
|
+
bytes.push(...Buffer.from(character, 'utf8'));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const escape = value[++index];
|
|
56
|
+
if (escape === undefined)
|
|
57
|
+
fail();
|
|
58
|
+
if (/^[0-7]$/u.test(escape)) {
|
|
59
|
+
const octal = `${escape}${value[++index] ?? ''}${value[++index] ?? ''}`;
|
|
60
|
+
if (!/^[0-7]{3}$/u.test(octal))
|
|
61
|
+
fail();
|
|
62
|
+
bytes.push(Number.parseInt(octal, 8));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const escaped = { a: 7, b: 8, f: 12, n: 10, r: 13, t: 9, v: 11, '\\': 92, '"': 34 };
|
|
66
|
+
if (escaped[escape] === undefined)
|
|
67
|
+
fail();
|
|
68
|
+
bytes.push(escaped[escape]);
|
|
69
|
+
}
|
|
70
|
+
return Buffer.from(bytes);
|
|
71
|
+
}
|
|
72
|
+
function exactPath(value, prefix) {
|
|
73
|
+
if (value === '/dev/null')
|
|
74
|
+
return undefined;
|
|
75
|
+
const bytes = decodeQuotedPath(value);
|
|
76
|
+
const path = prefix !== undefined && bytes.subarray(0, 2).equals(Buffer.from(prefix, 'ascii')) ? bytes.subarray(2) : bytes;
|
|
77
|
+
if (path.length === 0 || path[0] === 47 || path[0] === 92 || path.includes(0))
|
|
78
|
+
fail();
|
|
79
|
+
for (const part of path.toString('latin1').split('/')) {
|
|
80
|
+
if (part === '' || part === '.' || part === '..' || part === '.git' || part === '.cumpa' || part === newStateDirSegment)
|
|
81
|
+
fail();
|
|
82
|
+
}
|
|
83
|
+
return Object.freeze({ bytes: Buffer.from(path), path: createExactPath(path) });
|
|
84
|
+
}
|
|
85
|
+
function splitGitTokens(value) {
|
|
86
|
+
const result = [];
|
|
87
|
+
let token = '';
|
|
88
|
+
let quoted = false;
|
|
89
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
90
|
+
const character = value[index];
|
|
91
|
+
if (character === '"')
|
|
92
|
+
quoted = !quoted;
|
|
93
|
+
if (character === ' ' && !quoted) {
|
|
94
|
+
if (token.length > 0)
|
|
95
|
+
result.push(token);
|
|
96
|
+
token = '';
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
token += character;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (quoted || token.length === 0)
|
|
103
|
+
fail();
|
|
104
|
+
result.push(token);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function parsePatch(content, oidLength) {
|
|
108
|
+
let lineCount = 1;
|
|
109
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
110
|
+
if (content.charCodeAt(index) === 10 && ++lineCount > MAX_PATCH_LINES)
|
|
111
|
+
fail();
|
|
112
|
+
}
|
|
113
|
+
const lines = content.split('\n').map((line) => line.endsWith('\r') ? line.slice(0, -1) : line);
|
|
114
|
+
const records = [];
|
|
115
|
+
let current;
|
|
116
|
+
let body;
|
|
117
|
+
let activePayload;
|
|
118
|
+
const finish = () => {
|
|
119
|
+
if (current === undefined || current.oldOid === undefined || current.newOid === undefined || records.length >= MAX_PATCH_ENTRIES)
|
|
120
|
+
fail();
|
|
121
|
+
let binary;
|
|
122
|
+
if (current.binary !== undefined) {
|
|
123
|
+
if (current.binary.payloads.length !== 2 || current.binary.payloads.some((payload) => payload.lines.length === 0))
|
|
124
|
+
fail();
|
|
125
|
+
const [forward, reverse] = current.binary.payloads;
|
|
126
|
+
binary = Object.freeze({
|
|
127
|
+
forward: Object.freeze({ ...forward, lines: Object.freeze([...forward.lines]) }),
|
|
128
|
+
reverse: Object.freeze({ ...reverse, lines: Object.freeze([...reverse.lines]) }),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
records.push(Object.freeze({
|
|
132
|
+
...current,
|
|
133
|
+
oldOid: current.oldOid,
|
|
134
|
+
newOid: current.newOid,
|
|
135
|
+
text: current.text === undefined ? undefined : Object.freeze([...current.text]),
|
|
136
|
+
binary,
|
|
137
|
+
}));
|
|
138
|
+
current = undefined;
|
|
139
|
+
body = undefined;
|
|
140
|
+
activePayload = undefined;
|
|
141
|
+
};
|
|
142
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
143
|
+
const line = lines[index];
|
|
144
|
+
if (line.startsWith('diff --combined ') || line.startsWith('diff --cc '))
|
|
145
|
+
fail();
|
|
146
|
+
if (line.startsWith('diff --git ')) {
|
|
147
|
+
if (current !== undefined)
|
|
148
|
+
finish();
|
|
149
|
+
const tokens = splitGitTokens(line.slice('diff --git '.length));
|
|
150
|
+
if (tokens.length !== 2)
|
|
151
|
+
fail();
|
|
152
|
+
current = { oldPath: exactPath(tokens[0], 'a/'), newPath: exactPath(tokens[1], 'b/'), oldMode: '100644', newMode: '100644', status: 'M', similarity: null };
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (current === undefined) {
|
|
156
|
+
if (line.length !== 0)
|
|
157
|
+
fail();
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (body === 'text') {
|
|
161
|
+
current.text.push(line);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (body === 'binary') {
|
|
165
|
+
const header = /^(literal|delta) (0|[1-9][0-9]*)$/u.exec(line);
|
|
166
|
+
if (header !== null) {
|
|
167
|
+
const declaredSize = Number(header[2]);
|
|
168
|
+
if (activePayload !== undefined || current.binary.payloads.length >= 2 || !Number.isSafeInteger(declaredSize) || declaredSize > MAX_GROUNDED_OBJECT_BYTES)
|
|
169
|
+
fail();
|
|
170
|
+
activePayload = { kind: header[1] === 'literal' ? 'literal' : 'delta', declaredSize, lines: [] };
|
|
171
|
+
current.binary.payloads.push(activePayload);
|
|
172
|
+
}
|
|
173
|
+
else if (line.length === 0) {
|
|
174
|
+
if (activePayload !== undefined && activePayload.lines.length > 0)
|
|
175
|
+
activePayload = undefined;
|
|
176
|
+
else if (!(current.binary.payloads.length === 2 && index === lines.length - 1))
|
|
177
|
+
fail();
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
if (activePayload === undefined || activePayload.lines.length >= MAX_PATCH_LINES)
|
|
181
|
+
fail();
|
|
182
|
+
activePayload.lines.push(line);
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (line.startsWith('index ')) {
|
|
187
|
+
const match = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: ([0-7]{6}))?$/u.exec(line);
|
|
188
|
+
if (current.oldOid !== undefined || current.newOid !== undefined || match === null || match[1].length !== oidLength || match[2].length !== oidLength || !oidPattern.test(match[1]) || !oidPattern.test(match[2]))
|
|
189
|
+
fail();
|
|
190
|
+
current.oldOid = match[1];
|
|
191
|
+
current.newOid = match[2];
|
|
192
|
+
if (match[3] !== undefined)
|
|
193
|
+
current.oldMode = current.newMode = match[3];
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (line.startsWith('new file mode ')) {
|
|
197
|
+
current.oldMode = '000000';
|
|
198
|
+
current.newMode = line.slice(14);
|
|
199
|
+
current.status = 'A';
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (line.startsWith('deleted file mode ')) {
|
|
203
|
+
current.oldMode = line.slice(18);
|
|
204
|
+
current.newMode = '000000';
|
|
205
|
+
current.status = 'D';
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (line.startsWith('old mode ')) {
|
|
209
|
+
current.oldMode = line.slice(9);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (line.startsWith('new mode ')) {
|
|
213
|
+
current.newMode = line.slice(9);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (line.startsWith('similarity index ')) {
|
|
217
|
+
const number = Number.parseInt(line.slice(17, -1), 10);
|
|
218
|
+
if (!/^[0-9]{1,3}%$/u.test(line.slice(17)) || number > 100)
|
|
219
|
+
fail();
|
|
220
|
+
current.similarity = number;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (line.startsWith('rename from ')) {
|
|
224
|
+
current.oldPath = exactPath(line.slice(12), undefined);
|
|
225
|
+
current.status = 'R';
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (line.startsWith('rename to ')) {
|
|
229
|
+
current.newPath = exactPath(line.slice(10), undefined);
|
|
230
|
+
current.status = 'R';
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (line.startsWith('copy from ')) {
|
|
234
|
+
current.oldPath = exactPath(line.slice(10), undefined);
|
|
235
|
+
current.status = 'C';
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (line.startsWith('copy to ')) {
|
|
239
|
+
current.newPath = exactPath(line.slice(8), undefined);
|
|
240
|
+
current.status = 'C';
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (line.startsWith('--- ')) {
|
|
244
|
+
current.oldPath = exactPath(line.slice(4), 'a/');
|
|
245
|
+
const next = lines[++index];
|
|
246
|
+
if (next === undefined || !next.startsWith('+++ '))
|
|
247
|
+
fail();
|
|
248
|
+
current.newPath = exactPath(next.slice(4), 'b/');
|
|
249
|
+
current.text = [];
|
|
250
|
+
body = 'text';
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (line === 'GIT binary patch') {
|
|
254
|
+
current.binary = { payloads: [] };
|
|
255
|
+
body = 'binary';
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (line.length !== 0)
|
|
259
|
+
fail();
|
|
260
|
+
}
|
|
261
|
+
if (current !== undefined)
|
|
262
|
+
finish();
|
|
263
|
+
if (records.length === 0)
|
|
264
|
+
fail();
|
|
265
|
+
return Object.freeze(records);
|
|
266
|
+
}
|
|
267
|
+
function decodeBinaryPayload(payload) {
|
|
268
|
+
const chunks = [];
|
|
269
|
+
let compressedLength = 0;
|
|
270
|
+
for (const line of payload.lines) {
|
|
271
|
+
const lengthCode = line.charCodeAt(0);
|
|
272
|
+
const length = lengthCode >= 65 && lengthCode <= 90 ? lengthCode - 64 : lengthCode >= 97 && lengthCode <= 122 ? lengthCode - 70 : -1;
|
|
273
|
+
const encodedLength = 1 + (Math.ceil(length / 4) * 5);
|
|
274
|
+
if (length < 1 || length > 52 || line.length !== encodedLength || compressedLength + length > MAX_INLINE_TEXT_BYTES)
|
|
275
|
+
fail();
|
|
276
|
+
const block = Buffer.allocUnsafe(Math.ceil(length / 4) * 4);
|
|
277
|
+
let blockOffset = 0;
|
|
278
|
+
for (let index = 1; index < line.length; index += 5) {
|
|
279
|
+
let value = 0;
|
|
280
|
+
for (const character of line.slice(index, index + 5)) {
|
|
281
|
+
const digit = BASE85_ALPHABET.indexOf(character);
|
|
282
|
+
if (digit < 0)
|
|
283
|
+
fail();
|
|
284
|
+
value = (value * 85) + digit;
|
|
285
|
+
}
|
|
286
|
+
if (value > 0xffffffff)
|
|
287
|
+
fail();
|
|
288
|
+
block.writeUInt32BE(value, blockOffset);
|
|
289
|
+
blockOffset += 4;
|
|
290
|
+
}
|
|
291
|
+
chunks.push(block.subarray(0, length));
|
|
292
|
+
compressedLength += length;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const compressed = Buffer.concat(chunks, compressedLength);
|
|
296
|
+
const inflated = inflateSync(compressed, {
|
|
297
|
+
info: true,
|
|
298
|
+
maxOutputLength: MAX_GROUNDED_OBJECT_BYTES,
|
|
299
|
+
});
|
|
300
|
+
if (typeof inflated !== 'object' ||
|
|
301
|
+
inflated === null ||
|
|
302
|
+
!('buffer' in inflated) ||
|
|
303
|
+
!Buffer.isBuffer(inflated.buffer) ||
|
|
304
|
+
!('engine' in inflated) ||
|
|
305
|
+
typeof inflated.engine !== 'object' ||
|
|
306
|
+
inflated.engine === null ||
|
|
307
|
+
!('bytesWritten' in inflated.engine) ||
|
|
308
|
+
inflated.engine.bytesWritten !== compressed.length)
|
|
309
|
+
fail();
|
|
310
|
+
const decoded = Buffer.from(inflated.buffer);
|
|
311
|
+
if (decoded.length !== payload.declaredSize)
|
|
312
|
+
fail();
|
|
313
|
+
return decoded;
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (error instanceof ExactPatchGroundingError)
|
|
317
|
+
throw error;
|
|
318
|
+
return fail();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function readVarint(bytes, offset) {
|
|
322
|
+
let value = 0;
|
|
323
|
+
let shift = 0;
|
|
324
|
+
while (true) {
|
|
325
|
+
const byte = bytes[offset.value++];
|
|
326
|
+
if (byte === undefined || shift > 28)
|
|
327
|
+
fail();
|
|
328
|
+
value += (byte & 127) * (2 ** shift);
|
|
329
|
+
if (!Number.isSafeInteger(value) || value > MAX_GROUNDED_OBJECT_BYTES)
|
|
330
|
+
fail();
|
|
331
|
+
if ((byte & 128) === 0)
|
|
332
|
+
return value;
|
|
333
|
+
shift += 7;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function applyDelta(base, delta) {
|
|
337
|
+
const offset = { value: 0 };
|
|
338
|
+
if (readVarint(delta, offset) !== base.length)
|
|
339
|
+
fail();
|
|
340
|
+
const resultSize = readVarint(delta, offset);
|
|
341
|
+
if (resultSize > MAX_GROUNDED_OBJECT_BYTES)
|
|
342
|
+
fail();
|
|
343
|
+
const result = [];
|
|
344
|
+
let length = 0;
|
|
345
|
+
while (offset.value < delta.length) {
|
|
346
|
+
const instruction = delta[offset.value++];
|
|
347
|
+
if ((instruction & 128) === 0) {
|
|
348
|
+
if (instruction === 0 || offset.value + instruction > delta.length)
|
|
349
|
+
fail();
|
|
350
|
+
if (length > resultSize - instruction)
|
|
351
|
+
fail();
|
|
352
|
+
result.push(delta.subarray(offset.value, offset.value + instruction));
|
|
353
|
+
offset.value += instruction;
|
|
354
|
+
length += instruction;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
let source = 0;
|
|
358
|
+
let count = 0;
|
|
359
|
+
for (let bit = 0; bit < 4; bit += 1)
|
|
360
|
+
if ((instruction & (1 << bit)) !== 0)
|
|
361
|
+
source |= (delta[offset.value++] ?? fail()) << (bit * 8);
|
|
362
|
+
for (let bit = 0; bit < 3; bit += 1)
|
|
363
|
+
if ((instruction & (1 << (bit + 4))) !== 0)
|
|
364
|
+
count |= (delta[offset.value++] ?? fail()) << (bit * 8);
|
|
365
|
+
if (count === 0)
|
|
366
|
+
count = 0x10000;
|
|
367
|
+
if (source + count > base.length || length > resultSize - count)
|
|
368
|
+
fail();
|
|
369
|
+
result.push(base.subarray(source, source + count));
|
|
370
|
+
length += count;
|
|
371
|
+
}
|
|
372
|
+
if (length !== resultSize)
|
|
373
|
+
fail();
|
|
374
|
+
return Buffer.concat(result, resultSize);
|
|
375
|
+
}
|
|
376
|
+
function applyText(preimage, lines) {
|
|
377
|
+
let source;
|
|
378
|
+
try {
|
|
379
|
+
source = new TextDecoder('utf-8', { fatal: true }).decode(preimage);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return fail();
|
|
383
|
+
}
|
|
384
|
+
let originalLineCount = preimage.length > 0 && preimage[preimage.length - 1] !== 10 ? 1 : 0;
|
|
385
|
+
for (const byte of preimage) {
|
|
386
|
+
if (byte === 10 && ++originalLineCount > MAX_PATCH_LINES)
|
|
387
|
+
fail();
|
|
388
|
+
}
|
|
389
|
+
const original = source.match(/.*(?:\n|$)/gu)?.filter((line) => line.length > 0) ?? [];
|
|
390
|
+
if (original.length !== originalLineCount)
|
|
391
|
+
fail();
|
|
392
|
+
const output = [];
|
|
393
|
+
let outputBytes = 0;
|
|
394
|
+
const append = (value) => {
|
|
395
|
+
outputBytes += Buffer.byteLength(value, 'utf8');
|
|
396
|
+
if (outputBytes > MAX_GROUNDED_OBJECT_BYTES)
|
|
397
|
+
fail();
|
|
398
|
+
output.push(value);
|
|
399
|
+
};
|
|
400
|
+
let cursor = 0;
|
|
401
|
+
let hunkCount = 0;
|
|
402
|
+
let hunk;
|
|
403
|
+
const completeHunk = () => hunk !== undefined && hunk.oldConsumed === hunk.oldCount && hunk.newConsumed === hunk.newCount;
|
|
404
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
405
|
+
const line = lines[index];
|
|
406
|
+
const header = /^@@ -(0|[1-9][0-9]*)(?:,(0|[1-9][0-9]*))? \+(0|[1-9][0-9]*)(?:,(0|[1-9][0-9]*))? @@(?: .*)?$/u.exec(line);
|
|
407
|
+
if (header !== null) {
|
|
408
|
+
if (hunk !== undefined && !completeHunk())
|
|
409
|
+
fail();
|
|
410
|
+
if (++hunkCount > MAX_PATCH_HUNKS)
|
|
411
|
+
fail();
|
|
412
|
+
const oldStart = Number(header[1]);
|
|
413
|
+
const oldCount = Number(header[2] ?? 1);
|
|
414
|
+
const newStart = Number(header[3]);
|
|
415
|
+
const newCount = Number(header[4] ?? 1);
|
|
416
|
+
if (![oldStart, oldCount, newStart, newCount].every((value) => Number.isSafeInteger(value) && value <= MAX_PATCH_LINES))
|
|
417
|
+
fail();
|
|
418
|
+
if ((oldCount > 0 && oldStart === 0) || (newCount > 0 && newStart === 0))
|
|
419
|
+
fail();
|
|
420
|
+
const oldIndex = oldCount === 0 ? oldStart : oldStart - 1;
|
|
421
|
+
const newIndex = newCount === 0 ? newStart : newStart - 1;
|
|
422
|
+
if (oldIndex < cursor || oldIndex > original.length)
|
|
423
|
+
fail();
|
|
424
|
+
for (const value of original.slice(cursor, oldIndex))
|
|
425
|
+
append(value);
|
|
426
|
+
cursor = oldIndex;
|
|
427
|
+
if (newIndex !== output.length)
|
|
428
|
+
fail();
|
|
429
|
+
hunk = { oldCount, newCount, oldConsumed: 0, newConsumed: 0 };
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (line.length === 0 && index === lines.length - 1 && completeHunk())
|
|
433
|
+
continue;
|
|
434
|
+
if (hunk === undefined || completeHunk() || line === '\')
|
|
435
|
+
fail();
|
|
436
|
+
const prefix = line[0];
|
|
437
|
+
if (prefix !== ' ' && prefix !== '+' && prefix !== '-')
|
|
438
|
+
fail();
|
|
439
|
+
const noNewline = lines[index + 1] === '\';
|
|
440
|
+
if (noNewline)
|
|
441
|
+
index += 1;
|
|
442
|
+
const value = `${line.slice(1)}${noNewline ? '' : '\n'}`;
|
|
443
|
+
if (prefix === ' ' || prefix === '-') {
|
|
444
|
+
if (hunk.oldConsumed >= hunk.oldCount || original[cursor] !== value)
|
|
445
|
+
fail();
|
|
446
|
+
hunk.oldConsumed += 1;
|
|
447
|
+
cursor += 1;
|
|
448
|
+
}
|
|
449
|
+
if (prefix === ' ' || prefix === '+') {
|
|
450
|
+
if (hunk.newConsumed >= hunk.newCount)
|
|
451
|
+
fail();
|
|
452
|
+
hunk.newConsumed += 1;
|
|
453
|
+
append(value);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (hunkCount === 0 || !completeHunk())
|
|
457
|
+
fail();
|
|
458
|
+
for (const value of original.slice(cursor))
|
|
459
|
+
append(value);
|
|
460
|
+
return Buffer.from(output.join(''), 'utf8');
|
|
461
|
+
}
|
|
462
|
+
export function blobOid(bytes, format) {
|
|
463
|
+
return createHash(format).update(`blob ${bytes.length}\0`, 'ascii').update(bytes).digest('hex');
|
|
464
|
+
}
|
|
465
|
+
function availability(oldMode, newMode, oldBytes, newBytes) {
|
|
466
|
+
if (oldMode === '160000' || newMode === '160000')
|
|
467
|
+
return Object.freeze({ kind: 'unsupported', reason: 'submodule' });
|
|
468
|
+
if (oldMode === '120000' || newMode === '120000')
|
|
469
|
+
return Object.freeze({ kind: 'unsupported', reason: 'symlink' });
|
|
470
|
+
const modes = [oldMode, newMode].filter((mode) => mode !== '000000');
|
|
471
|
+
const bytes = [oldBytes, newBytes].filter((value) => value !== undefined);
|
|
472
|
+
if (modes.length === 0 || modes.some((mode) => !/^100(?:644|755)$/u.test(mode)) || bytes.length !== modes.length)
|
|
473
|
+
return Object.freeze({ kind: 'unsupported', reason: 'mode-or-type' });
|
|
474
|
+
if (bytes.some((value) => value.length > MAX_INLINE_TEXT_BYTES))
|
|
475
|
+
return Object.freeze({ kind: 'unsupported', reason: 'oversized' });
|
|
476
|
+
if (bytes.some((value) => value.includes(0)))
|
|
477
|
+
return Object.freeze({ kind: 'unsupported', reason: 'binary' });
|
|
478
|
+
try {
|
|
479
|
+
for (const value of bytes)
|
|
480
|
+
new TextDecoder('utf-8', { fatal: true }).decode(value);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
return Object.freeze({ kind: 'unsupported', reason: 'non-utf8' });
|
|
484
|
+
}
|
|
485
|
+
return Object.freeze({ kind: 'text' });
|
|
486
|
+
}
|
|
487
|
+
async function repositoryTree(root, runner, signal) {
|
|
488
|
+
const result = await runner.run(['ls-tree', '-r', '-z', '--full-tree', 'HEAD'], { cwd: root, maxStdoutBytes: MAX_REPOSITORY_TREE_BYTES, signal });
|
|
489
|
+
const tree = new Map();
|
|
490
|
+
for (const record of result.stdout.toString('latin1').split('\0').filter(Boolean)) {
|
|
491
|
+
const tab = record.indexOf('\t');
|
|
492
|
+
const header = record.slice(0, tab);
|
|
493
|
+
const path = Buffer.from(record.slice(tab + 1), 'latin1');
|
|
494
|
+
const fields = header.split(' ');
|
|
495
|
+
if (tab < 0 || fields.length !== 3 || !modePattern.test(fields[0]) || !oidPattern.test(fields[2]))
|
|
496
|
+
fail();
|
|
497
|
+
if (tree.size >= MAX_PATCH_LINES)
|
|
498
|
+
fail();
|
|
499
|
+
tree.set(path.toString('base64url'), Object.freeze({ mode: fields[0], type: fields[1], oid: fields[2] }));
|
|
500
|
+
}
|
|
501
|
+
return tree;
|
|
502
|
+
}
|
|
503
|
+
async function worktreeEntry(root, path, expectedMode, runner, signal) {
|
|
504
|
+
try {
|
|
505
|
+
let current = Buffer.from(root, 'utf8');
|
|
506
|
+
const parts = path.bytes.toString('latin1').split('/');
|
|
507
|
+
for (const [index, part] of parts.entries()) {
|
|
508
|
+
current = Buffer.concat([current, Buffer.from('/', 'ascii'), Buffer.from(part, 'latin1')]);
|
|
509
|
+
const entry = await lstat(current);
|
|
510
|
+
if (index < parts.length - 1 && entry.isSymbolicLink())
|
|
511
|
+
fail();
|
|
512
|
+
if (index === parts.length - 1) {
|
|
513
|
+
if (entry.isSymbolicLink()) {
|
|
514
|
+
const bytes = await readlink(current, { encoding: 'buffer' });
|
|
515
|
+
if (bytes.length > MAX_GROUNDED_OBJECT_BYTES)
|
|
516
|
+
fail();
|
|
517
|
+
return Object.freeze({ mode: '120000', type: 'blob', bytes });
|
|
518
|
+
}
|
|
519
|
+
if (entry.isFile()) {
|
|
520
|
+
if (entry.size > MAX_GROUNDED_OBJECT_BYTES)
|
|
521
|
+
fail();
|
|
522
|
+
const bytes = await readFile(current);
|
|
523
|
+
if (bytes.length > MAX_GROUNDED_OBJECT_BYTES)
|
|
524
|
+
fail();
|
|
525
|
+
return Object.freeze({ mode: (entry.mode & 0o111) === 0 ? '100644' : '100755', type: 'blob', bytes });
|
|
526
|
+
}
|
|
527
|
+
if (entry.isDirectory() && expectedMode === '160000' && path.path.utf8 !== undefined) {
|
|
528
|
+
const result = await runner.run(['rev-parse', '--verify', 'HEAD^{commit}'], {
|
|
529
|
+
cwd: current.toString('utf8'),
|
|
530
|
+
maxStdoutBytes: 65,
|
|
531
|
+
signal,
|
|
532
|
+
});
|
|
533
|
+
return Object.freeze({ mode: '160000', type: 'commit', oid: result.stdout.toString('ascii').trim() });
|
|
534
|
+
}
|
|
535
|
+
fail();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT')
|
|
542
|
+
return undefined;
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
export async function createGroundedExactPatch(options) {
|
|
547
|
+
try {
|
|
548
|
+
const submittedByteLength = Buffer.byteLength(options.patchContent, 'utf8');
|
|
549
|
+
if (options.patchContent.length === 0 || options.patchContent.includes('\0') || !validUnicode(options.patchContent) || submittedByteLength > MAX_INLINE_TEXT_BYTES)
|
|
550
|
+
fail();
|
|
551
|
+
const runner = createGitRunner();
|
|
552
|
+
const repository = await discoverGitRepository(options.cwd, runner, options.signal);
|
|
553
|
+
const formatResult = await runner.run(['rev-parse', '--show-object-format'], { cwd: repository.root, maxStdoutBytes: 16, signal: options.signal });
|
|
554
|
+
const objectFormat = formatResult.stdout.toString('ascii').trim();
|
|
555
|
+
if (objectFormat !== 'sha1' && objectFormat !== 'sha256')
|
|
556
|
+
fail();
|
|
557
|
+
const oidLength = objectFormat === 'sha1' ? 40 : 64;
|
|
558
|
+
const records = parsePatch(options.patchContent, oidLength);
|
|
559
|
+
const objectReader = createObjectReader(repository.root, { runner });
|
|
560
|
+
const tree = options.target.kind === 'repository' ? await repositoryTree(repository.root, runner, options.signal) : undefined;
|
|
561
|
+
const seen = new Set();
|
|
562
|
+
const files = [];
|
|
563
|
+
const contents = new Map();
|
|
564
|
+
let groundedByteLength = 0;
|
|
565
|
+
const readTarget = async (path, expectedMode) => {
|
|
566
|
+
if (tree === undefined)
|
|
567
|
+
return worktreeEntry(repository.root, path, expectedMode, runner, options.signal);
|
|
568
|
+
const entry = tree.get(path.bytes.toString('base64url'));
|
|
569
|
+
if (entry === undefined)
|
|
570
|
+
return undefined;
|
|
571
|
+
if (entry.type !== 'blob')
|
|
572
|
+
return Object.freeze({ mode: entry.mode, type: entry.type, oid: entry.oid });
|
|
573
|
+
const metadata = await objectReader.inspect(entry.oid, options.signal);
|
|
574
|
+
if (metadata.kind !== 'available' || metadata.objectType !== 'blob' || metadata.size > MAX_GROUNDED_OBJECT_BYTES)
|
|
575
|
+
fail();
|
|
576
|
+
const object = await objectReader.read(entry.oid, { maxBytes: MAX_GROUNDED_OBJECT_BYTES, signal: options.signal });
|
|
577
|
+
if (object.kind !== 'available' || object.bytes.length !== metadata.size)
|
|
578
|
+
fail();
|
|
579
|
+
return Object.freeze({ mode: entry.mode, type: entry.type, oid: entry.oid, bytes: Buffer.from(object.bytes) });
|
|
580
|
+
};
|
|
581
|
+
for (const [index, record] of records.entries()) {
|
|
582
|
+
const absentOid = '0'.repeat(oidLength);
|
|
583
|
+
if (!modePattern.test(record.oldMode) || !modePattern.test(record.newMode))
|
|
584
|
+
fail();
|
|
585
|
+
const oldAbsent = record.oldOid === absentOid;
|
|
586
|
+
const newAbsent = record.newOid === absentOid;
|
|
587
|
+
if (oldAbsent !== (record.oldMode === '000000') ||
|
|
588
|
+
newAbsent !== (record.newMode === '000000') ||
|
|
589
|
+
oldAbsent !== (record.oldPath === undefined) ||
|
|
590
|
+
newAbsent !== (record.newPath === undefined) ||
|
|
591
|
+
(oldAbsent && newAbsent))
|
|
592
|
+
fail();
|
|
593
|
+
const identity = `${record.oldPath?.path.bytesBase64url ?? ''}\0${record.newPath?.path.bytesBase64url ?? ''}`;
|
|
594
|
+
if (seen.has(identity))
|
|
595
|
+
fail();
|
|
596
|
+
seen.add(identity);
|
|
597
|
+
let oldBytes;
|
|
598
|
+
let patchPreimage = Buffer.alloc(0);
|
|
599
|
+
if (!oldAbsent) {
|
|
600
|
+
const metadata = await objectReader.inspect(record.oldOid, options.signal);
|
|
601
|
+
if (metadata.kind !== 'available')
|
|
602
|
+
fail();
|
|
603
|
+
if (record.oldMode === '160000') {
|
|
604
|
+
if (metadata.objectType !== 'commit')
|
|
605
|
+
fail();
|
|
606
|
+
patchPreimage = Buffer.from(`Subproject commit ${record.oldOid}\n`, 'ascii');
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
if (metadata.objectType !== 'blob' || metadata.size > MAX_GROUNDED_OBJECT_BYTES || groundedByteLength > MAX_GROUNDED_TOTAL_BYTES - metadata.size)
|
|
610
|
+
fail();
|
|
611
|
+
const preimage = await objectReader.read(record.oldOid, { maxBytes: MAX_GROUNDED_OBJECT_BYTES, signal: options.signal });
|
|
612
|
+
if (preimage.kind !== 'available' || preimage.bytes.length !== metadata.size)
|
|
613
|
+
fail();
|
|
614
|
+
oldBytes = Buffer.from(preimage.bytes);
|
|
615
|
+
patchPreimage = oldBytes;
|
|
616
|
+
groundedByteLength += oldBytes.length;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
let produced;
|
|
620
|
+
if (record.text !== undefined) {
|
|
621
|
+
produced = applyText(patchPreimage, record.text);
|
|
622
|
+
}
|
|
623
|
+
else if (record.binary !== undefined) {
|
|
624
|
+
const forward = decodeBinaryPayload(record.binary.forward);
|
|
625
|
+
produced = record.binary.forward.kind === 'delta' ? applyDelta(patchPreimage, forward) : forward;
|
|
626
|
+
const reverse = decodeBinaryPayload(record.binary.reverse);
|
|
627
|
+
const reversed = record.binary.reverse.kind === 'delta' ? applyDelta(produced, reverse) : reverse;
|
|
628
|
+
if (!reversed.equals(patchPreimage))
|
|
629
|
+
fail();
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
produced = Buffer.from(patchPreimage);
|
|
633
|
+
}
|
|
634
|
+
let postBytes;
|
|
635
|
+
if (newAbsent) {
|
|
636
|
+
if (produced.length !== 0)
|
|
637
|
+
fail();
|
|
638
|
+
}
|
|
639
|
+
else if (record.newMode === '160000') {
|
|
640
|
+
if (!produced.equals(Buffer.from(`Subproject commit ${record.newOid}\n`, 'ascii')))
|
|
641
|
+
fail();
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
if (produced.length > MAX_GROUNDED_OBJECT_BYTES || blobOid(produced, objectFormat) !== record.newOid || groundedByteLength > MAX_GROUNDED_TOTAL_BYTES - produced.length)
|
|
645
|
+
fail();
|
|
646
|
+
postBytes = Buffer.from(produced);
|
|
647
|
+
groundedByteLength += postBytes.length;
|
|
648
|
+
}
|
|
649
|
+
const oldPathMustBeAbsent = newAbsent || (record.status === 'R' &&
|
|
650
|
+
record.oldPath !== undefined &&
|
|
651
|
+
record.newPath !== undefined &&
|
|
652
|
+
!record.oldPath.bytes.equals(record.newPath.bytes));
|
|
653
|
+
if (oldPathMustBeAbsent && record.oldPath !== undefined && await readTarget(record.oldPath, record.oldMode) !== undefined) {
|
|
654
|
+
fail('Patch does not match the selected target.');
|
|
655
|
+
}
|
|
656
|
+
if (!newAbsent) {
|
|
657
|
+
const actual = await readTarget(record.newPath, record.newMode);
|
|
658
|
+
const mismatch = actual === undefined ||
|
|
659
|
+
actual.mode !== record.newMode ||
|
|
660
|
+
(record.newMode === '160000'
|
|
661
|
+
? actual.type !== 'commit' || actual.oid !== record.newOid
|
|
662
|
+
: actual.type !== 'blob' || actual.bytes === undefined || postBytes === undefined || !actual.bytes.equals(postBytes));
|
|
663
|
+
if (mismatch)
|
|
664
|
+
fail('Patch does not match the selected target.');
|
|
665
|
+
}
|
|
666
|
+
const statusCode = record.status === 'R' ? 'R' : record.status === 'C' ? 'C' : oldAbsent ? 'A' : newAbsent ? 'D' : record.oldMode !== record.newMode ? 'T' : 'M';
|
|
667
|
+
const kind = statusCode === 'A' ? 'added' : statusCode === 'D' ? 'deleted' : statusCode === 'R' ? 'renamed' : statusCode === 'C' ? 'copied' : statusCode === 'T' ? 'type-changed' : 'modified';
|
|
668
|
+
const id = `file_${createHash('sha256').update(`${index}\0${identity}`, 'utf8').digest('base64url')}`;
|
|
669
|
+
const additions = record.binary === undefined ? record.text?.filter((line) => line.startsWith('+')).length ?? 0 : null;
|
|
670
|
+
const deletions = record.binary === undefined ? record.text?.filter((line) => line.startsWith('-')).length ?? 0 : null;
|
|
671
|
+
files.push(Object.freeze({
|
|
672
|
+
id,
|
|
673
|
+
status: Object.freeze({ code: statusCode, kind, similarity: record.similarity }),
|
|
674
|
+
oldMode: record.oldMode,
|
|
675
|
+
newMode: record.newMode,
|
|
676
|
+
oldBlobOid: record.oldOid,
|
|
677
|
+
newBlobOid: record.newOid,
|
|
678
|
+
...(record.oldPath === undefined ? {} : { oldPath: record.oldPath.path }),
|
|
679
|
+
...(record.newPath === undefined ? {} : { newPath: record.newPath.path }),
|
|
680
|
+
additions,
|
|
681
|
+
deletions,
|
|
682
|
+
availability: availability(record.oldMode, record.newMode, oldBytes, postBytes),
|
|
683
|
+
}));
|
|
684
|
+
contents.set(id, Object.freeze({
|
|
685
|
+
preimage: oldBytes === undefined ? undefined : Buffer.from(oldBytes),
|
|
686
|
+
postimage: postBytes === undefined ? undefined : Buffer.from(postBytes),
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
const scope = Object.freeze({
|
|
690
|
+
kind: 'exact-patch',
|
|
691
|
+
digest: createHash('sha256').update(Buffer.from(options.patchContent, 'utf8')).digest('hex'),
|
|
692
|
+
validationTarget: Object.freeze({ ...options.target }),
|
|
693
|
+
submittedByteLength,
|
|
694
|
+
});
|
|
695
|
+
return Object.freeze({ repositoryRoot: repository.root, objectFormat, scope, changedFiles: Object.freeze(files), contents: new Map(contents) });
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
if (error instanceof ExactPatchGroundingError)
|
|
699
|
+
throw error;
|
|
700
|
+
throw new ExactPatchGroundingError();
|
|
701
|
+
}
|
|
702
|
+
}
|