Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/ai/cost_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"time"

"github.com/thomas-vilte/matecommit/internal/cache"
"github.com/thomas-vilte/matecommit/internal/cost"
"github.com/thomas-vilte/matecommit/internal/errors"
"github.com/thomas-vilte/matecommit/internal/models"
"github.com/thomas-vilte/matecommit/internal/services/cost"
"github.com/thomas-vilte/matecommit/internal/services/routing"
"github.com/thomas-vilte/matecommit/internal/routing"
)

type ConfirmationCallback func(result ConfirmationResult) (choice string, proceed bool)
Expand Down
2 changes: 1 addition & 1 deletion internal/ai/cost_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"time"

"github.com/stretchr/testify/mock"
"github.com/thomas-vilte/matecommit/internal/cost"
"github.com/thomas-vilte/matecommit/internal/models"
"github.com/thomas-vilte/matecommit/internal/services/cost"
)

type mockProvider struct {
Expand Down
36 changes: 36 additions & 0 deletions internal/ai/gemini/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,39 @@ func extractTextFromMap(respMap map[string]interface{}) string {

return result.String()
}

// CleanLabels cleans and validates labels, keeping only the allowed ones.
// It accepts a list of labels to clean and a list of available labels from the repository.
// If availableLabels is empty, it falls back to a default list of common labels.
func CleanLabels(labels []string, availableLabels []string) []string {
allowedLabels := make(map[string]bool)

if len(availableLabels) > 0 {
for _, l := range availableLabels {
allowedLabels[strings.ToLower(l)] = true
}
} else {
// Fallback to default list if no repo labels provided
defaultLabels := []string{
"feature", "fix", "refactor", "docs", "test", "infra",
"enhancement", "bug", "good first issue", "help wanted",
"chore", "performance", "security", "tech-debt", "breaking-change",
}
for _, l := range defaultLabels {
allowedLabels[l] = true
}
}

cleaned := make([]string, 0)
seen := make(map[string]bool)

for _, label := range labels {
trimmed := strings.TrimSpace(strings.ToLower(label))
if trimmed != "" && allowedLabels[trimmed] && !seen[trimmed] {
cleaned = append(cleaned, trimmed)
seen[trimmed] = true
}
}

return cleaned
}
41 changes: 0 additions & 41 deletions internal/ai/gemini/helpers.go

This file was deleted.

2 changes: 1 addition & 1 deletion internal/commands/stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

"github.com/fatih/color"
"github.com/thomas-vilte/matecommit/internal/config"
"github.com/thomas-vilte/matecommit/internal/cost"
"github.com/thomas-vilte/matecommit/internal/i18n"
"github.com/thomas-vilte/matecommit/internal/services/cost"
"github.com/urfave/cli/v3"
)

Expand Down
2 changes: 1 addition & 1 deletion internal/commands/stats/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thomas-vilte/matecommit/internal/cost"
"github.com/thomas-vilte/matecommit/internal/i18n"
"github.com/thomas-vilte/matecommit/internal/services/cost"
)

func TestNewStatsCommand(t *testing.T) {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 17 additions & 0 deletions internal/git/git_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,23 @@ func (s *GitService) AddFileToStaging(ctx context.Context, file string) error {
absFile = file
}

// If the file has no pending change relative to the index (e.g. it was
// already staged by the user via a manual "git rm" or "git add"), "git
// add" has nothing to do — and once the file no longer exists on disk
// at all (a staged deletion), running it anyway fails with "pathspec
// did not match any files" instead of being the harmless no-op it
// should be.
precheckCmd := exec.CommandContext(ctx, "git", "status", "--porcelain", "--", absFile)
precheckCmd.Dir = repoRoot
precheckOutput, _ := precheckCmd.Output()
precheckLine := strings.TrimRight(string(precheckOutput), "\n")
if len(precheckLine) > 1 && precheckLine[1] == ' ' {
log.Debug("file already fully staged, skipping git add",
"file", file,
"status", precheckLine)
return nil
}

log.Debug("adding file to staging",
"file", file,
"abs_file", absFile,
Expand Down
38 changes: 38 additions & 0 deletions internal/git/git_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,44 @@ func TestAddFileToStaging(t *testing.T) {
}
})

t.Run("Already fully staged deleted file is a no-op", func(t *testing.T) {
// Reproduces a real failure: if a file was already staged for
// deletion by the user directly (e.g. "git rm"), it no longer
// exists on disk or in the index diff, so running "git add" on it
// again used to fail with "pathspec did not match any files"
// instead of being the harmless no-op it should be.
tempDir := setupTestRepo(t)
defer cleanupTestRepo(t, tempDir)

service := NewGitService()
testFile := "already-staged-deletion.txt"

if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
return
}
if err := service.AddFileToStaging(context.Background(), testFile); err != nil {
t.Fatalf("Error al agregar archivo al staging: %v", err)
}
if err := service.CreateCommit(context.Background(), "Commit inicial"); err != nil {
t.Fatalf("Error al crear commit inicial: %v", err)
}

rmCmd := exec.Command("git", "rm", testFile)
if err := rmCmd.Run(); err != nil {
t.Fatalf("Error al hacer git rm: %v", err)
}

if err := service.AddFileToStaging(context.Background(), testFile); err != nil {
t.Fatalf("AddFileToStaging debería ser un no-op silencioso, pero devolvió: %v", err)
}

cmd := exec.Command("git", "diff", "--cached", "--name-status")
output, _ := cmd.Output()
if !strings.Contains(string(output), "D\t"+testFile) {
t.Error("La eliminación previamente stageada se perdió")
}
})

t.Run("Non-existent file", func(t *testing.T) {
tempDir := setupTestRepo(t)
defer cleanupTestRepo(t, tempDir)
Expand Down
Loading