summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-29 17:07:22 +0300
committerPaul Buetow <paul@buetow.org>2026-05-29 17:07:22 +0300
commitb076c5e2ac5403de28c5d0ba44fddf216f3034ce (patch)
treea979b40edc269005879d6be2c4a2fced4210156c
parenta956a672859e92190149506197ad0fa682e964e2 (diff)
fix(vq): handle ignored stash/pop and parse errors explicitly
-rw-r--r--internal/release/release.go15
-rw-r--r--internal/release/release_tags_test.go20
-rw-r--r--internal/sync/git_operations.go9
-rw-r--r--internal/sync/git_operations_test.go43
-rw-r--r--internal/sync/sync.go6
5 files changed, 88 insertions, 5 deletions
diff --git a/internal/release/release.go b/internal/release/release.go
index 9af3392..0c8b33f 100644
--- a/internal/release/release.go
+++ b/internal/release/release.go
@@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"sort"
+ "strconv"
"strings"
"codeberg.org/snonux/gitsyncer/internal/aitool"
@@ -164,10 +165,10 @@ func compareVersions(v1, v2 string) int {
var n1, n2 int
if i < len(parts1) {
- fmt.Sscanf(parts1[i], "%d", &n1)
+ n1 = parseVersionPart(parts1[i])
}
if i < len(parts2) {
- fmt.Sscanf(parts2[i], "%d", &n2)
+ n2 = parseVersionPart(parts2[i])
}
if n1 < n2 {
@@ -180,6 +181,16 @@ func compareVersions(v1, v2 string) int {
return 0
}
+func parseVersionPart(part string) int {
+ n, err := strconv.Atoi(part)
+ if err != nil {
+ // Keep current behavior for non-numeric segments.
+ return 0
+ }
+
+ return n
+}
+
// GetCommitsSinceTag gets all commits since a specific tag
func (m *Manager) GetCommitsSinceTag(repoPath, fromTag, toTag string) ([]string, error) {
// Use git log to get commits between tags
diff --git a/internal/release/release_tags_test.go b/internal/release/release_tags_test.go
index 334c165..eb6d581 100644
--- a/internal/release/release_tags_test.go
+++ b/internal/release/release_tags_test.go
@@ -41,6 +41,26 @@ func TestGetLocalTags_FiltersToStrictVersionTags(t *testing.T) {
}
}
+func TestParseVersionPart_NonNumericReturnsZero(t *testing.T) {
+ t.Parallel()
+
+ if got := parseVersionPart("abc"); got != 0 {
+ t.Fatalf("parseVersionPart(non-numeric) = %d, want 0", got)
+ }
+}
+
+func TestCompareVersions_NonNumericSegmentsFollowZeroFallback(t *testing.T) {
+ t.Parallel()
+
+ if got := compareVersions("v1.alpha.1", "v1.0.2"); got != -1 {
+ t.Fatalf("compareVersions(v1.alpha.1, v1.0.2) = %d, want -1", got)
+ }
+
+ if got := compareVersions("v2.beta", "v2.0"); got != 0 {
+ t.Fatalf("compareVersions(v2.beta, v2.0) = %d, want 0", got)
+ }
+}
+
func runGit(t *testing.T, repoPath string, args ...string) string {
t.Helper()
diff --git a/internal/sync/git_operations.go b/internal/sync/git_operations.go
index 08eea34..1acfb95 100644
--- a/internal/sync/git_operations.go
+++ b/internal/sync/git_operations.go
@@ -43,8 +43,13 @@ func stashChanges(repoPath string) error {
}
// popStash attempts to pop the stash (used in defer)
-func popStash(repoPath string) {
- gitCommand(repoPath, "stash", "pop").Run()
+func popStash(repoPath string) error {
+ output, err := gitCommand(repoPath, "stash", "pop").CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("failed to restore stashed changes: %w\n%s", err, strings.TrimSpace(string(output)))
+ }
+
+ return nil
}
// mergeBranch merges a branch from a remote
diff --git a/internal/sync/git_operations_test.go b/internal/sync/git_operations_test.go
index e696fb7..b80bf0e 100644
--- a/internal/sync/git_operations_test.go
+++ b/internal/sync/git_operations_test.go
@@ -1,7 +1,9 @@
package sync
import (
+ "os"
"os/exec"
+ "path/filepath"
"strings"
"testing"
)
@@ -77,6 +79,47 @@ func TestGetTagCommitHash_LocalTagMissingReturnsError(t *testing.T) {
}
}
+func TestPopStash_NoStashReturnsError(t *testing.T) {
+ repoPath := t.TempDir()
+ runGit(t, repoPath, "init")
+
+ if err := popStash(repoPath); err == nil {
+ t.Fatal("expected error when popping stash with no entries")
+ }
+}
+
+func TestPopStash_RestoresStashedChanges(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("first\n"), 0o644); err != nil {
+ t.Fatalf("write tracked file: %v", err)
+ }
+ runGit(t, repoPath, "add", "tracked.txt")
+ runGit(t, repoPath, "commit", "-m", "initial")
+
+ if err := os.WriteFile(trackedFile, []byte("second\n"), 0o644); err != nil {
+ t.Fatalf("update tracked file: %v", err)
+ }
+ runGit(t, repoPath, "stash", "push", "-m", "test-stash")
+
+ if err := popStash(repoPath); err != nil {
+ t.Fatalf("expected popStash to succeed, got error: %v", err)
+ }
+
+ content, err := os.ReadFile(trackedFile)
+ if err != nil {
+ t.Fatalf("read tracked file: %v", err)
+ }
+ if string(content) != "second\n" {
+ t.Fatalf("expected stashed content to be restored, got %q", string(content))
+ }
+}
+
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 2f05110..949cb43 100644
--- a/internal/sync/sync.go
+++ b/internal/sync/sync.go
@@ -324,7 +324,11 @@ func (s *Syncer) syncBranch(branch string, remotes map[string]*config.Organizati
return err
}
if stashed {
- defer popStash(repoPath)
+ defer func() {
+ if err := popStash(repoPath); err != nil {
+ fmt.Printf(" Warning: %v\n", err)
+ }
+ }()
}
// Create or checkout the branch