@humanu/orchestra 0.5.76 → 0.5.78
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/bin/{gw.js → orchestra-cli.js} +1 -1
- package/bin/orchestra.js +2 -2
- package/install.js +41 -42
- package/package.json +2 -3
- package/resources/api/git.sh +4 -444
- package/resources/api/tmux.sh +4 -2791
- package/resources/prebuilt/linux-x64/{gw-env-copy → env-copy} +0 -0
- package/resources/prebuilt/linux-x64/orchestra +0 -0
- package/resources/prebuilt/macos-arm64/{gw-env-copy → env-copy} +0 -0
- package/resources/prebuilt/macos-arm64/orchestra +0 -0
- package/resources/prebuilt/macos-intel/{gw-env-copy → env-copy} +0 -0
- package/resources/prebuilt/macos-intel/orchestra +0 -0
- package/resources/scripts/{gw.sh → orchestra-cli.sh} +14 -14
- package/resources/scripts/orchestra-local.sh +6 -6
- package/resources/scripts/{gwr.sh → orchestra.sh} +11 -55
- package/resources/scripts/{shell/bridge → server/services}/ai.sh +4 -4
- package/resources/scripts/{gw-bridge.sh → server/services/dispatch.sh} +62 -59
- package/resources/scripts/server/services/git/api.sh +447 -0
- package/resources/scripts/{shell/git/bridge_check_branch.sh → server/services/git/check_branch_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_create_worktree.sh → server/services/git/create_worktree_api.sh} +3 -3
- package/resources/scripts/{shell/git/bridge_create_worktree_from_existing.sh → server/services/git/create_worktree_from_existing_api.sh} +2 -2
- package/resources/scripts/{shell/git/bridge_create_worktree_from_remote.sh → server/services/git/create_worktree_from_remote_api.sh} +2 -2
- package/resources/scripts/{shell/git/bridge_delete_branch_only.sh → server/services/git/delete_branch_only_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_delete_worktree.sh → server/services/git/delete_worktree_api.sh} +2 -2
- package/resources/scripts/{shell/git/bridge_delete_worktree_only.sh → server/services/git/delete_worktree_only_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_enhanced_git_status.sh → server/services/git/enhanced_git_status_api.sh} +3 -3
- package/resources/scripts/{shell/git/bridge_git_status.sh → server/services/git/git_status_api.sh} +2 -2
- package/resources/scripts/{shell/git/bridge_list_worktrees.sh → server/services/git/list_worktrees_api.sh} +2 -2
- package/resources/scripts/server/services/git/merge_api.sh +12 -0
- package/resources/scripts/{shell/git/bridge_merge_from_primary.sh → server/services/git/merge_from_primary_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_merge_into_primary.sh → server/services/git/merge_into_primary_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_primary_branch.sh → server/services/git/primary_branch_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_rebase_from_primary.sh → server/services/git/rebase_from_primary_api.sh} +1 -1
- package/resources/scripts/server/services/git/repo_api.sh +12 -0
- package/resources/scripts/{shell/git/bridge_repo_info.sh → server/services/git/repo_info_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_squash_into_primary.sh → server/services/git/squash_into_primary_api.sh} +1 -1
- package/resources/scripts/{shell/git/bridge_switch_worktree.sh → server/services/git/switch_worktree_api.sh} +2 -2
- package/resources/scripts/server/services/git/worktree_api.sh +17 -0
- package/resources/scripts/{shell/bridge/utils.sh → server/services/json.sh} +23 -23
- package/resources/scripts/{shell/bridge → server/services/session}/tmux.sh +14 -14
- package/resources/scripts/server/session/tmux_api.sh +2806 -0
- package/resources/scripts/services.sh +6 -0
- package/resources/scripts/shell/AGENTS.md +63 -74
- package/resources/scripts/shell/build/dependencies.sh +33 -0
- package/resources/scripts/shell/build/install.sh +7 -0
- package/resources/scripts/shell/build/load.sh +10 -0
- package/resources/scripts/shell/build/logging.sh +6 -0
- package/resources/scripts/shell/build/rust.sh +18 -0
- package/resources/scripts/shell/build/services.sh +17 -0
- package/resources/scripts/shell/build/{build_usage.sh → usage.sh} +6 -6
- package/resources/scripts/shell/cli_load.sh +9 -0
- package/resources/scripts/shell/{gw_env_copy.sh → env_copy.sh} +11 -11
- package/resources/scripts/shell/env_copy_command.sh +2 -2
- package/resources/scripts/shell/git/checkout_worktree.sh +4 -4
- package/resources/scripts/shell/git/create_worktree.sh +2 -2
- package/resources/scripts/shell/git/delete_worktree.sh +1 -1
- package/resources/scripts/shell/git/merge.sh +1 -1
- package/resources/scripts/shell/git/repo.sh +1 -1
- package/resources/scripts/shell/git/worktree.sh +1 -1
- package/resources/scripts/shell/gwr/check-updates.sh +1 -1
- package/resources/scripts/shell/gwr_binary.sh +4 -4
- package/resources/scripts/shell/gwr_load.sh +1 -1
- package/resources/scripts/shell/gwr_services.sh +10 -0
- package/resources/scripts/shell/gwr_usage.sh +10 -10
- package/resources/scripts/shell/orchestra-command-hook.sh +15 -15
- package/resources/scripts/shell/orchestra-local.sh +6 -6
- package/resources/scripts/shell/tmux/new_session_command.sh +1 -1
- package/bin/gwr.js +0 -10
- package/resources/scripts/shell/build/build_bridge.sh +0 -17
- package/resources/scripts/shell/build/build_dependencies.sh +0 -33
- package/resources/scripts/shell/build/build_install.sh +0 -7
- package/resources/scripts/shell/build/build_load.sh +0 -10
- package/resources/scripts/shell/build/build_logging.sh +0 -6
- package/resources/scripts/shell/build/build_rust.sh +0 -18
- package/resources/scripts/shell/git/bridge_merge.sh +0 -12
- package/resources/scripts/shell/git/bridge_repo.sh +0 -12
- package/resources/scripts/shell/git/bridge_worktree.sh +0 -17
- package/resources/scripts/shell/gw_legacy_wrappers.sh +0 -7
- package/resources/scripts/shell/gw_load.sh +0 -10
- package/resources/scripts/shell/gwr_bridge.sh +0 -10
- /package/resources/scripts/shell/{gw_debug.sh → cli_debug.sh} +0 -0
- /package/resources/scripts/shell/{gw_err.sh → cli_err.sh} +0 -0
- /package/resources/scripts/shell/{gw_have_cmd.sh → cli_have_cmd.sh} +0 -0
- /package/resources/scripts/shell/{gw_info.sh → cli_info.sh} +0 -0
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const { spawnSync } = require('child_process');
|
|
5
5
|
|
|
6
6
|
const installScript = path.join(__dirname, '..', 'install.js');
|
|
7
|
-
const result = spawnSync(process.execPath, [installScript, '--invoke=
|
|
7
|
+
const result = spawnSync(process.execPath, [installScript, '--invoke=orchestra-cli', ...process.argv.slice(2)], {
|
|
8
8
|
stdio: 'inherit',
|
|
9
9
|
});
|
|
10
10
|
process.exit(result.status ?? 0);
|
package/bin/orchestra.js
CHANGED
|
@@ -5,8 +5,8 @@ const { spawnSync } = require('child_process');
|
|
|
5
5
|
|
|
6
6
|
const installScript = path.join(__dirname, '..', 'install.js');
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
|
-
const
|
|
9
|
-
const invoke =
|
|
8
|
+
const tuiArgs = new Set(['', '--debug', '-d', '--help', '-h', '--version']);
|
|
9
|
+
const invoke = tuiArgs.has(args[0] ?? '') ? 'orchestra' : 'orchestra-cli';
|
|
10
10
|
const result = spawnSync(process.execPath, [installScript, `--invoke=${invoke}`, ...args], {
|
|
11
11
|
stdio: 'inherit',
|
|
12
12
|
});
|
package/install.js
CHANGED
|
@@ -5,9 +5,9 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const BINARY_NAME = 'orchestra';
|
|
8
|
-
const ENV_COPY_NAME = '
|
|
9
|
-
const SHELL_SCRIPTS = ['
|
|
10
|
-
const SHELL_DIRS = ['shell'];
|
|
8
|
+
const ENV_COPY_NAME = 'env-copy';
|
|
9
|
+
const SHELL_SCRIPTS = ['orchestra.sh', 'orchestra-cli.sh', 'services.sh', 'orchestra-local.sh'];
|
|
10
|
+
const SHELL_DIRS = ['shell', 'server'];
|
|
11
11
|
const SUPPORTED_PLATFORMS = {
|
|
12
12
|
'darwin-x64': 'macos-intel',
|
|
13
13
|
'darwin-arm64': 'macos-arm64',
|
|
@@ -22,7 +22,7 @@ const scriptsDir = path.join(resourcesDir, 'scripts');
|
|
|
22
22
|
const apiDir = path.join(resourcesDir, 'api');
|
|
23
23
|
const prebuiltDir = path.join(resourcesDir, 'prebuilt');
|
|
24
24
|
const projectRoot = path.resolve(packageRoot, '..', '..');
|
|
25
|
-
const hasLocalSource = fs.existsSync(path.join(projectRoot, '
|
|
25
|
+
const hasLocalSource = fs.existsSync(path.join(projectRoot, 'tui'));
|
|
26
26
|
|
|
27
27
|
function ensureDir(dir) {
|
|
28
28
|
if (!fs.existsSync(dir)) {
|
|
@@ -116,9 +116,9 @@ function copyApiScripts() {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
function linkCompatibilityBinary(binaryPath) {
|
|
119
|
-
const
|
|
119
|
+
const tuiPath = path.join(distDir, 'tui');
|
|
120
120
|
try {
|
|
121
|
-
fs.rmSync(
|
|
121
|
+
fs.rmSync(tuiPath, { force: true });
|
|
122
122
|
} catch (err) {
|
|
123
123
|
if (err.code !== 'ENOENT') {
|
|
124
124
|
throw err;
|
|
@@ -126,10 +126,10 @@ function linkCompatibilityBinary(binaryPath) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
try {
|
|
129
|
-
fs.symlinkSync(binaryPath,
|
|
129
|
+
fs.symlinkSync(binaryPath, tuiPath);
|
|
130
130
|
} catch (err) {
|
|
131
|
-
fs.copyFileSync(binaryPath,
|
|
132
|
-
fs.chmodSync(
|
|
131
|
+
fs.copyFileSync(binaryPath, tuiPath);
|
|
132
|
+
fs.chmodSync(tuiPath, 0o755);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -206,10 +206,10 @@ function buildFromSource() {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
console.log('🔨 Building from source... (this may take a few minutes)');
|
|
209
|
-
console.log(' Working directory:', path.join(projectRoot, '
|
|
209
|
+
console.log(' Working directory:', path.join(projectRoot, 'tui'));
|
|
210
210
|
|
|
211
211
|
try {
|
|
212
|
-
execSync('cargo build --release', { stdio: 'inherit', cwd: path.join(projectRoot, '
|
|
212
|
+
execSync('cargo build --release', { stdio: 'inherit', cwd: path.join(projectRoot, 'tui') });
|
|
213
213
|
} catch (error) {
|
|
214
214
|
console.error('❌ Build failed.');
|
|
215
215
|
console.error('');
|
|
@@ -224,9 +224,9 @@ function buildFromSource() {
|
|
|
224
224
|
process.exit(1);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
const builtBinary = path.join(projectRoot, '
|
|
227
|
+
const builtBinary = path.join(projectRoot, 'tui', 'target', 'release', 'tui');
|
|
228
228
|
if (!fs.existsSync(builtBinary)) {
|
|
229
|
-
console.error('❌ Build finished but
|
|
229
|
+
console.error('❌ Build finished but tui binary was not produced.');
|
|
230
230
|
console.error(' Expected location:', builtBinary);
|
|
231
231
|
process.exit(1);
|
|
232
232
|
}
|
|
@@ -236,7 +236,7 @@ function buildFromSource() {
|
|
|
236
236
|
fs.chmodSync(destination, 0o755);
|
|
237
237
|
linkCompatibilityBinary(destination);
|
|
238
238
|
|
|
239
|
-
const envCopyBinary = path.join(projectRoot, '
|
|
239
|
+
const envCopyBinary = path.join(projectRoot, 'tui', 'target', 'release', ENV_COPY_NAME);
|
|
240
240
|
if (!fs.existsSync(envCopyBinary)) {
|
|
241
241
|
console.error(`❌ ${ENV_COPY_NAME} not found after build.`);
|
|
242
242
|
console.error(' Expected location:', envCopyBinary);
|
|
@@ -253,30 +253,31 @@ function assetsReady() {
|
|
|
253
253
|
return (
|
|
254
254
|
fs.existsSync(path.join(distDir, BINARY_NAME)) &&
|
|
255
255
|
fs.existsSync(path.join(distDir, ENV_COPY_NAME)) &&
|
|
256
|
-
fs.existsSync(path.join(distDir, '
|
|
256
|
+
fs.existsSync(path.join(distDir, 'orchestra.sh'))
|
|
257
257
|
);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
function printShellWrapperInstructions(
|
|
261
|
-
const distPathEscaped = distDir.split('\\').join('\\\\');
|
|
262
|
-
const binaryPathEscaped = binaryPath.split('\\').join('\\\\');
|
|
263
|
-
|
|
260
|
+
function printShellWrapperInstructions() {
|
|
264
261
|
console.log('\n📝 Shell wrapper functions needed for directory switching:\n');
|
|
265
|
-
console.log(`#
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
262
|
+
console.log(`# Orchestra shell wrappers
|
|
263
|
+
orchestra() {
|
|
264
|
+
case "\${1:-}" in
|
|
265
|
+
""|"--debug"|"-d"|"--help"|"-h"|"--version")
|
|
266
|
+
command orchestra "$@"
|
|
267
|
+
;;
|
|
268
|
+
*)
|
|
269
|
+
local out="$(command orchestra-cli "$@")"
|
|
270
|
+
local status=$?
|
|
271
|
+
local cd_line="$(echo "$out" | grep -m1 '^cd')"
|
|
272
|
+
[[ -n $cd_line ]] && eval "$cd_line"
|
|
273
|
+
echo "$out" | grep -v '^cd'
|
|
274
|
+
return $status
|
|
275
|
+
;;
|
|
276
|
+
esac
|
|
275
277
|
}
|
|
276
278
|
|
|
277
|
-
|
|
278
|
-
local
|
|
279
|
-
local out="$(bash "$dist_path/gw.sh" "$@")"
|
|
279
|
+
orchestra-cli() {
|
|
280
|
+
local out="$(command orchestra-cli "$@")"
|
|
280
281
|
local status=$?
|
|
281
282
|
local cd_line="$(echo "$out" | grep -m1 '^cd')"
|
|
282
283
|
[[ -n $cd_line ]] && eval "$cd_line"
|
|
@@ -300,13 +301,11 @@ function performSetup({ quiet = false, showInstructions = false } = {}) {
|
|
|
300
301
|
buildFromSource();
|
|
301
302
|
}
|
|
302
303
|
|
|
303
|
-
const binaryPath = path.join(distDir, BINARY_NAME);
|
|
304
|
-
|
|
305
304
|
if (!quiet) {
|
|
306
305
|
console.log('\n✅ Installation complete!');
|
|
307
306
|
}
|
|
308
307
|
if (showInstructions) {
|
|
309
|
-
printShellWrapperInstructions(
|
|
308
|
+
printShellWrapperInstructions();
|
|
310
309
|
}
|
|
311
310
|
}
|
|
312
311
|
|
|
@@ -320,12 +319,12 @@ function runCommand(command, args) {
|
|
|
320
319
|
ensureAssets();
|
|
321
320
|
|
|
322
321
|
let scriptName;
|
|
323
|
-
if (command === '
|
|
324
|
-
scriptName = '
|
|
322
|
+
if (command === 'orchestra-cli') {
|
|
323
|
+
scriptName = 'orchestra-cli.sh';
|
|
325
324
|
} else if (command === 'orchestra-local') {
|
|
326
325
|
scriptName = 'orchestra-local.sh';
|
|
327
326
|
} else {
|
|
328
|
-
scriptName = '
|
|
327
|
+
scriptName = 'orchestra.sh';
|
|
329
328
|
}
|
|
330
329
|
const scriptPath = path.join(distDir, scriptName);
|
|
331
330
|
if (!fs.existsSync(scriptPath)) {
|
|
@@ -334,7 +333,7 @@ function runCommand(command, args) {
|
|
|
334
333
|
}
|
|
335
334
|
|
|
336
335
|
const env = { ...process.env };
|
|
337
|
-
if (command !== '
|
|
336
|
+
if (command !== 'orchestra-cli' && command !== 'orchestra-local') {
|
|
338
337
|
env.GW_TUI_BIN = path.join(distDir, BINARY_NAME);
|
|
339
338
|
}
|
|
340
339
|
|
|
@@ -384,12 +383,12 @@ function main() {
|
|
|
384
383
|
|
|
385
384
|
if (invoke) {
|
|
386
385
|
let command;
|
|
387
|
-
if (invoke === '
|
|
388
|
-
command = '
|
|
386
|
+
if (invoke === 'orchestra-cli') {
|
|
387
|
+
command = 'orchestra-cli';
|
|
389
388
|
} else if (invoke === 'orchestra-local') {
|
|
390
389
|
command = 'orchestra-local';
|
|
391
390
|
} else {
|
|
392
|
-
command = '
|
|
391
|
+
command = 'orchestra';
|
|
393
392
|
}
|
|
394
393
|
runCommand(command, args);
|
|
395
394
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanu/orchestra",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.78",
|
|
4
4
|
"description": "AI-powered Git worktree and tmux session manager with modern TUI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git",
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
"main": "install.js",
|
|
28
28
|
"bin": {
|
|
29
29
|
"orchestra": "bin/orchestra.js",
|
|
30
|
-
"
|
|
31
|
-
"gw": "bin/gw.js",
|
|
30
|
+
"orchestra-cli": "bin/orchestra-cli.js",
|
|
32
31
|
"orchestra-local": "bin/orchestra-local.js"
|
|
33
32
|
},
|
|
34
33
|
"files": [
|
package/resources/api/git.sh
CHANGED
|
@@ -1,447 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# git.sh – Git Worktree API Module
|
|
5
|
-
# ---------------------------------------------------------------------------
|
|
6
|
-
# This module provides a centralized API for all Git operations used by
|
|
7
|
-
# gw.sh, commands.sh, and gw-bridge.sh. It abstracts Git worktree management
|
|
8
|
-
# and other Git operations into a clean, consistent interface.
|
|
9
|
-
###############################################################################
|
|
3
|
+
# shellcheck shell=bash
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
git_repo_root() {
|
|
15
|
-
git rev-parse --show-toplevel 2>/dev/null || true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
# Check if current directory is in a Git repository
|
|
19
|
-
git_is_repo() {
|
|
20
|
-
git rev-parse --git-dir >/dev/null 2>&1
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Get current absolute path (resolved)
|
|
24
|
-
git_current_path() {
|
|
25
|
-
pwd -P
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
# Get the primary repository root (shared across all worktrees)
|
|
29
|
-
git_shared_root() {
|
|
30
|
-
local common_dir
|
|
31
|
-
common_dir="$(git rev-parse --git-common-dir 2>/dev/null)" || return 1
|
|
32
|
-
if [[ -z "$common_dir" ]]; then
|
|
33
|
-
return 1
|
|
34
|
-
fi
|
|
35
|
-
if [[ "$common_dir" != /* ]]; then
|
|
36
|
-
common_dir="$(pwd -P)/$common_dir"
|
|
37
|
-
fi
|
|
38
|
-
dirname "$common_dir"
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Convert branch name to slug (replace / with -)
|
|
42
|
-
git_branch_to_slug() {
|
|
43
|
-
echo "$1" | tr '/' '-'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# --------------------------- Branch Operations ------------------------------
|
|
47
|
-
|
|
48
|
-
# Check if a branch exists
|
|
49
|
-
git_branch_exists() {
|
|
50
|
-
local branch_name="$1"
|
|
51
|
-
git rev-parse --verify "$branch_name" >/dev/null 2>&1
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# Get current branch name (or empty if detached HEAD)
|
|
55
|
-
git_current_branch() {
|
|
56
|
-
git symbolic-ref --short HEAD 2>/dev/null || true
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# Get current commit SHA (short format)
|
|
60
|
-
git_current_commit_short() {
|
|
61
|
-
git rev-parse --short HEAD 2>/dev/null || true
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
# Delete a local branch (force delete)
|
|
65
|
-
git_delete_branch() {
|
|
66
|
-
local branch_name="$1"
|
|
67
|
-
git branch -D "$branch_name" 2>/dev/null || true
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
# --------------------------- Worktree Operations ----------------------------
|
|
71
|
-
|
|
72
|
-
# Ensure ignore rules for worktrees directory exist in both .gitignore and .git/info/exclude
|
|
73
|
-
# Non-destructive and idempotent; creates files if needed
|
|
74
|
-
git_ensure_ignore_worktrees() {
|
|
75
|
-
local root; root="$(git_repo_root)"
|
|
76
|
-
[[ -z "$root" ]] && return 1
|
|
77
|
-
|
|
78
|
-
# .git/info/exclude (local-only)
|
|
79
|
-
local exclude_file="$root/.git/info/exclude"
|
|
80
|
-
mkdir -p "$root/.git/info" 2>/dev/null || true
|
|
81
|
-
if [[ -f "$exclude_file" ]]; then
|
|
82
|
-
if ! grep -q -E '^worktrees/(\s*)?$' "$exclude_file" 2>/dev/null; then
|
|
83
|
-
echo 'worktrees/' >> "$exclude_file"
|
|
84
|
-
fi
|
|
85
|
-
else
|
|
86
|
-
echo 'worktrees/' >> "$exclude_file"
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# Top-level .gitignore (shared)
|
|
90
|
-
local gi_file="$root/.gitignore"
|
|
91
|
-
if [[ -f "$gi_file" ]]; then
|
|
92
|
-
if ! grep -q -E '^worktrees/(\s*)?$' "$gi_file" 2>/dev/null; then
|
|
93
|
-
echo 'worktrees/' >> "$gi_file"
|
|
94
|
-
fi
|
|
95
|
-
else
|
|
96
|
-
echo 'worktrees/' > "$gi_file"
|
|
97
|
-
fi
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
# List all worktrees: returns lines with "path\tbranch\tsha_short"
|
|
101
|
-
git_list_worktrees() {
|
|
102
|
-
local root; root="$(git_repo_root)"
|
|
103
|
-
[[ -z "$root" ]] && return 1
|
|
104
|
-
|
|
105
|
-
local current_path=""
|
|
106
|
-
local current_branch=""
|
|
107
|
-
local current_head=""
|
|
108
|
-
|
|
109
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
110
|
-
case "$line" in
|
|
111
|
-
worktree\ *)
|
|
112
|
-
current_path="${line#worktree }"
|
|
113
|
-
;;
|
|
114
|
-
HEAD\ *)
|
|
115
|
-
current_head="${line#HEAD }"
|
|
116
|
-
;;
|
|
117
|
-
branch\ *)
|
|
118
|
-
current_branch="${line#branch }"
|
|
119
|
-
if [[ "$current_branch" == refs/heads/* ]]; then
|
|
120
|
-
current_branch="${current_branch#refs/heads/}"
|
|
121
|
-
fi
|
|
122
|
-
;;
|
|
123
|
-
*)
|
|
124
|
-
;;
|
|
125
|
-
esac
|
|
126
|
-
|
|
127
|
-
if [[ -z "$line" ]]; then
|
|
128
|
-
if [[ -n "$current_path" ]]; then
|
|
129
|
-
printf '%s\t%s\t%s\n' "$current_path" "$current_branch" "${current_head:0:7}"
|
|
130
|
-
fi
|
|
131
|
-
current_path=""
|
|
132
|
-
current_branch=""
|
|
133
|
-
current_head=""
|
|
134
|
-
fi
|
|
135
|
-
done < <(cd "$root" && git worktree list --porcelain)
|
|
136
|
-
|
|
137
|
-
if [[ -n "$current_path" ]]; then
|
|
138
|
-
printf '%s\t%s\t%s\n' "$current_path" "$current_branch" "${current_head:0:7}"
|
|
139
|
-
fi
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
# Get branch name for a given worktree path
|
|
143
|
-
git_worktree_path_to_branch() {
|
|
144
|
-
local path="$1"
|
|
145
|
-
local root; root="$(git_repo_root)"
|
|
146
|
-
[[ -z "$root" ]] && return 1
|
|
147
|
-
|
|
148
|
-
local current_path=""
|
|
149
|
-
local current_branch=""
|
|
150
|
-
|
|
151
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
152
|
-
case "$line" in
|
|
153
|
-
worktree\ *)
|
|
154
|
-
current_path="${line#worktree }"
|
|
155
|
-
;;
|
|
156
|
-
branch\ *)
|
|
157
|
-
current_branch="${line#branch }"
|
|
158
|
-
if [[ "$current_branch" == refs/heads/* ]]; then
|
|
159
|
-
current_branch="${current_branch#refs/heads/}"
|
|
160
|
-
fi
|
|
161
|
-
;;
|
|
162
|
-
*)
|
|
163
|
-
;;
|
|
164
|
-
esac
|
|
165
|
-
|
|
166
|
-
if [[ -z "$line" ]]; then
|
|
167
|
-
if [[ "$current_path" == "$path" ]]; then
|
|
168
|
-
printf '%s\n' "$current_branch"
|
|
169
|
-
return 0
|
|
170
|
-
fi
|
|
171
|
-
current_path=""
|
|
172
|
-
current_branch=""
|
|
173
|
-
fi
|
|
174
|
-
done < <(cd "$root" && git worktree list --porcelain)
|
|
175
|
-
|
|
176
|
-
return 1
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
# Get worktree path for a given branch name
|
|
180
|
-
git_branch_to_worktree_path() {
|
|
181
|
-
local branch_name="$1"
|
|
182
|
-
local root; root="$(git_repo_root)"
|
|
183
|
-
[[ -z "$root" ]] && return 1
|
|
184
|
-
|
|
185
|
-
local current_path=""
|
|
186
|
-
local current_branch=""
|
|
187
|
-
|
|
188
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
189
|
-
case "$line" in
|
|
190
|
-
worktree\ *)
|
|
191
|
-
current_path="${line#worktree }"
|
|
192
|
-
;;
|
|
193
|
-
branch\ *)
|
|
194
|
-
current_branch="${line#branch }"
|
|
195
|
-
if [[ "$current_branch" == refs/heads/* ]]; then
|
|
196
|
-
current_branch="${current_branch#refs/heads/}"
|
|
197
|
-
fi
|
|
198
|
-
;;
|
|
199
|
-
*)
|
|
200
|
-
;;
|
|
201
|
-
esac
|
|
202
|
-
|
|
203
|
-
if [[ -z "$line" ]]; then
|
|
204
|
-
if [[ "$current_branch" == "$branch_name" ]]; then
|
|
205
|
-
printf '%s\n' "$current_path"
|
|
206
|
-
return 0
|
|
207
|
-
fi
|
|
208
|
-
current_path=""
|
|
209
|
-
current_branch=""
|
|
210
|
-
fi
|
|
211
|
-
done < <(cd "$root" && git worktree list --porcelain)
|
|
212
|
-
|
|
213
|
-
return 1
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
# Create a new worktree with a new branch
|
|
217
|
-
git_create_worktree_with_branch() {
|
|
218
|
-
local branch_name="$1"
|
|
219
|
-
local worktree_path="$2"
|
|
220
|
-
local root; root="$(git_repo_root)"
|
|
221
|
-
[[ -z "$root" ]] && return 1
|
|
222
|
-
|
|
223
|
-
local relative_path="${worktree_path#$root/}"
|
|
224
|
-
(cd "$root" && git worktree add "$relative_path" -b "$branch_name" >&2)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
# Create a new worktree from existing branch
|
|
228
|
-
git_create_worktree_from_branch() {
|
|
229
|
-
local branch_name="$1"
|
|
230
|
-
local worktree_path="$2"
|
|
231
|
-
local root; root="$(git_repo_root)"
|
|
232
|
-
[[ -z "$root" ]] && return 1
|
|
233
|
-
|
|
234
|
-
local relative_path="${worktree_path#$root/}"
|
|
235
|
-
(cd "$root" && git worktree add "$relative_path" "$branch_name" >&2)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
git_create_worktree_for_existing_branch() {
|
|
239
|
-
local branch_name="$1"
|
|
240
|
-
local root; root="$(git_repo_root)"
|
|
241
|
-
[[ -z "$root" ]] && return 1
|
|
242
|
-
|
|
243
|
-
git_branch_exists "$branch_name" || { echo "Branch does not exist: $branch_name" >&2; return 1; }
|
|
244
|
-
|
|
245
|
-
git_ensure_ignore_worktrees >/dev/null 2>&1 || true
|
|
246
|
-
|
|
247
|
-
local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
|
|
248
|
-
mkdir -p "$(dirname "$worktree_path")"
|
|
249
|
-
|
|
250
|
-
git_create_worktree_from_branch "$branch_name" "$worktree_path" || return 1
|
|
251
|
-
|
|
252
|
-
echo "$worktree_path"
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
git_create_worktree_from_remote_branch() {
|
|
256
|
-
local branch_name="$1"
|
|
257
|
-
local remote_name="${2:-origin}"
|
|
258
|
-
local root; root="$(git_repo_root)"
|
|
259
|
-
[[ -z "$root" ]] && return 1
|
|
260
|
-
|
|
261
|
-
git_ensure_ignore_worktrees >/dev/null 2>&1 || true
|
|
262
|
-
|
|
263
|
-
local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
|
|
264
|
-
mkdir -p "$(dirname "$worktree_path")"
|
|
265
|
-
|
|
266
|
-
local relative_path="${worktree_path#$root/}"
|
|
267
|
-
(cd "$root" && git worktree add "$relative_path" -b "$branch_name" "$remote_name/$branch_name" >&2) || return 1
|
|
268
|
-
|
|
269
|
-
echo "$worktree_path"
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
# Remove a worktree (with force fallback)
|
|
273
|
-
git_remove_worktree() {
|
|
274
|
-
local worktree_path="$1"
|
|
275
|
-
local root; root="$(git_repo_root)"
|
|
276
|
-
[[ -z "$root" ]] && return 1
|
|
277
|
-
|
|
278
|
-
local relative_path="${worktree_path#$root/}"
|
|
279
|
-
(cd "$root" && {
|
|
280
|
-
git worktree remove "$relative_path" 2>/dev/null || \
|
|
281
|
-
git worktree remove --force "$relative_path" 2>/dev/null || true
|
|
282
|
-
})
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
# Get Git status for current directory
|
|
286
|
-
git_status() {
|
|
287
|
-
git status "$@"
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
# Get short Git status for current directory
|
|
291
|
-
git_status_short() {
|
|
292
|
-
git status --short
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
# --------------------------- Worktree Path Utilities ------------------------
|
|
296
|
-
|
|
297
|
-
# Build standard worktree path for a branch
|
|
298
|
-
git_build_worktree_path() {
|
|
299
|
-
local branch_name="$1"
|
|
300
|
-
local root; root="$(git_repo_root)"
|
|
301
|
-
[[ -z "$root" ]] && return 1
|
|
302
|
-
|
|
303
|
-
local slug; slug="$(git_branch_to_slug "$branch_name")"
|
|
304
|
-
echo "$root/worktrees/$slug"
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
# Check if a worktree exists for a branch
|
|
308
|
-
git_worktree_exists_for_branch() {
|
|
309
|
-
local branch_name="$1"
|
|
310
|
-
local path; path="$(git_branch_to_worktree_path "$branch_name")"
|
|
311
|
-
[[ -n "$path" ]]
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
# Get or create worktree path for a branch
|
|
315
|
-
git_ensure_worktree_for_branch() {
|
|
316
|
-
local branch_name="$1"
|
|
317
|
-
local root; root="$(git_repo_root)"
|
|
318
|
-
[[ -z "$root" ]] && return 1
|
|
319
|
-
|
|
320
|
-
# Check if worktree already exists
|
|
321
|
-
local existing_path; existing_path="$(git_branch_to_worktree_path "$branch_name")"
|
|
322
|
-
if [[ -n "$existing_path" ]]; then
|
|
323
|
-
echo "$existing_path"
|
|
324
|
-
return 0
|
|
325
|
-
fi
|
|
326
|
-
|
|
327
|
-
# Create worktree if branch exists
|
|
328
|
-
if git_branch_exists "$branch_name"; then
|
|
329
|
-
# Ensure ignores before creating worktree
|
|
330
|
-
git_ensure_ignore_worktrees >/dev/null 2>&1 || true
|
|
331
|
-
local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
|
|
332
|
-
mkdir -p "$(dirname "$worktree_path")"
|
|
333
|
-
git_create_worktree_from_branch "$branch_name" "$worktree_path"
|
|
334
|
-
echo "$worktree_path"
|
|
335
|
-
return 0
|
|
336
|
-
fi
|
|
337
|
-
|
|
338
|
-
return 1
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
# Create new branch and worktree
|
|
342
|
-
git_create_branch_and_worktree() {
|
|
343
|
-
local branch_name="$1"
|
|
344
|
-
local root; root="$(git_repo_root)"
|
|
345
|
-
[[ -z "$root" ]] && return 1
|
|
346
|
-
|
|
347
|
-
# Ensure ignores before creating worktree
|
|
348
|
-
git_ensure_ignore_worktrees >/dev/null 2>&1 || true
|
|
349
|
-
local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
|
|
350
|
-
mkdir -p "$(dirname "$worktree_path")"
|
|
351
|
-
git_create_worktree_with_branch "$branch_name" "$worktree_path"
|
|
352
|
-
echo "$worktree_path"
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
# --------------------------- Validation Functions ---------------------------
|
|
356
|
-
|
|
357
|
-
# Validate that we're in a Git repository
|
|
358
|
-
git_require_repo() {
|
|
359
|
-
git_is_repo || { echo "❌ Not a git repository or git command not found." >&2; return 1; }
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
# Get repository root with validation
|
|
363
|
-
git_require_repo_root() {
|
|
364
|
-
local root; root="$(git_repo_root)"
|
|
365
|
-
[[ -z "$root" ]] && { echo "❌ Not a git repository" >&2; return 1; }
|
|
366
|
-
echo "$root"
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
# --------------------------- Merge/Rebase Helpers ----------------------------
|
|
370
|
-
|
|
371
|
-
# Detect the primary branch name (default remote HEAD), fall back to common names
|
|
372
|
-
# Echoes the branch name on success; returns non-zero on failure
|
|
373
|
-
git_primary_branch() {
|
|
374
|
-
local head_ref branch
|
|
375
|
-
# Try origin/HEAD symbolic ref
|
|
376
|
-
head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
|
|
377
|
-
if [[ -n "$head_ref" ]]; then
|
|
378
|
-
# refs/remotes/origin/main -> extract last component
|
|
379
|
-
branch="${head_ref##*/}"
|
|
380
|
-
[[ -n "$branch" ]] && { echo "$branch"; return 0; }
|
|
381
|
-
fi
|
|
382
|
-
# Try parsing `git remote show origin`
|
|
383
|
-
branch=$(git remote show origin 2>/dev/null | awk -F': ' '/HEAD branch/ {print $2; exit}')
|
|
384
|
-
if [[ -n "$branch" && "$branch" != "(unknown)" ]]; then
|
|
385
|
-
echo "$branch"; return 0
|
|
386
|
-
fi
|
|
387
|
-
# Fallback candidates
|
|
388
|
-
for cand in main master trunk; do
|
|
389
|
-
if git rev-parse --verify "$cand" >/dev/null 2>&1 || git rev-parse --verify "origin/$cand" >/dev/null 2>&1; then
|
|
390
|
-
echo "$cand"; return 0
|
|
391
|
-
fi
|
|
392
|
-
done
|
|
393
|
-
return 1
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
# Check if a worktree at path is clean (no changes)
|
|
397
|
-
# Usage: git_is_worktree_clean <path>
|
|
398
|
-
git_is_worktree_clean() {
|
|
399
|
-
local path="$1"
|
|
400
|
-
[[ -z "$path" ]] && return 1
|
|
401
|
-
local out
|
|
402
|
-
out=$(git -C "$path" status --porcelain 2>/dev/null || true)
|
|
403
|
-
[[ -z "$out" ]]
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
# Fetch from origin with prune for a given path
|
|
407
|
-
git_fetch_prune() {
|
|
408
|
-
local path="$1"
|
|
409
|
-
git -C "$path" fetch origin --prune 2>&1
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
# Fast-forward-only pull for the currently checked out branch at path
|
|
413
|
-
# Returns non-zero if not a fast-forward
|
|
414
|
-
# Usage: git_pull_ff_only <path>
|
|
415
|
-
git_pull_ff_only() {
|
|
416
|
-
local path="$1"
|
|
417
|
-
git -C "$path" pull --ff-only 2>&1
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
# Merge a ref into the currently checked out branch at target path
|
|
421
|
-
# Usage: git_merge_into <target_path> <ref>
|
|
422
|
-
git_merge_into() {
|
|
423
|
-
local target="$1"; local ref="$2"
|
|
424
|
-
git -C "$target" merge "$ref" 2>&1
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
# Rebase the current branch at target path onto upstream ref
|
|
428
|
-
# Usage: git_rebase_onto <target_path> <upstream_ref>
|
|
429
|
-
git_rebase_onto() {
|
|
430
|
-
local target="$1"; local upstream="$2"
|
|
431
|
-
git -C "$target" rebase "$upstream" 2>&1
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
# Squash-merge branch into the primary worktree path, then commit if changes staged
|
|
435
|
-
# Usage: git_squash_merge_into <primary_path> <branch>
|
|
436
|
-
git_squash_merge_into() {
|
|
437
|
-
local primary_path="$1"; local branch="$2"
|
|
438
|
-
git -C "$primary_path" merge --squash "$branch" 2>&1
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
# Ensure a primary worktree exists; echoes the path
|
|
442
|
-
# Usage: git_ensure_primary_worktree
|
|
443
|
-
git_ensure_primary_worktree() {
|
|
444
|
-
local primary
|
|
445
|
-
primary=$(git_primary_branch) || return 1
|
|
446
|
-
git_ensure_worktree_for_branch "$primary"
|
|
447
|
-
}
|
|
5
|
+
_ORCHESTRA_API_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
# shellcheck source=/dev/null
|
|
7
|
+
source "$_ORCHESTRA_API_DIR/../server/services/git/api.sh"
|