stim 1.25.0 → 1.27.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 +1 -1
- package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
- package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
- package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
- package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
- package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
- package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
- package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
- package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
- package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
- package/dist/api.d.mts +20 -7
- package/dist/api.mjs +42 -32
- package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
- package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
- package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
- package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
- package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
- package/dist/auto-placement-z-H5pJza.mjs +350 -0
- package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
- package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
- package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
- package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
- package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
- package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
- package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
- package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
- package/dist/cli.mjs +1 -1
- package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
- package/dist/collector-run.d.mts +10 -9
- package/dist/collector-run.mjs +42 -27
- package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
- package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
- package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
- package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
- package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
- package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
- package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
- package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
- package/dist/device-host-worker.mjs +19 -21
- package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
- package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
- package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
- package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
- package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
- package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
- package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
- package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
- package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
- package/dist/eas-fallback-9fqb46kW.mjs +87 -0
- package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
- package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
- package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
- package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
- package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
- package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
- package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
- package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
- package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
- package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
- package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
- package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
- package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
- package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
- package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
- package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
- package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
- package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
- package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
- package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
- package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
- package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
- package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
- package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
- package/dist/lightened-BCsdAGj_.mjs +171 -0
- package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
- package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
- package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
- package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +30 -13
- package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
- package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
- package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
- package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
- package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
- package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
- package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
- package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
- package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
- package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
- package/dist/offload-worker.d.mts +3 -1
- package/dist/offload-worker.mjs +34 -25
- package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
- package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
- package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
- package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
- package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
- package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
- package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
- package/dist/prebuild-BDpToDgR.mjs +1246 -0
- package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
- package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
- package/dist/pull-requests.mjs +1 -1
- package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
- package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
- package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
- package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
- package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
- package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
- package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
- package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
- package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
- package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
- package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
- package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
- package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
- package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
- package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
- package/dist/server-expo-Ba_Y21vB.mjs +2 -0
- package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
- package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
- package/dist/settings-payload-cm60Zhib.mjs +257 -0
- package/dist/settings-write-BdNRLlBn.mjs +185 -0
- package/dist/settings.schema.json +84 -39
- package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
- package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
- package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
- package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
- package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
- package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
- package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
- package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
- package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
- package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
- package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
- package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
- package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
- package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
- package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
- package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
- package/dist/supervisor-run.d.mts +1 -0
- package/dist/supervisor-run.mjs +17 -18
- package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
- package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
- package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
- package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
- package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
- package/dist/toolchain-JYAOquPg.mjs +2 -0
- package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
- package/dist/trigger-qp2ji5wE.mjs +2 -0
- package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
- package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
- package/dist/web-Je6abcpz.mjs +2 -0
- package/dist/web-run.mjs +9 -9
- package/dist/windows-report.mjs +1 -1
- package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
- package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
- package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
- package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
- package/package.json +8 -8
- package/dist/ccache-h1FKhZeg.mjs +0 -143
- package/dist/created-devices-D2Aca-xu.mjs +0 -52
- package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
- package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
- package/dist/native-run-D5OJsGwA.mjs +0 -276
- package/dist/paths-DQtTgsBf.mjs +0 -42
- package/dist/prebuild-JUaqOLpc.mjs +0 -501
- package/dist/server-expo-C16Zf2k7.mjs +0 -2
- package/dist/settings-write-n0zwfohx.mjs +0 -360
- package/dist/toolchain-DB1aeqxO.mjs +0 -2
- package/dist/toolchain-VzLJzZ7O.mjs +0 -908
- package/dist/trigger-BLsHgAGR.mjs +0 -2
- package/dist/web-DS4UDWvf.mjs +0 -2
- package/dist/xcode-CiIIDnuD.mjs +0 -910
|
@@ -1,809 +1,808 @@
|
|
|
1
|
-
import { D as ownership_claim_exports, E as debugLog, l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-
|
|
2
|
-
import {
|
|
3
|
-
import { l as gitQuietAsync, o as git$1, s as gitAsync } from "./operation-exec-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { s as pullRequestLookups } from "./pull-request-CbNlkFok.mjs";
|
|
1
|
+
import { D as ownership_claim_exports, E as debugLog, l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-aULwtNly.mjs";
|
|
2
|
+
import { E as workspaceDir, M as withDirLock, _ as ensureWorkspaceStorage, h as discoverAgentSessions, m as attributeAgentSessions, o as updateWorkspaceState, p as agentKey } from "./workspace-state-BjqdjnJR.mjs";
|
|
3
|
+
import { l as gitQuietAsync, o as git$1, s as gitAsync } from "./operation-exec-Dp4FuIGR.mjs";
|
|
4
|
+
import { Dt as linkedWorktreesOnDisk, St as gitCommonDirOnDisk } from "./settings-BDVI8Ivc.mjs";
|
|
5
|
+
import { V as ownedAvdDirectory } from "./android-DriNoAVy.mjs";
|
|
6
|
+
import { s as pullRequestLookups } from "./pull-request-BzLmzAIQ.mjs";
|
|
8
7
|
import { clearClaimChild, markClaimChildPending, releaseClaim, setClaimChild, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
|
|
9
8
|
import { randomUUID } from "node:crypto";
|
|
10
9
|
import { existsSync, mkdirSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
11
10
|
import { homedir } from "node:os";
|
|
12
11
|
import { basename, dirname, join, sep } from "node:path";
|
|
13
|
-
import { ENDED_AGENT_RETENTION_MS, READY_PHASE_MS, WARM_KEY, agentSessionsCacheFile, diskUsageCacheFile, diskUsageFailureFile, diskUsageWalkClaims, pullRequestCacheFile, readAgentSessionsCache, readDiskUsage, readDiskUsageFailure, readEndedAgentSessions, readPullRequestCache, readWarmRecord, readWorkspaceAgent, readWorkspaceState, workspaceEndedAgentsFile, workspaceEndedAgentsLock } from "@stim-cli/core/state";
|
|
12
|
+
import { ENDED_AGENT_RETENTION_MS, READY_PHASE_MS, WARM_KEY, agentSessionsCacheFile, diskUsageCacheFile, diskUsageFailureFile, diskUsageWalkClaims, pullRequestCacheFile, readAgentSessionsCache, readDiskUsage, readDiskUsageFailure, readEndedAgentSessions, readPullRequestCache, readWarmRecord, readWorkspaceAgent, readWorkspaceState, trace, workspaceEndedAgentsFile, workspaceEndedAgentsLock } from "@stim-cli/core/state";
|
|
14
13
|
import { captureProcessIdentity } from "@stim-cli/core/process-identity";
|
|
15
|
-
//#region src/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
function fetchTime(path) {
|
|
28
|
-
path = path?.trim() ?? null;
|
|
14
|
+
//#region src/status-measures.ts
|
|
15
|
+
const LIVE_DISK_MAX_AGE_MS = 3e5;
|
|
16
|
+
const IDLE_DISK_MAX_AGE_MS = 36e5;
|
|
17
|
+
const DU_TIMEOUT_MS = 12e4;
|
|
18
|
+
const DU_NICENESS = "19";
|
|
19
|
+
const PULL_REQUEST_MAX_AGE_MS = 3e5;
|
|
20
|
+
const PULL_REQUEST_CHECK_MS = 6e4;
|
|
21
|
+
const GIT_TIMEOUT_MS$1 = 5e3;
|
|
22
|
+
const AGENT_DISCOVERY_MS = 15e3;
|
|
23
|
+
const AGENT_CACHE_MAX_AGE_MS = 12e4;
|
|
24
|
+
function writeCacheFile(file, entry) {
|
|
25
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
29
26
|
try {
|
|
30
|
-
|
|
27
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
28
|
+
writeFileSync(temporary, JSON.stringify(entry));
|
|
29
|
+
renameSync(temporary, file);
|
|
31
30
|
} catch {
|
|
32
|
-
|
|
31
|
+
rmSync(temporary, { force: true });
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
function forgetStatusMeasures(worktree, workspaces) {
|
|
35
|
+
const roots = [.../* @__PURE__ */ new Set([worktree, ...workspaces])];
|
|
36
|
+
const files = [pullRequestCacheFile(worktree), ...roots.flatMap((root) => [
|
|
37
|
+
diskUsageCacheFile(root),
|
|
38
|
+
diskUsageFailureFile(root),
|
|
39
|
+
diskUsageCacheFile(join(root, "node_modules")),
|
|
40
|
+
diskUsageFailureFile(join(root, "node_modules")),
|
|
41
|
+
diskUsageCacheFile(workspaceDir(root)),
|
|
42
|
+
diskUsageFailureFile(workspaceDir(root))
|
|
43
|
+
])];
|
|
44
|
+
for (const file of files) try {
|
|
45
|
+
rmSync(file, { force: true });
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
function simulatorDataDir(udid) {
|
|
49
|
+
return join(homedir(), "Library", "Developer", "CoreSimulator", "Devices", udid);
|
|
50
|
+
}
|
|
51
|
+
function checkoutDir(state) {
|
|
52
|
+
if (state.worktree?.path) return state.worktree.path;
|
|
53
|
+
const common = gitCommonDirOnDisk(state.path);
|
|
54
|
+
return common && basename(common) === ".git" ? dirname(common) : state.path;
|
|
55
|
+
}
|
|
56
|
+
function deviceFolders(state) {
|
|
57
|
+
const folders = [];
|
|
58
|
+
for (const { ios, android } of [state, ...state.slots ?? []]) {
|
|
59
|
+
if (ios?.owned) folders.push({
|
|
60
|
+
device: ios,
|
|
61
|
+
path: simulatorDataDir(ios.udid)
|
|
62
|
+
});
|
|
63
|
+
if (android?.owned && !android.physical && android.name) {
|
|
64
|
+
const path = ownedAvdDirectory(android.name);
|
|
65
|
+
if (path) folders.push({
|
|
66
|
+
device: android,
|
|
67
|
+
path
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return folders;
|
|
72
|
+
}
|
|
73
|
+
/** The folders an environment's disk use sums: its checkout, its node_modules folders and Stim's workspace folder. */
|
|
74
|
+
function environmentFolders(state) {
|
|
75
|
+
const worktree = checkoutDir(state);
|
|
38
76
|
return {
|
|
39
|
-
|
|
40
|
-
|
|
77
|
+
worktree,
|
|
78
|
+
nodeModules: [.../* @__PURE__ */ new Set([join(worktree, "node_modules"), join(state.path, "node_modules")])],
|
|
79
|
+
build: workspaceDir(state.path)
|
|
41
80
|
};
|
|
42
81
|
}
|
|
43
|
-
|
|
82
|
+
/** The environment's disk use from the cached measurements; null before any folder was measured. */
|
|
83
|
+
function environmentDisk(folders) {
|
|
84
|
+
const worktree = readDiskUsage(folders.worktree);
|
|
85
|
+
const modules = folders.nodeModules.map((path) => readDiskUsage(path));
|
|
86
|
+
const build = readDiskUsage(folders.build);
|
|
87
|
+
const measured = [
|
|
88
|
+
worktree,
|
|
89
|
+
...modules,
|
|
90
|
+
build
|
|
91
|
+
].filter((entry) => entry !== null);
|
|
92
|
+
if (!measured.length) return null;
|
|
44
93
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
94
|
+
worktreeBytes: worktree?.bytes ?? null,
|
|
95
|
+
nodeModulesBytes: modules.every((entry) => entry !== null) ? modules.reduce((sum, entry) => sum + entry.bytes, 0) : null,
|
|
96
|
+
buildBytes: build?.bytes ?? null,
|
|
97
|
+
measuredAt: measured.map((entry) => entry.measuredAt).toSorted()[0]
|
|
48
98
|
};
|
|
49
99
|
}
|
|
50
|
-
function
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
100
|
+
function statusPullRequest(fact, checkedAt) {
|
|
101
|
+
return {
|
|
102
|
+
number: fact.number,
|
|
103
|
+
url: fact.url,
|
|
104
|
+
title: fact.title ?? "",
|
|
105
|
+
state: fact.state === "open" && fact.draft ? "draft" : fact.state,
|
|
106
|
+
checks: fact.checks ?? null,
|
|
107
|
+
reviewDecision: fact.reviewDecision ?? null,
|
|
108
|
+
checkedAt
|
|
54
109
|
};
|
|
55
110
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Writes a pull request lookup into the cache `status` and report-only `gc` read. A lookup made without detail is
|
|
113
|
+
* marked `partial`, so `status` asks again for the title, checks and review decision; until then it keeps the title
|
|
114
|
+
* cached for the same pull request, and the checks and review decision cached for the same HEAD.
|
|
115
|
+
*/
|
|
116
|
+
function recordPullRequestLookup({ path, branch, head }, fact, checkedAt) {
|
|
117
|
+
let pullRequest = fact ? statusPullRequest(fact, checkedAt) : null;
|
|
118
|
+
const partial = pullRequest !== null && fact?.title === void 0;
|
|
119
|
+
const previous = partial ? readPullRequestCache(path) : null;
|
|
120
|
+
if (pullRequest && previous?.pullRequest?.number === pullRequest.number) {
|
|
121
|
+
const same = previous.head === head;
|
|
122
|
+
pullRequest = {
|
|
123
|
+
...pullRequest,
|
|
124
|
+
title: previous.pullRequest.title,
|
|
125
|
+
checks: same ? previous.pullRequest.checks : null,
|
|
126
|
+
reviewDecision: same ? previous.pullRequest.reviewDecision : null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const entry = {
|
|
130
|
+
path,
|
|
131
|
+
branch,
|
|
132
|
+
head,
|
|
133
|
+
checkedAt,
|
|
134
|
+
pullRequest,
|
|
135
|
+
...fact ? {
|
|
136
|
+
containsHead: fact.containsHead,
|
|
137
|
+
endedAt: fact.endedAt
|
|
138
|
+
} : {},
|
|
139
|
+
...partial ? { partial: true } : {}
|
|
63
140
|
};
|
|
141
|
+
writeCacheFile(pullRequestCacheFile(path), entry);
|
|
64
142
|
}
|
|
65
|
-
function
|
|
143
|
+
function canonicalPath(path) {
|
|
66
144
|
try {
|
|
67
|
-
return
|
|
68
|
-
} catch
|
|
69
|
-
|
|
70
|
-
return null;
|
|
145
|
+
return realpathSync(path);
|
|
146
|
+
} catch {
|
|
147
|
+
return path;
|
|
71
148
|
}
|
|
72
149
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
150
|
+
function agentWorkspaces(states) {
|
|
151
|
+
return states.map((state) => ({
|
|
152
|
+
path: canonicalPath(state.path),
|
|
153
|
+
root: canonicalPath(checkoutDir(state)),
|
|
154
|
+
recorded: readWorkspaceAgent(readWorkspaceState(state.path))
|
|
78
155
|
}));
|
|
79
156
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"HEAD"
|
|
98
|
-
]))?.trim();
|
|
99
|
-
return ref?.startsWith("refs/heads/") ? ref : null;
|
|
100
|
-
}
|
|
101
|
-
function* upstreamGone(branch) {
|
|
102
|
-
if (!branch) return false;
|
|
103
|
-
const [upstream, state] = ((yield* probe([
|
|
104
|
-
"for-each-ref",
|
|
105
|
-
"--format=%(upstream)%00%(upstream:track)",
|
|
106
|
-
branch
|
|
107
|
-
])) ?? "").trim().split("\0");
|
|
108
|
-
return Boolean(upstream) && state === "[gone]";
|
|
157
|
+
function applyAgentSessions(states, now) {
|
|
158
|
+
const cache = readAgentSessionsCache();
|
|
159
|
+
const sessions = cache && now - Date.parse(cache.discoveredAt) <= AGENT_CACHE_MAX_AGE_MS ? cache.sessions : [];
|
|
160
|
+
const discovered = new Set(sessions.map(agentKey));
|
|
161
|
+
const ended = states.map((state) => readEndedAgentSessions(state.path, now));
|
|
162
|
+
const workspaces = agentWorkspaces(states);
|
|
163
|
+
workspaces.forEach((workspace, i) => {
|
|
164
|
+
const { recorded } = workspace;
|
|
165
|
+
if (!recorded || discovered.has(agentKey(recorded))) return;
|
|
166
|
+
if (ended[i].some((session) => agentKey(session) === agentKey(recorded))) workspace.recorded = null;
|
|
167
|
+
});
|
|
168
|
+
attributeAgentSessions(workspaces, sessions, now).forEach((agents, i) => {
|
|
169
|
+
if (agents.length) states[i].agents = agents;
|
|
170
|
+
const running = new Set(agents.map(agentKey));
|
|
171
|
+
const endedAgents = ended[i].filter((session) => !running.has(agentKey(session)));
|
|
172
|
+
if (endedAgents.length) states[i].endedAgents = endedAgents;
|
|
173
|
+
});
|
|
109
174
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const entry of entries.split("\n")) {
|
|
119
|
-
const [sha = "", ...subject] = entry.split(" ");
|
|
120
|
-
if (/^(commit|cherry-pick|rebase|revert)\b/.test(subject.join(" ")) && (yield* probe([
|
|
121
|
-
"merge-base",
|
|
122
|
-
"--is-ancestor",
|
|
123
|
-
sha,
|
|
124
|
-
head
|
|
125
|
-
])) !== null) return true;
|
|
175
|
+
function writeFileAtomic(file, entry) {
|
|
176
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
177
|
+
try {
|
|
178
|
+
writeFileSync(temporary, JSON.stringify(entry));
|
|
179
|
+
renameSync(temporary, file);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
rmSync(temporary, { force: true });
|
|
182
|
+
throw error;
|
|
126
183
|
}
|
|
127
|
-
return false;
|
|
128
184
|
}
|
|
129
|
-
function
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
detail: `no commits of its own beyond ${name}`,
|
|
135
|
-
noOwnCommits: true
|
|
185
|
+
function recordEndedAgents(states, gone, endedAt, now) {
|
|
186
|
+
const goneKeys = new Set(gone.map(agentKey));
|
|
187
|
+
const result = {
|
|
188
|
+
changed: false,
|
|
189
|
+
failed: false
|
|
136
190
|
};
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"--parents",
|
|
154
|
-
`${head}..${ref}`
|
|
155
|
-
])).split("\n").some((line) => line.split(" ")[1] === head) || !(yield* committedOn(branch, head))) return noOwnCommits;
|
|
156
|
-
const descendants = new Set((yield* git([
|
|
157
|
-
"rev-list",
|
|
158
|
-
"--ancestry-path",
|
|
159
|
-
`${head}..${ref}`
|
|
160
|
-
])).split("\n"));
|
|
161
|
-
const landed = (yield* git([
|
|
162
|
-
"rev-list",
|
|
163
|
-
"--first-parent",
|
|
164
|
-
"--reverse",
|
|
165
|
-
`${head}..${ref}`
|
|
166
|
-
])).split("\n").find((commit) => descendants.has(commit));
|
|
167
|
-
if (!landed) throw new Error(`no commit on ${name} merges ${head}`);
|
|
168
|
-
return {
|
|
169
|
-
merged: true,
|
|
170
|
-
into: name,
|
|
171
|
-
head,
|
|
172
|
-
coversUnpushed: false,
|
|
173
|
-
mergedAt: yield* committedAt([landed])
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const aheadCount = Number(yield* git([
|
|
177
|
-
"rev-list",
|
|
178
|
-
"--count",
|
|
179
|
-
`${base}..${head}`
|
|
180
|
-
]));
|
|
181
|
-
if (aheadCount > maxCommits) return {
|
|
182
|
-
merged: false,
|
|
183
|
-
unknown: true,
|
|
184
|
-
detail: `merge state unknown: ${aheadCount} commits of its own ahead of the merge base with ${name} exceeds the ${maxCommits}-commit limit`
|
|
185
|
-
};
|
|
186
|
-
const files = (yield* git([
|
|
187
|
-
"diff",
|
|
188
|
-
"--name-only",
|
|
189
|
-
"--no-renames",
|
|
190
|
-
"-z",
|
|
191
|
-
base,
|
|
192
|
-
head
|
|
193
|
-
])).split("\0").filter(Boolean);
|
|
194
|
-
if (!files.length) return notMerged(`no net change beyond ${name}`);
|
|
195
|
-
const log = function* (range, pathspec = []) {
|
|
196
|
-
return yield* patch([
|
|
197
|
-
"log",
|
|
198
|
-
"-p",
|
|
199
|
-
"--no-merges",
|
|
200
|
-
...diffOptions,
|
|
201
|
-
"--format=commit %H",
|
|
202
|
-
"--stdin"
|
|
203
|
-
], pathspec.length ? `${range}\n--\n${pathspec.join("\n")}\n` : `${range}\n`);
|
|
204
|
-
};
|
|
205
|
-
const upstream = yield* patchIdCommits(yield* log(`${base}..${ref}`, files));
|
|
206
|
-
const patchEquivalent = function* (ids) {
|
|
207
|
-
return {
|
|
208
|
-
merged: true,
|
|
209
|
-
into: name,
|
|
210
|
-
head,
|
|
211
|
-
coversUnpushed: yield* upstreamGone(branch),
|
|
212
|
-
mergedAt: yield* committedAt(ids.map((id) => upstream.get(id)))
|
|
213
|
-
};
|
|
214
|
-
};
|
|
215
|
-
const squash = yield* patchIds(yield* patch([
|
|
216
|
-
"diff",
|
|
217
|
-
...diffOptions,
|
|
218
|
-
base,
|
|
219
|
-
head
|
|
220
|
-
]));
|
|
221
|
-
if (squash.length === 1 && upstream.has(squash[0])) return yield* patchEquivalent(squash);
|
|
222
|
-
if (!(yield* git([
|
|
223
|
-
"rev-list",
|
|
224
|
-
"--merges",
|
|
225
|
-
`${base}..${head}`
|
|
226
|
-
]))) {
|
|
227
|
-
const own = yield* patchIds(yield* log(`${base}..${head}`));
|
|
228
|
-
if (own.length && own.every((id) => upstream.has(id))) return yield* patchEquivalent(own);
|
|
191
|
+
attributeAgentSessions(agentWorkspaces(states), gone, now).forEach((sessions, i) => {
|
|
192
|
+
const root = states[i].path;
|
|
193
|
+
const ended = sessions.filter((session) => goneKeys.has(agentKey(session)));
|
|
194
|
+
if (!ended.length || !existsSync(workspaceDir(root))) return;
|
|
195
|
+
try {
|
|
196
|
+
withDirLock(workspaceEndedAgentsLock(root), () => {
|
|
197
|
+
const kept = new Map(readEndedAgentSessions(root, now).map((session) => [agentKey(session), session]));
|
|
198
|
+
for (const { pid: _pid, ...session } of ended) kept.set(agentKey(session), {
|
|
199
|
+
...session,
|
|
200
|
+
endedAt
|
|
201
|
+
});
|
|
202
|
+
writeFileAtomic(workspaceEndedAgentsFile(root), { sessions: [...kept.values()] });
|
|
203
|
+
});
|
|
204
|
+
result.changed = true;
|
|
205
|
+
} catch {
|
|
206
|
+
result.failed = true;
|
|
229
207
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const timedOut = isTimeoutError(error) ? { timedOut: true } : {};
|
|
233
|
-
return {
|
|
234
|
-
merged: false,
|
|
235
|
-
unknown: true,
|
|
236
|
-
detail: `merge state unknown: ${failure(error)}`,
|
|
237
|
-
...timedOut
|
|
238
|
-
};
|
|
239
|
-
}
|
|
208
|
+
});
|
|
209
|
+
return result;
|
|
240
210
|
}
|
|
241
211
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* - HEAD is an ancestor of the default branch, off its first-parent line, and the branch's reflog shows a commit made
|
|
246
|
-
* on it that HEAD contains, so a merge commit brought the branch's own work in. A branch with no commit of its own
|
|
247
|
-
* is not merged.
|
|
248
|
-
* - The branch changes the tree, has no merge commits, and every commit since the merge base has the same
|
|
249
|
-
* `git patch-id --verbatim` as a commit on the default branch (a rebase merge).
|
|
250
|
-
* - The branch changes the tree and its whole diff since the merge base has the same verbatim patch id as a commit on
|
|
251
|
-
* the default branch, limited to the files the branch changes (a squash merge).
|
|
252
|
-
* A HEAD the default branch contains with no commit of its own on the branch is not merged and carries
|
|
253
|
-
* `noOwnCommits`. Anything git cannot answer is unknown, never merged. `coversUnpushed` is true for a patch-equivalent HEAD whose
|
|
254
|
-
* upstream branch was deleted: its commits exist only locally, but their change is on the default branch.
|
|
212
|
+
* Adds the cached measurements to a status read: each environment's `disk`, `agents` and `endedAgents`, each owned
|
|
213
|
+
* device's `disk`, and each linked worktree's `pullRequest` when the cache holds a lookup for its current branch.
|
|
214
|
+
* Reads files only.
|
|
255
215
|
*/
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
...options
|
|
265
|
-
}));
|
|
266
|
-
} catch (error) {
|
|
267
|
-
step = reads.throw(error);
|
|
216
|
+
function applyStatusMeasures(states, worktrees) {
|
|
217
|
+
applyAgentSessions(states, Date.now());
|
|
218
|
+
for (const state of states) {
|
|
219
|
+
const disk = environmentDisk(environmentFolders(state));
|
|
220
|
+
if (disk) state.disk = disk;
|
|
221
|
+
for (const { device, path } of deviceFolders(state)) {
|
|
222
|
+
const measure = readDiskUsage(path);
|
|
223
|
+
if (measure) device.disk = measure;
|
|
268
224
|
}
|
|
269
225
|
}
|
|
270
|
-
|
|
226
|
+
for (const worktree of worktrees) {
|
|
227
|
+
const cached = worktree.branch ? readPullRequestCache(worktree.path) : null;
|
|
228
|
+
if (cached && cached.branch === worktree.branch) worktree.pullRequest = cached.pullRequest;
|
|
229
|
+
}
|
|
271
230
|
}
|
|
272
|
-
/** `
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
231
|
+
/** Kilobytes from `du -sk` output, summed over its lines, or null when it printed none. */
|
|
232
|
+
function parseDuKilobytes(output) {
|
|
233
|
+
const sizes = [...output.matchAll(/^(\d+)\s/gm)].map((match) => Number(match[1]));
|
|
234
|
+
return sizes.length ? sizes.reduce((sum, kb) => sum + kb, 0) : null;
|
|
235
|
+
}
|
|
236
|
+
/** The folders inside a checkout that another measurement owns: its linked worktrees and Stim's workspace folder. */
|
|
237
|
+
function nestedFolders(state) {
|
|
238
|
+
const common = gitCommonDirOnDisk(state.path);
|
|
239
|
+
return [workspaceDir(state.path), ...common ? linkedWorktreesOnDisk(common).map((entry) => entry.path) : []];
|
|
240
|
+
}
|
|
241
|
+
/** The entries to hand to `du` so it sums `root` without descending into `nested`; `[root]` when none is inside it. */
|
|
242
|
+
function walkTargets(root, nested) {
|
|
243
|
+
const top = canonicalPath(root);
|
|
244
|
+
const skip = new Set(nested.map(canonicalPath));
|
|
245
|
+
const inside = [...skip].filter((path) => path.startsWith(top + sep));
|
|
246
|
+
if (!inside.length) return [root];
|
|
247
|
+
const targets = [];
|
|
248
|
+
const visit = (dir) => {
|
|
249
|
+
let names;
|
|
279
250
|
try {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}));
|
|
285
|
-
} catch (error) {
|
|
286
|
-
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
287
|
-
execution.signal?.throwIfAborted();
|
|
288
|
-
step = reads.throw(error);
|
|
251
|
+
names = readdirSync(dir);
|
|
252
|
+
} catch {
|
|
253
|
+
targets.push(dir);
|
|
254
|
+
return;
|
|
289
255
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
"--git-path",
|
|
300
|
-
"FETCH_HEAD"
|
|
301
|
-
], execution));
|
|
256
|
+
for (const name of names) {
|
|
257
|
+
const path = join(dir, name);
|
|
258
|
+
if (skip.has(path)) continue;
|
|
259
|
+
if (inside.some((nestedPath) => nestedPath.startsWith(path + sep))) visit(path);
|
|
260
|
+
else targets.push(path);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
visit(top);
|
|
264
|
+
return targets;
|
|
302
265
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return defaultBranch(repo, await gitQuietAsync(repo, [
|
|
306
|
-
"symbolic-ref",
|
|
307
|
-
"--quiet",
|
|
308
|
-
"refs/remotes/origin/HEAD"
|
|
309
|
-
], execution));
|
|
266
|
+
function isStale(measure, maxAgeMs, now) {
|
|
267
|
+
return !measure || now - Date.parse(measure.measuredAt) >= maxAgeMs;
|
|
310
268
|
}
|
|
311
|
-
|
|
312
|
-
async function fetchDefaultBranchAsync(repo, now = Date.now(), execution = {}) {
|
|
313
|
-
execution.signal?.throwIfAborted();
|
|
314
|
-
const target = await localDefaultBranchAsync(repo, execution);
|
|
315
|
-
if ("error" in target) return target;
|
|
316
|
-
const at = await lastFetchedAtAsync(repo, execution);
|
|
317
|
-
if (at !== null && now - at < 6e5) return target;
|
|
318
|
-
const branch = target.ref.slice(20);
|
|
269
|
+
function takeWalkClaim() {
|
|
319
270
|
try {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
"--no-recurse-submodules",
|
|
325
|
-
"origin",
|
|
326
|
-
`+refs/heads/${branch}:${target.ref}`
|
|
327
|
-
], {
|
|
328
|
-
...execution,
|
|
329
|
-
write: true,
|
|
330
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
331
|
-
env: {
|
|
332
|
-
...execution.env,
|
|
333
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
334
|
-
},
|
|
335
|
-
omitEnv: execution.omitEnv?.filter((name) => name !== "GIT_TERMINAL_PROMPT")
|
|
271
|
+
const attempt = tryAcquireClaim({
|
|
272
|
+
root: diskUsageWalkClaims(),
|
|
273
|
+
mode: "exclusive",
|
|
274
|
+
label: "disk usage walk"
|
|
336
275
|
});
|
|
276
|
+
if (attempt.pending) releaseClaim(attempt.pending);
|
|
277
|
+
return attempt.acquired ?? null;
|
|
337
278
|
} catch (error) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
return { error: `git fetch origin ${branch} failed: ${failure(error)}` };
|
|
341
|
-
}
|
|
342
|
-
return target;
|
|
343
|
-
}
|
|
344
|
-
//#endregion
|
|
345
|
-
//#region src/status-measures.ts
|
|
346
|
-
const LIVE_DISK_MAX_AGE_MS = 3e5;
|
|
347
|
-
const IDLE_DISK_MAX_AGE_MS = 36e5;
|
|
348
|
-
const DU_TIMEOUT_MS = 12e4;
|
|
349
|
-
const DU_NICENESS = "19";
|
|
350
|
-
const PULL_REQUEST_MAX_AGE_MS = 3e5;
|
|
351
|
-
const PULL_REQUEST_CHECK_MS = 6e4;
|
|
352
|
-
const GIT_TIMEOUT_MS = 5e3;
|
|
353
|
-
const AGENT_DISCOVERY_MS = 15e3;
|
|
354
|
-
const AGENT_CACHE_MAX_AGE_MS = 12e4;
|
|
355
|
-
function writeCacheFile(file, entry) {
|
|
356
|
-
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
357
|
-
try {
|
|
358
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
359
|
-
writeFileSync(temporary, JSON.stringify(entry));
|
|
360
|
-
renameSync(temporary, file);
|
|
361
|
-
} catch {
|
|
362
|
-
rmSync(temporary, { force: true });
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
function forgetStatusMeasures(worktree, workspaces) {
|
|
366
|
-
const roots = [.../* @__PURE__ */ new Set([worktree, ...workspaces])];
|
|
367
|
-
const files = [pullRequestCacheFile(worktree), ...roots.flatMap((root) => [
|
|
368
|
-
diskUsageCacheFile(root),
|
|
369
|
-
diskUsageFailureFile(root),
|
|
370
|
-
diskUsageCacheFile(join(root, "node_modules")),
|
|
371
|
-
diskUsageFailureFile(join(root, "node_modules")),
|
|
372
|
-
diskUsageCacheFile(workspaceDir(root)),
|
|
373
|
-
diskUsageFailureFile(workspaceDir(root))
|
|
374
|
-
])];
|
|
375
|
-
for (const file of files) try {
|
|
376
|
-
rmSync(file, { force: true });
|
|
377
|
-
} catch {}
|
|
378
|
-
}
|
|
379
|
-
function simulatorDataDir(udid) {
|
|
380
|
-
return join(homedir(), "Library", "Developer", "CoreSimulator", "Devices", udid);
|
|
381
|
-
}
|
|
382
|
-
function checkoutDir(state) {
|
|
383
|
-
if (state.worktree?.path) return state.worktree.path;
|
|
384
|
-
const common = gitCommonDirOnDisk(state.path);
|
|
385
|
-
return common && basename(common) === ".git" ? dirname(common) : state.path;
|
|
386
|
-
}
|
|
387
|
-
function deviceFolders(state) {
|
|
388
|
-
const folders = [];
|
|
389
|
-
for (const { ios, android } of [state, ...state.slots ?? []]) {
|
|
390
|
-
if (ios?.owned) folders.push({
|
|
391
|
-
device: ios,
|
|
392
|
-
path: simulatorDataDir(ios.udid)
|
|
393
|
-
});
|
|
394
|
-
if (android?.owned && !android.physical && android.name) {
|
|
395
|
-
const path = ownedAvdDirectory(android.name);
|
|
396
|
-
if (path) folders.push({
|
|
397
|
-
device: android,
|
|
398
|
-
path
|
|
399
|
-
});
|
|
400
|
-
}
|
|
279
|
+
debugLog.log("disk-walk-claim", { error: String(error?.message ?? error) });
|
|
280
|
+
return null;
|
|
401
281
|
}
|
|
402
|
-
return folders;
|
|
403
|
-
}
|
|
404
|
-
/** The folders an environment's disk use sums: its checkout, its node_modules folders and Stim's workspace folder. */
|
|
405
|
-
function environmentFolders(state) {
|
|
406
|
-
const worktree = checkoutDir(state);
|
|
407
|
-
return {
|
|
408
|
-
worktree,
|
|
409
|
-
nodeModules: [.../* @__PURE__ */ new Set([join(worktree, "node_modules"), join(state.path, "node_modules")])],
|
|
410
|
-
build: workspaceDir(state.path)
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
/** The environment's disk use from the cached measurements; null before any folder was measured. */
|
|
414
|
-
function environmentDisk(folders) {
|
|
415
|
-
const worktree = readDiskUsage(folders.worktree);
|
|
416
|
-
const modules = folders.nodeModules.map((path) => readDiskUsage(path));
|
|
417
|
-
const build = readDiskUsage(folders.build);
|
|
418
|
-
const measured = [
|
|
419
|
-
worktree,
|
|
420
|
-
...modules,
|
|
421
|
-
build
|
|
422
|
-
].filter((entry) => entry !== null);
|
|
423
|
-
if (!measured.length) return null;
|
|
424
|
-
return {
|
|
425
|
-
worktreeBytes: worktree?.bytes ?? null,
|
|
426
|
-
nodeModulesBytes: modules.every((entry) => entry !== null) ? modules.reduce((sum, entry) => sum + entry.bytes, 0) : null,
|
|
427
|
-
buildBytes: build?.bytes ?? null,
|
|
428
|
-
measuredAt: measured.map((entry) => entry.measuredAt).toSorted()[0]
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
function statusPullRequest(fact, checkedAt) {
|
|
432
|
-
return {
|
|
433
|
-
number: fact.number,
|
|
434
|
-
url: fact.url,
|
|
435
|
-
title: fact.title ?? "",
|
|
436
|
-
state: fact.state === "open" && fact.draft ? "draft" : fact.state,
|
|
437
|
-
checks: fact.checks ?? null,
|
|
438
|
-
reviewDecision: fact.reviewDecision ?? null,
|
|
439
|
-
checkedAt
|
|
440
|
-
};
|
|
441
282
|
}
|
|
442
283
|
/**
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
284
|
+
* Keeps the disk use, pull request and agent session caches fresh for `status --watch`, off its refresh path: one
|
|
285
|
+
* agent session discovery at most every 15 seconds, which status ignores once it is over 2 minutes old, one `du -sk` at
|
|
286
|
+
* a time on the whole machine (an ownership claim; a failed walk is recorded so other watchers do not repeat it), at low
|
|
287
|
+
* priority, live environments first, a checkout without the linked worktrees nested in it, each folder at most every 5 minutes while its environment is live and every hour otherwise, and one `gh api
|
|
288
|
+
* graphql` per repository for worktrees whose lookup is over 5 minutes old or whose branch or HEAD moved. It
|
|
289
|
+
* runs git only in the repository, never in a worktree, and only for worktrees whose git state status read. It rechecks
|
|
290
|
+
* a folder's cache right before measuring, so two watchers rarely measure the same folder. A session the previous
|
|
291
|
+
* discovery found and this one does not is recorded as ended in the workspaces it worked in. `updated` runs after each
|
|
292
|
+
* write.
|
|
446
293
|
*/
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
294
|
+
function createStatusMeasurer({ updated, now = Date.now }) {
|
|
295
|
+
const exec = getExecutor();
|
|
296
|
+
let measuring = false;
|
|
297
|
+
let duMissing = false;
|
|
298
|
+
let checking = false;
|
|
299
|
+
let checkedAt = -Infinity;
|
|
300
|
+
let discovering = false;
|
|
301
|
+
let discoveredAt = -Infinity;
|
|
302
|
+
async function du(targets, claim) {
|
|
303
|
+
const hold = (child) => {
|
|
304
|
+
if (child.pid === void 0) return;
|
|
305
|
+
const captured = captureProcessIdentity(child.pid);
|
|
306
|
+
if (!captured.ok) throw new Error(`Cannot record the du process: ${captured.reason}`);
|
|
307
|
+
setClaimChild(claim, {
|
|
308
|
+
pid: child.pid,
|
|
309
|
+
processToken: captured.token
|
|
310
|
+
});
|
|
458
311
|
};
|
|
312
|
+
try {
|
|
313
|
+
markClaimChildPending(claim);
|
|
314
|
+
return parseDuKilobytes(await exec.runFileAsync("nice", [
|
|
315
|
+
"-n",
|
|
316
|
+
DU_NICENESS,
|
|
317
|
+
"du",
|
|
318
|
+
"-sk",
|
|
319
|
+
...targets
|
|
320
|
+
], {
|
|
321
|
+
timeoutMs: DU_TIMEOUT_MS,
|
|
322
|
+
onSpawn: hold
|
|
323
|
+
}));
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const { code, status, stdout } = error;
|
|
326
|
+
if (code === "ENOENT") duMissing = true;
|
|
327
|
+
return status === 1 && typeof stdout === "string" ? parseDuKilobytes(stdout) : null;
|
|
328
|
+
} finally {
|
|
329
|
+
clearClaimChild(claim);
|
|
330
|
+
}
|
|
459
331
|
}
|
|
460
|
-
|
|
461
|
-
path
|
|
462
|
-
|
|
463
|
-
head,
|
|
464
|
-
checkedAt,
|
|
465
|
-
pullRequest,
|
|
466
|
-
...fact ? {
|
|
467
|
-
containsHead: fact.containsHead,
|
|
468
|
-
endedAt: fact.endedAt
|
|
469
|
-
} : {},
|
|
470
|
-
...partial ? { partial: true } : {}
|
|
471
|
-
};
|
|
472
|
-
writeCacheFile(pullRequestCacheFile(path), entry);
|
|
473
|
-
}
|
|
474
|
-
function canonicalPath(path) {
|
|
475
|
-
try {
|
|
476
|
-
return realpathSync(path);
|
|
477
|
-
} catch {
|
|
478
|
-
return path;
|
|
332
|
+
function due(path, maxAgeMs) {
|
|
333
|
+
const failedAt = readDiskUsageFailure(path);
|
|
334
|
+
return isStale(readDiskUsage(path), maxAgeMs, now()) && (!failedAt || now() - Date.parse(failedAt) >= maxAgeMs);
|
|
479
335
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
336
|
+
async function measure(folders) {
|
|
337
|
+
for (const { path, maxAgeMs, nested } of folders) {
|
|
338
|
+
if (duMissing || !due(path, maxAgeMs)) continue;
|
|
339
|
+
const stamp = () => ({
|
|
340
|
+
path,
|
|
341
|
+
measuredAt: new Date(now()).toISOString()
|
|
342
|
+
});
|
|
343
|
+
if (!existsSync(path)) {
|
|
344
|
+
if (!basename(path).startsWith("node_modules")) continue;
|
|
345
|
+
writeCacheFile(diskUsageCacheFile(path), {
|
|
346
|
+
...stamp(),
|
|
347
|
+
bytes: 0
|
|
348
|
+
});
|
|
349
|
+
updated();
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const claim = takeWalkClaim();
|
|
353
|
+
if (!claim) return;
|
|
354
|
+
try {
|
|
355
|
+
if (!due(path, maxAgeMs)) continue;
|
|
356
|
+
const targets = walkTargets(path, nested?.() ?? []);
|
|
357
|
+
const kb = targets.length ? await du(targets, claim) : 0;
|
|
358
|
+
if (kb === null) {
|
|
359
|
+
writeCacheFile(diskUsageFailureFile(path), {
|
|
360
|
+
path,
|
|
361
|
+
failedAt: new Date(now()).toISOString()
|
|
362
|
+
});
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
writeCacheFile(diskUsageCacheFile(path), {
|
|
366
|
+
...stamp(),
|
|
367
|
+
bytes: kb * 1024
|
|
368
|
+
});
|
|
369
|
+
updated();
|
|
370
|
+
} finally {
|
|
371
|
+
releaseClaim(claim);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
514
374
|
}
|
|
515
|
-
|
|
516
|
-
function recordEndedAgents(states, gone, endedAt, now) {
|
|
517
|
-
const goneKeys = new Set(gone.map(agentKey));
|
|
518
|
-
const result = {
|
|
519
|
-
changed: false,
|
|
520
|
-
failed: false
|
|
521
|
-
};
|
|
522
|
-
attributeAgentSessions(agentWorkspaces(states), gone, now).forEach((sessions, i) => {
|
|
523
|
-
const root = states[i].path;
|
|
524
|
-
const ended = sessions.filter((session) => goneKeys.has(agentKey(session)));
|
|
525
|
-
if (!ended.length || !existsSync(workspaceDir(root))) return;
|
|
375
|
+
async function branchHead(repository, branch) {
|
|
526
376
|
try {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
});
|
|
533
|
-
writeFileAtomic(workspaceEndedAgentsFile(root), { sessions: [...kept.values()] });
|
|
534
|
-
});
|
|
535
|
-
result.changed = true;
|
|
377
|
+
return (await gitAsync(repository, [
|
|
378
|
+
"rev-parse",
|
|
379
|
+
"--verify",
|
|
380
|
+
`refs/heads/${branch}`
|
|
381
|
+
], { timeoutMs: GIT_TIMEOUT_MS$1 })).trim() || null;
|
|
536
382
|
} catch {
|
|
537
|
-
|
|
383
|
+
return null;
|
|
538
384
|
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
385
|
+
}
|
|
386
|
+
async function checkPullRequests(worktrees) {
|
|
387
|
+
const lookup = pullRequestLookups({ detail: true });
|
|
388
|
+
const byRepository = /* @__PURE__ */ new Map();
|
|
389
|
+
for (const { path, branch, repository, git } of worktrees) {
|
|
390
|
+
if (!branch || !repository || !git) continue;
|
|
391
|
+
const head = await branchHead(repository, branch);
|
|
392
|
+
if (!head) continue;
|
|
393
|
+
const cached = readPullRequestCache(path);
|
|
394
|
+
if (!(cached?.branch === branch && cached.head === head && !cached.partial && now() - Date.parse(cached.checkedAt) < PULL_REQUEST_MAX_AGE_MS)) byRepository.set(repository, [...byRepository.get(repository) ?? [], {
|
|
395
|
+
path,
|
|
396
|
+
cwd: repository,
|
|
397
|
+
branch,
|
|
398
|
+
head
|
|
399
|
+
}]);
|
|
400
|
+
}
|
|
401
|
+
for (const [repository, queries] of byRepository) {
|
|
402
|
+
const results = await lookup(repository, queries);
|
|
403
|
+
const at = new Date(now()).toISOString();
|
|
404
|
+
results.forEach((result, i) => {
|
|
405
|
+
if (!("pullRequest" in result)) return;
|
|
406
|
+
const { path, branch, head } = queries[i];
|
|
407
|
+
recordPullRequestLookup({
|
|
408
|
+
path,
|
|
409
|
+
branch,
|
|
410
|
+
head
|
|
411
|
+
}, result.pullRequest, at);
|
|
412
|
+
});
|
|
413
|
+
if (results.some((result) => "pullRequest" in result)) updated();
|
|
555
414
|
}
|
|
556
415
|
}
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
416
|
+
async function discoverAgents(states) {
|
|
417
|
+
const at = now();
|
|
418
|
+
const sessions = await discoverAgentSessions({ now: at });
|
|
419
|
+
const previous = readAgentSessionsCache();
|
|
420
|
+
const running = new Set(sessions.map(agentKey));
|
|
421
|
+
const lastFound = previous ? Date.parse(previous.discoveredAt) : -Infinity;
|
|
422
|
+
const gone = at - lastFound <= ENDED_AGENT_RETENTION_MS ? previous.sessions.filter((session) => !running.has(agentKey(session))) : [];
|
|
423
|
+
const ended = gone.length ? recordEndedAgents(states, gone, previous.discoveredAt, at) : {
|
|
424
|
+
changed: false,
|
|
425
|
+
failed: false
|
|
426
|
+
};
|
|
427
|
+
if (ended.failed && at - lastFound <= AGENT_CACHE_MAX_AGE_MS) return;
|
|
428
|
+
writeCacheFile(agentSessionsCacheFile(), {
|
|
429
|
+
discoveredAt: new Date(at).toISOString(),
|
|
430
|
+
sessions
|
|
431
|
+
});
|
|
432
|
+
const shown = previous && at - Date.parse(previous.discoveredAt) <= AGENT_CACHE_MAX_AGE_MS;
|
|
433
|
+
if (ended.changed || !shown || JSON.stringify(previous.sessions) !== JSON.stringify(sessions)) updated();
|
|
434
|
+
}
|
|
435
|
+
return { schedule(states, worktrees) {
|
|
436
|
+
if (!discovering && now() - discoveredAt >= AGENT_DISCOVERY_MS) {
|
|
437
|
+
discovering = true;
|
|
438
|
+
discoveredAt = now();
|
|
439
|
+
discoverAgents(states).catch(() => {}).finally(() => {
|
|
440
|
+
discovering = false;
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (!measuring) {
|
|
444
|
+
const folders = /* @__PURE__ */ new Map();
|
|
445
|
+
for (const state of states.toSorted((a, b) => Number(b.live) - Number(a.live))) {
|
|
446
|
+
if (!existsSync(state.path)) continue;
|
|
447
|
+
const maxAgeMs = state.live ? LIVE_DISK_MAX_AGE_MS : IDLE_DISK_MAX_AGE_MS;
|
|
448
|
+
const { worktree, nodeModules, build } = environmentFolders(state);
|
|
449
|
+
for (const path of [
|
|
450
|
+
worktree,
|
|
451
|
+
...nodeModules,
|
|
452
|
+
build,
|
|
453
|
+
...deviceFolders(state).map((folder) => folder.path)
|
|
454
|
+
]) {
|
|
455
|
+
const known = folders.get(path);
|
|
456
|
+
folders.set(path, {
|
|
457
|
+
path,
|
|
458
|
+
maxAgeMs: Math.min(known?.maxAgeMs ?? Infinity, maxAgeMs),
|
|
459
|
+
nested: known?.nested ?? (path === worktree ? () => nestedFolders(state) : void 0)
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
measuring = true;
|
|
464
|
+
measure([...folders.values()]).catch(() => {}).finally(() => {
|
|
465
|
+
measuring = false;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (!checking && now() - checkedAt >= PULL_REQUEST_CHECK_MS) {
|
|
469
|
+
checking = true;
|
|
470
|
+
checkedAt = now();
|
|
471
|
+
checkPullRequests(worktrees).catch(() => {}).finally(() => {
|
|
472
|
+
checking = false;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
} };
|
|
476
|
+
}
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/workspace/merge-state.ts
|
|
479
|
+
const FETCH_TIMEOUT_MS = 3e4;
|
|
480
|
+
/** How old a fetch or a cached lookup can be and still count as fresh. */
|
|
481
|
+
const FETCH_FRESH_MS = 6e5;
|
|
482
|
+
const GIT_TIMEOUT_MS = 6e4;
|
|
483
|
+
const REMOTE_PREFIX = "refs/remotes/origin/";
|
|
484
|
+
const MAX_MERGE_STATE_COMMITS = 2e3;
|
|
485
|
+
function failure(error) {
|
|
486
|
+
const { code, stderr, message } = error;
|
|
487
|
+
const text = (code === "ENOBUFS" ? "" : String(stderr ?? "").trim()) || String(message ?? error);
|
|
488
|
+
return text.split("\n")[0] ?? text;
|
|
489
|
+
}
|
|
490
|
+
function fetchTime(path) {
|
|
491
|
+
path = path?.trim() ?? null;
|
|
492
|
+
try {
|
|
493
|
+
return path ? statSync(path).mtimeMs : null;
|
|
494
|
+
} catch {
|
|
495
|
+
return null;
|
|
560
496
|
}
|
|
561
497
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return
|
|
498
|
+
function defaultBranch(repo, ref) {
|
|
499
|
+
ref = ref?.trim() ?? null;
|
|
500
|
+
if (!ref?.startsWith(REMOTE_PREFIX)) return { error: `origin/HEAD is not set; run \`git -C ${repo} remote set-head origin --auto\`` };
|
|
501
|
+
return {
|
|
502
|
+
ref,
|
|
503
|
+
name: `origin/${ref.slice(20)}`
|
|
504
|
+
};
|
|
566
505
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
506
|
+
function notMerged(detail) {
|
|
507
|
+
return {
|
|
508
|
+
merged: false,
|
|
509
|
+
unknown: false,
|
|
510
|
+
detail
|
|
511
|
+
};
|
|
571
512
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const inside = [...skip].filter((path) => path.startsWith(top + sep));
|
|
577
|
-
if (!inside.length) return [root];
|
|
578
|
-
const targets = [];
|
|
579
|
-
const visit = (dir) => {
|
|
580
|
-
let names;
|
|
581
|
-
try {
|
|
582
|
-
names = readdirSync(dir);
|
|
583
|
-
} catch {
|
|
584
|
-
targets.push(dir);
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
for (const name of names) {
|
|
588
|
-
const path = join(dir, name);
|
|
589
|
-
if (skip.has(path)) continue;
|
|
590
|
-
if (inside.some((nestedPath) => nestedPath.startsWith(path + sep))) visit(path);
|
|
591
|
-
else targets.push(path);
|
|
592
|
-
}
|
|
513
|
+
function* git(args, input) {
|
|
514
|
+
return yield {
|
|
515
|
+
args: ["--literal-pathspecs", ...args],
|
|
516
|
+
options: { input }
|
|
593
517
|
};
|
|
594
|
-
visit(top);
|
|
595
|
-
return targets;
|
|
596
518
|
}
|
|
597
|
-
function
|
|
598
|
-
return
|
|
519
|
+
function* patch(args, input) {
|
|
520
|
+
return yield {
|
|
521
|
+
args: ["--literal-pathspecs", ...args],
|
|
522
|
+
options: {
|
|
523
|
+
input,
|
|
524
|
+
untrimmed: true
|
|
525
|
+
}
|
|
526
|
+
};
|
|
599
527
|
}
|
|
600
|
-
function
|
|
528
|
+
function* probe(args) {
|
|
601
529
|
try {
|
|
602
|
-
|
|
603
|
-
root: diskUsageWalkClaims(),
|
|
604
|
-
mode: "exclusive",
|
|
605
|
-
label: "disk usage walk"
|
|
606
|
-
});
|
|
607
|
-
if (attempt.pending) releaseClaim(attempt.pending);
|
|
608
|
-
return attempt.acquired ?? null;
|
|
530
|
+
return yield { args };
|
|
609
531
|
} catch (error) {
|
|
610
|
-
|
|
532
|
+
if (isTimeoutError(error) || typeof error === "object" && error !== null && "code" in error && error.code === "ENOBUFS") throw error;
|
|
611
533
|
return null;
|
|
612
534
|
}
|
|
613
535
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
536
|
+
function* patchIdCommits(text) {
|
|
537
|
+
const output = text ? yield* git(["patch-id", "--verbatim"], text) : "";
|
|
538
|
+
return new Map(output.split("\n").flatMap((line) => {
|
|
539
|
+
const [id, commit] = line.split(" ");
|
|
540
|
+
return id && commit ? [[id, commit]] : [];
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
function* patchIds(text) {
|
|
544
|
+
return [...(yield* patchIdCommits(text)).keys()];
|
|
545
|
+
}
|
|
546
|
+
function* committedAt(commits) {
|
|
547
|
+
const times = commits.length ? (yield* git([
|
|
548
|
+
"log",
|
|
549
|
+
"--no-walk",
|
|
550
|
+
"--stdin",
|
|
551
|
+
"--format=%ct"
|
|
552
|
+
], commits.join("\n"))).split("\n").map(Number) : [];
|
|
553
|
+
if (!times.length || !times.every(Number.isFinite)) throw new Error(`no committer date for ${commits.join(" ")}`);
|
|
554
|
+
return Math.max(...times) * 1e3;
|
|
555
|
+
}
|
|
556
|
+
function* currentBranch() {
|
|
557
|
+
const ref = (yield* probe([
|
|
558
|
+
"symbolic-ref",
|
|
559
|
+
"--quiet",
|
|
560
|
+
"HEAD"
|
|
561
|
+
]))?.trim();
|
|
562
|
+
return ref?.startsWith("refs/heads/") ? ref : null;
|
|
563
|
+
}
|
|
564
|
+
function* upstreamGone(branch) {
|
|
565
|
+
if (!branch) return false;
|
|
566
|
+
const [upstream, state] = ((yield* probe([
|
|
567
|
+
"for-each-ref",
|
|
568
|
+
"--format=%(upstream)%00%(upstream:track)",
|
|
569
|
+
branch
|
|
570
|
+
])) ?? "").trim().split("\0");
|
|
571
|
+
return Boolean(upstream) && state === "[gone]";
|
|
572
|
+
}
|
|
573
|
+
function* committedOn(branch, head) {
|
|
574
|
+
if (!branch) return false;
|
|
575
|
+
const entries = (yield* probe([
|
|
576
|
+
"reflog",
|
|
577
|
+
"show",
|
|
578
|
+
"--format=%H %gs",
|
|
579
|
+
branch
|
|
580
|
+
])) ?? "";
|
|
581
|
+
for (const entry of entries.split("\n")) {
|
|
582
|
+
const [sha = "", ...subject] = entry.split(" ");
|
|
583
|
+
if (/^(commit|cherry-pick|rebase|revert)\b/.test(subject.join(" ")) && (yield* probe([
|
|
584
|
+
"merge-base",
|
|
585
|
+
"--is-ancestor",
|
|
586
|
+
sha,
|
|
587
|
+
head
|
|
588
|
+
])) !== null) return true;
|
|
589
|
+
}
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
function* mergeStateReads({ ref, name }, maxCommits) {
|
|
593
|
+
const diffOptions = ["--no-color", "--no-ext-diff"];
|
|
594
|
+
const noOwnCommits = {
|
|
595
|
+
merged: false,
|
|
596
|
+
unknown: false,
|
|
597
|
+
detail: `no commits of its own beyond ${name}`,
|
|
598
|
+
noOwnCommits: true
|
|
599
|
+
};
|
|
600
|
+
try {
|
|
601
|
+
const head = yield* git([
|
|
602
|
+
"rev-parse",
|
|
603
|
+
"--verify",
|
|
604
|
+
"HEAD^{commit}"
|
|
605
|
+
]);
|
|
606
|
+
const base = yield* git([
|
|
607
|
+
"merge-base",
|
|
608
|
+
head,
|
|
609
|
+
ref
|
|
610
|
+
]);
|
|
611
|
+
const branch = yield* currentBranch();
|
|
612
|
+
if (base === head) {
|
|
613
|
+
if ((yield* git(["rev-parse", ref])) === head || (yield* git([
|
|
614
|
+
"rev-list",
|
|
615
|
+
"--first-parent",
|
|
616
|
+
"--parents",
|
|
617
|
+
`${head}..${ref}`
|
|
618
|
+
])).split("\n").some((line) => line.split(" ")[1] === head) || !(yield* committedOn(branch, head))) return noOwnCommits;
|
|
619
|
+
const descendants = new Set((yield* git([
|
|
620
|
+
"rev-list",
|
|
621
|
+
"--ancestry-path",
|
|
622
|
+
`${head}..${ref}`
|
|
623
|
+
])).split("\n"));
|
|
624
|
+
const landed = (yield* git([
|
|
625
|
+
"rev-list",
|
|
626
|
+
"--first-parent",
|
|
627
|
+
"--reverse",
|
|
628
|
+
`${head}..${ref}`
|
|
629
|
+
])).split("\n").find((commit) => descendants.has(commit));
|
|
630
|
+
if (!landed) throw new Error(`no commit on ${name} merges ${head}`);
|
|
631
|
+
return {
|
|
632
|
+
merged: true,
|
|
633
|
+
into: name,
|
|
634
|
+
head,
|
|
635
|
+
coversUnpushed: false,
|
|
636
|
+
mergedAt: yield* committedAt([landed])
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const aheadCount = Number(yield* git([
|
|
640
|
+
"rev-list",
|
|
641
|
+
"--count",
|
|
642
|
+
`${base}..${head}`
|
|
643
|
+
]));
|
|
644
|
+
if (aheadCount > maxCommits) return {
|
|
645
|
+
merged: false,
|
|
646
|
+
unknown: true,
|
|
647
|
+
detail: `merge state unknown: ${aheadCount} commits of its own ahead of the merge base with ${name} exceeds the ${maxCommits}-commit limit`
|
|
648
|
+
};
|
|
649
|
+
const files = (yield* git([
|
|
650
|
+
"diff",
|
|
651
|
+
"--name-only",
|
|
652
|
+
"--no-renames",
|
|
653
|
+
"-z",
|
|
654
|
+
base,
|
|
655
|
+
head
|
|
656
|
+
])).split("\0").filter(Boolean);
|
|
657
|
+
if (!files.length) return notMerged(`no net change beyond ${name}`);
|
|
658
|
+
const log = function* (range, pathspec = []) {
|
|
659
|
+
return yield* patch([
|
|
660
|
+
"log",
|
|
661
|
+
"-p",
|
|
662
|
+
"--no-merges",
|
|
663
|
+
...diffOptions,
|
|
664
|
+
"--format=commit %H",
|
|
665
|
+
"--stdin"
|
|
666
|
+
], pathspec.length ? `${range}\n--\n${pathspec.join("\n")}\n` : `${range}\n`);
|
|
667
|
+
};
|
|
668
|
+
const upstream = yield* patchIdCommits(yield* log(`${base}..${ref}`, files));
|
|
669
|
+
const patchEquivalent = function* (ids) {
|
|
670
|
+
return {
|
|
671
|
+
merged: true,
|
|
672
|
+
into: name,
|
|
673
|
+
head,
|
|
674
|
+
coversUnpushed: yield* upstreamGone(branch),
|
|
675
|
+
mergedAt: yield* committedAt(ids.map((id) => upstream.get(id)))
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
const squash = yield* patchIds(yield* patch([
|
|
679
|
+
"diff",
|
|
680
|
+
...diffOptions,
|
|
681
|
+
base,
|
|
682
|
+
head
|
|
683
|
+
]));
|
|
684
|
+
if (squash.length === 1 && upstream.has(squash[0])) return yield* patchEquivalent(squash);
|
|
685
|
+
if (!(yield* git([
|
|
686
|
+
"rev-list",
|
|
687
|
+
"--merges",
|
|
688
|
+
`${base}..${head}`
|
|
689
|
+
]))) {
|
|
690
|
+
const own = yield* patchIds(yield* log(`${base}..${head}`));
|
|
691
|
+
if (own.length && own.every((id) => upstream.has(id))) return yield* patchEquivalent(own);
|
|
692
|
+
}
|
|
693
|
+
return notMerged(`not merged into ${name}`);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
const timedOut = isTimeoutError(error) ? { timedOut: true } : {};
|
|
696
|
+
return {
|
|
697
|
+
merged: false,
|
|
698
|
+
unknown: true,
|
|
699
|
+
detail: `merge state unknown: ${failure(error)}`,
|
|
700
|
+
...timedOut
|
|
642
701
|
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Whether the worktree's HEAD is merged into `target`, and when: `mergedAt` is the committer date, in epoch
|
|
706
|
+
* milliseconds, of the commit on `target` that brought the work in, the latest one for a rebase merge.
|
|
707
|
+
* The signals, all local git:
|
|
708
|
+
* - HEAD is an ancestor of the default branch, off its first-parent line, and the branch's reflog shows a commit made
|
|
709
|
+
* on it that HEAD contains, so a merge commit brought the branch's own work in. A branch with no commit of its own
|
|
710
|
+
* is not merged.
|
|
711
|
+
* - The branch changes the tree, has no merge commits, and every commit since the merge base has the same
|
|
712
|
+
* `git patch-id --verbatim` as a commit on the default branch (a rebase merge).
|
|
713
|
+
* - The branch changes the tree and its whole diff since the merge base has the same verbatim patch id as a commit on
|
|
714
|
+
* the default branch, limited to the files the branch changes (a squash merge).
|
|
715
|
+
* A HEAD the default branch contains with no commit of its own on the branch is not merged and carries
|
|
716
|
+
* `noOwnCommits`. Anything git cannot answer is unknown, never merged. `coversUnpushed` is true for a patch-equivalent HEAD whose
|
|
717
|
+
* upstream branch was deleted: its commits exist only locally, but their change is on the default branch.
|
|
718
|
+
*/
|
|
719
|
+
function mergeState(path, target, { timeoutMs = GIT_TIMEOUT_MS, maxCommits = MAX_MERGE_STATE_COMMITS } = {}) {
|
|
720
|
+
const reads = mergeStateReads(target, maxCommits);
|
|
721
|
+
let step = reads.next();
|
|
722
|
+
while (!step.done) {
|
|
723
|
+
const { args, options } = step.value;
|
|
643
724
|
try {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
DU_NICENESS,
|
|
648
|
-
"du",
|
|
649
|
-
"-sk",
|
|
650
|
-
...targets
|
|
651
|
-
], {
|
|
652
|
-
timeoutMs: DU_TIMEOUT_MS,
|
|
653
|
-
onSpawn: hold
|
|
725
|
+
step = reads.next(git$1(path, args, {
|
|
726
|
+
timeoutMs,
|
|
727
|
+
...options
|
|
654
728
|
}));
|
|
655
729
|
} catch (error) {
|
|
656
|
-
|
|
657
|
-
if (code === "ENOENT") duMissing = true;
|
|
658
|
-
return status === 1 && typeof stdout === "string" ? parseDuKilobytes(stdout) : null;
|
|
659
|
-
} finally {
|
|
660
|
-
clearClaimChild(claim);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
function due(path, maxAgeMs) {
|
|
664
|
-
const failedAt = readDiskUsageFailure(path);
|
|
665
|
-
return isStale(readDiskUsage(path), maxAgeMs, now()) && (!failedAt || now() - Date.parse(failedAt) >= maxAgeMs);
|
|
666
|
-
}
|
|
667
|
-
async function measure(folders) {
|
|
668
|
-
for (const { path, maxAgeMs, nested } of folders) {
|
|
669
|
-
if (duMissing || !due(path, maxAgeMs)) continue;
|
|
670
|
-
const stamp = () => ({
|
|
671
|
-
path,
|
|
672
|
-
measuredAt: new Date(now()).toISOString()
|
|
673
|
-
});
|
|
674
|
-
if (!existsSync(path)) {
|
|
675
|
-
if (!basename(path).startsWith("node_modules")) continue;
|
|
676
|
-
writeCacheFile(diskUsageCacheFile(path), {
|
|
677
|
-
...stamp(),
|
|
678
|
-
bytes: 0
|
|
679
|
-
});
|
|
680
|
-
updated();
|
|
681
|
-
continue;
|
|
682
|
-
}
|
|
683
|
-
const claim = takeWalkClaim();
|
|
684
|
-
if (!claim) return;
|
|
685
|
-
try {
|
|
686
|
-
if (!due(path, maxAgeMs)) continue;
|
|
687
|
-
const targets = walkTargets(path, nested?.() ?? []);
|
|
688
|
-
const kb = targets.length ? await du(targets, claim) : 0;
|
|
689
|
-
if (kb === null) {
|
|
690
|
-
writeCacheFile(diskUsageFailureFile(path), {
|
|
691
|
-
path,
|
|
692
|
-
failedAt: new Date(now()).toISOString()
|
|
693
|
-
});
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
writeCacheFile(diskUsageCacheFile(path), {
|
|
697
|
-
...stamp(),
|
|
698
|
-
bytes: kb * 1024
|
|
699
|
-
});
|
|
700
|
-
updated();
|
|
701
|
-
} finally {
|
|
702
|
-
releaseClaim(claim);
|
|
703
|
-
}
|
|
730
|
+
step = reads.throw(error);
|
|
704
731
|
}
|
|
705
732
|
}
|
|
706
|
-
|
|
733
|
+
return step.value;
|
|
734
|
+
}
|
|
735
|
+
/** `mergeState` without blocking the event loop during Git reads. */
|
|
736
|
+
async function mergeStateAsync(path, target, { timeoutMs = GIT_TIMEOUT_MS, maxCommits = MAX_MERGE_STATE_COMMITS, ...execution } = {}) {
|
|
737
|
+
const reads = mergeStateReads(target, maxCommits);
|
|
738
|
+
let step = reads.next();
|
|
739
|
+
while (!step.done) {
|
|
740
|
+
execution.signal?.throwIfAborted();
|
|
741
|
+
const { args, options } = step.value;
|
|
707
742
|
try {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
} catch {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
async function checkPullRequests(worktrees) {
|
|
718
|
-
const lookup = pullRequestLookups({ detail: true });
|
|
719
|
-
const byRepository = /* @__PURE__ */ new Map();
|
|
720
|
-
for (const { path, branch, repository, git } of worktrees) {
|
|
721
|
-
if (!branch || !repository || !git) continue;
|
|
722
|
-
const head = await branchHead(repository, branch);
|
|
723
|
-
if (!head) continue;
|
|
724
|
-
const cached = readPullRequestCache(path);
|
|
725
|
-
if (!(cached?.branch === branch && cached.head === head && !cached.partial && now() - Date.parse(cached.checkedAt) < PULL_REQUEST_MAX_AGE_MS)) byRepository.set(repository, [...byRepository.get(repository) ?? [], {
|
|
726
|
-
path,
|
|
727
|
-
cwd: repository,
|
|
728
|
-
branch,
|
|
729
|
-
head
|
|
730
|
-
}]);
|
|
731
|
-
}
|
|
732
|
-
for (const [repository, queries] of byRepository) {
|
|
733
|
-
const results = await lookup(repository, queries);
|
|
734
|
-
const at = new Date(now()).toISOString();
|
|
735
|
-
results.forEach((result, i) => {
|
|
736
|
-
if (!("pullRequest" in result)) return;
|
|
737
|
-
const { path, branch, head } = queries[i];
|
|
738
|
-
recordPullRequestLookup({
|
|
739
|
-
path,
|
|
740
|
-
branch,
|
|
741
|
-
head
|
|
742
|
-
}, result.pullRequest, at);
|
|
743
|
-
});
|
|
744
|
-
if (results.some((result) => "pullRequest" in result)) updated();
|
|
743
|
+
step = reads.next(await gitAsync(path, args, {
|
|
744
|
+
...execution,
|
|
745
|
+
timeoutMs,
|
|
746
|
+
...options
|
|
747
|
+
}));
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
750
|
+
execution.signal?.throwIfAborted();
|
|
751
|
+
step = reads.throw(error);
|
|
745
752
|
}
|
|
746
753
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
754
|
+
execution.signal?.throwIfAborted();
|
|
755
|
+
return step.value;
|
|
756
|
+
}
|
|
757
|
+
/** The last fetch timestamp, or null, read without blocking on Git. */
|
|
758
|
+
async function lastFetchedAtAsync(repo, execution = {}) {
|
|
759
|
+
return fetchTime(await gitQuietAsync(repo, [
|
|
760
|
+
"rev-parse",
|
|
761
|
+
"--path-format=absolute",
|
|
762
|
+
"--git-path",
|
|
763
|
+
"FETCH_HEAD"
|
|
764
|
+
], execution));
|
|
765
|
+
}
|
|
766
|
+
/** The existing origin/HEAD target, or a refusal, without fetching or blocking on Git. */
|
|
767
|
+
async function localDefaultBranchAsync(repo, execution = {}) {
|
|
768
|
+
return defaultBranch(repo, await gitQuietAsync(repo, [
|
|
769
|
+
"symbolic-ref",
|
|
770
|
+
"--quiet",
|
|
771
|
+
"refs/remotes/origin/HEAD"
|
|
772
|
+
], execution));
|
|
773
|
+
}
|
|
774
|
+
/** Refreshes origin/HEAD without credential prompts when the last fetch is at least ten minutes old. */
|
|
775
|
+
async function fetchDefaultBranchAsync(repo, now = Date.now(), execution = {}) {
|
|
776
|
+
execution.signal?.throwIfAborted();
|
|
777
|
+
const target = await localDefaultBranchAsync(repo, execution);
|
|
778
|
+
if ("error" in target) return target;
|
|
779
|
+
const at = await lastFetchedAtAsync(repo, execution);
|
|
780
|
+
if (at !== null && now - at < 6e5) return target;
|
|
781
|
+
const branch = target.ref.slice(20);
|
|
782
|
+
try {
|
|
783
|
+
await gitAsync(repo, [
|
|
784
|
+
"fetch",
|
|
785
|
+
"--quiet",
|
|
786
|
+
"--no-tags",
|
|
787
|
+
"--no-recurse-submodules",
|
|
788
|
+
"origin",
|
|
789
|
+
`+refs/heads/${branch}:${target.ref}`
|
|
790
|
+
], {
|
|
791
|
+
...execution,
|
|
792
|
+
write: true,
|
|
793
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
794
|
+
env: {
|
|
795
|
+
...execution.env,
|
|
796
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
797
|
+
},
|
|
798
|
+
omitEnv: execution.omitEnv?.filter((name) => name !== "GIT_TERMINAL_PROMPT")
|
|
762
799
|
});
|
|
763
|
-
|
|
764
|
-
if (
|
|
800
|
+
} catch (error) {
|
|
801
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
802
|
+
execution.signal?.throwIfAborted();
|
|
803
|
+
return { error: `git fetch origin ${branch} failed: ${failure(error)}` };
|
|
765
804
|
}
|
|
766
|
-
return
|
|
767
|
-
if (!discovering && now() - discoveredAt >= AGENT_DISCOVERY_MS) {
|
|
768
|
-
discovering = true;
|
|
769
|
-
discoveredAt = now();
|
|
770
|
-
discoverAgents(states).catch(() => {}).finally(() => {
|
|
771
|
-
discovering = false;
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
if (!measuring) {
|
|
775
|
-
const folders = /* @__PURE__ */ new Map();
|
|
776
|
-
for (const state of states.toSorted((a, b) => Number(b.live) - Number(a.live))) {
|
|
777
|
-
if (!existsSync(state.path)) continue;
|
|
778
|
-
const maxAgeMs = state.live ? LIVE_DISK_MAX_AGE_MS : IDLE_DISK_MAX_AGE_MS;
|
|
779
|
-
const { worktree, nodeModules, build } = environmentFolders(state);
|
|
780
|
-
for (const path of [
|
|
781
|
-
worktree,
|
|
782
|
-
...nodeModules,
|
|
783
|
-
build,
|
|
784
|
-
...deviceFolders(state).map((folder) => folder.path)
|
|
785
|
-
]) {
|
|
786
|
-
const known = folders.get(path);
|
|
787
|
-
folders.set(path, {
|
|
788
|
-
path,
|
|
789
|
-
maxAgeMs: Math.min(known?.maxAgeMs ?? Infinity, maxAgeMs),
|
|
790
|
-
nested: known?.nested ?? (path === worktree ? () => nestedFolders(state) : void 0)
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
measuring = true;
|
|
795
|
-
measure([...folders.values()]).catch(() => {}).finally(() => {
|
|
796
|
-
measuring = false;
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
if (!checking && now() - checkedAt >= PULL_REQUEST_CHECK_MS) {
|
|
800
|
-
checking = true;
|
|
801
|
-
checkedAt = now();
|
|
802
|
-
checkPullRequests(worktrees).catch(() => {}).finally(() => {
|
|
803
|
-
checking = false;
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
} };
|
|
805
|
+
return target;
|
|
807
806
|
}
|
|
808
807
|
//#endregion
|
|
809
808
|
//#region src/engine/warm-progress.ts
|
|
@@ -861,14 +860,18 @@ function startWarmProgress(root, step, { note, now = Date.now }) {
|
|
|
861
860
|
...state,
|
|
862
861
|
[WARM_KEY]: warming(step)
|
|
863
862
|
}));
|
|
863
|
+
const phases = trace.phases("worktree warm");
|
|
864
|
+
phases.enter(step);
|
|
864
865
|
return {
|
|
865
866
|
step(next) {
|
|
867
|
+
phases.enter(next);
|
|
866
868
|
write((state) => ours(state) ? {
|
|
867
869
|
...state,
|
|
868
870
|
[WARM_KEY]: warming(next)
|
|
869
871
|
} : state);
|
|
870
872
|
},
|
|
871
873
|
finish(succeeded) {
|
|
874
|
+
phases.end({ ok: succeeded });
|
|
872
875
|
write((state) => {
|
|
873
876
|
if (!ours(state)) return state;
|
|
874
877
|
const { [WARM_KEY]: _, ...rest } = state;
|
|
@@ -915,4 +918,4 @@ function workspacePhase(live, state, { now = Date.now(), survey = ownership_clai
|
|
|
915
918
|
};
|
|
916
919
|
}
|
|
917
920
|
//#endregion
|
|
918
|
-
export {
|
|
921
|
+
export { lastFetchedAtAsync as a, mergeStateAsync as c, forgetStatusMeasures as d, recordPullRequestLookup as f, fetchDefaultBranchAsync as i, applyStatusMeasures as l, workspacePhase as n, localDefaultBranchAsync as o, writeCacheFile as p, FETCH_FRESH_MS as r, mergeState as s, startWarmProgress as t, createStatusMeasurer as u };
|