diff --git a/codewiki/cli/commands/generate.py b/codewiki/cli/commands/generate.py index b7caea2d..1c370cb8 100644 --- a/codewiki/cli/commands/generate.py +++ b/codewiki/cli/commands/generate.py @@ -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 @@ -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) @@ -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.") diff --git a/codewiki/cli/utils/repo_validator.py b/codewiki/cli/utils/repo_validator.py index 608e6037..12e9f952 100644 --- a/codewiki/cli/utils/repo_validator.py +++ b/codewiki/cli/utils/repo_validator.py @@ -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 "" @@ -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 ""