@kubohiroya/turbowarp-plus-editor-git 0.15.0 → 0.16.1

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.
Files changed (38) hide show
  1. package/README.ja.md +5 -3
  2. package/README.md +5 -3
  3. package/dist/close-button.d.ts +6 -0
  4. package/dist/file-diff-view.d.ts +3 -1
  5. package/dist/filesystem.d.ts +2 -2
  6. package/dist/history-graph-view.d.ts +40 -0
  7. package/dist/history-graph.d.ts +44 -0
  8. package/dist/index.d.ts +18 -1
  9. package/dist/index.js +697 -31
  10. package/dist/local-operations.d.ts +14 -0
  11. package/dist/messages.d.ts +52 -0
  12. package/dist/{project-comparison-LAyP_beL.js → project-comparison-1rggIJse.js} +240 -20
  13. package/dist/project-comparison.d.ts +1 -0
  14. package/dist/project-source-diff.d.ts +2 -0
  15. package/dist/project-source-diff.js +2 -2
  16. package/dist/project-source-review.d.ts +43 -0
  17. package/dist/project-source-tabs.d.ts +35 -0
  18. package/dist/pull-request-panel.d.ts +6 -1
  19. package/dist/{repository-Cx-mxT9l.js → repository-BYno-5f8.js} +491 -4
  20. package/dist/repository.d.ts +44 -0
  21. package/dist/repository.js +1 -1
  22. package/dist/style.css +31 -4
  23. package/package.json +1 -1
  24. package/src/close-button.tsx +10 -0
  25. package/src/file-diff-view.tsx +7 -3
  26. package/src/filesystem.ts +2 -1
  27. package/src/history-graph-view.tsx +233 -0
  28. package/src/history-graph.ts +90 -0
  29. package/src/index.tsx +137 -17
  30. package/src/local-operations.ts +59 -4
  31. package/src/messages.ts +26 -2
  32. package/src/project-comparison.tsx +4 -2
  33. package/src/project-source-diff.tsx +2 -0
  34. package/src/project-source-review.tsx +127 -15
  35. package/src/project-source-tabs.tsx +118 -0
  36. package/src/pull-request-panel.tsx +26 -5
  37. package/src/repository.ts +155 -1
  38. package/src/style.css +31 -4
@@ -12374,6 +12374,141 @@ async function _resolveFilepath({ fs, cache, gitdir, tree, pathArray, oid, filep
12374
12374
  throw new NotFoundError(`file or directory found at "${oid}:${filepath}"`);
12375
12375
  }
12376
12376
  /**
12377
+ *
12378
+ * @typedef {Object} ReadTreeResult - The object returned has the following schema:
12379
+ * @property {string} oid - SHA-1 object id of this tree
12380
+ * @property {TreeObject} tree - the parsed tree object
12381
+ */
12382
+ /**
12383
+ * @param {object} args
12384
+ * @param {import('../models/FileSystem.js').FileSystem} args.fs
12385
+ * @param {any} args.cache
12386
+ * @param {string} args.gitdir
12387
+ * @param {string} args.oid
12388
+ * @param {string} [args.filepath]
12389
+ *
12390
+ * @returns {Promise<ReadTreeResult>}
12391
+ */
12392
+ async function _readTree({ fs, cache, gitdir, oid, filepath = void 0 }) {
12393
+ if (filepath !== void 0) oid = await resolveFilepath({
12394
+ fs,
12395
+ cache,
12396
+ gitdir,
12397
+ oid,
12398
+ filepath
12399
+ });
12400
+ const { tree, oid: treeOid } = await resolveTree({
12401
+ fs,
12402
+ cache,
12403
+ gitdir,
12404
+ oid
12405
+ });
12406
+ return {
12407
+ oid: treeOid,
12408
+ tree: tree.entries()
12409
+ };
12410
+ }
12411
+ /**
12412
+ * Create a branch
12413
+ *
12414
+ * @param {object} args
12415
+ * @param {import('../models/FileSystem.js').FileSystem} args.fs
12416
+ * @param {string} args.gitdir
12417
+ * @param {string} args.ref
12418
+ * @param {string} [args.object = 'HEAD']
12419
+ * @param {boolean} [args.checkout = false]
12420
+ * @param {boolean} [args.force = false]
12421
+ *
12422
+ * @returns {Promise<void>} Resolves successfully when filesystem operations are complete
12423
+ *
12424
+ * @example
12425
+ * await git.branch({ dir: '$input((/))', ref: '$input((develop))' })
12426
+ * console.log('done')
12427
+ *
12428
+ */
12429
+ async function _branch({ fs, gitdir, ref, object, checkout = false, force = false }) {
12430
+ if (!isValidRef(ref, true)) throw new InvalidRefNameError(ref, import_lib.default.clean(ref));
12431
+ const fullref = `refs/heads/${ref}`;
12432
+ if (!force) {
12433
+ if (await GitRefManager.exists({
12434
+ fs,
12435
+ gitdir,
12436
+ ref: fullref
12437
+ })) throw new AlreadyExistsError("branch", ref, false);
12438
+ }
12439
+ let oid;
12440
+ try {
12441
+ oid = await GitRefManager.resolve({
12442
+ fs,
12443
+ gitdir,
12444
+ ref: object || "HEAD"
12445
+ });
12446
+ } catch (e) {
12447
+ if (!(e instanceof NotFoundError)) throw e;
12448
+ }
12449
+ if (oid) {
12450
+ oid = (await resolveCommit({
12451
+ fs,
12452
+ cache: {},
12453
+ gitdir,
12454
+ oid
12455
+ })).oid;
12456
+ await GitRefManager.writeRef({
12457
+ fs,
12458
+ gitdir,
12459
+ ref: fullref,
12460
+ value: oid
12461
+ });
12462
+ }
12463
+ if (checkout) await GitRefManager.writeSymbolicRef({
12464
+ fs,
12465
+ gitdir,
12466
+ ref: "HEAD",
12467
+ value: fullref
12468
+ });
12469
+ }
12470
+ /**
12471
+ * Create a branch
12472
+ *
12473
+ * @param {object} args
12474
+ * @param {FsClient} args.fs - a file system implementation
12475
+ * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path
12476
+ * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path
12477
+ * @param {string} args.ref - What to name the branch
12478
+ * @param {string} [args.object = 'HEAD'] - What oid to use as the start point. Accepts a symbolic ref.
12479
+ * @param {boolean} [args.checkout = false] - Update `HEAD` to point at the newly created branch
12480
+ * @param {boolean} [args.force = false] - Instead of throwing an error if a branched named `ref` already exists, overwrite the existing branch.
12481
+ *
12482
+ * @returns {Promise<void>} Resolves successfully when filesystem operations are complete
12483
+ *
12484
+ * @example
12485
+ * await git.branch({ fs, dir: '/tutorial', ref: 'develop' })
12486
+ * console.log('done')
12487
+ *
12488
+ */
12489
+ async function branch({ fs, dir, gitdir = join(dir, ".git"), ref, object, checkout = false, force = false }) {
12490
+ try {
12491
+ assertParameter("fs", fs);
12492
+ assertParameter("gitdir", gitdir);
12493
+ assertParameter("ref", ref);
12494
+ const fsp = new FileSystem(fs);
12495
+ return await _branch({
12496
+ fs: fsp,
12497
+ gitdir: await discoverGitdir({
12498
+ fsp,
12499
+ dotgit: gitdir
12500
+ }),
12501
+ ref,
12502
+ object,
12503
+ checkout,
12504
+ force
12505
+ });
12506
+ } catch (err) {
12507
+ err.caller = "git.branch";
12508
+ throw err;
12509
+ }
12510
+ }
12511
+ /**
12377
12512
  * Refuse to materialize a working-tree entry whose parent path traverses a
12378
12513
  * symbolic link. A leading component that is a symlink could otherwise redirect
12379
12514
  * a write or mkdir outside the working tree. git applies the same check: it does
@@ -12867,6 +13002,97 @@ async function batchAllSettled(operationName, tasks, onProgress, batchSize) {
12867
13002
  if (rejections.length > 0) throw new MultipleGitError(rejections);
12868
13003
  return results;
12869
13004
  }
13005
+ /**
13006
+ * Checkout a branch
13007
+ *
13008
+ * If the branch already exists it will check out that branch. Otherwise, it will create a new remote tracking branch set to track the remote branch of that name.
13009
+ *
13010
+ * @param {object} args
13011
+ * @param {FsClient} args.fs - a file system implementation
13012
+ * @param {ProgressCallback} [args.onProgress] - optional progress event callback
13013
+ * @param {PostCheckoutCallback} [args.onPostCheckout] - optional post-checkout hook callback
13014
+ * @param {string} args.dir - The [working tree](dir-vs-gitdir.md) directory path
13015
+ * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path
13016
+ * @param {string} [args.ref = 'HEAD'] - Source to checkout files from
13017
+ * @param {string[]} [args.filepaths] - Limit the checkout to the given files and directories. If `ref` is not provided, restore them from the index.
13018
+ * @param {string} [args.remote = 'origin'] - Which remote repository to use
13019
+ * @param {boolean} [args.noCheckout = false] - If true, will update HEAD but won't update the working directory
13020
+ * @param {boolean} [args.noUpdateHead] - If true, will update the working directory but won't update HEAD. Defaults to `false` when `ref` is provided, and `true` if `ref` is not provided.
13021
+ * @param {boolean} [args.dryRun = false] - If true, simulates a checkout so you can test whether it would succeed.
13022
+ * @param {boolean} [args.force = false] - If true, conflicts will be ignored and files will be overwritten regardless of local changes.
13023
+ * @param {boolean} [args.track = true] - If false, will not set the remote branch tracking information. Defaults to true.
13024
+ * @param {object} [args.cache] - a [cache](cache.md) object
13025
+ * @param {boolean} [args.nonBlocking = false] - If true, will use non-blocking file system operations to allow for better performance in certain environments (For example, in Browsers)
13026
+ * @param {number} [args.batchSize = 100] - If args.nonBlocking is true, batchSize is the number of files to process at a time avoid blocking the executing thread. The default value of 100 is a good starting point.
13027
+ *
13028
+ * @returns {Promise<void>} Resolves successfully when filesystem operations are complete
13029
+ *
13030
+ * @example
13031
+ * // switch to the main branch
13032
+ * await git.checkout({
13033
+ * fs,
13034
+ * dir: '/tutorial',
13035
+ * ref: 'main'
13036
+ * })
13037
+ * console.log('done')
13038
+ *
13039
+ * @example
13040
+ * // restore the 'docs' and 'src/docs' folders from the index, overwriting any unstaged changes
13041
+ * await git.checkout({
13042
+ * fs,
13043
+ * dir: '/tutorial',
13044
+ * force: true,
13045
+ * filepaths: ['docs', 'src/docs']
13046
+ * })
13047
+ * console.log('done')
13048
+ *
13049
+ * @example
13050
+ * // restore the 'docs' and 'src/docs' folders to the way they are in the 'develop' branch, overwriting any changes
13051
+ * await git.checkout({
13052
+ * fs,
13053
+ * dir: '/tutorial',
13054
+ * ref: 'develop',
13055
+ * noUpdateHead: true,
13056
+ * force: true,
13057
+ * filepaths: ['docs', 'src/docs']
13058
+ * })
13059
+ * console.log('done')
13060
+ */
13061
+ async function checkout({ fs, onProgress, onPostCheckout, dir, gitdir = join(dir, ".git"), remote = "origin", ref: _ref, filepaths, noCheckout = false, noUpdateHead = _ref === void 0, dryRun = false, force = false, track = true, cache = {}, nonBlocking = false, batchSize = 100 }) {
13062
+ try {
13063
+ assertParameter("fs", fs);
13064
+ assertParameter("dir", dir);
13065
+ assertParameter("gitdir", gitdir);
13066
+ const ref = _ref || "HEAD";
13067
+ const restoreFromIndex = _ref === void 0 && filepaths != null && filepaths.length > 0;
13068
+ const fsp = new FileSystem(fs);
13069
+ return await _checkout({
13070
+ fs: fsp,
13071
+ cache,
13072
+ onProgress,
13073
+ onPostCheckout,
13074
+ dir,
13075
+ gitdir: await discoverGitdir({
13076
+ fsp,
13077
+ dotgit: gitdir
13078
+ }),
13079
+ remote,
13080
+ ref,
13081
+ filepaths,
13082
+ noCheckout,
13083
+ noUpdateHead,
13084
+ dryRun,
13085
+ force,
13086
+ track,
13087
+ restoreFromIndex,
13088
+ nonBlocking,
13089
+ batchSize
13090
+ });
13091
+ } catch (err) {
13092
+ err.caller = "git.checkout";
13093
+ throw err;
13094
+ }
13095
+ }
12870
13096
  var LINEBREAKS = /^.*(\r?\n|$)/gm;
12871
13097
  function mergeFile({ branches, contents }) {
12872
13098
  const ourName = branches[1];
@@ -15996,6 +16222,50 @@ async function readCommit({ fs, dir, gitdir = join(dir, ".git"), oid, cache = {}
15996
16222
  }
15997
16223
  }
15998
16224
  /**
16225
+ *
16226
+ * @typedef {Object} ReadTreeResult - The object returned has the following schema:
16227
+ * @property {string} oid - SHA-1 object id of this tree
16228
+ * @property {TreeObject} tree - the parsed tree object
16229
+ */
16230
+ /**
16231
+ * Read a tree object directly
16232
+ *
16233
+ * @param {object} args
16234
+ * @param {FsClient} args.fs - a file system client
16235
+ * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path
16236
+ * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path
16237
+ * @param {string} args.oid - The SHA-1 object id to get. Annotated tags and commits are peeled.
16238
+ * @param {string} [args.filepath] - Don't return the object with `oid` itself, but resolve `oid` to a tree and then return the tree object at that filepath.
16239
+ * @param {object} [args.cache] - a [cache](cache.md) object
16240
+ *
16241
+ * @returns {Promise<ReadTreeResult>} Resolves successfully with a git tree object
16242
+ * @see ReadTreeResult
16243
+ * @see TreeObject
16244
+ * @see TreeEntry
16245
+ *
16246
+ */
16247
+ async function readTree({ fs, dir, gitdir = join(dir, ".git"), oid, filepath = void 0, cache = {} }) {
16248
+ try {
16249
+ assertParameter("fs", fs);
16250
+ assertParameter("gitdir", gitdir);
16251
+ assertParameter("oid", oid);
16252
+ const fsp = new FileSystem(fs);
16253
+ return await _readTree({
16254
+ fs: fsp,
16255
+ cache,
16256
+ gitdir: await discoverGitdir({
16257
+ fsp,
16258
+ dotgit: gitdir
16259
+ }),
16260
+ oid,
16261
+ filepath
16262
+ });
16263
+ } catch (err) {
16264
+ err.caller = "git.readTree";
16265
+ throw err;
16266
+ }
16267
+ }
16268
+ /**
15999
16269
  * Remove a file from the git index (aka staging area)
16000
16270
  *
16001
16271
  * Note that this does NOT delete the file in the working directory.
@@ -17186,7 +17456,8 @@ async function runLocalOperation(root, operation, guard) {
17186
17456
  var _globalThis$navigator;
17187
17457
  const execute = async () => {
17188
17458
  guard();
17189
- const transaction = createMutationFs(root, false);
17459
+ const switching = operation.kind === "switchBranch" || operation.kind === "createBranch" && operation.checkout;
17460
+ const transaction = createMutationFs(root, switching);
17190
17461
  const options = {
17191
17462
  fs: transaction.fs,
17192
17463
  dir: "/",
@@ -17210,8 +17481,57 @@ async function runLocalOperation(root, operation, guard) {
17210
17481
  "/.git/REVERT_HEAD",
17211
17482
  "/.git/rebase-merge",
17212
17483
  "/.git/rebase-apply"
17213
- ]) if (await exists(path)) throw new GitPanelError("changed");
17214
- if (operation.kind === "stage") {
17484
+ ]) if (await exists(path)) throw new GitPanelError("locked");
17485
+ if (operation.kind === "createBranch" || operation.kind === "switchBranch") {
17486
+ const name = operation.name.trim();
17487
+ const local = `refs/heads/${name}`;
17488
+ if (switching) {
17489
+ if ((await statusMatrix(_objectSpread2(_objectSpread2({}, options), {}, { refresh: false }))).some(([, head, worktree, stage]) => head !== worktree || head !== stage)) throw new GitPanelError("switchUnsafe");
17490
+ }
17491
+ if (operation.kind === "createBranch") {
17492
+ let object;
17493
+ if (operation.at !== void 0) {
17494
+ if (operation.fromRemote || !/^[0-9a-f]{40}$/.test(operation.at)) throw new GitPanelError("invalid");
17495
+ object = await readCommit(_objectSpread2(_objectSpread2({}, options), {}, { oid: operation.at })).then(() => operation.at, () => {
17496
+ throw new GitPanelError("invalid");
17497
+ });
17498
+ } else if (operation.fromRemote) try {
17499
+ object = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: `refs/remotes/origin/${name}` }));
17500
+ } catch (_unused) {
17501
+ throw new GitPanelError("invalid");
17502
+ }
17503
+ else object = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: "HEAD" })).catch(() => {
17504
+ throw new GitPanelError("unsupported");
17505
+ });
17506
+ try {
17507
+ await branch(_objectSpread2(_objectSpread2({}, options), {}, {
17508
+ ref: name,
17509
+ object,
17510
+ checkout: false
17511
+ }));
17512
+ } catch (error) {
17513
+ const code = error === null || error === void 0 ? void 0 : error.code;
17514
+ if (code === "AlreadyExistsError") throw new GitPanelError("branchExists");
17515
+ if (code === "InvalidRefNameError") throw new GitPanelError("branchName");
17516
+ throw error;
17517
+ }
17518
+ } else if (!(await listBranches(options)).includes(name)) throw new GitPanelError("invalid");
17519
+ if (switching) {
17520
+ const commit = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: local }));
17521
+ await walk(_objectSpread2(_objectSpread2({}, options), {}, {
17522
+ trees: [TREE({ ref: commit })],
17523
+ map: async (path, [entry]) => {
17524
+ if (!entry || path === ".") return true;
17525
+ const type = await entry.type();
17526
+ if (type === "tree") return true;
17527
+ if (type !== "blob" || await entry.mode() !== 33188) throw new GitPanelError("unsupported");
17528
+ return true;
17529
+ }
17530
+ }));
17531
+ await checkout(_objectSpread2(_objectSpread2({}, options), {}, { ref: name }));
17532
+ if (await currentBranch(options) !== name) throw new GitPanelError("changed");
17533
+ }
17534
+ } else if (operation.kind === "stage") {
17215
17535
  const modes = /* @__PURE__ */ new Map();
17216
17536
  await walk(_objectSpread2(_objectSpread2({}, options), {}, {
17217
17537
  trees: [STAGE()],
@@ -17259,6 +17579,8 @@ async function runLocalOperation(root, operation, guard) {
17259
17579
  }
17260
17580
  // src/repository.ts
17261
17581
  init_objectSpread2();
17582
+ /** Commits read per history page unless asked otherwise. */
17583
+ var historyPageSize = 50;
17262
17584
  var projectSourceLimit = 16777216;
17263
17585
  var emptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
17264
17586
  var textLimit = 1048576;
@@ -17522,6 +17844,7 @@ async function openRepository(root) {
17522
17844
  }
17523
17845
  const branch = await currentBranch(options);
17524
17846
  const branches = await listBranches(options);
17847
+ const remoteBranches = (await listBranches(_objectSpread2(_objectSpread2({}, options), {}, { remote: "origin" }))).filter((name) => name !== "HEAD" && !branches.includes(name)).sort();
17525
17848
  const commits = head ? (await log(_objectSpread2(_objectSpread2({}, options), {}, {
17526
17849
  cache,
17527
17850
  ref: head,
@@ -17577,6 +17900,7 @@ async function openRepository(root) {
17577
17900
  branch: branch || void 0,
17578
17901
  head,
17579
17902
  branches,
17903
+ remoteBranches,
17580
17904
  commits,
17581
17905
  changes,
17582
17906
  scope,
@@ -17585,6 +17909,147 @@ async function openRepository(root) {
17585
17909
  upstream
17586
17910
  };
17587
17911
  };
17912
+ const walks = /* @__PURE__ */ new WeakMap();
17913
+ /**
17914
+ * Reads commits reachable from the local branches, `origin/*` and HEAD, newest committer time first, like
17915
+ * `git log --all --date-order` limited to those refs. A commit is listed only after every child read so far, and
17916
+ * only once no commit still to be read is newer, keeping a page's worth read ahead, so a child stays above its
17917
+ * parents unless committer times run backwards by more than that.
17918
+ */
17919
+ const history = async ({ limit = 50, after } = {}) => {
17920
+ fs.clearReads();
17921
+ if (!(limit >= 1)) limit = 50;
17922
+ let walk = after ? walks.get(after) : void 0;
17923
+ if (after && !walk) throw new GitPanelError("changed");
17924
+ if (after) walks.delete(after);
17925
+ const readNode = async (oid) => {
17926
+ try {
17927
+ const { commit } = await readCommit(_objectSpread2(_objectSpread2({}, options), {}, {
17928
+ cache,
17929
+ oid
17930
+ }));
17931
+ return {
17932
+ commit: {
17933
+ oid,
17934
+ parents: commit.parent,
17935
+ message: commit.message.split("\n")[0],
17936
+ author: commit.author.name,
17937
+ timestamp: commit.author.timestamp,
17938
+ refs: []
17939
+ },
17940
+ time: commit.committer.timestamp,
17941
+ children: 0,
17942
+ expanded: false
17943
+ };
17944
+ } catch (error) {
17945
+ if (errorCode(error) === "NotFoundError") return null;
17946
+ throw error;
17947
+ }
17948
+ };
17949
+ if (!walk) {
17950
+ walk = {
17951
+ nodes: /* @__PURE__ */ new Map(),
17952
+ pending: /* @__PURE__ */ new Set(),
17953
+ frontier: /* @__PURE__ */ new Set()
17954
+ };
17955
+ const tips = [];
17956
+ const branch = await currentBranch(options);
17957
+ for (const name of (await listBranches(options)).sort()) tips.push({
17958
+ ref: `refs/heads/${name}`,
17959
+ label: {
17960
+ kind: "branch",
17961
+ name,
17962
+ current: name === branch
17963
+ }
17964
+ });
17965
+ for (const name of (await listBranches(_objectSpread2(_objectSpread2({}, options), {}, { remote: "origin" }))).sort()) if (name !== "HEAD") tips.push({
17966
+ ref: `refs/remotes/origin/${name}`,
17967
+ label: {
17968
+ kind: "remote",
17969
+ name: `origin/${name}`,
17970
+ current: false
17971
+ }
17972
+ });
17973
+ if (!branch) tips.push({
17974
+ ref: "HEAD",
17975
+ label: {
17976
+ kind: "head",
17977
+ name: "HEAD",
17978
+ current: true
17979
+ }
17980
+ });
17981
+ for (const { ref, label } of tips) {
17982
+ let oid;
17983
+ try {
17984
+ oid = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref }));
17985
+ } catch (error) {
17986
+ if (errorCode(error) === "NotFoundError") continue;
17987
+ throw error;
17988
+ }
17989
+ let node = walk.nodes.get(oid);
17990
+ if (!node) {
17991
+ const read = await readNode(oid);
17992
+ if (!read) continue;
17993
+ node = read;
17994
+ walk.nodes.set(oid, node);
17995
+ walk.pending.add(node);
17996
+ walk.frontier.add(node);
17997
+ }
17998
+ node.commit.refs.push(label);
17999
+ }
18000
+ }
18001
+ const { nodes, pending, frontier } = walk;
18002
+ const expand = async (node) => {
18003
+ frontier.delete(node);
18004
+ node.expanded = true;
18005
+ for (const parent of new Set(node.commit.parents)) {
18006
+ let next = nodes.get(parent);
18007
+ if (!next) {
18008
+ const read = await readNode(parent);
18009
+ if (!read) continue;
18010
+ next = read;
18011
+ nodes.set(parent, next);
18012
+ pending.add(next);
18013
+ frontier.add(next);
18014
+ }
18015
+ if (pending.has(node) && pending.has(next)) next.children++;
18016
+ }
18017
+ };
18018
+ const newer = (a, b) => a.time !== b.time ? a.time > b.time : a.commit.oid > b.commit.oid;
18019
+ const pick = (items, accept) => {
18020
+ let best = null;
18021
+ for (const node of items) if (accept(node) && (!best || newer(node, best))) best = node;
18022
+ return best;
18023
+ };
18024
+ const commits = [];
18025
+ while (commits.length < limit) {
18026
+ let ready = pick(pending, (node) => node.children === 0);
18027
+ let latest = pick(frontier, () => true);
18028
+ while (latest && (!ready || !ready.expanded || latest.time >= ready.time || pending.size < limit * 2)) {
18029
+ await expand(latest);
18030
+ ready = pick(pending, (node) => node.children === 0);
18031
+ latest = pick(frontier, () => true);
18032
+ }
18033
+ if (!ready) break;
18034
+ pending.delete(ready);
18035
+ for (const parent of new Set(ready.commit.parents)) {
18036
+ const node = nodes.get(parent);
18037
+ if (node && pending.has(node) && ready.expanded) node.children--;
18038
+ }
18039
+ commits.push(ready.commit);
18040
+ }
18041
+ await fs.checkReads();
18042
+ if (!pending.size) return {
18043
+ commits,
18044
+ cursor: null
18045
+ };
18046
+ const cursor = Object.freeze({ continues: "history" });
18047
+ walks.set(cursor, walk);
18048
+ return {
18049
+ commits,
18050
+ cursor
18051
+ };
18052
+ };
17588
18053
  const historyChanges = async (snapshot, oid) => {
17589
18054
  fs.clearReads();
17590
18055
  await assertSnapshot(snapshot);
@@ -17872,10 +18337,32 @@ async function openRepository(root) {
17872
18337
  }),
17873
18338
  /** Stages, unstages or commits; see `runLocalOperation`. `guard` throws to stop before each write. */
17874
18339
  localOperation: (operation, guard) => serial(() => runLocalOperation(root, operation, guard)),
18340
+ /**
18341
+ * Whether a local branch, `origin/<name>` with `remote`, or the commit `name` with `remote` set to `'commit'`,
18342
+ * has the folder `path` (the root always exists).
18343
+ */
18344
+ hasFolder: (name, path, remote = false) => serial(async () => {
18345
+ const ref = remote === "commit" ? name : remote ? `refs/remotes/origin/${name}` : `refs/heads/${name}`;
18346
+ if (!path) return true;
18347
+ try {
18348
+ const { tree } = await readTree(_objectSpread2(_objectSpread2({}, options), {}, {
18349
+ oid: await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref })),
18350
+ filepath: path
18351
+ }));
18352
+ return tree.length > 0;
18353
+ } catch (_unused5) {
18354
+ return false;
18355
+ }
18356
+ }),
17875
18357
  read: (scope) => serial(() => read(scope)),
17876
18358
  readDiff: (snapshot, change) => serial(() => readDiff(snapshot, change)),
17877
18359
  historyChanges: (snapshot, oid) => serial(() => historyChanges(snapshot, oid)),
17878
18360
  /**
18361
+ * Reads one page of the commits reachable from the local branches, `origin/*` and a detached HEAD, newest first
18362
+ * with each child above its parents, and the refs at each. Pass the returned `cursor` as `after` for the next page.
18363
+ */
18364
+ history: (options) => serial(() => history(options)),
18365
+ /**
17879
18366
  * Reads the assets of both versions of a selected project file, for thumbnails. Staged changes compare HEAD with
17880
18367
  * the index, unstaged ones the index with the working tree, untracked ones nothing with the working tree, and
17881
18368
  * commit changes the first parent with the commit. Each version's sb3-source.json names its assets directory.
@@ -17891,4 +18378,4 @@ async function openRepository(root) {
17891
18378
  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)
17892
18379
  };
17893
18380
  }
17894
- export { createReadOnlyFs as i, upstreamWalkLimit as n, GitPanelError as r, openRepository as t };
18381
+ export { createReadOnlyFs as a, GitPanelError as i, openRepository as n, upstreamWalkLimit as r, historyPageSize as t };
@@ -30,11 +30,42 @@ export type ProjectComparison = {
30
30
  kind: 'commit';
31
31
  commit: string;
32
32
  };
33
+ /** A ref drawn next to a commit in the history: a local branch, `origin/<name>`, or HEAD when it is detached. */
34
+ export interface HistoryRef {
35
+ kind: 'branch' | 'remote' | 'head';
36
+ /** The branch name; `origin/<name>` for remote branches; `HEAD` for a detached HEAD. */
37
+ name: string;
38
+ /** The branch HEAD is on, or the detached HEAD. */
39
+ current: boolean;
40
+ }
41
+ export interface HistoryCommit {
42
+ oid: string;
43
+ parents: string[];
44
+ /** The first line of the message. */
45
+ message: string;
46
+ author: string;
47
+ /** Author time in seconds. */
48
+ timestamp: number;
49
+ refs: HistoryRef[];
50
+ }
51
+ /** Where `history` continues. It only works with the repository that returned it. */
52
+ export type HistoryCursor = {
53
+ readonly continues: 'history';
54
+ };
55
+ export interface HistoryPage {
56
+ commits: HistoryCommit[];
57
+ /** Pass as `after` to read the next page; `null` when every commit has been read. */
58
+ cursor: HistoryCursor | null;
59
+ }
60
+ /** Commits read per history page unless asked otherwise. */
61
+ export declare const historyPageSize = 50;
33
62
  type Metadata = Map<string, Uint8Array | null>;
34
63
  export interface Snapshot {
35
64
  branch: string | undefined;
36
65
  head: string | null;
37
66
  branches: string[];
67
+ /** Branches fetched from `origin` that have no local branch of the same name yet. */
68
+ remoteBranches: string[];
38
69
  commits: Commit[];
39
70
  changes: FileChange[];
40
71
  scope: string;
@@ -87,9 +118,22 @@ export declare function openRepository(root: FileSystemDirectoryHandle): Promise
87
118
  remoteOperation: (operation: RemoteOperation, getSession: GetRemoteSession, guard: () => void, signal?: AbortSignal) => Promise<void>;
88
119
  /** Stages, unstages or commits; see `runLocalOperation`. `guard` throws to stop before each write. */
89
120
  localOperation: (operation: LocalOperation, guard: () => void) => Promise<void>;
121
+ /**
122
+ * Whether a local branch, `origin/<name>` with `remote`, or the commit `name` with `remote` set to `'commit'`,
123
+ * has the folder `path` (the root always exists).
124
+ */
125
+ hasFolder: (name: string, path: string, remote?: boolean | "commit") => Promise<boolean>;
90
126
  read: (scope?: string) => Promise<Snapshot>;
91
127
  readDiff: (snapshot: Snapshot, change: FileChange) => Promise<FileDiff>;
92
128
  historyChanges: (snapshot: Snapshot, oid: string) => Promise<FileChange[]>;
129
+ /**
130
+ * Reads one page of the commits reachable from the local branches, `origin/*` and a detached HEAD, newest first
131
+ * with each child above its parents, and the refs at each. Pass the returned `cursor` as `after` for the next page.
132
+ */
133
+ history: (options?: {
134
+ limit?: number;
135
+ after?: HistoryCursor | null;
136
+ }) => Promise<HistoryPage>;
93
137
  /**
94
138
  * Reads the assets of both versions of a selected project file, for thumbnails. Staged changes compare HEAD with
95
139
  * the index, unstaged ones the index with the working tree, untracked ones nothing with the working tree, and
@@ -1 +1 @@
1
- export { r as GitPanelError, i as createReadOnlyFs, t as openRepository, n as upstreamWalkLimit } from "./repository-Cx-mxT9l.js";
1
+ export { i as GitPanelError, a as createReadOnlyFs, t as historyPageSize, n as openRepository, r as upstreamWalkLimit } from "./repository-BYno-5f8.js";