diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1c740..3fa6c7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- Read the workspace state from `profile-state.db`, the SQLite store of Orca 1.4.214 and later. The sync process opens it read-only. Profiles that store the state in `orca-data.json` continue to work. +- The installer prefers `profile-state.db` when a profile has both state files. +- Read `profile-state.db` when it is next to a configured `orca-data.json`. Orca does not update the JSON file after the import, so an installation made before an Orca update keeps working. + ## 0.1.0 — 2026-09-15 Initial public release. diff --git a/README.md b/README.md index 64a7dc2..c73b008 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ View branches, merges, tags, authors, and commit details without leaving Orca. S ## Quick start -**Requirements:** Git, Python 3.9+, and Orca desktop. The desktop integration was tested with **Orca 1.4.185 on macOS**. Windows and Linux support is experimental; their filesystem/installer paths have automated tests, but desktop integration needs validation. +**Requirements:** Git, Python 3.9+, and Orca desktop. The desktop integration was tested with **Orca 1.4.185 on macOS**. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Windows and Linux support is experimental; their filesystem/installer paths have automated tests, but desktop integration needs validation. Run these commands in an **Orca terminal**: @@ -63,7 +63,7 @@ flowchart LR D --> E[Orca right-sidebar plugin] ``` -The sync process reads the selected workspace from the configured Orca profile's local state file. It polls selection every 0.75 seconds and Git references every 10 seconds. On a change, it replaces the generated HTML atomically; Orca's development-plugin watcher reloads the panel. Workspace switching typically takes about 1–3 seconds for small local repositories, with additional Git collection time for larger histories. +The sync process reads the selected workspace from the configured Orca profile's local state file: `orca-data.json` in Orca 1.4.185, or the `profile-state.db` SQLite database in Orca 1.4.214 and later. It polls selection every 0.75 seconds and Git references every 10 seconds. On a change, it replaces the generated HTML atomically; Orca's development-plugin watcher reloads the panel. Workspace switching typically takes about 1–3 seconds for small local repositories, with additional Git collection time for larger histories. This is an independent community project. It is not affiliated with Orca, VS Code, or the VS Code Git Graph extension. It uses Orca's experimental plugin panel API and a version-dependent local state-file integration. See [architecture and compatibility](docs/architecture.md). diff --git a/docs/architecture.md b/docs/architecture.md index fef9a14..a3c3d46 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,7 +4,7 @@ Orca Git Graph combines a standard-library Python sync process with an Orca plug ## Data flow -1. Read the configured profile's `orca-data.json`. +1. Read the configured profile's state: `profile-state.db` (Orca 1.4.214 and later) or `orca-data.json` (Orca 1.4.185). 2. Resolve `workspaceSession.activeWorktreeId` to the exact selected local worktree path. 3. Read Git commits, references, HEAD, upstream divergence, and changed-file names with argument-based subprocess calls. 4. Escape commit metadata before embedding it in a self-contained HTML document. @@ -13,12 +13,18 @@ Orca Git Graph combines a standard-library Python sync process with an Orca plug `activeWorktreeId` takes precedence over `activeWorkspaceKey`. During development with Orca 1.4.185, the latter could retain a previous workspace after navigation. If `activeWorktreeId` explicitly becomes null, the graph clears; it does not silently fall back to an old project. +### State formats + +Orca 1.4.185 stores the state in one JSON file, `orca-data.json`. Orca 1.4.214 and later store it in a SQLite database, `profile-state.db`. Its `profile_state_documents` table has one JSON `payload` for each former top-level key of `orca-data.json`. The sync process reads only the `workspaceSession`, `repos`, and `folderWorkspaces` rows, so the same selection rules apply to both formats. If a profile has both files, the installer uses `profile-state.db`. Orca imports `orca-data.json` once and does not update it after that, so the file becomes stale. For this reason, the sync process also reads `profile-state.db` when `config.json` names an `orca-data.json` in the same folder. + +The sync process opens the database with SQLite's read-only mode (`mode=ro`). Orca keeps the database in WAL mode, so the read does not block Orca's writes and sees only committed state. When Orca is closed, SQLite can create empty `-wal` and `-shm` files next to the database. The database file itself does not change. + ## Modules | File | Responsibility | | --- | --- | | `install.py` | Locate Orca state, copy an isolated runtime, print registration steps, optionally open a sync terminal | -| `orca_git_graph/workspace.py` | Platform paths, explicit profile selection, selected-workspace resolution | +| `orca_git_graph/workspace.py` | Platform paths, explicit profile selection, JSON and SQLite state reading, selected-workspace resolution | | `orca_git_graph/git_data.py` | Read-only Git commands and NUL-delimited parsing | | `orca_git_graph/app.py` | Polling, process lock, escaping, atomic HTML generation, diagnostics | | `orca_git_graph/panel.html` | SVG lane layout, commit table, search, branch filter, details | @@ -27,7 +33,7 @@ Orca Git Graph combines a standard-library Python sync process with an Orca plug ## Compatibility contract -The initial desktop target is Orca 1.4.185 on macOS. The plugin manifest requires `>=1.4.185`, but that is a minimum version gate, not a guarantee that every later Orca build is compatible. Both the plugin API and the persisted selection format are experimental/version-dependent. +The initial desktop target is Orca 1.4.185 on macOS. The state reader also supports the `profile-state.db` store of Orca 1.4.214 and later, checked with 1.4.224 on macOS. The plugin manifest requires `>=1.4.185`, but that is a minimum version gate, not a guarantee that every later Orca build is compatible. Both the plugin API and the persisted selection format are experimental/version-dependent. Linux and Windows have filesystem and process-lock implementations and automated core checks. Their Orca desktop registration, profile layout, and live-reload behavior are not yet end-to-end validated. Remote/SSH/cloud workspaces are intentionally unsupported. @@ -45,4 +51,4 @@ Changed-file lists are capped at 200 entries per commit. Rename detection is dis Runtime HTML contains metadata from the selected repository, including commit messages, names/emails, hashes, and file paths. Keep the runtime directory private. The installer places it outside the source checkout; generated data and local config are excluded from the public package. -The sync process reads Orca's state JSON to select a workspace. It does not print or export the whole state document, read runtime authentication tokens, or call Orca's internal RPC endpoints. The optional installer terminal launch uses the public Orca CLI. +The sync process reads Orca's state (the JSON file, or three rows of the SQLite database) to select a workspace. It does not print or export the state, read runtime authentication tokens, or call Orca's internal RPC endpoints. The optional installer terminal launch uses the public Orca CLI. diff --git a/docs/faq.md b/docs/faq.md index b784f10..3e792b2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -34,7 +34,7 @@ No. Keep Git Graph Sync running and restart it after quitting Orca. No operating ## Which platforms and Orca versions are supported? -Orca 1.4.185 on macOS is the tested desktop target. Windows and Linux desktop integration is experimental. The plugin/state interfaces are version-dependent; a newer Orca release may require an update. SSH and cloud workspaces are not supported in this release. +Orca 1.4.185 on macOS is the tested desktop target. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Windows and Linux desktop integration is experimental. The plugin/state interfaces are version-dependent; a newer Orca release may require an update. SSH and cloud workspaces are not supported in this release. ## Where should an AI assistant start? diff --git a/docs/index.md b/docs/index.md index 1472911..3be8a62 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,7 @@ View branches, merges, tags, authors, and commit details without leaving Orca. S ## Quick start -**Requirements:** Git, Python 3.9+, and Orca desktop. The desktop integration was tested with **Orca 1.4.185 on macOS**. Windows and Linux support is experimental; their filesystem/installer paths have automated tests, but desktop integration needs validation. +**Requirements:** Git, Python 3.9+, and Orca desktop. The desktop integration was tested with **Orca 1.4.185 on macOS**. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Windows and Linux support is experimental; their filesystem/installer paths have automated tests, but desktop integration needs validation. Run these commands in an **Orca terminal**: @@ -63,7 +63,7 @@ flowchart LR D --> E[Orca right-sidebar plugin] ``` -The sync process reads the selected workspace from the configured Orca profile's local state file. It polls selection every 0.75 seconds and Git references every 10 seconds. On a change, it replaces the generated HTML atomically; Orca's development-plugin watcher reloads the panel. Workspace switching typically takes about 1–3 seconds for small local repositories, with additional Git collection time for larger histories. +The sync process reads the selected workspace from the configured Orca profile's local state file: `orca-data.json` in Orca 1.4.185, or the `profile-state.db` SQLite database in Orca 1.4.214 and later. It polls selection every 0.75 seconds and Git references every 10 seconds. On a change, it replaces the generated HTML atomically; Orca's development-plugin watcher reloads the panel. Workspace switching typically takes about 1–3 seconds for small local repositories, with additional Git collection time for larger histories. This is an independent community project. It is not affiliated with Orca, VS Code, or the VS Code Git Graph extension. It uses Orca's experimental plugin panel API and a version-dependent local state-file integration. See [architecture and compatibility](architecture.md). diff --git a/docs/installation.md b/docs/installation.md index b6054ad..5dd57a2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -4,7 +4,7 @@ Orca Git Graph adds an interactive Git history graph to the Orca desktop IDE's r ## 1. Check the requirements -- Orca desktop, tested on version 1.4.185 on macOS. +- Orca desktop, tested on version 1.4.185 on macOS. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). - Python 3.9 or later: `python3 --version`. - Git: `git --version`. - A local repository already opened in Orca. @@ -97,9 +97,11 @@ python3 install.py --profile local-default Or supply the exact state-file path: ```sh -python3 install.py --orca-state "/path/to/orca/profile/orca-data.json" +python3 install.py --orca-state "/path/to/orca/profile/profile-state.db" ``` +Use the path of `orca-data.json` for an Orca release that stores the state as JSON. + Default Orca data roots: | Platform | Data root | @@ -108,7 +110,7 @@ Default Orca data roots: | Linux | `${XDG_CONFIG_HOME:-~/.config}/orca` | | Windows | `%APPDATA%\orca` | -`ORCA_USER_DATA_PATH`, when set, overrides the root. The installer searches profile directories for `orca-data.json`. If multiple profiles exist, it asks for an explicit `--profile` or `--orca-state` argument rather than choosing one silently. +`ORCA_USER_DATA_PATH`, when set, overrides the root. The installer searches profile directories for `profile-state.db` (Orca 1.4.214 and later) or `orca-data.json` (Orca 1.4.185). If a profile has both files, it uses `profile-state.db`. If multiple profiles exist, it asks for an explicit `--profile` or `--orca-state` argument rather than choosing one silently. ## Configuration @@ -116,7 +118,7 @@ The installed `config.json` contains: ```json { - "orca_state_path": "/path/to/the/selected/profile/orca-data.json", + "orca_state_path": "/path/to/the/selected/profile/profile-state.db", "limit": 400, "git_interval": 10, "selection_interval": 0.75 @@ -138,7 +140,7 @@ Change the limit or intervals, then restart Git Graph Sync. `limit` must be 1– 3. Keep the same Development path registered. If the plugin manifest changes, Orca may request another review. 4. Start Git Graph Sync if the installer did not start it. -The installer preserves your commit limit and polling intervals. If you originally used a custom profile or installation path, pass those flags again. It refuses to overwrite an unrecognized nonempty directory or update a running watcher. +The installer preserves your commit limit and polling intervals. It finds the state file again, so a run after an Orca update also changes `orca_state_path` to the new state format. If you originally used a custom profile or installation path, pass those flags again. It refuses to overwrite an unrecognized nonempty directory or update a running watcher. ## Uninstall diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 1e80c67..0afbf87 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -22,7 +22,7 @@ View branches, merges, tags, authors, and commit details without leaving Orca. S ## Quick start -**Requirements:** Git, Python 3.9+, and Orca desktop. The desktop integration was tested with **Orca 1.4.185 on macOS**. Windows and Linux support is experimental; their filesystem/installer paths have automated tests, but desktop integration needs validation. +**Requirements:** Git, Python 3.9+, and Orca desktop. The desktop integration was tested with **Orca 1.4.185 on macOS**. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Windows and Linux support is experimental; their filesystem/installer paths have automated tests, but desktop integration needs validation. Run these commands in an **Orca terminal**: @@ -63,7 +63,7 @@ flowchart LR D --> E[Orca right-sidebar plugin] ``` -The sync process reads the selected workspace from the configured Orca profile's local state file. It polls selection every 0.75 seconds and Git references every 10 seconds. On a change, it replaces the generated HTML atomically; Orca's development-plugin watcher reloads the panel. Workspace switching typically takes about 1–3 seconds for small local repositories, with additional Git collection time for larger histories. +The sync process reads the selected workspace from the configured Orca profile's local state file: `orca-data.json` in Orca 1.4.185, or the `profile-state.db` SQLite database in Orca 1.4.214 and later. It polls selection every 0.75 seconds and Git references every 10 seconds. On a change, it replaces the generated HTML atomically; Orca's development-plugin watcher reloads the panel. Workspace switching typically takes about 1–3 seconds for small local repositories, with additional Git collection time for larger histories. This is an independent community project. It is not affiliated with Orca, VS Code, or the VS Code Git Graph extension. It uses Orca's experimental plugin panel API and a version-dependent local state-file integration. See [architecture and compatibility](architecture.md). @@ -111,7 +111,7 @@ Orca Git Graph adds an interactive Git history graph to the Orca desktop IDE's r ## 1. Check the requirements -- Orca desktop, tested on version 1.4.185 on macOS. +- Orca desktop, tested on version 1.4.185 on macOS. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). - Python 3.9 or later: `python3 --version`. - Git: `git --version`. - A local repository already opened in Orca. @@ -204,9 +204,11 @@ python3 install.py --profile local-default Or supply the exact state-file path: ```sh -python3 install.py --orca-state "/path/to/orca/profile/orca-data.json" +python3 install.py --orca-state "/path/to/orca/profile/profile-state.db" ``` +Use the path of `orca-data.json` for an Orca release that stores the state as JSON. + Default Orca data roots: | Platform | Data root | @@ -215,7 +217,7 @@ Default Orca data roots: | Linux | `${XDG_CONFIG_HOME:-~/.config}/orca` | | Windows | `%APPDATA%\orca` | -`ORCA_USER_DATA_PATH`, when set, overrides the root. The installer searches profile directories for `orca-data.json`. If multiple profiles exist, it asks for an explicit `--profile` or `--orca-state` argument rather than choosing one silently. +`ORCA_USER_DATA_PATH`, when set, overrides the root. The installer searches profile directories for `profile-state.db` (Orca 1.4.214 and later) or `orca-data.json` (Orca 1.4.185). If a profile has both files, it uses `profile-state.db`. If multiple profiles exist, it asks for an explicit `--profile` or `--orca-state` argument rather than choosing one silently. ## Configuration @@ -223,7 +225,7 @@ The installed `config.json` contains: ```json { - "orca_state_path": "/path/to/the/selected/profile/orca-data.json", + "orca_state_path": "/path/to/the/selected/profile/profile-state.db", "limit": 400, "git_interval": 10, "selection_interval": 0.75 @@ -245,7 +247,7 @@ Change the limit or intervals, then restart Git Graph Sync. `limit` must be 1– 3. Keep the same Development path registered. If the plugin manifest changes, Orca may request another review. 4. Start Git Graph Sync if the installer did not start it. -The installer preserves your commit limit and polling intervals. If you originally used a custom profile or installation path, pass those flags again. It refuses to overwrite an unrecognized nonempty directory or update a running watcher. +The installer preserves your commit limit and polling intervals. It finds the state file again, so a run after an Orca update also changes `orca_state_path` to the new state format. If you originally used a custom profile or installation path, pass those flags again. It refuses to overwrite an unrecognized nonempty directory or update a running watcher. ## Uninstall @@ -273,7 +275,7 @@ Orca Git Graph combines a standard-library Python sync process with an Orca plug ## Data flow -1. Read the configured profile's `orca-data.json`. +1. Read the configured profile's state: `profile-state.db` (Orca 1.4.214 and later) or `orca-data.json` (Orca 1.4.185). 2. Resolve `workspaceSession.activeWorktreeId` to the exact selected local worktree path. 3. Read Git commits, references, HEAD, upstream divergence, and changed-file names with argument-based subprocess calls. 4. Escape commit metadata before embedding it in a self-contained HTML document. @@ -282,12 +284,18 @@ Orca Git Graph combines a standard-library Python sync process with an Orca plug `activeWorktreeId` takes precedence over `activeWorkspaceKey`. During development with Orca 1.4.185, the latter could retain a previous workspace after navigation. If `activeWorktreeId` explicitly becomes null, the graph clears; it does not silently fall back to an old project. +### State formats + +Orca 1.4.185 stores the state in one JSON file, `orca-data.json`. Orca 1.4.214 and later store it in a SQLite database, `profile-state.db`. Its `profile_state_documents` table has one JSON `payload` for each former top-level key of `orca-data.json`. The sync process reads only the `workspaceSession`, `repos`, and `folderWorkspaces` rows, so the same selection rules apply to both formats. If a profile has both files, the installer uses `profile-state.db`. Orca imports `orca-data.json` once and does not update it after that, so the file becomes stale. For this reason, the sync process also reads `profile-state.db` when `config.json` names an `orca-data.json` in the same folder. + +The sync process opens the database with SQLite's read-only mode (`mode=ro`). Orca keeps the database in WAL mode, so the read does not block Orca's writes and sees only committed state. When Orca is closed, SQLite can create empty `-wal` and `-shm` files next to the database. The database file itself does not change. + ## Modules | File | Responsibility | | --- | --- | | `install.py` | Locate Orca state, copy an isolated runtime, print registration steps, optionally open a sync terminal | -| `orca_git_graph/workspace.py` | Platform paths, explicit profile selection, selected-workspace resolution | +| `orca_git_graph/workspace.py` | Platform paths, explicit profile selection, JSON and SQLite state reading, selected-workspace resolution | | `orca_git_graph/git_data.py` | Read-only Git commands and NUL-delimited parsing | | `orca_git_graph/app.py` | Polling, process lock, escaping, atomic HTML generation, diagnostics | | `orca_git_graph/panel.html` | SVG lane layout, commit table, search, branch filter, details | @@ -296,7 +304,7 @@ Orca Git Graph combines a standard-library Python sync process with an Orca plug ## Compatibility contract -The initial desktop target is Orca 1.4.185 on macOS. The plugin manifest requires `>=1.4.185`, but that is a minimum version gate, not a guarantee that every later Orca build is compatible. Both the plugin API and the persisted selection format are experimental/version-dependent. +The initial desktop target is Orca 1.4.185 on macOS. The state reader also supports the `profile-state.db` store of Orca 1.4.214 and later, checked with 1.4.224 on macOS. The plugin manifest requires `>=1.4.185`, but that is a minimum version gate, not a guarantee that every later Orca build is compatible. Both the plugin API and the persisted selection format are experimental/version-dependent. Linux and Windows have filesystem and process-lock implementations and automated core checks. Their Orca desktop registration, profile layout, and live-reload behavior are not yet end-to-end validated. Remote/SSH/cloud workspaces are intentionally unsupported. @@ -314,7 +322,7 @@ Changed-file lists are capped at 200 entries per commit. Rename detection is dis Runtime HTML contains metadata from the selected repository, including commit messages, names/emails, hashes, and file paths. Keep the runtime directory private. The installer places it outside the source checkout; generated data and local config are excluded from the public package. -The sync process reads Orca's state JSON to select a workspace. It does not print or export the whole state document, read runtime authentication tokens, or call Orca's internal RPC endpoints. The optional installer terminal launch uses the public Orca CLI. +The sync process reads Orca's state (the JSON file, or three rows of the SQLite database) to select a workspace. It does not print or export the state, read runtime authentication tokens, or call Orca's internal RPC endpoints. The optional installer terminal launch uses the public Orca CLI. --- @@ -337,6 +345,8 @@ python3 ~/.local/share/orca-git-graph/run.py watch Run `doctor` and confirm the configured Orca profile. A switch normally takes the selection poll plus Git collection and Orca's Development reload. A large repository may take longer. +If you updated Orca after the installation, the configured state file can be out of date. Orca 1.4.214 and later store the workspace state in `profile-state.db` and stop updating `orca-data.json`. The sync process reads `profile-state.db` when it is next to the configured `orca-data.json`. To update `config.json` as well, stop Git Graph Sync, run `python3 install.py` again from the source checkout, and start the watcher. `doctor` shows the state file that the sync process reads. + If the installed HTML changes but Orca does not reload, verify that you registered the runtime path under **Development**, not a copied Local folder installation. Turn to another sidebar tab and back to test remounting. A newer Orca release may require an integration update. ## The panel shows a setup message or never updates @@ -371,7 +381,7 @@ Drag the left boundary of the right sidebar to widen it, or scroll the graph hor ## Report a problem -Open a [GitHub issue](https://github.com/pty0220/orca-git-graph/issues/new/choose) with Orca version, OS, Python version, installation method, and reproduction steps. Include a redacted `doctor` result when useful. Do not attach `orca-data.json`, runtime authentication files, private generated HTML, or unredacted screenshots of repository history. +Open a [GitHub issue](https://github.com/pty0220/orca-git-graph/issues/new/choose) with Orca version, OS, Python version, installation method, and reproduction steps. Include a redacted `doctor` result when useful. Do not attach `orca-data.json`, `profile-state.db`, runtime authentication files, private generated HTML, or unredacted screenshots of repository history. --- @@ -412,7 +422,7 @@ No. Keep Git Graph Sync running and restart it after quitting Orca. No operating ## Which platforms and Orca versions are supported? -Orca 1.4.185 on macOS is the tested desktop target. Windows and Linux desktop integration is experimental. The plugin/state interfaces are version-dependent; a newer Orca release may require an update. SSH and cloud workspaces are not supported in this release. +Orca 1.4.185 on macOS is the tested desktop target. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Windows and Linux desktop integration is experimental. The plugin/state interfaces are version-dependent; a newer Orca release may require an update. SSH and cloud workspaces are not supported in this release. ## Where should an AI assistant start? diff --git a/docs/llms.txt b/docs/llms.txt index d4de74b..03149a8 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -4,7 +4,7 @@ Canonical repository: https://github.com/pty0220/orca-git-graph Documentation: https://pty0220.github.io/orca-git-graph/ -Initial release: 0.1.0. Tested desktop integration: Orca 1.4.185 on macOS. Linux/Windows desktop support is experimental. Remote workspaces are unsupported. +Initial release: 0.1.0. Tested desktop integration: Orca 1.4.185 on macOS. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Linux/Windows desktop support is experimental. Remote workspaces are unsupported. Install with `git clone https://github.com/pty0220/orca-git-graph.git`, then `python3 install.py` in the checkout. Register the printed runtime plugin folder under Orca Settings > Plugins > Development and review/enable it. Keep the separate Git Graph Sync terminal running. The generic Git URL plugin installer is not the live-update installation path. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 03704f3..0b04420 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -16,6 +16,8 @@ python3 ~/.local/share/orca-git-graph/run.py watch Run `doctor` and confirm the configured Orca profile. A switch normally takes the selection poll plus Git collection and Orca's Development reload. A large repository may take longer. +If you updated Orca after the installation, the configured state file can be out of date. Orca 1.4.214 and later store the workspace state in `profile-state.db` and stop updating `orca-data.json`. The sync process reads `profile-state.db` when it is next to the configured `orca-data.json`. To update `config.json` as well, stop Git Graph Sync, run `python3 install.py` again from the source checkout, and start the watcher. `doctor` shows the state file that the sync process reads. + If the installed HTML changes but Orca does not reload, verify that you registered the runtime path under **Development**, not a copied Local folder installation. Turn to another sidebar tab and back to test remounting. A newer Orca release may require an integration update. ## The panel shows a setup message or never updates @@ -50,4 +52,4 @@ Drag the left boundary of the right sidebar to widen it, or scroll the graph hor ## Report a problem -Open a [GitHub issue](https://github.com/pty0220/orca-git-graph/issues/new/choose) with Orca version, OS, Python version, installation method, and reproduction steps. Include a redacted `doctor` result when useful. Do not attach `orca-data.json`, runtime authentication files, private generated HTML, or unredacted screenshots of repository history. +Open a [GitHub issue](https://github.com/pty0220/orca-git-graph/issues/new/choose) with Orca version, OS, Python version, installation method, and reproduction steps. Include a redacted `doctor` result when useful. Do not attach `orca-data.json`, `profile-state.db`, runtime authentication files, private generated HTML, or unredacted screenshots of repository history. diff --git a/install.py b/install.py index 28a9945..3079cf4 100644 --- a/install.py +++ b/install.py @@ -30,7 +30,7 @@ def find_orca(): def main(argv=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--install-dir", type=Path, default=Path.home() / ".local/share/orca-git-graph") - parser.add_argument("--orca-state", help="Explicit path to the active profile's orca-data.json") + parser.add_argument("--orca-state", help="Explicit path to the active profile's profile-state.db or orca-data.json") parser.add_argument("--profile", help="Orca profile directory name, for example local-default") parser.add_argument("--no-start", action="store_true", help="Install files without opening a sync terminal") args = parser.parse_args(argv) diff --git a/llms.txt b/llms.txt index d4de74b..03149a8 100644 --- a/llms.txt +++ b/llms.txt @@ -4,7 +4,7 @@ Canonical repository: https://github.com/pty0220/orca-git-graph Documentation: https://pty0220.github.io/orca-git-graph/ -Initial release: 0.1.0. Tested desktop integration: Orca 1.4.185 on macOS. Linux/Windows desktop support is experimental. Remote workspaces are unsupported. +Initial release: 0.1.0. Tested desktop integration: Orca 1.4.185 on macOS. The sync process also reads the `profile-state.db` workspace state of Orca 1.4.214 and later (checked with 1.4.224). Linux/Windows desktop support is experimental. Remote workspaces are unsupported. Install with `git clone https://github.com/pty0220/orca-git-graph.git`, then `python3 install.py` in the checkout. Register the printed runtime plugin folder under Orca Settings > Plugins > Development and review/enable it. Keep the separate Git Graph Sync terminal running. The generic Git URL plugin installer is not the live-update installation path. diff --git a/orca_git_graph/app.py b/orca_git_graph/app.py index 3bce2f3..2afcf2d 100644 --- a/orca_git_graph/app.py +++ b/orca_git_graph/app.py @@ -10,7 +10,7 @@ from pathlib import Path from .git_data import GitError, collect, fingerprint -from .workspace import discover_state, read_selection +from .workspace import current_state_file, discover_state, read_selection PACKAGE = Path(__file__).resolve().parent ROOT = PACKAGE.parent @@ -119,7 +119,7 @@ def main(argv=None): from shutil import which print("Python: " + sys.version.split()[0]) print("Git: " + (which("git") or "NOT FOUND")) - print("Orca state: " + config["orca_state_path"]) + print("Orca state: " + str(current_state_file(config["orca_state_path"]))) print("Plugin folder: " + str(ROOT / "plugin")) selection = read_selection(config["orca_state_path"]) print("Selection: " + (selection.get("path") or selection.get("error", "unknown"))) diff --git a/orca_git_graph/workspace.py b/orca_git_graph/workspace.py index c13d4e5..43b3812 100644 --- a/orca_git_graph/workspace.py +++ b/orca_git_graph/workspace.py @@ -1,9 +1,18 @@ -"""Isolate the experimental Orca 1.4.185 state-file integration here.""" +"""Isolate the experimental Orca state integration here. + +Orca 1.4.185 keeps the state in orca-data.json. Orca 1.4.214 and later keep it in +profile-state.db, with one JSON document per former top-level key of orca-data.json. They +import orca-data.json once and never update it again, so that file becomes stale. +""" import json import os +import sqlite3 import sys from pathlib import Path +STATE_FILES = ("profile-state.db", "orca-data.json") +MAX_STATE_SIZE = 16 * 1024 * 1024 + class WorkspaceError(RuntimeError): pass @@ -20,18 +29,32 @@ def data_root(): return Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "orca" +def profile_state(directory): + return next((directory / name for name in STATE_FILES if (directory / name).is_file()), None) + + +def current_state_file(path): + path = Path(path) + database = path.with_name("profile-state.db") + return database if path.name == "orca-data.json" and database.is_file() else path + + def discover_state(explicit=None, profile=None): if explicit: path = Path(explicit).expanduser().resolve() if not path.is_file(): raise WorkspaceError("The supplied Orca state file does not exist.") - return path + return current_state_file(path) root = data_root() if profile: if Path(profile).name != profile or profile in (".", ".."): raise WorkspaceError("Profile must be a directory name, not a path.") - return discover_state(root / "profiles" / profile / "orca-data.json") - candidates = list((root / "profiles").glob("*/orca-data.json")) + state = profile_state(root / "profiles" / profile) + if not state: + raise WorkspaceError("The selected Orca profile has no state file.") + return state.resolve() + profiles = sorted(path for path in (root / "profiles").glob("*") if path.is_dir()) + candidates = [state for state in map(profile_state, profiles) if state] if (root / "orca-data.json").is_file(): candidates.append(root / "orca-data.json") if len(candidates) == 1: @@ -71,13 +94,34 @@ def resolve_workspace(state): return {"path": path, "key": key} +def load_state_database(path): + # mode=ro cannot change Orca's database, and a WAL reader does not block Orca's writes. + connection = sqlite3.connect(Path(path).resolve().as_uri() + "?mode=ro", uri=True, timeout=1) + try: + rows = connection.execute( + "SELECT domain, CASE WHEN length(payload) <= ? THEN payload END FROM profile_state_documents" + " WHERE domain IN ('workspaceSession', 'repos', 'folderWorkspaces')", (MAX_STATE_SIZE,)).fetchall() + finally: + connection.close() + if any(payload is None for _, payload in rows): + raise ValueError("State document is too large") + return {domain: json.loads(payload) for domain, payload in rows} + + +def load_state(state_path): + path = current_state_file(state_path) + if path.suffix == ".db": + return load_state_database(path) + with path.open(encoding="utf-8") as source: + # Bound malformed or unexpected files. Never print or copy the state document. + raw = source.read(MAX_STATE_SIZE + 1) + if len(raw) > MAX_STATE_SIZE: + raise ValueError("State document is too large") + return json.loads(raw) + + def read_selection(state_path): try: - with Path(state_path).open(encoding="utf-8") as source: - # Bound malformed or unexpected files. Never print or copy the state document. - raw = source.read(16 * 1024 * 1024 + 1) - if len(raw) > 16 * 1024 * 1024: - raise ValueError("State document is too large") - return resolve_workspace(json.loads(raw)) - except (OSError, ValueError, AttributeError, TypeError): + return resolve_workspace(load_state(state_path)) + except (OSError, ValueError, AttributeError, TypeError, sqlite3.Error): return {"error": "Cannot read Orca's current workspace. Check the state-file setting.", "key": None} diff --git a/tests/test_core.py b/tests/test_core.py index 55041f2..9b15e12 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,15 +1,31 @@ import copy import json import os +import sqlite3 import subprocess import tempfile import unittest +from contextlib import closing from pathlib import Path from unittest.mock import patch from orca_git_graph.app import empty_repo, render_html, watcher_lock from orca_git_graph.git_data import collect -from orca_git_graph.workspace import discover_state, resolve_workspace, WorkspaceError +from orca_git_graph.workspace import discover_state, read_selection, resolve_workspace, WorkspaceError + +READ_ERROR = {"error": "Cannot read Orca's current workspace. Check the state-file setting.", "key": None} + + +def write_state_database(path, documents): + path.parent.mkdir(parents=True, exist_ok=True) + with closing(sqlite3.connect(path)) as connection: + connection.execute("PRAGMA journal_mode=WAL") + connection.execute("CREATE TABLE profile_state_documents (domain TEXT PRIMARY KEY NOT NULL," + " payload TEXT NOT NULL, domain_version INTEGER NOT NULL, revision INTEGER NOT NULL," + " updated_at INTEGER NOT NULL, content_hash TEXT NOT NULL)") + connection.executemany("INSERT INTO profile_state_documents VALUES (?, ?, 1, 1, 0, '')", + [(domain, json.dumps(value)) for domain, value in documents.items()]) + connection.commit() class WorkspaceTests(unittest.TestCase): @@ -54,6 +70,96 @@ def test_multiple_profiles_require_explicit_choice(self): discover_state() self.assertEqual(discover_state(profile="work").parent.name, "work") + def test_profile_state_database_is_preferred_over_json(self): + with tempfile.TemporaryDirectory() as root: + profile = Path(root) / "profiles" / "local-default" + profile.mkdir(parents=True) + (profile / "orca-data.json").write_text("{}") + (profile / "profile-state.db").write_bytes(b"") + with patch.dict(os.environ, {"ORCA_USER_DATA_PATH": root}): + self.assertEqual(discover_state().name, "profile-state.db") + self.assertEqual(discover_state(profile="local-default").name, "profile-state.db") + + def test_stale_json_next_to_a_database_is_not_read(self): + stale = copy.deepcopy(self.state) + stale["workspaceSession"]["activeWorktreeId"] = "repo::" + str(Path(tempfile.gettempdir()) / "old worktree") + with tempfile.TemporaryDirectory() as root: + profile = Path(root) / "profiles" / "local-default" + write_state_database(profile / "profile-state.db", self.state) + (profile / "orca-data.json").write_text(json.dumps(stale)) + self.assertEqual(read_selection(profile / "orca-data.json")["path"], self.path) + self.assertEqual(discover_state(profile / "orca-data.json").name, "profile-state.db") + + def test_mixed_profile_formats_require_explicit_choice(self): + with tempfile.TemporaryDirectory() as root: + for name, state in (("personal", "orca-data.json"), ("work", "profile-state.db")): + path = Path(root) / "profiles" / name / state + path.parent.mkdir(parents=True) + path.write_bytes(b"") + with patch.dict(os.environ, {"ORCA_USER_DATA_PATH": root}): + with self.assertRaisesRegex(WorkspaceError, "Multiple Orca profiles found"): + discover_state() + self.assertEqual(discover_state(profile="personal").name, "orca-data.json") + with self.assertRaisesRegex(WorkspaceError, "has no state file"): + discover_state(profile="missing") + + def test_profile_state_database_worktree(self): + with tempfile.TemporaryDirectory() as root: + database = Path(root) / "Application Support" / "profile-state.db" + write_state_database(database, self.state) + self.assertEqual(read_selection(database), {"path": self.path, "key": "repo::" + self.path}) + + def test_profile_state_database_folder(self): + self.state["workspaceSession"]["activeWorktreeId"] = "folder:notes" + self.state["folderWorkspaces"] = [{"id": "notes", "path": self.path}] + with tempfile.TemporaryDirectory() as root: + database = Path(root) / "profile-state.db" + write_state_database(database, self.state) + self.assertEqual(read_selection(database), {"path": self.path, "key": "folder:notes"}) + + def test_profile_state_database_reads_committed_state_during_a_write(self): + other = str(Path(tempfile.gettempdir()) / "another worktree") + with tempfile.TemporaryDirectory() as root: + database = Path(root) / "profile-state.db" + write_state_database(database, self.state) + with closing(sqlite3.connect(database)) as orca: + orca.execute("BEGIN IMMEDIATE") + orca.execute("UPDATE profile_state_documents SET payload = ? WHERE domain = 'workspaceSession'", + (json.dumps({"activeWorktreeId": "repo::" + other}),)) + self.assertEqual(read_selection(database)["path"], self.path) + orca.commit() + self.assertEqual(read_selection(database)["path"], other) + + def test_profile_state_database_is_not_changed(self): + with tempfile.TemporaryDirectory() as root: + database = Path(root) / "profile-state.db" + write_state_database(database, self.state) + before = database.read_bytes() + read_selection(database) + self.assertEqual(database.read_bytes(), before) + + def test_unreadable_profile_state_database(self): + with tempfile.TemporaryDirectory() as root: + other_schema = Path(root) / "other-schema" / "profile-state.db" + other_schema.parent.mkdir() + with closing(sqlite3.connect(other_schema)) as connection: + connection.execute("CREATE TABLE unrelated (id INTEGER)") + connection.commit() + not_sqlite = Path(root) / "not-sqlite" / "profile-state.db" + not_sqlite.parent.mkdir() + not_sqlite.write_text("This is not a database.") + missing = Path(root) / "missing" / "profile-state.db" + for database in (other_schema, not_sqlite, missing): + with self.subTest(database.parent.name): + self.assertEqual(read_selection(database), READ_ERROR) + + def test_oversized_profile_state_document_is_rejected(self): + with tempfile.TemporaryDirectory() as root: + database = Path(root) / "profile-state.db" + write_state_database(database, self.state) + with patch("orca_git_graph.workspace.MAX_STATE_SIZE", 10): + self.assertEqual(read_selection(database), READ_ERROR) + class GitTests(unittest.TestCase): def setUp(self): @@ -145,5 +251,25 @@ def test_clean_install_and_build_outside_checkout(self): self.assertNotEqual(duplicate.returncode, 0) self.assertIn("already running", duplicate.stderr) + def test_build_follows_a_profile_state_database(self): + import install + with tempfile.TemporaryDirectory() as root: + repository = Path(root) / "example project" + repository.mkdir() + for args in (["init", "-b", "main"], + ["-c", "user.name=Example Developer", "-c", "user.email=developer@example.com", + "commit", "--allow-empty", "-m", "Initial commit"]): + subprocess.run(["git", "-c", "core.hooksPath=" + str(repository / "no-hooks"), + "-C", str(repository), *args], check=True, capture_output=True) + database = Path(root) / "profile" / "profile-state.db" + write_state_database(database, {"repos": [{"id": "repo", "kind": "git"}], + "workspaceSession": {"activeWorktreeId": "repo::" + str(repository)}}) + dest = Path(root) / "runtime" + self.assertEqual(install.main(["--no-start", "--install-dir", str(dest), "--orca-state", str(database)]), 0) + result = subprocess.run([os.sys.executable, str(dest / "run.py"), "build"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("Updated Git Graph: example project (1 commits)", result.stdout) + self.assertIn("Initial commit", (dest / "plugin/panel.html").read_text(encoding="utf-8")) + if __name__ == "__main__": unittest.main()