Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/actions/setup-jj/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Setup Jujutsu
description: Install the jj CLI from the official jj-vcs release artifacts.

inputs:
version:
description: Jujutsu release tag to install.
required: false
default: v0.46.0

runs:
using: composite
steps:
- name: Install jj
shell: bash
env:
JJ_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail

case "${JJ_VERSION}-${RUNNER_OS}-${RUNNER_ARCH}" in
v0.46.0-Linux-X64)
asset="jj-${JJ_VERSION}-x86_64-unknown-linux-musl.tar.gz"
sha256="fce0271158e665ceb82dc66c5eb95b1728649094e9133074f3b5baece345fc08"
;;
*)
echo "No pinned jj checksum for ${JJ_VERSION}-${RUNNER_OS}-${RUNNER_ARCH}" >&2
exit 1
;;
esac

install_dir="${RUNNER_TEMP}/jj-${JJ_VERSION}"
mkdir -p "$install_dir"

curl -fsSL \
"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/jj-vcs/jj/releases/download/${JJ_VERSION}/${asset}" \
-o "${RUNNER_TEMP}/${asset}"
echo "${sha256} ${RUNNER_TEMP}/${asset}" | sha256sum --check --status
tar -xzf "${RUNNER_TEMP}/${asset}" -C "$install_dir"

chmod +x "${install_dir}/jj"
echo "$install_dir" >> "$GITHUB_PATH"
"${install_dir}/jj" --version
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ jobs:
if: steps.changes.outputs.test_server != 'false'
uses: ./.github/actions/setup-ci-deps

# The jj suites in apps/server/src/jj and apps/server/src/vcs skip themselves when jj is absent,
# so this step is what makes them actually run.
- uses: ./.github/actions/setup-jj
if: steps.changes.outputs.test_server != 'false'

# No Electron setup here: `t3` (apps/server) has no Electron dependency
# and none of its tests touch the runtime. Only the non-server `test`
# job, which covers @t3tools/desktop, needs the download.
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ jobs:
- name: Install Chromium for server browser integration tests
run: node apps/server/node_modules/playwright-core/cli.js install --with-deps --only-shell chromium

- uses: ./.github/actions/setup-jj

- name: Test
run: vp run --filter t3 test --shard ${{ matrix.shard }}/${{ strategy.job-total }}

Expand Down
32 changes: 27 additions & 5 deletions apps/server/src/checkpointing/CheckpointStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ function buildLargeText(lineCount = 5_000): string {
}

it.layer(layerTest)("CheckpointStore.layer", (it) => {
describe("isGitRepository", () => {
it.effect("returns false when no Git repository is detected", () =>
describe("supportsCheckpoints", () => {
it.effect("returns false when no repository is detected", () =>
Effect.gen(function* () {
const tmp = yield* makeTmpDir();
const checkpointStore = yield* CheckpointStore.CheckpointStore;

expect(yield* checkpointStore.isGitRepository(tmp)).toBe(false);
expect(yield* checkpointStore.supportsCheckpoints(tmp)).toBe(false);
}),
);

Expand All @@ -118,7 +118,7 @@ it.layer(layerTest)("CheckpointStore.layer", (it) => {
yield* initRepoWithCommit(tmp);
const checkpointStore = yield* CheckpointStore.CheckpointStore;

expect(yield* checkpointStore.isGitRepository(tmp)).toBe(true);
expect(yield* checkpointStore.supportsCheckpoints(tmp)).toBe(true);
}),
);
});
Expand All @@ -131,7 +131,29 @@ it.layer(layerTest)("CheckpointStore.layer", (it) => {
const nested = NodePath.join(tmp, "packages", "nested");
yield* fileSystem.makeDirectory(nested, { recursive: true });
const checkpointStore = yield* CheckpointStore.CheckpointStore;
expect(yield* checkpointStore.isGitRepository(nested)).toBe(true);
expect(yield* checkpointStore.supportsCheckpoints(nested)).toBe(true);
}),
);

it.effect("resolves checkpoint operations without overriding the registry selection", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
yield* initRepoWithCommit(cwd);
const registry = yield* VcsDriverRegistry.VcsDriverRegistry;
const resolutions: VcsDriverRegistry.VcsDriverResolveInput[] = [];
const store = yield* CheckpointStore.make.pipe(
Effect.provideService(VcsDriverRegistry.VcsDriverRegistry, {
...registry,
resolve: (input) => {
resolutions.push(input);
return registry.resolve(input);
},
}),
);
const checkpointRef = checkpointRefForThreadTurn(ThreadId.make("registry-selection"), 0);
yield* store.captureCheckpoint({ cwd, checkpointRef });
expect(resolutions).toEqual([{ cwd }]);
expect(yield* store.hasCheckpointRef({ cwd, checkpointRef })).toBe(true);
}),
);
describe("diffCheckpoints", () => {
Expand Down
22 changes: 16 additions & 6 deletions apps/server/src/checkpointing/CheckpointStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as Layer from "effect/Layer";
import type { CheckpointStoreError } from "./Errors.ts";
import type { VcsCheckpointOps } from "../vcs/VcsDriver.ts";
import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
import { JJ_REPOSITORY_DETECTION_ENABLED } from "../vcs/JjRepo.ts";

export interface CaptureCheckpointInput {
readonly cwd: string;
Expand Down Expand Up @@ -43,7 +44,7 @@ export interface DeleteCheckpointRefsInput {
export class CheckpointStore extends Context.Service<
CheckpointStore,
{
readonly isGitRepository: (cwd: string) => Effect.Effect<boolean, CheckpointStoreError>;
readonly supportsCheckpoints: (cwd: string) => Effect.Effect<boolean, CheckpointStoreError>;

readonly captureCheckpoint: (
input: CaptureCheckpointInput,
Expand Down Expand Up @@ -94,10 +95,19 @@ export const make = Effect.gen(function* () {
return handle.driver.checkpoints satisfies VcsCheckpointOps;
});

const isGitRepository: CheckpointStore["Service"]["isGitRepository"] = (cwd) =>
vcsRegistry
.detect({ cwd, requestedKind: "git" })
.pipe(Effect.map((repository) => repository !== null));
const supportsCheckpoints: CheckpointStore["Service"]["supportsCheckpoints"] = Effect.fn(
"CheckpointStore.supportsCheckpoints",
)(function* (cwd: string) {
const handle = yield* vcsRegistry.detect({
cwd,
...(!JJ_REPOSITORY_DETECTION_ENABLED ? { requestedKind: "git" as const } : {}),
});
if (!handle?.driver.checkpoints) {
return false;
}
const usable = handle.driver.checkpointsUsable;
return usable ? yield* usable(cwd) : true;
});

const captureCheckpoint: CheckpointStore["Service"]["captureCheckpoint"] = Effect.fn(
"captureCheckpoint",
Expand Down Expand Up @@ -145,7 +155,7 @@ export const make = Effect.gen(function* () {
});

return CheckpointStore.of({
isGitRepository,
supportsCheckpoints,
captureCheckpoint,
hasCheckpointRef,
restoreCheckpoint,
Expand Down
Loading
Loading