-
Notifications
You must be signed in to change notification settings - Fork 19
Refactor: Extract internal modules for better organization #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| package display | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/pterm/pterm" | ||
| ) | ||
|
|
||
| // FileStatistics holds statistics about changed files | ||
| type FileStatistics struct { | ||
| StagedFiles []string | ||
| UnstagedFiles []string | ||
| UntrackedFiles []string | ||
| TotalFiles int | ||
| LinesAdded int | ||
| LinesDeleted int | ||
| } | ||
|
|
||
| // ShowFileStatistics displays file statistics with colored output | ||
| func ShowFileStatistics(stats *FileStatistics) { | ||
| pterm.DefaultSection.Println("📊 Changes Summary") | ||
|
|
||
| // Create bullet list items | ||
| bulletItems := []pterm.BulletListItem{} | ||
|
|
||
| if len(stats.StagedFiles) > 0 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 0, | ||
| Text: pterm.Green(fmt.Sprintf("✅ Staged files: %d", len(stats.StagedFiles))), | ||
| TextStyle: pterm.NewStyle(pterm.FgGreen), | ||
| BulletStyle: pterm.NewStyle(pterm.FgGreen), | ||
| }) | ||
| for i, file := range stats.StagedFiles { | ||
| if i < 5 { // Show first 5 files | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 1, | ||
| Text: file, | ||
| }) | ||
| } | ||
| } | ||
| if len(stats.StagedFiles) > 5 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 1, | ||
| Text: pterm.Gray(fmt.Sprintf("... and %d more", len(stats.StagedFiles)-5)), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| if len(stats.UnstagedFiles) > 0 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 0, | ||
| Text: pterm.Yellow(fmt.Sprintf("⚠️ Unstaged files: %d", len(stats.UnstagedFiles))), | ||
| TextStyle: pterm.NewStyle(pterm.FgYellow), | ||
| BulletStyle: pterm.NewStyle(pterm.FgYellow), | ||
| }) | ||
| for i, file := range stats.UnstagedFiles { | ||
| if i < 3 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 1, | ||
| Text: file, | ||
| }) | ||
| } | ||
| } | ||
| if len(stats.UnstagedFiles) > 3 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 1, | ||
| Text: pterm.Gray(fmt.Sprintf("... and %d more", len(stats.UnstagedFiles)-3)), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| if len(stats.UntrackedFiles) > 0 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 0, | ||
| Text: pterm.Cyan(fmt.Sprintf("📝 Untracked files: %d", len(stats.UntrackedFiles))), | ||
| TextStyle: pterm.NewStyle(pterm.FgCyan), | ||
| BulletStyle: pterm.NewStyle(pterm.FgCyan), | ||
| }) | ||
| for i, file := range stats.UntrackedFiles { | ||
| if i < 3 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 1, | ||
| Text: file, | ||
| }) | ||
| } | ||
| } | ||
| if len(stats.UntrackedFiles) > 3 { | ||
| bulletItems = append(bulletItems, pterm.BulletListItem{ | ||
| Level: 1, | ||
| Text: pterm.Gray(fmt.Sprintf("... and %d more", len(stats.UntrackedFiles)-3)), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| pterm.DefaultBulletList.WithItems(bulletItems).Render() | ||
| } | ||
|
|
||
| // ShowCommitMessage displays the commit message in a styled panel | ||
| func ShowCommitMessage(message string) { | ||
| pterm.DefaultSection.Println("📝 Generated Commit Message") | ||
|
|
||
| // Create a panel with the commit message | ||
| panel := pterm.DefaultBox. | ||
| WithTitle("Commit Message"). | ||
| WithTitleTopCenter(). | ||
| WithBoxStyle(pterm.NewStyle(pterm.FgLightGreen)). | ||
| WithHorizontalString("─"). | ||
| WithVerticalString("│"). | ||
| WithTopLeftCornerString("┌"). | ||
| WithTopRightCornerString("┐"). | ||
| WithBottomLeftCornerString("└"). | ||
| WithBottomRightCornerString("┘") | ||
|
|
||
| panel.Println(pterm.LightGreen(message)) | ||
| } | ||
|
|
||
| // ShowChangesPreview displays a preview of changes with line statistics | ||
| func ShowChangesPreview(stats *FileStatistics) { | ||
| pterm.DefaultSection.Println("🔍 Changes Preview") | ||
|
|
||
| // Create info boxes | ||
| if stats.LinesAdded > 0 || stats.LinesDeleted > 0 { | ||
| infoData := [][]string{ | ||
| {"Lines Added", pterm.Green(fmt.Sprintf("+%d", stats.LinesAdded))}, | ||
| {"Lines Deleted", pterm.Red(fmt.Sprintf("-%d", stats.LinesDeleted))}, | ||
| {"Total Files", pterm.Cyan(fmt.Sprintf("%d", stats.TotalFiles))}, | ||
| } | ||
|
|
||
| pterm.DefaultTable.WithHasHeader(false).WithData(infoData).Render() | ||
| } else { | ||
| pterm.Info.Println("No line statistics available for unstaged changes") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| package git | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "github.com/dfanso/commit-msg/src/internal/utils" | ||
| "github.com/dfanso/commit-msg/src/types" | ||
| ) | ||
|
|
||
| // IsRepository checks if a directory is a git repository | ||
| func IsRepository(path string) bool { | ||
| cmd := exec.Command("git", "-C", path, "rev-parse", "--is-inside-work-tree") | ||
| output, err := cmd.CombinedOutput() | ||
| if err != nil { | ||
| return false | ||
| } | ||
| return strings.TrimSpace(string(output)) == "true" | ||
| } | ||
|
|
||
| // GetChanges retrieves all Git changes including staged, unstaged, and untracked files | ||
| func GetChanges(config *types.RepoConfig) (string, error) { | ||
| var changes strings.Builder | ||
|
|
||
| // 1. Check for unstaged changes | ||
| cmd := exec.Command("git", "-C", config.Path, "diff", "--name-status") | ||
| output, err := cmd.Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("git diff failed: %v", err) | ||
| } | ||
|
|
||
| if len(output) > 0 { | ||
| changes.WriteString("Unstaged changes:\n") | ||
| changes.WriteString(string(output)) | ||
| changes.WriteString("\n\n") | ||
|
|
||
| // Get the content of these changes | ||
| diffCmd := exec.Command("git", "-C", config.Path, "diff") | ||
| diffOutput, err := diffCmd.Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("git diff content failed: %v", err) | ||
| } | ||
|
|
||
| changes.WriteString("Unstaged diff content:\n") | ||
| changes.WriteString(string(diffOutput)) | ||
| changes.WriteString("\n\n") | ||
| } | ||
|
|
||
| // 2. Check for staged changes | ||
| stagedCmd := exec.Command("git", "-C", config.Path, "diff", "--name-status", "--cached") | ||
| stagedOutput, err := stagedCmd.Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("git diff --cached failed: %v", err) | ||
| } | ||
|
|
||
| if len(stagedOutput) > 0 { | ||
| changes.WriteString("Staged changes:\n") | ||
| changes.WriteString(string(stagedOutput)) | ||
| changes.WriteString("\n\n") | ||
|
|
||
| // Get the content of these changes | ||
| stagedDiffCmd := exec.Command("git", "-C", config.Path, "diff", "--cached") | ||
| stagedDiffOutput, err := stagedDiffCmd.Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("git diff --cached content failed: %v", err) | ||
| } | ||
|
|
||
| changes.WriteString("Staged diff content:\n") | ||
| changes.WriteString(string(stagedDiffOutput)) | ||
| changes.WriteString("\n\n") | ||
| } | ||
|
|
||
| // 3. Check for untracked files | ||
| untrackedCmd := exec.Command("git", "-C", config.Path, "ls-files", "--others", "--exclude-standard") | ||
| untrackedOutput, err := untrackedCmd.Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("git ls-files failed: %v", err) | ||
| } | ||
|
|
||
| if len(untrackedOutput) > 0 { | ||
| changes.WriteString("Untracked files:\n") | ||
| changes.WriteString(string(untrackedOutput)) | ||
| changes.WriteString("\n\n") | ||
|
|
||
| // Try to get content of untracked files (limited to text files and smaller size) | ||
| untrackedFiles := strings.Split(strings.TrimSpace(string(untrackedOutput)), "\n") | ||
| for _, file := range untrackedFiles { | ||
| if file == "" { | ||
| continue | ||
| } | ||
|
|
||
| fullPath := filepath.Join(config.Path, file) | ||
| if utils.IsTextFile(fullPath) && utils.IsSmallFile(fullPath) { | ||
| fileContent, err := os.ReadFile(fullPath) | ||
| if err != nil { | ||
| // Log but don't fail - untracked file may have been deleted or is inaccessible | ||
| continue | ||
| } | ||
| changes.WriteString(fmt.Sprintf("Content of new file %s:\n", file)) | ||
| changes.WriteString(string(fileContent)) | ||
| changes.WriteString("\n\n") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 4. Get recent commits for context | ||
| recentCommitsCmd := exec.Command("git", "-C", config.Path, "log", "--oneline", "-n", "3") | ||
| recentCommitsOutput, err := recentCommitsCmd.Output() | ||
| if err == nil && len(recentCommitsOutput) > 0 { | ||
| changes.WriteString("Recent commits for context:\n") | ||
| changes.WriteString(string(recentCommitsOutput)) | ||
| changes.WriteString("\n") | ||
| } | ||
|
|
||
| return changes.String(), nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package stats | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os/exec" | ||
| "strings" | ||
|
|
||
| "github.com/dfanso/commit-msg/src/internal/display" | ||
| "github.com/dfanso/commit-msg/src/internal/utils" | ||
| "github.com/dfanso/commit-msg/src/types" | ||
| ) | ||
|
|
||
| // GetFileStatistics collects comprehensive file statistics from Git | ||
| func GetFileStatistics(config *types.RepoConfig) (*display.FileStatistics, error) { | ||
| stats := &display.FileStatistics{ | ||
| StagedFiles: []string{}, | ||
| UnstagedFiles: []string{}, | ||
| UntrackedFiles: []string{}, | ||
| } | ||
|
|
||
| // Get staged files | ||
| stagedCmd := exec.Command("git", "-C", config.Path, "diff", "--name-only", "--cached") | ||
| stagedOutput, err := stagedCmd.Output() | ||
| if err == nil && len(stagedOutput) > 0 { | ||
| stats.StagedFiles = strings.Split(strings.TrimSpace(string(stagedOutput)), "\n") | ||
| } | ||
|
|
||
| // Get unstaged files | ||
| unstagedCmd := exec.Command("git", "-C", config.Path, "diff", "--name-only") | ||
| unstagedOutput, err := unstagedCmd.Output() | ||
| if err == nil && len(unstagedOutput) > 0 { | ||
| stats.UnstagedFiles = strings.Split(strings.TrimSpace(string(unstagedOutput)), "\n") | ||
| } | ||
|
|
||
| // Get untracked files | ||
| untrackedCmd := exec.Command("git", "-C", config.Path, "ls-files", "--others", "--exclude-standard") | ||
| untrackedOutput, err := untrackedCmd.Output() | ||
| if err == nil && len(untrackedOutput) > 0 { | ||
| stats.UntrackedFiles = strings.Split(strings.TrimSpace(string(untrackedOutput)), "\n") | ||
| } | ||
|
|
||
| // Filter empty strings | ||
| stats.StagedFiles = utils.FilterEmpty(stats.StagedFiles) | ||
| stats.UnstagedFiles = utils.FilterEmpty(stats.UnstagedFiles) | ||
| stats.UntrackedFiles = utils.FilterEmpty(stats.UntrackedFiles) | ||
|
|
||
| stats.TotalFiles = len(stats.StagedFiles) + len(stats.UnstagedFiles) + len(stats.UntrackedFiles) | ||
|
|
||
| // Get line statistics from staged changes | ||
| if len(stats.StagedFiles) > 0 { | ||
| statCmd := exec.Command("git", "-C", config.Path, "diff", "--cached", "--numstat") | ||
| statOutput, err := statCmd.Output() | ||
| if err == nil { | ||
| lines := strings.Split(strings.TrimSpace(string(statOutput)), "\n") | ||
| for _, line := range lines { | ||
| parts := strings.Fields(line) | ||
| if len(parts) >= 2 { | ||
| if added := parts[0]; added != "-" { | ||
| var addedNum int | ||
| fmt.Sscanf(added, "%d", &addedNum) | ||
| stats.LinesAdded += addedNum | ||
| } | ||
| if deleted := parts[1]; deleted != "-" { | ||
| var deletedNum int | ||
| fmt.Sscanf(deleted, "%d", &deletedNum) | ||
| stats.LinesDeleted += deletedNum | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return stats, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package utils | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| ) | ||
|
|
||
| // NormalizePath handles both forward and backslashes | ||
| func NormalizePath(path string) string { | ||
| // Replace backslashes with forward slashes | ||
| normalized := strings.ReplaceAll(path, "\\", "/") | ||
| // Remove any trailing slash | ||
| normalized = strings.TrimSuffix(normalized, "/") | ||
| return normalized | ||
| } | ||
|
|
||
| // IsTextFile checks if a file is likely to be a text file | ||
| func IsTextFile(filename string) bool { | ||
| // List of common text file extensions | ||
| textExtensions := []string{ | ||
| ".txt", ".md", ".go", ".js", ".py", ".java", ".c", ".cpp", ".h", | ||
| ".html", ".css", ".json", ".xml", ".yaml", ".yml", ".sh", ".bash", | ||
| ".ts", ".tsx", ".jsx", ".php", ".rb", ".rs", ".dart", | ||
| } | ||
|
|
||
| ext := strings.ToLower(filepath.Ext(filename)) | ||
| for _, textExt := range textExtensions { | ||
| if ext == textExt { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| // IsSmallFile checks if a file is small enough to include in context | ||
| func IsSmallFile(filename string) bool { | ||
| const maxSize = 10 * 1024 // 10KB max | ||
|
|
||
| info, err := os.Stat(filename) | ||
| if err != nil { | ||
| return false | ||
| } | ||
|
|
||
| return info.Size() <= maxSize | ||
| } | ||
|
|
||
| // FilterEmpty removes empty strings from a slice | ||
| func FilterEmpty(slice []string) []string { | ||
| filtered := []string{} | ||
| for _, s := range slice { | ||
| if s != "" { | ||
| filtered = append(filtered, s) | ||
| } | ||
| } | ||
| return filtered | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Propagate git command errors to the caller.
The function silently ignores all errors from git commands. If git is not installed, the repository is corrupted, or there's a permission issue, the function returns empty statistics without any indication of failure. This makes debugging difficult and could mask critical issues.
Consider propagating the first encountered error:
// Get staged files stagedCmd := exec.Command("git", "-C", config.Path, "diff", "--name-only", "--cached") stagedOutput, err := stagedCmd.Output() - if err == nil && len(stagedOutput) > 0 { + if err != nil { + return nil, fmt.Errorf("failed to get staged files: %w", err) + } + if len(stagedOutput) > 0 { stats.StagedFiles = strings.Split(strings.TrimSpace(string(stagedOutput)), "\n") } // Get unstaged files unstagedCmd := exec.Command("git", "-C", config.Path, "diff", "--name-only") unstagedOutput, err := unstagedCmd.Output() - if err == nil && len(unstagedOutput) > 0 { + if err != nil { + return nil, fmt.Errorf("failed to get unstaged files: %w", err) + } + if len(unstagedOutput) > 0 { stats.UnstagedFiles = strings.Split(strings.TrimSpace(string(unstagedOutput)), "\n") } // Get untracked files untrackedCmd := exec.Command("git", "-C", config.Path, "ls-files", "--others", "--exclude-standard") untrackedOutput, err := untrackedCmd.Output() - if err == nil && len(untrackedOutput) > 0 { + if err != nil { + return nil, fmt.Errorf("failed to get untracked files: %w", err) + } + if len(untragedOutput) > 0 { stats.UntrackedFiles = strings.Split(strings.TrimSpace(string(untrackedOutput)), "\n") }🤖 Prompt for AI Agents