@cuongtran001/kanna 1.3.0 → 1.4.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/dist/client/assets/{arc-CAysfmX5.js → arc-BHj370W7.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-1G88q1PY.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-D0L3Co5v.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-CQtERl3H.js} +1 -1
- package/dist/client/assets/channel-BP9IMEvd.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-DxOwt09f.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-Wry66y9R.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-D6p-Hi4x.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-CRQZt23N.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-BzgTKOM9.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CKfD-91C.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-a71B27wc.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.js → chunk-QZHKN3VN-vv9gJwx0.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK--W6Wt7FF.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2--W6Wt7FF.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-YgpMurXf.js → cose-bilkent-S5V4N54A-Cu-dCCaI.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-D8coOWLK.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-NIwZ65jE.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-d46aE_le.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-xdm2UxWc.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-hDbLwC5K.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-MHUGG7ki.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-Ro6n9X70.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-DOZqvRMu.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-Bn2NXYIy.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DK44bfbC.js} +1 -1
- package/dist/client/assets/{index-C0U2kwuB.js → index-CWJpHyQy.js} +115 -115
- package/dist/client/assets/{index-CLsOWXff.css → index-Cyzd4zZx.css} +1 -1
- package/dist/client/assets/{index-CbSY9HfS.js → index-DU56d4oC.js} +1 -1
- package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-C2tW_Dwj.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-C__4Z1wz.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-Bbn5EawR.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-RgS5IRWl.js} +1 -1
- package/dist/client/assets/{linear-DLR6miy3.js → linear-DfI0qBO_.js} +1 -1
- package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-BiN10VHc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-BQhibhh3.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-BEfLRn7a.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-ba641Y2x.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-BGtl-tVO.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-DyziaSFr.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BfYBAYga.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.js → stateDiagram-AJRCARHV-BfUfmLYA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdGcgYEq.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-ByWRdpQX.js → timeline-definition-PNZ67QCA-T6z7dEFl.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-BAwQbFFd.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-DF1cRKsP.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BRtAZ8za.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-Brb-q76h.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent-coordinator-types.ts +158 -0
- package/src/server/agent-coordinator.ts +919 -0
- package/src/server/agent-deps-builders.ts +425 -0
- package/src/server/agent.test.ts +22 -6
- package/src/server/agent.ts +35 -4987
- package/src/server/chat-op-log.test.ts +63 -0
- package/src/server/chat-op-log.ts +71 -0
- package/src/server/chat-ops-diff.test.ts +120 -0
- package/src/server/chat-ops-diff.ts +97 -0
- package/src/server/chat-ops-parity.test.ts +140 -0
- package/src/server/claude-autocontinue-commands.test.ts +494 -0
- package/src/server/claude-autocontinue-commands.ts +256 -0
- package/src/server/claude-cancel-handler.test.ts +472 -0
- package/src/server/claude-cancel-handler.ts +241 -0
- package/src/server/claude-chat-management.test.ts +395 -0
- package/src/server/claude-chat-management.ts +251 -0
- package/src/server/claude-harness-stream.test.ts +273 -0
- package/src/server/claude-harness-stream.ts +158 -0
- package/src/server/claude-loop-orch-commands.test.ts +579 -0
- package/src/server/claude-loop-orch-commands.ts +466 -0
- package/src/server/claude-message-normalizer.test.ts +213 -0
- package/src/server/claude-message-normalizer.ts +436 -0
- package/src/server/claude-prompt-helpers.test.ts +248 -0
- package/src/server/claude-prompt-helpers.ts +139 -0
- package/src/server/claude-sdk-queue.test.ts +169 -0
- package/src/server/claude-sdk-queue.ts +82 -0
- package/src/server/claude-send-command.test.ts +414 -0
- package/src/server/claude-send-command.ts +420 -0
- package/src/server/claude-session-config-helpers.test.ts +279 -0
- package/src/server/claude-session-config-helpers.ts +151 -0
- package/src/server/claude-session-config.test.ts +250 -0
- package/src/server/claude-session-config.ts +159 -0
- package/src/server/claude-session-error-handler.test.ts +498 -0
- package/src/server/claude-session-error-handler.ts +361 -0
- package/src/server/claude-session-lifecycle.test.ts +669 -0
- package/src/server/claude-session-lifecycle.ts +283 -0
- package/src/server/claude-session-rebuild.test.ts +224 -0
- package/src/server/claude-session-rebuild.ts +101 -0
- package/src/server/claude-session-runner.test.ts +523 -0
- package/src/server/claude-session-runner.ts +521 -0
- package/src/server/claude-session-spawner.test.ts +401 -0
- package/src/server/claude-session-spawner.ts +401 -0
- package/src/server/claude-session-start.test.ts +205 -0
- package/src/server/claude-session-start.ts +277 -0
- package/src/server/claude-session-state-queries.test.ts +344 -0
- package/src/server/claude-session-state-queries.ts +160 -0
- package/src/server/claude-session-state.ts +104 -0
- package/src/server/claude-slash-commands.test.ts +277 -0
- package/src/server/claude-slash-commands.ts +234 -0
- package/src/server/claude-spawn-helpers.test.ts +221 -0
- package/src/server/claude-spawn-helpers.ts +179 -0
- package/src/server/claude-steer-log.test.ts +243 -0
- package/src/server/claude-steer-log.ts +58 -0
- package/src/server/claude-subagent-tool-response.test.ts +213 -0
- package/src/server/claude-subagent-tool-response.ts +165 -0
- package/src/server/claude-subagent-wiring.test.ts +263 -0
- package/src/server/claude-subagent-wiring.ts +324 -0
- package/src/server/claude-tool-respond.test.ts +321 -0
- package/src/server/claude-tool-respond.ts +142 -0
- package/src/server/claude-turn-runner.test.ts +310 -0
- package/src/server/claude-turn-runner.ts +228 -0
- package/src/server/claude-turn-starter.test.ts +266 -0
- package/src/server/claude-turn-starter.ts +566 -0
- package/src/server/claude-usage-math.test.ts +169 -0
- package/src/server/claude-usage-math.ts +132 -0
- package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
- package/src/server/diff-store-branch-ops.adapter.ts +602 -0
- package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
- package/src/server/diff-store-commit-ops.adapter.ts +258 -0
- package/src/server/diff-store-errors.ts +85 -0
- package/src/server/diff-store-file-ops.adapter.ts +212 -0
- package/src/server/diff-store-git-branch.adapter.ts +686 -0
- package/src/server/diff-store-io.adapter.ts +27 -0
- package/src/server/diff-store-parse.test.ts +212 -0
- package/src/server/diff-store-parse.ts +122 -0
- package/src/server/diff-store-state.ts +82 -0
- package/src/server/diff-store.ts +76 -1854
- package/src/server/event-store-apply.test.ts +280 -0
- package/src/server/event-store-apply.ts +132 -0
- package/src/server/event-store-chat-lifecycle.test.ts +408 -0
- package/src/server/event-store-chat-lifecycle.ts +441 -0
- package/src/server/event-store-entity-write.ts +356 -0
- package/src/server/event-store-helpers.test.ts +230 -0
- package/src/server/event-store-helpers.ts +235 -0
- package/src/server/event-store-init.ts +276 -0
- package/src/server/event-store-messages.adapter.test.ts +532 -0
- package/src/server/event-store-messages.adapter.ts +455 -0
- package/src/server/event-store-orch-subscription.ts +124 -0
- package/src/server/event-store-orch.test.ts +462 -0
- package/src/server/event-store-orch.ts +312 -0
- package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
- package/src/server/event-store-peripheral-events.adapter.ts +105 -0
- package/src/server/event-store-snapshot.test.ts +597 -0
- package/src/server/event-store-snapshot.ts +658 -0
- package/src/server/event-store-subagent.test.ts +408 -0
- package/src/server/event-store-subagent.ts +198 -0
- package/src/server/event-store-tool-requests.test.ts +210 -0
- package/src/server/event-store-tool-requests.ts +131 -0
- package/src/server/event-store-transcript-write.adapter.ts +291 -0
- package/src/server/event-store-write-ops.test.ts +554 -0
- package/src/server/event-store-write-ops.ts +529 -0
- package/src/server/event-store.test.ts +75 -0
- package/src/server/event-store.ts +257 -2285
- package/src/server/events.ts +1 -1
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/storage/backend.ts +7 -0
- package/src/server/storage/fs-storage.adapter.ts +21 -1
- package/src/server/storage/in-memory-storage.ts +12 -0
- package/src/server/ws-router-agent-ctrl.test.ts +187 -0
- package/src/server/ws-router-agent-ctrl.ts +137 -0
- package/src/server/ws-router-broadcast.ts +560 -0
- package/src/server/ws-router-chat.test.ts +461 -0
- package/src/server/ws-router-chat.ts +279 -0
- package/src/server/ws-router-defaults.test.ts +283 -0
- package/src/server/ws-router-defaults.ts +434 -0
- package/src/server/ws-router-diff.test.ts +167 -0
- package/src/server/ws-router-diff.ts +250 -0
- package/src/server/ws-router-envelope.ts +360 -0
- package/src/server/ws-router-misc.test.ts +422 -0
- package/src/server/ws-router-misc.ts +265 -0
- package/src/server/ws-router-orch.test.ts +190 -0
- package/src/server/ws-router-orch.ts +93 -0
- package/src/server/ws-router-project.test.ts +403 -0
- package/src/server/ws-router-project.ts +256 -0
- package/src/server/ws-router-push.test.ts +193 -0
- package/src/server/ws-router-push.ts +120 -0
- package/src/server/ws-router-settings.test.ts +173 -0
- package/src/server/ws-router-settings.ts +416 -0
- package/src/server/ws-router-skills.test.ts +194 -0
- package/src/server/ws-router-skills.ts +204 -0
- package/src/server/ws-router-utils.ts +238 -0
- package/src/server/ws-router.test.ts +90 -0
- package/src/server/ws-router.ts +340 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/chat-ops.test.ts +138 -0
- package/src/shared/chat-ops.ts +71 -0
- package/src/shared/core-types.ts +27 -0
- package/src/shared/git-diff-types.ts +165 -0
- package/src/shared/mcp-types.ts +105 -0
- package/src/shared/protocol.ts +3 -2
- package/src/shared/provider-model-types.ts +437 -0
- package/src/shared/subagent-types.ts +198 -0
- package/src/shared/tool-call-types.ts +314 -0
- package/src/shared/transcript-types.ts +270 -0
- package/src/shared/types.ts +31 -1711
- package/dist/client/assets/channel-KQhRwDzJ.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-ChxT1GNq.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ChxT1GNq.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D4X-ZnLV.js +0 -1
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatGitFailure,
|
|
3
|
+
runGit,
|
|
4
|
+
summarizeGitFailure,
|
|
5
|
+
} from "./diff-store-io.adapter"
|
|
6
|
+
import {
|
|
7
|
+
createBranchActionFailure,
|
|
8
|
+
createMergeActionFailure,
|
|
9
|
+
extractGitHubRepoSlug,
|
|
10
|
+
fetchGitHubPullRequests,
|
|
11
|
+
getBranchName,
|
|
12
|
+
getBranchUpdatedAtMap,
|
|
13
|
+
getGitHubRemoteSlugs,
|
|
14
|
+
getLocalBranchNames,
|
|
15
|
+
getMergeCommitCount,
|
|
16
|
+
getOriginRemoteUrl,
|
|
17
|
+
getRecentBranchNames,
|
|
18
|
+
getRemoteBranchNames,
|
|
19
|
+
getUpstreamStatusCounts,
|
|
20
|
+
hasUpstreamBranch,
|
|
21
|
+
predictMergeConflicts,
|
|
22
|
+
resolveDefaultBranchName,
|
|
23
|
+
resolveRepo,
|
|
24
|
+
resolveSelectedBranchRef,
|
|
25
|
+
} from "./diff-store-git-branch.adapter"
|
|
26
|
+
import type { SelectedBranch } from "./diff-store-git-branch.adapter"
|
|
27
|
+
import { createSyncPushFailure } from "./diff-store-errors"
|
|
28
|
+
import { listDirtyPaths } from "./diff-store-file-ops.adapter"
|
|
29
|
+
import type {
|
|
30
|
+
ChatBranchListEntry,
|
|
31
|
+
ChatBranchListResult,
|
|
32
|
+
ChatCheckoutBranchResult,
|
|
33
|
+
ChatCreateBranchResult,
|
|
34
|
+
ChatMergeBranchResult,
|
|
35
|
+
ChatMergePreviewResult,
|
|
36
|
+
ChatSyncResult,
|
|
37
|
+
} from "../shared/types"
|
|
38
|
+
|
|
39
|
+
export interface DiffStoreBranchOpsDeps {
|
|
40
|
+
readonly refreshSnapshot: (projectId: string, projectPath: string) => Promise<boolean>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function listBranches(
|
|
44
|
+
_deps: DiffStoreBranchOpsDeps,
|
|
45
|
+
args: { projectPath: string }
|
|
46
|
+
): Promise<ChatBranchListResult> {
|
|
47
|
+
const repo = await resolveRepo(args.projectPath)
|
|
48
|
+
if (!repo) {
|
|
49
|
+
throw new Error("Project is not in a git repository")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const [currentBranchName, defaultBranchName, localBranchNames, remoteBranchNames, recentBranchNames, localUpdatedAtMap, remoteUpdatedAtMap] = await Promise.all([
|
|
53
|
+
getBranchName(repo.repoRoot),
|
|
54
|
+
resolveDefaultBranchName(repo.repoRoot),
|
|
55
|
+
getLocalBranchNames(repo.repoRoot),
|
|
56
|
+
getRemoteBranchNames(repo.repoRoot),
|
|
57
|
+
getRecentBranchNames(repo.repoRoot),
|
|
58
|
+
getBranchUpdatedAtMap(repo.repoRoot, "refs/heads"),
|
|
59
|
+
getBranchUpdatedAtMap(repo.repoRoot, "refs/remotes"),
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const local = localBranchNames.map((name) => ({
|
|
63
|
+
id: `local:${name}`,
|
|
64
|
+
kind: "local",
|
|
65
|
+
name,
|
|
66
|
+
displayName: name,
|
|
67
|
+
updatedAt: localUpdatedAtMap.get(name),
|
|
68
|
+
} satisfies ChatBranchListEntry))
|
|
69
|
+
|
|
70
|
+
const remote = remoteBranchNames.map((remoteRef) => ({
|
|
71
|
+
id: `remote:${remoteRef}`,
|
|
72
|
+
kind: "remote",
|
|
73
|
+
name: remoteRef.replace(/^[^/]+\//u, ""),
|
|
74
|
+
displayName: remoteRef,
|
|
75
|
+
updatedAt: remoteUpdatedAtMap.get(remoteRef),
|
|
76
|
+
remoteRef,
|
|
77
|
+
} satisfies ChatBranchListEntry))
|
|
78
|
+
|
|
79
|
+
const localBranchSet = new Set(localBranchNames)
|
|
80
|
+
const remoteByName = new Map(remote.map((entry) => [entry.name, entry]))
|
|
81
|
+
const remoteEntriesByName = new Map<string, ChatBranchListEntry[]>()
|
|
82
|
+
for (const entry of remote) {
|
|
83
|
+
const entries = remoteEntriesByName.get(entry.name) ?? []
|
|
84
|
+
entries.push(entry)
|
|
85
|
+
remoteEntriesByName.set(entry.name, entries)
|
|
86
|
+
}
|
|
87
|
+
const recent: ChatBranchListEntry[] = recentBranchNames.flatMap<ChatBranchListEntry>((branchName) => {
|
|
88
|
+
if (localBranchSet.has(branchName)) {
|
|
89
|
+
return {
|
|
90
|
+
id: `recent:local:${branchName}`,
|
|
91
|
+
kind: "local",
|
|
92
|
+
name: branchName,
|
|
93
|
+
displayName: branchName,
|
|
94
|
+
updatedAt: localUpdatedAtMap.get(branchName),
|
|
95
|
+
} satisfies ChatBranchListEntry
|
|
96
|
+
}
|
|
97
|
+
const remoteEntry = remoteByName.get(branchName)
|
|
98
|
+
return remoteEntry
|
|
99
|
+
? {
|
|
100
|
+
...remoteEntry,
|
|
101
|
+
id: `recent:${remoteEntry.id}`,
|
|
102
|
+
} satisfies ChatBranchListEntry
|
|
103
|
+
: []
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const [remoteUrl, githubRemoteSlugs] = await Promise.all([
|
|
107
|
+
getOriginRemoteUrl(repo.repoRoot),
|
|
108
|
+
getGitHubRemoteSlugs(repo.repoRoot),
|
|
109
|
+
])
|
|
110
|
+
const repoSlug = extractGitHubRepoSlug(remoteUrl)
|
|
111
|
+
let pullRequests: ChatBranchListEntry[] = []
|
|
112
|
+
const pullRequestRemoteRefs = new Set<string>()
|
|
113
|
+
const pullRequestHeadNames = new Set<string>()
|
|
114
|
+
let pullRequestsStatus: ChatBranchListResult["pullRequestsStatus"] = "unavailable"
|
|
115
|
+
let pullRequestsError: string | undefined
|
|
116
|
+
|
|
117
|
+
if (repoSlug) {
|
|
118
|
+
try {
|
|
119
|
+
const prs = await fetchGitHubPullRequests(repoSlug)
|
|
120
|
+
pullRequests = prs.flatMap<ChatBranchListEntry>((pr) => {
|
|
121
|
+
const headRefName = pr.head?.ref?.trim()
|
|
122
|
+
if (!headRefName) return []
|
|
123
|
+
pullRequestHeadNames.add(headRefName)
|
|
124
|
+
const cloneUrl = pr.head?.repo?.clone_url?.trim() || undefined
|
|
125
|
+
const fullName = pr.head?.repo?.full_name?.trim() || undefined
|
|
126
|
+
const headRepoSlug = fullName?.toLowerCase()
|
|
127
|
+
const matchingRemoteEntries = (remoteEntriesByName.get(headRefName) ?? []).filter((entry) => {
|
|
128
|
+
const remoteName = entry.remoteRef?.split("/")[0]
|
|
129
|
+
if (!remoteName) return false
|
|
130
|
+
const remoteSlug = githubRemoteSlugs.get(remoteName)
|
|
131
|
+
if (!remoteSlug) return false
|
|
132
|
+
if (headRepoSlug) {
|
|
133
|
+
return remoteSlug === headRepoSlug
|
|
134
|
+
}
|
|
135
|
+
return remoteName === "origin"
|
|
136
|
+
})
|
|
137
|
+
for (const entry of matchingRemoteEntries) {
|
|
138
|
+
if (entry.remoteRef) {
|
|
139
|
+
pullRequestRemoteRefs.add(entry.remoteRef)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const preferredRemoteEntry = matchingRemoteEntries[0] ?? remoteByName.get(headRefName)
|
|
143
|
+
const remoteRef = preferredRemoteEntry?.remoteRef ?? `origin/${headRefName}`
|
|
144
|
+
return {
|
|
145
|
+
id: `pr:${pr.number}`,
|
|
146
|
+
kind: "pull_request",
|
|
147
|
+
name: headRefName,
|
|
148
|
+
displayName: `PR #${pr.number}`,
|
|
149
|
+
updatedAt: (remoteRef ? remoteUpdatedAtMap.get(remoteRef) : undefined) ?? localUpdatedAtMap.get(headRefName),
|
|
150
|
+
description: pr.title,
|
|
151
|
+
remoteRef,
|
|
152
|
+
prNumber: pr.number,
|
|
153
|
+
prTitle: pr.title,
|
|
154
|
+
headRefName,
|
|
155
|
+
headLabel: pr.head?.label?.trim() || fullName,
|
|
156
|
+
headRepoCloneUrl: cloneUrl,
|
|
157
|
+
isCrossRepository: Boolean(fullName && fullName.toLowerCase() !== repoSlug.toLowerCase()),
|
|
158
|
+
} satisfies ChatBranchListEntry
|
|
159
|
+
})
|
|
160
|
+
pullRequestsStatus = "available"
|
|
161
|
+
} catch (error) {
|
|
162
|
+
pullRequestsStatus = "error"
|
|
163
|
+
pullRequestsError = error instanceof Error ? error.message : String(error)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const visibleRemote = remote.filter((entry) => {
|
|
168
|
+
if (pullRequestHeadNames.has(entry.name)) {
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
return !entry.remoteRef || !pullRequestRemoteRefs.has(entry.remoteRef)
|
|
172
|
+
})
|
|
173
|
+
const visibleRemoteByName = new Map(visibleRemote.map((entry) => [entry.name, entry]))
|
|
174
|
+
const visibleRecent = recent.filter((entry) => entry.kind !== "remote" || !entry.remoteRef || visibleRemoteByName.has(entry.name))
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
currentBranchName,
|
|
178
|
+
defaultBranchName,
|
|
179
|
+
recent: visibleRecent,
|
|
180
|
+
local,
|
|
181
|
+
remote: visibleRemote,
|
|
182
|
+
pullRequests,
|
|
183
|
+
pullRequestsStatus,
|
|
184
|
+
pullRequestsError,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function previewMergeBranch(
|
|
189
|
+
_deps: DiffStoreBranchOpsDeps,
|
|
190
|
+
args: { projectPath: string; branch: SelectedBranch }
|
|
191
|
+
): Promise<ChatMergePreviewResult> {
|
|
192
|
+
const repo = await resolveRepo(args.projectPath)
|
|
193
|
+
if (!repo) {
|
|
194
|
+
throw new Error("Project is not in a git repository")
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const currentBranchName = await getBranchName(repo.repoRoot)
|
|
198
|
+
const resolvedBranch = await resolveSelectedBranchRef(repo.repoRoot, args.branch)
|
|
199
|
+
|
|
200
|
+
if (currentBranchName && resolvedBranch.branchName === currentBranchName) {
|
|
201
|
+
return {
|
|
202
|
+
currentBranchName,
|
|
203
|
+
targetBranchName: resolvedBranch.branchName,
|
|
204
|
+
targetDisplayName: resolvedBranch.displayName,
|
|
205
|
+
status: "up_to_date",
|
|
206
|
+
commitCount: 0,
|
|
207
|
+
hasConflicts: false,
|
|
208
|
+
message: `${currentBranchName} is already up to date with ${resolvedBranch.displayName}.`,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const commitCount = await getMergeCommitCount(repo.repoRoot, resolvedBranch.ref)
|
|
214
|
+
if (commitCount === 0) {
|
|
215
|
+
return {
|
|
216
|
+
currentBranchName,
|
|
217
|
+
targetBranchName: resolvedBranch.branchName,
|
|
218
|
+
targetDisplayName: resolvedBranch.displayName,
|
|
219
|
+
status: "up_to_date",
|
|
220
|
+
commitCount,
|
|
221
|
+
hasConflicts: false,
|
|
222
|
+
message: `${currentBranchName ?? "Current branch"} is already up to date with ${resolvedBranch.displayName}.`,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const conflictPrediction = await predictMergeConflicts(repo.repoRoot, resolvedBranch.ref)
|
|
227
|
+
if (conflictPrediction.hasConflicts) {
|
|
228
|
+
return {
|
|
229
|
+
currentBranchName,
|
|
230
|
+
targetBranchName: resolvedBranch.branchName,
|
|
231
|
+
targetDisplayName: resolvedBranch.displayName,
|
|
232
|
+
status: "conflicts",
|
|
233
|
+
commitCount,
|
|
234
|
+
hasConflicts: true,
|
|
235
|
+
message: `${commitCount} ${commitCount === 1 ? "commit" : "commits"} from ${resolvedBranch.displayName} would merge into ${currentBranchName ?? "the current branch"}, but conflicts are expected.`,
|
|
236
|
+
detail: conflictPrediction.detail,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
currentBranchName,
|
|
242
|
+
targetBranchName: resolvedBranch.branchName,
|
|
243
|
+
targetDisplayName: resolvedBranch.displayName,
|
|
244
|
+
status: "mergeable",
|
|
245
|
+
commitCount,
|
|
246
|
+
hasConflicts: false,
|
|
247
|
+
message: `${commitCount} ${commitCount === 1 ? "commit" : "commits"} from ${resolvedBranch.displayName} will merge into ${currentBranchName ?? "the current branch"}.`,
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
251
|
+
return {
|
|
252
|
+
currentBranchName,
|
|
253
|
+
targetBranchName: resolvedBranch.branchName,
|
|
254
|
+
targetDisplayName: resolvedBranch.displayName,
|
|
255
|
+
status: "error",
|
|
256
|
+
commitCount: 0,
|
|
257
|
+
hasConflicts: false,
|
|
258
|
+
message: "Could not preview this merge.",
|
|
259
|
+
detail: message,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function mergeBranch(
|
|
265
|
+
deps: DiffStoreBranchOpsDeps,
|
|
266
|
+
args: { projectId: string; projectPath: string; branch: SelectedBranch }
|
|
267
|
+
): Promise<ChatMergeBranchResult> {
|
|
268
|
+
const repo = await resolveRepo(args.projectPath)
|
|
269
|
+
if (!repo) {
|
|
270
|
+
throw new Error("Project is not in a git repository")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const currentDirtyPaths = await listDirtyPaths(repo.repoRoot)
|
|
274
|
+
if (currentDirtyPaths.length > 0) {
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
title: "Merge blocked",
|
|
278
|
+
message: "Commit, discard, or stash your local changes before merging.",
|
|
279
|
+
snapshotChanged: false,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const resolvedBranch = await resolveSelectedBranchRef(repo.repoRoot, args.branch)
|
|
284
|
+
const commitCount = await getMergeCommitCount(repo.repoRoot, resolvedBranch.ref)
|
|
285
|
+
if (commitCount === 0) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
title: "Already up to date",
|
|
289
|
+
message: `${resolvedBranch.displayName} is already merged into ${await getBranchName(repo.repoRoot) ?? "the current branch"}.`,
|
|
290
|
+
snapshotChanged: false,
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const mergeResult = await runGit(["merge", "--no-edit", resolvedBranch.ref], repo.repoRoot)
|
|
295
|
+
const detail = formatGitFailure(mergeResult)
|
|
296
|
+
|
|
297
|
+
if (mergeResult.exitCode !== 0) {
|
|
298
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
299
|
+
const normalized = detail.toLowerCase()
|
|
300
|
+
const title = normalized.includes("conflict")
|
|
301
|
+
? "Merge conflicts need resolution"
|
|
302
|
+
: "Merge failed"
|
|
303
|
+
const fallback = normalized.includes("conflict")
|
|
304
|
+
? "Git reported merge conflicts while merging this branch."
|
|
305
|
+
: "Git could not merge this branch."
|
|
306
|
+
return createMergeActionFailure({
|
|
307
|
+
title,
|
|
308
|
+
detail,
|
|
309
|
+
fallback,
|
|
310
|
+
snapshotChanged,
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
branchName: await getBranchName(repo.repoRoot),
|
|
318
|
+
snapshotChanged,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function checkoutBranch(
|
|
323
|
+
deps: DiffStoreBranchOpsDeps,
|
|
324
|
+
args: {
|
|
325
|
+
projectId: string
|
|
326
|
+
projectPath: string
|
|
327
|
+
branch: SelectedBranch
|
|
328
|
+
bringChanges?: boolean
|
|
329
|
+
}
|
|
330
|
+
): Promise<ChatCheckoutBranchResult> {
|
|
331
|
+
const repo = await resolveRepo(args.projectPath)
|
|
332
|
+
if (!repo) {
|
|
333
|
+
throw new Error("Project is not in a git repository")
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const currentDirtyPaths = await listDirtyPaths(repo.repoRoot)
|
|
337
|
+
if (currentDirtyPaths.length > 0 && !args.bringChanges) {
|
|
338
|
+
return {
|
|
339
|
+
ok: false,
|
|
340
|
+
cancelled: true,
|
|
341
|
+
title: "Branch switch cancelled",
|
|
342
|
+
message: "Your current changes were kept on the current branch.",
|
|
343
|
+
snapshotChanged: false,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let switchResult: Awaited<ReturnType<typeof runGit>>
|
|
348
|
+
if (args.branch.kind === "local") {
|
|
349
|
+
switchResult = await runGit(["switch", args.branch.name], repo.repoRoot)
|
|
350
|
+
} else if (args.branch.kind === "remote") {
|
|
351
|
+
const localBranchNames = await getLocalBranchNames(repo.repoRoot)
|
|
352
|
+
if (localBranchNames.includes(args.branch.name)) {
|
|
353
|
+
switchResult = await runGit(["switch", args.branch.name], repo.repoRoot)
|
|
354
|
+
} else {
|
|
355
|
+
switchResult = await runGit(["switch", "--track", "--no-guess", args.branch.remoteRef], repo.repoRoot)
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
const localBranchNames = await getLocalBranchNames(repo.repoRoot)
|
|
359
|
+
let localBranchName = args.branch.name
|
|
360
|
+
|
|
361
|
+
if (localBranchNames.includes(localBranchName) && args.branch.isCrossRepository) {
|
|
362
|
+
localBranchName = `${args.branch.name}-pr-${args.branch.prNumber}`
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (localBranchNames.includes(localBranchName)) {
|
|
366
|
+
switchResult = await runGit(["switch", localBranchName], repo.repoRoot)
|
|
367
|
+
} else if (args.branch.isCrossRepository && args.branch.headRepoCloneUrl) {
|
|
368
|
+
const fetchResult = await runGit(
|
|
369
|
+
[
|
|
370
|
+
"fetch",
|
|
371
|
+
"--no-tags",
|
|
372
|
+
args.branch.headRepoCloneUrl,
|
|
373
|
+
`refs/heads/${args.branch.headRefName}:refs/heads/${localBranchName}`,
|
|
374
|
+
],
|
|
375
|
+
repo.repoRoot
|
|
376
|
+
)
|
|
377
|
+
if (fetchResult.exitCode !== 0) {
|
|
378
|
+
return createBranchActionFailure("Checkout failed", formatGitFailure(fetchResult), "Git could not fetch the pull request branch.")
|
|
379
|
+
}
|
|
380
|
+
switchResult = await runGit(["switch", localBranchName], repo.repoRoot)
|
|
381
|
+
} else {
|
|
382
|
+
const remoteRef = args.branch.remoteRef ?? `origin/${args.branch.headRefName}`
|
|
383
|
+
const remoteBranchNames = await getRemoteBranchNames(repo.repoRoot)
|
|
384
|
+
if (!remoteBranchNames.includes(remoteRef)) {
|
|
385
|
+
const fetchResult = await runGit(
|
|
386
|
+
["fetch", "--no-tags", "origin", `refs/heads/${args.branch.headRefName}:refs/remotes/${remoteRef}`],
|
|
387
|
+
repo.repoRoot
|
|
388
|
+
)
|
|
389
|
+
if (fetchResult.exitCode !== 0) {
|
|
390
|
+
return createBranchActionFailure("Checkout failed", formatGitFailure(fetchResult), "Git could not fetch the pull request branch.")
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
switchResult = await runGit(["switch", "--track", "--no-guess", remoteRef], repo.repoRoot)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (switchResult.exitCode !== 0) {
|
|
398
|
+
return createBranchActionFailure("Checkout failed", formatGitFailure(switchResult), "Git could not switch branches.")
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
402
|
+
return {
|
|
403
|
+
ok: true,
|
|
404
|
+
branchName: await getBranchName(repo.repoRoot),
|
|
405
|
+
snapshotChanged,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export async function createBranch(
|
|
410
|
+
deps: DiffStoreBranchOpsDeps,
|
|
411
|
+
args: {
|
|
412
|
+
projectId: string
|
|
413
|
+
projectPath: string
|
|
414
|
+
name: string
|
|
415
|
+
baseBranchName?: string
|
|
416
|
+
}
|
|
417
|
+
): Promise<ChatCreateBranchResult> {
|
|
418
|
+
const repo = await resolveRepo(args.projectPath)
|
|
419
|
+
if (!repo) {
|
|
420
|
+
throw new Error("Project is not in a git repository")
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const branchName = args.name.trim()
|
|
424
|
+
if (!branchName) {
|
|
425
|
+
throw new Error("Branch name is required")
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const refValidation = await runGit(["check-ref-format", "--branch", branchName], repo.repoRoot)
|
|
429
|
+
if (refValidation.exitCode !== 0) {
|
|
430
|
+
return createBranchActionFailure("Create branch failed", formatGitFailure(refValidation), "Branch name is not valid.")
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const localBranchNames = await getLocalBranchNames(repo.repoRoot)
|
|
434
|
+
if (localBranchNames.includes(branchName)) {
|
|
435
|
+
return {
|
|
436
|
+
ok: false,
|
|
437
|
+
title: "Create branch failed",
|
|
438
|
+
message: `A local branch named "${branchName}" already exists.`,
|
|
439
|
+
snapshotChanged: false,
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const baseBranchName = args.baseBranchName?.trim() || await resolveDefaultBranchName(repo.repoRoot) || await getBranchName(repo.repoRoot)
|
|
444
|
+
if (!baseBranchName) {
|
|
445
|
+
throw new Error("Could not determine a base branch")
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const switchResult = await runGit(["switch", "-c", branchName, baseBranchName], repo.repoRoot)
|
|
449
|
+
if (switchResult.exitCode !== 0) {
|
|
450
|
+
return createBranchActionFailure("Create branch failed", formatGitFailure(switchResult), "Git could not create the branch.")
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
454
|
+
return {
|
|
455
|
+
ok: true,
|
|
456
|
+
branchName,
|
|
457
|
+
snapshotChanged,
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function syncBranch(
|
|
462
|
+
deps: DiffStoreBranchOpsDeps,
|
|
463
|
+
args: {
|
|
464
|
+
projectId: string
|
|
465
|
+
projectPath: string
|
|
466
|
+
action: "fetch" | "pull" | "push" | "publish"
|
|
467
|
+
}
|
|
468
|
+
): Promise<ChatSyncResult> {
|
|
469
|
+
const repo = await resolveRepo(args.projectPath)
|
|
470
|
+
if (!repo) {
|
|
471
|
+
throw new Error("Project is not in a git repository")
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const hasUpstream = await hasUpstreamBranch(repo.repoRoot)
|
|
475
|
+
if (args.action === "publish") {
|
|
476
|
+
const publishResult = await runGit(["push", "-u", "origin", "HEAD"], repo.repoRoot)
|
|
477
|
+
if (publishResult.exitCode !== 0) {
|
|
478
|
+
const detail = formatGitFailure(publishResult)
|
|
479
|
+
const normalized = detail.toLowerCase()
|
|
480
|
+
let title = "Publish branch failed"
|
|
481
|
+
let message = summarizeGitFailure(detail, "Git could not publish this branch.")
|
|
482
|
+
|
|
483
|
+
if (normalized.includes("could not read from remote repository") || normalized.includes("authentication failed") || normalized.includes("permission denied")) {
|
|
484
|
+
title = "Remote authentication failed"
|
|
485
|
+
message = "Git could not authenticate with the remote repository."
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
ok: false,
|
|
490
|
+
action: args.action,
|
|
491
|
+
title,
|
|
492
|
+
message,
|
|
493
|
+
detail,
|
|
494
|
+
snapshotChanged: false,
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
499
|
+
const branchName = await getBranchName(repo.repoRoot)
|
|
500
|
+
const nextHasUpstream = await hasUpstreamBranch(repo.repoRoot)
|
|
501
|
+
const { aheadCount, behindCount } = nextHasUpstream
|
|
502
|
+
? await getUpstreamStatusCounts(repo.repoRoot)
|
|
503
|
+
: { aheadCount: undefined, behindCount: undefined }
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
ok: true,
|
|
507
|
+
action: args.action,
|
|
508
|
+
branchName,
|
|
509
|
+
aheadCount,
|
|
510
|
+
behindCount,
|
|
511
|
+
snapshotChanged,
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (args.action === "push") {
|
|
516
|
+
if (!hasUpstream) {
|
|
517
|
+
return {
|
|
518
|
+
ok: false,
|
|
519
|
+
action: args.action,
|
|
520
|
+
title: "Push failed",
|
|
521
|
+
message: "This branch does not have an upstream remote branch configured yet.",
|
|
522
|
+
snapshotChanged: false,
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const pushResult = await runGit(["push"], repo.repoRoot)
|
|
527
|
+
if (pushResult.exitCode !== 0) {
|
|
528
|
+
const detail = formatGitFailure(pushResult)
|
|
529
|
+
return createSyncPushFailure(detail, false)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
533
|
+
const branchName = await getBranchName(repo.repoRoot)
|
|
534
|
+
const nextHasUpstream = await hasUpstreamBranch(repo.repoRoot)
|
|
535
|
+
const { aheadCount, behindCount } = nextHasUpstream
|
|
536
|
+
? await getUpstreamStatusCounts(repo.repoRoot)
|
|
537
|
+
: { aheadCount: undefined, behindCount: undefined }
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
ok: true,
|
|
541
|
+
action: args.action,
|
|
542
|
+
branchName,
|
|
543
|
+
aheadCount,
|
|
544
|
+
behindCount,
|
|
545
|
+
snapshotChanged,
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (args.action === "pull" && !hasUpstream) {
|
|
550
|
+
return {
|
|
551
|
+
ok: false,
|
|
552
|
+
action: args.action,
|
|
553
|
+
title: "Pull failed",
|
|
554
|
+
message: "This branch does not have an upstream remote branch configured yet.",
|
|
555
|
+
snapshotChanged: false,
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const syncResult = args.action === "pull"
|
|
560
|
+
? await runGit(["pull", "--ff-only"], repo.repoRoot)
|
|
561
|
+
: await runGit(["fetch", "--all", "--prune"], repo.repoRoot)
|
|
562
|
+
|
|
563
|
+
if (syncResult.exitCode !== 0) {
|
|
564
|
+
const detail = formatGitFailure(syncResult)
|
|
565
|
+
const normalized = detail.toLowerCase()
|
|
566
|
+
let title = args.action === "pull" ? "Pull failed" : "Fetch failed"
|
|
567
|
+
let message = summarizeGitFailure(detail, args.action === "pull" ? "Git could not pull the latest changes." : "Git could not fetch the latest changes.")
|
|
568
|
+
|
|
569
|
+
if (args.action === "pull" && normalized.includes("not possible to fast-forward")) {
|
|
570
|
+
title = "Pull requires merge or rebase"
|
|
571
|
+
message = "Your branch cannot be fast-forwarded. Rebase or merge manually, then try again."
|
|
572
|
+
} else if (normalized.includes("could not read from remote repository") || normalized.includes("authentication failed") || normalized.includes("permission denied")) {
|
|
573
|
+
title = "Remote authentication failed"
|
|
574
|
+
message = "Git could not authenticate with the remote repository."
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
ok: false,
|
|
579
|
+
action: args.action,
|
|
580
|
+
title,
|
|
581
|
+
message,
|
|
582
|
+
detail,
|
|
583
|
+
snapshotChanged: false,
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const snapshotChanged = await deps.refreshSnapshot(args.projectId, args.projectPath)
|
|
588
|
+
const branchName = await getBranchName(repo.repoRoot)
|
|
589
|
+
const nextHasUpstream = await hasUpstreamBranch(repo.repoRoot)
|
|
590
|
+
const { aheadCount, behindCount } = nextHasUpstream
|
|
591
|
+
? await getUpstreamStatusCounts(repo.repoRoot)
|
|
592
|
+
: { aheadCount: undefined, behindCount: undefined }
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
ok: true,
|
|
596
|
+
action: args.action,
|
|
597
|
+
branchName,
|
|
598
|
+
aheadCount,
|
|
599
|
+
behindCount,
|
|
600
|
+
snapshotChanged,
|
|
601
|
+
}
|
|
602
|
+
}
|