Repository: lima-vm/lima

Issue:
Lima can't parse version if built from tag with dirty checkout
### Description

Scenario:
1. checkout tag
2. apply patch using `patch` command
3. build
4. run integration tests

Error:
```
time="2025-04-28T17:17:27Z" level=fatal msg="failed to validate YAML file \"D:\\\\a\\\\qcw\\\\qcw\\\\lima-release\\\\templates\\\\experimental\\\\default-windows.yaml\": can't parse builtin Lima version \"v1.1.0.m\": strconv.ParseInt: parsing \"0.m\": invalid syntax"
```

I added 1.1.0 tag artificially here to have no issues in my development build (no tags fetched) with lima version being too low.

The issue will go away if I tag the previous commit to my build, because ".m" would be in the part cut by splitting on '-' symbol.

I believe that building from a tagged commit with local changes should still result in a working binary.

Hints from issue comments:
@afbjorklund: > I added 1.1.0 tag artificially here to have no issues in my development build (no tags fetched)

@arixmkii : why were the tags missing? There seem to be others with the same issue, running into the `--always`

`can't parse builtin Lima version "edec9bc1": edec9bc1 is not in dotted-tri format`

I thought the git default was to fetch all tags, that are on the branch that is currently being fetched (e.g. master)?

Maybe it should just fail instead: `fatal: No names found, cannot describe anything.`@jandubois: > I thought the git default was to fetch all tags, that are on the branch that is currently being fetched

It only fetches "annotated" tags by default. Our tags are "lightweight tags" only and must be fetched explicitly.@afbjorklund: I get all tags (that are on the branch) with `git fetch` and friends, but I do have to use `--tags` with `git describe` (to respect lightweight tags)

But I think you can configure your git to do `--no-tags` by default, or if you do a shallow clone (depth 1) you might also miss it...@arixmkii: > or if you do a shallow clone (depth 1)

My scenario was a shallow clone with patches on top. Tag was added to the only commit fetched. 

Solution patch:
diff --git a/pkg/version/versionutil/versionutil.go b/pkg/version/versionutil/versionutil.go
index b4390fe7..36efceba 100644
--- a/pkg/version/versionutil/versionutil.go
+++ b/pkg/version/versionutil/versionutil.go
@@ -11,10 +11,12 @@ import (
 
 // Parse parses a Lima version string by removing the leading "v" character and
 // stripping everything from the first "-" forward (which are `git describe` artifacts and
-// not semver pre-release markers). So "v0.19.1-16-gf3dc6ed.m" will be parsed as "0.19.1".
+// not semver pre-release markers) or cutting ".m" suffix from exact version.
+// So "v0.19.1-16-gf3dc6ed.m" will be parsed as "0.19.1".
 func Parse(version string) (*semver.Version, error) {
 	version = strings.TrimPrefix(version, "v")
 	version, _, _ = strings.Cut(version, "-")
+	version = strings.TrimSuffix(version, ".m")
 	return semver.NewVersion(version)
 }
 



Test patch:
diff --git a/pkg/version/versionutil/versionutil_test.go b/pkg/version/versionutil/versionutil_test.go
index d6526ae8..620efc82 100644
--- a/pkg/version/versionutil/versionutil_test.go
+++ b/pkg/version/versionutil/versionutil_test.go
@@ -27,3 +27,17 @@ func TestGreaterEqual(t *testing.T) {
 	assert.Equal(t, GreaterEqual("0.2.0", "0.1.0"), true)
 	assert.Equal(t, GreaterEqual("abacab", "0.1.0"), true)
 }
+
+func TestParse(t *testing.T) {
+	v1, err1 := Parse("v0.19.1-16-gf3dc6ed.m")
+	assert.NilError(t, err1)
+	assert.Equal(t, v1.Major, int64(0))
+	assert.Equal(t, v1.Minor, int64(19))
+	assert.Equal(t, v1.Patch, int64(1))
+
+	v2, err2 := Parse("v0.19.1.m")
+	assert.NilError(t, err2)
+	assert.Equal(t, v2.Major, int64(0))
+	assert.Equal(t, v2.Minor, int64(19))
+	assert.Equal(t, v2.Patch, int64(1))
+}