""" Annotate - Create git commits organized by features from features.yaml For each feature, checks which files have modifications and creates a commit with the feature name and description. """ import yaml from pathlib import Path from typing import List, Tuple, Optional, Dict from ..apply.utils import run_git_command from ...common.context import Context from ...common.module import CommandModule, ValidationError from ...common.utils import log_info, log_error, log_success, log_warning def load_features(features_file: Path) -> Dict: """Load features from YAML file.""" try: with open(features_file, "r") as f: data = yaml.safe_load(f) return data.get("features", {}) except Exception as e: log_error(f"Failed to load features file: {e}") return {} def get_modified_files(chromium_src: Path, files: List[str]) -> List[str]: """Get list of files that have modifications or are untracked. Args: chromium_src: Chromium source directory files: List of file paths to check Returns: List of file paths that have modifications """ modified = [] for file_path in files: full_path = chromium_src / file_path if not full_path.exists(): continue result = run_git_command( ["git", "status", "--porcelain", str(file_path)], cwd=chromium_src, ) if result.returncode == 0 and result.stdout.strip(): modified.append(file_path) return modified def git_add_and_commit( chromium_src: Path, files: List[str], commit_message: str ) -> bool: """Add files and create commit. Args: chromium_src: Chromium source directory files: List of file paths to add commit_message: Commit message Returns: True if commit was created successfully """ # Add all specified files for file_path in files: result = run_git_command( ["git", "add", str(file_path)], cwd=chromium_src, ) if result.returncode != 0: log_error(f"Failed to add file: {file_path}") return False # Create commit result = run_git_command( ["git", "commit", "-m", commit_message], cwd=chromium_src, ) if result.returncode != 0: stderr = result.stderr or "" if "nothing to commit" in stderr or "nothing added to commit" in stderr: return False log_error(f"Failed to commit: {stderr}") return False return True def annotate_features( ctx: Context, feature_filter: Optional[str] = None, ) -> Tuple[int, int]: """Create commits for features with modified files. Iterates through features.yaml and creates a commit for each feature that has modified files in the working tree. Args: ctx: Build context feature_filter: If specified, only process this feature Returns: Tuple of (commits_created, features_skipped) """ features_file = ctx.get_features_yaml_path() if not features_file.exists(): log_error(f"Features file not found: {features_file}") return 0, 0 features = load_features(features_file) if not features: log_error("No features found in features.yaml") return 0, 0 # Filter to specific feature if requested if feature_filter: if feature_filter not in features: log_error(f"Feature '{feature_filter}' not found in features.yaml") return 0, 0 features = {feature_filter: features[feature_filter]} log_info(f"šŸ“‹ Processing {len(features)} feature(s)") log_info("=" * 60) commits_created = 0 features_skipped = 0 for feature_name, feature_data in features.items(): description = feature_data.get("description", feature_name) files = feature_data.get("files", []) log_info(f"\nšŸ”§ {feature_name}") log_info(f" {description}") if not files: log_warning(" No files specified, skipping") features_skipped += 1 continue # Find files with modifications modified_files = get_modified_files(ctx.chromium_src, files) if not modified_files: log_warning(f" No modified files ({len(files)} files checked)") features_skipped += 1 continue log_info(f" Found {len(modified_files)} modified file(s)") # Create commit (use description only from features.yaml) commit_message = description if git_add_and_commit(ctx.chromium_src, modified_files, commit_message): log_success(f" āœ“ Committed {len(modified_files)} file(s)") commits_created += 1 else: log_warning(" No changes staged, skipping commit") features_skipped += 1 return commits_created, features_skipped def annotate_single_feature( ctx: Context, feature_name: str, ) -> bool: """Create a commit for a single feature. Args: ctx: Build context feature_name: Name of the feature to commit Returns: True if commit was created successfully """ commits, _ = annotate_features(ctx, feature_filter=feature_name) return commits > 0 class AnnotateModule(CommandModule): """Create git commits organized by features from features.yaml""" produces = [] requires = [] description = "Create git commits organized by features" def validate(self, ctx: Context) -> None: """Validate git is available and chromium_src exists.""" import shutil if not shutil.which("git"): raise ValidationError("Git is not available in PATH") if not ctx.chromium_src.exists(): raise ValidationError(f"Chromium source not found: {ctx.chromium_src}") # Check if it's a git repository git_dir = ctx.chromium_src / ".git" if not git_dir.exists(): raise ValidationError(f"Not a git repository: {ctx.chromium_src}") def execute( self, ctx: Context, feature_name: Optional[str] = None, **kwargs, ) -> None: """Execute annotate. Args: ctx: Build context feature_name: If specified, only annotate this feature """ log_info("šŸ—ļø Annotate Features") log_info("=" * 60) log_info(f"šŸ“ Chromium source: {ctx.chromium_src}") log_info(f"šŸ“„ Features file: {ctx.get_features_yaml_path()}") commits_created, features_skipped = annotate_features( ctx, feature_filter=feature_name ) log_info("\n" + "=" * 60) if commits_created > 0: log_success(f"āœ“ Created {commits_created} commit(s)") else: log_info("No commits created (no modified files found)") if features_skipped > 0: log_info(f" Skipped {features_skipped} feature(s) with no changes") log_info("=" * 60)