@hienlh/ppm 0.9.86 → 0.9.87
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/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
- package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
- package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
- package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
- package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
- package/CHANGELOG.md +14 -0
- package/dist/web/assets/{chat-tab-BEEd-Km4.js → chat-tab-R4gKsnxD.js} +1 -1
- package/dist/web/assets/{code-editor-Ij4p30cr.js → code-editor-Br0vzTOy.js} +2 -2
- package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
- package/dist/web/assets/{csv-preview-CwQnOa3E.js → csv-preview-BZRICDP0.js} +1 -1
- package/dist/web/assets/{database-viewer-C1UHSgft.js → database-viewer-DaUoQ-oR.js} +1 -1
- package/dist/web/assets/{diff-viewer-CVx5naBA.js → diff-viewer-BzvK3gAE.js} +1 -1
- package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
- package/dist/web/assets/{index-OqgGFmh8.js → index-CKsEzQ4f.js} +4 -4
- package/dist/web/assets/index-Chf0otez.css +2 -0
- package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
- package/dist/web/assets/{markdown-renderer-CRy8xw2B.js → markdown-renderer-DSYnGywb.js} +1 -1
- package/dist/web/assets/{port-forwarding-tab-Biua8ov5.js → port-forwarding-tab-vmqDKmk2.js} +1 -1
- package/dist/web/assets/{postgres-viewer-BcVjCAl4.js → postgres-viewer-0lIAosrr.js} +1 -1
- package/dist/web/assets/{settings-tab-C9X-N8hE.js → settings-tab-CMnv1fce.js} +1 -1
- package/dist/web/assets/{sql-query-editor-BFvRvJn0.js → sql-query-editor-Bc2hAwqT.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-CPfvwFl4.js → sqlite-viewer-B60MS2Dy.js} +1 -1
- package/dist/web/assets/{terminal-tab-mWwk_weB.js → terminal-tab-CCJoLstH.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-CPaeSMAA.js → use-monaco-theme-BJK48EmK.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +39 -6
- package/docs/project-changelog.md +86 -25
- package/docs/project-roadmap.md +3 -2
- package/docs/system-architecture.md +44 -1
- package/package.json +1 -1
- package/packages/ext-git-graph/src/extension.ts +126 -5
- package/packages/ext-git-graph/src/types.ts +13 -2
- package/packages/ext-git-graph/src/webview-html.ts +223 -5
- package/src/server/ws/extensions.ts +28 -2
- package/src/services/extension-host-worker.ts +6 -1
- package/src/services/extension.service.ts +17 -3
- package/src/types/extension-messages.ts +1 -1
- package/src/web/components/editor/conflict-editor.tsx +368 -0
- package/src/web/components/extensions/extension-webview.tsx +45 -3
- package/src/web/components/layout/editor-panel.tsx +1 -0
- package/src/web/components/layout/mobile-nav.tsx +1 -0
- package/src/web/components/layout/tab-bar.tsx +1 -0
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/hooks/use-extension-ws.ts +8 -0
- package/src/web/stores/extension-store.ts +8 -0
- package/src/web/stores/panel-utils.ts +2 -0
- package/src/web/stores/tab-store.ts +2 -1
- package/dist/web/assets/extension-webview-CHVVpV34.js +0 -3
- package/dist/web/assets/index-vA7juDri.css +0 -2
- package/dist/web/assets/keybindings-store-BQxgPV5o.js +0 -1
- /package/dist/web/assets/{lib-CeBVkQ-7.js → lib-DSLzfeW0.js} +0 -0
|
@@ -2,13 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to PPM are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
4
4
|
|
|
5
|
-
**Current Version:** v0.9.
|
|
5
|
+
**Current Version:** v0.9.86
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## [Unreleased] —
|
|
9
|
+
## [Unreleased] — Git-Graph Stash Management, Rebase, Conflict Resolution + Worktree CRUD
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
|
+
- **Git Stash Management** — Toolbar popover for interactive stash operations
|
|
13
|
+
- List all stashes with index, hash (abbreviated), and message
|
|
14
|
+
- Apply/Pop/Drop actions per stash via context menu or action buttons
|
|
15
|
+
- "Stash Changes" button in main toolbar to stash uncommitted work
|
|
16
|
+
- Stash list persisted in RepoInfo and refreshed on uncommitted status updates
|
|
17
|
+
- Keyboard shortcuts for quick access to stash operations
|
|
18
|
+
|
|
19
|
+
- **Interactive Rebase** — Branch context menu + commit history actions
|
|
20
|
+
- "Rebase current branch onto..." option in commit context menu
|
|
21
|
+
- Confirmation dialog shows branch selection and target commit
|
|
22
|
+
- Rebase state detection and progress tracking (e.g., "3/5" for interactive rebase)
|
|
23
|
+
- Continue/Skip/Abort controls in merge state banner during rebase
|
|
24
|
+
|
|
25
|
+
- **Merge/Rebase/Cherry-Pick Conflict Detection** — Visual conflict indicators
|
|
26
|
+
- Detects merge state from .git sentinel files (MERGE_HEAD, REBASE_MERGE, CHERRY_PICK_HEAD)
|
|
27
|
+
- Parses git status UU/AA/DD/AU/UA/DU/UD codes for unmerged entries
|
|
28
|
+
- Conflicted files displayed in "Conflicts" section in uncommitted status
|
|
29
|
+
- Conflict state banner shows merge state type + progress + action buttons (Continue/Skip/Abort)
|
|
30
|
+
- Auto-detects merge state only when conflicts exist (performance optimization)
|
|
31
|
+
|
|
32
|
+
- **Inline Conflict Resolution Editor** — New conflict-editor tab type with Monaco
|
|
33
|
+
- Dedicated `conflict-editor` tab type for visual conflict resolution
|
|
34
|
+
- Monaco-based editor with syntax highlighting per file type (JS/TS/Python/etc.)
|
|
35
|
+
- Conflict regions visually highlighted: green for current, blue for incoming, gray for markers
|
|
36
|
+
- Accept buttons: Accept Current / Accept Incoming / Accept Both (with proper merging)
|
|
37
|
+
- Conflict counter in header: "N conflicts remaining" → "All conflicts resolved" on completion
|
|
38
|
+
- Automatic file save after each resolution; conflict count updates in real-time
|
|
39
|
+
- Parses 3-way conflict markers (<<<<<<, =======, >>>>>>>) and extracts labels (HEAD/branch names)
|
|
40
|
+
|
|
41
|
+
- **Worktree Management** — Full CRUD with project integration
|
|
42
|
+
- Worktree toolbar popover listing all worktrees (with path, branch, HEAD status)
|
|
43
|
+
- Create worktree from commit context menu ("Create Worktree Here...")
|
|
44
|
+
- Remove, prune, and auto-lock operations per worktree
|
|
45
|
+
- Current worktree highlighted with badge and active background
|
|
46
|
+
- Auto-add unregistered worktrees as projects via confirmation dialog
|
|
47
|
+
- Branch-already-exists dialog offers force-replace option for branch conflicts
|
|
48
|
+
- Project switcher integration: switch to worktree branches directly
|
|
49
|
+
|
|
12
50
|
- **Bundled Extensions Support** — Auto-discover extensions from packages/ext-* directories
|
|
13
51
|
- PPM discovers bundled extensions (e.g., ext-git-graph) without manual installation
|
|
14
52
|
- Bundled extensions available out-of-the-box with all PPM instances
|
|
@@ -29,12 +67,13 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
|
|
|
29
67
|
- Path traversal validation for security (assertSafePath in RPC handlers)
|
|
30
68
|
- Fallback guards for all tab type handling (unknown tab types safely ignored)
|
|
31
69
|
|
|
32
|
-
- **Extension
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
70
|
+
- **Extension Error Reporting & Logging** — Silent failure debugging & user feedback
|
|
71
|
+
- Activation error tracking: Map stores `extId → error message` in ExtensionService
|
|
72
|
+
- Error toasts on command failure: "Extension command failed: {error}" displays in UI
|
|
73
|
+
- Timeout UX improved: Fallback UI shows activation error + "Retry" button for quick recovery
|
|
74
|
+
- Breadcrumb logging with tags for debugging: `[ExtService]`, `[ExtHost]`, `[ExtWS]`, `[ext-git-graph]`
|
|
75
|
+
- Console logs track: activation start/success, command routing, failures with context
|
|
76
|
+
- Activation errors included in `contributions:update` message sent to browser on WS connect
|
|
38
77
|
|
|
39
78
|
- **Faithful SVG Graph Rendering** — Port of vscode-git-graph algorithm with deterministic layout
|
|
40
79
|
- Single SVG model with continuous branch paths using Bézier curves
|
|
@@ -47,32 +86,54 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
|
|
|
47
86
|
- Removed dot alignment bug that forced rows to 29px
|
|
48
87
|
|
|
49
88
|
### Technical Details
|
|
89
|
+
|
|
90
|
+
**Stash/Rebase/Conflict/Worktree Implementation:**
|
|
91
|
+
- **Files Created:**
|
|
92
|
+
- `src/web/components/editor/conflict-editor.tsx` — Monaco-based conflict resolution editor with visual highlighting, accept buttons, real-time conflict counter, auto-save on resolution
|
|
93
|
+
|
|
50
94
|
- **Files Modified:**
|
|
95
|
+
- `packages/ext-git-graph/src/extension.ts` — Added stash parsing, merge state detection (merge/rebase/cherry-pick), conflict file opening, worktree CRUD operations, branch context menu rebase action
|
|
96
|
+
- `packages/ext-git-graph/src/types.ts` — Added Stash interface, MergeState interface (type, progress, message), FileChange status "U" for unmerged, Worktree interface, updated UncommittedData with conflicted field
|
|
97
|
+
- `packages/ext-git-graph/src/webview-html.ts` — Added stash popover UI, rebase context menu item, conflict section in uncommitted panel, merge state banner with Continue/Skip/Abort, worktree popover with CRUD UI
|
|
98
|
+
- `src/web/stores/tab-store.ts` — Added "conflict-editor" as valid TabType
|
|
99
|
+
- `src/web/stores/panel-utils.ts` — Added conflict-editor case to deriveTabId() for metadata-driven tab ID generation
|
|
100
|
+
- `src/web/components/layout/editor-panel.tsx` — Lazy-imported ConflictEditor component with fallback
|
|
101
|
+
- `src/web/components/layout/tab-content.tsx` — Lazy-imported ConflictEditor component with fallback
|
|
102
|
+
- `src/web/components/layout/mobile-nav.tsx` — Added conflict-editor icon (conflict/warning icon)
|
|
103
|
+
- `src/web/components/layout/tab-bar.tsx` — Added conflict-editor icon (conflict/warning icon)
|
|
104
|
+
|
|
105
|
+
- **Services Modified:**
|
|
51
106
|
- `src/services/extension-manifest.ts` — Added discoverBundledManifests() to scan packages/ext-* dirs
|
|
52
107
|
- `src/services/extension.service.ts` — Added extensionPaths Map, bundledIds Set, isBundled() method; updated discover()/activate()/remove() to handle bundled extensions
|
|
53
108
|
- `src/cli/commands/ext-cmd.ts` — Added "Source" column to `ppm ext list`, calls discover() to populate bundled info
|
|
54
|
-
- `packages/ext-git-graph/src/webview-html.ts` — UX refinements: branch context menu, dblclick checkout, toast notifications, SVG Lucide icons, 10s auto-fetch option, improved UI responsiveness
|
|
55
|
-
- `packages/ext-git-graph/src/extension.ts` — 5s uncommitted status polling, openFile/openSourceControl handlers, enhanced error handling
|
|
56
|
-
- `packages/ext-git-graph/src/types.ts` — Added autoFetchInterval, new message types
|
|
57
109
|
- `src/services/extension-rpc-handlers.ts` — Allow git operations in all registered project paths (not just CWD), improved path validation
|
|
58
110
|
- `src/services/extension-host-worker.ts` — Enhanced error logging, localHandlers check, disposed flag fix for polling race conditions
|
|
59
|
-
|
|
60
|
-
- `src/web/components/layout/mobile-nav.tsx` — Fallback guard for unknown tab types
|
|
61
|
-
- `src/web/components/layout/tab-content.tsx` — Fallback guard for unknown tab types
|
|
62
|
-
- `src/web/components/layout/editor-panel.tsx` — Fallback guard for unknown tab types
|
|
63
|
-
- `src/web/hooks/use-extension-ws.ts` — Enhanced RPC integration
|
|
64
|
-
- `src/types/extension-messages.ts` — New message types for UX events
|
|
65
|
-
- **New Tests:**
|
|
66
|
-
- `tests/unit/services/extension-manifest.test.ts` — 9 tests for discoverBundledManifests() and manifest parsing
|
|
67
|
-
- `tests/unit/services/extension-service-bundled.test.ts` — 9 tests for isBundled(), discover(), and removal protection
|
|
111
|
+
|
|
68
112
|
- **Type Changes:**
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
- New
|
|
72
|
-
-
|
|
113
|
+
- New: `Stash` = { index, hash, message }
|
|
114
|
+
- New: `MergeState` = { type: "merge" | "rebase" | "cherry-pick", progress?, message? }
|
|
115
|
+
- New: `Worktree` = { path, branch, head, isMain, isDetached, locked, lockReason?, prunable }
|
|
116
|
+
- Updated: `FileChange.status` now includes "U" for unmerged/conflicted entries (was "A" | "M" | "D" | "R" | "C")
|
|
117
|
+
- Updated: `UncommittedData` now includes `conflicted: FileChange[]` and optional `mergeState: MergeState`
|
|
118
|
+
- Updated: `RepoInfo` now includes `stashes: Stash[]` and `currentBranch: string`
|
|
119
|
+
- Updated: `TabType` now includes "conflict-editor"
|
|
120
|
+
|
|
121
|
+
- **Git State Detection:**
|
|
122
|
+
- Merge state determined by .git sentinel files: MERGE_HEAD (merge), rebase-merge/ (interactive rebase), CHERRY_PICK_HEAD (cherry-pick)
|
|
123
|
+
- Progress tracked: "3/5" format from rebase-merge/msgnum and rebase-merge/end
|
|
124
|
+
- Conflict detection: UU/AA/DD/AU/UA/DU/UD git status codes parsed as unmerged entries
|
|
125
|
+
- Only detects merge state when conflicts exist (perf optimization)
|
|
126
|
+
|
|
127
|
+
- **Conflict Resolution Flow:**
|
|
128
|
+
- User clicks conflicted file → opens conflict-editor tab
|
|
129
|
+
- Component parses conflict markers (<<<<<<, =======, >>>>>>>) from file content
|
|
130
|
+
- Displays visual highlighting + Accept buttons above each conflict region
|
|
131
|
+
- Resolution applies edit via Monaco, saves file automatically, updates conflict counter
|
|
132
|
+
- Conflict count reflects real-time resolution progress
|
|
133
|
+
|
|
73
134
|
- **Security:** Path validation ensures extensions can only operate on registered project paths
|
|
74
135
|
- **Breaking Changes:** None (backward compatible)
|
|
75
|
-
- **Test Coverage:**
|
|
136
|
+
- **Test Coverage:** All changes maintain test suite passing
|
|
76
137
|
|
|
77
138
|
---
|
|
78
139
|
|
package/docs/project-roadmap.md
CHANGED
|
@@ -71,6 +71,7 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
71
71
|
- File download feature (v0.9.2) — Single-file + folder-as-zip downloads with short-lived tokens, context menu + toolbar UI
|
|
72
72
|
- Agent Team UI (v0.9.9) — Real-time team monitoring dashboard: REST API + fs.watch inbox events, team activity button with unread pulse, popover/drawer with members + messages, team management in Settings
|
|
73
73
|
- Git-Graph UI Improvements (v0.9.85+) — ✅ Faithful SVG graph rendering (vscode-git-graph port), interactive git workflow (stage/unstage/commit/stash), branch filters, auto-fetch, mobile support, tab system safety guards, UX refinements (branch context menu, dblclick checkout, toast notifications, SVG icons)
|
|
74
|
+
- Git Workflow Enhancements (v0.9.86+) — ✅ Stash management (popover with Apply/Pop/Drop), rebase from context menu, conflict detection (merge/rebase/cherry-pick states), inline conflict resolution editor (Monaco with visual highlighting + Accept buttons), worktree full CRUD (create/remove/prune with project integration)
|
|
74
75
|
|
|
75
76
|
**Multi-provider — v0.9 scope (reduced):**
|
|
76
77
|
- Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
|
|
@@ -100,12 +101,12 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
100
101
|
|
|
101
102
|
### v0.10.0 — "Enhanced Workflow" (Q3 2026)
|
|
102
103
|
|
|
103
|
-
**Theme:** Agent collaboration + git workflow. High-impact, independent features that ship fast.
|
|
104
|
+
**Theme:** Agent collaboration + advanced git workflow. High-impact, independent features that ship fast.
|
|
104
105
|
|
|
105
106
|
| Feature | Priority | Description |
|
|
106
107
|
|---------|----------|-------------|
|
|
107
108
|
| **Agent Team** | High | Multi-agent collaboration within PPM. Spawn agent teams for parallel task execution — lead agent delegates to specialist agents (coder, tester, reviewer). Task coordination, file ownership, progress tracking. |
|
|
108
|
-
| **
|
|
109
|
+
| **Advanced Git Operations** | Medium | Interactive rebase UI, cherry-pick workflow, merge strategy selection. (Worktree management completed in v0.9.86) |
|
|
109
110
|
|
|
110
111
|
---
|
|
111
112
|
|
|
@@ -151,13 +151,14 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
|
151
151
|
```
|
|
152
152
|
/project/{name} → Project root (project switcher)
|
|
153
153
|
/project/{name}/editor/{filePath} → Open editor tab (e.g., src/index.ts)
|
|
154
|
+
/project/{name}/conflict-editor/{filePath} → Open conflict resolution editor (during merge/rebase)
|
|
154
155
|
/project/{name}/chat/{provider}/{sessionId} → Open chat tab
|
|
155
156
|
/project/{name}/terminal/{index} → Open terminal tab
|
|
156
157
|
/project/{name}/database/{connId}/{table} → Open database browser
|
|
157
158
|
/project/{name}/git-graph → Git history graph (singleton)
|
|
158
159
|
/project/{name}/settings → Settings panel (singleton)
|
|
159
160
|
```
|
|
160
|
-
Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
|
|
161
|
+
Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `conflict-editor:src/file.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
|
|
161
162
|
|
|
162
163
|
---
|
|
163
164
|
|
|
@@ -1676,6 +1677,48 @@ Main Process Worker
|
|
|
1676
1677
|
|
|
1677
1678
|
5. Extension auto-activates based on `activationEvents`, state persists
|
|
1678
1679
|
|
|
1680
|
+
### Error Handling & Debugging
|
|
1681
|
+
|
|
1682
|
+
**Activation Error Tracking:**
|
|
1683
|
+
- `ExtensionService.activationErrors` Map tracks `extId → error message` for all failed activations
|
|
1684
|
+
- Errors set during `activate()` if worker response indicates failure (`!result.ok`)
|
|
1685
|
+
- Errors cleared on successful activation or worker termination
|
|
1686
|
+
- Errors included in `contributions:update` message sent via WS to browser on client connect
|
|
1687
|
+
|
|
1688
|
+
**User Feedback (UI):**
|
|
1689
|
+
- **Command Errors:** When extension command fails, toast shows "Extension command failed: {error}" with error details
|
|
1690
|
+
- **Timeout Handling:** If webview panel doesn't load within 10s, fallback UI displays activation error (if available) + "Retry" button
|
|
1691
|
+
- **Retry Button:** User can click to re-trigger the command without page reload (re-dispatches `ext:command:execute`)
|
|
1692
|
+
|
|
1693
|
+
**Breadcrumb Logging (Console):**
|
|
1694
|
+
- **`[ExtService]`** — Main process lifecycle: activation start/success, worker lifecycle, contributions broadcast
|
|
1695
|
+
- **`[ExtHost]`** — Worker-side execution: command routing, handler invocation, error context
|
|
1696
|
+
- **`[ExtWS]`** — WebSocket bridge: client connect, message handling, error responses
|
|
1697
|
+
- **Extension-specific tags** — e.g., `[ext-git-graph]` for extension-specific log context
|
|
1698
|
+
|
|
1699
|
+
**Example Log Flow (normal):**
|
|
1700
|
+
```
|
|
1701
|
+
[ExtService] startup: activating ext-git-graph...
|
|
1702
|
+
[ExtWS] Client connected (1 total)
|
|
1703
|
+
[ExtHost] activating ext-git-graph from dist/extension.js
|
|
1704
|
+
[ExtHost] activated ext-git-graph (1 total)
|
|
1705
|
+
[ExtService] activated ext-git-graph successfully
|
|
1706
|
+
[ExtWS] command:execute "git-graph.view"
|
|
1707
|
+
[ExtHost] command:execute "git-graph.view" (1 extensions active)
|
|
1708
|
+
[ExtHost] routing "git-graph.view" → ext-git-graph
|
|
1709
|
+
```
|
|
1710
|
+
|
|
1711
|
+
**Example Log Flow (error):**
|
|
1712
|
+
```
|
|
1713
|
+
[ExtService] startup: activating ext-git-graph...
|
|
1714
|
+
[ExtHost] activating ext-git-graph from dist/extension.ts
|
|
1715
|
+
[ExtHost] ERROR: Cannot find module 'missing-dep'
|
|
1716
|
+
[ExtService] Failed to activate ext-git-graph on startup: Cannot find module 'missing-dep'
|
|
1717
|
+
→ activationErrors["ext-git-graph"] = "Cannot find module 'missing-dep'"
|
|
1718
|
+
→ browser receives { type: "contributions:update", activationErrors: {"ext-git-graph": "..."} }
|
|
1719
|
+
→ user sees toast: "Extension "ext-git-graph" failed to activate: Cannot find module..."
|
|
1720
|
+
```
|
|
1721
|
+
|
|
1679
1722
|
### Crash Safety
|
|
1680
1723
|
|
|
1681
1724
|
**Worker Isolation:**
|
package/package.json
CHANGED
|
@@ -63,12 +63,20 @@ export function activate(context: ExtensionContext, vscode: VscodeApi): void {
|
|
|
63
63
|
context.subscriptions.push(
|
|
64
64
|
vscode.commands.registerCommand("git-graph.view", async (...args: unknown[]) => {
|
|
65
65
|
const projectPath = args[0] as string | undefined;
|
|
66
|
+
console.log(`[ext-git-graph] git-graph.view command received, projectPath=${projectPath ?? "(none)"}`);
|
|
66
67
|
const resolvedPath = projectPath || await resolveProjectPath();
|
|
67
68
|
if (!resolvedPath) {
|
|
69
|
+
console.warn("[ext-git-graph] no project path resolved");
|
|
68
70
|
await vscode.window.showErrorMessage("Git Graph: No project selected. Open a project first, then try again.");
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
|
-
|
|
73
|
+
try {
|
|
74
|
+
await openGitGraph(vscode, context, resolvedPath);
|
|
75
|
+
console.log("[ext-git-graph] webview panel created");
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error("[ext-git-graph] openGitGraph failed:", e);
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
72
80
|
}),
|
|
73
81
|
);
|
|
74
82
|
|
|
@@ -156,6 +164,7 @@ function openGitGraph(
|
|
|
156
164
|
await handleRequestCommits(vscode, panel, pp, context);
|
|
157
165
|
handleUncommittedStatus(vscode, panel, pp); // fire-and-forget
|
|
158
166
|
handleWorktrees(vscode, panel, pp); // fire-and-forget
|
|
167
|
+
handleStashes(vscode, panel, pp); // fire-and-forget
|
|
159
168
|
break;
|
|
160
169
|
case "requestRepoInfo":
|
|
161
170
|
await handleRepoInfo(vscode, panel, pp);
|
|
@@ -263,6 +272,9 @@ function openGitGraph(
|
|
|
263
272
|
case "requestWorktrees":
|
|
264
273
|
await handleWorktrees(vscode, panel, pp);
|
|
265
274
|
break;
|
|
275
|
+
case "requestStashes":
|
|
276
|
+
await handleStashes(vscode, panel, pp);
|
|
277
|
+
break;
|
|
266
278
|
case "addWorktree": {
|
|
267
279
|
const addArgs = ["worktree", "add"];
|
|
268
280
|
if (msg.newBranch) {
|
|
@@ -333,6 +345,16 @@ function openGitGraph(
|
|
|
333
345
|
}
|
|
334
346
|
break;
|
|
335
347
|
}
|
|
348
|
+
case "openConflictFile": {
|
|
349
|
+
assertSafeFilePaths([msg.filePath], pp);
|
|
350
|
+
const projectName = await resolveProjectName(pp);
|
|
351
|
+
// Opens as conflict-editor tab (Phase 4 will wire this properly)
|
|
352
|
+
await vscode.window.openTab("conflict-editor", `Conflict: ${msg.filePath.split(/[\\/]/).pop()}`, projectName, {
|
|
353
|
+
projectName,
|
|
354
|
+
filePath: msg.filePath,
|
|
355
|
+
});
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
336
358
|
case "openSourceControl": {
|
|
337
359
|
await vscode.window.showInformationMessage("Open the Source Control panel from the sidebar.");
|
|
338
360
|
break;
|
|
@@ -372,6 +394,7 @@ async function reloadPanelData(
|
|
|
372
394
|
await handleRequestCommits(vscode, panel, projectPath, context);
|
|
373
395
|
handleUncommittedStatus(vscode, panel, projectPath);
|
|
374
396
|
handleWorktrees(vscode, panel, projectPath);
|
|
397
|
+
handleStashes(vscode, panel, projectPath);
|
|
375
398
|
}
|
|
376
399
|
|
|
377
400
|
async function handleRepoInfo(
|
|
@@ -453,6 +476,8 @@ async function handleCommitDetails(
|
|
|
453
476
|
await panel.webview.postMessage({ command: "commitDetails", data: detail });
|
|
454
477
|
}
|
|
455
478
|
|
|
479
|
+
const UNMERGED_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
480
|
+
|
|
456
481
|
async function handleUncommittedStatus(
|
|
457
482
|
vscode: VscodeApi,
|
|
458
483
|
panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
|
|
@@ -466,11 +491,20 @@ async function handleUncommittedStatus(
|
|
|
466
491
|
}
|
|
467
492
|
const staged: import("./types.ts").FileChange[] = [];
|
|
468
493
|
const unstaged: import("./types.ts").FileChange[] = [];
|
|
494
|
+
const conflicted: import("./types.ts").FileChange[] = [];
|
|
469
495
|
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
470
|
-
if (staged.length + unstaged.length >= 500) break;
|
|
471
|
-
const
|
|
472
|
-
const y = line[1]; // unstaged status
|
|
496
|
+
if (staged.length + unstaged.length + conflicted.length >= 500) break;
|
|
497
|
+
const xy = line.substring(0, 2);
|
|
473
498
|
const filePath = line.substring(3);
|
|
499
|
+
|
|
500
|
+
// Check for unmerged/conflict entries first
|
|
501
|
+
if (UNMERGED_CODES.has(xy)) {
|
|
502
|
+
conflicted.push({ path: filePath, status: "U", additions: 0, deletions: 0 });
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const x = xy[0]; // staged status
|
|
507
|
+
const y = xy[1]; // unstaged status
|
|
474
508
|
if (x !== " " && x !== "?") {
|
|
475
509
|
staged.push({ path: filePath, status: mapStatusCode(x), additions: 0, deletions: 0 });
|
|
476
510
|
}
|
|
@@ -481,15 +515,72 @@ async function handleUncommittedStatus(
|
|
|
481
515
|
unstaged.push({ path: filePath, status: "A", additions: 0, deletions: 0 });
|
|
482
516
|
}
|
|
483
517
|
}
|
|
518
|
+
|
|
519
|
+
// Only detect merge state when conflicts exist (perf optimization)
|
|
520
|
+
let mergeState: import("./types.ts").MergeState | undefined;
|
|
521
|
+
if (conflicted.length > 0) {
|
|
522
|
+
mergeState = await detectMergeState(vscode, projectPath);
|
|
523
|
+
}
|
|
524
|
+
|
|
484
525
|
await panel.webview.postMessage({
|
|
485
526
|
command: "loadUncommitted",
|
|
486
|
-
data: { staged, unstaged },
|
|
527
|
+
data: { staged, unstaged, conflicted, mergeState },
|
|
487
528
|
});
|
|
488
529
|
} catch {
|
|
489
530
|
await panel.webview.postMessage({ command: "loadUncommitted", data: null });
|
|
490
531
|
}
|
|
491
532
|
}
|
|
492
533
|
|
|
534
|
+
async function detectMergeState(
|
|
535
|
+
vscode: VscodeApi,
|
|
536
|
+
projectPath: string,
|
|
537
|
+
): Promise<import("./types.ts").MergeState | undefined> {
|
|
538
|
+
// Resolve the actual GIT_DIR (handles worktrees where .git is a pointer file)
|
|
539
|
+
const gitDirResult = await spawnGit(vscode, ["rev-parse", "--git-dir"], projectPath, 2000);
|
|
540
|
+
if (gitDirResult.exitCode !== 0) return undefined;
|
|
541
|
+
let gitDir = gitDirResult.stdout.trim();
|
|
542
|
+
// Make absolute if relative
|
|
543
|
+
if (!gitDir.startsWith("/")) gitDir = `${projectPath}/${gitDir}`;
|
|
544
|
+
|
|
545
|
+
// Check rebase-merge (interactive rebase)
|
|
546
|
+
const rebaseMergeDir = `${gitDir}/rebase-merge`;
|
|
547
|
+
const checkRebase = await vscode.process.spawn("test", ["-d", rebaseMergeDir], projectPath, { timeout: 2000 });
|
|
548
|
+
if (checkRebase.exitCode === 0) {
|
|
549
|
+
const [numResult, endResult, msgResult] = await Promise.all([
|
|
550
|
+
vscode.process.spawn("cat", [`${rebaseMergeDir}/msgnum`], projectPath, { timeout: 2000 }),
|
|
551
|
+
vscode.process.spawn("cat", [`${rebaseMergeDir}/end`], projectPath, { timeout: 2000 }),
|
|
552
|
+
vscode.process.spawn("cat", [`${rebaseMergeDir}/message`], projectPath, { timeout: 2000 }),
|
|
553
|
+
]);
|
|
554
|
+
const current = numResult.stdout.trim();
|
|
555
|
+
const total = endResult.stdout.trim();
|
|
556
|
+
return {
|
|
557
|
+
type: "rebase",
|
|
558
|
+
progress: current && total ? `${current}/${total}` : undefined,
|
|
559
|
+
message: msgResult.stdout.trim().split("\n")[0] || undefined,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Check rebase-apply (non-interactive rebase / am)
|
|
564
|
+
const checkRebaseApply = await vscode.process.spawn("test", ["-d", `${gitDir}/rebase-apply`], projectPath, { timeout: 2000 });
|
|
565
|
+
if (checkRebaseApply.exitCode === 0) {
|
|
566
|
+
return { type: "rebase" };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Check merge
|
|
570
|
+
const checkMerge = await vscode.process.spawn("test", ["-f", `${gitDir}/MERGE_HEAD`], projectPath, { timeout: 2000 });
|
|
571
|
+
if (checkMerge.exitCode === 0) {
|
|
572
|
+
return { type: "merge" };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Check cherry-pick
|
|
576
|
+
const checkCherry = await vscode.process.spawn("test", ["-f", `${gitDir}/CHERRY_PICK_HEAD`], projectPath, { timeout: 2000 });
|
|
577
|
+
if (checkCherry.exitCode === 0) {
|
|
578
|
+
return { type: "cherry-pick" };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
|
|
493
584
|
async function handleWorktrees(
|
|
494
585
|
vscode: VscodeApi,
|
|
495
586
|
panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
|
|
@@ -527,6 +618,29 @@ async function handleWorktrees(
|
|
|
527
618
|
await panel.webview.postMessage({ command: "loadWorktrees", data: worktrees });
|
|
528
619
|
}
|
|
529
620
|
|
|
621
|
+
async function handleStashes(
|
|
622
|
+
vscode: VscodeApi,
|
|
623
|
+
panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
|
|
624
|
+
projectPath: string,
|
|
625
|
+
): Promise<void> {
|
|
626
|
+
const result = await spawnGit(vscode, ["stash", "list", "--format=%gd|%H|%s"], projectPath, 10_000);
|
|
627
|
+
const stashes: import("./types.ts").Stash[] = [];
|
|
628
|
+
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
629
|
+
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
630
|
+
const parts = line.split("|");
|
|
631
|
+
if (parts.length >= 3) {
|
|
632
|
+
const refMatch = parts[0].match(/\{(\d+)\}/);
|
|
633
|
+
stashes.push({
|
|
634
|
+
index: refMatch ? parseInt(refMatch[1]) : stashes.length,
|
|
635
|
+
hash: parts[1],
|
|
636
|
+
message: parts.slice(2).join("|"),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
await panel.webview.postMessage({ command: "loadStashes", data: stashes });
|
|
642
|
+
}
|
|
643
|
+
|
|
530
644
|
function mapStatusCode(code: string): "A" | "M" | "D" | "R" {
|
|
531
645
|
if (code === "A" || code === "?") return "A";
|
|
532
646
|
if (code === "D") return "D";
|
|
@@ -754,6 +868,13 @@ function buildGitActionArgs(action: string, args: Record<string, unknown>): stri
|
|
|
754
868
|
case "stashSave": return ["stash", "push", ...(args.message ? ["-m", String(args.message)] : [])];
|
|
755
869
|
case "stashPop": return ["stash", "pop", ...(args.stashRef ? [assertValidRef(args.stashRef, "stashRef")] : [])];
|
|
756
870
|
case "stashDrop": return ["stash", "drop", ...(args.stashRef ? [assertValidRef(args.stashRef, "stashRef")] : [])];
|
|
871
|
+
case "stashApply": return ["stash", "apply", ...(args.stashRef ? [assertValidRef(args.stashRef, "stashRef")] : [])];
|
|
872
|
+
case "rebaseContinue": return ["rebase", "--continue"];
|
|
873
|
+
case "rebaseAbort": return ["rebase", "--abort"];
|
|
874
|
+
case "rebaseSkip": return ["rebase", "--skip"];
|
|
875
|
+
case "mergeAbort": return ["merge", "--abort"];
|
|
876
|
+
case "cherryPickAbort": return ["cherry-pick", "--abort"];
|
|
877
|
+
case "cherryPickContinue": return ["cherry-pick", "--continue"];
|
|
757
878
|
case "fetch": return ["fetch", ...(args.remote ? [assertValidRemote(args.remote)] : []), ...(args.prune ? ["--prune"] : [])];
|
|
758
879
|
case "pull": return ["pull", ...(args.remote ? [assertValidRemote(args.remote)] : []), ...(args.branch ? [assertValidRef(args.branch, "branch")] : [])];
|
|
759
880
|
case "renameBranch": {
|
|
@@ -71,11 +71,17 @@ export interface CommitDetail {
|
|
|
71
71
|
export interface FileChange {
|
|
72
72
|
path: string;
|
|
73
73
|
oldPath?: string;
|
|
74
|
-
status: "A" | "M" | "D" | "R" | "C";
|
|
74
|
+
status: "A" | "M" | "D" | "R" | "C" | "U";
|
|
75
75
|
additions: number;
|
|
76
76
|
deletions: number;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export interface MergeState {
|
|
80
|
+
type: "merge" | "rebase" | "cherry-pick";
|
|
81
|
+
progress?: string; // e.g. "3/5" for rebase
|
|
82
|
+
message?: string; // current commit message being rebased
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
export interface RepoInfo {
|
|
80
86
|
path: string;
|
|
81
87
|
branches: Branch[];
|
|
@@ -94,6 +100,8 @@ export interface ActionResult {
|
|
|
94
100
|
export interface UncommittedData {
|
|
95
101
|
staged: FileChange[];
|
|
96
102
|
unstaged: FileChange[];
|
|
103
|
+
conflicted: FileChange[];
|
|
104
|
+
mergeState?: MergeState;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
// --- Settings ---
|
|
@@ -152,6 +160,7 @@ export type ExtToWebview =
|
|
|
152
160
|
| { command: "refresh"; data: GitVertex[]; repoInfo: RepoInfo }
|
|
153
161
|
| { command: "actionResult"; action: string; args?: Record<string, unknown>; result: ActionResult }
|
|
154
162
|
| { command: "loadWorktrees"; data: Worktree[] }
|
|
163
|
+
| { command: "loadStashes"; data: Stash[] }
|
|
155
164
|
| { command: "error"; message: string };
|
|
156
165
|
|
|
157
166
|
// --- Webview → Extension messages ---
|
|
@@ -178,4 +187,6 @@ export type WebviewToExt =
|
|
|
178
187
|
| { command: "pruneWorktrees" }
|
|
179
188
|
| { command: "openWorktree"; path: string }
|
|
180
189
|
| { command: "openFile"; filePath: string }
|
|
181
|
-
| { command: "
|
|
190
|
+
| { command: "openConflictFile"; filePath: string }
|
|
191
|
+
| { command: "openSourceControl" }
|
|
192
|
+
| { command: "requestStashes" };
|