@kubohiroya/turbowarp-plus-editor-git 0.16.1 → 0.18.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.
@@ -1,4 +1,5 @@
1
- import { S as createProjectSourceDiff, _ as usePreviewSync, a as openProjectSourceTabs, c as gitHubRepositoryFromOrigin, d as assetFileName, f as organizeProjectChanges, g as createPreviewSync, h as BlockPreview, i as createScratchBlocksDiffLocator, l as ProjectSourceComparison, m as compareProjectForReview, n as openProjectComparison, p as projectPropertyLabel, r as createScratchBlocksDiffRenderer, s as createGitHubPullRequests, t as ProjectComparisonDialog, u as ProjectSourceReview, v as currentCostume, w as readProjectSourceDiff, x as compareProjectBlocks } from "./project-comparison-1rggIJse.js";
1
+ import { a as compareProjectBlocks, o as createProjectSourceDiff, u as readProjectSourceDiff } from "./project-asset-paths-BzwVf9HE.js";
2
+ import { _ as usePreviewSync, a as openProjectSourceTabs, c as gitHubRepositoryFromOrigin, d as assetFileName, f as organizeProjectChanges, g as createPreviewSync, h as BlockPreview, i as createScratchBlocksDiffLocator, l as ProjectSourceComparison, m as compareProjectForReview, n as openProjectComparison, p as projectPropertyLabel, r as createScratchBlocksDiffRenderer, s as createGitHubPullRequests, t as ProjectComparisonDialog, u as ProjectSourceReview, v as currentCostume } from "./project-comparison-BXOQcZ73.js";
2
3
  import { n as init_objectSpread2, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
3
4
  import React, { useEffect, useMemo, useRef, useState } from "react";
4
5
  // src/project-source-diff.tsx
@@ -3,11 +3,35 @@ import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
3
3
  import type { Repository } from './repository.js';
4
4
  import type { ProjectBlockLocator } from './block-diff-location.js';
5
5
  import type { ProjectSourceTabsFile } from './project-source-tabs.js';
6
+ import type { GetRemoteSession } from './remote-operations.js';
6
7
  type OpenTabs = (key: string, files: readonly ProjectSourceTabsFile[], rememberAs?: string) => number;
7
- export declare function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, openTabs }: {
8
+ /**
9
+ * Opens a pull request version fetched over the connection in the work tree: the host creates (or reuses) the local
10
+ * branch `pullRequestBranch(number)` at `head` and switches to it. `busy` disables the button.
11
+ */
12
+ export interface PullRequestOpenLocally {
13
+ open(number: number, head: string): unknown;
14
+ busy: boolean;
15
+ }
16
+ /** The person's GitHub connection: while it is connected, pull request contents are read over Git Smart HTTP. */
17
+ export interface PullRequestConnection {
18
+ getSession: GetRemoteSession;
19
+ connected(): boolean;
20
+ }
21
+ export declare function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, openTabs, connection, openLocally }: {
8
22
  repository: Repository;
9
23
  locale: string;
10
24
  renderBlock: ProjectBlockRenderer;
25
+ /**
26
+ * While connected, pull request contents are fetched over Git Smart HTTP (`refs/pull/<number>/head`) and compared
27
+ * from local Git objects, so private repositories can be reviewed. Without it, the public GitHub API is read.
28
+ */
29
+ connection?: PullRequestConnection;
30
+ /**
31
+ * Offers to open a pull request read over the connection in the work tree, on the branch `pr/<number>`. Pull
32
+ * requests read from the public API have no local objects, so the button is not shown for them.
33
+ */
34
+ openLocally?: PullRequestOpenLocally;
11
35
  /** Opens a pull request's project changes as tabs of the host's layout. */
12
36
  openTabs?: OpenTabs;
13
37
  /** Returns a locator for a project file whose PR head matches the open project, or `undefined`. */
@@ -2,4 +2,27 @@ import type { GitSmartHttpOptions } from './smart-http.js';
2
2
  export type RemoteOperation = 'fetch' | 'pull' | 'push';
3
3
  export type RemoteSession = NonNullable<Awaited<ReturnType<GitSmartHttpOptions['getSession']>>>;
4
4
  export type GetRemoteSession = (remote: string, write: boolean) => Promise<RemoteSession | null>;
5
+ /**
6
+ * Where a fetched pull request head is kept: outside `refs/heads`, `refs/remotes` and `refs/tags`, so Git CLI lists it
7
+ * with `git for-each-ref refs/pull` and `git log --all` but not as a branch or tag, `git fetch --prune` leaves it alone,
8
+ * and `git gc` keeps its objects. Each fetch of the same pull request replaces it.
9
+ */
10
+ export declare const pullRequestRef: (number: number) => string;
11
+ export interface PullRequestFetch {
12
+ readonly number: number;
13
+ /** The commit at GitHub's `refs/pull/<number>/head`, kept locally at `ref`. */
14
+ readonly head: string;
15
+ readonly ref: string;
16
+ /** GitHub's default branch, from the advertised `HEAD`, or `null` when it is not advertised. */
17
+ readonly defaultBranch: string | null;
18
+ /** The branches on GitHub, as `refs/remotes/origin/<name>` after this fetch. */
19
+ readonly branches: readonly string[];
20
+ }
21
+ /**
22
+ * Fetches GitHub's `refs/pull/<number>/head` together with every branch, over the connection's Git Smart HTTP, like
23
+ * `git fetch origin refs/pull/<number>/head`. The branches update `refs/remotes/origin/*` as fetch does, and the head
24
+ * is kept at `pullRequestRef(number)`. The work tree, the index, HEAD and `.git/config` are never written. The writes
25
+ * are staged first and applied together, under the same lock as fetch, pull and push.
26
+ */
27
+ export declare function runPullRequestFetch(root: FileSystemDirectoryHandle, number: number, getSession: GetRemoteSession, guard: () => void, signal?: AbortSignal): Promise<PullRequestFetch>;
5
28
  export declare function runRemoteOperation(root: FileSystemDirectoryHandle, operation: RemoteOperation, getSession: GetRemoteSession, guard: () => void, signal?: AbortSignal): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { i as __toESM, n as init_objectSpread2, r as __commonJSMin, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
2
- import { a as createTwoFilesPatch, i as isAssetName, n as assetsDirectoryParts, r as folderSettingsLimit, t as assetLimit } from "./project-asset-paths-KV715791.js";
2
+ import { i as isAssetName, n as assetsDirectoryParts, o as createProjectSourceDiff, p as createTwoFilesPatch, r as folderSettingsLimit, t as assetLimit } from "./project-asset-paths-BzwVf9HE.js";
3
3
  import { a as _OverloadYield, i as _wrapAsyncGenerator, o as _asyncIterator, r as createGitSmartHttp, t as GitSmartHttpError } from "./smart-http-BmHEkE7K.js";
4
4
  // node_modules/inherits/inherits_browser.js
5
5
  var require_inherits_browser = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -16929,6 +16929,79 @@ async function walk({ fs, dir, gitdir = join(dir, ".git"), trees, map, reduce, i
16929
16929
  throw err;
16930
16930
  }
16931
16931
  }
16932
+ /**
16933
+ * Write a ref which refers to the specified SHA-1 object id, or a symbolic ref which refers to the specified ref.
16934
+ *
16935
+ * @param {object} args
16936
+ * @param {FsClient} args.fs - a file system client
16937
+ * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path
16938
+ * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path
16939
+ * @param {string} args.ref - The name of the ref to write
16940
+ * @param {string} args.value - When `symbolic` is false, a ref or an SHA-1 object id. When true, a ref starting with `refs/`.
16941
+ * @param {boolean} [args.force = false] - Instead of throwing an error if a ref named `ref` already exists, overwrite the existing ref.
16942
+ * @param {boolean} [args.symbolic = false] - Whether the ref is symbolic or not.
16943
+ *
16944
+ * @returns {Promise<void>} Resolves successfully when filesystem operations are complete
16945
+ *
16946
+ * @example
16947
+ * await git.writeRef({
16948
+ * fs,
16949
+ * dir: '/tutorial',
16950
+ * ref: 'refs/heads/another-branch',
16951
+ * value: 'HEAD'
16952
+ * })
16953
+ * await git.writeRef({
16954
+ * fs,
16955
+ * dir: '/tutorial',
16956
+ * ref: 'HEAD',
16957
+ * value: 'refs/heads/another-branch',
16958
+ * force: true,
16959
+ * symbolic: true
16960
+ * })
16961
+ * console.log('done')
16962
+ *
16963
+ */
16964
+ async function writeRef({ fs: _fs, dir, gitdir = join(dir, ".git"), ref, value, force = false, symbolic = false }) {
16965
+ try {
16966
+ assertParameter("fs", _fs);
16967
+ assertParameter("gitdir", gitdir);
16968
+ assertParameter("ref", ref);
16969
+ assertParameter("value", value);
16970
+ const fs = new FileSystem(_fs);
16971
+ if (!isValidRef(ref, true)) throw new InvalidRefNameError(ref, import_lib.default.clean(ref));
16972
+ const updatedGitdir = await discoverGitdir({
16973
+ fsp: fs,
16974
+ dotgit: gitdir
16975
+ });
16976
+ if (!force && await GitRefManager.exists({
16977
+ fs,
16978
+ gitdir: updatedGitdir,
16979
+ ref
16980
+ })) throw new AlreadyExistsError("ref", ref);
16981
+ if (symbolic) await GitRefManager.writeSymbolicRef({
16982
+ fs,
16983
+ gitdir: updatedGitdir,
16984
+ ref,
16985
+ value
16986
+ });
16987
+ else {
16988
+ value = await GitRefManager.resolve({
16989
+ fs,
16990
+ gitdir: updatedGitdir,
16991
+ ref: value
16992
+ });
16993
+ await GitRefManager.writeRef({
16994
+ fs,
16995
+ gitdir: updatedGitdir,
16996
+ ref,
16997
+ value
16998
+ });
16999
+ }
17000
+ } catch (err) {
17001
+ err.caller = "git.writeRef";
17002
+ throw err;
17003
+ }
17004
+ }
16932
17005
  // src/filesystem.ts
16933
17006
  var GitPanelError = class extends Error {
16934
17007
  constructor(code, message = code) {
@@ -17342,8 +17415,104 @@ function _asyncGeneratorDelegate(t) {
17342
17415
  }
17343
17416
  // src/remote-operations.ts
17344
17417
  init_objectSpread2();
17345
- async function runRemoteOperation(root, operation, getSession, guard, signal) {
17418
+ var sessionCodes = [
17419
+ "SESSION_REQUIRED",
17420
+ "SESSION_UNAVAILABLE",
17421
+ "SESSION_EXPIRED",
17422
+ "REAUTH_REQUIRED",
17423
+ "INVALID_SESSION"
17424
+ ];
17425
+ async function checkRefspecs(options) {
17426
+ if ((await getConfigAll(_objectSpread2(_objectSpread2({}, options), {}, { path: "remote.origin.fetch" }))).some((spec) => !/^\+?refs\/heads\/[^:\s]+:refs\/remotes\/origin\/[^:\s]+$/u.test(spec))) throw new GitPanelError("unsupported");
17427
+ }
17428
+ /**
17429
+ * Where a fetched pull request head is kept: outside `refs/heads`, `refs/remotes` and `refs/tags`, so Git CLI lists it
17430
+ * with `git for-each-ref refs/pull` and `git log --all` but not as a branch or tag, `git fetch --prune` leaves it alone,
17431
+ * and `git gc` keeps its objects. Each fetch of the same pull request replaces it.
17432
+ */
17433
+ var pullRequestRef = (number) => `refs/pull/origin/${number}/head`;
17434
+ /**
17435
+ * Fetches GitHub's `refs/pull/<number>/head` together with every branch, over the connection's Git Smart HTTP, like
17436
+ * `git fetch origin refs/pull/<number>/head`. The branches update `refs/remotes/origin/*` as fetch does, and the head
17437
+ * is kept at `pullRequestRef(number)`. The work tree, the index, HEAD and `.git/config` are never written. The writes
17438
+ * are staged first and applied together, under the same lock as fetch, pull and push.
17439
+ */
17440
+ async function runPullRequestFetch(root, number, getSession, guard, signal) {
17346
17441
  var _globalThis$navigator;
17442
+ if (!Number.isSafeInteger(number) || number < 1) throw new GitPanelError("pullRequestNotFound");
17443
+ const execute = async () => {
17444
+ var _result$defaultBranch;
17445
+ guard();
17446
+ const transaction = createMutationFs(root, false);
17447
+ const options = {
17448
+ fs: transaction.fs,
17449
+ dir: "/",
17450
+ gitdir: "/.git",
17451
+ cache: {}
17452
+ };
17453
+ const remote = await getConfig(_objectSpread2(_objectSpread2({}, options), {}, { path: "remote.origin.url" }));
17454
+ if (typeof remote !== "string") throw new GitPanelError("session");
17455
+ await checkRefspecs(options);
17456
+ const session = await getSession(remote, false);
17457
+ if (!session) throw new GitPanelError("session");
17458
+ guard();
17459
+ const transport = createGitSmartHttp({
17460
+ remote,
17461
+ connection: session.connection,
17462
+ getSession: () => getSession(remote, false),
17463
+ signal
17464
+ });
17465
+ const source = `refs/pull/${number}/head`;
17466
+ let result;
17467
+ try {
17468
+ result = await fetch(_objectSpread2(_objectSpread2(_objectSpread2({}, options), transport), {}, {
17469
+ remote: "origin",
17470
+ remoteRef: source,
17471
+ tags: false
17472
+ }));
17473
+ } catch (error) {
17474
+ const { code, data } = error;
17475
+ if (code === "NotFoundError" && (data === null || data === void 0 ? void 0 : data.what) === source) throw new GitPanelError("pullRequestNotFound");
17476
+ throw remoteError(error);
17477
+ }
17478
+ const head = result.fetchHead;
17479
+ if (!head || !/^[0-9a-f]{40}$/u.test(head)) throw new GitPanelError("remoteFailed");
17480
+ const ref = pullRequestRef(number);
17481
+ await writeRef(_objectSpread2(_objectSpread2({}, options), {}, {
17482
+ ref,
17483
+ value: head,
17484
+ force: true
17485
+ }));
17486
+ const branches = (await listBranches(_objectSpread2(_objectSpread2({}, options), {}, { remote: "origin" }))).filter((name) => name !== "HEAD").sort();
17487
+ const advertised = ((_result$defaultBranch = result.defaultBranch) === null || _result$defaultBranch === void 0 ? void 0 : _result$defaultBranch.startsWith("refs/heads/")) ? result.defaultBranch.slice(11) : null;
17488
+ await transaction.commit(guard);
17489
+ return {
17490
+ number,
17491
+ head,
17492
+ ref,
17493
+ defaultBranch: advertised && branches.includes(advertised) ? advertised : null,
17494
+ branches
17495
+ };
17496
+ };
17497
+ return ((_globalThis$navigator = globalThis.navigator) === null || _globalThis$navigator === void 0 ? void 0 : _globalThis$navigator.locks) ? navigator.locks.request("tw-plus-git-mutation", execute) : execute();
17498
+ }
17499
+ /** Maps a failed read from GitHub to what the person can act on: the connection, access, or the network. */
17500
+ function remoteError(error) {
17501
+ if (error instanceof GitPanelError) return error;
17502
+ if (error instanceof GitSmartHttpError) {
17503
+ if (sessionCodes.includes(error.code)) return new GitPanelError("session");
17504
+ if (error.code === "FORBIDDEN") return new GitPanelError("forbidden");
17505
+ if ([
17506
+ "NETWORK_ERROR",
17507
+ "UPSTREAM_FAILURE",
17508
+ "UPSTREAM_TIMEOUT",
17509
+ "RATE_LIMITED"
17510
+ ].includes(error.code)) return new GitPanelError("network");
17511
+ }
17512
+ return new GitPanelError("remoteFailed");
17513
+ }
17514
+ async function runRemoteOperation(root, operation, getSession, guard, signal) {
17515
+ var _globalThis$navigator2;
17347
17516
  const execute = async () => {
17348
17517
  guard();
17349
17518
  const transaction = createMutationFs(root, operation === "pull");
@@ -17361,7 +17530,7 @@ async function runRemoteOperation(root, operation, getSession, guard, signal) {
17361
17530
  const trackingRemote = await config("branch." + ref + ".remote");
17362
17531
  const trackingRef = await config("branch." + ref + ".merge");
17363
17532
  if (trackingRemote && trackingRemote !== "origin" || trackingRef && !trackingRef.startsWith("refs/heads/")) throw new GitPanelError("unsupported");
17364
- if ((await getConfigAll(_objectSpread2(_objectSpread2({}, options), {}, { path: "remote.origin.fetch" }))).some((spec) => !/^\+?refs\/heads\/[^:\s]+:refs\/remotes\/origin\/[^:\s]+$/u.test(spec))) throw new GitPanelError("unsupported");
17533
+ await checkRefspecs(options);
17365
17534
  if (operation === "pull") {
17366
17535
  if ((await statusMatrix(_objectSpread2(_objectSpread2({}, options), {}, { refresh: false }))).some(([, head, worktree, stage]) => head !== worktree || head !== stage)) throw new GitPanelError("pullUnsafe");
17367
17536
  await walk(_objectSpread2(_objectSpread2({}, options), {}, {
@@ -17435,10 +17604,15 @@ async function runRemoteOperation(root, operation, getSession, guard, signal) {
17435
17604
  throw error;
17436
17605
  }
17437
17606
  };
17438
- return ((_globalThis$navigator = globalThis.navigator) === null || _globalThis$navigator === void 0 ? void 0 : _globalThis$navigator.locks) ? navigator.locks.request("tw-plus-git-mutation", execute) : execute();
17607
+ return ((_globalThis$navigator2 = globalThis.navigator) === null || _globalThis$navigator2 === void 0 ? void 0 : _globalThis$navigator2.locks) ? navigator.locks.request("tw-plus-git-mutation", execute) : execute();
17439
17608
  }
17440
17609
  // src/local-operations.ts
17441
17610
  init_objectSpread2();
17611
+ /**
17612
+ * The local branch a pull request is opened on: `pr/<number>`, a name `git check-ref-format --branch` accepts. It is
17613
+ * created at the fetched head (`pullRequestRef(number)`) and is an ordinary branch afterwards.
17614
+ */
17615
+ var pullRequestBranch = (number) => `pr/${number}`;
17442
17616
  var missing = (error) => {
17443
17617
  var _errorCode;
17444
17618
  return [
@@ -17490,6 +17664,7 @@ async function runLocalOperation(root, operation, guard) {
17490
17664
  }
17491
17665
  if (operation.kind === "createBranch") {
17492
17666
  let object;
17667
+ if (operation.reuse && operation.at === void 0) throw new GitPanelError("invalid");
17493
17668
  if (operation.at !== void 0) {
17494
17669
  if (operation.fromRemote || !/^[0-9a-f]{40}$/.test(operation.at)) throw new GitPanelError("invalid");
17495
17670
  object = await readCommit(_objectSpread2(_objectSpread2({}, options), {}, { oid: operation.at })).then(() => operation.at, () => {
@@ -17503,7 +17678,15 @@ async function runLocalOperation(root, operation, guard) {
17503
17678
  else object = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: "HEAD" })).catch(() => {
17504
17679
  throw new GitPanelError("unsupported");
17505
17680
  });
17506
- try {
17681
+ let existing;
17682
+ if (operation.reuse && (await listBranches(options)).includes(name)) {
17683
+ existing = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: local }));
17684
+ if (existing !== object) throw new GitPanelError("pullRequestBranch");
17685
+ }
17686
+ if (existing === void 0) {
17687
+ if ((await listBranches(options)).some((branch) => branch !== name && (name.startsWith(`${branch}/`) || branch.startsWith(`${name}/`)))) throw new GitPanelError("branchConflict");
17688
+ }
17689
+ if (existing === void 0) try {
17507
17690
  await branch(_objectSpread2(_objectSpread2({}, options), {}, {
17508
17691
  ref: name,
17509
17692
  object,
@@ -17583,6 +17766,8 @@ init_objectSpread2();
17583
17766
  var historyPageSize = 50;
17584
17767
  var projectSourceLimit = 16777216;
17585
17768
  var emptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
17769
+ var isSha = (value) => typeof value === "string" && /^[0-9a-f]{40}$/u.test(value);
17770
+ var validBranch = (name) => /^[^\s~^:?*[\\]+$/u.test(name) && !name.startsWith("/") && !name.endsWith("/") && !name.endsWith(".lock") && !name.includes("..") && !name.includes("//") && !name.includes("@{");
17586
17771
  var textLimit = 1048576;
17587
17772
  var inScope = (path, scope) => !scope || path === scope || path.startsWith(scope + "/");
17588
17773
  var probeLimit = 5e3;
@@ -17829,12 +18014,7 @@ async function openRepository(root) {
17829
18014
  const assertSnapshot = async (snapshot) => {
17830
18015
  if (!sameMetadata(snapshot.metadata, await metadata())) throw new GitPanelError("changed");
17831
18016
  };
17832
- const read = async (scope = "") => {
17833
- scope = pathParts(scope).join("/");
17834
- fs.clearReads();
17835
- cache = {};
17836
- const fingerprint = await probe(scope);
17837
- const before = await metadata();
18017
+ const readRefs = async () => {
17838
18018
  let head;
17839
18019
  try {
17840
18020
  head = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: "HEAD" }));
@@ -17845,6 +18025,20 @@ async function openRepository(root) {
17845
18025
  const branch = await currentBranch(options);
17846
18026
  const branches = await listBranches(options);
17847
18027
  const remoteBranches = (await listBranches(_objectSpread2(_objectSpread2({}, options), {}, { remote: "origin" }))).filter((name) => name !== "HEAD" && !branches.includes(name)).sort();
18028
+ return {
18029
+ branch: branch || void 0,
18030
+ head,
18031
+ branches,
18032
+ remoteBranches
18033
+ };
18034
+ };
18035
+ const read = async (scope = "") => {
18036
+ scope = pathParts(scope).join("/");
18037
+ fs.clearReads();
18038
+ cache = {};
18039
+ const fingerprint = await probe(scope);
18040
+ const before = await metadata();
18041
+ const { branch, head, branches, remoteBranches } = await readRefs();
17848
18042
  const commits = head ? (await log(_objectSpread2(_objectSpread2({}, options), {}, {
17849
18043
  cache,
17850
18044
  ref: head,
@@ -17897,7 +18091,7 @@ async function openRepository(root) {
17897
18091
  if (!sameMetadata(before, await metadata())) throw new GitPanelError("changed");
17898
18092
  await fs.checkReads();
17899
18093
  return {
17900
- branch: branch || void 0,
18094
+ branch,
17901
18095
  head,
17902
18096
  branches,
17903
18097
  remoteBranches,
@@ -18258,6 +18452,146 @@ async function openRepository(root) {
18258
18452
  }
18259
18453
  };
18260
18454
  };
18455
+ const hasCommit = async (oid) => {
18456
+ try {
18457
+ await readCommit(_objectSpread2(_objectSpread2({}, options), {}, {
18458
+ cache,
18459
+ oid
18460
+ }));
18461
+ return true;
18462
+ } catch (_unused5) {
18463
+ return false;
18464
+ }
18465
+ };
18466
+ /** Lists the paths that differ between two trees, skipping subtrees with the same object. */
18467
+ const treeChanges = async (before, after) => {
18468
+ const file = async (entry) => entry && await entry.type() !== "tree" ? {
18469
+ oid: await entry.oid(),
18470
+ mode: await entry.mode()
18471
+ } : null;
18472
+ return (await walk(_objectSpread2(_objectSpread2({}, options), {}, {
18473
+ cache,
18474
+ trees: [TREE({ ref: before }), TREE({ ref: after })],
18475
+ map: async (path, [a, b]) => {
18476
+ if (path === ".") return;
18477
+ if (a && b && await a.oid() === await b.oid() && await a.mode() === await b.mode()) return null;
18478
+ const change = {
18479
+ path,
18480
+ before: await file(a),
18481
+ after: await file(b)
18482
+ };
18483
+ return change.before || change.after ? change : void 0;
18484
+ }
18485
+ }))).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
18486
+ };
18487
+ const readBlobText = async (entry) => {
18488
+ if (!entry) return "{\"targets\":[]}\n";
18489
+ if (entry.mode !== 33188 && entry.mode !== 33261) throw new GitPanelError("unsupported");
18490
+ const { blob } = await readBlob(_objectSpread2(_objectSpread2({}, options), {}, {
18491
+ cache,
18492
+ oid: entry.oid
18493
+ }));
18494
+ if (blob.byteLength > projectSourceLimit) throw new GitPanelError("large");
18495
+ return new TextDecoder("utf-8", { fatal: true }).decode(blob);
18496
+ };
18497
+ const readTreeAsset = async (ref, folder, md5ext, signal) => {
18498
+ if (signal.aborted) return null;
18499
+ const source = {
18500
+ kind: "tree",
18501
+ ref
18502
+ };
18503
+ const settings = await readVersion(source, [...folder, "sb3-source.json"].join("/"), folderSettingsLimit);
18504
+ const directory = assetsDirectoryParts(settings && new TextDecoder().decode(settings));
18505
+ if (!directory || signal.aborted) return null;
18506
+ const bytes = await readVersion(source, [
18507
+ ...folder,
18508
+ ...directory,
18509
+ md5ext
18510
+ ].join("/"), assetLimit);
18511
+ return bytes && !signal.aborted ? new Blob([bytes]) : null;
18512
+ };
18513
+ const pullRequestAssets = (versions) => ({ version, md5ext, signal }) => {
18514
+ const location = versions[version];
18515
+ if (!location || !isAssetName(md5ext) || signal.aborted) return Promise.resolve(null);
18516
+ return serial(() => readTreeAsset(location.ref, pathParts(location.path).slice(0, -1), md5ext, signal)).catch(() => null);
18517
+ };
18518
+ /** Compares a fetched pull request head with its merge base, from local Git objects only. */
18519
+ const readPullRequest = async (head, base) => {
18520
+ var _added$get, _base$branch;
18521
+ if (!isSha(head) || !await hasCommit(head)) throw new GitPanelError("pullRequestNotFound");
18522
+ let sha;
18523
+ if (isSha(base.sha) && await hasCommit(base.sha)) sha = base.sha;
18524
+ else if (base.branch !== void 0 && validBranch(base.branch)) try {
18525
+ sha = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: "refs/remotes/origin/" + base.branch }));
18526
+ } catch (_unused6) {}
18527
+ if (!sha || !await hasCommit(sha)) throw new GitPanelError("baseMissing");
18528
+ const [mergeBase = null] = await findMergeBase(_objectSpread2(_objectSpread2({}, options), {}, {
18529
+ cache,
18530
+ oids: [sha, head]
18531
+ }));
18532
+ const changes = await treeChanges(mergeBase !== null && mergeBase !== void 0 ? mergeBase : emptyTree, head);
18533
+ const added = /* @__PURE__ */ new Map();
18534
+ for (const change of changes) if (!change.before && change.after) added.set(change.after.oid, [...(_added$get = added.get(change.after.oid)) !== null && _added$get !== void 0 ? _added$get : [], change]);
18535
+ const renamed = /* @__PURE__ */ new Map();
18536
+ for (const change of changes) {
18537
+ const candidates = change.before && !change.after ? added.get(change.before.oid) : void 0;
18538
+ const target = candidates === null || candidates === void 0 ? void 0 : candidates.find((candidate) => !renamed.has(candidate));
18539
+ if (target) renamed.set(target, change);
18540
+ }
18541
+ const removedByRename = new Set(renamed.values());
18542
+ const files = [];
18543
+ for (const change of changes) {
18544
+ var _from$before;
18545
+ if (removedByRename.has(change)) continue;
18546
+ const from = renamed.get(change);
18547
+ const before = (_from$before = from === null || from === void 0 ? void 0 : from.before) !== null && _from$before !== void 0 ? _from$before : change.before;
18548
+ const previousPath = from === null || from === void 0 ? void 0 : from.path;
18549
+ const status = !before ? "added" : !change.after ? "removed" : "updated";
18550
+ if (change.path.split("/").pop() !== "project.source.json") {
18551
+ files.push({
18552
+ path: change.path,
18553
+ previousPath,
18554
+ status
18555
+ });
18556
+ continue;
18557
+ }
18558
+ try {
18559
+ const patch = createProjectSourceDiff(await readBlobText(before), await readBlobText(change.after));
18560
+ const loadAsset = pullRequestAssets({
18561
+ before: before && mergeBase ? {
18562
+ ref: mergeBase,
18563
+ path: previousPath !== null && previousPath !== void 0 ? previousPath : change.path
18564
+ } : null,
18565
+ after: change.after ? {
18566
+ ref: head,
18567
+ path: change.path
18568
+ } : null
18569
+ });
18570
+ files.push({
18571
+ path: change.path,
18572
+ previousPath,
18573
+ patch,
18574
+ loadAsset
18575
+ });
18576
+ } catch (_unused7) {
18577
+ files.push({
18578
+ path: change.path,
18579
+ previousPath,
18580
+ status,
18581
+ unavailable: true
18582
+ });
18583
+ }
18584
+ }
18585
+ return {
18586
+ base: {
18587
+ ref: (_base$branch = base.branch) !== null && _base$branch !== void 0 ? _base$branch : null,
18588
+ sha
18589
+ },
18590
+ head,
18591
+ mergeBase,
18592
+ files
18593
+ };
18594
+ };
18261
18595
  const directoryAt = async (path) => {
18262
18596
  let directory = root;
18263
18597
  for (const part of pathParts(path)) directory = await directory.getDirectoryHandle(part);
@@ -18335,6 +18669,20 @@ async function openRepository(root) {
18335
18669
  throw error;
18336
18670
  }
18337
18671
  }),
18672
+ /**
18673
+ * Fetches a pull request's head (`refs/pull/<number>/head`, which also covers pull requests from forks) and every
18674
+ * branch over the connection; see `runPullRequestFetch`. Only Git objects and refs are written.
18675
+ */
18676
+ fetchPullRequest: (number, getSession, guard, signal) => serial(async () => {
18677
+ const result = await runPullRequestFetch(root, number, getSession, guard, signal);
18678
+ cache = {};
18679
+ return result;
18680
+ }),
18681
+ /**
18682
+ * Reads the changes of a fetched pull request head against a base, from local Git objects: the changed paths
18683
+ * from the merge base to the head, and each project file's diff with assets read from the same commits.
18684
+ */
18685
+ readPullRequest: (head, base) => serial(() => readPullRequest(head, base)),
18338
18686
  /** Stages, unstages or commits; see `runLocalOperation`. `guard` throws to stop before each write. */
18339
18687
  localOperation: (operation, guard) => serial(() => runLocalOperation(root, operation, guard)),
18340
18688
  /**
@@ -18350,11 +18698,19 @@ async function openRepository(root) {
18350
18698
  filepath: path
18351
18699
  }));
18352
18700
  return tree.length > 0;
18353
- } catch (_unused5) {
18701
+ } catch (_unused8) {
18354
18702
  return false;
18355
18703
  }
18356
18704
  }),
18357
18705
  read: (scope) => serial(() => read(scope)),
18706
+ /**
18707
+ * The current branch, HEAD and the branch names, read from refs only. Cheap enough to follow branch changes made
18708
+ * by other tools while nothing else is read; `read` returns the same fields.
18709
+ */
18710
+ refs: () => serial(() => {
18711
+ fs.clearReads();
18712
+ return readRefs();
18713
+ }),
18358
18714
  readDiff: (snapshot, change) => serial(() => readDiff(snapshot, change)),
18359
18715
  historyChanges: (snapshot, oid) => serial(() => historyChanges(snapshot, oid)),
18360
18716
  /**
@@ -18378,4 +18734,4 @@ async function openRepository(root) {
18378
18734
  projectAssetLoader: (snapshot, folder, comparison) => ({ version, md5ext, signal }) => !isAssetName(md5ext) || signal.aborted ? Promise.resolve(null) : serial(() => readFolderAsset(snapshot, pathParts(folder), () => comparisonSource(snapshot, comparison, version), md5ext, signal)).catch(() => null)
18379
18735
  };
18380
18736
  }
18381
- export { createReadOnlyFs as a, GitPanelError as i, openRepository as n, upstreamWalkLimit as r, historyPageSize as t };
18737
+ export { pullRequestRef as a, pullRequestBranch as i, openRepository as n, GitPanelError as o, upstreamWalkLimit as r, createReadOnlyFs as s, historyPageSize as t };
@@ -1,7 +1,10 @@
1
+ import type { ProjectSourceReviewFile } from './project-source-review.js';
1
2
  import type { LocalOperation } from './local-operations.js';
2
3
  import type { GetRemoteSession, RemoteOperation } from './remote-operations.js';
3
4
  import type { ProjectAssetLoader } from './project-assets.js';
4
5
  export { createReadOnlyFs, GitPanelError } from './filesystem.js';
6
+ export { pullRequestRef } from './remote-operations.js';
7
+ export type { PullRequestFetch } from './remote-operations.js';
5
8
  export type ChangeGroup = 'staged' | 'unstaged' | 'untracked' | 'commit';
6
9
  export interface FileChange {
7
10
  path: string;
@@ -14,6 +17,25 @@ export interface FileChange {
14
17
  /** For `commit` changes, the commit compared with its first parent. */
15
18
  commit?: string;
16
19
  }
20
+ /** A version of the pull request's base to compare with: GitHub's base commit when known, else a branch on origin. */
21
+ export interface PullRequestBase {
22
+ /** The base commit GitHub reported, used when the fetch brought it. */
23
+ sha?: string;
24
+ /** The base branch, read from `refs/remotes/origin/<branch>` when `sha` is missing or not local. */
25
+ branch?: string;
26
+ }
27
+ export interface PullRequestFiles {
28
+ /** The base that was compared: the branch it was named by, if any, and its commit. */
29
+ base: {
30
+ ref: string | null;
31
+ sha: string;
32
+ };
33
+ head: string;
34
+ /** The merge base of `base.sha` and `head`, like GitHub's three-dot comparison; `null` for unrelated histories. */
35
+ mergeBase: string | null;
36
+ /** Every changed path from the merge base to the head, with project data as a project-source diff. */
37
+ files: ProjectSourceReviewFile[];
38
+ }
17
39
  export interface Commit {
18
40
  oid: string;
19
41
  message: string;
@@ -60,6 +82,8 @@ export interface HistoryPage {
60
82
  /** Commits read per history page unless asked otherwise. */
61
83
  export declare const historyPageSize = 50;
62
84
  type Metadata = Map<string, Uint8Array | null>;
85
+ /** The branch fields of a snapshot. */
86
+ export type BranchRefs = Pick<Snapshot, 'branch' | 'head' | 'branches' | 'remoteBranches'>;
63
87
  export interface Snapshot {
64
88
  branch: string | undefined;
65
89
  head: string | null;
@@ -116,6 +140,16 @@ export declare function openRepository(root: FileSystemDirectoryHandle): Promise
116
140
  probe: (scope?: string) => Promise<string>;
117
141
  readOrigin: () => Promise<any>;
118
142
  remoteOperation: (operation: RemoteOperation, getSession: GetRemoteSession, guard: () => void, signal?: AbortSignal) => Promise<void>;
143
+ /**
144
+ * Fetches a pull request's head (`refs/pull/<number>/head`, which also covers pull requests from forks) and every
145
+ * branch over the connection; see `runPullRequestFetch`. Only Git objects and refs are written.
146
+ */
147
+ fetchPullRequest: (number: number, getSession: GetRemoteSession, guard: () => void, signal?: AbortSignal) => Promise<import("./remote-operations.js").PullRequestFetch>;
148
+ /**
149
+ * Reads the changes of a fetched pull request head against a base, from local Git objects: the changed paths
150
+ * from the merge base to the head, and each project file's diff with assets read from the same commits.
151
+ */
152
+ readPullRequest: (head: string, base: PullRequestBase) => Promise<PullRequestFiles>;
119
153
  /** Stages, unstages or commits; see `runLocalOperation`. `guard` throws to stop before each write. */
120
154
  localOperation: (operation: LocalOperation, guard: () => void) => Promise<void>;
121
155
  /**
@@ -124,6 +158,11 @@ export declare function openRepository(root: FileSystemDirectoryHandle): Promise
124
158
  */
125
159
  hasFolder: (name: string, path: string, remote?: boolean | "commit") => Promise<boolean>;
126
160
  read: (scope?: string) => Promise<Snapshot>;
161
+ /**
162
+ * The current branch, HEAD and the branch names, read from refs only. Cheap enough to follow branch changes made
163
+ * by other tools while nothing else is read; `read` returns the same fields.
164
+ */
165
+ refs: () => Promise<BranchRefs>;
127
166
  readDiff: (snapshot: Snapshot, change: FileChange) => Promise<FileDiff>;
128
167
  historyChanges: (snapshot: Snapshot, oid: string) => Promise<FileChange[]>;
129
168
  /**
@@ -1 +1 @@
1
- export { i as GitPanelError, a as createReadOnlyFs, t as historyPageSize, n as openRepository, r as upstreamWalkLimit } from "./repository-BYno-5f8.js";
1
+ export { o as GitPanelError, s as createReadOnlyFs, t as historyPageSize, n as openRepository, a as pullRequestRef, r as upstreamWalkLimit } from "./repository-ByKRBftJ.js";