Detect changes since last documentation generation. Returns a changes dict with affected modules, or None if no previous generation exists (first run). Detection strategy: 1. Git-based: compare stored commit_id with current HEAD, plus check uncommitted changes via ``git
(
repo_path: Path,
output_dir: Path,
)
| 26 | # --------------------------------------------------------------------------- |
| 27 | |
| 28 | def _detect_changes( |
| 29 | repo_path: Path, |
| 30 | output_dir: Path, |
| 31 | ) -> Optional[Dict[str, Any]]: |
| 32 | """Detect changes since last documentation generation. |
| 33 | |
| 34 | Returns a changes dict with affected modules, or None if no previous |
| 35 | generation exists (first run). |
| 36 | |
| 37 | Detection strategy: |
| 38 | 1. Git-based: compare stored commit_id with current HEAD, plus check |
| 39 | uncommitted changes via ``git status``. |
| 40 | 2. Fallback: compare file mtime with stored ``timestamp`` in metadata. |
| 41 | """ |
| 42 | metadata_path = output_dir / "metadata.json" |
| 43 | module_tree_path = output_dir / "module_tree.json" |
| 44 | |
| 45 | if not metadata_path.exists() or not module_tree_path.exists(): |
| 46 | return None |
| 47 | |
| 48 | try: |
| 49 | metadata = json.loads(metadata_path.read_text(encoding="utf-8")) |
| 50 | module_tree = json.loads(module_tree_path.read_text(encoding="utf-8")) |
| 51 | except (json.JSONDecodeError, OSError, UnicodeDecodeError): |
| 52 | return None |
| 53 | |
| 54 | # Try git-based detection first |
| 55 | changes = _detect_via_git(repo_path, metadata, output_dir) |
| 56 | |
| 57 | # Fallback to mtime-based detection |
| 58 | if changes is None: |
| 59 | changes = _detect_via_mtime(repo_path, metadata) |
| 60 | |
| 61 | if changes is None: |
| 62 | return None |
| 63 | |
| 64 | changed_files = changes["changed_files"] |
| 65 | if not changed_files: |
| 66 | return { |
| 67 | "has_previous": True, |
| 68 | "no_changes": True, |
| 69 | "method": changes.get("method", "unknown"), |
| 70 | "message": "No changes detected since last generation. Documentation is up to date.", |
| 71 | } |
| 72 | |
| 73 | affected, cascade = _find_affected_modules(module_tree, changed_files) |
| 74 | |
| 75 | return { |
| 76 | "has_previous": True, |
| 77 | "no_changes": False, |
| 78 | "method": changes.get("method", "unknown"), |
| 79 | "changed_files": changed_files, |
| 80 | "affected_modules": sorted(affected), |
| 81 | "cascade_modules": sorted(cascade), |
| 82 | "hint": ( |
| 83 | f"Only {len(affected)} module(s) need updating: {sorted(affected)}. " |
| 84 | f"Parent modules to refresh: {sorted(cascade)}. " |
| 85 | "Use edit_doc_file for targeted updates, write_doc_file for new modules." |
no test coverage detected