ctrlrelay 0.17.0__tar.gz → 0.17.1__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.
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/CHANGELOG.md +35 -1
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/PKG-INFO +1 -1
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/cli.md +1 -1
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/operations.md +21 -5
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/pyproject.toml +1 -1
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/cli.py +65 -2
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/worktree.py +117 -20
- ctrlrelay-0.17.1/src/ctrlrelay/install.py +323 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/setup.py +34 -8
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/launchd/bridge.plist.template +9 -2
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/launchd/poller.plist.template +9 -2
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/systemd/bridge.service.template +7 -1
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/systemd/poller.service.template +7 -1
- ctrlrelay-0.17.1/tests/test_install.py +870 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_setup.py +115 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_worktree.py +216 -13
- ctrlrelay-0.17.0/src/ctrlrelay/install.py +0 -204
- ctrlrelay-0.17.0/tests/test_install.py +0 -365
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/dependabot.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/workflows/build.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/workflows/cla.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/workflows/pages.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/workflows/publish.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.github/workflows/test.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/.gitignore +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/CODE_OF_CONDUCT.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/CONTRIBUTING.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/LICENSE +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/README.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/SECURITY.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/config/orchestrator.yaml.example +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/Gemfile +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/_config.yml +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/adding-a-machine.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/architecture.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/bridge.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/configuration.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/development.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/feedback-loop.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/getting-started.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/index.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/personalization.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/superpowers/plans/2026-10-30-status-page.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/docs/superpowers/specs/2026-10-30-status-page-design.md +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/__main__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/factory.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/handler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/mattermost_handler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/node_tag.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/protocol.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/server.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/bridge/telegram_handler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/agent_env.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/archived.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/audit.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/binaries.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/checkpoint.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/claims.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/code_review.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/config.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/dispatch_health.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/dispatcher.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/events.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/github.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/github_budget.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/heartbeat.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/job_claims.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/network.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/node_check.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/obs.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/poller.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/pr_verifier.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/pr_watcher.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/publish.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/question_expiry.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/redact.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/scheduler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/state.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/core/usage.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/dashboard/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/dashboard/client.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/gh_protocol.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/personalization/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/personalization/manager.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/personalization/paths.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/pipelines/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/pipelines/base.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/pipelines/dev.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/pipelines/post_merge.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/pipelines/secops.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/pipelines/task.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/global-CLAUDE.md.snippet +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/launchd/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/systemd/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/templates/systemd/web.service.template +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/transports/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/transports/base.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/transports/file_mock.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/transports/socket_client.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/fleet.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/health.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/links.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/server.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/static/index.html +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/status.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/transcript.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/src/ctrlrelay/web/waiting.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/__init__.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/_poller_fakes.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/_publish_fakes.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/conftest.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/fixtures/gh_search_issues.json +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/fixtures/stream_json_rate_limit.jsonl +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/fixtures/transcript_sample.jsonl +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_agent_no_keys.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_agent_sandbox.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_archived.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_audit.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_binary_resolution.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_bridge_protocol.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_bridge_server.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_bridge_start_command.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_chat_machines.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_checkpoint.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_claim_reconciliation.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_cli_ci_wait.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_cli_dev.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_cli_repos.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_cli_secops.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_cli_start.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_cli_version.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_code_review.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_config.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_dashboard_client.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_dev_integration.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_dev_pipeline.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_dispatch_health.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_dispatch_outcome_hook.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_dispatcher.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_docs_site.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_event_emission.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_events.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_fleet.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_gh_protocol.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_github.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_github_budget.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_issue_outcome_announcement.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_job_claims.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_mattermost_handler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_network.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_node_check.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_node_claims.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_notify_transport.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_obs.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_personalization.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_pipeline_base.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_poller.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_poller_search.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_post_merge.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_pr_verifier.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_pr_watcher.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_pre_dispatch_retry.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_publish_failure_retry.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_question_backoff.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_question_expiry.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_redact.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_relay_alert_off_lock.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_resume_prompt_contract.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_scheduler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_secops_integration.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_secops_pipeline.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_state.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_task_pipeline.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_telegram_handler.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_transport.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_transport_resolution.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_unreviewed_pr_announcement.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_usage.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_web_health.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_web_links.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_web_server.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_web_status.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_web_transcript.py +0 -0
- {ctrlrelay-0.17.0 → ctrlrelay-0.17.1}/tests/test_web_waiting.py +0 -0
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.17.1] - 2026-11-01
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Installed bridge and poller units carry the token variable the configured
|
|
15
|
+
transport names, not a fixed Telegram one. `ctrlrelay install
|
|
16
|
+
launchd|systemd` and `ctrlrelay setup --install-daemons` read
|
|
17
|
+
`transport.<type>.bot_token_env` and render `Environment=<that
|
|
18
|
+
name>=<value>` (systemd) or the matching plist entry (launchd), with the
|
|
19
|
+
value taken from that variable in the installing shell. A Mattermost
|
|
20
|
+
install previously got a unit naming `CTRLRELAY_TELEGRAM_TOKEN`, which it
|
|
21
|
+
never sets, so the bridge could not log in once systemd or launchd started
|
|
22
|
+
it — the server here only worked because the line had been added by hand.
|
|
23
|
+
The unresolved-variable hint names the same variable, and the install
|
|
24
|
+
commands take `--config` to say which `orchestrator.yaml` to read
|
|
25
|
+
(closes #267)
|
|
26
|
+
- Updating a repository's clone no longer deletes local branches GitHub does
|
|
27
|
+
not have. The fetch paired `--prune` with `refs/heads/*:refs/heads/*`, which
|
|
28
|
+
tells git to delete every local branch missing from origin: a branch made in
|
|
29
|
+
the clone went with `- [deleted] (none) -> held`. The worktree stayed,
|
|
30
|
+
but its branch ref was gone, so a blocked session whose branch was never
|
|
31
|
+
pushed lost the branch the next time any run touched that repo, and its
|
|
32
|
+
commits could later be garbage-collected. The fetch is now two: the
|
|
33
|
+
`dependabot/*` branches, which never carry local work and are the refs that
|
|
34
|
+
actually pile up, are still pruned; every other branch is fetched without
|
|
35
|
+
`--prune`. The cost is that a branch GitHub deleted now lingers in the
|
|
36
|
+
clone, since a prune cannot tell it from one GitHub never had — the
|
|
37
|
+
branch-reuse path already recognises a local-only ref and decides whether
|
|
38
|
+
its commits are merged. Because a renamed default branch is no longer
|
|
39
|
+
pruned either, each update also asks GitHub which branch is the default
|
|
40
|
+
and points the clone at it when it changed and the branch here is at origin's
|
|
41
|
+
commit (#243).
|
|
42
|
+
|
|
10
43
|
## [0.17.0] - 2026-11-01
|
|
11
44
|
|
|
12
45
|
Polling costs one GitHub search instead of one call per repo. **No config
|
|
@@ -2076,7 +2109,8 @@ pipeline).
|
|
|
2076
2109
|
per-phase implementation plans (Phase 0 through Phase 4).
|
|
2077
2110
|
- `docs/Claude_Code_Project_Guide.md` — project development guide.
|
|
2078
2111
|
|
|
2079
|
-
[Unreleased]: https://github.com/AInvirion/ctrlrelay/compare/v0.17.
|
|
2112
|
+
[Unreleased]: https://github.com/AInvirion/ctrlrelay/compare/v0.17.1...HEAD
|
|
2113
|
+
[0.17.1]: https://github.com/AInvirion/ctrlrelay/compare/v0.17.0...v0.17.1
|
|
2080
2114
|
[0.17.0]: https://github.com/AInvirion/ctrlrelay/compare/v0.16.4...v0.17.0
|
|
2081
2115
|
[0.16.4]: https://github.com/AInvirion/ctrlrelay/compare/v0.16.3...v0.16.4
|
|
2082
2116
|
[0.16.3]: https://github.com/AInvirion/ctrlrelay/compare/v0.16.2...v0.16.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ctrlrelay
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.1
|
|
4
4
|
Summary: Local-first orchestrator for headless coding agents across multiple GitHub repos
|
|
5
5
|
Project-URL: Homepage, https://github.com/AInvirion/ctrlrelay
|
|
6
6
|
Project-URL: Documentation, https://ainvirion.github.io/ctrlrelay/
|
|
@@ -68,7 +68,7 @@ Key flags:
|
|
|
68
68
|
| `--telegram-chat-id ID` | none | Required for `--transport=telegram`. |
|
|
69
69
|
| `--personalization-repo OWNER/REPO` | none | Adds a personalization block. |
|
|
70
70
|
| `--no-personalization` | off | Skip the personalization prompt entirely. |
|
|
71
|
-
| `--install-daemons` | (prompted) | Render and write launchd/systemd unit files. Reads `$CTRLRELAY_TELEGRAM_TOKEN` so the rendered
|
|
71
|
+
| `--install-daemons` | (prompted) | Render and write launchd/systemd unit files. Reads the token variable the chosen transport names (`$CTRLRELAY_TELEGRAM_TOKEN`, `$CTRLRELAY_MATTERMOST_TOKEN`) so the rendered unit isn't a placeholder. |
|
|
72
72
|
| `--skip-archived/--include-archived` | skip | gh repo list filter. |
|
|
73
73
|
| `--skip-forks/--include-forks` | skip | gh repo list filter. |
|
|
74
74
|
| `--yes` / `-y` | off | Accept every default; never prompt. |
|
|
@@ -34,12 +34,19 @@ and restart on failure — the templates below already do this.
|
|
|
34
34
|
|
|
35
35
|
Recommended: let `ctrlrelay install launchd` render the plists for you.
|
|
36
36
|
It substitutes `${USER}`, `${HOME}`, the absolute `ctrlrelay` path,
|
|
37
|
-
your working directory, and (when exported)
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
your working directory, and (when exported) your chat token into the
|
|
38
|
+
in-package templates and writes them to `~/Library/LaunchAgents/`.
|
|
39
|
+
|
|
40
|
+
The token goes into the plist under the variable your transport names
|
|
41
|
+
in `transport.<type>.bot_token_env` — `CTRLRELAY_TELEGRAM_TOKEN` for
|
|
42
|
+
Telegram, `CTRLRELAY_MATTERMOST_TOKEN` for Mattermost, or whatever you
|
|
43
|
+
renamed that setting to. The command reads your `orchestrator.yaml` to
|
|
44
|
+
find out which; pass `--config` if it is somewhere the usual search
|
|
45
|
+
does not look. Export that variable before running, or the plist keeps
|
|
46
|
+
a placeholder and the command says which variable to export.
|
|
40
47
|
|
|
41
48
|
```bash
|
|
42
|
-
export
|
|
49
|
+
export CTRLRELAY_MATTERMOST_TOKEN=your-bot-token # or _TELEGRAM_, per your config
|
|
43
50
|
ctrlrelay install launchd \
|
|
44
51
|
--workdir ~/Projects/ctrlrelay \
|
|
45
52
|
--label-prefix com.yourname \
|
|
@@ -158,8 +165,13 @@ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.ctrlrelay-po
|
|
|
158
165
|
Recommended: `ctrlrelay install systemd` renders the user unit files
|
|
159
166
|
for you (no root needed) and writes them to `~/.config/systemd/user/`.
|
|
160
167
|
|
|
168
|
+
As on macOS, the bridge and poller units carry the token under the
|
|
169
|
+
variable `transport.<type>.bot_token_env` names, read from your
|
|
170
|
+
`orchestrator.yaml` (`--config` to point at a different one). Export
|
|
171
|
+
that variable before running.
|
|
172
|
+
|
|
161
173
|
```bash
|
|
162
|
-
export
|
|
174
|
+
export CTRLRELAY_MATTERMOST_TOKEN=your-bot-token # or _TELEGRAM_, per your config
|
|
163
175
|
ctrlrelay install systemd \
|
|
164
176
|
--workdir ~/Projects/ctrlrelay \
|
|
165
177
|
--poller-interval 300
|
|
@@ -190,6 +202,8 @@ After=network-online.target
|
|
|
190
202
|
[Service]
|
|
191
203
|
Type=simple
|
|
192
204
|
WorkingDirectory=%h/Projects/ctrlrelay
|
|
205
|
+
# The variable your transport names in bot_token_env — use
|
|
206
|
+
# CTRLRELAY_MATTERMOST_TOKEN on a Mattermost config.
|
|
193
207
|
Environment=CTRLRELAY_TELEGRAM_TOKEN=your-bot-token-here
|
|
194
208
|
ExecStart=%h/.local/bin/ctrlrelay bridge start --foreground
|
|
195
209
|
Restart=always
|
|
@@ -211,6 +225,8 @@ After=network-online.target ctrlrelay-bridge.service
|
|
|
211
225
|
[Service]
|
|
212
226
|
Type=simple
|
|
213
227
|
WorkingDirectory=%h/Projects/ctrlrelay
|
|
228
|
+
# The variable your transport names in bot_token_env — use
|
|
229
|
+
# CTRLRELAY_MATTERMOST_TOKEN on a Mattermost config.
|
|
214
230
|
Environment=CTRLRELAY_TELEGRAM_TOKEN=your-bot-token-here
|
|
215
231
|
ExecStart=%h/.local/bin/ctrlrelay poller start --foreground --interval 300
|
|
216
232
|
Restart=always
|
|
@@ -5052,6 +5052,12 @@ def setup(
|
|
|
5052
5052
|
console.print(" daemon unit files:")
|
|
5053
5053
|
for p in result.daemon_units:
|
|
5054
5054
|
console.print(f" - {p}")
|
|
5055
|
+
if result.daemon_unresolved:
|
|
5056
|
+
console.print(
|
|
5057
|
+
" [yellow]warning:[/yellow] the units still carry "
|
|
5058
|
+
f"{', '.join('${'+v+'}' for v in result.daemon_unresolved)}; "
|
|
5059
|
+
"export it and re-run with --force, or edit the files by hand."
|
|
5060
|
+
)
|
|
5055
5061
|
console.print(
|
|
5056
5062
|
"\n[bold]Next:[/bold] bootstrap the daemons via:\n"
|
|
5057
5063
|
" launchctl bootstrap gui/$(id -u) <plist>"
|
|
@@ -5463,13 +5469,50 @@ def _print_install_summary(
|
|
|
5463
5469
|
f" [yellow]warning:[/yellow] unresolved template variables: "
|
|
5464
5470
|
f"{', '.join('${'+v+'}' for v in unit.unresolved)}"
|
|
5465
5471
|
)
|
|
5466
|
-
|
|
5472
|
+
# Name the variable this install actually needs. It is
|
|
5473
|
+
# whatever `transport.<type>.bot_token_env` says, so hinting
|
|
5474
|
+
# a fixed CTRLRELAY_TELEGRAM_TOKEN sent a Mattermost
|
|
5475
|
+
# operator to export a variable nothing reads (#267).
|
|
5476
|
+
if unit.token_env_var in unit.unresolved:
|
|
5467
5477
|
console.print(
|
|
5468
|
-
" [dim]hint: export
|
|
5478
|
+
f" [dim]hint: export {unit.token_env_var} before "
|
|
5469
5479
|
"re-running, or edit the file by hand.[/dim]"
|
|
5470
5480
|
)
|
|
5471
5481
|
|
|
5472
5482
|
|
|
5483
|
+
def _install_token_env_var(config_path: str | None) -> str:
|
|
5484
|
+
"""Which env var the rendered units should carry the chat token in.
|
|
5485
|
+
|
|
5486
|
+
Reads the active transport's ``bot_token_env`` so a Mattermost box
|
|
5487
|
+
gets its own variable (#267). Exits non-zero on a config that was
|
|
5488
|
+
asked for and cannot be loaded: rendering the Telegram default on
|
|
5489
|
+
the strength of a path the operator got wrong would write a unit for
|
|
5490
|
+
a transport they are not running. Finding no config at all is a
|
|
5491
|
+
different case — nothing names a transport, so the historical
|
|
5492
|
+
default stands and the summary flags it as unresolved.
|
|
5493
|
+
"""
|
|
5494
|
+
from ctrlrelay.install import DEFAULT_TOKEN_ENV_VAR, token_env_var_from_config
|
|
5495
|
+
|
|
5496
|
+
if config_path is None:
|
|
5497
|
+
try:
|
|
5498
|
+
path = resolve_config_path(None)
|
|
5499
|
+
except ConfigError:
|
|
5500
|
+
console.print(
|
|
5501
|
+
f"[yellow]No orchestrator.yaml found, so nothing names the chat "
|
|
5502
|
+
f"transport: the units carry ${DEFAULT_TOKEN_ENV_VAR}. Pass "
|
|
5503
|
+
"--config to use your transport's token variable.[/yellow]"
|
|
5504
|
+
)
|
|
5505
|
+
return DEFAULT_TOKEN_ENV_VAR
|
|
5506
|
+
else:
|
|
5507
|
+
path = resolve_config_path(config_path)
|
|
5508
|
+
try:
|
|
5509
|
+
config = load_config(path)
|
|
5510
|
+
except ConfigError as e:
|
|
5511
|
+
console.print(f"[red]Error loading config:[/red] {e}")
|
|
5512
|
+
raise typer.Exit(1)
|
|
5513
|
+
return token_env_var_from_config(config)
|
|
5514
|
+
|
|
5515
|
+
|
|
5473
5516
|
@install_app.command("launchd")
|
|
5474
5517
|
def install_launchd(
|
|
5475
5518
|
workdir: Path = typer.Option(
|
|
@@ -5499,9 +5542,18 @@ def install_launchd(
|
|
|
5499
5542
|
"--dry-run",
|
|
5500
5543
|
help="Print what would be written without touching the filesystem.",
|
|
5501
5544
|
),
|
|
5545
|
+
config_path: str | None = typer.Option(
|
|
5546
|
+
None,
|
|
5547
|
+
"--config",
|
|
5548
|
+
"-c",
|
|
5549
|
+
help="Path to orchestrator.yaml (default: auto-discover; see $CTRLRELAY_CONFIG).",
|
|
5550
|
+
),
|
|
5502
5551
|
) -> None:
|
|
5503
5552
|
"""Render and install bridge + poller plists for macOS launchd.
|
|
5504
5553
|
|
|
5554
|
+
The plists carry the chat token in the variable the configured
|
|
5555
|
+
transport names, so export that variable before running.
|
|
5556
|
+
|
|
5505
5557
|
After running, load the agents with:
|
|
5506
5558
|
launchctl bootstrap gui/$(id -u) <plist-path>
|
|
5507
5559
|
"""
|
|
@@ -5512,6 +5564,7 @@ def install_launchd(
|
|
|
5512
5564
|
label_prefix=label_prefix,
|
|
5513
5565
|
poller_interval=poller_interval,
|
|
5514
5566
|
target_dir=target_dir,
|
|
5567
|
+
token_env_var=_install_token_env_var(config_path),
|
|
5515
5568
|
)
|
|
5516
5569
|
written: list[Path] = []
|
|
5517
5570
|
if not dry_run:
|
|
@@ -5547,9 +5600,18 @@ def install_systemd(
|
|
|
5547
5600
|
"--dry-run",
|
|
5548
5601
|
help="Print what would be written without touching the filesystem.",
|
|
5549
5602
|
),
|
|
5603
|
+
config_path: str | None = typer.Option(
|
|
5604
|
+
None,
|
|
5605
|
+
"--config",
|
|
5606
|
+
"-c",
|
|
5607
|
+
help="Path to orchestrator.yaml (default: auto-discover; see $CTRLRELAY_CONFIG).",
|
|
5608
|
+
),
|
|
5550
5609
|
) -> None:
|
|
5551
5610
|
"""Render and install bridge, poller and status-page systemd user units (Linux).
|
|
5552
5611
|
|
|
5612
|
+
The bridge and poller units carry the chat token in the variable the
|
|
5613
|
+
configured transport names, so export that variable before running.
|
|
5614
|
+
|
|
5553
5615
|
After running, enable with:
|
|
5554
5616
|
systemctl --user daemon-reload
|
|
5555
5617
|
systemctl --user enable --now ctrlrelay-bridge.service
|
|
@@ -5562,6 +5624,7 @@ def install_systemd(
|
|
|
5562
5624
|
workdir=workdir,
|
|
5563
5625
|
poller_interval=poller_interval,
|
|
5564
5626
|
target_dir=target_dir,
|
|
5627
|
+
token_env_var=_install_token_env_var(config_path),
|
|
5565
5628
|
)
|
|
5566
5629
|
written: list[Path] = []
|
|
5567
5630
|
if not dry_run:
|
|
@@ -51,6 +51,13 @@ _GIT_TRANSFER_TIMEOUT_SECONDS = 1800
|
|
|
51
51
|
# a non-fast-forward rejection.
|
|
52
52
|
_BOT_BRANCH_REFSPEC = "+refs/heads/dependabot/*:refs/heads/dependabot/*"
|
|
53
53
|
|
|
54
|
+
# ...and the same pattern as a negative refspec, so the fetch that
|
|
55
|
+
# carries every other branch leaves them alone. Keeping the two fetches
|
|
56
|
+
# disjoint is what lets only one of them prune (#243), and it also
|
|
57
|
+
# removes the ordering dependency that made a rebased bot branch a
|
|
58
|
+
# tolerated rejection under the plain refspec.
|
|
59
|
+
_NOT_BOT_BRANCHES_REFSPEC = "^refs/heads/dependabot/*"
|
|
60
|
+
|
|
54
61
|
|
|
55
62
|
def _rejections_are_only_non_fast_forward(stderr: str) -> list[str]:
|
|
56
63
|
"""Return the refs a fetch declined to rewind, or ``[]`` if the
|
|
@@ -1335,23 +1342,36 @@ class WorktreeManager:
|
|
|
1335
1342
|
gets to inspect divergence and decide.
|
|
1336
1343
|
- Local and origin equal: no-op.
|
|
1337
1344
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1345
|
+
Two fetches, not one, and only the first prunes (#243).
|
|
1346
|
+
``--prune`` is per command and scoped to that command's
|
|
1347
|
+
refspecs, so ``--prune`` paired with ``refs/heads/*`` deletes
|
|
1348
|
+
every local branch origin does not have — measured on 31 Oct,
|
|
1349
|
+
a branch made in the clone went with
|
|
1350
|
+
``- [deleted] (none) -> held``. A blocked session whose
|
|
1351
|
+
branch was never pushed lost it the next time any run touched
|
|
1352
|
+
that repo, and its commits were then reachable only from the
|
|
1353
|
+
worktree's HEAD, one ``git gc`` from gone. So:
|
|
1354
|
+
|
|
1355
|
+
- ``dependabot/*`` with a force refspec **and** ``--prune``.
|
|
1346
1356
|
Dependabot rebases its branches, which rewrites their tips,
|
|
1347
|
-
so under a plain refspec every rebase is a rejection
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1357
|
+
so under a plain refspec every rebase is a rejection — a
|
|
1358
|
+
secops sweep died on ``AInvirion/zzsites`` that way with
|
|
1359
|
+
``main`` already updated, and twelve bare repos were holding
|
|
1360
|
+
such a ref. These branches are bot-authored, never carry
|
|
1361
|
+
local work, and are the ones that actually pile up, so this
|
|
1362
|
+
is where pruning belongs.
|
|
1363
|
+
- Everything else with the plain refspec and **no** ``--prune``,
|
|
1364
|
+
with ``dependabot/*`` excluded by a negative refspec so the
|
|
1365
|
+
two never overlap. A branch origin deleted therefore lingers
|
|
1366
|
+
here; that is the trade, and the branch-reuse path in
|
|
1367
|
+
:meth:`create_worktree_with_new_branch` already has to
|
|
1368
|
+
recognise a local-only ref and decide whether its commits
|
|
1369
|
+
are already merged.
|
|
1370
|
+
|
|
1371
|
+
Any rejection that survives either fetch is tolerated rather
|
|
1372
|
+
than raised. Git exits 1 on a declined rewind even when every
|
|
1373
|
+
other ref applied, and that exit code was aborting the whole
|
|
1374
|
+
session for a branch it had correctly protected.
|
|
1355
1375
|
|
|
1356
1376
|
The default branch is the exception: tolerating a rejection
|
|
1357
1377
|
there would swap a loud failure for work done quietly on a stale
|
|
@@ -1391,11 +1411,26 @@ class WorktreeManager:
|
|
|
1391
1411
|
for branch in await self._checked_out_branches(bare_path)
|
|
1392
1412
|
if branch != default_branch
|
|
1393
1413
|
] if default_branch else []
|
|
1414
|
+
# The bot branches: force refspec, and the only --prune.
|
|
1415
|
+
# `skip` applies here too — git refuses the WHOLE fetch over
|
|
1416
|
+
# a checked-out branch, and a secops run can have a
|
|
1417
|
+
# dependabot branch checked out. Excluding one also spares
|
|
1418
|
+
# it the prune, which is the answer we want anyway.
|
|
1419
|
+
await self._run_git(
|
|
1420
|
+
"fetch", "--prune", "origin",
|
|
1421
|
+
_BOT_BRANCH_REFSPEC,
|
|
1422
|
+
*skip,
|
|
1423
|
+
cwd=bare_path,
|
|
1424
|
+
timeout=_GIT_TRANSFER_TIMEOUT_SECONDS,
|
|
1425
|
+
tolerate_non_fast_forward=True,
|
|
1426
|
+
)
|
|
1427
|
+
# Every other branch: no --prune, so a local-only branch
|
|
1428
|
+
# survives (#243).
|
|
1394
1429
|
try:
|
|
1395
1430
|
await self._run_git(
|
|
1396
|
-
"fetch", "
|
|
1397
|
-
_BOT_BRANCH_REFSPEC,
|
|
1431
|
+
"fetch", "origin",
|
|
1398
1432
|
"refs/heads/*:refs/heads/*",
|
|
1433
|
+
_NOT_BOT_BRANCHES_REFSPEC,
|
|
1399
1434
|
*skip,
|
|
1400
1435
|
cwd=bare_path,
|
|
1401
1436
|
timeout=_GIT_TRANSFER_TIMEOUT_SECONDS,
|
|
@@ -1407,11 +1442,72 @@ class WorktreeManager:
|
|
|
1407
1442
|
)
|
|
1408
1443
|
except DefaultBranchDivergedError as e:
|
|
1409
1444
|
await self._set_aside_and_reclone(repo, bare_path, e)
|
|
1445
|
+
else:
|
|
1446
|
+
await self._follow_origin_default(repo, bare_path, default_branch)
|
|
1410
1447
|
else:
|
|
1411
1448
|
await self._clone_bare(repo, bare_path)
|
|
1412
1449
|
|
|
1413
1450
|
return bare_path
|
|
1414
1451
|
|
|
1452
|
+
async def _follow_origin_default(
|
|
1453
|
+
self, repo: str, bare_path: Path, default_branch: str,
|
|
1454
|
+
) -> None:
|
|
1455
|
+
"""Point the clone's HEAD at origin's default branch when origin
|
|
1456
|
+
changed it. The fetch no longer prunes branches (#243), so a
|
|
1457
|
+
renamed default's old branch stays here, and without this the
|
|
1458
|
+
clone would keep cutting sessions from it. HEAD moves only when
|
|
1459
|
+
the new default is here at the commit origin reports, so a stale
|
|
1460
|
+
or diverged local branch is never adopted; anything else is left
|
|
1461
|
+
for the next update and logged, never silent."""
|
|
1462
|
+
try:
|
|
1463
|
+
out = await self._run_git(
|
|
1464
|
+
"ls-remote", "--symref", "origin", "HEAD",
|
|
1465
|
+
cwd=bare_path, timeout=_GIT_TRANSFER_TIMEOUT_SECONDS,
|
|
1466
|
+
)
|
|
1467
|
+
except WorktreeError as exc:
|
|
1468
|
+
log_event(
|
|
1469
|
+
_logger, "worktree.default_branch_unchecked", repo=repo,
|
|
1470
|
+
reason=str(exc)[:200],
|
|
1471
|
+
)
|
|
1472
|
+
return
|
|
1473
|
+
prefix, suffix = "ref: refs/heads/", "\tHEAD"
|
|
1474
|
+
new = next(
|
|
1475
|
+
(line[len(prefix):-len(suffix)] for line in out.splitlines()
|
|
1476
|
+
if line.startswith(prefix) and line.endswith(suffix)),
|
|
1477
|
+
"",
|
|
1478
|
+
)
|
|
1479
|
+
remote_sha = next(
|
|
1480
|
+
(line.split("\t", 1)[0] for line in out.splitlines()
|
|
1481
|
+
if line.endswith("\tHEAD") and not line.startswith("ref: ")),
|
|
1482
|
+
"",
|
|
1483
|
+
)
|
|
1484
|
+
if not new or new == default_branch:
|
|
1485
|
+
return
|
|
1486
|
+
try:
|
|
1487
|
+
local_sha = (await self._run_git(
|
|
1488
|
+
"rev-parse", "--verify", "--quiet", f"refs/heads/{new}",
|
|
1489
|
+
cwd=bare_path,
|
|
1490
|
+
)).strip()
|
|
1491
|
+
except WorktreeError:
|
|
1492
|
+
local_sha = ""
|
|
1493
|
+
if not remote_sha or local_sha != remote_sha:
|
|
1494
|
+
log_event(
|
|
1495
|
+
_logger, "worktree.default_branch_not_followed", repo=repo,
|
|
1496
|
+
old=default_branch or None, new=new,
|
|
1497
|
+
reason="not here yet" if not local_sha else "not current",
|
|
1498
|
+
)
|
|
1499
|
+
return
|
|
1500
|
+
try:
|
|
1501
|
+
await self._run_git(
|
|
1502
|
+
"symbolic-ref", "HEAD", f"refs/heads/{new}", cwd=bare_path,
|
|
1503
|
+
)
|
|
1504
|
+
except WorktreeError:
|
|
1505
|
+
return
|
|
1506
|
+
log_event(
|
|
1507
|
+
_logger, "worktree.default_branch_changed", repo=repo,
|
|
1508
|
+
old=default_branch or None, new=new,
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1415
1511
|
async def _checked_out_branches(self, bare_path: Path) -> list[str]:
|
|
1416
1512
|
"""Branches checked out in this clone's linked worktrees."""
|
|
1417
1513
|
listing = await self._run_git(
|
|
@@ -1444,8 +1540,9 @@ class WorktreeManager:
|
|
|
1444
1540
|
|
|
1445
1541
|
The old clone is never deleted here, and the operator is told
|
|
1446
1542
|
where it went so they can delete it. It holds what the failed
|
|
1447
|
-
fetch left:
|
|
1448
|
-
|
|
1543
|
+
fetch left: the bot-branch fetch that ran before it prunes
|
|
1544
|
+
``dependabot/*`` refs origin no longer has. Branches outside
|
|
1545
|
+
that namespace are not pruned by any fetch here (#243).
|
|
1449
1546
|
|
|
1450
1547
|
Refused, re-raising ``cause``, while any worktree is registered
|
|
1451
1548
|
in the clone: a BLOCKED session keeps its worktree for the
|