@zgeoff/atc 3.1.0 → 3.1.1
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/package.json +1 -1
- package/src/daemon/restore-fleet.ts +19 -6
- package/src/daemon/sessions.ts +15 -4
- package/src/shared/resolve-repo-root.ts +29 -11
package/package.json
CHANGED
|
@@ -80,13 +80,26 @@ export async function restoreFleet(params: RestoreFleetParams): Promise<RestoreF
|
|
|
80
80
|
// until revived by hand, so no terminal is adopted for them. An entry
|
|
81
81
|
// whose session is still listed, dead, revives that session in place
|
|
82
82
|
// rather than listing a second session under the same id.
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// A row that cannot be registered is logged and skipped, so one bad row
|
|
84
|
+
// never keeps the rest of the fleet down.
|
|
85
|
+
const registered: { session: Session; revive: boolean }[] = [];
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
try {
|
|
89
|
+
const listed = mgr.sessions.find((s) => s.id === entry.sessionID);
|
|
90
|
+
|
|
91
|
+
const row =
|
|
92
|
+
listed === undefined
|
|
93
|
+
? { session: mgr.restore(entry), revive: false }
|
|
94
|
+
: { session: listed, revive: true };
|
|
95
|
+
|
|
96
|
+
registered.push(row);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
99
|
+
|
|
100
|
+
mgr.log(`atc could not restore session ${entry.sessionID} (${reason})`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
90
103
|
|
|
91
104
|
const queued = registered
|
|
92
105
|
.filter((r) => r.revive || r.session.state !== 'exited')
|
package/src/daemon/sessions.ts
CHANGED
|
@@ -535,6 +535,17 @@ export class SessionManager {
|
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
+
// An exited row never runs again until revived, and a row on a remote
|
|
539
|
+
// target holds a path from another machine, so neither resolves a
|
|
540
|
+
// repository on this one: the directory is its own root.
|
|
541
|
+
private pickRestoredRepoRoot(cwd: string, exited: boolean, target: string): string {
|
|
542
|
+
if (exited || this.targets.get(target)?.provider?.remote === true) {
|
|
543
|
+
return cwd;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return resolveRepoRoot(cwd);
|
|
547
|
+
}
|
|
548
|
+
|
|
538
549
|
// Registers a fleet entry as a session with no terminal yet, under the
|
|
539
550
|
// atc session id its row holds, so a fleet-wide restore can show every
|
|
540
551
|
// incoming session at once; adopting it later attaches the terminal.
|
|
@@ -584,7 +595,7 @@ export class SessionManager {
|
|
|
584
595
|
agent: entry.agent,
|
|
585
596
|
pinned: entry.pinned ?? false,
|
|
586
597
|
lastAttachedAt: entry.lastAttachedAt ?? Date.now(),
|
|
587
|
-
repoRoot:
|
|
598
|
+
repoRoot: this.pickRestoredRepoRoot(entry.cwd, exited, target),
|
|
588
599
|
namedBy: 'auto',
|
|
589
600
|
createdAt: Date.now(),
|
|
590
601
|
parent: parent !== null && this.sessions.some((s) => s.id === parent) ? parent : null,
|
|
@@ -757,6 +768,7 @@ export class SessionManager {
|
|
|
757
768
|
s.kind = 'pty';
|
|
758
769
|
s.state = 'running';
|
|
759
770
|
s.lastMsg = 'revived';
|
|
771
|
+
s.repoRoot = provider.remote ? s.cwd : resolveRepoRoot(s.cwd);
|
|
760
772
|
s.desired = 'run';
|
|
761
773
|
s.suspended = false;
|
|
762
774
|
|
|
@@ -959,9 +971,8 @@ export class SessionManager {
|
|
|
959
971
|
overrides.trustClonedWorkspace ??
|
|
960
972
|
this.targets.get(target)?.options['trustClonedWorkspace'] === true;
|
|
961
973
|
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
// remote directory is not on the daemon's machine, so it is its own root.
|
|
974
|
+
// A remote directory is not on the daemon's machine, so it is its own
|
|
975
|
+
// root.
|
|
965
976
|
const repoRoot = provider.remote ? cwd : resolveRepoRoot(cwd);
|
|
966
977
|
const hostKey = this.pickHostKey(id, parent, target, execution.identity);
|
|
967
978
|
const auth = this.resolveHarnessAuth(adapter, provider, target);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Stats } from 'node:fs';
|
|
1
2
|
import { readFileSync, statSync } from 'node:fs';
|
|
2
3
|
import { dirname, join } from 'node:path';
|
|
3
4
|
|
|
@@ -6,17 +7,33 @@ import { dirname, join } from 'node:path';
|
|
|
6
7
|
* overlay. Walks toward the filesystem root looking for a `.git` entry: a
|
|
7
8
|
* directory holding HEAD marks the repository root itself, and a linked
|
|
8
9
|
* worktree's `.git` file points back at the main repository, so worktrees
|
|
9
|
-
* cluster with it. A directory outside any repository resolves to itself
|
|
10
|
+
* cluster with it. A directory outside any repository resolves to itself, and
|
|
11
|
+
* so does one whose path cannot be read: a filesystem error other than a
|
|
12
|
+
* missing entry ends the walk instead of throwing.
|
|
10
13
|
*/
|
|
11
14
|
export function resolveRepoRoot(cwd: string): string {
|
|
12
15
|
let dir = cwd;
|
|
13
16
|
|
|
14
17
|
while (true) {
|
|
15
18
|
const marker = join(dir, '.git');
|
|
16
|
-
const stat =
|
|
19
|
+
const stat = tryStat(marker);
|
|
17
20
|
|
|
18
|
-
if (stat
|
|
19
|
-
return
|
|
21
|
+
if (stat === null) {
|
|
22
|
+
return cwd;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (stat !== undefined && stat.isDirectory()) {
|
|
26
|
+
// git needs HEAD in a repository, and an empty `.git` directory in a shared
|
|
27
|
+
// temporary directory would otherwise cluster every session under it.
|
|
28
|
+
const head = tryStat(join(marker, 'HEAD'));
|
|
29
|
+
|
|
30
|
+
if (head === null) {
|
|
31
|
+
return cwd;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (head !== undefined) {
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
if (stat !== undefined && stat.isFile()) {
|
|
@@ -33,13 +50,14 @@ export function resolveRepoRoot(cwd: string): string {
|
|
|
33
50
|
}
|
|
34
51
|
}
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
// The stat of a path: undefined when nothing is there, null when the path
|
|
54
|
+
// cannot be read at all.
|
|
55
|
+
function tryStat(path: string): Stats | undefined | null {
|
|
56
|
+
try {
|
|
57
|
+
return statSync(path, { throwIfNoEntry: false });
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
// A linked worktree's `.git` file reads `gitdir: <main>/.git/worktrees/<name>`.
|