Coverage for little_loops / cli / gitignore.py: 23%
44 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-16 13:11 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-16 13:11 -0500
1"""ll-gitignore: Suggest and apply .gitignore patterns."""
3from __future__ import annotations
5import argparse
6import sys
7from pathlib import Path
9from little_loops.cli.output import print_json
10from little_loops.cli_args import add_config_arg, add_dry_run_arg, add_json_arg, add_quiet_arg
11from little_loops.git_operations import (
12 GitignorePattern,
13 add_patterns_to_gitignore,
14 suggest_gitignore_patterns,
15)
16from little_loops.logger import Logger
17from little_loops.session_store import DEFAULT_DB_PATH, cli_event_context
20def main_gitignore() -> int:
21 """Entry point for ll-gitignore command.
23 Scan for untracked files, suggest .gitignore patterns, and optionally apply them.
25 Returns:
26 Exit code (0 = success, 1 = error)
27 """
28 with cli_event_context(DEFAULT_DB_PATH, "ll-gitignore", sys.argv[1:]):
29 parser = argparse.ArgumentParser(
30 prog="ll-gitignore",
31 description="Suggest and apply .gitignore patterns based on untracked files",
32 formatter_class=argparse.RawDescriptionHelpFormatter,
33 epilog="""
34Examples:
35 %(prog)s # Show suggestions and apply approved patterns
36 %(prog)s --dry-run # Preview suggestions without modifying .gitignore
37 %(prog)s --quiet # Suppress non-essential output
38""",
39 )
41 add_dry_run_arg(parser)
42 add_quiet_arg(parser)
43 add_config_arg(parser)
44 add_json_arg(parser)
46 args = parser.parse_args()
48 repo_root = args.config or Path.cwd()
49 logger = Logger(verbose=not args.quiet)
50 dry_run: bool = args.dry_run
52 if dry_run:
53 logger.info("[DRY RUN] Showing suggestions without modifying .gitignore")
55 suggestion = suggest_gitignore_patterns(repo_root=repo_root, logger=logger)
57 if args.json:
58 print_json(
59 {
60 "has_suggestions": suggestion.has_suggestions,
61 "summary": suggestion.summary,
62 "suggestions": [
63 {
64 "pattern": p.pattern,
65 "category": p.category,
66 "description": p.description,
67 "files_matched": p.files_matched,
68 "priority": p.priority,
69 }
70 for p in suggestion.patterns
71 ],
72 }
73 )
74 return 0
76 if not suggestion.has_suggestions:
77 logger.info("No .gitignore suggestions — your repo looks clean.")
78 return 0
80 logger.info(suggestion.summary)
82 # Display categorized suggestions
83 categories: dict[str, list[GitignorePattern]] = {}
84 for pattern in suggestion.patterns:
85 categories.setdefault(pattern.category, []).append(pattern)
87 for category, patterns in sorted(categories.items()):
88 logger.info(f"\n [{category}]")
89 for p in patterns:
90 file_count = len(p.files_matched)
91 files_label = f"{file_count} file{'s' if file_count != 1 else ''}"
92 logger.info(f" {p.pattern:<30} {p.description} ({files_label})")
94 if dry_run:
95 return 0
97 pattern_strings = [p.pattern for p in suggestion.patterns]
98 success = add_patterns_to_gitignore(pattern_strings, repo_root=repo_root, logger=logger)
100 return 0 if success else 1