authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-09 23:38:06-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-11 09:00:18-08:00
logd1ecacda43718b0a96e9fca1cd7d30f82a64a574
tree67c7d9d77e840efd93e53752def77da530e75a44
parent647449069dc5348b1dffe406b71210d7c988b881

Polish git worktree helpers after rebase

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 9 insertions(+), 16 deletions(-)

apps/desktop/src/git.test.ts-10
...@@ -404,16 +404,6 @@ describe("git integration", () => {...@@ -404,16 +404,6 @@ describe("git integration", () => {
404 await git(tmp.path, "add .");404 await git(tmp.path, "add .");
405 await git(tmp.path, "commit -m 'diverge'");405 await git(tmp.path, "commit -m 'diverge'");
406406
407 // Go back to default branch
408 const defaultBranch = (await git(tmp.path, "rev-parse --abbrev-ref HEAD")).includes(
409 "diverged",
410 )
411 ? // we're on diverged, need to find the other one
412 (await listGitBranches({ cwd: tmp.path })).branches.find(
413 (b) => !b.current && b.name !== "diverged",
414 )!.name
415 : await git(tmp.path, "rev-parse --abbrev-ref HEAD");
416
417 // Actually, let's just get back to the initial branch explicitly407 // Actually, let's just get back to the initial branch explicitly
418 const allBranches = await listGitBranches({ cwd: tmp.path });408 const allBranches = await listGitBranches({ cwd: tmp.path });
419 const initialBranch = allBranches.branches.find((b) => b.name !== "diverged")!.name;409 const initialBranch = allBranches.branches.find((b) => b.name !== "diverged")!.name;
apps/desktop/src/git.ts+9-6
...@@ -14,6 +14,10 @@ import type {...@@ -14,6 +14,10 @@ import type {
14 TerminalCommandResult,14 TerminalCommandResult,
15} from "@t3tools/contracts";15} from "@t3tools/contracts";
1616
17function escapeSingleQuotes(value: string): string {
18 return value.replace(/'/g, "'\\''");
19}
20
17export async function runTerminalCommand(21export async function runTerminalCommand(
18 input: TerminalCommandInput,22 input: TerminalCommandInput,
19): Promise<TerminalCommandResult> {23): Promise<TerminalCommandResult> {
...@@ -111,7 +115,7 @@ export async function listGitBranches(...@@ -111,7 +115,7 @@ export async function listGitBranches(
111 current: line.startsWith("* "),115 current: line.startsWith("* "),
112 isDefault: line.replace(/^[*+]\s+/, "") === defaultBranch,116 isDefault: line.replace(/^[*+]\s+/, "") === defaultBranch,
113 }))117 }))
114 .sort((a, b) => {118 .toSorted((a, b) => {
115 if (a.current !== b.current) return a.current ? -1 : 1;119 if (a.current !== b.current) return a.current ? -1 : 1;
116 if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1;120 if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1;
117 return a.name.localeCompare(b.name);121 return a.name.localeCompare(b.name);
...@@ -123,7 +127,6 @@ export async function listGitBranches(...@@ -123,7 +127,6 @@ export async function listGitBranches(
123export async function createGitWorktree(127export async function createGitWorktree(
124 input: GitCreateWorktreeInput,128 input: GitCreateWorktreeInput,
125): Promise<GitCreateWorktreeResult> {129): Promise<GitCreateWorktreeResult> {
126 const esc = (s: string) => s.replace(/'/g, "'\\''");
127 const sanitizedBranch = input.newBranch.replace(/\//g, "-");130 const sanitizedBranch = input.newBranch.replace(/\//g, "-");
128 const repoName = path.basename(input.cwd);131 const repoName = path.basename(input.cwd);
129 const worktreePath =132 const worktreePath =
...@@ -132,7 +135,7 @@ export async function createGitWorktree(...@@ -132,7 +135,7 @@ export async function createGitWorktree(
132135
133 // Create a new branch from the base branch in a new worktree136 // Create a new branch from the base branch in a new worktree
134 const result = await runTerminalCommand({137 const result = await runTerminalCommand({
135 command: `git worktree add -b '${esc(input.newBranch)}' '${esc(worktreePath)}' '${esc(input.branch)}'`,138 command: `git worktree add -b '${escapeSingleQuotes(input.newBranch)}' '${escapeSingleQuotes(worktreePath)}' '${escapeSingleQuotes(input.branch)}'`,
136 cwd: input.cwd,139 cwd: input.cwd,
137 timeoutMs: 30_000,140 timeoutMs: 30_000,
138 });141 });
...@@ -153,7 +156,7 @@ export async function removeGitWorktree(...@@ -153,7 +156,7 @@ export async function removeGitWorktree(
153 input: GitRemoveWorktreeInput,156 input: GitRemoveWorktreeInput,
154): Promise<void> {157): Promise<void> {
155 const result = await runTerminalCommand({158 const result = await runTerminalCommand({
156 command: `git worktree remove '${input.path.replace(/'/g, "'\\''")}'`,159 command: `git worktree remove '${escapeSingleQuotes(input.path)}'`,
157 cwd: input.cwd,160 cwd: input.cwd,
158 timeoutMs: 15_000,161 timeoutMs: 15_000,
159 });162 });
...@@ -167,7 +170,7 @@ export async function createGitBranch(...@@ -167,7 +170,7 @@ export async function createGitBranch(
167 input: GitCreateBranchInput,170 input: GitCreateBranchInput,
168): Promise<void> {171): Promise<void> {
169 const result = await runTerminalCommand({172 const result = await runTerminalCommand({
170 command: `git branch '${input.branch.replace(/'/g, "'\\''")}'`,173 command: `git branch '${escapeSingleQuotes(input.branch)}'`,
171 cwd: input.cwd,174 cwd: input.cwd,
172 timeoutMs: 10_000,175 timeoutMs: 10_000,
173 });176 });
...@@ -181,7 +184,7 @@ export async function checkoutGitBranch(...@@ -181,7 +184,7 @@ export async function checkoutGitBranch(
181 input: GitCheckoutInput,184 input: GitCheckoutInput,
182): Promise<void> {185): Promise<void> {
183 const result = await runTerminalCommand({186 const result = await runTerminalCommand({
184 command: `git checkout '${input.branch.replace(/'/g, "'\\''")}'`,187 command: `git checkout '${escapeSingleQuotes(input.branch)}'`,
185 cwd: input.cwd,188 cwd: input.cwd,
186 timeoutMs: 10_000,189 timeoutMs: 10_000,
187 });190 });