Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package melange for openSUSE:Factory checked 
in at 2026-01-17 14:53:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/melange (Old)
 and      /work/SRC/openSUSE:Factory/.melange.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "melange"

Sat Jan 17 14:53:48 2026 rev:130 rq:1327516 version:0.38.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/melange/melange.changes  2026-01-14 
16:23:30.381981876 +0100
+++ /work/SRC/openSUSE:Factory/.melange.new.1928/melange.changes        
2026-01-17 14:54:45.711203656 +0100
@@ -1,0 +2,9 @@
+Fri Jan 16 05:58:34 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.38.0:
+  * build(deps): bump chainguard.dev/apko in the gomod group
+    (#2298)
+  * build(deps): bump the gomod group with 2 updates (#2297)
+  * feat: add QEMU_ADDITIONAL_PACKAGES environment variable (#2266)
+
+-------------------------------------------------------------------

Old:
----
  melange-0.37.5.obscpio

New:
----
  melange-0.38.0.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ melange.spec ++++++
--- /var/tmp/diff_new_pack.2sBuSK/_old  2026-01-17 14:54:47.059260016 +0100
+++ /var/tmp/diff_new_pack.2sBuSK/_new  2026-01-17 14:54:47.067260351 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           melange
-Version:        0.37.5
+Version:        0.38.0
 Release:        0
 Summary:        Build APKs from source code
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.2sBuSK/_old  2026-01-17 14:54:47.131263027 +0100
+++ /var/tmp/diff_new_pack.2sBuSK/_new  2026-01-17 14:54:47.143263529 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/chainguard-dev/melange</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v0.37.5</param>
+    <param name="revision">v0.38.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.2sBuSK/_old  2026-01-17 14:54:47.207266204 +0100
+++ /var/tmp/diff_new_pack.2sBuSK/_new  2026-01-17 14:54:47.211266372 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/chainguard-dev/melange</param>
-              <param 
name="changesrevision">3dbcceae87ea60c03373260e691d21a0b5446387</param></service></servicedata>
+              <param 
name="changesrevision">f4230d0eff43c4dd2f5a8cc50b6d73b7b9940508</param></service></servicedata>
 (No newline at EOF)
 

++++++ melange-0.37.5.obscpio -> melange-0.38.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.37.5/go.mod new/melange-0.38.0/go.mod
--- old/melange-0.37.5/go.mod   2026-01-13 14:39:34.000000000 +0100
+++ new/melange-0.38.0/go.mod   2026-01-15 19:21:44.000000000 +0100
@@ -3,7 +3,7 @@
 go 1.25.0
 
 require (
-       chainguard.dev/apko v1.0.1
+       chainguard.dev/apko v1.0.3
        github.com/chainguard-dev/clog v1.8.0
        github.com/chainguard-dev/go-pkgconfig 
v0.0.0-20240404163941-6351b37b2a10
        github.com/chainguard-dev/yam v0.2.44
@@ -11,7 +11,7 @@
        github.com/docker/cli v29.1.4+incompatible
        github.com/docker/docker v28.5.2+incompatible
        github.com/dprotaso/go-yit v0.0.0-20250513224043-18a80f8f6df4
-       github.com/github/go-spdx/v2 v2.3.5
+       github.com/github/go-spdx/v2 v2.3.6
        github.com/go-git/go-git/v5 v5.16.4
        github.com/google/go-cmp v0.7.0
        github.com/google/go-containerregistry v0.20.7
@@ -73,7 +73,7 @@
 
 require (
        chainguard.dev/go-grpc-kit v0.17.15 // indirect
-       chainguard.dev/sdk v0.1.45 // indirect
+       chainguard.dev/sdk v0.1.46 // indirect
        cloud.google.com/go/auth v0.18.0 // indirect
        cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
        cloud.google.com/go/compute/metadata v0.9.0 // indirect
@@ -117,7 +117,7 @@
        github.com/google/s2a-go v0.1.9 // indirect
        github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
        github.com/google/uuid v1.6.0 // indirect
-       github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
+       github.com/googleapis/enterprise-certificate-proxy v0.3.9 // indirect
        github.com/googleapis/gax-go/v2 v2.16.0 // indirect
        github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
        github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -167,7 +167,7 @@
        golang.org/x/mod v0.31.0 // indirect
        golang.org/x/net v0.48.0 // indirect
        golang.org/x/oauth2 v0.34.0 // indirect
-       google.golang.org/api v0.259.0 // indirect
+       google.golang.org/api v0.260.0 // indirect
        google.golang.org/genproto/googleapis/api 
v0.0.0-20251202230838-ff82c1b0f217 // indirect
        google.golang.org/genproto/googleapis/rpc 
v0.0.0-20251222181119-0a764e51fe1b // indirect
        google.golang.org/grpc v1.78.0 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.37.5/go.sum new/melange-0.38.0/go.sum
--- old/melange-0.37.5/go.sum   2026-01-13 14:39:34.000000000 +0100
+++ new/melange-0.38.0/go.sum   2026-01-15 19:21:44.000000000 +0100
@@ -1,9 +1,9 @@
-chainguard.dev/apko v1.0.1 h1:Jnrt205MtvJgCZyQajRQgg3jAxXcVjgqm0SVeROi4l8=
-chainguard.dev/apko v1.0.1/go.mod 
h1:y2bnsR6Axhy9g47OT5XmxXe4rJ3vfp/RtGWu7sthUxM=
+chainguard.dev/apko v1.0.3 h1:wFFXYyNx5mpcvLL3eQ59r1hEMiY+aDPOx2y6lDaaves=
+chainguard.dev/apko v1.0.3/go.mod 
h1:hiAOfLcuA+U2uwmwU1DPORZ7UJAdlooKmNI0qwYsyM4=
 chainguard.dev/go-grpc-kit v0.17.15 
h1:y+FBjta2lsC0XxlkG+W5P1VxYl0zG74GRvoYN2o+p7s=
 chainguard.dev/go-grpc-kit v0.17.15/go.mod 
h1:1wAVAX2CCamtFlfMs9PFzfgQQxX1/TQyF6cbWApbJ2U=
-chainguard.dev/sdk v0.1.45 h1:s8lqcoqwq+9nfZXYvPd3h9sDzwhQ0cjXLWUCFRTSJ20=
-chainguard.dev/sdk v0.1.45/go.mod 
h1:Xq7KQhJHsWAovd8AiWBAj/ftcNkxMPx5YoQeGVTIj2c=
+chainguard.dev/sdk v0.1.46 h1:tDyxr/VtSlSdAoQJjtN0iHqMYrkShDM2sk4xJpzMYBg=
+chainguard.dev/sdk v0.1.46/go.mod 
h1:b4isqRFaCBaRuVcHUrFeaqvt3d8J9GNOMS0RAyoawXw=
 cloud.google.com/go v0.26.0/go.mod 
h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go/auth v0.18.0 
h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
 cloud.google.com/go/auth v0.18.0/go.mod 
h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
@@ -117,8 +117,8 @@
 github.com/felixge/httpsnoop v1.0.4/go.mod 
h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fsnotify/fsnotify v1.9.0 
h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
 github.com/fsnotify/fsnotify v1.9.0/go.mod 
h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/github/go-spdx/v2 v2.3.5 
h1:rtRQmzDSq2sU/F2oTIvNQQ+6oInq7yxex5npgY//bJQ=
-github.com/github/go-spdx/v2 v2.3.5/go.mod 
h1:VziiWwQ/hoGS++2ifYyr/za0Ng9rlgMS+c4U7zckrDs=
+github.com/github/go-spdx/v2 v2.3.6 
h1:9flm625VmmTlWXi0YH5W9V8FdMfulvxalHdYnUfoqxc=
+github.com/github/go-spdx/v2 v2.3.6/go.mod 
h1:/5rwgS0txhGtRdUZwc02bTglzg6HK3FfuEbECKlK2Sg=
 github.com/gliderlabs/ssh v0.3.8 
h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
 github.com/gliderlabs/ssh v0.3.8/go.mod 
h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 
h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -184,8 +184,8 @@
 github.com/google/uuid v1.1.2/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.7 
h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
-github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod 
h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+github.com/googleapis/enterprise-certificate-proxy v0.3.9 
h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU=
+github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod 
h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
 github.com/googleapis/gax-go/v2 v2.16.0 
h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
 github.com/googleapis/gax-go/v2 v2.16.0/go.mod 
h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 
h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
@@ -479,8 +479,8 @@
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
 gonum.org/v1/gonum v0.16.0/go.mod 
h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ=
-google.golang.org/api v0.259.0/go.mod 
h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=
+google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4=
+google.golang.org/api v0.260.0/go.mod 
h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o=
 google.golang.org/appengine v1.1.0/go.mod 
h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod 
h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod 
h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.37.5/pkg/container/qemu_runner.go 
new/melange-0.38.0/pkg/container/qemu_runner.go
--- old/melange-0.37.5/pkg/container/qemu_runner.go     2026-01-13 
14:39:34.000000000 +0100
+++ new/melange-0.38.0/pkg/container/qemu_runner.go     2026-01-15 
19:21:44.000000000 +0100
@@ -23,6 +23,7 @@
        "crypto/ed25519"
        "crypto/elliptic"
        "crypto/rand"
+       "crypto/sha256"
        _ "embed"
        "encoding/base64"
        "encoding/pem"
@@ -35,6 +36,7 @@
        "os/signal"
        "os/user"
        "path/filepath"
+       "regexp"
        "runtime"
        "strconv"
        "strings"
@@ -1762,16 +1764,20 @@
 
        cacheDir = filepath.Join(cacheDir, "melange-cpio")
 
+       // Include additional packages in cache filename to invalidate cache 
when they change
+       additionalPkgs := getAdditionalPackages(ctx)
+       cacheSuffix := getPackageCacheSuffix(additionalPkgs)
+
        baseInitramfs := filepath.Join(
                cacheDir,
-               "melange-guest.initramfs.cpio")
+               fmt.Sprintf("melange-guest%s.initramfs.cpio", cacheSuffix))
        initramfsInfo, err := os.Stat(baseInitramfs)
 
        // Check if we can use the cached base initramfs (less than 24h old)
        useCache := err == nil && time.Since(initramfsInfo.ModTime()) < 
24*time.Hour
 
        if !useCache {
-               err = generateBaseInitramfs(ctx, cfg, baseInitramfs, cacheDir)
+               err = generateBaseInitramfs(ctx, cfg, baseInitramfs, cacheDir, 
additionalPkgs)
                if err != nil {
                        return "", err
                }
@@ -1782,7 +1788,7 @@
 
 // generateBaseInitramfs creates the base initramfs with microvm-init.
 // This is cached for performance as it doesn't change often.
-func generateBaseInitramfs(ctx context.Context, cfg *Config, initramfsPath, 
cacheDir string) error {
+func generateBaseInitramfs(ctx context.Context, cfg *Config, initramfsPath, 
cacheDir string, additionalPkgs []string) error {
        clog.FromContext(ctx).Info("qemu: generating base initramfs")
 
        err := os.MkdirAll(cacheDir, 0o755)
@@ -1790,14 +1796,16 @@
                return fmt.Errorf("unable to create dest directory: %w", err)
        }
 
+       // Start with base packages and add any additional packages from 
environment
+       packages := []string{"microvm-init"}
+       packages = append(packages, additionalPkgs...)
+
        spec := apko_types.ImageConfiguration{
                Contents: apko_types.ImageContents{
                        BuildRepositories: []string{
                                "https://apk.cgr.dev/chainguard";,
                        },
-                       Packages: []string{
-                               "microvm-init",
-                       },
+                       Packages: packages,
                },
        }
        opts := []apko_build.Option{
@@ -1857,6 +1865,53 @@
        return nil
 }
 
+// getAdditionalPackages parses and validates the QEMU_ADDITIONAL_PACKAGES 
environment variable.
+// Returns a list of package names to add to the initramfs, or empty slice if 
none/invalid.
+func getAdditionalPackages(ctx context.Context) []string {
+       pkgEnv := os.Getenv("QEMU_ADDITIONAL_PACKAGES")
+       if pkgEnv == "" {
+               return nil
+       }
+
+       // Basic validation: check for suspicious characters that could cause 
injection
+       // Allow: alphanumeric, hyphens, underscores, commas, dots
+       if matched, _ := regexp.MatchString(`^[a-zA-Z0-9_,.-]+$`, pkgEnv); 
!matched {
+               clog.FromContext(ctx).Warnf("qemu: QEMU_ADDITIONAL_PACKAGES 
contains invalid characters, ignoring: %s", pkgEnv)
+               return nil
+       }
+
+       clog.FromContext(ctx).Infof("qemu: QEMU_ADDITIONAL_PACKAGES env set to 
%s, adding to initramfs", pkgEnv)
+
+       // Split comma-separated list and filter empty entries
+       var packages []string
+       for pkg := range strings.SplitSeq(pkgEnv, ",") {
+               pkg = strings.TrimSpace(pkg)
+               if pkg != "" {
+                       packages = append(packages, pkg)
+               }
+       }
+
+       return packages
+}
+
+// getPackageCacheSuffix generates a deterministic cache suffix based on the 
package list.
+// Uses SHA256 hash (first 12 chars) to avoid collisions and keep filenames 
reasonable.
+// Returns empty string if packages list is empty.
+func getPackageCacheSuffix(packages []string) string {
+       if len(packages) == 0 {
+               return ""
+       }
+
+       // Join packages with commas to create a stable input string
+       pkgString := strings.Join(packages, ",")
+
+       // Generate SHA256 hash
+       hash := sha256.Sum256([]byte(pkgString))
+
+       // Use first 12 characters of hex encoding (like git short SHA)
+       return fmt.Sprintf("-%x", hash[:6])
+}
+
 // injectHostKeyIntoCpio creates a new initramfs by concatenating the base
 // initramfs with a secondary cpio containing the VM's SSH host key.
 // This allows us to use cached base initramfs while injecting fresh keys each 
time.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.37.5/pkg/container/qemu_runner_test.go 
new/melange-0.38.0/pkg/container/qemu_runner_test.go
--- old/melange-0.37.5/pkg/container/qemu_runner_test.go        2026-01-13 
14:39:34.000000000 +0100
+++ new/melange-0.38.0/pkg/container/qemu_runner_test.go        2026-01-15 
19:21:44.000000000 +0100
@@ -15,8 +15,13 @@
 package container
 
 import (
+       "context"
+       "os"
        "runtime"
        "testing"
+
+       "github.com/chainguard-dev/clog"
+       "github.com/chainguard-dev/clog/slogtest"
 )
 
 func TestGetAvailableMemoryKB(t *testing.T) {
@@ -106,3 +111,183 @@
                t.Errorf("Fallback value %d KB seems too low", expectedFallback)
        }
 }
+
+func TestGetAdditionalPackages(t *testing.T) {
+       ctx := clog.WithLogger(context.Background(), slogtest.TestLogger(t))
+
+       tests := []struct {
+               name     string
+               envValue string
+               expected []string
+       }{
+               {
+                       name:     "empty environment variable",
+                       envValue: "",
+                       expected: nil,
+               },
+               {
+                       name:     "single package",
+                       envValue: "hello-wolfi",
+                       expected: []string{"hello-wolfi"},
+               },
+               {
+                       name:     "multiple packages",
+                       envValue: "hello-wolfi,nginx-stable,strace",
+                       expected: []string{"hello-wolfi", "nginx-stable", 
"strace"},
+               },
+               {
+                       name:     "packages with spaces around commas rejected",
+                       envValue: "hello-wolfi, nginx-stable , strace",
+                       expected: nil,
+               },
+               {
+                       name:     "packages with dots and underscores",
+                       envValue: "python-3.11,gcc_musl,nginx-1.25.0",
+                       expected: []string{"python-3.11", "gcc_musl", 
"nginx-1.25.0"},
+               },
+               {
+                       name:     "empty entries filtered out",
+                       envValue: "hello-wolfi,,nginx-stable",
+                       expected: []string{"hello-wolfi", "nginx-stable"},
+               },
+               {
+                       name:     "invalid characters rejected",
+                       envValue: "hello-wolfi;rm -rf /",
+                       expected: nil,
+               },
+               {
+                       name:     "shell injection attempt",
+                       envValue: "package$(evil_command)",
+                       expected: nil,
+               },
+               {
+                       name:     "quotes rejected",
+                       envValue: "\"hello-wolfi\"",
+                       expected: nil,
+               },
+               {
+                       name:     "spaces in package name rejected",
+                       envValue: "hello wolfi",
+                       expected: nil,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       // Set environment variable
+                       if tt.envValue == "" {
+                               os.Unsetenv("QEMU_ADDITIONAL_PACKAGES")
+                       } else {
+                               os.Setenv("QEMU_ADDITIONAL_PACKAGES", 
tt.envValue)
+                       }
+                       defer os.Unsetenv("QEMU_ADDITIONAL_PACKAGES")
+
+                       result := getAdditionalPackages(ctx)
+
+                       // Compare results
+                       if len(result) != len(tt.expected) {
+                               t.Errorf("getAdditionalPackages() returned %d 
packages, expected %d: got %v, want %v",
+                                       len(result), len(tt.expected), result, 
tt.expected)
+                               return
+                       }
+
+                       for i, pkg := range result {
+                               if pkg != tt.expected[i] {
+                                       t.Errorf("getAdditionalPackages()[%d] = 
%q, expected %q", i, pkg, tt.expected[i])
+                               }
+                       }
+               })
+       }
+}
+
+func TestGetPackageCacheSuffix(t *testing.T) {
+       tests := []struct {
+               name     string
+               packages []string
+               expected string
+       }{
+               {
+                       name:     "empty package list",
+                       packages: []string{},
+                       expected: "",
+               },
+               {
+                       name:     "nil package list",
+                       packages: nil,
+                       expected: "",
+               },
+               {
+                       name:     "single package",
+                       packages: []string{"hello-wolfi"},
+                       expected: "-f5c4369d6487",
+               },
+               {
+                       name:     "multiple packages deterministic",
+                       packages: []string{"hello-wolfi", "nginx-stable", 
"strace"},
+                       expected: "-8315f3ef029a",
+               },
+               {
+                       name:     "same packages different order produces 
different hash",
+                       packages: []string{"strace", "hello-wolfi", 
"nginx-stable"},
+                       expected: "-5236a484f919",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       result := getPackageCacheSuffix(tt.packages)
+
+                       if result != tt.expected {
+                               t.Errorf("getPackageCacheSuffix(%v) = %q, 
expected %q", tt.packages, result, tt.expected)
+                       }
+
+                       // Additional validation: check format
+                       if len(tt.packages) > 0 {
+                               // Should start with dash and have 12 hex 
characters
+                               if len(result) != 13 { // "-" + 12 hex chars
+                                       t.Errorf("getPackageCacheSuffix(%v) = 
%q, expected 13 characters (dash + 12 hex)", tt.packages, result)
+                               }
+                               if result[0] != '-' {
+                                       t.Errorf("getPackageCacheSuffix(%v) = 
%q, expected to start with '-'", tt.packages, result)
+                               }
+                       }
+               })
+       }
+}
+
+func TestGetPackageCacheSuffix_Deterministic(t *testing.T) {
+       // Test that the same packages always produce the same hash
+       packages := []string{"hello-wolfi", "nginx-stable", "strace"}
+
+       result1 := getPackageCacheSuffix(packages)
+       result2 := getPackageCacheSuffix(packages)
+
+       if result1 != result2 {
+               t.Errorf("getPackageCacheSuffix is not deterministic: first 
call returned %q, second call returned %q", result1, result2)
+       }
+}
+
+func TestGetPackageCacheSuffix_NoCollisions(t *testing.T) {
+       // Test that different package lists produce different hashes
+       testCases := [][]string{
+               {"package-a", "package-b", "package-c", "package-d", 
"package-e"},
+               {"package-a", "package-b", "package-c", "package-d", 
"package-f"},
+               {"hello-wolfi"},
+               {"hello-wolfi", "nginx-stable"},
+               {"nginx-stable", "hello-wolfi"}, // Order matters
+       }
+
+       hashes := make(map[string][]string)
+       for _, packages := range testCases {
+               hash := getPackageCacheSuffix(packages)
+               hashes[hash] = packages
+       }
+
+       // Should have unique hashes for each unique package list
+       if len(hashes) != len(testCases) {
+               t.Errorf("getPackageCacheSuffix produced collisions: %d unique 
hashes for %d test cases", len(hashes), len(testCases))
+               for hash, packages := range hashes {
+                       t.Logf("Hash %q: %v", hash, packages)
+               }
+       }
+}

++++++ melange.obsinfo ++++++
--- /var/tmp/diff_new_pack.2sBuSK/_old  2026-01-17 14:54:48.023300322 +0100
+++ /var/tmp/diff_new_pack.2sBuSK/_new  2026-01-17 14:54:48.039300991 +0100
@@ -1,5 +1,5 @@
 name: melange
-version: 0.37.5
-mtime: 1768311574
-commit: 3dbcceae87ea60c03373260e691d21a0b5446387
+version: 0.38.0
+mtime: 1768501304
+commit: f4230d0eff43c4dd2f5a8cc50b6d73b7b9940508
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/melange/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.melange.new.1928/vendor.tar.gz differ: char 13, 
line 1

Reply via email to