Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ def __init__(
self,
repo_path: str = ".",
main_branch: str = "main",
default_remote: str = "origin"
default_remote: str = "origin",
rc_branch: str | None = None,
):
"""
Initialize Git client.
Expand All @@ -64,6 +65,7 @@ def __init__(
self.repo_path = repo_path
self.main_branch = main_branch
self.default_remote = default_remote
self.rc_branch = rc_branch

# Initialize network layer
self.network = GitNetwork(repo_path=repo_path)
Expand Down
3 changes: 2 additions & 1 deletion plugins/titan-plugin-git/titan_plugin_git/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def initialize(self, config: TitanConfig, secrets: SecretManager) -> None:
# Initialize client with validated configuration
self._client = GitClient(
main_branch=validated_config.main_branch,
default_remote=validated_config.default_remote
default_remote=validated_config.default_remote,
rc_branch=validated_config.rc_branch,
)

def _get_plugin_config(self, config: TitanConfig) -> dict:
Expand Down
25 changes: 25 additions & 0 deletions plugins/titan-plugin-github/tests/unit/test_pull_request_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
merge_pull_request_step,
verify_pull_request_state_step,
)
from titan_plugin_github.steps.create_pr_step import create_pr_step


class MockTextual:
Expand All @@ -18,6 +19,7 @@ def __init__(self):
self.success_text = Mock()
self.warning_text = Mock()
self.dim_text = Mock()
self.text = Mock()

def loading(self, _message):
class _Loader:
Expand Down Expand Up @@ -147,3 +149,26 @@ def test_verify_pull_request_state_step_requires_expected_state():
assert isinstance(result, Error)
assert result.message == "No expected PR state in context"
ctx.textual.end_step.assert_called_once_with("error")


def test_create_pr_step_uses_context_base_branch():
github = Mock()
github.config.auto_assign_prs = False
github.create_pull_request.return_value = ClientSuccess(
data=Mock(number=4105, url="https://github.example/pr/4105"),
message="ok",
)
ctx = make_context(
github,
pr_title="notes: Add release notes for 26.18",
pr_body="Release notes",
pr_head_branch="notes/release-notes",
pr_base_branch="rc/26.18",
)
ctx.git = Mock(main_branch="develop")

result = create_pr_step(ctx)

assert isinstance(result, Success)
github.create_pull_request.assert_called_once()
assert github.create_pull_request.call_args.kwargs["base"] == "rc/26.18"
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def create_pr_step(ctx: WorkflowContext) -> WorkflowResult:
# 2. Get required data from context and client config
title = ctx.get("pr_title")
body = ctx.get("pr_body")
base = ctx.git.main_branch # Get base branch from git client config
base = ctx.get("pr_base_branch") or ctx.git.main_branch
head = ctx.get("pr_head_branch")
is_draft = ctx.get("pr_is_draft", False) # Default to not a draft

Expand Down
31 changes: 31 additions & 0 deletions titan_cli/core/plugins/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class GitPluginConfig(BaseModel):
"""Configuration for Git plugin."""
main_branch: str = Field("main", description="Main/default branch name")
default_remote: str = Field("origin", description="Default remote name")
rc_branch: str | None = Field(None, description="Prefix for versioned RC branches (for example, 'rc')")

class GitHubPluginConfig(BaseModel):
"""Configuration for GitHub plugin."""
Expand Down Expand Up @@ -121,3 +122,33 @@ def validate_email(cls, v):
if '@' not in v:
raise ValueError("email must be a valid email address")
return v.lower() # Normalize email to lowercase


class TekelPluginConfig(BaseModel):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Pydantic model TekelPluginConfig is defined twice in this file, once starting at line 131 and again at line 179. This duplication will likely cause runtime errors and should be removed. It seems to be an accidental copy-paste.

"""Configuration for Tekel plugin."""
base_url: Optional[str] = Field(
None,
description="Tekel API base URL (e.g. 'https://tekel-api.example.com')",
json_schema_extra={"config_scope": "global"},
)
api_token: Optional[str] = Field(
None,
description="Tekel API token",
json_schema_extra={"format": "password", "required_in_schema": True},
)
timeout: int = Field(
30,
description="Request timeout in seconds",
json_schema_extra={"config_scope": "global"},
)

@field_validator('base_url')
@classmethod
def validate_base_url(cls, v):
if not v:
raise ValueError(
"Tekel base_url not configured. Add [plugins.tekel.config] with base_url in ~/.titan/config.toml"
)
if not v.startswith(('http://', 'https://')):
raise ValueError("base_url must start with http:// or https://")
return v.rstrip('/')