@lumpcode/core 0.0.15 → 0.1.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 +7 -1
- package/dist/helpers/execBinary/main.d.ts +19 -5
- package/dist/helpers/execBinary/main.d.ts.map +1 -1
- package/dist/helpers/executeStepsForContextList/main.d.ts +10 -3
- package/dist/helpers/executeStepsForContextList/main.d.ts.map +1 -1
- package/dist/helpers/getContextStatus/main.d.ts +5 -0
- package/dist/helpers/getContextStatus/main.d.ts.map +1 -1
- package/dist/helpers/getToDoContextList/main.d.ts +6 -0
- package/dist/helpers/getToDoContextList/main.d.ts.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/refreshRemoteTrackingRefs/index.d.ts +2 -0
- package/dist/helpers/refreshRemoteTrackingRefs/index.d.ts.map +1 -0
- package/dist/helpers/refreshRemoteTrackingRefs/main.d.ts +11 -0
- package/dist/helpers/refreshRemoteTrackingRefs/main.d.ts.map +1 -0
- package/dist/index.cjs +469 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +467 -174
- package/dist/index.js.map +1 -1
- package/dist/testing/processTreeChild.d.cts +2 -0
- package/dist/testing/processTreeChild.d.cts.map +1 -0
- package/dist/testing/processTreeTestHelpers.d.ts +12 -0
- package/dist/testing/processTreeTestHelpers.d.ts.map +1 -0
- package/dist/testing/sigtermIgnorantTreeChild.d.cts +2 -0
- package/dist/testing/sigtermIgnorantTreeChild.d.cts.map +1 -0
- package/dist/types/ExecShellFn.d.ts +16 -0
- package/dist/types/ExecShellFn.d.ts.map +1 -0
- package/dist/types/ExecuteStepsFailureData.d.ts +6 -0
- package/dist/types/ExecuteStepsFailureData.d.ts.map +1 -0
- package/dist/types/ExecuteStepsFailureReason.d.ts +2 -0
- package/dist/types/ExecuteStepsFailureReason.d.ts.map +1 -0
- package/dist/types/GitAddCommandFn.d.ts +7 -1
- package/dist/types/GitAddCommandFn.d.ts.map +1 -1
- package/dist/types/GitCommitCommandFn.d.ts +7 -1
- package/dist/types/GitCommitCommandFn.d.ts.map +1 -1
- package/dist/types/GitPushCommandFn.d.ts +7 -1
- package/dist/types/GitPushCommandFn.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/usages/runLump/main.d.ts +10 -5
- package/dist/usages/runLump/main.d.ts.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/isProcessAlive/index.d.ts +2 -1
- package/dist/utils/isProcessAlive/index.d.ts.map +1 -1
- package/dist/utils/killProcessTree/index.d.ts +2 -0
- package/dist/utils/killProcessTree/index.d.ts.map +1 -0
- package/dist/utils/killProcessTree/main.d.ts +11 -0
- package/dist/utils/killProcessTree/main.d.ts.map +1 -0
- package/dist/utils/nodeErrnoCode/index.d.ts +1 -1
- package/dist/utils/nodeErrnoCode/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3424,6 +3424,152 @@ function parseGitLogHashSubjectLines(stdout) {
|
|
|
3424
3424
|
});
|
|
3425
3425
|
}
|
|
3426
3426
|
|
|
3427
|
+
/** Node-style `code` from a caught unknown error (e.g. `ENOENT`), or undefined. */
|
|
3428
|
+
function nodeErrnoCode(error) {
|
|
3429
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
3430
|
+
const code = error.code;
|
|
3431
|
+
return typeof code === 'string' ? code : undefined;
|
|
3432
|
+
}
|
|
3433
|
+
return undefined;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
/** Returns whether a process id is still running (signal 0 probe). */
|
|
3437
|
+
function isProcessAlive(pid, options) {
|
|
3438
|
+
const onProbeError = options?.onProbeError ?? 'throw';
|
|
3439
|
+
try {
|
|
3440
|
+
process.kill(pid, 0);
|
|
3441
|
+
return true;
|
|
3442
|
+
}
|
|
3443
|
+
catch (error) {
|
|
3444
|
+
if (nodeErrnoCode(error) === 'ESRCH')
|
|
3445
|
+
return false;
|
|
3446
|
+
if (onProbeError === 'throw')
|
|
3447
|
+
throw error;
|
|
3448
|
+
return onProbeError === 'alive';
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
const execFileAsync = node_util.promisify(node_child_process.execFile);
|
|
3453
|
+
function sleep(ms) {
|
|
3454
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3455
|
+
}
|
|
3456
|
+
async function listUnixProcessTreePids(rootPid) {
|
|
3457
|
+
const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,ppid=']);
|
|
3458
|
+
const childrenByParent = new Map();
|
|
3459
|
+
for (const line of stdout.split('\n')) {
|
|
3460
|
+
const trimmed = line.trim();
|
|
3461
|
+
if (!trimmed)
|
|
3462
|
+
continue;
|
|
3463
|
+
const parts = trimmed.split(/\s+/);
|
|
3464
|
+
if (parts.length < 2)
|
|
3465
|
+
continue;
|
|
3466
|
+
const pid = Number.parseInt(parts[0] ?? '', 10);
|
|
3467
|
+
const ppid = Number.parseInt(parts[1] ?? '', 10);
|
|
3468
|
+
if (Number.isNaN(pid) || Number.isNaN(ppid))
|
|
3469
|
+
continue;
|
|
3470
|
+
const siblings = childrenByParent.get(ppid) ?? [];
|
|
3471
|
+
siblings.push(pid);
|
|
3472
|
+
childrenByParent.set(ppid, siblings);
|
|
3473
|
+
}
|
|
3474
|
+
const ordered = [];
|
|
3475
|
+
const queue = [rootPid];
|
|
3476
|
+
while (queue.length > 0) {
|
|
3477
|
+
const pid = queue.shift();
|
|
3478
|
+
if (pid === undefined)
|
|
3479
|
+
break;
|
|
3480
|
+
ordered.push(pid);
|
|
3481
|
+
const children = childrenByParent.get(pid) ?? [];
|
|
3482
|
+
for (const childPid of children) {
|
|
3483
|
+
queue.push(childPid);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
return ordered;
|
|
3487
|
+
}
|
|
3488
|
+
function killPid(pid, signal) {
|
|
3489
|
+
try {
|
|
3490
|
+
process.kill(pid, signal);
|
|
3491
|
+
}
|
|
3492
|
+
catch (error) {
|
|
3493
|
+
const code = nodeErrnoCode(error);
|
|
3494
|
+
if (code !== 'ESRCH') {
|
|
3495
|
+
throw error;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
async function killUnixProcessTreeImmediate(rootPid) {
|
|
3500
|
+
const pids = await listUnixProcessTreePids(rootPid);
|
|
3501
|
+
for (let index = pids.length - 1; index >= 0; index -= 1) {
|
|
3502
|
+
killPid(pids[index], 'SIGKILL');
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
async function killUnixProcessTreeWithGrace(rootPid, graceMs) {
|
|
3506
|
+
const pids = await listUnixProcessTreePids(rootPid);
|
|
3507
|
+
for (let index = pids.length - 1; index >= 0; index -= 1) {
|
|
3508
|
+
killPid(pids[index], 'SIGTERM');
|
|
3509
|
+
}
|
|
3510
|
+
const deadline = Date.now() + graceMs;
|
|
3511
|
+
while (Date.now() < deadline) {
|
|
3512
|
+
const anyAlive = pids.some((pid) => isProcessAlive(pid, { onProbeError: 'dead' }));
|
|
3513
|
+
if (!anyAlive) {
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
await sleep(25);
|
|
3517
|
+
}
|
|
3518
|
+
const remaining = await listUnixProcessTreePids(rootPid);
|
|
3519
|
+
for (let index = remaining.length - 1; index >= 0; index -= 1) {
|
|
3520
|
+
killPid(remaining[index], 'SIGKILL');
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function killWindowsProcessTree(rootPid) {
|
|
3524
|
+
try {
|
|
3525
|
+
await execFileAsync('taskkill', ['/PID', String(rootPid), '/T', '/F'], {
|
|
3526
|
+
windowsHide: true,
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3529
|
+
catch (error) {
|
|
3530
|
+
const stderr = error && typeof error === 'object' && 'stderr' in error
|
|
3531
|
+
? String(error.stderr ?? '')
|
|
3532
|
+
: '';
|
|
3533
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3534
|
+
const combined = `${message}\n${stderr}`;
|
|
3535
|
+
if (/not found|no running instance|ne existe pas|introuvable/i.test(combined)) {
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
// taskkill can fail to terminate some descendants while the root exits (SEA/agent trees).
|
|
3539
|
+
if (!isProcessAlive(rootPid)) {
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
throw error;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Kill a process and its descendants.
|
|
3547
|
+
* `graceMs` default 0 → immediate SIGKILL / taskkill /T /F.
|
|
3548
|
+
* When `graceMs > 0` (Unix), SIGTERM first, then SIGKILL after the grace window.
|
|
3549
|
+
*/
|
|
3550
|
+
async function killProcessTree(input) {
|
|
3551
|
+
const { pid, graceMs = 0 } = input;
|
|
3552
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
3553
|
+
return failure(`Invalid pid: ${pid}`);
|
|
3554
|
+
}
|
|
3555
|
+
try {
|
|
3556
|
+
if (process.platform === 'win32') {
|
|
3557
|
+
await killWindowsProcessTree(pid);
|
|
3558
|
+
}
|
|
3559
|
+
else if (graceMs > 0) {
|
|
3560
|
+
await killUnixProcessTreeWithGrace(pid, graceMs);
|
|
3561
|
+
}
|
|
3562
|
+
else {
|
|
3563
|
+
await killUnixProcessTreeImmediate(pid);
|
|
3564
|
+
}
|
|
3565
|
+
return success(undefined);
|
|
3566
|
+
}
|
|
3567
|
+
catch (error) {
|
|
3568
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3569
|
+
return failure(`Could not kill process tree for pid ${pid}: ${message}`);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3427
3573
|
const execAsyncBase = node_util.promisify(node_child_process.exec);
|
|
3428
3574
|
async function execAsync(command, options) {
|
|
3429
3575
|
const { stdout, stderr, hasErrored } = await execAsyncBase(command, { cwd: options?.cwd })
|
|
@@ -3453,38 +3599,86 @@ async function execAsync(command, options) {
|
|
|
3453
3599
|
});
|
|
3454
3600
|
}
|
|
3455
3601
|
|
|
3456
|
-
function execBinary(
|
|
3602
|
+
function execBinary(input) {
|
|
3603
|
+
const { binaryPath, args, timeoutMillis = 1000 * 60 * 10, cwd, env, stdio, signal, killGraceMs = 5000, } = input;
|
|
3457
3604
|
return new Promise((resolve) => {
|
|
3458
3605
|
let settled = false;
|
|
3606
|
+
let canceling = false;
|
|
3607
|
+
let child;
|
|
3608
|
+
let stdout = '';
|
|
3609
|
+
let stderr = '';
|
|
3610
|
+
let timeout;
|
|
3459
3611
|
const finish = (result) => {
|
|
3460
3612
|
if (settled)
|
|
3461
3613
|
return;
|
|
3462
3614
|
settled = true;
|
|
3463
|
-
|
|
3615
|
+
if (timeout !== undefined)
|
|
3616
|
+
clearTimeout(timeout);
|
|
3617
|
+
if (signal) {
|
|
3618
|
+
signal.removeEventListener('abort', onAbort);
|
|
3619
|
+
}
|
|
3464
3620
|
resolve(result);
|
|
3465
3621
|
};
|
|
3466
|
-
const
|
|
3622
|
+
const cancelWith = async (reason, message) => {
|
|
3623
|
+
if (settled || canceling)
|
|
3624
|
+
return;
|
|
3625
|
+
canceling = true;
|
|
3626
|
+
const pid = child?.pid;
|
|
3627
|
+
if (pid != null) {
|
|
3628
|
+
await killProcessTree({ pid, graceMs: killGraceMs });
|
|
3629
|
+
}
|
|
3467
3630
|
finish(failure({
|
|
3468
|
-
message
|
|
3631
|
+
message,
|
|
3469
3632
|
binaryPath,
|
|
3470
3633
|
args,
|
|
3634
|
+
stdout,
|
|
3635
|
+
stderr,
|
|
3636
|
+
reason,
|
|
3471
3637
|
}));
|
|
3472
|
-
}
|
|
3638
|
+
};
|
|
3639
|
+
const onAbort = () => {
|
|
3640
|
+
void cancelWith('aborted', 'Process aborted');
|
|
3641
|
+
};
|
|
3642
|
+
if (signal?.aborted) {
|
|
3643
|
+
void cancelWith('aborted', 'Process aborted');
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
if (signal) {
|
|
3647
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
3648
|
+
}
|
|
3473
3649
|
const { executable: resolvedExecutable, args: resolvedArgs } = resolveSpawnExecutable(binaryPath, args);
|
|
3474
|
-
const
|
|
3475
|
-
|
|
3650
|
+
const spawnOptions = {
|
|
3651
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
3652
|
+
...(env !== undefined ? { env } : {}),
|
|
3653
|
+
...(stdio !== undefined ? { stdio } : {}),
|
|
3654
|
+
};
|
|
3655
|
+
child = node_child_process.spawn(resolvedExecutable, resolvedArgs, spawnOptions);
|
|
3656
|
+
// Start the timeout only after the OS has spawned the process so
|
|
3657
|
+
// timeoutMillis measures command runtime, not spawn/setup overhead.
|
|
3658
|
+
child.on('spawn', () => {
|
|
3659
|
+
if (settled || canceling)
|
|
3660
|
+
return;
|
|
3661
|
+
timeout = setTimeout(() => {
|
|
3662
|
+
void cancelWith('timeout', `Process timed out after ${timeoutMillis} milliseconds`);
|
|
3663
|
+
}, timeoutMillis);
|
|
3664
|
+
});
|
|
3476
3665
|
child.stdout?.on('data', (data) => { stdout += data; });
|
|
3477
3666
|
child.stderr?.on('data', (data) => { stderr += data; });
|
|
3478
3667
|
child.on('error', (err) => {
|
|
3668
|
+
if (settled || canceling)
|
|
3669
|
+
return;
|
|
3479
3670
|
finish(failure({
|
|
3480
3671
|
message: err.message,
|
|
3481
3672
|
binaryPath,
|
|
3482
3673
|
args,
|
|
3483
3674
|
stdout,
|
|
3484
3675
|
stderr,
|
|
3676
|
+
reason: 'spawn',
|
|
3485
3677
|
}));
|
|
3486
3678
|
});
|
|
3487
3679
|
child.on('close', (code) => {
|
|
3680
|
+
if (settled || canceling)
|
|
3681
|
+
return;
|
|
3488
3682
|
if (code === 0) {
|
|
3489
3683
|
finish(success({ stdout, stderr }));
|
|
3490
3684
|
return;
|
|
@@ -3496,6 +3690,7 @@ function execBinary(binaryPath, args, timeoutMillis = 1000 * 60 * 10, options) {
|
|
|
3496
3690
|
code: code ?? undefined,
|
|
3497
3691
|
stdout,
|
|
3498
3692
|
stderr,
|
|
3693
|
+
reason: 'exit',
|
|
3499
3694
|
}));
|
|
3500
3695
|
});
|
|
3501
3696
|
});
|
|
@@ -3571,7 +3766,28 @@ async function collectStepsForContext(params) {
|
|
|
3571
3766
|
return collectedSteps;
|
|
3572
3767
|
}
|
|
3573
3768
|
|
|
3574
|
-
async function
|
|
3769
|
+
async function runOptionalGitCommand(input) {
|
|
3770
|
+
const { label, getCommand, cwd, logger } = input;
|
|
3771
|
+
try {
|
|
3772
|
+
const command = await getCommand();
|
|
3773
|
+
if (command == null || command === '') {
|
|
3774
|
+
return 'ok';
|
|
3775
|
+
}
|
|
3776
|
+
const result = await execAsync(command, { cwd });
|
|
3777
|
+
logger.verbose(`${label} ${JSON.stringify(result)}`);
|
|
3778
|
+
if (!result.success) {
|
|
3779
|
+
logger.error(formatExecFailureMessage({ label, failure: result }));
|
|
3780
|
+
return 'failed';
|
|
3781
|
+
}
|
|
3782
|
+
return 'ok';
|
|
3783
|
+
}
|
|
3784
|
+
catch (error) {
|
|
3785
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3786
|
+
logger.error(`Failed to run ${label}: ${message}`);
|
|
3787
|
+
return 'failed';
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables, contextList, gitAddCommandFn, gitCommitCommandFn, gitPushCommandFn, gitCommitMessageFn, projectRoot, steps, setupFn, setupWorkspaceFn, teardownFn, teardownWorkspaceFn, getKeepHistoryFilePathFn, logger: loggerInput, signal, }) {
|
|
3575
3791
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
3576
3792
|
const contextNames = contextList.map(context => context.name);
|
|
3577
3793
|
logger.verbose(`contextNames ${JSON.stringify(contextNames)}`);
|
|
@@ -3606,180 +3822,232 @@ async function executeStepsForContextList({ baseBranch, branchFn, lumpVariables,
|
|
|
3606
3822
|
const gitStatusCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3607
3823
|
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusCommand.data)}`);
|
|
3608
3824
|
injectedGitAndWorkspaceFnsInput.workspacePath = workspacePath;
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
if (typeof step === 'function') {
|
|
3632
|
-
subSteps =
|
|
3825
|
+
let runFailure;
|
|
3826
|
+
try {
|
|
3827
|
+
for (let i = 0; i < contextList.length; i++) {
|
|
3828
|
+
const context = contextList[i];
|
|
3829
|
+
logger.info(contextList.length > 1
|
|
3830
|
+
? `Running context "${context.name}" (${i + 1}/${contextList.length})`
|
|
3831
|
+
: `Running context "${context.name}"`);
|
|
3832
|
+
const setupResult = await setupFn({
|
|
3833
|
+
contextList,
|
|
3834
|
+
lumpVariables,
|
|
3835
|
+
currentContextIndex: i,
|
|
3836
|
+
});
|
|
3837
|
+
const contextRunState = setupResult?.contextRunState || {};
|
|
3838
|
+
let stepWalkFailure;
|
|
3839
|
+
async function walkAndExecuteSteps(stepsToExec, currStepIndex) {
|
|
3840
|
+
for (let stepIndex = 0; stepIndex < stepsToExec.length; stepIndex++) {
|
|
3841
|
+
if (stepWalkFailure) {
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3844
|
+
const step = stepsToExec[stepIndex];
|
|
3845
|
+
const nextCallHeadIndex = [...currStepIndex, stepIndex];
|
|
3846
|
+
const compositeStepIndex = nextCallHeadIndex.length === 1 ? nextCallHeadIndex[0] : nextCallHeadIndex;
|
|
3847
|
+
if (typeof step === 'function' || Array.isArray(step)) {
|
|
3848
|
+
let subSteps = [];
|
|
3849
|
+
if (typeof step === 'function') {
|
|
3850
|
+
subSteps = await step({
|
|
3851
|
+
context,
|
|
3852
|
+
stepIndex: compositeStepIndex,
|
|
3853
|
+
contextRunState,
|
|
3854
|
+
lumpVariables,
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
else {
|
|
3858
|
+
subSteps = step;
|
|
3859
|
+
}
|
|
3860
|
+
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3861
|
+
continue;
|
|
3862
|
+
}
|
|
3863
|
+
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3864
|
+
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3865
|
+
const prompt = promptFn
|
|
3866
|
+
? await promptFn({
|
|
3633
3867
|
context,
|
|
3634
3868
|
stepIndex: compositeStepIndex,
|
|
3635
3869
|
contextRunState,
|
|
3636
3870
|
lumpVariables,
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
}
|
|
3642
|
-
await walkAndExecuteSteps(subSteps, nextCallHeadIndex);
|
|
3643
|
-
continue;
|
|
3644
|
-
}
|
|
3645
|
-
logger.verbose(`step ${JSON.stringify(step)}`);
|
|
3646
|
-
const { commandFn = () => null, stepVariables, promptFn, postCommandExecFn, continueOnError, timeoutMillis = 1000 * 60 * 30, } = step;
|
|
3647
|
-
const prompt = promptFn
|
|
3648
|
-
? await promptFn({
|
|
3871
|
+
stepVariables,
|
|
3872
|
+
})
|
|
3873
|
+
: '';
|
|
3874
|
+
const command = await commandFn({
|
|
3649
3875
|
context,
|
|
3876
|
+
prompt,
|
|
3650
3877
|
stepIndex: compositeStepIndex,
|
|
3651
3878
|
contextRunState,
|
|
3652
3879
|
lumpVariables,
|
|
3653
3880
|
stepVariables,
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
const command = await commandFn({
|
|
3657
|
-
context,
|
|
3658
|
-
prompt,
|
|
3659
|
-
stepIndex: compositeStepIndex,
|
|
3660
|
-
contextRunState,
|
|
3661
|
-
lumpVariables,
|
|
3662
|
-
stepVariables,
|
|
3663
|
-
projectRoot,
|
|
3664
|
-
workspacePath,
|
|
3665
|
-
});
|
|
3666
|
-
let commandResult = '';
|
|
3667
|
-
let commandSucceeded = true;
|
|
3668
|
-
if (command != null) {
|
|
3669
|
-
const { executable, args, env } = command;
|
|
3670
|
-
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3671
|
-
if (env != null) {
|
|
3672
|
-
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3673
|
-
}
|
|
3674
|
-
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3675
|
-
const commandExec = await execBinary(executable, args, timeoutMillis, {
|
|
3676
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3677
|
-
cwd: workspacePath,
|
|
3678
|
-
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3881
|
+
projectRoot,
|
|
3882
|
+
workspacePath,
|
|
3679
3883
|
});
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3884
|
+
let commandResult = '';
|
|
3885
|
+
let commandSucceeded = true;
|
|
3886
|
+
if (command != null) {
|
|
3887
|
+
const { executable, args, env } = command;
|
|
3888
|
+
logger.verbose(`command for prompt ${executable} ${args.join(' ')}`);
|
|
3889
|
+
if (env != null) {
|
|
3890
|
+
logger.verbose(`command env overrides ${JSON.stringify(env)}`);
|
|
3891
|
+
}
|
|
3892
|
+
logger.verbose(`workspacePath ${workspacePath}`);
|
|
3893
|
+
const commandExec = await execBinary({
|
|
3894
|
+
binaryPath: executable,
|
|
3895
|
+
args,
|
|
3896
|
+
timeoutMillis,
|
|
3897
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
3898
|
+
cwd: workspacePath,
|
|
3899
|
+
signal,
|
|
3900
|
+
...(env != null ? { env: { ...process.env, ...env } } : {}),
|
|
3901
|
+
});
|
|
3902
|
+
logger.verbose(`commandExec ${JSON.stringify(commandExec)}`);
|
|
3903
|
+
if (!commandExec.success) {
|
|
3904
|
+
const aborted = commandExec.data.reason === 'aborted';
|
|
3905
|
+
if (aborted || !continueOnError) {
|
|
3906
|
+
stepWalkFailure = {
|
|
3907
|
+
success: false,
|
|
3908
|
+
data: {
|
|
3909
|
+
message: `Failed to run the command: ${commandExec.data.message}. Command: ${executable} ${args.join(' ')}`,
|
|
3910
|
+
reason: 'stepWalkFailed',
|
|
3911
|
+
},
|
|
3912
|
+
};
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
commandSucceeded = false;
|
|
3916
|
+
commandResult = (commandExec.data.stdout
|
|
3917
|
+
|| commandExec.data.stderr
|
|
3918
|
+
|| commandExec.data.message
|
|
3919
|
+
|| '').toString();
|
|
3920
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3921
|
+
}
|
|
3922
|
+
else {
|
|
3923
|
+
commandResult = (commandExec.data.stdout || commandExec.data.stderr || '').toString();
|
|
3924
|
+
logger.verbose(`commandResult ${commandResult}`);
|
|
3925
|
+
}
|
|
3926
|
+
if (commandSucceeded) {
|
|
3927
|
+
const gitStatusAfterCommand = await execAsync(`git status`, { cwd: workspacePath });
|
|
3928
|
+
logger.verbose(`gitStatusCommand ${JSON.stringify(gitStatusAfterCommand.data)}`);
|
|
3685
3929
|
}
|
|
3686
|
-
commandSucceeded = false;
|
|
3687
|
-
commandResult = (commandExec.data.stdout
|
|
3688
|
-
|| commandExec.data.stderr
|
|
3689
|
-
|| commandExec.data.message
|
|
3690
|
-
|| '').toString();
|
|
3691
|
-
logger.verbose(`commandResult ${commandResult}`);
|
|
3692
3930
|
}
|
|
3693
|
-
|
|
3694
|
-
commandResult
|
|
3695
|
-
|
|
3931
|
+
const postCommandExecFnInput = {
|
|
3932
|
+
commandResult,
|
|
3933
|
+
commandSucceeded,
|
|
3934
|
+
context,
|
|
3935
|
+
prompt,
|
|
3936
|
+
stepIndex: compositeStepIndex,
|
|
3937
|
+
contextRunState,
|
|
3938
|
+
lumpVariables,
|
|
3939
|
+
stepVariables,
|
|
3940
|
+
projectRoot,
|
|
3941
|
+
};
|
|
3942
|
+
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3943
|
+
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3944
|
+
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3945
|
+
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3946
|
+
const appendResult = await appendHistoryEntry({
|
|
3947
|
+
filePath: keepHistoryFilePath,
|
|
3948
|
+
entry: postCommandExecFnInput,
|
|
3949
|
+
});
|
|
3950
|
+
if (!appendResult.success) {
|
|
3951
|
+
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3952
|
+
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3953
|
+
}
|
|
3696
3954
|
}
|
|
3697
|
-
if (
|
|
3698
|
-
const
|
|
3699
|
-
|
|
3955
|
+
if (postCommandExecFn) {
|
|
3956
|
+
const returnedSteps = await postCommandExecFn(postCommandExecFnInput);
|
|
3957
|
+
if (returnedSteps != null && returnedSteps.length > 0) {
|
|
3958
|
+
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
3959
|
+
}
|
|
3700
3960
|
}
|
|
3701
3961
|
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
logger.verbose(`context is ${JSON.stringify(context)}`);
|
|
3714
|
-
const keepHistoryFilePath = getKeepHistoryFilePathFn(context) || '';
|
|
3715
|
-
logger.verbose(`keepHistoryFilePath ${keepHistoryFilePath}`);
|
|
3716
|
-
if (!!command && keepHistoryFilePath.length > 0) {
|
|
3717
|
-
const appendResult = await appendHistoryEntry({
|
|
3718
|
-
filePath: keepHistoryFilePath,
|
|
3719
|
-
entry: postCommandExecFnInput,
|
|
3962
|
+
}
|
|
3963
|
+
try {
|
|
3964
|
+
await walkAndExecuteSteps(steps, []);
|
|
3965
|
+
}
|
|
3966
|
+
finally {
|
|
3967
|
+
try {
|
|
3968
|
+
await teardownFn({
|
|
3969
|
+
lumpVariables,
|
|
3970
|
+
contextList,
|
|
3971
|
+
currentContextIndex: i,
|
|
3972
|
+
contextRunState,
|
|
3720
3973
|
});
|
|
3721
|
-
if (!appendResult.success) {
|
|
3722
|
-
// TODO: sanitize history appending to avoid this warning for certain commands outputs
|
|
3723
|
-
logger.warn(`Failed to append history entry to ${keepHistoryFilePath}: ${appendResult.data}`);
|
|
3724
|
-
}
|
|
3725
3974
|
}
|
|
3726
|
-
|
|
3727
|
-
const
|
|
3728
|
-
|
|
3729
|
-
await walkAndExecuteSteps(returnedSteps, nextCallHeadIndex);
|
|
3730
|
-
}
|
|
3975
|
+
catch (error) {
|
|
3976
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3977
|
+
logger.error(`Failed to run teardownFn: ${message}`);
|
|
3731
3978
|
}
|
|
3732
3979
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
commitMessage,
|
|
3757
|
-
|
|
3758
|
-
logger.verbose(`commitCommand ${JSON.stringify(commitCommand)}`);
|
|
3759
|
-
if (!commitCommand.success) {
|
|
3760
|
-
logger.error(formatExecFailureMessage({
|
|
3980
|
+
if (stepWalkFailure) {
|
|
3981
|
+
runFailure = stepWalkFailure;
|
|
3982
|
+
break;
|
|
3983
|
+
}
|
|
3984
|
+
const perContextInput = {
|
|
3985
|
+
...injectedGitAndWorkspaceFnsInput,
|
|
3986
|
+
context,
|
|
3987
|
+
};
|
|
3988
|
+
const addOutcome = await runOptionalGitCommand({
|
|
3989
|
+
label: `git add for context ${context.name}`,
|
|
3990
|
+
getCommand: () => gitAddCommandFn(perContextInput),
|
|
3991
|
+
cwd: workspacePath,
|
|
3992
|
+
logger,
|
|
3993
|
+
});
|
|
3994
|
+
if (addOutcome === 'failed') {
|
|
3995
|
+
runFailure = {
|
|
3996
|
+
success: false,
|
|
3997
|
+
data: {
|
|
3998
|
+
message: `Failed to add the changes for context ${context.name}`,
|
|
3999
|
+
},
|
|
4000
|
+
};
|
|
4001
|
+
break;
|
|
4002
|
+
}
|
|
4003
|
+
const commitMessage = gitCommitMessageFn({ context, lumpVariables, baseBranch });
|
|
4004
|
+
await runOptionalGitCommand({
|
|
3761
4005
|
label: `git commit for context ${context.name}`,
|
|
3762
|
-
|
|
3763
|
-
|
|
4006
|
+
getCommand: () => gitCommitCommandFn({
|
|
4007
|
+
...perContextInput,
|
|
4008
|
+
commitMessage,
|
|
4009
|
+
}),
|
|
4010
|
+
cwd: workspacePath,
|
|
4011
|
+
logger,
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
if (!runFailure) {
|
|
4015
|
+
await runOptionalGitCommand({
|
|
4016
|
+
label: `git push on branch ${branchName}`,
|
|
4017
|
+
getCommand: () => gitPushCommandFn(injectedGitAndWorkspaceFnsInput),
|
|
4018
|
+
cwd: workspacePath,
|
|
4019
|
+
logger,
|
|
4020
|
+
});
|
|
3764
4021
|
}
|
|
3765
4022
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
4023
|
+
finally {
|
|
4024
|
+
const teardownWorkspaceCommand = await teardownWorkspaceFn(injectedGitAndWorkspaceFnsInput);
|
|
4025
|
+
logger.verbose(`teardownWorkspaceCommand ${teardownWorkspaceCommand}`);
|
|
4026
|
+
if (teardownWorkspaceCommand) {
|
|
4027
|
+
const teardownWorkspaceCommandExec = await execAsync(teardownWorkspaceCommand, { cwd: workspacePath });
|
|
4028
|
+
logger.verbose(`teardownWorkspaceCommandExec ${JSON.stringify(teardownWorkspaceCommandExec)}`);
|
|
4029
|
+
if (!teardownWorkspaceCommandExec.success) {
|
|
4030
|
+
if (runFailure) {
|
|
4031
|
+
logger.error(formatExecFailureMessage({
|
|
4032
|
+
label: 'teardown workspace',
|
|
4033
|
+
failure: teardownWorkspaceCommandExec,
|
|
4034
|
+
}));
|
|
4035
|
+
}
|
|
4036
|
+
else {
|
|
4037
|
+
runFailure = {
|
|
4038
|
+
success: false,
|
|
4039
|
+
data: {
|
|
4040
|
+
message: `Failed to teardown the workspace: ${teardownWorkspaceCommandExec.data.message}`,
|
|
4041
|
+
reason: 'workspaceTeardownFailed',
|
|
4042
|
+
},
|
|
4043
|
+
};
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
3781
4046
|
}
|
|
3782
4047
|
}
|
|
4048
|
+
if (runFailure) {
|
|
4049
|
+
return runFailure;
|
|
4050
|
+
}
|
|
3783
4051
|
return success({
|
|
3784
4052
|
branchName,
|
|
3785
4053
|
contextNames,
|
|
@@ -3865,17 +4133,32 @@ async function scanDirectory({ dirPath, allPaths, gitignoresMap, projectRoot, lo
|
|
|
3865
4133
|
}
|
|
3866
4134
|
}
|
|
3867
4135
|
|
|
4136
|
+
/**
|
|
4137
|
+
* Refreshes remote-tracking refs for status / planning (one network fetch).
|
|
4138
|
+
* Uses `--no-write-fetch-head` so concurrent preflight/pull paths are safer.
|
|
4139
|
+
*/
|
|
4140
|
+
const refreshRemoteTrackingRefs = async (input) => {
|
|
4141
|
+
const { projectRoot, remoteName = 'origin' } = input;
|
|
4142
|
+
const result = await execAsync(`git fetch --prune --no-write-fetch-head ${remoteName}`, { cwd: projectRoot });
|
|
4143
|
+
if (!result.success) {
|
|
4144
|
+
return failure(result.data.message);
|
|
4145
|
+
}
|
|
4146
|
+
return success(undefined);
|
|
4147
|
+
};
|
|
4148
|
+
|
|
3868
4149
|
async function getContextStatus(params) {
|
|
3869
|
-
const { contextName, gitCommitMessageFn, projectRoot, baseBranch, lumpVariables: lumpVariablesInput, contextVariables = {}, remoteName = "origin", logger, } = params;
|
|
4150
|
+
const { contextName, gitCommitMessageFn, projectRoot, baseBranch, lumpVariables: lumpVariablesInput, contextVariables = {}, remoteName = "origin", logger, skipFetch = false, } = params;
|
|
3870
4151
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
3871
4152
|
const commitMessage = gitCommitMessageFn({
|
|
3872
4153
|
context: { name: contextName, variables: contextVariables },
|
|
3873
4154
|
lumpVariables,
|
|
3874
4155
|
baseBranch,
|
|
3875
4156
|
});
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
4157
|
+
if (!skipFetch) {
|
|
4158
|
+
const fetchResult = await refreshRemoteTrackingRefs({ projectRoot, remoteName });
|
|
4159
|
+
if (!fetchResult.success)
|
|
4160
|
+
return 'toDo';
|
|
4161
|
+
}
|
|
3879
4162
|
const logResult = await execAsync(`git log --remotes=${remoteName} -F --grep=${shellSingleQuote(commitMessage)} --format=${shellSingleQuote('%H %s')}`, { cwd: projectRoot });
|
|
3880
4163
|
if (!logResult.success)
|
|
3881
4164
|
return 'toDo';
|
|
@@ -3884,6 +4167,7 @@ async function getContextStatus(params) {
|
|
|
3884
4167
|
const matchingHashes = parseGitLogHashSubjectLines(logResultOutput)
|
|
3885
4168
|
.filter((entry) => entry.subject === commitMessage)
|
|
3886
4169
|
.map((entry) => entry.hash);
|
|
4170
|
+
logger?.verbose(`contextName ${contextName}`);
|
|
3887
4171
|
logger?.verbose(`remoteName ${remoteName}`);
|
|
3888
4172
|
logger?.verbose(`baseBranch ${baseBranch}`);
|
|
3889
4173
|
logger?.verbose(`commitMessage ${commitMessage}`);
|
|
@@ -3938,7 +4222,7 @@ function validateContextListNames(contextList) {
|
|
|
3938
4222
|
}
|
|
3939
4223
|
|
|
3940
4224
|
async function getToDoContextList(params) {
|
|
3941
|
-
const { getContextListFn, lumpVariables, gitCommitMessageFn, projectRoot, baseBranch, logger } = params;
|
|
4225
|
+
const { getContextListFn, lumpVariables, gitCommitMessageFn, projectRoot, baseBranch, logger, refreshRemoteTrackingRefsFn = refreshRemoteTrackingRefs, } = params;
|
|
3942
4226
|
const codeBasePathsResult = await getCodeBasePaths({ cwd: projectRoot, logger });
|
|
3943
4227
|
if (!codeBasePathsResult.success) {
|
|
3944
4228
|
return failure({
|
|
@@ -3956,21 +4240,28 @@ async function getToDoContextList(params) {
|
|
|
3956
4240
|
const allCtxNames = contextList.flatMap(context => [context.name, ...(context.options?.dependsOnContexts ?? [])]);
|
|
3957
4241
|
const allCtxNamesSet = new Set(allCtxNames);
|
|
3958
4242
|
const allCtxNamesList = Array.from(allCtxNamesSet);
|
|
3959
|
-
const
|
|
3960
|
-
|
|
3961
|
-
|
|
4243
|
+
const refreshResult = await refreshRemoteTrackingRefsFn({ projectRoot });
|
|
4244
|
+
let contextStatusMap;
|
|
4245
|
+
if (!refreshResult.success) {
|
|
4246
|
+
logger?.warn(`Failed to refresh remote-tracking refs for context status; treating contexts as toDo: ${refreshResult.data}`);
|
|
4247
|
+
contextStatusMap = new Map(allCtxNamesList.map((name) => [name, 'toDo']));
|
|
4248
|
+
}
|
|
4249
|
+
else {
|
|
4250
|
+
const contextStatusList = await Promise.all(allCtxNamesList.map((contextName) => getContextStatus({
|
|
4251
|
+
contextName,
|
|
3962
4252
|
contextVariables: {}, // TODO: Remove contextVariables from getContextStatus, really not needed
|
|
3963
4253
|
gitCommitMessageFn,
|
|
3964
4254
|
lumpVariables,
|
|
3965
4255
|
projectRoot,
|
|
3966
4256
|
baseBranch,
|
|
3967
4257
|
logger,
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4258
|
+
skipFetch: true,
|
|
4259
|
+
})));
|
|
4260
|
+
contextStatusMap = new Map(allCtxNamesList.map((contextName, i) => [contextName, contextStatusList[i]]));
|
|
4261
|
+
}
|
|
3971
4262
|
const contextListToDo = contextList
|
|
3972
|
-
.filter((context
|
|
3973
|
-
const contextStatus =
|
|
4263
|
+
.filter((context) => {
|
|
4264
|
+
const contextStatus = contextStatusMap.get(context.name);
|
|
3974
4265
|
if (contextStatus && contextStatus !== 'toDo')
|
|
3975
4266
|
return false;
|
|
3976
4267
|
const deps = context.options?.dependsOnContexts;
|
|
@@ -4036,7 +4327,7 @@ const defaultSetupWorkspaceFnWithWorktree = async (input) => {
|
|
|
4036
4327
|
};
|
|
4037
4328
|
|
|
4038
4329
|
async function runLump(input) {
|
|
4039
|
-
const { baseBranch, branchFn, lumpVariables: lumpVariablesInput, getContextListFn, gitAddCommandFn = defaultGitAddCommandFn, gitCommitCommandFn = defaultGitCommitCommandFn, gitCommitMessageFn = defaultGitCommitMessageFn, gitPushCommandFn = defaultGitPushCommandFn, numberOfContextsPerBranch = 1, projectRoot, steps, setupFn = () => ({ contextRunState: {} }), setupWorkspaceFn = defaultSetupWorkspaceFn, teardownFn = () => undefined, teardownWorkspaceFn = defaultTeardownWorkspaceFn, getKeepHistoryFilePathFn = () => undefined, logger: loggerInput, } = input;
|
|
4330
|
+
const { baseBranch, branchFn, lumpVariables: lumpVariablesInput, getContextListFn, gitAddCommandFn = defaultGitAddCommandFn, gitCommitCommandFn = defaultGitCommitCommandFn, gitCommitMessageFn = defaultGitCommitMessageFn, gitPushCommandFn = defaultGitPushCommandFn, numberOfContextsPerBranch = 1, projectRoot, steps, setupFn = () => ({ contextRunState: {} }), setupWorkspaceFn = defaultSetupWorkspaceFn, teardownFn = () => undefined, teardownWorkspaceFn = defaultTeardownWorkspaceFn, getKeepHistoryFilePathFn = () => undefined, logger: loggerInput, signal, refreshRemoteTrackingRefsFn, } = input;
|
|
4040
4331
|
const lumpVariables = (lumpVariablesInput ?? {});
|
|
4041
4332
|
const logger = loggerInput ?? createConsoleLogger({});
|
|
4042
4333
|
const contextListToDoResult = await getToDoContextList({
|
|
@@ -4046,6 +4337,7 @@ async function runLump(input) {
|
|
|
4046
4337
|
baseBranch,
|
|
4047
4338
|
gitCommitMessageFn,
|
|
4048
4339
|
logger,
|
|
4340
|
+
refreshRemoteTrackingRefsFn,
|
|
4049
4341
|
});
|
|
4050
4342
|
if (!contextListToDoResult.success) {
|
|
4051
4343
|
return set(contextListToDoResult, ['data', 'message'], "Error in runLump: Failed to get to do context list. Original Error: " + contextListToDoResult.data.message);
|
|
@@ -4082,6 +4374,7 @@ async function runLump(input) {
|
|
|
4082
4374
|
teardownWorkspaceFn,
|
|
4083
4375
|
logger,
|
|
4084
4376
|
getKeepHistoryFilePathFn,
|
|
4377
|
+
signal,
|
|
4085
4378
|
});
|
|
4086
4379
|
if (!executeStepsResult.success) {
|
|
4087
4380
|
return set(executeStepsResult, ['data', 'message'], "Error in runLump: Failed to execute steps for context list. Original Error: " + executeStepsResult.data.message);
|
|
@@ -4112,9 +4405,13 @@ exports.getCodeBasePaths = getCodeBasePaths;
|
|
|
4112
4405
|
exports.getContextStatus = getContextStatus;
|
|
4113
4406
|
exports.getToDoContextList = getToDoContextList;
|
|
4114
4407
|
exports.historyFormatFromPath = historyFormatFromPath;
|
|
4408
|
+
exports.isProcessAlive = isProcessAlive;
|
|
4409
|
+
exports.killProcessTree = killProcessTree;
|
|
4410
|
+
exports.nodeErrnoCode = nodeErrnoCode;
|
|
4115
4411
|
exports.parseGitLogHashSubjectLines = parseGitLogHashSubjectLines;
|
|
4116
4412
|
exports.pathExists = pathExists;
|
|
4117
4413
|
exports.readHistoryFile = readHistoryFile;
|
|
4414
|
+
exports.refreshRemoteTrackingRefs = refreshRemoteTrackingRefs;
|
|
4118
4415
|
exports.resolveSpawnExecutable = resolveSpawnExecutable;
|
|
4119
4416
|
exports.runLump = runLump;
|
|
4120
4417
|
exports.set = set;
|