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 1 month ago · 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
src/paths.ts test/paths.test.mjs CHANGELOG.md package.json src/api.ts src/extension.ts src/oxy.ts src/repository.ts
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
+