gitstow 0.7.0__tar.gz → 0.7.2__tar.gz
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.
- {gitstow-0.7.0 → gitstow-0.7.2}/.gitignore +2 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/AGENTS.md +2 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/CHANGELOG.md +23 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/CLAUDE.md +9 -3
- {gitstow-0.7.0 → gitstow-0.7.2}/PKG-INFO +10 -3
- {gitstow-0.7.0 → gitstow-0.7.2}/README.md +9 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/user/commands.md +37 -4
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/user/concepts.md +13 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/user/configuration.md +33 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/user/getting-started.md +41 -9
- {gitstow-0.7.0 → gitstow-0.7.2}/pyproject.toml +1 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/site/index.html +2 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/__init__.py +1 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/add.py +4 -3
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/config_cmd.py +10 -11
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/doctor.py +28 -10
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/exec_cmd.py +3 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/fetch.py +21 -4
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/helpers.py +107 -9
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/list_cmd.py +3 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/migrate.py +3 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/onboard.py +12 -3
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/pull.py +21 -4
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/search.py +3 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/serve.py +3 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/stats.py +3 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/status.py +3 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/workspace_cmd.py +13 -8
- gitstow-0.7.2/src/gitstow/core/config.py +251 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/paths.py +2 -1
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/mcp/server.py +28 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/skill/SKILL.md +25 -4
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/collection.py +10 -2
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/pages.py +58 -15
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/repos.py +37 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/server.py +6 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/add_repo.html +10 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/dashboard.html +12 -0
- gitstow-0.7.2/src/gitstow/web/templates/partials/no_workspaces.html +17 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/settings.html +28 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/workspaces.html +6 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/conftest.py +13 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_cli.py +350 -2
- gitstow-0.7.2/tests/test_config.py +567 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_mcp.py +37 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_onboard.py +126 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_serve.py +471 -16
- gitstow-0.7.0/src/gitstow/core/config.py +0 -144
- gitstow-0.7.0/tests/test_config.py +0 -245
- {gitstow-0.7.0 → gitstow-0.7.2}/.claude/settings.json +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/.github/workflows/ci.yml +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/.github/workflows/pages.yml +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/.github/workflows/publish.yml +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/BACKLOG.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/CODE_OF_CONDUCT.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/CONTRIBUTING.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/LICENSE +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/SECURITY.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/demo.gif +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/demo.tape +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/Gitstow Landing.dc.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/README.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/assets/demo.gif +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/assets/fonts/bricolage-grotesque-latin-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/assets/fonts/jetbrains-mono-latin-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/browser-window.jsx +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/01-hero.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/02-quick-start.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/03-dashboard-showcase.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/04-ai-integration.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/05-feature-grid.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/06-comparison.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/design_handoff_gitstow_landing/screenshots/07-cta-footer.png +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/audit-2026-07-06.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/implementation-plan.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/plans/2026-07-06-wave-1-correctness-safety.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/plans/2026-07-06-wave-2-status-model.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/plans/2026-07-06-wave-3-structure-polish.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/plans/2026-07-12-ui-wave-a-make-it-real.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/plans/2026-07-12-ui-wave-b-polish.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/plans/2026-07-16-move-repo-between-workspaces.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/roadmap.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/building/ui-audit-2026-07-12.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/superpowers/plans/2026-07-19-diff-viewer.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/superpowers/plans/2026-08-11-tailscale-ui.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/superpowers/specs/2026-07-19-diff-viewer-design.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/docs/superpowers/specs/2026-08-11-tailscale-ui-design.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/scripts/release.sh +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/site/assets/demo.gif +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/site/assets/fonts/bricolage-grotesque-latin-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/site/assets/fonts/jetbrains-mono-latin-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/site/style.css +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/__main__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/__init__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/diff_cmd.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/export_cmd.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/main.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/manage.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/open_cmd.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/remove.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/setup_ai.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/shell.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/skill_cmd.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/cli/update.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/__init__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/collection_io.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/diff.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/discovery.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/git.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/locking.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/operations.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/parallel.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/repo.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/status_model.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/tailscale.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/core/url_parser.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/mcp/__init__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/__init__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/__init__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/dashboard.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/system.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/routes/workspaces.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/app.css +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/dashboard.js +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/fonts/bricolage-grotesque-latin-ext-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/fonts/bricolage-grotesque-latin-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/fonts/fonts.css +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/fonts/jetbrains-mono-latin-ext-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/fonts/jetbrains-mono-latin-var.woff2 +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/vendor/VENDORED.md +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/static/vendor/htmx.min.js +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/_repo_drawer.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/base.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/partials/changes_section.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/partials/dashboard_rows.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/partials/diff_view.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/partials/fetch_summary.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/partials/pull_summary.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/src/gitstow/web/templates/partials/repo_row.html +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/__init__.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_collection_io.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_diff.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_git.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_locking.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_move.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_operations.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_repo.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_setup_ai.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_status_model.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_tailscale.py +0 -0
- {gitstow-0.7.0 → gitstow-0.7.2}/tests/test_url_parser.py +0 -0
|
@@ -73,7 +73,7 @@ src/gitstow/
|
|
|
73
73
|
|
|
74
74
|
## Key Files
|
|
75
75
|
|
|
76
|
-
- `core/config.py` — Workspace + Settings dataclasses. `get_workspaces()`
|
|
76
|
+
- `core/config.py` — Workspace + Settings dataclasses. `get_workspaces()` returns exactly what is configured and **never synthesizes a workspace**; zero workspaces is a valid state and `get_default_workspace()` returns `None` for it. The legacy `root_path` → `oss` migration shim lives only in `load_config()`, where it is persisted to disk. Both one-time migrations (`root_path` → `oss`, and adopting the pre-0.7.2 implicit `oss` workspace at `~/opensource`) run there too, gated by `config_version` — the provenance marker every save writes, which is what keeps an explicit `workspaces: []` from being re-migrated into a workspace the user just removed. `NO_WORKSPACES_HINT` is the one empty-state message every surface (CLI, MCP, web) reuses.
|
|
77
77
|
- `core/repo.py` — Repo with workspace field, RepoStore with nested YAML format, legacy auto-migration.
|
|
78
78
|
- `core/discovery.py` — `discover_repos(root, layout)` supports structured and flat layouts.
|
|
79
79
|
- `core/url_parser.py` — URL parsing (the hardest part). Test changes here thoroughly.
|
|
@@ -144,7 +144,7 @@ ruff check src/
|
|
|
144
144
|
- asyncio with semaphore for parallel git ops
|
|
145
145
|
- `git status --porcelain=v2 --branch` for single-call status (vs gita's 4-5 calls)
|
|
146
146
|
- Repo.global_key (`workspace:key`) for unique identification across workspaces
|
|
147
|
-
- Legacy format auto-migration (flat repos.yaml → nested, root_path → workspaces)
|
|
147
|
+
- Legacy format auto-migration (flat repos.yaml → nested, root_path → workspaces) — migration happens in `load_config()` and is written to disk, never synthesized on read
|
|
148
148
|
|
|
149
149
|
## AI Integration
|
|
150
150
|
|
|
@@ -4,6 +4,29 @@ All notable changes to gitstow will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.7.2] - 2026-09-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **A fresh install no longer invents an `oss` workspace at `~/opensource`** — with nothing configured, gitstow used to conjure a workspace that lived only in memory. It could not be removed (`workspace remove oss` refused: "Cannot remove the only workspace"), it could not be replaced (`workspace add ... --label oss` refused: "already exists"), removing it from the dashboard silently did nothing because the page re-conjured it on the next render, and `gitstow add owner/repo` quietly cloned into `~/opensource` under a workspace label that appeared nowhere in your config. Zero workspaces is now a real, visible state: `get_workspaces()` returns exactly what you configured, every surface shows the same empty state with the same one-line fix (`gitstow workspace add <path> --label <name>`, or `gitstow onboard`), and you may remove your last workspace — the CLI tells you what to do next, and the dashboard's library, add-repo, and workspaces pages each show an empty-state card instead of a dead form or a phantom row. The legacy `root_path` → `oss` migration is unchanged and still runs, but only in `load_config()` where it is written to disk, so what you see is what your config holds. `~/opensource` survives solely as the path `gitstow onboard` suggests for your first workspace.
|
|
12
|
+
- **The empty state now reaches every surface that needed it** — `gitstow onboard` works on a config with zero workspaces (it used to refuse because the *file* existed, and `--force` silently reset `parallel_limit`, `clone_timeout` and `ui_tailscale`); `gitstow pull owner/repo` and `gitstow fetch owner/repo` stop with the hint instead of "No repos to pull"; the MCP tools `list_repos`, `pull_repos`, `fetch_repos`, `search_repos`, `collection_stats` and `repo_status` return the hint instead of a success-shaped empty result; the dashboard's Settings page shows the empty-state card in place of the collection-import form, and a POST to it answers with that page rather than raw JSON; the library page no longer renders its "Pull all" / "Fetch all" bar and repo filters with no workspace to act on, and those endpoints answer with the empty-state card instead of a zero-item summary or a wall of "workspace not found"; `gitstow doctor` prints workspace labels in its orphan reports again (Rich was parsing `[label]` as a style tag and swallowing it) and skips the SSH probe when nothing is configured; and the terminal hint prints each command on its own line so it never wraps mid-command.
|
|
13
|
+
- **`--json` runs now get the empty state as JSON, not silence** — with zero workspaces configured, `add`, `pull`, `fetch`, `list`, `status`, `exec`, `search`, `stats` and `migrate` exited 1 with an empty stdout and the hint as prose on stderr, so anything parsing stdout (scripts, the Claude Code skill) saw a crash rather than "nothing configured yet". With `--json` they now write `{"success": false, "error": "No workspaces configured…"}` to stdout, in the same shape as every other JSON failure, and still exit 1. Without `--json` nothing changes.
|
|
14
|
+
- **One orphaned repo no longer aborts a bulk pull or fetch** — `gitstow pull good orphan`, where `orphan` is tracked under a workspace that has since left your config, exited 1 while building the target list and never touched `good`. It now warns and skips that one repo — the same contract as the existing "not tracked. Skipping." path — so the valid repos named alongside it still run. In `--json` mode the warning goes to stderr and stdout stays the result payload. Naming only the orphan still ends in "No repos to pull."
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Upgrading from 0.7.1 or earlier adopts your implicit `oss` workspace** — installs where `gitstow add` had cloned into `~/opensource` without ever writing a config now get that workspace written to `config.yaml` once, silently, on the next command, so those repos stay visible (if `~/opensource` no longer exists, nothing is invented and `gitstow doctor` reports the leftover records). The adoption only ever looks at configs that predate this version — every config gitstow writes from now on carries a `config_version` marker, so once you remove `oss` yourself it stays removed.
|
|
19
|
+
|
|
20
|
+
## [0.7.1] - 2026-08-11
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Tailscale toggle on the dashboard Settings page** — `ui_tailscale` was the last config field you could only set from the CLI. Because it applies when the server binds its sockets, the page never pretends the change is live: it compares what you saved against what the running server actually bound and tells you to restart `gitstow ui`. On a machine without the Tailscale CLI the row is disabled with the reason shown, and saving there leaves an existing `ui_tailscale: true` (e.g. a config synced from another machine) untouched rather than silently clearing it.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **`gitstow ui --tailscale` now prints the tailnet IP** instead of the MagicDNS name — the IP works from any peer, while the name depends on the other device's DNS. The MagicDNS name (and short name) still work if you type them.
|
|
29
|
+
|
|
7
30
|
## [0.7.0] - 2026-08-11
|
|
8
31
|
|
|
9
32
|
### Added
|
|
@@ -39,6 +39,7 @@ src/gitstow/
|
|
|
39
39
|
│ ├── status_model.py # Shared repo-state classifier — local composition vs remote relationship
|
|
40
40
|
│ ├── operations.py # Shared filter + bulk-runner layer (pull/fetch/MCP)
|
|
41
41
|
│ ├── locking.py # Cross-process file lock guarding repos.yaml writes
|
|
42
|
+
│ ├── tailscale.py # Tailscale CLI detection (tailnet IP + MagicDNS) for ui --tailscale
|
|
42
43
|
│ └── __init__.py
|
|
43
44
|
├── web/ # FastAPI browser dashboard (gitstow ui)
|
|
44
45
|
│ ├── server.py # FastAPI app, uvicorn runner, app.state.server stash
|
|
@@ -57,7 +58,7 @@ src/gitstow/
|
|
|
57
58
|
|
|
58
59
|
## Key Files
|
|
59
60
|
|
|
60
|
-
- `core/config.py` — Workspace + Settings dataclasses. `get_workspaces()`
|
|
61
|
+
- `core/config.py` — Workspace + Settings dataclasses. `get_workspaces()` returns exactly what is configured and **never synthesizes a workspace**; zero workspaces is a valid state and `get_default_workspace()` returns `None` for it. The legacy `root_path` → `oss` migration shim lives only in `load_config()`, where it is persisted to disk. Both one-time migrations (`root_path` → `oss`, and adopting the pre-0.7.2 implicit `oss` workspace at `~/opensource`) run there too, gated by `config_version` — the provenance marker every save writes, which is what keeps an explicit `workspaces: []` from being re-migrated into a workspace the user just removed. `NO_WORKSPACES_HINT` is the one empty-state message every surface (CLI, MCP, web) reuses.
|
|
61
62
|
- `core/repo.py` — Repo with workspace field, RepoStore with nested YAML format, legacy auto-migration.
|
|
62
63
|
- `core/discovery.py` — `discover_repos(root, layout)` supports structured and flat layouts.
|
|
63
64
|
- `core/url_parser.py` — URL parsing (the hardest part). Test changes here thoroughly.
|
|
@@ -66,13 +67,14 @@ src/gitstow/
|
|
|
66
67
|
- `core/status_model.py` — `RepoState` — the single source of truth for local (modified/staged/untracked) vs remote (in-sync/ahead/behind/diverged) classification, consumed by CLI, web, and JSON.
|
|
67
68
|
- `core/operations.py` — Shared `filter_repo_pairs()` + bulk-runner used by `pull`, `fetch`, and the MCP server so surfaces can't drift.
|
|
68
69
|
- `core/locking.py` — `file_lock()` cross-process advisory lock guarding `repos.yaml` against concurrent CLI/web writes.
|
|
70
|
+
- `core/tailscale.py` — `detect_tailscale()` / `tailscale_available()`. Shells out to the `tailscale` CLI (3s timeouts, None on any failure). Powers `gitstow ui --tailscale`: `web/server.py` binds the tailnet IP as a second socket and widens the Host/Origin guard by exact match to the machine's own tailnet identity only — never `0.0.0.0`. Every failure mode degrades to localhost-only with a warning.
|
|
69
71
|
- `cli/helpers.py` — Shared workspace resolution used by all CLI commands.
|
|
70
72
|
- `cli/workspace_cmd.py` — workspace list/add/remove/scan subcommands.
|
|
71
73
|
- `cli/main.py` — Typer app, global `-w/--workspace` option, command registration.
|
|
72
74
|
|
|
73
75
|
## Data Files
|
|
74
76
|
|
|
75
|
-
- `~/.gitstow/config.yaml` — Settings (workspaces list, default host, SSH pref).
|
|
77
|
+
- `~/.gitstow/config.yaml` — Settings (workspaces list, default host, SSH pref, `ui_tailscale` dashboard-over-tailnet toggle).
|
|
76
78
|
- `~/.gitstow/repos.yaml` — Repo metadata nested by workspace label. Central location.
|
|
77
79
|
- `~/.gitstow/repos.lock` — Cross-process advisory lock file (`core/locking.py`) held during `repos.yaml` writes. Not user-facing data; safe to ignore/delete if orphaned.
|
|
78
80
|
|
|
@@ -116,6 +118,8 @@ ruff check src/
|
|
|
116
118
|
|
|
117
119
|
- **Worktree gotcha:** tests run from a git worktree silently exercise the main checkout (the editable install points at the primary clone). From a worktree, run `PYTHONPATH=<worktree>/src .venv/bin/python -m pytest -q`.
|
|
118
120
|
- **Releasing publishes to PyPI.** `scripts/release.sh X.Y.Z` tags and pushes; the tag triggers a public PyPI publish. Require the user's explicit in-session instruction to release — plan approval or a "let's go" on implementation does not cover it.
|
|
121
|
+
- **Lint with CI's ruff, not the venv's.** CI installs fresh (`ruff>=0.16` floor); the local venv keeps whatever was installed last. Before trusting "ruff clean" ahead of a push or release, compare `ruff --version` to the pyproject floor and `pip install -U ruff` if older (this blocked the v0.7.0 publish).
|
|
122
|
+
- **Pressure-test before merging a feature branch.** Run `/code-review <PR#> high` and fix confirmed findings before merge — plan-scoped task reviews and even a clean whole-branch review do not count as the pressure test (on v0.7.0 it found 10 real issues after both were green).
|
|
119
123
|
|
|
120
124
|
## Patterns
|
|
121
125
|
|
|
@@ -128,13 +132,15 @@ ruff check src/
|
|
|
128
132
|
- asyncio with semaphore for parallel git ops
|
|
129
133
|
- `git status --porcelain=v2 --branch` for single-call status (vs gita's 4-5 calls)
|
|
130
134
|
- Repo.global_key (`workspace:key`) for unique identification across workspaces
|
|
131
|
-
- Legacy format auto-migration (flat repos.yaml → nested, root_path → workspaces)
|
|
135
|
+
- Legacy format auto-migration (flat repos.yaml → nested, root_path → workspaces) — migration happens in `load_config()` and is written to disk, never synthesized on read
|
|
136
|
+
- `app.state.tailscale_serving` — `run()` records what it actually bound; the settings page compares saved config against that instead of guessing, so restart notices state facts (and direction), never implied live changes
|
|
132
137
|
|
|
133
138
|
## Product & Implementation Standards
|
|
134
139
|
|
|
135
140
|
- Prefer proper long-term solutions over shortcut patches. If a feature is incomplete in one surface, build the feature into that surface instead of papering over it with wording, partial conditionals, or one-off display logic.
|
|
136
141
|
- Do not recommend quick fixes, temporary patches, or narrow workarounds unless the user explicitly asks for a shortcut.
|
|
137
142
|
- Keep CLI, web dashboard, JSON output, docs, and tests semantically aligned when changing user-facing status behavior.
|
|
143
|
+
- **Feature scope includes every surface that states the behavior.** A user-facing feature plan must carry, as explicit in-scope tasks: README, CHANGELOG, `docs/user/commands.md`, `src/gitstow/skill/SKILL.md`, and the landing page (`site/`) with core-feature billing. Enumerate the surfaces by grepping the repo for the old behavior's claim (e.g. `grep -rn "127.0.0.1" --include="*.md" --include="*.html"`) during planning — every hit becomes a plan task. Reviews inherit the plan's surface list, so a surface missed at planning stays stale through every review.
|
|
138
144
|
- For repo state presentation, avoid using "dirty" as a broad user-facing bucket for every local change. Present it as local/uncommitted changes with the composition visible: modified, staged, and untracked counts.
|
|
139
145
|
- Keep local working-tree state separate from remote relationship state. For example: local changes, clean, ahead, behind, diverged, frozen, missing.
|
|
140
146
|
- When improving the web dashboard, implement the actual missing dashboard feature and shared classification/model behavior instead of copying a CLI-only assumption into the template.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gitstow
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A git repository library manager — clone, organize, and maintain collections of repos you learn from
|
|
5
5
|
Project-URL: Homepage, https://gitstow.com
|
|
6
6
|
Project-URL: Repository, https://github.com/rishmadaan/gitstow
|
|
@@ -71,8 +71,10 @@ pipx install gitstow # recommended — full install: CLI + browser dashboard
|
|
|
71
71
|
# sudo apt install pipx (Debian/Ubuntu)
|
|
72
72
|
# brew install pipx (macOS)
|
|
73
73
|
|
|
74
|
-
# First-run setup
|
|
74
|
+
# First-run setup — creates your first workspace, sets your default host
|
|
75
75
|
gitstow onboard
|
|
76
|
+
# ...or skip the wizard and add a workspace directly:
|
|
77
|
+
# gitstow workspace add ~/oss --label oss
|
|
76
78
|
|
|
77
79
|
# Add repos (GitHub shorthand or full URLs)
|
|
78
80
|
gitstow add anthropic/claude-code
|
|
@@ -206,6 +208,7 @@ Private by default: binds `127.0.0.1` (arbitrary git execution must not be LAN-r
|
|
|
206
208
|
gitstow config set ui_tailscale true # every gitstow ui from now on
|
|
207
209
|
gitstow ui --tailscale # or per-run
|
|
208
210
|
```
|
|
211
|
+
You can also flip it on the dashboard's own **Settings** page — it takes effect the next time you start `gitstow ui`.
|
|
209
212
|
If Tailscale isn't running, `gitstow ui` warns and serves localhost only.
|
|
210
213
|
|
|
211
214
|
### JSON Output
|
|
@@ -308,7 +311,9 @@ active:
|
|
|
308
311
|
2. **Folder-as-state** — The directory structure is the primary source of truth. `repos.yaml` supplements with metadata (frozen, tags, timestamps).
|
|
309
312
|
3. **Error isolation** — One bad repo never stops operations on others. Failures are collected and reported in a summary.
|
|
310
313
|
4. **Parallel execution** — Bulk operations use `asyncio` with a semaphore (default 6 concurrent) to prevent SSH connection storms.
|
|
311
|
-
5. **
|
|
314
|
+
5. **Explicit state** — gitstow never invents a workspace. A fresh install has none, and
|
|
315
|
+
commands that need one say exactly how to create it. Zero workspaces is a valid state:
|
|
316
|
+
you can remove your last workspace and gitstow will simply say so.
|
|
312
317
|
|
|
313
318
|
### A note on folder structure
|
|
314
319
|
|
|
@@ -322,6 +327,8 @@ Flat workspaces skip the owner directory entirely — repos are just `workspace/
|
|
|
322
327
|
|
|
323
328
|
**SSH clone fails with "Permission denied (publickey)"** — Your SSH key isn't configured for the git host. Either add your key (`ssh-add`) or use HTTPS: `gitstow config set prefer_ssh false`.
|
|
324
329
|
|
|
330
|
+
**`No workspaces configured`** — Expected on a fresh install, and after removing your last workspace. gitstow never invents one. Run `gitstow workspace add <path> --label <name>` or `gitstow onboard`.
|
|
331
|
+
|
|
325
332
|
**Workspace directory doesn't exist** — Run `gitstow doctor` to check workspace health. Create missing directories or update the path with `gitstow workspace add`.
|
|
326
333
|
|
|
327
334
|
**`gitstow doctor`** — Run this first when something isn't working. It checks git installation, config files, and workspace integrity.
|
|
@@ -26,8 +26,10 @@ pipx install gitstow # recommended — full install: CLI + browser dashboard
|
|
|
26
26
|
# sudo apt install pipx (Debian/Ubuntu)
|
|
27
27
|
# brew install pipx (macOS)
|
|
28
28
|
|
|
29
|
-
# First-run setup
|
|
29
|
+
# First-run setup — creates your first workspace, sets your default host
|
|
30
30
|
gitstow onboard
|
|
31
|
+
# ...or skip the wizard and add a workspace directly:
|
|
32
|
+
# gitstow workspace add ~/oss --label oss
|
|
31
33
|
|
|
32
34
|
# Add repos (GitHub shorthand or full URLs)
|
|
33
35
|
gitstow add anthropic/claude-code
|
|
@@ -161,6 +163,7 @@ Private by default: binds `127.0.0.1` (arbitrary git execution must not be LAN-r
|
|
|
161
163
|
gitstow config set ui_tailscale true # every gitstow ui from now on
|
|
162
164
|
gitstow ui --tailscale # or per-run
|
|
163
165
|
```
|
|
166
|
+
You can also flip it on the dashboard's own **Settings** page — it takes effect the next time you start `gitstow ui`.
|
|
164
167
|
If Tailscale isn't running, `gitstow ui` warns and serves localhost only.
|
|
165
168
|
|
|
166
169
|
### JSON Output
|
|
@@ -263,7 +266,9 @@ active:
|
|
|
263
266
|
2. **Folder-as-state** — The directory structure is the primary source of truth. `repos.yaml` supplements with metadata (frozen, tags, timestamps).
|
|
264
267
|
3. **Error isolation** — One bad repo never stops operations on others. Failures are collected and reported in a summary.
|
|
265
268
|
4. **Parallel execution** — Bulk operations use `asyncio` with a semaphore (default 6 concurrent) to prevent SSH connection storms.
|
|
266
|
-
5. **
|
|
269
|
+
5. **Explicit state** — gitstow never invents a workspace. A fresh install has none, and
|
|
270
|
+
commands that need one say exactly how to create it. Zero workspaces is a valid state:
|
|
271
|
+
you can remove your last workspace and gitstow will simply say so.
|
|
267
272
|
|
|
268
273
|
### A note on folder structure
|
|
269
274
|
|
|
@@ -277,6 +282,8 @@ Flat workspaces skip the owner directory entirely — repos are just `workspace/
|
|
|
277
282
|
|
|
278
283
|
**SSH clone fails with "Permission denied (publickey)"** — Your SSH key isn't configured for the git host. Either add your key (`ssh-add`) or use HTTPS: `gitstow config set prefer_ssh false`.
|
|
279
284
|
|
|
285
|
+
**`No workspaces configured`** — Expected on a fresh install, and after removing your last workspace. gitstow never invents one. Run `gitstow workspace add <path> --label <name>` or `gitstow onboard`.
|
|
286
|
+
|
|
280
287
|
**Workspace directory doesn't exist** — Run `gitstow doctor` to check workspace health. Create missing directories or update the path with `gitstow workspace add`.
|
|
281
288
|
|
|
282
289
|
**`gitstow doctor`** — Run this first when something isn't working. It checks git installation, config files, and workspace integrity.
|
|
@@ -80,7 +80,7 @@ gitstow add anthropic/claude-code --update
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
**Behavior:**
|
|
83
|
-
- Repos are added to the
|
|
83
|
+
- Repos are added to the first configured workspace, or the workspace specified with `-w`. With none configured, `add` exits 1 with `No workspaces configured…` and clones nothing
|
|
84
84
|
- If the repo is already tracked: skips (or pulls with `--update`)
|
|
85
85
|
- If the path exists on disk but isn't tracked: registers it automatically
|
|
86
86
|
- If the path exists on disk with a **different** remote than the one requested: errors with a remote-mismatch message instead of silently registering — move the directory or pick another workspace
|
|
@@ -548,7 +548,7 @@ gitstow ui --tailscale # also serve on this machine's Tailscale address
|
|
|
548
548
|
- Workspace CRUD + Scan; Collection export/import under Settings
|
|
549
549
|
- Click **Shutdown** in the footer (or Ctrl+C) to stop
|
|
550
550
|
|
|
551
|
-
**Tailscale access:** with `--tailscale` (or `gitstow config set ui_tailscale true` to make it the default), the dashboard additionally binds this machine's own Tailscale address, so you can open it from any other device on your tailnet — useful for a VPS or headless box. `--no-tailscale` turns it off for a single run. If the Tailscale CLI isn't installed or the daemon isn't reachable, gitstow prints a warning and serves localhost-only instead of failing.
|
|
551
|
+
**Tailscale access:** with `--tailscale` (or `gitstow config set ui_tailscale true` to make it the default), the dashboard additionally binds this machine's own Tailscale address, so you can open it from any other device on your tailnet — useful for a VPS or headless box. `--no-tailscale` turns it off for a single run. The same setting has a checkbox on the dashboard's Settings page — saving it there applies from the next `gitstow ui` onward, and the page says so. If the Tailscale CLI isn't installed or the daemon isn't reachable, gitstow prints a warning and serves localhost-only instead of failing.
|
|
552
552
|
|
|
553
553
|
**Security:** binds `127.0.0.1` by default, and — only when Tailscale access is enabled — this machine's own Tailscale address as well. It never binds `0.0.0.0`, and there is no `--host` flag. The server runs git operations in arbitrary workspace directories, so it must not be LAN-reachable; with Tailscale access on, your trust boundary is your tailnet. Requests arriving with any other Host/Origin are rejected.
|
|
554
554
|
|
|
@@ -628,7 +628,7 @@ Valid keys: `default_host`, `prefer_ssh`, `parallel_limit`, `clone_timeout` (clo
|
|
|
628
628
|
Move one workspace's repos to a new directory and update that workspace's `path` in config.
|
|
629
629
|
|
|
630
630
|
```bash
|
|
631
|
-
gitstow config migrate-root ~/new-location # Moves the
|
|
631
|
+
gitstow config migrate-root ~/new-location # Moves the first configured workspace
|
|
632
632
|
gitstow -w active config migrate-root ~/new-location # Moves a specific workspace
|
|
633
633
|
gitstow config migrate-root ~/new-location --copy # Keep originals
|
|
634
634
|
gitstow config migrate-root ~/new-location --yes # Skip confirmation
|
|
@@ -685,6 +685,18 @@ gitstow workspace list
|
|
|
685
685
|
gitstow workspace list --quiet # One label per line (for scripting/completions)
|
|
686
686
|
```
|
|
687
687
|
|
|
688
|
+
With no workspaces configured — a fresh install, or after removing your last one —
|
|
689
|
+
the command exits **0** (this is a valid state, not an error) and prints:
|
|
690
|
+
|
|
691
|
+
```
|
|
692
|
+
No workspaces configured. Add one with:
|
|
693
|
+
gitstow workspace add <path> --label <name>
|
|
694
|
+
or run:
|
|
695
|
+
gitstow onboard
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
`--quiet` prints nothing in that case, so scripts see an empty list.
|
|
699
|
+
|
|
688
700
|
### `gitstow workspace add`
|
|
689
701
|
|
|
690
702
|
Add a new workspace.
|
|
@@ -729,7 +741,28 @@ gitstow workspace remove <label> [flags]
|
|
|
729
741
|
|------|-------------|
|
|
730
742
|
| `--keep-repos/--untrack-repos` | Keep tracked repos in the store (default) or untrack them. |
|
|
731
743
|
|
|
732
|
-
|
|
744
|
+
**Removing your last workspace is allowed.** Zero workspaces is a valid state —
|
|
745
|
+
gitstow does not invent one to fill the gap. The command reports the removal and
|
|
746
|
+
then tells you how to get back:
|
|
747
|
+
|
|
748
|
+
```
|
|
749
|
+
✓ Workspace oss removed
|
|
750
|
+
No workspaces configured. Add one with:
|
|
751
|
+
gitstow workspace add <path> --label <name>
|
|
752
|
+
or run:
|
|
753
|
+
gitstow onboard
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
Until you add one, commands that sweep workspaces (`add`, `pull`, `fetch`, `list`, `status`, `exec`, `search`, `stats`, `migrate`,
|
|
757
|
+
`collection import`, `config migrate-root` and `shell pick`)
|
|
758
|
+
exit **1** with that same message on stderr. Run with `--json`, the same failure
|
|
759
|
+
is emitted as `{"success": false, "error": "..."}` on stdout instead (still exit
|
|
760
|
+
1), so a parsed run never sees an empty stdout — `collection import`,
|
|
761
|
+
`config migrate-root` and `shell pick` have no `--json` option and always use the
|
|
762
|
+
prose form. `workspace list`, `config show` and
|
|
763
|
+
`doctor` still run, and each points at the same fix. Commands that name a single
|
|
764
|
+
repo (`diff`, `open`, `remove`, `repo …`) report that the repo isn't tracked, which
|
|
765
|
+
is what actually happened.
|
|
733
766
|
|
|
734
767
|
### `gitstow workspace scan`
|
|
735
768
|
|
|
@@ -53,11 +53,22 @@ gitstow pull --workspace oss
|
|
|
53
53
|
gitstow repo info anthropic/claude-code -w work
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
> **Migration from single root:** If you have a pre-workspace config with `root_path`, gitstow auto-migrates it to a single workspace labeled `oss` on first use. No action needed.
|
|
56
|
+
> **Migration from single root:** If you have a pre-workspace config with `root_path`, gitstow auto-migrates it to a single workspace labeled `oss` on first use, and rewrites `config.yaml` to match. No action needed.
|
|
57
|
+
>
|
|
58
|
+
> This is the only case where gitstow creates a workspace for you, and it persists it. A config with no `root_path` and no workspaces stays empty.
|
|
57
59
|
|
|
58
60
|
## Folder Structure
|
|
59
61
|
|
|
60
|
-
When you add a repo, gitstow places it in
|
|
62
|
+
When you add a repo, gitstow places it in your first configured workspace (or the one you
|
|
63
|
+
specify with `-w`). A fresh install has none — gitstow does not invent one, so create a
|
|
64
|
+
workspace before adding repos. Every command that sweeps workspaces says so:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Error: No workspaces configured. Add one with:
|
|
68
|
+
gitstow workspace add <path> --label <name>
|
|
69
|
+
or run:
|
|
70
|
+
gitstow onboard
|
|
71
|
+
```
|
|
61
72
|
|
|
62
73
|
### Structured layout (default)
|
|
63
74
|
|
|
@@ -36,10 +36,13 @@ gitstow onboard
|
|
|
36
36
|
|
|
37
37
|
| Key | Default | Description |
|
|
38
38
|
|-----|---------|-------------|
|
|
39
|
-
| `workspaces` |
|
|
39
|
+
| `workspaces` | *(none)* | List of workspace directories. Each has a path, label, layout, and optional auto-tags. A fresh install has none — add one with `gitstow workspace add <path> --label <name>` or `gitstow onboard`. An empty list is valid; gitstow never invents a workspace. |
|
|
40
40
|
| `default_host` | `github.com` | Assumed host when you type shorthand like `owner/repo`. |
|
|
41
41
|
| `prefer_ssh` | `false` | If `true`, clones via SSH (`git@host:owner/repo.git`) instead of HTTPS. |
|
|
42
42
|
| `parallel_limit` | `6` | Maximum concurrent git operations during `pull` and `status`. |
|
|
43
|
+
| `clone_timeout` | `300` | Seconds before a clone is abandoned. Raise it for very large repos. |
|
|
44
|
+
| `ui_tailscale` | `false` | If `true`, `gitstow ui` also serves the dashboard on this machine's Tailscale address. See [Web Dashboard over Tailscale](#web-dashboard-over-tailscale). |
|
|
45
|
+
| `config_version` | *(written by gitstow)* | Internal — records which version of gitstow last wrote this file, so one-time upgrade migrations know they have already run. Do not edit. |
|
|
43
46
|
|
|
44
47
|
## File Locations
|
|
45
48
|
|
|
@@ -61,6 +64,9 @@ workspaces:
|
|
|
61
64
|
default_host: github.com
|
|
62
65
|
prefer_ssh: false
|
|
63
66
|
parallel_limit: 6
|
|
67
|
+
clone_timeout: 300
|
|
68
|
+
ui_tailscale: false
|
|
69
|
+
config_version: 2
|
|
64
70
|
```
|
|
65
71
|
|
|
66
72
|
Each workspace entry has:
|
|
@@ -163,6 +169,32 @@ If you see SSH connection errors during bulk pulls, lower it:
|
|
|
163
169
|
gitstow config set parallel_limit 3
|
|
164
170
|
```
|
|
165
171
|
|
|
172
|
+
## Web Dashboard over Tailscale
|
|
173
|
+
|
|
174
|
+
By default the dashboard (`gitstow ui`) binds `127.0.0.1` only. On a VPS or
|
|
175
|
+
home server you can also serve it on the machine's own Tailscale address, so
|
|
176
|
+
any device on your tailnet can open it:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
gitstow config set ui_tailscale true # every gitstow ui from now on
|
|
180
|
+
gitstow ui --tailscale # or per-run (--no-tailscale to skip once)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The same setting can be flipped from the dashboard's own **Settings** page,
|
|
184
|
+
and `gitstow onboard` offers it during setup when Tailscale is installed.
|
|
185
|
+
|
|
186
|
+
Things worth knowing:
|
|
187
|
+
|
|
188
|
+
- The setting applies when the server starts (that's when the socket binds).
|
|
189
|
+
Toggling it in the dashboard saves the config and tells you to restart
|
|
190
|
+
`gitstow ui` — it never pretends to change the running server.
|
|
191
|
+
- It never binds `0.0.0.0`. The trust boundary is your tailnet: traffic is
|
|
192
|
+
WireGuard-encrypted, and anyone on your tailnet can reach the dashboard.
|
|
193
|
+
- The startup message prints the tailnet URL as `http://<tailscale-ip>:7853`.
|
|
194
|
+
The MagicDNS name works too if you type it.
|
|
195
|
+
- If Tailscale isn't installed or the daemon isn't running, `gitstow ui`
|
|
196
|
+
warns and serves localhost only — it never fails to start over this.
|
|
197
|
+
|
|
166
198
|
## MCP Server (Optional)
|
|
167
199
|
|
|
168
200
|
> **Most users don't need this.** The Claude Code skill is the recommended AI integration — it has zero context overhead and auto-updates on version bumps. The MCP server is only for AI tools that don't support Claude Code skills (Claude Desktop, Cursor, Windsurf).
|
|
@@ -41,7 +41,34 @@ gitstow --version
|
|
|
41
41
|
|
|
42
42
|
> **Command not found?** On some systems, pip installs to a directory not on your PATH. Try `python3 -m gitstow --version` instead, or use pipx which handles PATH automatically.
|
|
43
43
|
|
|
44
|
-
## 2.
|
|
44
|
+
## 2. Create a Workspace
|
|
45
|
+
|
|
46
|
+
A fresh install has **no workspaces**. A workspace is the directory gitstow clones
|
|
47
|
+
into, and gitstow never invents one for you — so this is the first step.
|
|
48
|
+
|
|
49
|
+
The interactive wizard walks you through it (and suggests `~/opensource` as a path):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gitstow onboard
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or do it in one line:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gitstow workspace add ~/oss --label oss --layout structured
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Until a workspace exists, commands that need one (`add`, `pull`, `list`, `status`, …)
|
|
62
|
+
stop with:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Error: No workspaces configured. Add one with:
|
|
66
|
+
gitstow workspace add <path> --label <name>
|
|
67
|
+
or run:
|
|
68
|
+
gitstow onboard
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 3. Add Your First Repo
|
|
45
72
|
|
|
46
73
|
```bash
|
|
47
74
|
gitstow add anthropic/claude-code
|
|
@@ -49,12 +76,13 @@ gitstow add anthropic/claude-code
|
|
|
49
76
|
|
|
50
77
|
That's it. gitstow:
|
|
51
78
|
1. Recognizes `anthropic/claude-code` as GitHub shorthand
|
|
52
|
-
2. Clones to `~/oss/anthropic/claude-code/` (your
|
|
79
|
+
2. Clones to `~/oss/anthropic/claude-code/` (your first configured workspace)
|
|
53
80
|
3. Registers it in your collection
|
|
54
81
|
|
|
55
|
-
> **
|
|
82
|
+
> **Which workspace?** With more than one configured, `gitstow add` uses the first
|
|
83
|
+
> one listed in `config.yaml`. Target another with `-w`, e.g. `gitstow -w active add owner/repo`.
|
|
56
84
|
|
|
57
|
-
##
|
|
85
|
+
## 4. Add More Repos
|
|
58
86
|
|
|
59
87
|
```bash
|
|
60
88
|
# GitHub shorthand (most common)
|
|
@@ -70,7 +98,7 @@ gitstow add git@bitbucket.org:owner/repo.git
|
|
|
70
98
|
gitstow add torvalds/linux --shallow
|
|
71
99
|
```
|
|
72
100
|
|
|
73
|
-
##
|
|
101
|
+
## 5. See Your Collection
|
|
74
102
|
|
|
75
103
|
```bash
|
|
76
104
|
gitstow list
|
|
@@ -90,7 +118,7 @@ Output:
|
|
|
90
118
|
linux just now
|
|
91
119
|
```
|
|
92
120
|
|
|
93
|
-
##
|
|
121
|
+
## 6. Update Everything
|
|
94
122
|
|
|
95
123
|
```bash
|
|
96
124
|
gitstow pull
|
|
@@ -110,7 +138,7 @@ Output:
|
|
|
110
138
|
|
|
111
139
|
Every repo is pulled in parallel (up to 6 at once). If one repo fails, the others still update — you get a summary at the end.
|
|
112
140
|
|
|
113
|
-
##
|
|
141
|
+
## 7. Check Status
|
|
114
142
|
|
|
115
143
|
```bash
|
|
116
144
|
gitstow status
|
|
@@ -132,7 +160,7 @@ For a guided first-run experience:
|
|
|
132
160
|
gitstow onboard
|
|
133
161
|
```
|
|
134
162
|
|
|
135
|
-
This walks you through
|
|
163
|
+
This walks you through creating your first workspace (suggesting `~/opensource` as its path), choosing a default git host and SSH vs HTTPS preference, then scans for existing repos to register.
|
|
136
164
|
|
|
137
165
|
## Multiple Workspaces
|
|
138
166
|
|
|
@@ -170,7 +198,7 @@ This is also done automatically during `gitstow onboard` and auto-updates when y
|
|
|
170
198
|
gitstow ui
|
|
171
199
|
```
|
|
172
200
|
|
|
173
|
-
Opens a local dark-themed dashboard at `http://127.0.0.1:7853` — a tab you leave open. Shows dirty state across every repo at a glance, pulls single repos or all-of-them in parallel, adds/removes repos, edits tags. Auto-refreshes every 30 seconds. Click **Shutdown** in the footer (or Ctrl+C) when done. See [commands.md — `gitstow ui`](commands.md#gitstow-ui) for full details.
|
|
201
|
+
Opens a local dark-themed dashboard at `http://127.0.0.1:7853` — a tab you leave open. Shows dirty state across every repo at a glance, pulls single repos or all-of-them in parallel, adds/removes repos, edits tags. Auto-refreshes every 30 seconds. Click **Shutdown** in the footer (or Ctrl+C) when done. Running gitstow on a VPS? `gitstow ui --tailscale` (or the Tailscale toggle in the dashboard's Settings) also serves it on your tailnet so you can open it from your other devices — see [configuration.md](configuration.md#web-dashboard-over-tailscale). See [commands.md — `gitstow ui`](commands.md#gitstow-ui) for full details.
|
|
174
202
|
|
|
175
203
|
## Keep gitstow Up to Date
|
|
176
204
|
|
|
@@ -195,6 +223,10 @@ gitstow update # upgrade to the latest PyPI version
|
|
|
195
223
|
- For SSH: check your SSH key is added (`ssh -T git@github.com`)
|
|
196
224
|
- Set SSH as default: `gitstow config set prefer_ssh true`
|
|
197
225
|
|
|
226
|
+
**"No workspaces configured"**
|
|
227
|
+
- Expected on a fresh install, and after removing your last workspace — zero workspaces is a valid state, not a broken one.
|
|
228
|
+
- Fix it with `gitstow workspace add <path> --label <name>` or the `gitstow onboard` wizard. The same message appears in the web dashboard, with a link to the workspaces page.
|
|
229
|
+
|
|
198
230
|
**"gitstow doctor" for diagnostics**
|
|
199
231
|
```bash
|
|
200
232
|
gitstow doctor
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitstow"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
description = "A git repository library manager — clone, organize, and maintain collections of repos you learn from"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
</div>
|
|
74
74
|
<p class="pipx-note" id="tab-note">Already have pipx? <code class="code-chip">pipx install gitstow</code> alone does it.</p>
|
|
75
75
|
<div class="term-lines">
|
|
76
|
-
<div><span class="prompt">$</span> <span class="cmd">gitstow onboard</span> <span class="comment">#
|
|
76
|
+
<div><span class="prompt">$</span> <span class="cmd">gitstow onboard</span> <span class="comment"># first-run wizard: creates your workspace, installs the Claude skill</span></div>
|
|
77
77
|
<div><span class="prompt">$</span> <span class="cmd">gitstow add anthropic/claude-code</span> <span class="comment"># shorthand or full URLs</span></div>
|
|
78
78
|
<div><span class="prompt">$</span> <span class="cmd">gitstow pull</span> <span class="comment"># update everything, in parallel</span></div>
|
|
79
79
|
<div><span class="prompt">$</span> <span class="cmd">gitstow ui</span> <span class="comment"># open the dashboard ↓</span></div>
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
<div class="center-head">
|
|
98
98
|
<div class="eyebrow"><span class="bar"></span>gitstow ui<span class="bar"></span></div>
|
|
99
99
|
<h2 class="section">The tab you leave open.</h2>
|
|
100
|
-
<p>A local-first browser dashboard, and the primary way to live with your library. Dirty state at a glance, one-click pulls, tags, freezes, and per-file diffs. It auto-refreshes every 30 seconds and never phones home: <code class="code-chip">127.0.0.1</code> by default — or flip on Tailscale access and open it from any device on your tailnet. Never the public internet.</p>
|
|
100
|
+
<p>A local-first browser dashboard, and the primary way to live with your library. Dirty state at a glance, one-click pulls, tags, freezes, and per-file diffs. It auto-refreshes every 30 seconds and never phones home: <code class="code-chip">127.0.0.1</code> by default — or flip on Tailscale access — right from its Settings page — and open it from any device on your tailnet. Never the public internet.</p>
|
|
101
101
|
</div>
|
|
102
102
|
<div class="bw-scroll">
|
|
103
103
|
<div class="bw">
|
|
@@ -67,7 +67,8 @@ def add(
|
|
|
67
67
|
"""[bold green]Add[/bold green] repos — clone into organized structure.
|
|
68
68
|
|
|
69
69
|
Accepts full URLs, SSH URLs, or shorthand (owner/repo assumes GitHub).
|
|
70
|
-
|
|
70
|
+
Clones into the first configured workspace unless -w is specified;
|
|
71
|
+
errors with a hint if none is configured.
|
|
71
72
|
|
|
72
73
|
\b
|
|
73
74
|
Examples:
|
|
@@ -79,8 +80,8 @@ def add(
|
|
|
79
80
|
settings = load_config()
|
|
80
81
|
store = RepoStore()
|
|
81
82
|
ws_label = ctx.obj.get("workspace") if ctx.obj else None
|
|
82
|
-
ws_list = resolve_workspaces(settings, ws_label)
|
|
83
|
-
ws = ws_list[0] #
|
|
83
|
+
ws_list = resolve_workspaces(settings, ws_label, output_json=output_json)
|
|
84
|
+
ws = ws_list[0] # The -w workspace, or the first configured one
|
|
84
85
|
root = ws.get_path()
|
|
85
86
|
tags = list(tag or []) + list(ws.auto_tags)
|
|
86
87
|
|
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
import typer
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
|
|
11
|
+
from gitstow.cli.helpers import print_no_workspaces_hint, resolve_workspaces
|
|
11
12
|
from gitstow.core.config import load_config, save_config
|
|
12
13
|
from gitstow.core.paths import CONFIG_FILE, REPOS_FILE
|
|
13
14
|
from gitstow.core.repo import RepoStore
|
|
@@ -57,6 +58,8 @@ def config_show(
|
|
|
57
58
|
# Show workspaces
|
|
58
59
|
workspaces = settings.get_workspaces()
|
|
59
60
|
console.print(f" [bold]Workspaces ({len(workspaces)}):[/bold]")
|
|
61
|
+
if not workspaces:
|
|
62
|
+
print_no_workspaces_hint(console, indent=" ")
|
|
60
63
|
ws_counts = store.all_workspaces()
|
|
61
64
|
for ws in workspaces:
|
|
62
65
|
count = ws_counts.get(ws.label, 0)
|
|
@@ -92,6 +95,9 @@ def config_set(
|
|
|
92
95
|
"""
|
|
93
96
|
settings = load_config()
|
|
94
97
|
|
|
98
|
+
# config_version is deliberately absent: it is provenance gitstow writes, not
|
|
99
|
+
# a setting. Editing it would either re-trigger or permanently disable the
|
|
100
|
+
# one-time legacy migrations.
|
|
95
101
|
valid_keys = {"default_host", "prefer_ssh", "parallel_limit", "clone_timeout", "ui_tailscale"}
|
|
96
102
|
if key not in valid_keys:
|
|
97
103
|
err_console.print(
|
|
@@ -138,7 +144,7 @@ def config_migrate_root(
|
|
|
138
144
|
|
|
139
145
|
\b
|
|
140
146
|
Examples:
|
|
141
|
-
gitstow config migrate-root ~/new-location #
|
|
147
|
+
gitstow config migrate-root ~/new-location # first configured workspace
|
|
142
148
|
gitstow -w active config migrate-root ~/new-location # specific workspace
|
|
143
149
|
"""
|
|
144
150
|
import shutil
|
|
@@ -149,16 +155,9 @@ def config_migrate_root(
|
|
|
149
155
|
settings = load_config()
|
|
150
156
|
store = RepoStore()
|
|
151
157
|
|
|
152
|
-
#
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
ws_label = (ctx.obj or {}).get("workspace") or settings.get_default_workspace().label
|
|
157
|
-
ws = settings.get_workspace(ws_label)
|
|
158
|
-
if ws is None:
|
|
159
|
-
labels = ", ".join(w.label for w in settings.get_workspaces())
|
|
160
|
-
err_console.print(f"[red]Error:[/red] Unknown workspace [bold]{ws_label}[/bold]. Available: {labels}")
|
|
161
|
-
raise typer.Exit(code=1)
|
|
158
|
+
# No configured workspace → the shared hint + exit 1; unknown -w label → the
|
|
159
|
+
# shared "Unknown workspace" error; otherwise the named or first workspace.
|
|
160
|
+
ws = resolve_workspaces(settings, (ctx.obj or {}).get("workspace"))[0]
|
|
162
161
|
|
|
163
162
|
old_root = ws.get_path()
|
|
164
163
|
new_root_path = Path(new_root).expanduser().resolve()
|