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
43 changes: 37 additions & 6 deletions codewiki/cli/commands/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ def _detect_changed_files(
Detect files changed since the last documentation generation.

Reads the commit_id from metadata.json and compares with current HEAD
using git diff. Returns list of changed file paths, or None if unable
to determine (e.g., no metadata, not a git repo).
using git diff. When running inside a subdirectory of a monorepo,
only files under that subdirectory are returned.

Returns list of changed file paths relative to repo_path, or None if
unable to determine (e.g., no metadata, not a git repo).
"""
import json

Expand Down Expand Up @@ -85,6 +88,21 @@ def _detect_changed_files(
logger.debug(f"HEAD is still at {current_commit[:8]} — no changes.")
return []

# Determine subdirectory prefix relative to the git root
if repo.working_tree_dir is None:
if verbose:
logger.debug("Bare git repository — running full generation.")
return None
git_root = Path(repo.working_tree_dir).resolve()
repo_path_resolved = repo_path.resolve()
try:
subpath_prefix = repo_path_resolved.relative_to(git_root).as_posix()
except ValueError:
# repo_path is outside git root — shouldn't happen, but fall back to full generation
if verbose:
logger.debug("Repo path is outside git root — running full generation.")
return None

# Get changed files between previous and current commit
try:
diff_index = repo.commit(prev_commit).diff(current_commit)
Expand All @@ -95,14 +113,27 @@ def _detect_changed_files(
if diff.b_path and diff.b_path != diff.a_path:
changed.append(diff.b_path)

# Filter to files under the current subdirectory and strip the prefix
# so paths align with module_tree.json component paths
filtered = []
if subpath_prefix == ".":
filtered = changed
else:
prefix = subpath_prefix + "/"
for path in changed:
if path.startswith(prefix):
filtered.append(path[len(prefix):])

if verbose:
logger.debug(f"Changes between {prev_commit[:8]} and {current_commit[:8]}:")
for f in changed[:10]:
if subpath_prefix != ".":
logger.debug(f" Scoped to subdirectory: {subpath_prefix}")
for f in filtered[:10]:
logger.debug(f" {f}")
if len(changed) > 10:
logger.debug(f" ... and {len(changed) - 10} more")
if len(filtered) > 10:
logger.debug(f" ... and {len(filtered) - 10} more")

return changed
return filtered
except Exception as e:
if verbose:
logger.debug(f"Git diff failed: {e} — running full generation.")
Expand Down
49 changes: 35 additions & 14 deletions codewiki/cli/utils/repo_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,36 +116,56 @@ def check_writable_output(output_dir: Path) -> Path:
return output_dir


def _get_git_repo(repo_path: Path):
"""
Find a git repository starting at repo_path and searching parent directories.

Args:
repo_path: Path to start searching from

Returns:
git.Repo instance or None if no repository found
"""
try:
import git
return git.Repo(repo_path, search_parent_directories=True)
except Exception:
return None


def is_git_repository(repo_path: Path) -> bool:
"""
Check if path is a git repository.
Check if path is inside a git repository.

Searches parent directories if .git is not directly at repo_path,
supporting monorepo subdirectories.

Args:
repo_path: Path to check

Returns:
True if git repository, False otherwise
True if inside a git repository, False otherwise
"""
git_dir = repo_path / ".git"
return git_dir.exists() and git_dir.is_dir()
return _get_git_repo(repo_path) is not None


def get_git_commit_hash(repo_path: Path) -> str:
"""
Get current git commit hash.

Searches parent directories to support monorepo subdirectories.

Args:
repo_path: Repository path
repo_path: Path inside a git repository

Returns:
Commit hash or empty string if not a git repo
Commit hash or empty string if not in a git repo
"""
if not is_git_repository(repo_path):
repo = _get_git_repo(repo_path)
if repo is None:
return ""

try:
import git
repo = git.Repo(repo_path)
return repo.head.commit.hexsha
except Exception:
return ""
Expand All @@ -155,18 +175,19 @@ def get_git_branch(repo_path: Path) -> str:
"""
Get current git branch name.

Searches parent directories to support monorepo subdirectories.

Args:
repo_path: Repository path
repo_path: Path inside a git repository

Returns:
Branch name or empty string if not a git repo
Branch name or empty string if not in a git repo
"""
if not is_git_repository(repo_path):
repo = _get_git_repo(repo_path)
if repo is None:
return ""

try:
import git
repo = git.Repo(repo_path)
return repo.active_branch.name
except Exception:
return ""
Expand Down