Skip to content

Commit 69b2610

Browse files
authored
Merge pull request #59 from p0thi/feature/subdirectory-incremental-updates
feat: Support subdirectories in generate and validator
2 parents 4949fa2 + f674538 commit 69b2610

2 files changed

Lines changed: 72 additions & 20 deletions

File tree

codewiki/cli/commands/generate.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ def _detect_changed_files(
4949
Detect files changed since the last documentation generation.
5050
5151
Reads the commit_id from metadata.json and compares with current HEAD
52-
using git diff. Returns list of changed file paths, or None if unable
53-
to determine (e.g., no metadata, not a git repo).
52+
using git diff. When running inside a subdirectory of a monorepo,
53+
only files under that subdirectory are returned.
54+
55+
Returns list of changed file paths relative to repo_path, or None if
56+
unable to determine (e.g., no metadata, not a git repo).
5457
"""
5558
import json
5659

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

91+
# Determine subdirectory prefix relative to the git root
92+
if repo.working_tree_dir is None:
93+
if verbose:
94+
logger.debug("Bare git repository — running full generation.")
95+
return None
96+
git_root = Path(repo.working_tree_dir).resolve()
97+
repo_path_resolved = repo_path.resolve()
98+
try:
99+
subpath_prefix = repo_path_resolved.relative_to(git_root).as_posix()
100+
except ValueError:
101+
# repo_path is outside git root — shouldn't happen, but fall back to full generation
102+
if verbose:
103+
logger.debug("Repo path is outside git root — running full generation.")
104+
return None
105+
88106
# Get changed files between previous and current commit
89107
try:
90108
diff_index = repo.commit(prev_commit).diff(current_commit)
@@ -95,14 +113,27 @@ def _detect_changed_files(
95113
if diff.b_path and diff.b_path != diff.a_path:
96114
changed.append(diff.b_path)
97115

116+
# Filter to files under the current subdirectory and strip the prefix
117+
# so paths align with module_tree.json component paths
118+
filtered = []
119+
if subpath_prefix == ".":
120+
filtered = changed
121+
else:
122+
prefix = subpath_prefix + "/"
123+
for path in changed:
124+
if path.startswith(prefix):
125+
filtered.append(path[len(prefix):])
126+
98127
if verbose:
99128
logger.debug(f"Changes between {prev_commit[:8]} and {current_commit[:8]}:")
100-
for f in changed[:10]:
129+
if subpath_prefix != ".":
130+
logger.debug(f" Scoped to subdirectory: {subpath_prefix}")
131+
for f in filtered[:10]:
101132
logger.debug(f" {f}")
102-
if len(changed) > 10:
103-
logger.debug(f" ... and {len(changed) - 10} more")
133+
if len(filtered) > 10:
134+
logger.debug(f" ... and {len(filtered) - 10} more")
104135

105-
return changed
136+
return filtered
106137
except Exception as e:
107138
if verbose:
108139
logger.debug(f"Git diff failed: {e} — running full generation.")

codewiki/cli/utils/repo_validator.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,56 @@ def check_writable_output(output_dir: Path) -> Path:
116116
return output_dir
117117

118118

119+
def _get_git_repo(repo_path: Path):
120+
"""
121+
Find a git repository starting at repo_path and searching parent directories.
122+
123+
Args:
124+
repo_path: Path to start searching from
125+
126+
Returns:
127+
git.Repo instance or None if no repository found
128+
"""
129+
try:
130+
import git
131+
return git.Repo(repo_path, search_parent_directories=True)
132+
except Exception:
133+
return None
134+
135+
119136
def is_git_repository(repo_path: Path) -> bool:
120137
"""
121-
Check if path is a git repository.
138+
Check if path is inside a git repository.
139+
140+
Searches parent directories if .git is not directly at repo_path,
141+
supporting monorepo subdirectories.
122142
123143
Args:
124144
repo_path: Path to check
125145
126146
Returns:
127-
True if git repository, False otherwise
147+
True if inside a git repository, False otherwise
128148
"""
129-
git_dir = repo_path / ".git"
130-
return git_dir.exists() and git_dir.is_dir()
149+
return _get_git_repo(repo_path) is not None
131150

132151

133152
def get_git_commit_hash(repo_path: Path) -> str:
134153
"""
135154
Get current git commit hash.
136155
156+
Searches parent directories to support monorepo subdirectories.
157+
137158
Args:
138-
repo_path: Repository path
159+
repo_path: Path inside a git repository
139160
140161
Returns:
141-
Commit hash or empty string if not a git repo
162+
Commit hash or empty string if not in a git repo
142163
"""
143-
if not is_git_repository(repo_path):
164+
repo = _get_git_repo(repo_path)
165+
if repo is None:
144166
return ""
145167

146168
try:
147-
import git
148-
repo = git.Repo(repo_path)
149169
return repo.head.commit.hexsha
150170
except Exception:
151171
return ""
@@ -155,18 +175,19 @@ def get_git_branch(repo_path: Path) -> str:
155175
"""
156176
Get current git branch name.
157177
178+
Searches parent directories to support monorepo subdirectories.
179+
158180
Args:
159-
repo_path: Repository path
181+
repo_path: Path inside a git repository
160182
161183
Returns:
162-
Branch name or empty string if not a git repo
184+
Branch name or empty string if not in a git repo
163185
"""
164-
if not is_git_repository(repo_path):
186+
repo = _get_git_repo(repo_path)
187+
if repo is None:
165188
return ""
166189

167190
try:
168-
import git
169-
repo = git.Repo(repo_path)
170191
return repo.active_branch.name
171192
except Exception:
172193
return ""

0 commit comments

Comments
 (0)