@solaqua/gji 0.12.4 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/application/worktree/catalog.d.ts +31 -0
- package/dist/application/worktree/catalog.js +88 -0
- package/dist/application/worktree/context-card.d.ts +14 -0
- package/dist/application/worktree/context-card.js +12 -0
- package/dist/{go-resolver.d.ts → application/worktree/go-resolution.d.ts} +12 -4
- package/dist/{go-resolver.js → application/worktree/go-resolution.js} +46 -44
- package/dist/application/worktree/read-models.d.ts +31 -0
- package/dist/application/worktree/sources.d.ts +7 -0
- package/dist/{worktree-sources.js → application/worktree/sources.js} +12 -24
- package/dist/{back.d.ts → cli/commands/back.d.ts} +3 -1
- package/dist/{back.js → cli/commands/back.js} +8 -6
- package/dist/{clean.d.ts → cli/commands/clean.d.ts} +4 -2
- package/dist/{clean.js → cli/commands/clean.js} +32 -24
- package/dist/{completion.js → cli/commands/completion.js} +2 -2
- package/dist/{config-command.d.ts → cli/commands/config-command.d.ts} +2 -0
- package/dist/{config-command.js → cli/commands/config-command.js} +7 -5
- package/dist/{doctor.d.ts → cli/commands/doctor.d.ts} +2 -0
- package/dist/{doctor.js → cli/commands/doctor.js} +23 -25
- package/dist/{done.d.ts → cli/commands/done.d.ts} +2 -0
- package/dist/{done.js → cli/commands/done.js} +26 -23
- package/dist/{go.d.ts → cli/commands/go.d.ts} +6 -1
- package/dist/{go.js → cli/commands/go.js} +57 -41
- package/dist/{history-command.d.ts → cli/commands/history-command.d.ts} +2 -0
- package/dist/{history-command.js → cli/commands/history-command.js} +3 -1
- package/dist/{hub.d.ts → cli/commands/hub.d.ts} +7 -3
- package/dist/{hub.js → cli/commands/hub.js} +23 -33
- package/dist/{init.d.ts → cli/commands/init.d.ts} +3 -1
- package/dist/{init.js → cli/commands/init.js} +37 -34
- package/dist/{ls.d.ts → cli/commands/ls.d.ts} +4 -2
- package/dist/{ls.js → cli/commands/ls.js} +8 -5
- package/dist/{new.d.ts → cli/commands/new.d.ts} +6 -2
- package/dist/{new.js → cli/commands/new.js} +237 -109
- package/dist/{open.d.ts → cli/commands/open.d.ts} +4 -2
- package/dist/{open.js → cli/commands/open.js} +19 -12
- package/dist/{pr-open.d.ts → cli/commands/pr-open.d.ts} +4 -2
- package/dist/{pr-open.js → cli/commands/pr-open.js} +20 -18
- package/dist/{pr.d.ts → cli/commands/pr.d.ts} +5 -3
- package/dist/{pr.js → cli/commands/pr.js} +34 -41
- package/dist/{remove.d.ts → cli/commands/remove.d.ts} +4 -2
- package/dist/{remove.js → cli/commands/remove.js} +23 -17
- package/dist/{root.d.ts → cli/commands/root.d.ts} +2 -0
- package/dist/{root.js → cli/commands/root.js} +4 -2
- package/dist/{run-hook.d.ts → cli/commands/run-hook.d.ts} +2 -0
- package/dist/{run-hook.js → cli/commands/run-hook.js} +7 -4
- package/dist/{status.d.ts → cli/commands/status.d.ts} +2 -0
- package/dist/{status.js → cli/commands/status.js} +10 -7
- package/dist/{sync-files-command.d.ts → cli/commands/sync-files-command.d.ts} +2 -0
- package/dist/{sync-files-command.js → cli/commands/sync-files-command.js} +11 -9
- package/dist/{sync.d.ts → cli/commands/sync.d.ts} +2 -0
- package/dist/{sync.js → cli/commands/sync.js} +7 -4
- package/dist/{task-command.d.ts → cli/commands/task-command.d.ts} +2 -0
- package/dist/{task-command.js → cli/commands/task-command.js} +4 -2
- package/dist/{undo.d.ts → cli/commands/undo.d.ts} +8 -6
- package/dist/{undo.js → cli/commands/undo.js} +44 -34
- package/dist/{warp.d.ts → cli/commands/warp.d.ts} +5 -2
- package/dist/{warp.js → cli/commands/warp.js} +20 -10
- package/dist/cli/dependencies.d.ts +114 -0
- package/dist/cli/dependencies.js +118 -0
- package/dist/{cli.d.ts → cli/program.d.ts} +4 -2
- package/dist/{cli.js → cli/program.js} +108 -124
- package/dist/domain/repository/context.d.ts +6 -0
- package/dist/domain/repository/context.js +1 -0
- package/dist/domain/repository/registry.d.ts +5 -0
- package/dist/domain/repository/registry.js +1 -0
- package/dist/domain/shared/concurrency.d.ts +1 -0
- package/dist/domain/shared/concurrency.js +15 -0
- package/dist/domain/worktree/matching.d.ts +4 -0
- package/dist/domain/worktree/matching.js +79 -0
- package/dist/domain/worktree/policy.d.ts +2 -0
- package/dist/domain/worktree/policy.js +58 -0
- package/dist/domain/worktree/pr-reference.d.ts +1 -0
- package/dist/domain/worktree/pr-reference.js +9 -0
- package/dist/{worktree-source.d.ts → domain/worktree/source.d.ts} +1 -1
- package/dist/domain/worktree/source.js +1 -0
- package/dist/domain/worktree/types.d.ts +5 -0
- package/dist/domain/worktree/types.js +1 -0
- package/dist/gji-bundle.mjs +7100 -6265
- package/dist/index.js +3 -3
- package/dist/{dependency-bootstrap.d.ts → infrastructure/bootstrap/dependency-bootstrap.d.ts} +3 -30
- package/dist/{dependency-bootstrap.js → infrastructure/bootstrap/dependency-bootstrap.js} +4 -4
- package/dist/infrastructure/git/health.d.ts +9 -0
- package/dist/infrastructure/git/health.js +44 -0
- package/dist/infrastructure/git/refs.d.ts +6 -0
- package/dist/{git.js → infrastructure/git/refs.js} +1 -51
- package/dist/infrastructure/git/runner.d.ts +2 -0
- package/dist/infrastructure/git/runner.js +16 -0
- package/dist/{editor.d.ts → infrastructure/integrations/editor.d.ts} +2 -6
- package/dist/{pull-requests.d.ts → infrastructure/integrations/pull-requests.d.ts} +3 -11
- package/dist/{config.d.ts → infrastructure/persistence/config.d.ts} +3 -17
- package/dist/{config.js → infrastructure/persistence/config.js} +2 -17
- package/dist/{history.d.ts → infrastructure/persistence/history.d.ts} +2 -5
- package/dist/{task.js → infrastructure/persistence/task.js} +1 -1
- package/dist/infrastructure/process/command-runner.d.ts +3 -0
- package/dist/{hooks.d.ts → infrastructure/process/hooks.d.ts} +2 -12
- package/dist/infrastructure/repository/adapters.d.ts +7 -0
- package/dist/infrastructure/repository/adapters.js +23 -0
- package/dist/infrastructure/repository/context.d.ts +3 -0
- package/dist/infrastructure/repository/context.js +20 -0
- package/dist/infrastructure/repository/refs.d.ts +3 -0
- package/dist/infrastructure/repository/refs.js +37 -0
- package/dist/{repo-registry.d.ts → infrastructure/repository/registry.d.ts} +1 -5
- package/dist/{repo-registry.js → infrastructure/repository/registry.js} +1 -1
- package/dist/infrastructure/repository/worktrees.d.ts +2 -0
- package/dist/infrastructure/repository/worktrees.js +48 -0
- package/dist/{uv-validation.d.ts → infrastructure/validation/uv.d.ts} +1 -1
- package/dist/{uv-validation.js → infrastructure/validation/uv.js} +1 -1
- package/dist/infrastructure/worktree/bootstrap.d.ts +3 -0
- package/dist/{worktree-bootstrap.js → infrastructure/worktree/bootstrap.js} +7 -10
- package/dist/infrastructure/worktree/info.d.ts +6 -0
- package/dist/infrastructure/worktree/info.js +61 -0
- package/dist/{worktree-management.d.ts → infrastructure/worktree/lifecycle.d.ts} +0 -6
- package/dist/{worktree-management.js → infrastructure/worktree/lifecycle.js} +1 -9
- package/dist/ports/bootstrap.d.ts +61 -0
- package/dist/ports/bootstrap.js +1 -0
- package/dist/ports/config.d.ts +21 -0
- package/dist/ports/config.js +17 -0
- package/dist/ports/editor.d.ts +6 -0
- package/dist/ports/editor.js +1 -0
- package/dist/ports/git.d.ts +8 -0
- package/dist/ports/git.js +1 -0
- package/dist/ports/history.d.ts +5 -0
- package/dist/ports/history.js +1 -0
- package/dist/ports/hooks.d.ts +12 -0
- package/dist/ports/hooks.js +1 -0
- package/dist/{command-runner.d.ts → ports/process.d.ts} +0 -1
- package/dist/ports/process.js +1 -0
- package/dist/ports/pull-requests.d.ts +11 -0
- package/dist/ports/pull-requests.js +1 -0
- package/dist/ports/repository.d.ts +17 -0
- package/dist/ports/repository.js +1 -0
- package/dist/{bootstrap-output.d.ts → presentation/bootstrap/output.d.ts} +1 -1
- package/dist/presentation/bootstrap/preview.d.ts +2 -0
- package/dist/{bootstrap-preview.js → presentation/bootstrap/preview.js} +0 -4
- package/dist/{shell-completion.js → presentation/shell/completion.js} +5 -4
- package/dist/presentation/terminal/context-card.d.ts +2 -0
- package/dist/{context-card.js → presentation/terminal/context-card.js} +3 -12
- package/dist/presentation/worktree/format.d.ts +4 -0
- package/dist/presentation/worktree/format.js +41 -0
- package/dist/{worktree-picker.d.ts → presentation/worktree/picker.d.ts} +7 -11
- package/dist/{worktree-picker.js → presentation/worktree/picker.js} +36 -191
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +1 -1
- package/man/man1/gji-done.1 +1 -1
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +6 -3
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +1 -1
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +2 -2
- package/package.json +1 -1
- package/dist/bootstrap-preview.d.ts +0 -9
- package/dist/context-card.d.ts +0 -1
- package/dist/format-bytes.d.ts +0 -1
- package/dist/format-bytes.js +0 -14
- package/dist/git.d.ts +0 -18
- package/dist/repo.d.ts +0 -20
- package/dist/repo.js +0 -161
- package/dist/worktree-bootstrap.d.ts +0 -24
- package/dist/worktree-info.d.ts +0 -37
- package/dist/worktree-info.js +0 -123
- package/dist/worktree-sources.d.ts +0 -4
- /package/dist/{worktree-source.js → application/worktree/read-models.js} +0 -0
- /package/dist/{completion.d.ts → cli/commands/completion.d.ts} +0 -0
- /package/dist/{headless.d.ts → cli/runtime/headless.d.ts} +0 -0
- /package/dist/{headless.js → cli/runtime/headless.js} +0 -0
- /package/dist/{paths.d.ts → domain/shared/paths.d.ts} +0 -0
- /package/dist/{paths.js → domain/shared/paths.js} +0 -0
- /package/dist/{file-sync.d.ts → infrastructure/filesystem/file-sync.d.ts} +0 -0
- /package/dist/{file-sync.js → infrastructure/filesystem/file-sync.js} +0 -0
- /package/dist/{fs-utils.d.ts → infrastructure/filesystem/fs-utils.d.ts} +0 -0
- /package/dist/{fs-utils.js → infrastructure/filesystem/fs-utils.js} +0 -0
- /package/dist/{safe-destination.d.ts → infrastructure/filesystem/safe-destination.d.ts} +0 -0
- /package/dist/{safe-destination.js → infrastructure/filesystem/safe-destination.js} +0 -0
- /package/dist/{browser.d.ts → infrastructure/integrations/browser.d.ts} +0 -0
- /package/dist/{browser.js → infrastructure/integrations/browser.js} +0 -0
- /package/dist/{editor.js → infrastructure/integrations/editor.js} +0 -0
- /package/dist/{pull-requests.js → infrastructure/integrations/pull-requests.js} +0 -0
- /package/dist/{history.js → infrastructure/persistence/history.js} +0 -0
- /package/dist/{slots.d.ts → infrastructure/persistence/slots.d.ts} +0 -0
- /package/dist/{slots.js → infrastructure/persistence/slots.js} +0 -0
- /package/dist/{task.d.ts → infrastructure/persistence/task.d.ts} +0 -0
- /package/dist/{command-runner.js → infrastructure/process/command-runner.js} +0 -0
- /package/dist/{hooks.js → infrastructure/process/hooks.js} +0 -0
- /package/dist/{bootstrap-output.js → presentation/bootstrap/output.js} +0 -0
- /package/dist/{conflict.d.ts → presentation/prompts/path-conflict.d.ts} +0 -0
- /package/dist/{conflict.js → presentation/prompts/path-conflict.js} +0 -0
- /package/dist/{shell-completion.d.ts → presentation/shell/completion.d.ts} +0 -0
- /package/dist/{shell-handoff.d.ts → presentation/shell/handoff.d.ts} +0 -0
- /package/dist/{shell-handoff.js → presentation/shell/handoff.js} +0 -0
- /package/dist/{shell-setup.d.ts → presentation/shell/setup.d.ts} +0 -0
- /package/dist/{shell-setup.js → presentation/shell/setup.js} +0 -0
- /package/dist/{shell.d.ts → presentation/shell/shell.d.ts} +0 -0
- /package/dist/{shell.js → presentation/shell/shell.js} +0 -0
- /package/dist/{navigation-output.d.ts → presentation/terminal/navigation.d.ts} +0 -0
- /package/dist/{navigation-output.js → presentation/terminal/navigation.js} +0 -0
- /package/dist/{terminal-text.d.ts → presentation/terminal/text.d.ts} +0 -0
- /package/dist/{terminal-text.js → presentation/terminal/text.js} +0 -0
- /package/dist/{worktree-prompts.d.ts → presentation/worktree/prompts.d.ts} +0 -0
- /package/dist/{worktree-prompts.js → presentation/worktree/prompts.js} +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
1
|
+
import type { CommandRunner as BootstrapCommandRunner } from "../../ports/process.js";
|
|
2
|
+
import { type PathConflictChoice } from "../../presentation/prompts/path-conflict.js";
|
|
3
|
+
import { type CliRuntime } from "../dependencies.js";
|
|
3
4
|
export type { PathConflictChoice };
|
|
4
5
|
export type NewWorktreeMode = "create" | "checkout" | "track";
|
|
5
6
|
export interface NewCommandOptions {
|
|
6
7
|
branch?: string;
|
|
8
|
+
branchOnly?: boolean;
|
|
7
9
|
copy?: boolean;
|
|
8
10
|
cwd: string;
|
|
9
11
|
detached?: boolean;
|
|
@@ -22,7 +24,9 @@ export interface NewCommandOptions {
|
|
|
22
24
|
stderr: (chunk: string) => void;
|
|
23
25
|
stdout: (chunk: string) => void;
|
|
24
26
|
task?: string;
|
|
27
|
+
runtime?: NewRuntime;
|
|
25
28
|
}
|
|
29
|
+
type NewRuntime = CliRuntime<"bootstrap" | "config" | "git" | "history" | "integrations" | "repositoryContext" | "tasks">;
|
|
26
30
|
export interface NewCommandDependencies {
|
|
27
31
|
createBranchPlaceholder: () => string;
|
|
28
32
|
promptForBranch: (placeholder: string) => Promise<string | null>;
|
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { access, mkdir } from "node:fs/promises";
|
|
3
2
|
import { dirname, resolve } from "node:path";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
3
|
import { confirm, isCancel, text } from "@clack/prompts";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { pathExists, promptForPathConflict, } from "
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { isHeadless } from "
|
|
14
|
-
import { recordWorktreeUsage } from "./history.js";
|
|
15
|
-
import { createNavigationRepository, createNavigationTarget, } from "./navigation-output.js";
|
|
16
|
-
import { detectRepository, resolveWorktreePath, validateBranchName, } from "./repo.js";
|
|
17
|
-
import { writeShellOutput } from "./shell-handoff.js";
|
|
18
|
-
import { writeTask } from "./task.js";
|
|
19
|
-
import { bootstrapWorktree } from "./worktree-bootstrap.js";
|
|
20
|
-
const execFileAsync = promisify(execFile);
|
|
4
|
+
import { resolveWorktreePath, validateBranchName, } from "../../domain/worktree/policy.js";
|
|
5
|
+
import { createBootstrapReporter } from "../../presentation/bootstrap/output.js";
|
|
6
|
+
import { formatDependencyBootstrapPreview } from "../../presentation/bootstrap/preview.js";
|
|
7
|
+
import { pathExists, promptForPathConflict, } from "../../presentation/prompts/path-conflict.js";
|
|
8
|
+
import { writeShellOutput } from "../../presentation/shell/handoff.js";
|
|
9
|
+
import { createNavigationRepository, createNavigationTarget, } from "../../presentation/terminal/navigation.js";
|
|
10
|
+
import { defaultCliDependencies, } from "../dependencies.js";
|
|
11
|
+
import { isHeadless } from "../runtime/headless.js";
|
|
21
12
|
const NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
22
13
|
export function createNewCommand(dependencies = {}) {
|
|
23
14
|
const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
|
|
24
15
|
const promptForBranch = dependencies.promptForBranch ?? defaultPromptForBranch;
|
|
25
16
|
const promptForFetchFailure = dependencies.promptForFetchFailure ?? defaultPromptForFetchFailure;
|
|
26
17
|
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
27
|
-
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
28
18
|
return async function runNewCommand(options) {
|
|
19
|
+
const runtime = options.runtime ?? defaultCliDependencies;
|
|
20
|
+
const spawnEditor = dependencies.spawnEditor ?? runtime.integrations.defaultSpawnEditor;
|
|
29
21
|
if (options.copy && !options.take)
|
|
30
22
|
return emitNewError(options, "--copy requires --take");
|
|
23
|
+
if (options.branchOnly) {
|
|
24
|
+
const incompatibleOption = findBranchOnlyIncompatibleOption(options);
|
|
25
|
+
if (incompatibleOption) {
|
|
26
|
+
return emitNewError(options, `${incompatibleOption} cannot be used with --branch-only`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
31
29
|
if (options.detached && options.fromCurrent) {
|
|
32
30
|
const message = "--from-current cannot be used with --detached";
|
|
33
31
|
if (options.json) {
|
|
@@ -38,10 +36,10 @@ export function createNewCommand(dependencies = {}) {
|
|
|
38
36
|
}
|
|
39
37
|
return 1;
|
|
40
38
|
}
|
|
41
|
-
const repository = await detectRepository(options.cwd);
|
|
39
|
+
const repository = await runtime.repositoryContext.detectRepository(options.cwd);
|
|
42
40
|
let config;
|
|
43
41
|
try {
|
|
44
|
-
config = await loadEffectiveConfig(repository.repoRoot, undefined, options.json ? undefined : options.stderr);
|
|
42
|
+
config = await runtime.config.loadEffectiveConfig(repository.repoRoot, undefined, options.json ? undefined : options.stderr);
|
|
45
43
|
}
|
|
46
44
|
catch (error) {
|
|
47
45
|
return emitNewError(options, error instanceof Error ? error.message : String(error));
|
|
@@ -86,7 +84,10 @@ export function createNewCommand(dependencies = {}) {
|
|
|
86
84
|
return 1;
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
|
-
|
|
87
|
+
if (options.branchOnly) {
|
|
88
|
+
return runBranchOnly(options, runtime, repository, config, rawBranch);
|
|
89
|
+
}
|
|
90
|
+
const rawBasePath = runtime.config.resolveConfigString(config, "worktreePath");
|
|
90
91
|
const configuredBasePath = rawBasePath?.startsWith("/") || rawBasePath?.startsWith("~")
|
|
91
92
|
? rawBasePath
|
|
92
93
|
: undefined;
|
|
@@ -102,7 +103,12 @@ export function createNewCommand(dependencies = {}) {
|
|
|
102
103
|
if (options.force) {
|
|
103
104
|
if (!options.dryRun) {
|
|
104
105
|
try {
|
|
105
|
-
await
|
|
106
|
+
await runtime.git.runGit(repository.repoRoot, [
|
|
107
|
+
"worktree",
|
|
108
|
+
"remove",
|
|
109
|
+
"--force",
|
|
110
|
+
worktreePath,
|
|
111
|
+
]);
|
|
106
112
|
}
|
|
107
113
|
catch (err) {
|
|
108
114
|
if (!isNotRegisteredWorktreeError(err)) {
|
|
@@ -117,9 +123,11 @@ export function createNewCommand(dependencies = {}) {
|
|
|
117
123
|
}
|
|
118
124
|
if (!options.detached) {
|
|
119
125
|
try {
|
|
120
|
-
await
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
await runtime.git.runGit(repository.repoRoot, [
|
|
127
|
+
"branch",
|
|
128
|
+
"-D",
|
|
129
|
+
worktreeName,
|
|
130
|
+
]);
|
|
123
131
|
}
|
|
124
132
|
catch {
|
|
125
133
|
// Branch may not exist; proceed anyway.
|
|
@@ -142,7 +150,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
142
150
|
else {
|
|
143
151
|
const choice = await prompt(worktreePath);
|
|
144
152
|
if (choice === "reuse") {
|
|
145
|
-
await recordWorktreeUsage(worktreePath, worktreeName);
|
|
153
|
+
await runtime.history.recordWorktreeUsage(worktreePath, worktreeName);
|
|
146
154
|
await writeOutput(worktreePath, options.stdout, options.outputEnv);
|
|
147
155
|
return 0;
|
|
148
156
|
}
|
|
@@ -150,13 +158,13 @@ export function createNewCommand(dependencies = {}) {
|
|
|
150
158
|
return 1;
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
|
-
const dependencyMode =
|
|
161
|
+
const dependencyMode = runtime.bootstrap.resolveMode(config.dependencyBootstrap, options.noInstall);
|
|
154
162
|
if (options.dryRun) {
|
|
155
163
|
if (options.take) {
|
|
156
|
-
const changedFiles = await listTakeFiles(options.cwd);
|
|
157
|
-
const submoduleFiles = await listSubmoduleFiles(options.cwd, changedFiles);
|
|
164
|
+
const changedFiles = await listTakeFiles(options.cwd, runtime.git.runGitRaw);
|
|
165
|
+
const submoduleFiles = await listSubmoduleFiles(options.cwd, changedFiles, runtime.git.runGitRaw);
|
|
158
166
|
const transferableFiles = changedFiles.filter((file) => !submoduleFiles.includes(file));
|
|
159
|
-
const ignoredFiles = await listIgnoredFiles(options.cwd);
|
|
167
|
+
const ignoredFiles = await listIgnoredFiles(options.cwd, runtime.git.runGitRaw);
|
|
160
168
|
if (transferableFiles.length === 0)
|
|
161
169
|
return emitNewError(options, submoduleFiles.length > 0
|
|
162
170
|
? "nothing to take: submodule changes are not supported"
|
|
@@ -170,7 +178,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
170
178
|
}
|
|
171
179
|
return 0;
|
|
172
180
|
}
|
|
173
|
-
const dryRunDependencyBootstrap = await
|
|
181
|
+
const dryRunDependencyBootstrap = await runtime.bootstrap.preview(dependencyMode, {
|
|
174
182
|
currentRoot: repository.currentRoot,
|
|
175
183
|
repoRoot: repository.repoRoot,
|
|
176
184
|
cargoBuildCommand: config.dependencyBuildCommand,
|
|
@@ -187,7 +195,8 @@ export function createNewCommand(dependencies = {}) {
|
|
|
187
195
|
}
|
|
188
196
|
else {
|
|
189
197
|
const resolvedEditor = options.open
|
|
190
|
-
? (options.editor ??
|
|
198
|
+
? (options.editor ??
|
|
199
|
+
runtime.config.resolveConfigString(config, "editor"))
|
|
191
200
|
: undefined;
|
|
192
201
|
const openNote = resolvedEditor
|
|
193
202
|
? `, then open in ${resolvedEditor}`
|
|
@@ -198,20 +207,20 @@ export function createNewCommand(dependencies = {}) {
|
|
|
198
207
|
}
|
|
199
208
|
await mkdir(dirname(worktreePath), { recursive: true });
|
|
200
209
|
const startPoint = options.fromCurrent && !options.detached
|
|
201
|
-
? await resolveCurrentWorktreeHead(options.cwd)
|
|
210
|
+
? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit)
|
|
202
211
|
: undefined;
|
|
203
212
|
const takeStartPoint = options.take
|
|
204
|
-
? await resolveCurrentWorktreeHead(options.cwd)
|
|
213
|
+
? await resolveCurrentWorktreeHead(options.cwd, runtime.git.runGit)
|
|
205
214
|
: undefined;
|
|
206
215
|
const changedTakeFiles = options.take
|
|
207
|
-
? await listTakeFiles(options.cwd)
|
|
216
|
+
? await listTakeFiles(options.cwd, runtime.git.runGitRaw)
|
|
208
217
|
: [];
|
|
209
218
|
const submoduleFiles = options.take
|
|
210
|
-
? await listSubmoduleFiles(options.cwd, changedTakeFiles)
|
|
219
|
+
? await listSubmoduleFiles(options.cwd, changedTakeFiles, runtime.git.runGitRaw)
|
|
211
220
|
: [];
|
|
212
221
|
const takeFiles = changedTakeFiles.filter((file) => !submoduleFiles.includes(file));
|
|
213
222
|
const takeUntracked = options.take
|
|
214
|
-
? await countUntrackedFiles(options.cwd)
|
|
223
|
+
? await countUntrackedFiles(options.cwd, runtime.git.runGitRaw)
|
|
215
224
|
: 0;
|
|
216
225
|
if (options.take && takeFiles.length === 0)
|
|
217
226
|
return emitNewError(options, submoduleFiles.length > 0
|
|
@@ -219,7 +228,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
219
228
|
: "nothing to take: working tree is clean");
|
|
220
229
|
if (options.take && submoduleFiles.length > 0)
|
|
221
230
|
options.stderr(`Warning: submodule changes are not transferred: ${submoduleFiles.join(", ")}\n`);
|
|
222
|
-
const existingLocalBranch = await localBranchExists(repository.repoRoot, worktreeName);
|
|
231
|
+
const existingLocalBranch = await localBranchExists(repository.repoRoot, worktreeName, runtime);
|
|
223
232
|
const shouldRefreshBase = !options.noFetch &&
|
|
224
233
|
!options.detached &&
|
|
225
234
|
!options.fromCurrent &&
|
|
@@ -228,17 +237,17 @@ export function createNewCommand(dependencies = {}) {
|
|
|
228
237
|
options.mode !== "checkout" &&
|
|
229
238
|
!existingLocalBranch;
|
|
230
239
|
const freshBaseRef = shouldRefreshBase
|
|
231
|
-
? await resolveFreshBaseRef(repository.repoRoot, config, options, promptForFetchFailure)
|
|
240
|
+
? await resolveFreshBaseRef(repository.repoRoot, config, options, promptForFetchFailure, runtime)
|
|
232
241
|
: undefined;
|
|
233
242
|
if (freshBaseRef === null)
|
|
234
243
|
return 1;
|
|
235
244
|
let stashSha = null;
|
|
236
245
|
if (options.take) {
|
|
237
246
|
try {
|
|
238
|
-
const inProgressState = await detectInProgressGitState(options.cwd);
|
|
247
|
+
const inProgressState = await detectInProgressGitState(options.cwd, runtime.git.runGit);
|
|
239
248
|
if (inProgressState)
|
|
240
249
|
return emitNewError(options, `cannot take changes while Git is in progress (${inProgressState})`);
|
|
241
|
-
stashSha = await createTakeStash(options.cwd, worktreeName);
|
|
250
|
+
stashSha = await createTakeStash(options.cwd, worktreeName, runtime.git.runGit);
|
|
242
251
|
}
|
|
243
252
|
catch (error) {
|
|
244
253
|
return emitNewError(options, `could not stash changes safely: ${toExecMessage(error)}`);
|
|
@@ -279,17 +288,22 @@ export function createNewCommand(dependencies = {}) {
|
|
|
279
288
|
: []),
|
|
280
289
|
];
|
|
281
290
|
try {
|
|
282
|
-
await
|
|
291
|
+
await runtime.git.runGit(repository.repoRoot, gitArgs);
|
|
283
292
|
}
|
|
284
293
|
catch (error) {
|
|
285
294
|
if (stashSha)
|
|
286
|
-
await restoreTakeStash(options.cwd, stashSha, options.stderr);
|
|
295
|
+
await restoreTakeStash(options.cwd, stashSha, options.stderr, runtime.git.runGit);
|
|
287
296
|
return emitNewError(options, `failed to create worktree: ${toExecMessage(error)}`);
|
|
288
297
|
}
|
|
289
298
|
if (stashSha &&
|
|
290
|
-
!(await applyTakeStash(worktreePath, options.cwd, stashSha, !!options.copy, options.stderr))) {
|
|
299
|
+
!(await applyTakeStash(worktreePath, options.cwd, stashSha, !!options.copy, options.stderr, runtime.git.runGit))) {
|
|
291
300
|
try {
|
|
292
|
-
await
|
|
301
|
+
await runtime.git.runGit(repository.repoRoot, [
|
|
302
|
+
"worktree",
|
|
303
|
+
"remove",
|
|
304
|
+
"--force",
|
|
305
|
+
worktreePath,
|
|
306
|
+
]);
|
|
293
307
|
}
|
|
294
308
|
catch {
|
|
295
309
|
/* rollback best effort */
|
|
@@ -298,13 +312,13 @@ export function createNewCommand(dependencies = {}) {
|
|
|
298
312
|
}
|
|
299
313
|
if (options.task !== undefined) {
|
|
300
314
|
try {
|
|
301
|
-
await writeTask(worktreePath, options.task);
|
|
315
|
+
await runtime.tasks.writeTask(worktreePath, options.task);
|
|
302
316
|
}
|
|
303
317
|
catch (error) {
|
|
304
318
|
options.stderr(`Warning: could not save task metadata: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
305
319
|
}
|
|
306
320
|
}
|
|
307
|
-
const bootstrap = await bootstrapWorktree({
|
|
321
|
+
const bootstrap = await runtime.bootstrap.bootstrapWorktree({
|
|
308
322
|
branch: worktreeName,
|
|
309
323
|
config,
|
|
310
324
|
currentRoot: repository.currentRoot,
|
|
@@ -315,7 +329,6 @@ export function createNewCommand(dependencies = {}) {
|
|
|
315
329
|
runCommand: dependencies.runCommand,
|
|
316
330
|
commandStdout: options.json ? () => undefined : options.stdout,
|
|
317
331
|
commandStderr: options.json ? () => undefined : options.stderr,
|
|
318
|
-
json: options.json,
|
|
319
332
|
worktreePath,
|
|
320
333
|
});
|
|
321
334
|
if (!bootstrap.ready) {
|
|
@@ -344,30 +357,135 @@ export function createNewCommand(dependencies = {}) {
|
|
|
344
357
|
else {
|
|
345
358
|
if (options.take)
|
|
346
359
|
options.stderr(`✓ took ${takeFiles.length} changed file${takeFiles.length === 1 ? "" : "s"} (${takeUntracked} untracked) → ${worktreeName}\n`);
|
|
347
|
-
await recordWorktreeUsage(worktreePath, worktreeName);
|
|
360
|
+
await runtime.history.recordWorktreeUsage(worktreePath, worktreeName);
|
|
348
361
|
await writeOutput(worktreePath, options.stdout, options.outputEnv);
|
|
349
362
|
}
|
|
350
363
|
if (options.open) {
|
|
351
|
-
const resolvedEditor = options.editor ?? resolveConfigString(config, "editor");
|
|
352
|
-
await openWorktree(worktreePath, resolvedEditor, spawnEditor, options.stderr);
|
|
364
|
+
const resolvedEditor = options.editor ?? runtime.config.resolveConfigString(config, "editor");
|
|
365
|
+
await openWorktree(worktreePath, resolvedEditor, spawnEditor, options.stderr, runtime.integrations.EDITORS);
|
|
353
366
|
}
|
|
354
367
|
return 0;
|
|
355
368
|
};
|
|
356
369
|
}
|
|
370
|
+
function findBranchOnlyIncompatibleOption(options) {
|
|
371
|
+
if (options.detached)
|
|
372
|
+
return "--detached";
|
|
373
|
+
if (options.fromCurrent)
|
|
374
|
+
return "--from-current";
|
|
375
|
+
if (options.take)
|
|
376
|
+
return "--take";
|
|
377
|
+
if (options.copy)
|
|
378
|
+
return "--copy";
|
|
379
|
+
if (options.task !== undefined)
|
|
380
|
+
return "--task";
|
|
381
|
+
if (options.force)
|
|
382
|
+
return "--force";
|
|
383
|
+
if (options.open)
|
|
384
|
+
return "--open";
|
|
385
|
+
if (options.editor)
|
|
386
|
+
return "--editor";
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
async function runBranchOnly(options, runtime, repository, config, rawBranch) {
|
|
390
|
+
const branchName = applyConfiguredBranchPrefix(rawBranch, config.branchPrefix);
|
|
391
|
+
const remote = runtime.config.resolveConfigString(config, "syncRemote") ?? "origin";
|
|
392
|
+
const configuredBaseBranch = runtime.config.resolveConfigString(config, "syncDefaultBranch");
|
|
393
|
+
if (options.dryRun) {
|
|
394
|
+
const baseDescription = configuredBaseBranch
|
|
395
|
+
? configuredBaseBranch
|
|
396
|
+
: `the default branch from ${remote}`;
|
|
397
|
+
if (options.json) {
|
|
398
|
+
const navigation = createNavigationTarget(createNavigationRepository(repository.repoName, repository.repoRoot), repository.currentRoot, branchName);
|
|
399
|
+
options.stdout(`${JSON.stringify({ ...navigation, baseBranch: configuredBaseBranch ?? null, branchOnly: true, dryRun: true }, null, 2)}\n`);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
const sourceDescription = configuredBaseBranch
|
|
403
|
+
? options.noFetch
|
|
404
|
+
? `local ${configuredBaseBranch}`
|
|
405
|
+
: `${remote}/${configuredBaseBranch}`
|
|
406
|
+
: baseDescription;
|
|
407
|
+
options.stdout(`Would create branch ${branchName} from ${sourceDescription} in the current worktree\n`);
|
|
408
|
+
}
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
if (await localBranchExists(repository.repoRoot, branchName, runtime)) {
|
|
412
|
+
return emitNewError(options, `branch already exists: ${branchName}`);
|
|
413
|
+
}
|
|
414
|
+
if (await runtime.git.isDirtyWorktree(options.cwd)) {
|
|
415
|
+
return emitNewError(options, "current worktree has uncommitted changes; commit or stash them before using --branch-only");
|
|
416
|
+
}
|
|
417
|
+
const inProgressState = await detectInProgressGitState(options.cwd, runtime.git.runGit);
|
|
418
|
+
if (inProgressState) {
|
|
419
|
+
return emitNewError(options, `cannot create a branch while Git is in progress (${inProgressState})`);
|
|
420
|
+
}
|
|
421
|
+
let baseBranch = configuredBaseBranch;
|
|
422
|
+
if (!baseBranch || !options.noFetch) {
|
|
423
|
+
try {
|
|
424
|
+
const remoteBase = await runtime.git.resolveRemoteBase(repository.repoRoot, remote, configuredBaseBranch);
|
|
425
|
+
baseBranch = remoteBase?.branch ?? baseBranch;
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
return emitNewError(options, `could not determine the default branch: ${toExecMessage(error)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (!baseBranch) {
|
|
432
|
+
return emitNewError(options, `could not determine the default branch; configure syncDefaultBranch or add a remote named ${remote}`);
|
|
433
|
+
}
|
|
434
|
+
if (options.noFetch &&
|
|
435
|
+
!(await localBranchExists(repository.repoRoot, baseBranch, runtime))) {
|
|
436
|
+
return emitNewError(options, `base branch does not exist locally: ${baseBranch}`);
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
let startPoint = baseBranch;
|
|
440
|
+
if (!options.noFetch) {
|
|
441
|
+
await runtime.git.runGit(repository.repoRoot, [
|
|
442
|
+
"fetch",
|
|
443
|
+
"--prune",
|
|
444
|
+
remote,
|
|
445
|
+
baseBranch,
|
|
446
|
+
]);
|
|
447
|
+
startPoint = `${remote}/${baseBranch}`;
|
|
448
|
+
}
|
|
449
|
+
await runtime.git.runGit(options.cwd, [
|
|
450
|
+
"switch",
|
|
451
|
+
"-c",
|
|
452
|
+
branchName,
|
|
453
|
+
startPoint,
|
|
454
|
+
]);
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
return emitNewError(options, `failed to create branch in the current worktree: ${toExecMessage(error)}`);
|
|
458
|
+
}
|
|
459
|
+
if (options.json) {
|
|
460
|
+
const navigation = createNavigationTarget(createNavigationRepository(repository.repoName, repository.repoRoot), repository.currentRoot, branchName);
|
|
461
|
+
options.stdout(`${JSON.stringify({ ...navigation, baseBranch, branchOnly: true }, null, 2)}\n`);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
options.stderr(`✓ created ${branchName} from ${options.noFetch ? baseBranch : `${remote}/${baseBranch}`} in the current worktree\n`);
|
|
465
|
+
await runtime.history.recordWorktreeUsage(repository.currentRoot, branchName);
|
|
466
|
+
await writeOutput(repository.currentRoot, options.stdout, options.outputEnv);
|
|
467
|
+
}
|
|
468
|
+
return 0;
|
|
469
|
+
}
|
|
357
470
|
export const runNewCommand = createNewCommand();
|
|
358
|
-
async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFailure) {
|
|
359
|
-
const remote = resolveConfigString(config, "syncRemote") ?? "origin";
|
|
360
|
-
const configuredRemote = resolveConfigString(config, "syncRemote");
|
|
361
|
-
let baseBranch = resolveConfigString(config, "syncDefaultBranch");
|
|
471
|
+
async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFailure, runtime) {
|
|
472
|
+
const remote = runtime.config.resolveConfigString(config, "syncRemote") ?? "origin";
|
|
473
|
+
const configuredRemote = runtime.config.resolveConfigString(config, "syncRemote");
|
|
474
|
+
let baseBranch = runtime.config.resolveConfigString(config, "syncDefaultBranch");
|
|
362
475
|
try {
|
|
363
|
-
const remoteBase = await resolveRemoteBase(repoRoot, remote, baseBranch);
|
|
476
|
+
const remoteBase = await runtime.git.resolveRemoteBase(repoRoot, remote, baseBranch);
|
|
364
477
|
if (!remoteBase) {
|
|
365
478
|
if (!configuredRemote)
|
|
366
479
|
return "HEAD";
|
|
367
480
|
throw new Error("the remote default branch is unknown");
|
|
368
481
|
}
|
|
369
482
|
baseBranch = remoteBase.branch;
|
|
370
|
-
await runGit(repoRoot, [
|
|
483
|
+
await runtime.git.runGit(repoRoot, [
|
|
484
|
+
"fetch",
|
|
485
|
+
"--prune",
|
|
486
|
+
remote,
|
|
487
|
+
remoteBase.branch,
|
|
488
|
+
]);
|
|
371
489
|
return remoteBase.ref;
|
|
372
490
|
}
|
|
373
491
|
catch (error) {
|
|
@@ -383,7 +501,7 @@ async function resolveFreshBaseRef(repoRoot, config, options, promptForFetchFail
|
|
|
383
501
|
return null;
|
|
384
502
|
}
|
|
385
503
|
if (baseBranch &&
|
|
386
|
-
(await localRefExists(repoRoot, `refs/remotes/${remote}/${baseBranch}
|
|
504
|
+
(await localRefExists(repoRoot, `refs/remotes/${remote}/${baseBranch}`, runtime))) {
|
|
387
505
|
options.stderr(`Continuing with the cached ${remote}/${baseBranch}.\n`);
|
|
388
506
|
return `${remote}/${baseBranch}`;
|
|
389
507
|
}
|
|
@@ -400,9 +518,14 @@ async function defaultPromptForFetchFailure(message) {
|
|
|
400
518
|
});
|
|
401
519
|
return !isCancel(choice) && choice;
|
|
402
520
|
}
|
|
403
|
-
async function localRefExists(repoRoot, ref) {
|
|
521
|
+
async function localRefExists(repoRoot, ref, runtime) {
|
|
404
522
|
try {
|
|
405
|
-
await runGit(repoRoot, [
|
|
523
|
+
await runtime.git.runGit(repoRoot, [
|
|
524
|
+
"rev-parse",
|
|
525
|
+
"--verify",
|
|
526
|
+
"--quiet",
|
|
527
|
+
ref,
|
|
528
|
+
]);
|
|
406
529
|
return true;
|
|
407
530
|
}
|
|
408
531
|
catch {
|
|
@@ -552,23 +675,28 @@ async function defaultPromptForBranch(placeholder) {
|
|
|
552
675
|
}
|
|
553
676
|
return choice.trim();
|
|
554
677
|
}
|
|
555
|
-
async function localBranchExists(repoRoot, branchName) {
|
|
678
|
+
async function localBranchExists(repoRoot, branchName, runtime) {
|
|
556
679
|
try {
|
|
557
|
-
await
|
|
680
|
+
await runtime.git.runGit(repoRoot, [
|
|
681
|
+
"show-ref",
|
|
682
|
+
"--verify",
|
|
683
|
+
"--quiet",
|
|
684
|
+
`refs/heads/${branchName}`,
|
|
685
|
+
]);
|
|
558
686
|
return true;
|
|
559
687
|
}
|
|
560
688
|
catch {
|
|
561
689
|
return false;
|
|
562
690
|
}
|
|
563
691
|
}
|
|
564
|
-
async function resolveCurrentWorktreeHead(cwd) {
|
|
565
|
-
return
|
|
692
|
+
async function resolveCurrentWorktreeHead(cwd, runGit) {
|
|
693
|
+
return runGit(cwd, ["rev-parse", "--verify", "HEAD"]);
|
|
566
694
|
}
|
|
567
|
-
async function createTakeStash(cwd, worktreeName) {
|
|
695
|
+
async function createTakeStash(cwd, worktreeName, runGit) {
|
|
568
696
|
const message = `gji-take: ${worktreeName}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
569
|
-
await
|
|
570
|
-
const
|
|
571
|
-
const line =
|
|
697
|
+
await runGit(cwd, ["stash", "push", "--include-untracked", "-m", message]);
|
|
698
|
+
const output = await runGit(cwd, ["stash", "list", "--format=%H%x00%s"]);
|
|
699
|
+
const line = output
|
|
572
700
|
.split("\n")
|
|
573
701
|
.map((candidate) => candidate.split("\0"))
|
|
574
702
|
.find(([, subject]) => subject?.endsWith(message));
|
|
@@ -576,7 +704,7 @@ async function createTakeStash(cwd, worktreeName) {
|
|
|
576
704
|
throw new Error("could not identify the stash created for --take");
|
|
577
705
|
return line[0];
|
|
578
706
|
}
|
|
579
|
-
async function detectInProgressGitState(cwd) {
|
|
707
|
+
async function detectInProgressGitState(cwd, runGit) {
|
|
580
708
|
const markers = [
|
|
581
709
|
["MERGE_HEAD", "merge"],
|
|
582
710
|
["rebase-merge", "rebase"],
|
|
@@ -587,8 +715,8 @@ async function detectInProgressGitState(cwd) {
|
|
|
587
715
|
];
|
|
588
716
|
for (const [marker, name] of markers) {
|
|
589
717
|
try {
|
|
590
|
-
const
|
|
591
|
-
await access(resolve(cwd,
|
|
718
|
+
const gitPath = await runGit(cwd, ["rev-parse", "--git-path", marker]);
|
|
719
|
+
await access(resolve(cwd, gitPath));
|
|
592
720
|
return name;
|
|
593
721
|
}
|
|
594
722
|
catch {
|
|
@@ -601,24 +729,19 @@ async function writeOutput(worktreePath, stdout, outputEnv) {
|
|
|
601
729
|
await writeShellOutput(outputEnv ?? NEW_OUTPUT_FILE_ENV, worktreePath, stdout);
|
|
602
730
|
}
|
|
603
731
|
function isNotRegisteredWorktreeError(error) {
|
|
604
|
-
const stderr =
|
|
732
|
+
const stderr = error instanceof Error ? error.message : String(error);
|
|
605
733
|
return (stderr.includes("is not a working tree") ||
|
|
606
734
|
stderr.includes("not a linked working tree"));
|
|
607
735
|
}
|
|
608
|
-
function hasExecStderr(error) {
|
|
609
|
-
return (error instanceof Error &&
|
|
610
|
-
"stderr" in error &&
|
|
611
|
-
typeof error.stderr === "string");
|
|
612
|
-
}
|
|
613
736
|
function toExecMessage(error) {
|
|
614
|
-
return
|
|
737
|
+
return error instanceof Error ? error.message : String(error);
|
|
615
738
|
}
|
|
616
|
-
async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
739
|
+
async function openWorktree(worktreePath, editorCli, spawnFn, stderr, editors) {
|
|
617
740
|
if (!editorCli) {
|
|
618
741
|
stderr("gji new: --open requires --editor <cli> or a saved editor in config\n");
|
|
619
742
|
return;
|
|
620
743
|
}
|
|
621
|
-
const editorDef =
|
|
744
|
+
const editorDef = editors.find((e) => e.cli === editorCli);
|
|
622
745
|
const args = [];
|
|
623
746
|
if (editorDef?.newWindowFlag) {
|
|
624
747
|
args.push(editorDef.newWindowFlag);
|
|
@@ -632,10 +755,10 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
|
632
755
|
stderr(`gji new: failed to open editor: ${message}\n`);
|
|
633
756
|
}
|
|
634
757
|
}
|
|
635
|
-
async function listTakeFiles(cwd) {
|
|
758
|
+
async function listTakeFiles(cwd, runGit) {
|
|
636
759
|
try {
|
|
637
|
-
const
|
|
638
|
-
return
|
|
760
|
+
const output = await runGit(cwd, ["status", "--porcelain=v1"]);
|
|
761
|
+
return output
|
|
639
762
|
.split("\n")
|
|
640
763
|
.filter((line) => line.length > 3)
|
|
641
764
|
.map((line) => line.slice(3).replace(/^"|"$/g, ""));
|
|
@@ -644,10 +767,15 @@ async function listTakeFiles(cwd) {
|
|
|
644
767
|
return [];
|
|
645
768
|
}
|
|
646
769
|
}
|
|
647
|
-
async function listIgnoredFiles(cwd) {
|
|
770
|
+
async function listIgnoredFiles(cwd, runGit) {
|
|
648
771
|
try {
|
|
649
|
-
const
|
|
650
|
-
|
|
772
|
+
const output = await runGit(cwd, [
|
|
773
|
+
"status",
|
|
774
|
+
"--porcelain=v1",
|
|
775
|
+
"--ignored",
|
|
776
|
+
"--untracked-files=all",
|
|
777
|
+
]);
|
|
778
|
+
return output
|
|
651
779
|
.split("\n")
|
|
652
780
|
.filter((line) => line.startsWith("!! "))
|
|
653
781
|
.map((line) => line.slice(3).replace(/^"|"$/g, ""));
|
|
@@ -656,14 +784,12 @@ async function listIgnoredFiles(cwd) {
|
|
|
656
784
|
return [];
|
|
657
785
|
}
|
|
658
786
|
}
|
|
659
|
-
async function listSubmoduleFiles(cwd, changedFiles) {
|
|
787
|
+
async function listSubmoduleFiles(cwd, changedFiles, runGit) {
|
|
660
788
|
if (changedFiles.length === 0)
|
|
661
789
|
return [];
|
|
662
790
|
try {
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
});
|
|
666
|
-
const submodules = new Set(stdout
|
|
791
|
+
const output = await runGit(cwd, ["ls-files", "--stage"]);
|
|
792
|
+
const submodules = new Set(output
|
|
667
793
|
.split("\n")
|
|
668
794
|
.filter((line) => line.startsWith("160000 "))
|
|
669
795
|
.map((line) => line.slice(line.indexOf("\t") + 1)));
|
|
@@ -673,31 +799,31 @@ async function listSubmoduleFiles(cwd, changedFiles) {
|
|
|
673
799
|
return [];
|
|
674
800
|
}
|
|
675
801
|
}
|
|
676
|
-
async function countUntrackedFiles(cwd) {
|
|
802
|
+
async function countUntrackedFiles(cwd, runGit) {
|
|
677
803
|
try {
|
|
678
|
-
const
|
|
679
|
-
|
|
804
|
+
const output = await runGit(cwd, [
|
|
805
|
+
"status",
|
|
806
|
+
"--porcelain=v1",
|
|
807
|
+
"--untracked-files=all",
|
|
808
|
+
]);
|
|
809
|
+
return output.split("\n").filter((line) => line.startsWith("?? ")).length;
|
|
680
810
|
}
|
|
681
811
|
catch {
|
|
682
812
|
return 0;
|
|
683
813
|
}
|
|
684
814
|
}
|
|
685
|
-
async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr) {
|
|
815
|
+
async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr, runGit) {
|
|
686
816
|
try {
|
|
687
|
-
await
|
|
688
|
-
cwd: worktreePath,
|
|
689
|
-
});
|
|
817
|
+
await runGit(worktreePath, ["stash", "apply", stashSha]);
|
|
690
818
|
}
|
|
691
819
|
catch (error) {
|
|
692
820
|
stderr(`Warning: stash apply failed: ${toExecMessage(error)}\n`);
|
|
693
|
-
await restoreTakeStash(sourcePath, stashSha, stderr);
|
|
821
|
+
await restoreTakeStash(sourcePath, stashSha, stderr, runGit);
|
|
694
822
|
return false;
|
|
695
823
|
}
|
|
696
824
|
if (copy) {
|
|
697
825
|
try {
|
|
698
|
-
await
|
|
699
|
-
cwd: sourcePath,
|
|
700
|
-
});
|
|
826
|
+
await runGit(sourcePath, ["stash", "apply", stashSha]);
|
|
701
827
|
}
|
|
702
828
|
catch (error) {
|
|
703
829
|
stderr(`Warning: could not restore copied changes to the source: ${toExecMessage(error)}\n`);
|
|
@@ -705,24 +831,26 @@ async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr)
|
|
|
705
831
|
}
|
|
706
832
|
}
|
|
707
833
|
try {
|
|
708
|
-
const
|
|
709
|
-
|
|
834
|
+
const output = await runGit(sourcePath, [
|
|
835
|
+
"stash",
|
|
836
|
+
"list",
|
|
837
|
+
"--format=%H %gd",
|
|
838
|
+
]);
|
|
839
|
+
const line = output
|
|
710
840
|
.split("\n")
|
|
711
841
|
.find((candidate) => candidate.startsWith(stashSha));
|
|
712
842
|
const ref = line?.trim().split(/\s+/).at(1);
|
|
713
843
|
if (ref)
|
|
714
|
-
await
|
|
844
|
+
await runGit(sourcePath, ["stash", "drop", ref]);
|
|
715
845
|
}
|
|
716
846
|
catch {
|
|
717
847
|
/* preserving the stash is safer */
|
|
718
848
|
}
|
|
719
849
|
return true;
|
|
720
850
|
}
|
|
721
|
-
async function restoreTakeStash(sourcePath, stashSha, stderr) {
|
|
851
|
+
async function restoreTakeStash(sourcePath, stashSha, stderr, runGit) {
|
|
722
852
|
try {
|
|
723
|
-
await
|
|
724
|
-
cwd: sourcePath,
|
|
725
|
-
});
|
|
853
|
+
await runGit(sourcePath, ["stash", "apply", stashSha]);
|
|
726
854
|
}
|
|
727
855
|
catch {
|
|
728
856
|
stderr(`Your changes are safe in stash: ${stashSha} — run "git stash apply ${stashSha}" to restore\n`);
|