Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package apko for openSUSE:Factory checked in 
at 2026-08-03 17:17:07
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/apko (Old)
 and      /work/SRC/openSUSE:Factory/.apko.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "apko"

Mon Aug  3 17:17:07 2026 rev:131 rq:1369151 version:1.2.31

Changes:
--------
--- /work/SRC/openSUSE:Factory/apko/apko.changes        2026-08-01 
18:35:11.135924587 +0200
+++ /work/SRC/openSUSE:Factory/.apko.new.16738/apko.changes     2026-08-03 
17:17:22.882894390 +0200
@@ -1,0 +2,8 @@
+Mon Aug 03 05:55:40 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.2.31:
+  * build(deps): bump go.step.sm/crypto from 0.85.0 to 0.86.0
+    (#2362)
+  * Intern parsed repository packages (#2357)
+
+-------------------------------------------------------------------

Old:
----
  apko-1.2.30.obscpio

New:
----
  apko-1.2.31.obscpio

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

Other differences:
------------------
++++++ apko.spec ++++++
--- /var/tmp/diff_new_pack.KWc2lO/_old  2026-08-03 17:17:25.498985428 +0200
+++ /var/tmp/diff_new_pack.KWc2lO/_new  2026-08-03 17:17:25.502985568 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           apko
-Version:        1.2.30
+Version:        1.2.31
 Release:        0
 Summary:        Build OCI images from APK packages directly without Dockerfile
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.KWc2lO/_old  2026-08-03 17:17:25.538986820 +0200
+++ /var/tmp/diff_new_pack.KWc2lO/_new  2026-08-03 17:17:25.542986960 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/chainguard-dev/apko.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v1.2.30</param>
+    <param name="revision">refs/tags/v1.2.31</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.KWc2lO/_old  2026-08-03 17:17:25.570987934 +0200
+++ /var/tmp/diff_new_pack.KWc2lO/_new  2026-08-03 17:17:25.574988073 +0200
@@ -3,6 +3,6 @@
                 <param 
name="url">https://github.com/chainguard-dev/apko</param>
               <param 
name="changesrevision">861f83f69e6fa9114405a2f7bb5cf6585ad00421</param></service><service
 name="tar_scm">
                 <param 
name="url">https://github.com/chainguard-dev/apko.git</param>
-              <param 
name="changesrevision">502643080cb38d339c5804fe7e62c9a5bd46b6d5</param></service></servicedata>
+              <param 
name="changesrevision">fc40b1ae3059b97fd591afe4d203379b16887b2a</param></service></servicedata>
 (No newline at EOF)
 

++++++ apko-1.2.30.obscpio -> apko-1.2.31.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.2.30/go.mod new/apko-1.2.31/go.mod
--- old/apko-1.2.30/go.mod      2026-07-30 20:33:18.000000000 +0200
+++ new/apko-1.2.31/go.mod      2026-07-31 20:19:09.000000000 +0200
@@ -25,7 +25,7 @@
        go.lsp.dev/uri v1.0.1
        go.opentelemetry.io/otel v1.44.0
        go.opentelemetry.io/otel/trace v1.44.0
-       go.step.sm/crypto v0.85.0
+       go.step.sm/crypto v0.86.0
        golang.org/x/oauth2 v0.36.0
        golang.org/x/sync v0.22.0
        golang.org/x/sys v0.47.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.2.30/go.sum new/apko-1.2.31/go.sum
--- old/apko-1.2.30/go.sum      2026-07-30 20:33:18.000000000 +0200
+++ new/apko-1.2.31/go.sum      2026-07-31 20:19:09.000000000 +0200
@@ -267,8 +267,8 @@
 go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod 
h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
 go.opentelemetry.io/otel/trace v1.44.0 
h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
 go.opentelemetry.io/otel/trace v1.44.0/go.mod 
h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
-go.step.sm/crypto v0.85.0 h1:h3U9gzCcxP1fWbseqy+CwsTxzHFNuoblefy7YP4jBHk=
-go.step.sm/crypto v0.85.0/go.mod 
h1:Rp/BdP0/ZJ7eYREhXcE4izCABLp0e8ziFqb0LBNG7Cc=
+go.step.sm/crypto v0.86.0 h1:ORItETVOEAo0+rfCm0VKSAolLtoxMSL+pOKfvUeR4MM=
+go.step.sm/crypto v0.86.0/go.mod 
h1:G+SbDNGA8ZYZBOXYJnxZLJnVCWp3RIKMF+ECS8G0Qug=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod 
h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.2.30/pkg/apk/apk/index.go 
new/apko-1.2.31/pkg/apk/apk/index.go
--- old/apko-1.2.30/pkg/apk/apk/index.go        2026-07-30 20:33:18.000000000 
+0200
+++ new/apko-1.2.31/pkg/apk/apk/index.go        2026-07-31 20:19:09.000000000 
+0200
@@ -432,6 +432,13 @@
                return nil, fmt.Errorf("unable to read convert repository index 
bytes to index struct: %w", err)
        }
 
+       // Share package payloads with other indexes carrying the same packages.
+       // Only the repository-fetch path interns: index manipulation tooling
+       // building on IndexFromArchive directly keeps exclusive ownership.
+       for i, pkg := range index.Packages {
+               index.Packages[i] = internPackage(pkg)
+       }
+
        return index, err
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.2.30/pkg/apk/apk/intern.go 
new/apko-1.2.31/pkg/apk/apk/intern.go
--- old/apko-1.2.30/pkg/apk/apk/intern.go       1970-01-01 01:00:00.000000000 
+0100
+++ new/apko-1.2.31/pkg/apk/apk/intern.go       2026-07-31 20:19:09.000000000 
+0200
@@ -0,0 +1,78 @@
+// Copyright 2026 Chainguard, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apk
+
+import (
+       "reflect"
+       "sync"
+       "weak"
+)
+
+// Repository indexes overlap heavily: consecutive generations of one index
+// share almost all of their packages, and indexes of related repositories
+// carry large common subsets. Interning parsed packages lets all of them
+// share a single Package per distinct package instead of a copy per index.
+// Entries hold weak pointers, so the table itself pins nothing.
+//
+// Sharing requires that packages parsed from repository indexes are never
+// mutated; they are treated as immutable everywhere.
+var packageInterner = struct {
+       sync.Mutex
+       m map[internKey]weak.Pointer[Package]
+}{m: map[internKey]weak.Pointer[Package]{}}
+
+type internKey struct {
+       name     string
+       checksum string
+}
+
+// internPackage returns the canonical *Package for pkg: a previously interned
+// package with an identical record, or pkg itself if none is registered yet.
+// Packages without a checksum are not interned.
+//
+// The {name, checksum} pair only buckets candidates; identity is confirmed
+// with reflect.DeepEqual over the whole package, so two records share a
+// Package only when every field matches. That keeps interning safe across
+// repository trust boundaries - a crafted index cannot donate its
+// dependencies to another repository's package by matching the control hash
+// alone - and cannot silently drop a field the way a hand-written comparison
+// could.
+func internPackage(pkg *Package) *Package {
+       if len(pkg.Checksum) == 0 {
+               return pkg
+       }
+       key := internKey{name: pkg.Name, checksum: string(pkg.Checksum)}
+
+       packageInterner.Lock()
+       defer packageInterner.Unlock()
+
+       if wp, ok := packageInterner.m[key]; ok {
+               if canonical := wp.Value(); canonical != nil {
+                       if reflect.DeepEqual(pkg, canonical) {
+                               return canonical
+                       }
+                       // A live but different package holds the bucket (a 
control-hash
+                       // collision or a poisoning attempt): keep both 
distinct.
+                       return pkg
+               }
+       }
+
+       // Weak so the payload is collected once no live index references it. A
+       // dead entry reads back as a miss and is overwritten when its record
+       // recurs; one forms only when a record leaves every live index at once,
+       // rare for largely append-only indexes, so tombstones accumulate 
slowly.
+       packageInterner.m[key] = weak.Make(pkg)
+       return pkg
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/apko-1.2.30/pkg/apk/apk/intern_test.go 
new/apko-1.2.31/pkg/apk/apk/intern_test.go
--- old/apko-1.2.30/pkg/apk/apk/intern_test.go  1970-01-01 01:00:00.000000000 
+0100
+++ new/apko-1.2.31/pkg/apk/apk/intern_test.go  2026-07-31 20:19:09.000000000 
+0200
@@ -0,0 +1,154 @@
+package apk
+
+import (
+       "fmt"
+       "io"
+       "net/http"
+       "net/http/httptest"
+       "runtime"
+       "strings"
+       "sync/atomic"
+       "testing"
+       "time"
+       "weak"
+)
+
+func internTestIndexBody(t *testing.T, nPkgs int) []byte {
+       t.Helper()
+       idx := &APKIndex{Description: "intern-test"}
+       for i := range nPkgs {
+               idx.Packages = append(idx.Packages, &Package{
+                       Name:    fmt.Sprintf("pkg-%d", i),
+                       Version: "1.0.0-r0",
+                       Arch:    "x86_64",
+                       // A fat payload so sharing dominates the heap 
measurements.
+                       Description:  strings.Repeat(fmt.Sprintf("synthetic 
package %d ", i), 50),
+                       Checksum:     fmt.Appendf(nil, "checksum-of-pkg-%05d", 
i),
+                       Dependencies: []string{fmt.Sprintf("pkg-%d", min(i+1, 
20))},
+                       Provides:     
[]string{fmt.Sprintf("cmd:tool-%d=1.0.0-r0", i)},
+                       BuildTime:    time.Unix(1700000000, 0).UTC(),
+               })
+       }
+       archive, err := ArchiveFromIndex(idx)
+       if err != nil {
+               t.Fatal(err)
+       }
+       body, err := io.ReadAll(archive)
+       if err != nil {
+               t.Fatal(err)
+       }
+       return body
+}
+
+func internTestServer(t *testing.T, body []byte) *httptest.Server {
+       t.Helper()
+       var etag atomic.Value
+       etag.Store("gen-0")
+       srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, 
r *http.Request) {
+               w.Header().Set("ETag", fmt.Sprintf("%q", 
r.URL.Query().Get("gen")+"gen"))
+               if r.Method == http.MethodHead {
+                       return
+               }
+               _, _ = w.Write(body)
+       }))
+       t.Cleanup(srv.Close)
+       return srv
+}
+
+// Indexes at different URLs carrying the same packages must share the parsed
+// Package payloads.
+func TestInterningSharesPackagesAcrossIndexes(t *testing.T) {
+       ctx := t.Context()
+       body := internTestIndexBody(t, 100)
+       srv := internTestServer(t, body)
+
+       fetch := func(repo string) NamedIndex {
+               indexes, err := GetRepositoryIndexes(ctx, []string{repo}, nil, 
"x86_64",
+                       WithIgnoreSignatures(true), 
WithHTTPClient(srv.Client()))
+               if err != nil {
+                       t.Fatal(err)
+               }
+               return indexes[0]
+       }
+
+       a := fetch(srv.URL + "/repo-a")
+       b := fetch(srv.URL + "/repo-b")
+
+       for i, pkg := range a.Packages() {
+               if pkg.Package != b.Packages()[i].Package {
+                       t.Fatalf("package %d not shared across indexes", i)
+               }
+       }
+}
+
+// Records that differ in any field must not be conflated, even when they share
+// a name and control hash. Dependencies is a field the old six-field guard did
+// not compare; DeepEqual does, which is what keeps interning safe across
+// repository trust boundaries.
+func TestInterningRejectsMismatchedRecords(t *testing.T) {
+       a := &Package{Name: "pkg", Version: "1.0.0-r0", Checksum: 
[]byte("intern-mismatch-test"), Dependencies: []string{"honest-dep"}}
+       b := &Package{Name: "pkg", Version: "1.0.0-r0", Checksum: 
[]byte("intern-mismatch-test"), Dependencies: []string{"honest-dep", 
"evil-dep"}}
+
+       canonical := internPackage(a)
+       if canonical != a {
+               t.Fatal("first package should become canonical")
+       }
+       // b shares a's name and checksum but differs, so it must not be 
conflated
+       // with the canonical. Comparing against canonical also keeps a live 
across
+       // this call, so the mismatch path is exercised rather than a 
dead-entry miss.
+       if internPackage(b) == canonical {
+               t.Fatal("record differing only in dependencies was conflated")
+       }
+}
+
+// The intern table holds packages weakly, so an interned package must be
+// collectable once no live index references it; a strong table would pin it
+// forever. (The dead map entry lingers until overwritten, which is intended.)
+func TestInterningDoesNotPinPackages(t *testing.T) {
+       var observer weak.Pointer[Package]
+       func() {
+               pkg := internPackage(&Package{
+                       Name:     "collectable",
+                       Version:  "1.0.0-r0",
+                       Checksum: []byte("intern-collect-test"),
+               })
+               observer = weak.Make(pkg)
+       }()
+
+       deadline := time.Now().Add(10 * time.Second)
+       for observer.Value() != nil {
+               if time.Now().After(deadline) {
+                       t.Fatal("interned package was not collected; the table 
is pinning it")
+               }
+               runtime.GC()
+               time.Sleep(10 * time.Millisecond)
+       }
+}
+
+// Loading identical indexes from distinct URLs must share package objects, not
+// duplicate them: the union of every index's package pointers is one index's
+// worth, not one per index.
+func TestInterningDeduplicatesAcrossURLs(t *testing.T) {
+       const nPkgs = 1000
+       const copies = 4
+
+       ctx := t.Context()
+       body := internTestIndexBody(t, nPkgs)
+       srv := internTestServer(t, body)
+
+       seen := map[*Package]struct{}{}
+       for i := range copies {
+               got, err := GetRepositoryIndexes(ctx, 
[]string{fmt.Sprintf("%s/repo-%d", srv.URL, i)}, nil, "x86_64",
+                       WithIgnoreSignatures(true), 
WithHTTPClient(srv.Client()))
+               if err != nil {
+                       t.Fatal(err)
+               }
+               for _, p := range got[0].Packages() {
+                       seen[p.Package] = struct{}{}
+               }
+       }
+
+       if len(seen) != nPkgs {
+               t.Errorf("saw %d distinct package objects across %d identical 
indexes, want %d", len(seen), copies, nPkgs)
+       }
+}

++++++ apko.obsinfo ++++++
--- /var/tmp/diff_new_pack.KWc2lO/_old  2026-08-03 17:17:26.199009789 +0200
+++ /var/tmp/diff_new_pack.KWc2lO/_new  2026-08-03 17:17:26.203009928 +0200
@@ -1,5 +1,5 @@
 name: apko
-version: 1.2.30
-mtime: 1785436398
-commit: 502643080cb38d339c5804fe7e62c9a5bd46b6d5
+version: 1.2.31
+mtime: 1785521949
+commit: fc40b1ae3059b97fd591afe4d203379b16887b2a
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/apko/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.apko.new.16738/vendor.tar.gz differ: char 133, line 
1

Reply via email to