summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-29 17:14:38 +0300
committerPaul Buetow <paul@buetow.org>2026-05-29 17:14:38 +0300
commite344b94d455712d9e753ef235bc6ff0e76fd91f2 (patch)
tree993268290262595107441b2a303e7797674d3e8c
parentb076c5e2ac5403de28c5d0ba44fddf216f3034ce (diff)
fix(vq): restore version-prefix parsing and stash no-op handling
-rw-r--r--internal/release/release.go17
-rw-r--r--internal/release/release_tags_test.go23
-rw-r--r--internal/sync/git_operations.go16
-rw-r--r--internal/sync/git_operations_test.go67
-rw-r--r--internal/sync/sync.go5
5 files changed, 121 insertions, 7 deletions
diff --git a/internal/release/release.go b/internal/release/release.go
index 0c8b33f..c6eca97 100644
--- a/internal/release/release.go
+++ b/internal/release/release.go
@@ -11,6 +11,7 @@ import (
"sort"
"strconv"
"strings"
+ "unicode"
"codeberg.org/snonux/gitsyncer/internal/aitool"
"codeberg.org/snonux/gitsyncer/internal/httpclient"
@@ -182,9 +183,21 @@ func compareVersions(v1, v2 string) int {
}
func parseVersionPart(part string) int {
- n, err := strconv.Atoi(part)
+ start := 0
+ if part != "" && (part[0] == '+' || part[0] == '-') {
+ start = 1
+ }
+
+ end := start
+ for end < len(part) && unicode.IsDigit(rune(part[end])) {
+ end++
+ }
+ if end == start {
+ return 0
+ }
+
+ n, err := strconv.Atoi(part[:end])
if err != nil {
- // Keep current behavior for non-numeric segments.
return 0
}
diff --git a/internal/release/release_tags_test.go b/internal/release/release_tags_test.go
index eb6d581..521fbe8 100644
--- a/internal/release/release_tags_test.go
+++ b/internal/release/release_tags_test.go
@@ -49,6 +49,25 @@ func TestParseVersionPart_NonNumericReturnsZero(t *testing.T) {
}
}
+func TestParseVersionPart_MixedNumericPrefix(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ in string
+ want int
+ }{
+ {in: "1beta", want: 1},
+ {in: "-2x", want: -2},
+ {in: "+12rc1", want: 12},
+ }
+
+ for _, tc := range testCases {
+ if got := parseVersionPart(tc.in); got != tc.want {
+ t.Fatalf("parseVersionPart(%q) = %d, want %d", tc.in, got, tc.want)
+ }
+ }
+}
+
func TestCompareVersions_NonNumericSegmentsFollowZeroFallback(t *testing.T) {
t.Parallel()
@@ -59,6 +78,10 @@ func TestCompareVersions_NonNumericSegmentsFollowZeroFallback(t *testing.T) {
if got := compareVersions("v2.beta", "v2.0"); got != 0 {
t.Fatalf("compareVersions(v2.beta, v2.0) = %d, want 0", got)
}
+
+ if got := compareVersions("v1beta.2", "v1.1"); got != 1 {
+ t.Fatalf("compareVersions(v1beta.2, v1.1) = %d, want 1", got)
+ }
}
func runGit(t *testing.T, repoPath string, args ...string) string {
diff --git a/internal/sync/git_operations.go b/internal/sync/git_operations.go
index 1acfb95..e5c68d9 100644
--- a/internal/sync/git_operations.go
+++ b/internal/sync/git_operations.go
@@ -36,10 +36,20 @@ func checkForMergeConflicts(repoPath string) (bool, string, error) {
return hasConflicts, statusStr, nil
}
-// stashChanges stashes uncommitted changes
-func stashChanges(repoPath string) error {
+// stashChanges stashes uncommitted changes.
+// Returns true only when a stash entry was created.
+func stashChanges(repoPath string) (bool, error) {
fmt.Println(" Stashing uncommitted changes...")
- return gitCommand(repoPath, "stash", "push", "-m", "gitsyncer-auto-stash").Run()
+ output, err := gitCommand(repoPath, "stash", "push", "-m", "gitsyncer-auto-stash").CombinedOutput()
+ if err != nil {
+ return false, err
+ }
+
+ if strings.Contains(string(output), "No local changes to save") {
+ return false, nil
+ }
+
+ return true, nil
}
// popStash attempts to pop the stash (used in defer)
diff --git a/internal/sync/git_operations_test.go b/internal/sync/git_operations_test.go
index b80bf0e..4ca630d 100644
--- a/internal/sync/git_operations_test.go
+++ b/internal/sync/git_operations_test.go
@@ -120,6 +120,73 @@ func TestPopStash_RestoresStashedChanges(t *testing.T) {
}
}
+func TestStashChanges_NoStashEntryReturnsFalse(t *testing.T) {
+ repoPath := t.TempDir()
+
+ runGit(t, repoPath, "init")
+ runGit(t, repoPath, "config", "user.name", "Test User")
+ runGit(t, repoPath, "config", "user.email", "test@example.com")
+
+ trackedFile := filepath.Join(repoPath, "tracked.txt")
+ if err := os.WriteFile(trackedFile, []byte("committed\n"), 0o644); err != nil {
+ t.Fatalf("write tracked file: %v", err)
+ }
+ runGit(t, repoPath, "add", "tracked.txt")
+ runGit(t, repoPath, "commit", "-m", "initial")
+
+ untrackedFile := filepath.Join(repoPath, "untracked.txt")
+ if err := os.WriteFile(untrackedFile, []byte("new\n"), 0o644); err != nil {
+ t.Fatalf("write untracked file: %v", err)
+ }
+
+ stashed, err := stashChanges(repoPath)
+ if err != nil {
+ t.Fatalf("stashChanges() unexpected error: %v", err)
+ }
+ if stashed {
+ t.Fatal("stashChanges() = true, want false when no stash entry is created")
+ }
+
+ if stashList := runGit(t, repoPath, "stash", "list"); stashList != "" {
+ t.Fatalf("expected no stash entries, got %q", stashList)
+ }
+}
+
+func TestHandleWorkingDirectoryState_NoStashEntryReturnsNotStashed(t *testing.T) {
+ workDir := t.TempDir()
+ repoName := "repo"
+ repoPath := filepath.Join(workDir, repoName)
+
+ if err := os.MkdirAll(repoPath, 0o755); err != nil {
+ t.Fatalf("mkdir repo path: %v", err)
+ }
+
+ runGit(t, repoPath, "init")
+ runGit(t, repoPath, "config", "user.name", "Test User")
+ runGit(t, repoPath, "config", "user.email", "test@example.com")
+
+ trackedFile := filepath.Join(repoPath, "tracked.txt")
+ if err := os.WriteFile(trackedFile, []byte("committed\n"), 0o644); err != nil {
+ t.Fatalf("write tracked file: %v", err)
+ }
+ runGit(t, repoPath, "add", "tracked.txt")
+ runGit(t, repoPath, "commit", "-m", "initial")
+
+ untrackedFile := filepath.Join(repoPath, "untracked.txt")
+ if err := os.WriteFile(untrackedFile, []byte("new\n"), 0o644); err != nil {
+ t.Fatalf("write untracked file: %v", err)
+ }
+
+ s := &Syncer{workDir: workDir, repoName: repoName}
+ stashed, err := s.handleWorkingDirectoryState()
+ if err != nil {
+ t.Fatalf("handleWorkingDirectoryState() unexpected error: %v", err)
+ }
+ if stashed {
+ t.Fatal("handleWorkingDirectoryState() = true, want false when stash push creates no entry")
+ }
+}
+
func runGit(t *testing.T, repoPath string, args ...string) string {
t.Helper()
diff --git a/internal/sync/sync.go b/internal/sync/sync.go
index 949cb43..048803e 100644
--- a/internal/sync/sync.go
+++ b/internal/sync/sync.go
@@ -367,10 +367,11 @@ func (s *Syncer) handleWorkingDirectoryState() (bool, error) {
}
// If we have uncommitted changes but no conflicts, try to stash them
- if err := stashChanges(repoPath); err != nil {
+ stashed, err := stashChanges(repoPath)
+ if err != nil {
return false, fmt.Errorf("failed to stash changes: %w", err)
}
- return true, nil
+ return stashed, nil
}
// checkoutBranch checks out a branch, creating it if necessary