Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "git-witty",
"bin": {
"git-witty": "./src/index.ts"
"git-witty": "dist/git-witty"
},
"module": "src/index.ts",
"type": "module",
Expand Down
8 changes: 6 additions & 2 deletions src/commands/add.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { join } from "node:path";
import { $ } from "bun";
import { getRepoRoot } from "../repo";
import { getRepoName, getRepoRoot } from "../repo";
import { addFolder } from "../workspace";

export async function add({ branch }: { branch: string }) {
const root = await getRepoRoot();
const repoName = await getRepoName();
const worktreePath = join(root, branch);

await $`git worktree add ${worktreePath}`;
await addFolder(root, worktreePath);
await $`git -C ${worktreePath} branch --set-upstream-to=origin/${branch}`
.quiet()
.nothrow();
await addFolder(root, repoName, worktreePath);
}
2 changes: 1 addition & 1 deletion src/commands/clone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ test("clone creates a workspace file", async () => {
await sh`git -C ${target}/.bare symbolic-ref --short HEAD`.text()
).trim();

const wsFile = Bun.file(join(tempDir, target, `${target}.code-workspace`));
const wsFile = Bun.file(join(tempDir, target, `${origin}.code-workspace`));
expect(await wsFile.exists()).toBe(true);

const workspace = await wsFile.json();
Expand Down
13 changes: 11 additions & 2 deletions src/commands/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import { addFolder } from "../workspace";
import { installHook } from "./init";

export async function clone({ url, name }: { url: string; name?: string }) {
name ??= repoNameFromUrl(url);
const repoName = repoNameFromUrl(url);
name ??= repoName;
const root = resolve(name);
const bareDir = `${root}/.bare`;

// Clone as bare repo
console.log(`Cloning ${url} into ${root}...`);
await $`git clone --bare ${url} ${bareDir}`;

// Bare clones need this to fetch all branches
await $`git -C ${bareDir} config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`;
await $`git -C ${bareDir} fetch origin`;

// Make the setup portable (can move the folder)
await $`git -C ${bareDir} config worktree.useRelativePaths true`;

// Create .git file pointing to the bare repo
await Bun.write(`${root}/.git`, "gitdir: ./.bare\n");

Expand All @@ -23,7 +31,8 @@ export async function clone({ url, name }: { url: string; name?: string }) {
// Create the first worktree
const worktreePath = resolve(root, primaryBranch);
await $`git -C ${bareDir} worktree add ${worktreePath}`;
await addFolder(root, worktreePath);
await $`git -C ${worktreePath} branch --set-upstream-to=origin/${primaryBranch}`;
await addFolder(root, repoName, worktreePath);

// Install git-witty hooks
await installHook({ bareDir });
Expand Down
5 changes: 3 additions & 2 deletions src/commands/remove.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { join } from "node:path";
import { $ } from "bun";
import { getRepoRoot } from "../repo";
import { getRepoName, getRepoRoot } from "../repo";
import { removeFolder } from "../workspace";

export async function remove({ branch }: { branch: string }) {
const root = await getRepoRoot();
const repoName = await getRepoName();
const worktreePath = join(root, branch);

await $`git worktree remove ${worktreePath}`;
await removeFolder(root, worktreePath);
await removeFolder(root, repoName, worktreePath);
}
10 changes: 9 additions & 1 deletion src/repo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dirname } from "node:path";
import { basename, dirname } from "node:path";
import { $ } from "bun";

/**
Expand All @@ -10,3 +10,11 @@ export async function getRepoRoot(): Promise<string> {
// gitCommonDir points to .bare — the repo root is its parent
return dirname(gitCommonDir);
}

/**
* Returns the repository name derived from the remote origin URL.
*/
export async function getRepoName(): Promise<string> {
const url = (await $`git remote get-url origin`.text()).trim();
return basename(url).replace(/\.git$/, "");
}
12 changes: 7 additions & 5 deletions src/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { basename, relative } from "node:path";
import { relative } from "node:path";

interface WorkspaceFolder {
path: string;
Expand All @@ -9,8 +9,8 @@ interface WorkspaceFile {
[key: string]: unknown;
}

export function workspacePath(root: string): string {
return `${root}/${basename(root)}.code-workspace`;
export function workspacePath(root: string, repoName: string): string {
return `${root}/${repoName}.code-workspace`;
}

async function read(path: string): Promise<WorkspaceFile> {
Expand All @@ -27,9 +27,10 @@ async function write(path: string, workspace: WorkspaceFile): Promise<void> {

export async function addFolder(
root: string,
repoName: string,
worktreePath: string,
): Promise<void> {
const wsPath = workspacePath(root);
const wsPath = workspacePath(root, repoName);
const workspace = await read(wsPath);

const rel = relative(root, worktreePath);
Expand All @@ -44,9 +45,10 @@ export async function addFolder(

export async function removeFolder(
root: string,
repoName: string,
worktreePath: string,
): Promise<void> {
const wsPath = workspacePath(root);
const wsPath = workspacePath(root, repoName);
const workspace = await read(wsPath);

const rel = relative(root, worktreePath);
Expand Down