fix: git commands fail when repo path contains spaces (#561)

* fix: use execFileSync for git commands to handle paths with spaces

execSync builds a shell command string via string interpolation, so any
path containing spaces (e.g. 'Current Projects/my-repo') gets word-split
by the shell into multiple arguments. This caused 'git worktree add' to
fail with a usage error whenever the repo was in a directory with spaces.

Switch all three git runner functions to execFileSync, which takes args
as an array and bypasses the shell entirely. Paths are passed as discrete
arguments and never subject to word-splitting or other shell expansions.

Affected files:
- worktree-manager.ts: runGit()
- git-service.ts: runGit()
- native-git-bridge.ts: gitExec()

* fix: restore pre-merge check command execution
This commit is contained in:
Jean-Dominique Stepek 2026-03-15 21:35:50 -04:00 committed by GitHub
parent 324d508eaf
commit 71cf0eef3b
3 changed files with 8 additions and 8 deletions

View file

@ -8,7 +8,7 @@
* paths, commit type inference, and the runGit shell helper.
*/
import { execSync } from "node:child_process";
import { execFileSync, execSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join, sep } from "node:path";
@ -212,7 +212,7 @@ function filterGitSvnNoise(message: string): string {
*/
export function runGit(basePath: string, args: string[], options: { allowFailure?: boolean; input?: string } = {}): string {
try {
return execSync(`git ${args.join(" ")}`, {
return execFileSync("git", args, {
cwd: basePath,
stdio: [options.input != null ? "pipe" : "ignore", "pipe", "pipe"],
encoding: "utf-8",
@ -452,7 +452,7 @@ export class GitServiceImpl {
} else {
// Auto-detect: look for package.json with a test script
try {
const pkg = execSync("cat package.json", { cwd: this.basePath, encoding: "utf-8" });
const pkg = execFileSync("cat", ["package.json"], { cwd: this.basePath, encoding: "utf-8" });
const parsed = JSON.parse(pkg);
if (parsed.scripts?.test) {
command = "npm test";

View file

@ -5,7 +5,7 @@
// Only READ operations are native — WRITE operations (commit, merge, checkout, push)
// remain as execSync calls in git-service.ts.
import { execSync } from "node:child_process";
import { execFileSync } from "node:child_process";
/** Env overlay that suppresses interactive git credential prompts and git-svn noise. */
const GIT_NO_PROMPT_ENV = {
@ -44,10 +44,10 @@ function loadNative(): typeof nativeModule {
return nativeModule;
}
/** Run a git command via execSync. Returns trimmed stdout. */
/** Run a git command via execFileSync. Returns trimmed stdout. */
function gitExec(basePath: string, args: string[], allowFailure = false): string {
try {
return execSync(`git ${args.join(" ")}`, {
return execFileSync("git", args, {
cwd: basePath,
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf-8",

View file

@ -16,7 +16,7 @@
*/
import { existsSync, mkdirSync, realpathSync } from "node:fs";
import { execSync } from "node:child_process";
import { execFileSync } from "node:child_process";
import { join, resolve, sep } from "node:path";
// ─── Types ─────────────────────────────────────────────────────────────────
@ -69,7 +69,7 @@ function filterGitSvnNoise(message: string): string {
function runGit(cwd: string, args: string[], opts: { allowFailure?: boolean } = {}): string {
try {
return execSync(`git ${args.join(" ")}`, {
return execFileSync("git", args, {
cwd,
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf-8",