@moritzwilksch/diffle 0.1.8 → 0.1.9
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/README.md +88 -131
- package/dist/client/assets/{angular-html-C3AR-WKY.js → angular-html-CJY2n6PT.js} +1 -1
- package/dist/client/assets/{angular-ts-bZzNmyh2.js → angular-ts-CZTM8_xo.js} +1 -1
- package/dist/client/assets/{apl-DKjQ1uUu.js → apl-Dcp77-Tz.js} +1 -1
- package/dist/client/assets/{astro-D6YBrldj.js → astro-C0gdsdoF.js} +1 -1
- package/dist/client/assets/{blade-QC3ty25r.js → blade-BYpI0fWU.js} +1 -1
- package/dist/client/assets/{c-CEfiNUIv.js → c-DvFVe1b3.js} +1 -1
- package/dist/client/assets/{chapel-XvVgmIw8.js → chapel-qAWjGK-V.js} +1 -1
- package/dist/client/assets/{cobol-Cyvb7uZ8.js → cobol-BeFQKGES.js} +1 -1
- package/dist/client/assets/{coffee-DKSUNEAJ.js → coffee-XLni1sjM.js} +1 -1
- package/dist/client/assets/{cpp-DvHlatt5.js → cpp-ChsJNYBC.js} +1 -1
- package/dist/client/assets/{crystal-BfxbaHfR.js → crystal-D3SbG8yD.js} +1 -1
- package/dist/client/assets/{css-CvzcDzFO.js → css-VXnGhPbY.js} +1 -1
- package/dist/client/assets/{edge-rOBhrNV9.js → edge-Dbzmpk4W.js} +1 -1
- package/dist/client/assets/{elixir-D3B6PlMg.js → elixir-DvQULd_0.js} +1 -1
- package/dist/client/assets/{elm-BnfJhLi3.js → elm-BM5mpULp.js} +1 -1
- package/dist/client/assets/{erb-CkLdBLG4.js → erb-nYCgf5TS.js} +1 -1
- package/dist/client/assets/{git-rebase-wqI2zI5G.js → git-rebase-CcDsi2L8.js} +1 -1
- package/dist/client/assets/{glimmer-js-CorAzX48.js → glimmer-js-94igFmPU.js} +1 -1
- package/dist/client/assets/{glimmer-ts-DphgSML7.js → glimmer-ts-8aOBlhVe.js} +1 -1
- package/dist/client/assets/{glsl-Mt2MgDCN.js → glsl-DAW_sXZj.js} +1 -1
- package/dist/client/assets/{graphql-CR58itij.js → graphql-BEeRZxhy.js} +1 -1
- package/dist/client/assets/{hack-BYLE2FB3.js → hack-e4pIPigB.js} +1 -1
- package/dist/client/assets/{haml-Bo5vpKMC.js → haml-DvZH4eyc.js} +1 -1
- package/dist/client/assets/{handlebars-Cd6w9Qte.js → handlebars-Bod10MIb.js} +1 -1
- package/dist/client/assets/{html-EAc4fezP.js → html-BOKVgXUz.js} +1 -1
- package/dist/client/assets/{html-derivative-BCmkEMhH.js → html-derivative-B5DftdEs.js} +1 -1
- package/dist/client/assets/{http-dQXVRwNo.js → http-Bli9WHgJ.js} +1 -1
- package/dist/client/assets/{hurl-CTzL1YrB.js → hurl-C45hn_gr.js} +1 -1
- package/dist/client/assets/index-BugDKSp3.js +2994 -0
- package/dist/client/assets/index-CvgG7WNQ.css +2 -0
- package/dist/client/assets/{java-C6GmB9Ie.js → java-BxezylI4.js} +1 -1
- package/dist/client/assets/{javascript-C3Xp2ZJG.js → javascript-CGUJoh42.js} +1 -1
- package/dist/client/assets/{jinja-Dm5uNF1A.js → jinja-Dhrq1TMH.js} +1 -1
- package/dist/client/assets/{jison-DWN1PAPH.js → jison-CK02w6aE.js} +1 -1
- package/dist/client/assets/{json-CD4Jl2TN.js → json-DbjMq68q.js} +1 -1
- package/dist/client/assets/{jsx-Dk_mg_Sq.js → jsx-BVYjBFnX.js} +1 -1
- package/dist/client/assets/{julia-ChQLeZ-J.js → julia-DUG0dPfw.js} +1 -1
- package/dist/client/assets/{just-Dw1MNaQ7.js → just-BzILq64e.js} +1 -1
- package/dist/client/assets/{latex-CHuqntlZ.js → latex-DJ-r-_gD.js} +1 -1
- package/dist/client/assets/{liquid-CdqvKBd5.js → liquid-CUeuBds8.js} +1 -1
- package/dist/client/assets/{lua-DtCcLDnQ.js → lua-CUx56wFu.js} +1 -1
- package/dist/client/assets/{marko-COZ6SLmm.js → marko-zidFMx4y.js} +1 -1
- package/dist/client/assets/{mdc-BTrsC_q_.js → mdc-B8YDeE-G.js} +1 -1
- package/dist/client/assets/{nginx-B_JY_bIl.js → nginx-CLmnmibq.js} +1 -1
- package/dist/client/assets/{nim-C7Boj9eE.js → nim-D_ow61_F.js} +1 -1
- package/dist/client/assets/{org-CYdh74lA.js → org-D6yFID6y.js} +1 -1
- package/dist/client/assets/{perl-CRiTh3Pi.js → perl-DlRNqEum.js} +1 -1
- package/dist/client/assets/{php-DSko-lNI.js → php-CPe77eH-.js} +1 -1
- package/dist/client/assets/{pug-BfUPln3e.js → pug-CzDq8hOp.js} +1 -1
- package/dist/client/assets/{qml-2jRMRran.js → qml-CmVqwUZS.js} +1 -1
- package/dist/client/assets/{r-sDW5nKEb.js → r-CZql-PDg.js} +1 -1
- package/dist/client/assets/{razor-Sa7GlYLb.js → razor-Dp5yql5G.js} +1 -1
- package/dist/client/assets/{regexp-AWVIUt-C.js → regexp-DjyDhvtX.js} +1 -1
- package/dist/client/assets/{rst-D20G5Wbg.js → rst-B-Q6Ljb6.js} +1 -1
- package/dist/client/assets/{ruby-DevA-y7a.js → ruby-BQD6FS2h.js} +1 -1
- package/dist/client/assets/{sas-DRiLtfNK.js → sas-DDGcym6k.js} +1 -1
- package/dist/client/assets/{scss-Ca4Dfxoe.js → scss-DNPPx0rx.js} +1 -1
- package/dist/client/assets/{shellscript-BNnaYtXN.js → shellscript-CHWpWo83.js} +1 -1
- package/dist/client/assets/{shellsession-B0nV-fpO.js → shellsession-_KE7LUMO.js} +1 -1
- package/dist/client/assets/{soy-CDZEZMSk.js → soy-B57xVh7W.js} +1 -1
- package/dist/client/assets/{sql-s_6SuPDf.js → sql-8OXlDHCR.js} +1 -1
- package/dist/client/assets/{stata-CaVdvC6P.js → stata-C452rT5l.js} +1 -1
- package/dist/client/assets/{surrealql-DZ5TdMgT.js → surrealql-B5gNeOUk.js} +1 -1
- package/dist/client/assets/{svelte-BkFQboE2.js → svelte-Cv_BwjgR.js} +1 -1
- package/dist/client/assets/{templ-QUoUDGUH.js → templ-qZdOs42N.js} +1 -1
- package/dist/client/assets/{tex-89lMVAG3.js → tex-BK-MJpDM.js} +1 -1
- package/dist/client/assets/{ts-tags-B2u4oUPE.js → ts-tags-C5Q-Olrs.js} +1 -1
- package/dist/client/assets/{tsx-ftFKhLnx.js → tsx-DwPm0lid.js} +1 -1
- package/dist/client/assets/{twig-DQCCfpmq.js → twig-DKj0IG75.js} +1 -1
- package/dist/client/assets/{typescript-DhCWy8LC.js → typescript-BeWKsdVP.js} +1 -1
- package/dist/client/assets/{typst-N-Gv0lEU.js → typst-B7u-3Y0n.js} +1 -1
- package/dist/client/assets/{vue-C7rUB8rE.js → vue-BkC1F40W.js} +1 -1
- package/dist/client/assets/{vue-html-D-dp6lDI.js → vue-html-BeYXHyr2.js} +1 -1
- package/dist/client/assets/{vue-vine-DEpfajiz.js → vue-vine-ymsHpgRC.js} +1 -1
- package/dist/client/assets/{xml-BGDSWH1X.js → xml-B6M3xnfe.js} +1 -1
- package/dist/client/assets/{xsl-D8Xmp-zV.js → xsl-CSlbm6tK.js} +1 -1
- package/dist/client/assets/{yaml-B3GKVR80.js → yaml-fMwD58zK.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/main.js +258 -52
- package/package.json +5 -3
- package/dist/client/assets/index-BkCL4UDj.css +0 -2
- package/dist/client/assets/index-DWcJ2X-_.js +0 -2989
package/dist/server/main.js
CHANGED
|
@@ -11,6 +11,7 @@ import { copyFile, lstat, mkdir, mkdtemp, open, readFile, readlink, realpath, re
|
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
12
|
import { homedir, hostname, tmpdir } from "node:os";
|
|
13
13
|
import { createServer } from "node:http";
|
|
14
|
+
import { getAssetKeys, getRawAsset, isSea } from "node:sea";
|
|
14
15
|
import { Http2ServerRequest, constants as constants$1 } from "http2";
|
|
15
16
|
import { Readable } from "stream";
|
|
16
17
|
import crypto from "crypto";
|
|
@@ -3951,7 +3952,7 @@ function generateCompletion(program, { shell, executable = program.name() }) {
|
|
|
3951
3952
|
}
|
|
3952
3953
|
//#endregion
|
|
3953
3954
|
//#region package.json
|
|
3954
|
-
var version$1 = "0.1.
|
|
3955
|
+
var version$1 = "0.1.9";
|
|
3955
3956
|
//#endregion
|
|
3956
3957
|
//#region src/server/comments/anchor.ts
|
|
3957
3958
|
function splitLines(contents) {
|
|
@@ -4198,6 +4199,7 @@ var GitRepo = class GitRepo {
|
|
|
4198
4199
|
new: next
|
|
4199
4200
|
};
|
|
4200
4201
|
}
|
|
4202
|
+
/** A commit's hash, abbreviation and full message; null when `rev` names no commit. */
|
|
4201
4203
|
async commitInfo(rev) {
|
|
4202
4204
|
let sha;
|
|
4203
4205
|
try {
|
|
@@ -4221,6 +4223,66 @@ var GitRepo = class GitRepo {
|
|
|
4221
4223
|
message: output.slice(separator + 1).trimEnd()
|
|
4222
4224
|
};
|
|
4223
4225
|
}
|
|
4226
|
+
/**
|
|
4227
|
+
* `sha`'s first parent, and the commit toward HEAD whose first parent it is. The
|
|
4228
|
+
* child is null at HEAD, on an unborn branch, or when `sha` is off HEAD's
|
|
4229
|
+
* first-parent line, e.g. on a merged side branch.
|
|
4230
|
+
*/
|
|
4231
|
+
async commitNeighbours(sha) {
|
|
4232
|
+
const [parent, list] = await Promise.all([this.resolve(`${sha}^`).catch((e) => {
|
|
4233
|
+
if (e instanceof GitError && e.code === 1) return null;
|
|
4234
|
+
throw e;
|
|
4235
|
+
}), this.text([
|
|
4236
|
+
"rev-list",
|
|
4237
|
+
"--first-parent",
|
|
4238
|
+
"--ancestry-path",
|
|
4239
|
+
"--parents",
|
|
4240
|
+
`${sha}..HEAD`,
|
|
4241
|
+
"--"
|
|
4242
|
+
]).catch(() => "")]);
|
|
4243
|
+
const [child, firstParent] = list.trim().split("\n").pop()?.split(" ") ?? [];
|
|
4244
|
+
return {
|
|
4245
|
+
parent,
|
|
4246
|
+
child: child && firstParent === sha ? child : null
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
/** The commits a revision range such as `a..b` or `c^!` selects, oldest first: the newest `limit`, and the total. */
|
|
4250
|
+
async rangeCommits(range, limit) {
|
|
4251
|
+
const [count, log] = await Promise.all([this.text([
|
|
4252
|
+
"rev-list",
|
|
4253
|
+
"--count",
|
|
4254
|
+
"--end-of-options",
|
|
4255
|
+
range,
|
|
4256
|
+
"--"
|
|
4257
|
+
]), this.text([
|
|
4258
|
+
"log",
|
|
4259
|
+
"-z",
|
|
4260
|
+
"--no-show-signature",
|
|
4261
|
+
"--reverse",
|
|
4262
|
+
`--max-count=${limit}`,
|
|
4263
|
+
"--format=%H%x00%h%x00%an%x00%ae%x00%at%x00%B",
|
|
4264
|
+
"--end-of-options",
|
|
4265
|
+
range,
|
|
4266
|
+
"--"
|
|
4267
|
+
])]);
|
|
4268
|
+
const fields = log.split("\0");
|
|
4269
|
+
const list = [];
|
|
4270
|
+
for (let i = 0; i + 5 < fields.length; i += 6) {
|
|
4271
|
+
const [sha = "", short = "", author = "", email = "", time = "", message = ""] = fields.slice(i, i + 6);
|
|
4272
|
+
list.push({
|
|
4273
|
+
sha,
|
|
4274
|
+
short,
|
|
4275
|
+
message: message.trimEnd(),
|
|
4276
|
+
author,
|
|
4277
|
+
email,
|
|
4278
|
+
date: Number(time) * 1e3
|
|
4279
|
+
});
|
|
4280
|
+
}
|
|
4281
|
+
return {
|
|
4282
|
+
list,
|
|
4283
|
+
total: Number(count.trim())
|
|
4284
|
+
};
|
|
4285
|
+
}
|
|
4224
4286
|
/** The empty tree under the repository's hash algorithm: the old side of an unborn branch. */
|
|
4225
4287
|
async emptyTree() {
|
|
4226
4288
|
return (await this.text([
|
|
@@ -12099,14 +12161,26 @@ const ModeRequestSchema = discriminatedUnion("kind", [
|
|
|
12099
12161
|
object({
|
|
12100
12162
|
kind: literal("revspec"),
|
|
12101
12163
|
args: string().array()
|
|
12164
|
+
}),
|
|
12165
|
+
object({
|
|
12166
|
+
kind: literal("focus"),
|
|
12167
|
+
commit: string().nullable()
|
|
12102
12168
|
})
|
|
12103
12169
|
]);
|
|
12104
|
-
/**
|
|
12105
|
-
|
|
12170
|
+
/**
|
|
12171
|
+
* How the old side is derived: `old` itself, merge-base(old, new) with worktree
|
|
12172
|
+
* taken as HEAD, or new's first parent (the empty tree for a root commit).
|
|
12173
|
+
*/
|
|
12174
|
+
const ComparisonBaseSchema = _enum([
|
|
12175
|
+
"direct",
|
|
12176
|
+
"merge-base",
|
|
12177
|
+
"parent"
|
|
12178
|
+
]);
|
|
12179
|
+
/** A comparison; both endpoints accept a Git revision or "worktree". Under `parent`, both name the one commit. */
|
|
12180
|
+
const ComparisonSchema = object({
|
|
12106
12181
|
old: string(),
|
|
12107
12182
|
new: string(),
|
|
12108
|
-
|
|
12109
|
-
mergeBase: boolean(),
|
|
12183
|
+
base: ComparisonBaseSchema,
|
|
12110
12184
|
live: _enum([
|
|
12111
12185
|
"worktree",
|
|
12112
12186
|
"refs",
|
|
@@ -12115,10 +12189,18 @@ const ModeSpecSchema = object({
|
|
|
12115
12189
|
/** Fixed when the comparison is entered; discovery never changes comment storage. */
|
|
12116
12190
|
commentKey: string()
|
|
12117
12191
|
});
|
|
12118
|
-
|
|
12192
|
+
const ModeSpecSchema = ComparisonSchema.extend({
|
|
12193
|
+
/** Set while one commit of a range is focused: the range, whose commits the snapshot keeps listing. */
|
|
12194
|
+
within: ComparisonSchema.optional() });
|
|
12195
|
+
/**
|
|
12196
|
+
* Display comparison endpoints with branch names instead of internal ref namespaces; a single commit as
|
|
12197
|
+
* `<sha>^!`, or as `<range> @ <sha>` when focused within a range.
|
|
12198
|
+
*/
|
|
12119
12199
|
function comparisonLabel(mode) {
|
|
12200
|
+
if (mode.within) return `${comparisonLabel(mode.within)} @ ${mode.new.slice(0, 7)}`;
|
|
12201
|
+
if (mode.base === "parent") return `${mode.new.slice(0, 7)}^!`;
|
|
12120
12202
|
const name = (rev) => rev.replace(/^refs\/diffle\/[^/]+\/\d+\/(?:base|head)\//, "").replace(/^refs\/(?:heads|remotes)\//, "");
|
|
12121
|
-
return `${name(mode.old)}${mode.
|
|
12203
|
+
return `${name(mode.old)}${mode.base === "merge-base" ? "..." : ".."}${name(mode.new)}`;
|
|
12122
12204
|
}
|
|
12123
12205
|
const GithubPullRequestSchema = object({
|
|
12124
12206
|
repository: string(),
|
|
@@ -12168,6 +12250,32 @@ const ChangedFileSchema = object({
|
|
|
12168
12250
|
/** A gitlink (mode 160000): `blob` is the recorded commit, the patch shows the commit-id change, and there is no file to open. */
|
|
12169
12251
|
submodule: literal(true).optional()
|
|
12170
12252
|
});
|
|
12253
|
+
const CommitInfoSchema = object({
|
|
12254
|
+
sha: string(),
|
|
12255
|
+
short: string(),
|
|
12256
|
+
message: string()
|
|
12257
|
+
});
|
|
12258
|
+
/** A single reviewed commit and its neighbours along HEAD's first-parent history, for stepping through it. */
|
|
12259
|
+
const ReviewedCommitSchema = CommitInfoSchema.extend({
|
|
12260
|
+
/** First parent; null for a root commit. */
|
|
12261
|
+
parent: string().nullable(),
|
|
12262
|
+
/** The commit whose first parent this is, toward HEAD; null at HEAD or off HEAD's first-parent line. */
|
|
12263
|
+
child: string().nullable()
|
|
12264
|
+
});
|
|
12265
|
+
/**
|
|
12266
|
+
* The commits a comparison spans: old..new, with the worktree taken as HEAD, or the one commit under `parent`.
|
|
12267
|
+
* A focused commit lists its range's.
|
|
12268
|
+
*/
|
|
12269
|
+
const RangeCommitsSchema = object({
|
|
12270
|
+
/** Oldest first; the newest MAX_RANGE_COMMITS when there are more. */
|
|
12271
|
+
list: CommitInfoSchema.extend({
|
|
12272
|
+
author: string(),
|
|
12273
|
+
email: string(),
|
|
12274
|
+
/** Author date, epoch milliseconds. */
|
|
12275
|
+
date: number()
|
|
12276
|
+
}).array(),
|
|
12277
|
+
total: number()
|
|
12278
|
+
});
|
|
12171
12279
|
object({
|
|
12172
12280
|
root: string(),
|
|
12173
12281
|
mode: ModeSpecSchema,
|
|
@@ -12182,7 +12290,10 @@ object({
|
|
|
12182
12290
|
context: number(),
|
|
12183
12291
|
changed: ChangedFileSchema.array(),
|
|
12184
12292
|
/** All paths on the new side: the new commit's tree, or index ∪ untracked for the worktree. Sorted. */
|
|
12185
|
-
tree: string().array()
|
|
12293
|
+
tree: string().array(),
|
|
12294
|
+
/** The reviewed commit when the comparison is a single commit against its parent; otherwise null. */
|
|
12295
|
+
commit: ReviewedCommitSchema.nullable(),
|
|
12296
|
+
commits: RangeCommitsSchema
|
|
12186
12297
|
});
|
|
12187
12298
|
/** Body of `POST /api/patch`: the changed files whose patches to return, concatenated. Unknown paths are skipped. */
|
|
12188
12299
|
const PatchRequestSchema = object({ paths: string().array().max(200) });
|
|
@@ -12279,11 +12390,6 @@ const ViewedEntrySchema = object({
|
|
|
12279
12390
|
blob: string(),
|
|
12280
12391
|
viewed: boolean()
|
|
12281
12392
|
});
|
|
12282
|
-
const CommitInfoSchema = object({
|
|
12283
|
-
sha: string(),
|
|
12284
|
-
short: string(),
|
|
12285
|
-
message: string()
|
|
12286
|
-
});
|
|
12287
12393
|
object({
|
|
12288
12394
|
old: CommitInfoSchema.nullable(),
|
|
12289
12395
|
new: CommitInfoSchema.nullable()
|
|
@@ -14044,6 +14150,7 @@ const WORKTREE = "worktree";
|
|
|
14044
14150
|
* "a..b" → a vs b
|
|
14045
14151
|
* "a...b" → merge-base(a, b) vs b
|
|
14046
14152
|
* "a" "b" → a vs b
|
|
14153
|
+
* "a^!" → a vs its first parent: the one commit, as git show shows it
|
|
14047
14154
|
* Empty sides default to HEAD. Either side accepts "worktree" for the uncommitted
|
|
14048
14155
|
* tree; a merge base against it is taken against HEAD, the commit it sits on.
|
|
14049
14156
|
*/
|
|
@@ -14052,34 +14159,45 @@ function parseRevspec(args) {
|
|
|
14052
14159
|
if (args.length === 2) {
|
|
14053
14160
|
const [a, b] = args;
|
|
14054
14161
|
if (a.includes("..") || b.includes("..")) throw new RevspecError("cannot combine \"..\" with two revisions");
|
|
14162
|
+
if (a.endsWith("^!") || b.endsWith("^!")) throw new RevspecError("cannot combine \"^!\" with two revisions");
|
|
14055
14163
|
return {
|
|
14056
14164
|
old: a,
|
|
14057
14165
|
new: b,
|
|
14058
|
-
|
|
14166
|
+
base: "direct"
|
|
14059
14167
|
};
|
|
14060
14168
|
}
|
|
14061
14169
|
const arg = args[0];
|
|
14170
|
+
if (arg.endsWith("^!")) {
|
|
14171
|
+
const commit = arg.slice(0, -2) || "HEAD";
|
|
14172
|
+
if (commit.includes("..")) throw new RevspecError("cannot combine \"^!\" with \"..\"");
|
|
14173
|
+
if (commit === WORKTREE) throw new RevspecError("the worktree is not a commit");
|
|
14174
|
+
return {
|
|
14175
|
+
old: commit,
|
|
14176
|
+
new: commit,
|
|
14177
|
+
base: "parent"
|
|
14178
|
+
};
|
|
14179
|
+
}
|
|
14062
14180
|
const three = arg.indexOf("...");
|
|
14063
14181
|
if (three !== -1) return {
|
|
14064
14182
|
old: arg.slice(0, three) || "HEAD",
|
|
14065
14183
|
new: arg.slice(three + 3) || "HEAD",
|
|
14066
|
-
|
|
14184
|
+
base: "merge-base"
|
|
14067
14185
|
};
|
|
14068
14186
|
const two = arg.indexOf("..");
|
|
14069
14187
|
if (two !== -1) return {
|
|
14070
14188
|
old: arg.slice(0, two) || "HEAD",
|
|
14071
14189
|
new: arg.slice(two + 2) || "HEAD",
|
|
14072
|
-
|
|
14190
|
+
base: "direct"
|
|
14073
14191
|
};
|
|
14074
14192
|
if (arg === WORKTREE) return {
|
|
14075
14193
|
old: "HEAD",
|
|
14076
14194
|
new: WORKTREE,
|
|
14077
|
-
|
|
14195
|
+
base: "direct"
|
|
14078
14196
|
};
|
|
14079
14197
|
return {
|
|
14080
14198
|
old: arg,
|
|
14081
14199
|
new: "HEAD",
|
|
14082
|
-
|
|
14200
|
+
base: "merge-base"
|
|
14083
14201
|
};
|
|
14084
14202
|
}
|
|
14085
14203
|
var RevspecError = class extends Error {};
|
|
@@ -17705,7 +17823,8 @@ var Server = class {
|
|
|
17705
17823
|
app.use("/api/*", compress());
|
|
17706
17824
|
app.route("/", createApi(this.deps));
|
|
17707
17825
|
app.all("/api/*", (c) => c.json({ error: "API endpoint not found. Restart diffle to load the latest server." }, 404));
|
|
17708
|
-
if (!this.opts.dev)
|
|
17826
|
+
if (!this.opts.dev && isSea()) app.get("*", serveEmbedded);
|
|
17827
|
+
else if (!this.opts.dev) {
|
|
17709
17828
|
const clientDir = resolveClientDir();
|
|
17710
17829
|
app.use("*", serveStatic({ root: clientDir }));
|
|
17711
17830
|
app.get("*", serveStatic({
|
|
@@ -17792,7 +17911,14 @@ function resolveClientDir() {
|
|
|
17792
17911
|
return join(projectRoot(), "dist", "client");
|
|
17793
17912
|
}
|
|
17794
17913
|
function hasClientBuild() {
|
|
17795
|
-
return existsSync(join(resolveClientDir(), "index.html"));
|
|
17914
|
+
return isSea() || existsSync(join(resolveClientDir(), "index.html"));
|
|
17915
|
+
}
|
|
17916
|
+
const embedded = isSea() ? new Set(getAssetKeys()) : /* @__PURE__ */ new Set();
|
|
17917
|
+
function serveEmbedded(c) {
|
|
17918
|
+
const path = c.req.path.slice(1);
|
|
17919
|
+
const key = embedded.has(path) ? path : "index.html";
|
|
17920
|
+
const type = getMimeType(key) ?? "application/octet-stream";
|
|
17921
|
+
return c.body(new Uint8Array(getRawAsset(key)), 200, { "content-type": type });
|
|
17796
17922
|
}
|
|
17797
17923
|
//#endregion
|
|
17798
17924
|
//#region src/server/github/pulls.ts
|
|
@@ -18307,17 +18433,19 @@ function describe(skipped) {
|
|
|
18307
18433
|
* A--B--C main
|
|
18308
18434
|
* \--D--E HEAD
|
|
18309
18435
|
*
|
|
18310
|
-
* old new
|
|
18311
|
-
* main HEAD
|
|
18312
|
-
* main HEAD
|
|
18313
|
-
* HEAD worktree
|
|
18314
|
-
* worktree HEAD
|
|
18315
|
-
* main worktree
|
|
18316
|
-
* <C hash> <E hash>
|
|
18436
|
+
* old new base oldSha newSha live
|
|
18437
|
+
* main HEAD direct C E refs
|
|
18438
|
+
* main HEAD merge-base B E refs
|
|
18439
|
+
* HEAD worktree direct E worktree worktree
|
|
18440
|
+
* worktree HEAD direct worktree E worktree
|
|
18441
|
+
* main worktree merge-base B worktree worktree
|
|
18442
|
+
* <C hash> <E hash> direct C E none
|
|
18443
|
+
* <E hash> <E hash> parent D E none
|
|
18317
18444
|
*
|
|
18318
|
-
*
|
|
18319
|
-
* HEAD for any worktree input
|
|
18320
|
-
*
|
|
18445
|
+
* merge-base replaces only the old endpoint with the common ancestor, using
|
|
18446
|
+
* HEAD for any worktree input; parent replaces it with new's first parent, or
|
|
18447
|
+
* the empty tree for a root commit. live is "worktree" if either input is
|
|
18448
|
+
* worktree, "none" if both inputs are pinned hashes, and "refs" otherwise.
|
|
18321
18449
|
*
|
|
18322
18450
|
* In an unborn repository, HEAD compared directly with worktree resolves to
|
|
18323
18451
|
* the empty tree. Invalid revisions or missing merge bases throw RevspecError.
|
|
@@ -18329,14 +18457,14 @@ async function resolveComparison(repo, comparison) {
|
|
|
18329
18457
|
try {
|
|
18330
18458
|
return await repo.resolve(rev);
|
|
18331
18459
|
} catch (e) {
|
|
18332
|
-
if (
|
|
18460
|
+
if (comparison.base === "direct" && rev === "HEAD" && endpoints.includes("worktree") && e instanceof GitError && e.code === 1) return repo.emptyTree();
|
|
18333
18461
|
if (e instanceof GitError) throw new RevspecError(`unknown revision: ${rev}`);
|
|
18334
18462
|
throw e;
|
|
18335
18463
|
}
|
|
18336
18464
|
};
|
|
18337
18465
|
const [old, next] = await Promise.all([resolve(comparison.old), resolve(comparison.new)]);
|
|
18338
18466
|
let oldSha = old;
|
|
18339
|
-
if (comparison.
|
|
18467
|
+
if (comparison.base === "merge-base") {
|
|
18340
18468
|
const [a, b] = await Promise.all([old === "worktree" ? resolve("HEAD") : old, next === "worktree" ? resolve("HEAD") : next]);
|
|
18341
18469
|
try {
|
|
18342
18470
|
oldSha = await repo.mergeBase(a, b);
|
|
@@ -18345,6 +18473,13 @@ async function resolveComparison(repo, comparison) {
|
|
|
18345
18473
|
throw e;
|
|
18346
18474
|
}
|
|
18347
18475
|
}
|
|
18476
|
+
if (comparison.base === "parent") {
|
|
18477
|
+
if (next === "worktree") throw new RevspecError("the worktree is not a commit");
|
|
18478
|
+
oldSha = await repo.resolve(`${next}^`).catch((e) => {
|
|
18479
|
+
if (e instanceof GitError && e.code === 1) return repo.emptyTree();
|
|
18480
|
+
throw e;
|
|
18481
|
+
});
|
|
18482
|
+
}
|
|
18348
18483
|
const pinned = (rev, sha) => rev.length >= 7 && sha.startsWith(rev.toLowerCase());
|
|
18349
18484
|
return {
|
|
18350
18485
|
oldSha,
|
|
@@ -18358,8 +18493,18 @@ async function resolveReview(req, repo, github) {
|
|
|
18358
18493
|
const parsed = req.kind === "working" ? {
|
|
18359
18494
|
old: "HEAD",
|
|
18360
18495
|
new: "worktree",
|
|
18361
|
-
|
|
18496
|
+
base: "direct"
|
|
18362
18497
|
} : parseRevspec(req.args);
|
|
18498
|
+
if (parsed.base === "parent") {
|
|
18499
|
+
const { newSha: sha } = await resolveComparison(repo, parsed);
|
|
18500
|
+
return { mode: {
|
|
18501
|
+
old: sha,
|
|
18502
|
+
new: sha,
|
|
18503
|
+
base: "parent",
|
|
18504
|
+
live: "none",
|
|
18505
|
+
commentKey: `commit:${sha}`
|
|
18506
|
+
} };
|
|
18507
|
+
}
|
|
18363
18508
|
const { oldSha, newSha, live } = await resolveComparison(repo, parsed);
|
|
18364
18509
|
return { mode: {
|
|
18365
18510
|
...parsed,
|
|
@@ -18369,19 +18514,21 @@ async function resolveReview(req, repo, github) {
|
|
|
18369
18514
|
}
|
|
18370
18515
|
/**
|
|
18371
18516
|
* Branch identities keep review state stable across pushes and local branch aliases.
|
|
18517
|
+
* Keys read like the comparison, with ... for a merge base and .. otherwise.
|
|
18372
18518
|
* For origin = github.com/o/r and HEAD on feat tracking origin/feat:
|
|
18373
|
-
* origin/main...HEAD →
|
|
18374
|
-
* origin/main..HEAD →
|
|
18519
|
+
* origin/main...HEAD → range:o/r:main...o/r:feat
|
|
18520
|
+
* origin/main..HEAD → range:o/r:main..o/r:feat
|
|
18375
18521
|
* The matching explicit PR uses the first key too. If feat tracks a fork,
|
|
18376
18522
|
* its identity becomes e.g. "contributor/r:feat", keeping forks distinct.
|
|
18377
18523
|
*
|
|
18378
18524
|
* Without a GitHub upstream, branches use full refs:
|
|
18379
|
-
* main...feat →
|
|
18525
|
+
* main...feat → range:refs/heads/main...refs/heads/feat
|
|
18380
18526
|
* Non-branch endpoints use their resolved hash or literal "worktree":
|
|
18381
|
-
* main..worktree →
|
|
18382
|
-
*
|
|
18527
|
+
* main..worktree → range:refs/heads/main..worktree
|
|
18528
|
+
* <C>..<E> → range:<C hash>..<E hash>
|
|
18383
18529
|
* Hash placeholders denote full resolved hashes, with oldSha already adjusted
|
|
18384
|
-
* for
|
|
18530
|
+
* for the merge base. The working command bypasses this helper and uses
|
|
18531
|
+
* "working"; a single commit uses "commit:<hash>".
|
|
18385
18532
|
*/
|
|
18386
18533
|
async function commentKey(repo, comparison, oldSha, newSha) {
|
|
18387
18534
|
const remotes = await repo.remotes();
|
|
@@ -18395,14 +18542,10 @@ async function commentKey(repo, comparison, oldSha, newSha) {
|
|
|
18395
18542
|
return repo.branchRef(rev);
|
|
18396
18543
|
};
|
|
18397
18544
|
const [old, next] = await Promise.all([identity(comparison.old), identity(comparison.new)]);
|
|
18398
|
-
return
|
|
18545
|
+
return rangeKey(old ?? oldSha, next ?? newSha, comparison.base === "merge-base");
|
|
18399
18546
|
}
|
|
18400
|
-
function
|
|
18401
|
-
return `
|
|
18402
|
-
old,
|
|
18403
|
-
next,
|
|
18404
|
-
mergeBase
|
|
18405
|
-
])}`;
|
|
18547
|
+
function rangeKey(old, next, mergeBase) {
|
|
18548
|
+
return `range:${old}${mergeBase ? "..." : ".."}${next}`;
|
|
18406
18549
|
}
|
|
18407
18550
|
/**
|
|
18408
18551
|
* A GitHub pull request, as GitHub shows it: merge-base(base tip, head) vs head.
|
|
@@ -18421,16 +18564,16 @@ async function resolvePr(req, repo, github) {
|
|
|
18421
18564
|
await resolveComparison(repo, {
|
|
18422
18565
|
old: base,
|
|
18423
18566
|
new: head,
|
|
18424
|
-
|
|
18567
|
+
base: "merge-base"
|
|
18425
18568
|
});
|
|
18426
18569
|
return {
|
|
18427
18570
|
prUrl: pr.url,
|
|
18428
18571
|
mode: {
|
|
18429
18572
|
old: base,
|
|
18430
18573
|
new: head,
|
|
18431
|
-
|
|
18574
|
+
base: "merge-base",
|
|
18432
18575
|
live: "none",
|
|
18433
|
-
commentKey:
|
|
18576
|
+
commentKey: rangeKey(`${pr.repository.toLowerCase()}:${pr.baseRefName}`, `${pr.headRepository?.toLowerCase() ?? `deleted:${pr.url}`}:${pr.headRefName}`, true)
|
|
18434
18577
|
}
|
|
18435
18578
|
};
|
|
18436
18579
|
}
|
|
@@ -18579,7 +18722,12 @@ var Snapshotter = class {
|
|
|
18579
18722
|
}
|
|
18580
18723
|
async compute(version) {
|
|
18581
18724
|
const [{ oldSha, newSha }, headSha] = await Promise.all([resolveComparison(this.repo, this.mode), this.repo.resolve("HEAD").catch(() => "")]);
|
|
18582
|
-
const [tracked, changed] = await Promise.all([
|
|
18725
|
+
const [tracked, changed, commit, commits] = await Promise.all([
|
|
18726
|
+
newSha === "worktree" ? Promise.all([this.repo.lsFiles(), this.repo.untracked()]).then((lists) => lists.flat().filter((p) => !p.endsWith("/"))) : this.repo.lsTree(newSha),
|
|
18727
|
+
this.repo.numstat(oldSha, newSha),
|
|
18728
|
+
this.mode.base === "parent" ? this.reviewedCommit(newSha) : null,
|
|
18729
|
+
this.rangeCommits(oldSha, newSha, headSha)
|
|
18730
|
+
]);
|
|
18583
18731
|
await this.fillGenerated(changed, newSha);
|
|
18584
18732
|
const tree = new Set(tracked);
|
|
18585
18733
|
for (const f of changed) {
|
|
@@ -18596,11 +18744,36 @@ var Snapshotter = class {
|
|
|
18596
18744
|
headSha,
|
|
18597
18745
|
context: this.context,
|
|
18598
18746
|
changed,
|
|
18599
|
-
tree: [...tree].sort()
|
|
18747
|
+
tree: [...tree].sort(),
|
|
18748
|
+
commit,
|
|
18749
|
+
commits
|
|
18600
18750
|
};
|
|
18601
18751
|
for (const f of changed.slice(0, PREWARM)) this.patchFor(snap, f).catch(() => {});
|
|
18602
18752
|
return snap;
|
|
18603
18753
|
}
|
|
18754
|
+
async reviewedCommit(sha) {
|
|
18755
|
+
const [info, neighbours] = await Promise.all([this.repo.commitInfo(sha), this.repo.commitNeighbours(sha)]);
|
|
18756
|
+
return info && {
|
|
18757
|
+
...info,
|
|
18758
|
+
...neighbours
|
|
18759
|
+
};
|
|
18760
|
+
}
|
|
18761
|
+
async rangeCommits(oldSha, newSha, headSha) {
|
|
18762
|
+
if (this.mode.within) {
|
|
18763
|
+
const range = await resolveComparison(this.repo, this.mode.within);
|
|
18764
|
+
return this.spanned(range.oldSha, range.newSha, headSha);
|
|
18765
|
+
}
|
|
18766
|
+
if (this.mode.base === "parent") return this.repo.rangeCommits(`${newSha}^!`, 250);
|
|
18767
|
+
return this.spanned(oldSha, newSha, headSha);
|
|
18768
|
+
}
|
|
18769
|
+
spanned(oldSha, newSha, headSha) {
|
|
18770
|
+
const [from, to] = [oldSha, newSha].map((sha) => sha === "worktree" ? headSha : sha);
|
|
18771
|
+
if (!from || !to || from === to) return Promise.resolve({
|
|
18772
|
+
list: [],
|
|
18773
|
+
total: 0
|
|
18774
|
+
});
|
|
18775
|
+
return this.repo.rangeCommits(`${from}..${to}`, 250);
|
|
18776
|
+
}
|
|
18604
18777
|
/**
|
|
18605
18778
|
* Sets `generated` on each non-binary changed file. A `linguist-generated`
|
|
18606
18779
|
* gitattribute on the new side decides outright, as on GitHub; otherwise a
|
|
@@ -18771,7 +18944,7 @@ var Session = class {
|
|
|
18771
18944
|
}
|
|
18772
18945
|
/** Switch modes at runtime. Broadcasts a snapshot bump on success. */
|
|
18773
18946
|
async switchMode(req) {
|
|
18774
|
-
const snap = await this.run(async () => this.activate(await resolveReview(req, this.repo, this.opts.github)));
|
|
18947
|
+
const snap = await this.run(async () => this.activate(req.kind === "focus" ? await this.focus(req.commit) : await resolveReview(req, this.repo, this.opts.github)));
|
|
18775
18948
|
this.hub.broadcast({
|
|
18776
18949
|
type: "snapshot",
|
|
18777
18950
|
version: snap.version
|
|
@@ -18779,6 +18952,33 @@ var Session = class {
|
|
|
18779
18952
|
return snap;
|
|
18780
18953
|
}
|
|
18781
18954
|
/**
|
|
18955
|
+
* Focuses `commit`, by hash or short hash, of the active range's listed commits, or with null returns to
|
|
18956
|
+
* the range. A focused commit keeps its own comments, as when entered alone, and the range's PR identity.
|
|
18957
|
+
*/
|
|
18958
|
+
async focus(commit) {
|
|
18959
|
+
const a = this.require();
|
|
18960
|
+
const { within, ...self } = a.mode;
|
|
18961
|
+
if (!within && self.base === "parent") throw new RevspecError("a single commit has no commits to focus");
|
|
18962
|
+
const range = within ?? self;
|
|
18963
|
+
if (commit === null) return {
|
|
18964
|
+
mode: range,
|
|
18965
|
+
prUrl: a.prUrl
|
|
18966
|
+
};
|
|
18967
|
+
const sha = (await a.snapshotter.current()).commits.list.find((c) => c.sha === commit || c.short === commit)?.sha;
|
|
18968
|
+
if (!sha) throw new RevspecError(`not a listed commit of ${comparisonLabel(range)}: ${commit}`);
|
|
18969
|
+
return {
|
|
18970
|
+
prUrl: a.prUrl,
|
|
18971
|
+
mode: {
|
|
18972
|
+
old: sha,
|
|
18973
|
+
new: sha,
|
|
18974
|
+
base: "parent",
|
|
18975
|
+
live: range.live === "none" ? "none" : "refs",
|
|
18976
|
+
commentKey: `commit:${sha}`,
|
|
18977
|
+
within: range
|
|
18978
|
+
}
|
|
18979
|
+
};
|
|
18980
|
+
}
|
|
18981
|
+
/**
|
|
18782
18982
|
* Runs `fn` after every earlier mutation settled. The only way to touch
|
|
18783
18983
|
* `active`, `version` or `opts.context`.
|
|
18784
18984
|
*/
|
|
@@ -22895,6 +23095,7 @@ Shorthands (in place of <revs>):
|
|
|
22895
23095
|
working same as HEAD..worktree: uncommitted changes, staged and untracked included
|
|
22896
23096
|
pr [number|url] a GitHub pull request: its base...head, fetched if needed
|
|
22897
23097
|
(without an argument: the pull request for this branch)
|
|
23098
|
+
show [rev] same as <rev>^!: one commit against its first parent, pinned (default: HEAD)
|
|
22898
23099
|
|
|
22899
23100
|
Revisions follow git diff, except that a lone one compares from the merge base:
|
|
22900
23101
|
diffle main same as main...HEAD: what this branch added since it left main
|
|
@@ -22902,6 +23103,7 @@ Revisions follow git diff, except that a lone one compares from the merge base:
|
|
|
22902
23103
|
diffle main..feat main vs feat
|
|
22903
23104
|
diffle main...feat what feat added since it left main
|
|
22904
23105
|
diffle main..worktree main vs the uncommitted tree ("worktree" works on either side)
|
|
23106
|
+
diffle a1b2c3d^! the commit a1b2c3d alone, like git show
|
|
22905
23107
|
|
|
22906
23108
|
Without revisions, diffle compares HEAD..worktree.
|
|
22907
23109
|
|
|
@@ -22914,6 +23116,10 @@ Ctrl+C always stops it.`).action(async (revs, _o, cmd) => {
|
|
|
22914
23116
|
}, cmd.optsWithGlobals());
|
|
22915
23117
|
});
|
|
22916
23118
|
program.command("working", { hidden: true }).summary("same as HEAD..worktree").description("Same as `diffle HEAD..worktree`: uncommitted changes, staged and untracked files included.").action(async (_o, cmd) => run({ kind: "working" }, cmd.optsWithGlobals()));
|
|
23119
|
+
program.command("show", { hidden: true }).argument("[rev]", "the commit to review", "HEAD").summary("one commit").description("Same as `diffle <rev>^!`: one commit against its first parent, as git show shows it.").action(async (rev, _o, cmd) => run({
|
|
23120
|
+
kind: "revspec",
|
|
23121
|
+
args: [`${rev}^!`]
|
|
23122
|
+
}, cmd.optsWithGlobals()));
|
|
22917
23123
|
program.command("pr", { hidden: true }).argument("[pr]", "pull request number or url; default: the pull request for this branch").summary("a GitHub pull request").description("A GitHub pull request, as GitHub shows it: merge-base(base, head) vs head.").action(async (pr, _o, cmd) => run({
|
|
22918
23124
|
kind: "pr",
|
|
22919
23125
|
pr
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moritzwilksch/diffle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Local git diff reviewer in the browser with line comments exported as agent prompts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"diff",
|
|
10
10
|
"git"
|
|
11
11
|
],
|
|
12
|
-
"homepage": "https://
|
|
12
|
+
"homepage": "https://diffle.app",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/moritzwilksch/diffle/issues"
|
|
15
15
|
},
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"bin",
|
|
27
27
|
"dist",
|
|
28
|
+
"!dist/sea",
|
|
28
29
|
"THIRD_PARTY_NOTICES.md"
|
|
29
30
|
],
|
|
30
31
|
"type": "module",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"build": "npm run build:client && npm run build:server",
|
|
38
39
|
"build:client": "vite build",
|
|
39
40
|
"build:server": "tsdown",
|
|
41
|
+
"build:sea": "npm run build && node scripts/build-sea.mjs",
|
|
40
42
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit && tsc -p tsconfig.e2e.json --noEmit",
|
|
41
43
|
"lint": "oxlint",
|
|
42
44
|
"format": "oxfmt",
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
"test:e2e": "playwright test",
|
|
46
48
|
"test:e2e:install": "playwright install chromium",
|
|
47
49
|
"test:e2e:install-deps": "playwright install-deps chromium",
|
|
48
|
-
"screenshot": "tsx",
|
|
50
|
+
"screenshot": "tsx scripts/screenshot.ts",
|
|
49
51
|
"test:e2e:docker": "node scripts/e2e-docker.mjs",
|
|
50
52
|
"test:e2e:docker:update": "node scripts/e2e-docker.mjs --update-snapshots",
|
|
51
53
|
"test:e2e:report": "playwright show-report playwright-report",
|