Skip to content

Commit d0457db

Browse files
author
Test User
committed
✨ feat: enhance commit process with CLI options
✨ feat: enhance commit process with CLI options ✨ Features: - Introduced CLI options for auto-commit, combining commits, and enabling debug logging - Implemented main command function using Click for better CLI interaction - Refactored commit processing logic to support batch processing 📜 Logging: - Added logging setup with Rich for improved output formatting - Implemented debug logging for better traceability during execution 🔧 Dependencies: - Updated dependencies to include Click for CLI functionality Enhanced commit process with new CLI options and improved logging
1 parent 6d4c0ef commit d0457db

File tree

5 files changed

+183
-71
lines changed

5 files changed

+183
-71
lines changed

commitloom/__main__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,25 @@
44

55
import os
66

7+
import click
78
from dotenv import load_dotenv
89

910
# Load environment variables before any imports
1011
env_path = os.path.join(os.path.dirname(__file__), "..", ".env")
1112
load_dotenv(dotenv_path=env_path)
1213

13-
from .cli.main import main
14+
from .cli.main import CommitLoom
15+
16+
17+
@click.command()
18+
@click.option("-y", "--yes", is_flag=True, help="Skip all confirmation prompts")
19+
@click.option("-c", "--combine", is_flag=True, help="Combine all changes into a single commit")
20+
@click.option("-d", "--debug", is_flag=True, help="Enable debug logging")
21+
def main(yes: bool, combine: bool, debug: bool) -> None:
22+
"""Create structured git commits with AI-generated messages."""
23+
loom = CommitLoom()
24+
loom.run(auto_commit=yes, combine_commits=combine, debug=debug)
25+
1426

1527
if __name__ == "__main__":
1628
main()

commitloom/cli/console.py

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Console output formatting and user interaction."""
22

33

4+
import logging
45
from unittest.mock import MagicMock
56

67
from rich.console import Console
8+
from rich.logging import RichHandler
79
from rich.panel import Panel
810
from rich.progress import (
911
BarColumn,
@@ -27,6 +29,57 @@
2729
from ..services.ai_service import TokenUsage
2830

2931
console = Console()
32+
logger = logging.getLogger("commitloom")
33+
34+
35+
def setup_logging(debug: bool = False):
36+
"""Configure logging with optional debug mode."""
37+
level = logging.DEBUG if debug else logging.INFO
38+
39+
# Configure rich handler
40+
rich_handler = RichHandler(rich_tracebacks=True, markup=True, show_time=debug, show_path=debug)
41+
rich_handler.setLevel(level)
42+
43+
# Configure logger
44+
logger.setLevel(level)
45+
logger.addHandler(rich_handler)
46+
47+
if debug:
48+
logger.debug("Debug mode enabled")
49+
50+
51+
def print_debug(message: str, exc_info: bool = False) -> None:
52+
"""Print debug message if debug mode is enabled.
53+
54+
Args:
55+
message: The message to print
56+
exc_info: Whether to include exception info in the log
57+
"""
58+
logger.debug(f"🔍 {message}", exc_info=exc_info)
59+
60+
61+
def print_info(message: str) -> None:
62+
"""Print info message."""
63+
logger.info(f"ℹ️ {message}")
64+
console.print(f"\n[bold blue]ℹ️ {message}[/bold blue]")
65+
66+
67+
def print_warning(message: str) -> None:
68+
"""Print warning message."""
69+
logger.warning(f"⚠️ {message}")
70+
console.print(f"\n[bold yellow]⚠️ {message}[/bold yellow]")
71+
72+
73+
def print_error(message: str) -> None:
74+
"""Print error message."""
75+
logger.error(f"❌ {message}")
76+
console.print(f"\n[bold red]❌ {message}[/bold red]")
77+
78+
79+
def print_success(message: str) -> None:
80+
"""Print success message."""
81+
logger.info(f"✅ {message}")
82+
console.print(f"\n[bold green]✅ {message}[/bold green]")
3083

3184

3285
def create_progress() -> Progress:
@@ -42,9 +95,7 @@ def create_progress() -> Progress:
4295

4396
def print_changed_files(files: list[GitFile]) -> None:
4497
"""Print list of changed files."""
45-
console.print(
46-
"\n[bold blue]📜 Changes detected in the following files:[/bold blue]"
47-
)
98+
console.print("\n[bold blue]📜 Changes detected in the following files:[/bold blue]")
4899
for file in files:
49100
console.print(f" - [cyan]{file.path}[/cyan]")
50101

@@ -66,7 +117,7 @@ def print_warnings(warnings: list[AnalyzerWarning] | CommitAnalysis) -> None:
66117
icon = "🔴" if warning.level == WarningLevel.HIGH else "🟡"
67118
console.print(f"{icon} {warning.message}")
68119

69-
if 'analysis' in locals():
120+
if "analysis" in locals():
70121
console.print("\n[cyan]📊 Commit Statistics:[/cyan]")
71122
console.print(f" • Estimated tokens: {analysis.estimated_tokens:,}")
72123
console.print(f" • Estimated cost: €{analysis.estimated_cost:.4f}")
@@ -75,9 +126,7 @@ def print_warnings(warnings: list[AnalyzerWarning] | CommitAnalysis) -> None:
75126

76127
def print_batch_start(batch_num: int, total_batches: int, files: list[GitFile]) -> None:
77128
"""Print information about starting a new batch."""
78-
console.print(
79-
f"\n[bold blue]📦 Processing Batch {batch_num}/{total_batches}[/bold blue]"
80-
)
129+
console.print(f"\n[bold blue]📦 Processing Batch {batch_num}/{total_batches}[/bold blue]")
81130
console.print("[cyan]Files in this batch:[/cyan]")
82131
for file in files:
83132
console.print(f" - [dim]{file.path}[/dim]")
@@ -147,32 +196,8 @@ def confirm_batch_continue() -> bool:
147196

148197
def select_commit_strategy() -> str:
149198
"""Ask user how they want to handle multiple commits."""
150-
console.print(
151-
"\n[bold blue]🤔 How would you like to handle the commits?[/bold blue]"
152-
)
153-
return Prompt.ask(
154-
"Choose strategy", choices=["individual", "combined"], default="individual"
155-
)
156-
157-
158-
def print_success(message: str) -> None:
159-
"""Print success message."""
160-
console.print(f"\n[bold green]✅ {message}[/bold green]")
161-
162-
163-
def print_error(message: str) -> None:
164-
"""Print error message."""
165-
console.print(f"\n[bold red]❌ {message}[/bold red]")
166-
167-
168-
def print_info(message: str) -> None:
169-
"""Print info message."""
170-
console.print(f"\n[bold blue]ℹ️ {message}[/bold blue]")
171-
172-
173-
def print_warning(message: str) -> None:
174-
"""Print warning message."""
175-
console.print(f"\n[bold yellow]⚠️ {message}[/bold yellow]")
199+
console.print("\n[bold blue]🤔 How would you like to handle the commits?[/bold blue]")
200+
return Prompt.ask("Choose strategy", choices=["individual", "combined"], default="individual")
176201

177202

178203
def print_analysis(analysis: CommitAnalysis | MagicMock, files: list[GitFile]) -> None:

commitloom/cli/main.py

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -144,41 +144,91 @@ def _create_batches(self, changed_files: list[GitFile]) -> list[list[GitFile]]:
144144
def process_files_in_batches(
145145
self, changed_files: list[GitFile], auto_commit: bool = False
146146
) -> list[dict]:
147-
"""Process files in batches if needed."""
147+
"""Process files in batches if needed.
148+
149+
This method implements a queue-based approach to process files in batches:
150+
1. First unstages all files to start from a clean state (only if multiple batches)
151+
2. Creates batches of files to process
152+
3. For each batch:
153+
- Stages only the files in the current batch
154+
- Processes the batch
155+
- If successful, keeps the commit and moves to next batch
156+
- If failed, unstages and stops
157+
"""
158+
console.print_debug("Starting batch processing")
159+
160+
# Create work queue
161+
console.print_debug(f"Creating batches from {len(changed_files)} files")
148162
batches = self._create_batches(changed_files)
149163
if not batches:
164+
console.print_debug("No valid files to process")
150165
return []
151166

152-
results = []
167+
# Print batch processing plan
168+
total_files = len(changed_files)
153169
total_batches = len(batches)
154-
155170
console.print_info("\nProcessing files in batches...")
156-
console.print_batch_summary(len(changed_files), total_batches)
171+
console.print_batch_summary(total_files, total_batches)
157172

158-
try:
159-
for i, batch in enumerate(batches, 1):
160-
# Stage only the current batch files
173+
# Start with a clean state only if we have multiple batches
174+
if total_batches > 1:
175+
console.print_debug("Multiple batches detected, resetting staged changes")
176+
self.git.reset_staged_changes()
177+
else:
178+
console.print_debug("Single batch detected, proceeding without reset")
179+
180+
# Process each batch atomically
181+
results = []
182+
for batch_num, batch in enumerate(batches, 1):
183+
try:
184+
# 1. Stage current batch
161185
batch_files = [f.path for f in batch]
186+
console.print_debug(
187+
f"Staging files for batch {batch_num}: {', '.join(batch_files)}"
188+
)
162189
self.git.stage_files(batch_files)
163-
164-
# Process current batch
165-
console.print_batch_start(i, total_batches, batch)
166-
result = self._handle_batch(batch, i, total_batches, auto_commit, False)
167-
190+
console.print_batch_start(batch_num, total_batches, batch)
191+
192+
# 2. Process batch
193+
console.print_debug(f"Processing batch {batch_num}/{total_batches}")
194+
result = self._handle_batch(
195+
batch=batch,
196+
batch_num=batch_num,
197+
total_batches=total_batches,
198+
auto_commit=auto_commit,
199+
combine_commits=False,
200+
)
201+
202+
# 3. Handle result
168203
if result:
204+
# Batch processed successfully
205+
console.print_debug(f"Batch {batch_num} processed successfully")
169206
results.append(result)
170-
# Only unstage if there are more batches to process
171-
if i < total_batches:
207+
# Clean staged files if more batches pending
208+
if batch_num < total_batches:
209+
console.print_debug("Cleaning staged files for next batch")
172210
self.git.reset_staged_changes()
173211
else:
212+
# Batch processing was cancelled or failed
213+
console.print_debug(f"Batch {batch_num} processing cancelled or failed")
174214
# _handle_batch already called reset_staged_changes
175215
break
176216

177-
return results
178-
except GitError as e:
179-
console.print_error(f"Error during batch processing: {str(e)}")
180-
self.git.reset_staged_changes()
181-
return []
217+
except GitError as e:
218+
console.print_error(f"Error processing batch {batch_num}: {str(e)}")
219+
console.print_debug(f"Stack trace for batch {batch_num} error:", exc_info=True)
220+
self.git.reset_staged_changes()
221+
if not auto_commit and not console.confirm_action(
222+
"Continue with remaining batches?"
223+
):
224+
console.print_debug("User chose to stop batch processing after error")
225+
break
226+
console.print_debug("Continuing with next batch after error")
227+
228+
console.print_debug(
229+
f"Batch processing completed. Processed {len(results)}/{total_batches} batches"
230+
)
231+
return results
182232

183233
def _create_combined_commit(self, batches: list[dict]) -> None:
184234
"""Create a combined commit from all batches."""
@@ -214,18 +264,30 @@ def _create_combined_commit(self, batches: list[dict]) -> None:
214264
except GitError as e:
215265
console.print_error(f"Failed to create commit: {str(e)}")
216266

217-
def run(self, auto_commit: bool = False, combine_commits: bool = False) -> None:
218-
"""Run the main application logic."""
267+
def run(
268+
self, auto_commit: bool = False, combine_commits: bool = False, debug: bool = False
269+
) -> None:
270+
"""Run the commit creation process.
271+
272+
Args:
273+
auto_commit: Whether to skip confirmation prompts
274+
combine_commits: Whether to combine all changes into a single commit
275+
debug: Whether to enable debug logging
276+
"""
219277
try:
220-
console.print_info("Analyzing your changes...")
278+
# Setup logging
279+
console.setup_logging(debug)
280+
console.print_debug("Starting CommitLoom")
221281

222282
# Get and validate changed files
283+
console.print_info("Analyzing your changes...")
223284
changed_files = self.git.get_changed_files()
224285
if not changed_files:
225286
console.print_error("No changes detected in the staging area.")
226287
return
227288

228289
# Get diff and analyze complexity
290+
console.print_debug("Getting diff and analyzing complexity")
229291
diff = self.git.get_diff(changed_files)
230292
analysis = self.analyzer.analyze_diff_complexity(diff, changed_files)
231293

@@ -238,23 +300,17 @@ def run(self, auto_commit: bool = False, combine_commits: bool = False) -> None:
238300

239301
# Process files in batches if needed
240302
if len(changed_files) > config.max_files_threshold:
241-
# Save current state and unstage all files
242-
self.git.stash_changes()
243-
self.git.reset_staged_changes()
303+
console.print_debug("Processing files in batches")
304+
batches = self.process_files_in_batches(changed_files, auto_commit)
305+
if not batches:
306+
return
244307

245-
try:
246-
# Process files in batches
247-
batches = self.process_files_in_batches(changed_files, auto_commit)
248-
if not batches:
249-
return
250-
251-
if combine_commits:
252-
self._create_combined_commit(batches)
253-
finally:
254-
# Restore any stashed changes
255-
self.git.pop_stashed_changes()
308+
if combine_commits:
309+
console.print_debug("Combining commits")
310+
self._create_combined_commit(batches)
256311
else:
257312
# Process as single commit
313+
console.print_debug("Processing as single commit")
258314
suggestion, usage = self.ai_service.generate_commit_message(diff, changed_files)
259315
console.print_info("\nGenerated Commit Message:")
260316
console.print_commit_message(suggestion.format_body())
@@ -268,11 +324,15 @@ def run(self, auto_commit: bool = False, combine_commits: bool = False) -> None:
268324

269325
except GitError as e:
270326
console.print_error(f"An error occurred: {str(e)}")
327+
if debug:
328+
console.print_debug("Git error details:", exc_info=True)
271329
except KeyboardInterrupt:
272330
console.print_warning("\nOperation cancelled by user")
273331
self.git.reset_staged_changes()
274332
except Exception as e:
275333
console.print_error(f"An unexpected error occurred: {str(e)}")
334+
if debug:
335+
console.print_debug("Unexpected error details:", exc_info=True)
276336
self.git.reset_staged_changes()
277337
raise
278338

poetry.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ python = "^3.11"
3838
python-dotenv = "^1.0.1"
3939
rich = "^13.9.4"
4040
requests = "^2.32.3"
41+
click = "^8.1.7"
4142

4243
[tool.poetry.group.dev.dependencies]
4344
pytest = "^8.3.4"

0 commit comments

Comments
 (0)