Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2879dc34cc | |||
| 5ded28896d | |||
| 1fd79dda9a | |||
| 86c7ac1843 | |||
| 2e0414f969 | |||
| 12b071833d | |||
| bf72282f8e | |||
| f2c3710d0d |
@@ -14,6 +14,7 @@ the private orchestrator `_launch_bottle`.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
@@ -195,7 +196,11 @@ def _start_headless(
|
||||
path, so the agent still execs on the inherited stdio/PTY — an
|
||||
orchestrator allocates that PTY and relays it to its
|
||||
desktop/mobile clients."""
|
||||
if not os.isatty(sys.stdin.fileno()):
|
||||
try:
|
||||
stdin_fd = sys.stdin.fileno()
|
||||
except io.UnsupportedOperation:
|
||||
stdin_fd = -1
|
||||
if not os.isatty(stdin_fd):
|
||||
die(
|
||||
"--headless requires a PTY on stdin; run via:\n"
|
||||
" script -q /dev/null ./cli.py start ..."
|
||||
|
||||
@@ -57,7 +57,7 @@ def _merge_two_bottles_runtime(base: "ManifestBottle", override: "ManifestBottle
|
||||
n for n in override_repos_by_name if n not in base_repos_by_name
|
||||
]
|
||||
merged_git = tuple(
|
||||
override_repos_by_name.get(n, base_repos_by_name[n])
|
||||
override_repos_by_name[n] if n in override_repos_by_name else base_repos_by_name[n]
|
||||
for n in merged_repos_names
|
||||
)
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ class TestCmdStartHeadless(unittest.TestCase):
|
||||
self._modal = patch.object(tui_mod, "name_color_modal").start()
|
||||
patch.dict(os.environ, {}, clear=False).start()
|
||||
os.environ.pop("BOT_BOTTLE_BACKEND", None)
|
||||
# PTY check uses os.isatty(sys.stdin.fileno()); stub both so
|
||||
# headless unit tests aren't blocked on a real TTY.
|
||||
patch("bot_bottle.cli.start.os.isatty", return_value=True).start()
|
||||
self.addCleanup(patch.stopall)
|
||||
|
||||
def _spec(self):
|
||||
|
||||
@@ -25,6 +25,10 @@ def _bottle(**kwargs: object) -> ManifestBottle:
|
||||
return ManifestBottle.from_dict("test", kwargs)
|
||||
|
||||
|
||||
def _git_repo(url: str) -> dict[str, object]:
|
||||
return {"url": url, "key": {"provider": "gitea", "forge_token_env": "TOK"}}
|
||||
|
||||
|
||||
class TestMergeBottlesRuntime(unittest.TestCase):
|
||||
def test_single_bottle_returns_as_is(self):
|
||||
b = _bottle(env={"FOO": "1"})
|
||||
@@ -115,6 +119,27 @@ class TestMergeBottlesRuntime(unittest.TestCase):
|
||||
self.assertEqual("2", result.env["B"])
|
||||
self.assertEqual("3", result.env["C"])
|
||||
|
||||
def test_git_repo_only_in_override_does_not_raise(self):
|
||||
# Regression for issue #457: override bottle declares a repo that the
|
||||
# base doesn't have → KeyError on base_repos_by_name[n].
|
||||
base = _bottle(env={"X": "base"})
|
||||
override = _bottle(**{"git-gate": {"repos": {"myrepo": _git_repo("ssh://git@example.com/repo.git")}}})
|
||||
result = merge_bottles_runtime([base, override])
|
||||
self.assertIn("myrepo", [e.Name for e in result.git])
|
||||
|
||||
def test_git_repo_only_in_base_survives_override(self):
|
||||
base = _bottle(**{"git-gate": {"repos": {"myrepo": _git_repo("ssh://git@example.com/repo.git")}}})
|
||||
override = _bottle(env={"X": "override"})
|
||||
result = merge_bottles_runtime([base, override])
|
||||
self.assertIn("myrepo", [e.Name for e in result.git])
|
||||
|
||||
def test_git_repo_override_wins_by_name(self):
|
||||
base = _bottle(**{"git-gate": {"repos": {"myrepo": _git_repo("ssh://git@base.example.com/repo.git")}}})
|
||||
override = _bottle(**{"git-gate": {"repos": {"myrepo": _git_repo("ssh://git@override.example.com/repo.git")}}})
|
||||
result = merge_bottles_runtime([base, override])
|
||||
self.assertEqual(1, len(result.git))
|
||||
self.assertEqual("ssh://git@override.example.com/repo.git", result.git[0].Upstream)
|
||||
|
||||
def test_empty_list_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
merge_bottles_runtime([])
|
||||
|
||||
Reference in New Issue
Block a user