detached d35750b

fix(push): warn when git has no tags and version falls back to 'latest'

commit
d35750bd3d09b3d2600b05bc1bd689ebc8b0e675
author
bnema · · 2026-02-24 21:53 UTC
parent
c357053
signature
unsigned
2 files · +52 -1
M internal/adapters/in/cli/push.go +4 -1
@@ -712,9 +712,12 @@ func parseImageRef(image string) (registry, name, tag string) {
712712 }
713713
714714 // getGitVersion returns git describe output, or empty string if unavailable.
715+// When it falls back it prints a warning to stderr so the user knows the
716+// image will be tagged "latest" rather than a real version.
715717 func getGitVersion(ctx context.Context) string {
716- out, err := exec.CommandContext(ctx, "git", "describe", "--tags", "--dirty").Output()
718+ out, err := exec.CommandContext(ctx, "git", "describe", "--tags", "--dirty").Output() // #nosec G204
717719 if err != nil {
720+ fmt.Fprintln(os.Stderr, "Warning: no git tags found — image version will be 'latest'. Tag your repo to get versioned images.")
718721 return ""
719722 }
720723 return strings.TrimSpace(string(out))
@@ -712,9 +712,12 @@ func parseImageRef(image string) (registry, name, tag string) {
712 }712 }
713 713
714 // getGitVersion returns git describe output, or empty string if unavailable.714 // getGitVersion returns git describe output, or empty string if unavailable.
715+// When it falls back it prints a warning to stderr so the user knows the
716+// image will be tagged "latest" rather than a real version.
715 func getGitVersion(ctx context.Context) string {717 func getGitVersion(ctx context.Context) string {
716- out, err := exec.CommandContext(ctx, "git", "describe", "--tags", "--dirty").Output()718+ out, err := exec.CommandContext(ctx, "git", "describe", "--tags", "--dirty").Output() // #nosec G204
717 if err != nil {719 if err != nil {
720+ fmt.Fprintln(os.Stderr, "Warning: no git tags found — image version will be 'latest'. Tag your repo to get versioned images.")
718 return ""721 return ""
719 }722 }
720 return strings.TrimSpace(string(out))723 return strings.TrimSpace(string(out))
M internal/adapters/in/cli/push_test.go +48 -0
@@ -1,7 +1,10 @@
11 package cli
22
33 import (
4+ "context"
5+ "io"
46 "os"
7+ "os/exec"
58 "path/filepath"
69 "strings"
710 "testing"
@@ -336,6 +339,51 @@ func TestSplitLabelPairs(t *testing.T) {
336339 }
337340 }
338341
342+func TestGetGitVersionNoTagsReturnsFallbackAndWarns(t *testing.T) {
343+ ctx := context.Background()
344+
345+ // Create a temp dir with a git repo that has no tags
346+ tmpDir := t.TempDir()
347+ if err := exec.Command("git", "-C", tmpDir, "init").Run(); err != nil { // #nosec G204
348+ t.Skipf("git init failed: %v", err)
349+ }
350+ // Need at least one commit so git describe has something to describe
351+ if err := exec.Command("git", "-C", tmpDir, "-c", "user.email=test@test.com", "-c", "user.name=Test", "commit", "--allow-empty", "-m", "init").Run(); err != nil { // #nosec G204
352+ t.Skipf("git commit failed: %v", err)
353+ }
354+
355+ // Change to the tmpDir for this test so getGitVersion uses the tag-less repo
356+ origDir, err := os.Getwd()
357+ if err != nil {
358+ t.Fatal(err)
359+ }
360+ if err := os.Chdir(tmpDir); err != nil { // #nosec G204
361+ t.Fatal(err)
362+ }
363+ defer os.Chdir(origDir) // #nosec G204
364+
365+ // Redirect stderr to capture the warning
366+ origStderr := os.Stderr
367+ r, w, err := os.Pipe()
368+ if err != nil {
369+ t.Fatal(err)
370+ }
371+ os.Stderr = w
372+
373+ v := getGitVersion(ctx)
374+
375+ w.Close()
376+ os.Stderr = origStderr
377+ stderrOutput, _ := io.ReadAll(r)
378+
379+ // Should return "" (fallback to "latest" handled by determineVersion)
380+ assert.Equal(t, "", v, "expected empty string fallback when no git tags exist")
381+
382+ // Should have printed a warning to stderr
383+ assert.Contains(t, string(stderrOutput), "latest",
384+ "expected a warning about 'latest' fallback on stderr")
385+}
386+
339387 func TestParseLabelPair(t *testing.T) {
340388 tests := []struct {
341389 name string
@@ -1,7 +1,10 @@
1 package cli1 package cli
2 2
3 import (3 import (
4+ "context"
5+ "io"
4 "os"6 "os"
7+ "os/exec"
5 "path/filepath"8 "path/filepath"
6 "strings"9 "strings"
7 "testing"10 "testing"
@@ -336,6 +339,51 @@ func TestSplitLabelPairs(t *testing.T) {
336 }339 }
337 }340 }
338 341
342+func TestGetGitVersionNoTagsReturnsFallbackAndWarns(t *testing.T) {
343+ ctx := context.Background()
344+
345+ // Create a temp dir with a git repo that has no tags
346+ tmpDir := t.TempDir()
347+ if err := exec.Command("git", "-C", tmpDir, "init").Run(); err != nil { // #nosec G204
348+ t.Skipf("git init failed: %v", err)
349+ }
350+ // Need at least one commit so git describe has something to describe
351+ if err := exec.Command("git", "-C", tmpDir, "-c", "user.email=test@test.com", "-c", "user.name=Test", "commit", "--allow-empty", "-m", "init").Run(); err != nil { // #nosec G204
352+ t.Skipf("git commit failed: %v", err)
353+ }
354+
355+ // Change to the tmpDir for this test so getGitVersion uses the tag-less repo
356+ origDir, err := os.Getwd()
357+ if err != nil {
358+ t.Fatal(err)
359+ }
360+ if err := os.Chdir(tmpDir); err != nil { // #nosec G204
361+ t.Fatal(err)
362+ }
363+ defer os.Chdir(origDir) // #nosec G204
364+
365+ // Redirect stderr to capture the warning
366+ origStderr := os.Stderr
367+ r, w, err := os.Pipe()
368+ if err != nil {
369+ t.Fatal(err)
370+ }
371+ os.Stderr = w
372+
373+ v := getGitVersion(ctx)
374+
375+ w.Close()
376+ os.Stderr = origStderr
377+ stderrOutput, _ := io.ReadAll(r)
378+
379+ // Should return "" (fallback to "latest" handled by determineVersion)
380+ assert.Equal(t, "", v, "expected empty string fallback when no git tags exist")
381+
382+ // Should have printed a warning to stderr
383+ assert.Contains(t, string(stderrOutput), "latest",
384+ "expected a warning about 'latest' fallback on stderr")
385+}
386+
339 func TestParseLabelPair(t *testing.T) {387 func TestParseLabelPair(t *testing.T) {
340 tests := []struct {388 tests := []struct {
341 name string389 name string

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 106

↑↓ moveenter openesc close