OXYGENHUB

Stop the Source Control refresh loop; remotes carry their CA (0.13.0) 0c26846a9a81

The watcher ignored only the top-level .oxy, so oxy writing a subrepo's
manifest while reporting status started the next refresh, and the list
flashed. Every .oxy store is now ignored (isInStore, tested). The API's
remotes() includes each remote's CA file, for the OxygenHub extension.

closes #7
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-29 12:40 UTC
Signature
✔ signed by Andy McCall
Hash
0c26846a9a81afd128493a28d0d727cbab07971edc285b69c272b94ac19c0d9a
Depends on
196392053c9f bed495a96408 c8f955891224 d2c9a5843004 f532c4266f21 f6bda77ecb9f
Depended on by
b55e0d00545e 013fefef11e3
In bookmarks
main
In tags
v0.16.0 v0.16.1 v0.17.0 v0.17.1 v0.18.0
Edits
16 atoms, 2 tree operations, 8 files

Browse the tree at this change

added src/paths.ts +11 −0
1 +
// Paths inside a checkout, without VS Code: shared by the watcher and tests.
2 +
3 +
/**
4 +
 * Whether a path relative to a repository root lies inside a `.oxy` store at
5 +
 * any depth: this repository's own, or a subrepo's, which oxy writes to
6 +
 * while it reports status (so a watcher that refreshed on those writes
7 +
 * would refresh forever).
8 +
 */
9 +
export function isInStore(rel: string): boolean {
10 +
  return rel.split(/[\\/]/).includes(".oxy");
11 +
}
added test/paths.test.mjs +12 −0
1 +
import { test } from "node:test";
2 +
import assert from "node:assert/strict";
3 +
import { isInStore } from "../out/paths.mjs";
4 +
5 +
test("every .oxy store is left out, a subrepo's too", () => {
6 +
  assert.equal(isInStore(".oxy/workspaces/a/manifest"), true);
7 +
  assert.equal(isInStore("rstar-api/.oxy/workspaces/b/manifest"), true, "a subrepo's store");
8 +
  assert.equal(isInStore("libs\\sub\\.oxy\\ops\\head"), true, "Windows separators");
9 +
  assert.equal(isInStore("src/main.c"), false);
10 +
  assert.equal(isInStore("docs/.oxy-notes.md"), false, "only a whole .oxy component");
11 +
  assert.equal(isInStore("a.oxy-conflict-1"), false);
12 +
});
modified CHANGELOG.md +5 −0
1 1
# Changelog
2 2
3 +
## 0.13.0 (2026-09-29)
4 +
5 +
- **No more flashing Source Control.** With a subrepo in the workspace, the list of changes flickered because each refresh started another. `oxy status` rewrote the subrepo's own `.oxy` files, and only the top-level `.oxy` was ignored. Every `.oxy` store is now ignored, and oxy itself (from the same day) no longer rewrites an unchanged status file.
6 +
- **Remotes carry their CA.** The API's `remotes()` includes the CA file a remote names (`remote.NAME.ca`), so the OxygenHub extension can reach a hub with a self-signed certificate.
7 +
3 8
## 0.12.0 (2026-09-29)
4 9
5 10
- **Plans from the swimlanes:** on a proposal's swimlane board in OxygenHub, drag one change onto another to plan a squash or a rebase. **Apply in VS Code** opens `vscode://oxygen.oxygen-vscode/plan?url=…&bookmark=…&kind=…&change=…&target=…`.
…
79 unchanged lines
modified package.json +2 −2
…
1 unchanged lines
2 2
  "name": "oxygen-vscode",
3 3
  "displayName": "Oxygen",
4 4
  "description": "Oxygen version control: source control view, diffs, decorations, bookmarks and history for oxy repositories",
5 -
  "version": "0.12.0",
5 +
  "version": "0.13.0",
6 6
  "publisher": "oxygen",
7 7
  "license": "MIT OR Apache-2.0",
8 8
  "icon": "assets/images/oxygen.png",
…
789 unchanged lines
798 798
    "build": "esbuild src/extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=dist/extension.js --sourcemap",
799 799
    "watch": "npm run build -- --watch",
800 800
    "typecheck": "tsc --noEmit -p .",
801 -
    "pretest": "esbuild src/oxy.ts --bundle --platform=node --format=esm --outfile=out/oxy.mjs && esbuild src/diffutil.ts --bundle --platform=node --format=esm --outfile=out/diffutil.mjs && esbuild src/markers.ts --bundle --platform=node --format=esm --outfile=out/markers.mjs && esbuild src/api.ts --bundle --platform=node --format=esm --outfile=out/api.mjs && esbuild src/commitbox.ts --bundle --platform=node --format=esm --outfile=out/commitbox.mjs && esbuild src/handoff.ts --bundle --platform=node --format=esm --outfile=out/handoff.mjs",
801 +
    "pretest": "esbuild src/oxy.ts --bundle --platform=node --format=esm --outfile=out/oxy.mjs && esbuild src/diffutil.ts --bundle --platform=node --format=esm --outfile=out/diffutil.mjs && esbuild src/markers.ts --bundle --platform=node --format=esm --outfile=out/markers.mjs && esbuild src/api.ts --bundle --platform=node --format=esm --outfile=out/api.mjs && esbuild src/commitbox.ts --bundle --platform=node --format=esm --outfile=out/commitbox.mjs && esbuild src/handoff.ts --bundle --platform=node --format=esm --outfile=out/handoff.mjs && esbuild src/paths.ts --bundle --platform=node --format=esm --outfile=out/paths.mjs",
802 802
    "test": "node --test \"test/**/*.test.mjs\"",
803 803
    "check": "npm run typecheck && npm run build && npm test",
804 804
    "package": "vsce package --no-dependencies"
…
9 unchanged lines
modified src/api.ts +2 −1
…
33 unchanged lines
34 34
  readonly status: unknown;
35 35
  readonly bookmarks: { name: string; heads: string[]; current?: boolean }[];
36 36
  readonly currentBookmark: string | undefined;
37 -
  remotes(): Promise<{ name: string; url: string }[]>;
37 +
  /** The repository's remotes, with the CA file each names for a self-signed hub. */
38 +
  remotes(): Promise<{ name: string; url: string; ca?: string }[]>;
38 39
  /** Run an oxy command in this repository and parse its JSON output. */
39 40
  json<T>(args: string[]): Promise<T>;
40 41
  refresh(): Promise<void>;
…
72 unchanged lines
modified src/extension.ts +1 −1
…
177 unchanged lines
178 178
    get currentBookmark() {
179 179
      return repo.currentBookmark?.name;
180 180
    },
181 -
    remotes: async () => (await repo.oxy.remotes(repo.root)).map((r) => ({ name: r.name, url: r.url })),
181 +
    remotes: async () => (await repo.oxy.remotes(repo.root)).map((r) => ({ name: r.name, url: r.url, ca: r.ca ?? undefined })),
182 182
    json: <T>(args: string[]) => repo.oxy.json<T>(repo.root, args),
183 183
    refresh: () => repo.refresh(),
184 184
  };
…
1419 unchanged lines
modified src/oxy.ts +2 −0
…
395 unchanged lines
396 396
export interface RemoteInfo {
397 397
  name: string;
398 398
  url: string;
399 +
  /** The CA file `remote.NAME.ca` names (a self-signed hub), if any. */
400 +
  ca?: string | null;
399 401
  bookmarks: { name: string; heads: string[] }[];
400 402
}
401 403
…
97 unchanged lines
modified src/repository.ts +5 −2
…
3 unchanged lines
4 4
5 5
import * as vscode from "vscode";
6 6
import { basename, join, relative } from "node:path";
7 +
import { isInStore } from "./paths";
7 8
import { BookmarkInfo, Oxy, PathStatus, StatusInfo, SubrepoInfo } from "./oxy";
8 9
import { baseUri } from "./contentProvider";
9 10
…
117 unchanged lines
127 128
    this.disposables.push(this.sourceControl, this.conflictsGroup, this.changesGroup, this.subreposGroup, this.statusBar, this.syncBar, this.onDidChangeStatusEmitter);
128 129
129 130
    if (vscode.workspace.getConfiguration("oxygen").get<boolean>("autoRefresh", true)) {
130 -
      // Files in the workspace, except the repository's own store.
131 +
      // Files in the workspace, except any repository store: this one's and
132 +
      // a subrepo's, which oxy itself writes while reporting status.
131 133
      const files = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(rootUri, "**/*"));
132 134
      const onFile = (uri: vscode.Uri) => {
133 -
        if (!uri.fsPath.startsWith(join(root, ".oxy"))) {
135 +
        if (!isInStore(relative(root, uri.fsPath))) {
134 136
          this.scheduleRefresh();
135 137
        }
136 138
      };
…
152 unchanged lines
289 291
    }
290 292
  }
291 293
}
294 +