Skip to content
Open
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
204 changes: 204 additions & 0 deletions apps/server/src/git/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3247,6 +3247,210 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
}),
);

it.effect.each([
["origin/main", "origin/main", "origin"],
["origin/team/fix", "origin/team/fix", "origin"],
["origin/main", "origin/main", "fork"],
["origin/main", "origin/main", "team/fork"],
["heads/origin/main", "heads/origin/main", "team/fork"],
["plain", "feature/plain", "team/fork"],
] as const)("publishes local namespace $0 to $2", ([suggestedBranch, branch, remoteName]) =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
const remoteDir = yield* createBareRemote();
yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
const mainSha = (yield* runGit(remoteDir, ["rev-parse", "refs/heads/main"])).stdout.trim();
const publishDir = remoteName === "origin" ? remoteDir : yield* createBareRemote();
if (remoteName !== "origin") {
yield* configureRemote(repoDir, remoteName, publishDir, remoteName);
yield* runGit(repoDir, ["config", "remote.pushDefault", remoteName]);
}
if (branch.startsWith("heads/")) {
yield* runGit(repoDir, ["tag", branch]);
}
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\npublication\n");

const { manager } = yield* makeManager({
ghScenario: {
prListByHeadSelector: {
[branch]: encodeCliJson([
{
number: 7073,
title: "Preserve branch namespace",
url: "https://github.com/pingdotgg/codething-mvp/pull/7073",
baseRefName: "main",
headRefName: branch,
state: "OPEN",
isCrossRepository: remoteName !== "origin",
headRepository: {
nameWithOwner: `${remoteName === "origin" ? "pingdotgg" : "contributor"}/codething-mvp`,
},
headRepositoryOwner: {
login: remoteName === "origin" ? "pingdotgg" : "contributor",
},
},
]),
},
},
textGeneration: {
generateCommitMessage: () =>
Effect.succeed({
subject: "Fix branch publication",
body: "",
branch: suggestedBranch,
}),
},
});
const result = yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
featureBranch: true,
});

expect(result.branch).toEqual({ status: "created", name: branch });
expect(result.commit.status).toBe("created");
const unpublished = yield* manager.status({ cwd: repoDir });
const driver = yield* GitVcsDriver.GitVcsDriver;
const preview = yield* driver.getReviewDiffPreview({ cwd: repoDir });
const changes = preview.sources.find((source) => source.kind === "branch-range");
expect(
yield* driver.getReviewDiffFileContents({
cwd: repoDir,
sourceKind: "branch-range",
changeType: "change",
baseRef: changes?.baseRef ?? null,
headRef: branch,
oldPath: "README.md",
newPath: "README.md",
}),
).toMatchObject({ oldContents: "hello\n", newContents: "hello\npublication\n" });
expect(unpublished.aheadCount).toBe(1);
expect(unpublished.aheadOfDefaultCount).toBe(1);
expect(unpublished.branchChanges).toMatchObject({ insertions: 1, deletions: 0 });
expect(changes).toMatchObject({
baseRef: "refs/remotes/origin/main",
headRef: branch,
title: "Changes vs origin/main",
files: [{ path: "README.md", previousPath: null, additions: 1, deletions: 0 }],
});
expect(
(yield* driver.getReviewDiffPreview({
cwd: repoDir,
baseRef: changes?.baseRef ?? undefined,
file: { path: "README.md", previousPath: null, sourceKind: "branch-range" },
})).sources.find((source) => source.kind === "branch-range")?.files,
).toEqual(changes?.files);
expect((yield* runStackedAction(manager, { cwd: repoDir, action: "push" })).push.status).toBe(
"pushed",
);
expect((yield* runGit(repoDir, ["branch", "--show-current"])).stdout.trim()).toBe(branch);
expect((yield* runGit(remoteDir, ["rev-parse", "refs/heads/main"])).stdout.trim()).toBe(
mainSha,
);
const headSha = (yield* runGit(repoDir, ["rev-parse", "HEAD"])).stdout.trim();
expect((yield* runGit(publishDir, ["rev-parse", `refs/heads/${branch}`])).stdout.trim()).toBe(
headSha,
);
expect((yield* runGit(repoDir, ["config", `branch.${branch}.merge`])).stdout.trim()).toBe(
`refs/heads/${branch}`,
);
yield* configureVisibleRemoteUrlWithLocalRewrite(
repoDir,
remoteName,
`git@github.com:${remoteName === "origin" ? "pingdotgg" : "contributor"}/codething-mvp.git`,
publishDir,
);
const status = yield* manager.status({ cwd: repoDir });
expect(status.refName).toBe(branch);
expect(status.aheadCount).toBe(0);
expect(status.aheadOfDefaultCount).toBe(1);
expect(status.branchChanges).toMatchObject({ insertions: 1, deletions: 0 });
expect(
(yield* driver.getReviewDiffPreview({ cwd: repoDir })).sources.find(
(source) => source.kind === "branch-range",
)?.files,
).toEqual(changes?.files);
expect(status.pr).toMatchObject({ number: 7073, headRef: branch });
expect(
(yield* manager.remoteStatus({ cwd: repoDir }, { refreshUpstream: false }))?.pr,
).toEqual(status.pr);
}),
);

it.effect.each([
{
branch: "fix/name",
existingBranches: ["fix"],
expected: "fix-2/name",
},
{
branch: "heads/fix/name",
existingBranches: ["heads/fix"],
expected: "heads/fix-2/name",
},
{
branch: "team/jules/fix/name",
existingBranches: [
"team/jules",
"team/jules-2/fix",
"team/jules-2/fix-2/name",
"team/jules-2/fix-2/name-2/child",
],
expected: "team/jules-2/fix-2/name-3",
},
{
branch: "fix/name",
existingBranches: ["fix/name/child", "fix/name-2/child"],
expected: "fix/name-3",
},
])("commits on $expected when $branch has ref namespace collisions", (scenario) =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
const mainSha = (yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim();
for (const branch of scenario.existingBranches) {
yield* runGit(repoDir, ["branch", branch]);
yield* runGit(repoDir, ["tag", branch]);
}
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nnamespace-collision\n");

const { manager } = yield* makeManager({
textGeneration: {
generateCommitMessage: () =>
Effect.succeed({
subject: "Fix namespace collision",
body: "",
branch: scenario.branch,
}),
},
});
const result = yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
featureBranch: true,
});

expect(result.branch).toEqual({ status: "created", name: scenario.expected });
expect(result.commit.status).toBe("created");
expect((yield* runGit(repoDir, ["branch", "--show-current"])).stdout.trim()).toBe(
scenario.expected,
);
expect((yield* runGit(repoDir, ["status", "--porcelain"])).stdout.trim()).toBe("");
for (const branch of ["main", ...scenario.existingBranches]) {
expect((yield* runGit(repoDir, ["rev-parse", `refs/heads/${branch}`])).stdout.trim()).toBe(
mainSha,
);
}
for (const tag of scenario.existingBranches) {
expect((yield* runGit(repoDir, ["rev-parse", `refs/tags/${tag}`])).stdout.trim()).toBe(
mainSha,
);
}
}),
);

it.effect("featureBranch uses custom commit message and derives branch name", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/textGeneration/CodexTextGeneration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => {
});

expect(generated.subject).toBe("Add important change");
expect(generated.branch).toBe("feature/fix/important-system-change");
expect(generated.branch).toBe("fix/important-system-change");
}),
),
);
Expand Down
92 changes: 57 additions & 35 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ it.effect("uses stable diagnostics for every parsed non-repository command", ()
assert.deepStrictEqual(commands, [
{ args: ["rev-parse", "--git-path", "index"], lcAll: "C" },
{ args: ["status", "--porcelain=2", "--branch"], lcAll: "C" },
{ args: ["rev-parse", "--abbrev-ref", "HEAD"], lcAll: "C" },
{ args: ["branch", "--show-current"], lcAll: "C" },
{ args: ["rev-parse", "--git-common-dir"], lcAll: "C" },
]);
}).pipe(Effect.provide(layer));
Expand Down Expand Up @@ -1824,7 +1824,8 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
yield* git(cwd, ["fetch", "origin"]);
const preview = yield* driver.getReviewDiffPreview({ cwd });
const changes = preview.sources.find((source) => source.kind === "branch-range")!;
assert.strictEqual(changes.baseRef, "origin/main");
assert.strictEqual(changes.baseRef, "refs/remotes/origin/main");
assert.strictEqual(changes.title, "Changes vs origin/main");
assert.deepStrictEqual(changes.files, [
{ path: "unpushed.txt", previousPath: null, additions: 1, deletions: 0 },
]);
Expand Down Expand Up @@ -1930,7 +1931,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
includeBranchChanges: true,
});
assert.deepStrictEqual(status.branchChanges, {
baseRef: "main",
baseRef: "refs/heads/main",
insertions: 3,
deletions: 0,
});
Expand All @@ -1946,6 +1947,10 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
const refs = yield* driver.listRefs({ cwd });
assert.equal(refs.isRepo, false);
assert.deepStrictEqual(refs.refs, []);
assert.equal(
(yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false })).isRepo,
false,
);
}),
);

Expand Down Expand Up @@ -2072,35 +2077,41 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
}),
);

it.effect("reports remote divergence without reading working-tree details", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
const remote = yield* makeTmpDir("git-vcs-driver-remote-");
const { initialBranch } = yield* initRepoWithCommit(cwd);
yield* git(remote, ["init", "--bare"]);
yield* git(cwd, ["remote", "add", "origin", remote]);
yield* git(cwd, ["push", "-u", "origin", initialBranch]);
yield* git(cwd, ["checkout", "-b", "feature/remote-status"]);
yield* writeTextFile(cwd, "feature.txt", "feature\n");
yield* git(cwd, ["add", "feature.txt"]);
yield* git(cwd, ["commit", "-m", "feature commit"]);
yield* git(cwd, ["push", "-u", "origin", "feature/remote-status"]);
yield* writeTextFile(cwd, "untracked.txt", "local-only\n");

const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd);

assert.equal(status.isRepo, true);
assert.equal(status.branch, "feature/remote-status");
assert.equal(status.hasUpstream, true);
assert.equal(status.aheadCount, 0);
assert.equal(status.behindCount, 0);
assert.equal(status.aheadOfDefaultCount, 1);
assert.notProperty(status, "workingTree");
assert.notProperty(status, "hasWorkingTreeChanges");
}),
it.effect.each(["feature/remote-status", "origin/main", "heads/origin/main"])(
"reports remote divergence for $0 without reading working-tree details",
(branch) =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
const remote = yield* makeTmpDir("git-vcs-driver-remote-");
yield* initRepoWithCommit(cwd);
yield* git(cwd, ["branch", "-M", "main"]);
yield* git(remote, ["init", "--bare"]);
yield* git(cwd, ["remote", "add", "origin", remote]);
yield* git(cwd, ["push", "-u", "origin", "main"]);
yield* git(cwd, ["checkout", "-b", branch]);
if (branch.startsWith("heads/")) {
yield* git(cwd, ["tag", branch]);
}
yield* writeTextFile(cwd, "feature.txt", "feature\n");
yield* git(cwd, ["add", "feature.txt"]);
yield* git(cwd, ["commit", "-m", "feature commit"]);
yield* git(cwd, ["push", "-u", "origin", `HEAD:refs/heads/${branch}`]);
yield* writeTextFile(cwd, "untracked.txt", "local-only\n");

const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd);

assert.equal(status.isRepo, true);
assert.equal(status.branch, branch);
assert.equal(status.hasUpstream, true);
assert.equal(status.aheadCount, 0);
assert.equal(status.behindCount, 0);
assert.equal(status.aheadOfDefaultCount, 1);
assert.notProperty(status, "workingTree");
assert.notProperty(status, "hasWorkingTreeChanges");
}),
);

it.effect("reports remote status on unborn HEAD without failing", () =>
it.effect("reports remote status on unborn or detached HEAD without failing", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
const driver = yield* GitVcsDriver.GitVcsDriver;
Expand All @@ -2114,6 +2125,17 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
assert.equal(status.hasUpstream, false);
assert.equal(status.aheadCount, 0);
assert.equal(status.behindCount, 0);
yield* git(cwd, ["symbolic-ref", "HEAD", "refs/heads/heads/origin/main"]);
assert.equal(
(yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false })).branch,
"heads/origin/main",
);
yield* initRepoWithCommit(cwd);
yield* git(cwd, ["checkout", "--detach"]);
const detachedStatus = yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false });
assert.equal(detachedStatus.isRepo, true);
assert.equal(detachedStatus.branch, null);
assert.equal(detachedStatus.hasUpstream, false);
}),
);

Expand Down Expand Up @@ -3474,14 +3496,14 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
}),
);

it.effect("pushes to the requested remote instead of the primary remote", () =>
it.effect.each(["main", "origin/main"])("publishes $0 to the requested remote", (branch) =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
const originRemote = yield* makeTmpDir("git-origin-remote-");
const publishRemote = yield* makeTmpDir("git-publish-remote-");
yield* initRepoWithCommit(cwd);
const driver = yield* GitVcsDriver.GitVcsDriver;
yield* git(cwd, ["branch", "-M", "main"]);
yield* git(cwd, ["branch", "-M", branch]);
yield* git(originRemote, ["init", "--bare"]);
yield* git(publishRemote, ["init", "--bare"]);
yield* git(cwd, ["remote", "add", "origin", originRemote]);
Expand All @@ -3491,12 +3513,12 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {

assert.deepInclude(pushed, {
status: "pushed",
branch: "main",
upstreamBranch: "origin-1/main",
branch,
upstreamBranch: `origin-1/${branch}`,
setUpstream: true,
});
assert.equal(
yield* git(publishRemote, ["log", "-1", "--pretty=%s", "main"]),
yield* git(publishRemote, ["log", "-1", "--pretty=%s", `refs/heads/${branch}`]),
"initial commit",
);
const originMain = yield* driver.execute({
Expand Down
Loading
Loading