Skip to content
Merged
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
4 changes: 3 additions & 1 deletion cli/bash/commands/basectl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ such command directories exist. Optional utility CLIs such as `caff` and
`--manifest <path>` takes precedence over `workspace.manifest`. Interactive
output uses a repository/action/result table with present, cloned, skipped,
planned, and failed results plus aggregate counts; successful delegated output
is suppressed and failure details remain visible.
is suppressed and failure details remain visible. Timestamped delegated Base
log records are kept out of the normal detail block and remain available in
debug diagnostics.
- `basectl workspace init <workspace-source>` bootstraps a workspace from a
workspace configuration repository. The source may be a local path, GitHub URL,
`owner/repo`, or a short repository name resolved by `--owner <owner>` or
Expand Down
27 changes: 27 additions & 0 deletions cli/python/base_projects/tests/test_workspace_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from unittest import mock

from base_projects import engine
from base_projects.workspace_clone_command import clone_detail


def write_workspace_manifest(path: Path, body: str) -> None:
Expand Down Expand Up @@ -41,6 +42,7 @@ def write_fake_basectl(base_home: Path, state_file: Path) -> None:
done
if [[ "$repo" == "codeforester/conflict" ]]; then
printf 'simulated clone conflict for %s\\n' "$repo" >&2
printf '2026-09-03 23:29:23 +0530 ERROR subcommands/repo.sh:2179 Failed to clone repository.\\n' >&2
exit 1
fi
if [[ "$dry_run" != "1" && -n "$path" ]]; then
Expand Down Expand Up @@ -84,6 +86,30 @@ def workspace_clone_row(stdout: str, repo_name: str) -> list[str]:


class WorkspaceCloneTests(unittest.TestCase):
def test_clone_detail_filters_timestamped_base_log_records(self) -> None:
detail = clone_detail(
"Cloning GitHub repository 'codeforester/bleach'.\n",
"\n".join(
(
"HTTP 401: Bad credentials (https://api.github.com/graphql)",
"Try authenticating with: gh auth refresh -h github.com",
"2026-09-03 23:29:23 +0530 ERROR subcommands/repo.sh:2179 Failed to clone repository.",
"2026-06-10 10:15:33 WARN repo.sh:100 retrying",
)
),
)

self.assertEqual(
detail,
"\n".join(
(
"HTTP 401: Bad credentials (https://api.github.com/graphql)",
"Try authenticating with: gh auth refresh -h github.com",
"Cloning GitHub repository 'codeforester/bleach'.",
)
),
)

def test_workspace_clone_dry_run_materializes_missing_required_repositories(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
Expand Down Expand Up @@ -200,6 +226,7 @@ def test_workspace_clone_include_optional_continues_after_clone_failures(self) -
["conflict", "CLONE", "failed", "(exit", "1)"],
)
self.assertIn("simulated clone conflict for codeforester/conflict", stdout)
self.assertNotIn("subcommands/repo.sh:2179", stdout)
self.assertEqual(workspace_clone_row(stdout, "api"), ["api", "CHECK", "present"])
self.assertEqual(
workspace_clone_row(stdout, "optional-tool"),
Expand Down
15 changes: 14 additions & 1 deletion cli/python/base_projects/workspace_clone_command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, Protocol
Expand Down Expand Up @@ -54,6 +55,13 @@ class WorkspaceCloneCounts:
failed: int = 0


BASE_LOG_RECORD_RE = re.compile(
r"^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}"
r"(?:\s+(?:[+-]\d{4}|UTC))?\s+"
r"(?:TRACE|DEBUG|INFO|WARN|WARNING|ERROR|FATAL)\s+"
)


def workspace_clone_command(ctx: base_cli.Context, options: WorkspaceCloneOptions) -> int:
if options.output_format != "text":
raise ProjectUsageError(f"Unsupported output format '{options.output_format}'. Expected: text.")
Expand Down Expand Up @@ -248,7 +256,12 @@ def clone_workspace_repo(


def clone_detail(stdout: str, stderr: str) -> str:
details = [part.strip() for part in (stderr, stdout) if part.strip()]
details = [
line.strip()
for stream in (stderr, stdout)
for line in stream.splitlines()
if line.strip() and not BASE_LOG_RECORD_RE.match(line.strip())
]
return "\n".join(details) or "clone failed without diagnostic output"


Expand Down
4 changes: 3 additions & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,9 @@ a no-write preview. Text output uses a stable repository/action/result table:
existing repositories are `present`, newly materialized repositories are
`cloned`, optional omissions are `skipped`, dry-run operations are `planned`,
and failures include concise details and exit codes. Successful delegated clone
output is suppressed in normal interactive mode. Optional repositories are
output is suppressed in normal interactive mode; timestamped delegated Base log
records remain in debug diagnostics rather than being rendered as detail lines.
Optional repositories are
reported but skipped unless `--include-optional` is supplied. Workspace manifests may list non-GitHub Git
URLs for reporting, but automatic materialization through `workspace clone` is
GitHub-only today; clone GitLab, Bitbucket, internal Git, or local repositories
Expand Down
2 changes: 2 additions & 0 deletions docs/workspace-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,8 @@ table: existing repositories are `present`, newly materialized repositories are
and failures include concise details and exit codes. Successful delegated clone
output is suppressed in normal interactive mode, while the completion summary
reports aggregate present, cloned, skipped, and failed counts.
Timestamped delegated Base log records remain available in debug diagnostics
rather than being rendered as indented failure details.

`basectl workspace configure --manifest <path>` configures present Base-managed
expected repositories through `basectl repo configure`. It skips missing
Expand Down
Loading