Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package melange for openSUSE:Factory checked 
in at 2025-04-14 12:59:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/melange (Old)
 and      /work/SRC/openSUSE:Factory/.melange.new.1907 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "melange"

Mon Apr 14 12:59:18 2025 rev:76 rq:1269154 version:0.23.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/melange/melange.changes  2025-04-07 
17:38:00.576318638 +0200
+++ /work/SRC/openSUSE:Factory/.melange.new.1907/melange.changes        
2025-04-14 12:59:24.582837852 +0200
@@ -1,0 +2,23 @@
+Mon Apr 14 07:57:49 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- Update to version 0.23.6:
+  * pipelines: cmake/build: Use verbose mode by default (#1910)
+  * Include /usr/local paths in generateShbangDeps; drop /bin
+    (#1909)
+  * fix(pipelines/git-checkout): preserve ownership of existing
+    files (#1893)
+  * sbin-merge: reorder default pipeline path (#1907)
+  * Move apko's tarball package into melange (#1906)
+  * Upgrade golangci-lint to 1.64.8 (#1904)
+  * build(deps): bump the gomod group across 1 directory with 6
+    updates (#1879)
+  * build(deps): bump the actions group across 1 directory with 5
+    updates (#1895)
+  * deprecate goreleaser build pipeline (#1892)
+  * test.go: Invoke apkofs.DirFS with option to create dir if
+    missing (#1897)
+  * github: fixup presubmit workflow (#1900)
+  * fix random issue with package.full-version (#1898)
+  * Improve failure message for usrmerge linter (#1894)
+
+-------------------------------------------------------------------

Old:
----
  melange-0.23.5.obscpio

New:
----
  melange-0.23.6.obscpio

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

Other differences:
------------------
++++++ melange.spec ++++++
--- /var/tmp/diff_new_pack.k57TwO/_old  2025-04-14 12:59:25.250865905 +0200
+++ /var/tmp/diff_new_pack.k57TwO/_new  2025-04-14 12:59:25.250865905 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           melange
-Version:        0.23.5
+Version:        0.23.6
 Release:        0
 Summary:        Build APKs from source code
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.k57TwO/_old  2025-04-14 12:59:25.278867081 +0200
+++ /var/tmp/diff_new_pack.k57TwO/_new  2025-04-14 12:59:25.282867249 +0200
@@ -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.23.5</param>
+    <param name="revision">v0.23.6</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.k57TwO/_old  2025-04-14 12:59:25.298867921 +0200
+++ /var/tmp/diff_new_pack.k57TwO/_new  2025-04-14 12:59:25.302868089 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/chainguard-dev/melange</param>
-              <param 
name="changesrevision">a5d28473b9091cf7a45a0864bb149ae9fddfff99</param></service></servicedata>
+              <param 
name="changesrevision">1c075e12cf123600b57cfb8bb66b9aa107a37e08</param></service></servicedata>
 (No newline at EOF)
 

++++++ melange-0.23.5.obscpio -> melange-0.23.6.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/Makefile new/melange-0.23.6/Makefile
--- old/melange-0.23.5/Makefile 2025-04-04 16:33:33.000000000 +0200
+++ new/melange-0.23.6/Makefile 2025-04-11 21:28:28.000000000 +0200
@@ -117,7 +117,7 @@
 setup-golangci-lint:
        rm -f $(GOLANGCI_LINT_BIN) || :
        set -e ;
-       GOBIN=$(GOLANGCI_LINT_DIR) go install 
github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4;
+       GOBIN=$(GOLANGCI_LINT_DIR) go install 
github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8;
 
 .PHONY: fmt
 fmt: ## Format all go files
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/e2e-tests/git-checkout-build.yaml 
new/melange-0.23.6/e2e-tests/git-checkout-build.yaml
--- old/melange-0.23.5/e2e-tests/git-checkout-build.yaml        2025-04-04 
16:33:33.000000000 +0200
+++ new/melange-0.23.6/e2e-tests/git-checkout-build.yaml        2025-04-11 
21:28:28.000000000 +0200
@@ -206,3 +206,88 @@
       [ "$hash" != "$expected_hash" ]
       cd ..
       rm -R cherry-pick-test
+
+  # When ownership of files created inside and outside of the runner container 
do not
+  # match, if mode is restrictive on files like 0700 on the Melange workspace, 
if the git-checkout
+  # pipeline changes ownership on them, being that it runs inside the runner 
container, subsequent
+  # filesystem operations run outside of the runner fail because of missing 
permissions.
+  # This happens specifically with tar --extract without preserving ownership 
of existing files,
+  # like '.', which in case of the default input and standard usage of the 
pipeline, is the
+  # workspace directory.
+  # This results in subsequent operations like xattrs restore to fail as they 
are run outside of
+  # the runner container.
+  #
+  # Bubblewrap runner:
+  #   Since the user namespace is unshared and that the runner uid is mapped 
to the
+  #   uid of the user who runs Melange, ownership always match.
+  #   - Melange run by root -> runner user: root.
+  #     Owner of files created outside of the runner:
+  #       - host: root
+  #       - runner: root
+  #     Owner of files creted inside the runner:
+  #       - host: root
+  #       - runner: root
+  #   - Melange run by non-root -> runner user: build (1000 mapped to 
non-root).
+  #     Owner of files created outside of the runner:
+  #       - host: non-root
+  #       - runner: build (1000 -> host's non-root)
+  #     Owner of files creted inside the runner:
+  #       - host: non-root
+  #       - runner: build (1000 -> host's non-root)
+  #
+  # Docker runner:
+  #   Since the user namespace is shared, ownership between files created 
inside and
+  #   outside the runner, match only when the user who runs Melange is root.
+  #   - Melange run by root -> runner user: root.
+  #     Owner of files created outside of the runner:
+  #       - host: root
+  #       - runner: root
+  #     Owner of files creted inside the runner:
+  #       - host: root
+  #       - runner: root
+  #   - Melange run by non-root -> runner user: root.
+  #     Owner of files created outside of the runner:
+  #       - host: non-root
+  #       - runner: non-root
+  #     Owner of files creted inside the runner:
+  #       - host: root
+  #       - runner: root
+  #
+  # QEMU runner:
+  #   File ownership inside and outside of the runner VM always match.
+  #
+  - name: "check ownership of existing files"
+    runs: |
+      set -eu
+      runner_user=$(id -u)
+      melange_user=$(stat -c "%u" .)
+      expected_runner=""
+      expected_melange=""
+      if [[ $runner_user == $melange_user ]]; then
+        # Ownership of existing files match the ownership of files created 
within the runner
+        # (i.e. by the git-checkout pipeline tar --extract command).
+        expected_runner=48
+        expected_melange=2
+      else
+        # Ownership of existing files is not changed.
+        expected_runner=42
+        expected_melange=6
+      fi
+      exclude_args="! -regex ^\.\/\.ssh.*$ ! -regex ^./.gitconfig$"
+      found_runner=$(find . -user $runner_user $exclude_args | wc -l)
+      mismatch=""
+      if [[ $found_runner != $expected_runner ]]; then
+        echo "Expected $expected_runner files owned by $runner_user, found 
$found_runner"
+        find . -user $runner_user $exclude_args
+        mismatch=true
+      fi
+      if [[ $melange_user != $runner_user ]]; then
+        found_melange=$(find . -user $melange_user $exclude_args | wc -l)
+        if [[ $found_runner != $expected_runner || $found_melange != 
$expected_melange ]]; then
+          echo "Expected $expected_melange files owned by $melange_user, found 
$found_melange"
+          find . -user $melange_user $exclude_args
+          mismatch=true
+        fi
+      fi
+      [[ $mismatch != "" ]] && exit 1
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/e2e-tests/run-tests 
new/melange-0.23.6/e2e-tests/run-tests
--- old/melange-0.23.5/e2e-tests/run-tests      2025-04-04 16:33:33.000000000 
+0200
+++ new/melange-0.23.6/e2e-tests/run-tests      2025-04-11 21:28:28.000000000 
+0200
@@ -14,6 +14,7 @@
 if [ "$MELANGE" = "melange" ]; then
     MELANGE=$(which melange)
 fi
+ARGS=${ARGS:-""}
 
 echo "Testing with melange from $MELANGE"
 $MELANGE version --json
@@ -32,7 +33,7 @@
 
 fails=""
 for yaml in "$@"; do
-  args=""
+  args="${ARGS}"
   ops=""
   base=${yaml%.yaml}
   case "$base" in
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/go.mod new/melange-0.23.6/go.mod
--- old/melange-0.23.5/go.mod   2025-04-04 16:33:33.000000000 +0200
+++ new/melange-0.23.6/go.mod   2025-04-11 21:28:28.000000000 +0200
@@ -3,15 +3,15 @@
 go 1.23.4
 
 require (
-       chainguard.dev/apko v0.25.4
+       chainguard.dev/apko v0.25.6
        cloud.google.com/go/storage v1.51.0
        dagger.io/dagger v0.16.3
        github.com/chainguard-dev/clog v1.7.0
        github.com/chainguard-dev/go-pkgconfig 
v0.0.0-20240404163941-6351b37b2a10
-       github.com/chainguard-dev/yam v0.2.10
+       github.com/chainguard-dev/yam v0.2.12
        github.com/charmbracelet/log v0.4.1
-       github.com/docker/cli v28.0.1+incompatible
-       github.com/docker/docker v28.0.1+incompatible
+       github.com/docker/cli v28.0.4+incompatible
+       github.com/docker/docker v28.0.4+incompatible
        github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936
        github.com/github/go-spdx/v2 v2.3.2
        github.com/go-git/go-git/v5 v5.14.0
@@ -25,7 +25,7 @@
        github.com/klauspost/compress v1.18.0
        github.com/klauspost/pgzip v1.2.6
        github.com/kubescape/go-git-url v0.0.30
-       github.com/moby/moby v28.0.1+incompatible
+       github.com/moby/moby v28.0.4+incompatible
        github.com/opencontainers/image-spec v1.1.1
        github.com/package-url/packageurl-go v0.1.3
        github.com/pkg/errors v0.9.1
@@ -50,7 +50,7 @@
        gopkg.in/ini.v1 v1.67.0
        gopkg.in/yaml.v3 v3.0.1
        mvdan.cc/sh/v3 v3.11.0
-       sigs.k8s.io/release-utils v0.11.0
+       sigs.k8s.io/release-utils v0.11.1
        sigs.k8s.io/yaml v1.4.0
 )
 
@@ -222,5 +222,5 @@
        google.golang.org/protobuf v1.36.5 // indirect
        gopkg.in/warnings.v0 v0.1.2 // indirect
        k8s.io/apimachinery v0.32.3 // indirect
-       k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
+       k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/go.sum new/melange-0.23.6/go.sum
--- old/melange-0.23.5/go.sum   2025-04-04 16:33:33.000000000 +0200
+++ new/melange-0.23.6/go.sum   2025-04-11 21:28:28.000000000 +0200
@@ -1,7 +1,7 @@
 cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=
 cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
-chainguard.dev/apko v0.25.4 h1:QFFCD7QR3q9AT5H+/ZCXQReCNN0CgRn1mkOq1T+FZfQ=
-chainguard.dev/apko v0.25.4/go.mod 
h1:1xdjg538oPqb7VCiDMc4IlNtS5gRfpJqv2VIveR6wIo=
+chainguard.dev/apko v0.25.6 h1:ie7+FO02C/UVbfIz1TwDUg0zsdY3SFALf24mlcLGecY=
+chainguard.dev/apko v0.25.6/go.mod 
h1:1xdjg538oPqb7VCiDMc4IlNtS5gRfpJqv2VIveR6wIo=
 chainguard.dev/go-grpc-kit v0.17.7 
h1:TqHua7er5k8m6WM96y0Tm7IoLLkuZ5vh3+5SR1gruKg=
 chainguard.dev/go-grpc-kit v0.17.7/go.mod 
h1:JroMzTY9mdhKe/bvtyChgfECaNh80+bMZH3HS+TGXHw=
 chainguard.dev/sdk v0.1.31 h1:Blvpa0Ji/tC1VVV8/l8UyQe022LoRxZLfgasyFE1EhQ=
@@ -89,8 +89,8 @@
 github.com/chainguard-dev/git-urls v1.0.2/go.mod 
h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
 github.com/chainguard-dev/go-pkgconfig v0.0.0-20240404163941-6351b37b2a10 
h1:XR2vgQC024I9/boh9r1ihVv8Z14+pbvWqXeYMCnZJpc=
 github.com/chainguard-dev/go-pkgconfig 
v0.0.0-20240404163941-6351b37b2a10/go.mod 
h1:1p6+MesLcjKeON5BRWa7I87mvAY0QmKjgginIM3w6BI=
-github.com/chainguard-dev/yam v0.2.10 
h1:2iklMLB+o+H6r30wWV5JYLcykNAXTE0Q8snjXsTBbx0=
-github.com/chainguard-dev/yam v0.2.10/go.mod 
h1:Dqfj6H4GyIBTyBRQi5S+cji6wH4UlZDaN/sFirjOjjA=
+github.com/chainguard-dev/yam v0.2.12 
h1:8d7a7Ri5EJjEnleEo0vSN0rlw1FYa0aWAabsvjSiRN0=
+github.com/chainguard-dev/yam v0.2.12/go.mod 
h1:Dqfj6H4GyIBTyBRQi5S+cji6wH4UlZDaN/sFirjOjjA=
 github.com/charmbracelet/lipgloss v1.0.0 
h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
 github.com/charmbracelet/lipgloss v1.0.0/go.mod 
h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
 github.com/charmbracelet/log v0.4.1 
h1:6AYnoHKADkghm/vt4neaNEXkxcXLSV2g1rdyFDOpTyk=
@@ -128,12 +128,12 @@
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/distribution/reference v0.6.0 
h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
 github.com/distribution/reference v0.6.0/go.mod 
h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/cli v28.0.1+incompatible 
h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs=
-github.com/docker/cli v28.0.1+incompatible/go.mod 
h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v28.0.4+incompatible 
h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A=
+github.com/docker/cli v28.0.4+incompatible/go.mod 
h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.3+incompatible 
h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod 
h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v28.0.1+incompatible 
h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
-github.com/docker/docker v28.0.1+incompatible/go.mod 
h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v28.0.4+incompatible 
h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
+github.com/docker/docker v28.0.4+incompatible/go.mod 
h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.8.2 
h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
 github.com/docker/docker-credential-helpers v0.8.2/go.mod 
h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
 github.com/docker/go-connections v0.5.0 
h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -345,8 +345,8 @@
 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod 
h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/docker-image-spec v1.3.1 
h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
 github.com/moby/docker-image-spec v1.3.1/go.mod 
h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/moby v28.0.1+incompatible 
h1:10ejBTwFhM3/9p6pSaKrLyXnx7QzzCmCYHAedOp67cQ=
-github.com/moby/moby v28.0.1+incompatible/go.mod 
h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
+github.com/moby/moby v28.0.4+incompatible 
h1:16Yx7MFk2ucIqMdWJ06YdPn6sCmso02RKkD1OROxrho=
+github.com/moby/moby v28.0.4+incompatible/go.mod 
h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
 github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
 github.com/moby/term v0.5.2/go.mod 
h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@@ -723,11 +723,11 @@
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod 
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
 k8s.io/apimachinery v0.32.3/go.mod 
h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
-k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 
h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
-k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod 
h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20241210054802-24370beab758 
h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
+k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod 
h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
 mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
-sigs.k8s.io/release-utils v0.11.0 
h1:FUVSw2dO67M7mfcQx9AITEGnTHoBOdJNbbQ3FT3o8mA=
-sigs.k8s.io/release-utils v0.11.0/go.mod 
h1:wAlXz8xruzvqZUsorI64dZ3lbkiDnYSlI4IYC6l2yEA=
+sigs.k8s.io/release-utils v0.11.1 
h1:hzvXGpHgHJfLOJB6TRuu14bzWc3XEglHmXHJqwClSZE=
+sigs.k8s.io/release-utils v0.11.1/go.mod 
h1:ybR2V/uQAOGxYfzYtBenSYeXWkBGNP2qnEiX77ACtpc=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/build/package.go 
new/melange-0.23.6/pkg/build/package.go
--- old/melange-0.23.5/pkg/build/package.go     2025-04-04 16:33:33.000000000 
+0200
+++ new/melange-0.23.6/pkg/build/package.go     2025-04-11 21:28:28.000000000 
+0200
@@ -38,8 +38,8 @@
 
        "chainguard.dev/melange/pkg/config"
        "chainguard.dev/melange/pkg/sca"
+       "chainguard.dev/melange/pkg/tarball"
 
-       "chainguard.dev/apko/pkg/apk/tarball"
        "github.com/chainguard-dev/clog"
        "github.com/psanford/memfs"
        "go.opentelemetry.io/otel"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipeline.go 
new/melange-0.23.6/pkg/build/pipeline.go
--- old/melange-0.23.5/pkg/build/pipeline.go    2025-04-04 16:33:33.000000000 
+0200
+++ new/melange-0.23.6/pkg/build/pipeline.go    2025-04-11 21:28:28.000000000 
+0200
@@ -82,7 +82,7 @@
                config.SubstitutionPackageName:        pkg.Name,
                config.SubstitutionPackageVersion:     pkg.Version,
                config.SubstitutionPackageEpoch:       
strconv.FormatUint(pkg.Epoch, 10),
-               config.SubstitutionPackageFullVersion: fmt.Sprintf("%s-r%s", 
config.SubstitutionPackageVersion, config.SubstitutionPackageEpoch),
+               config.SubstitutionPackageFullVersion: fmt.Sprintf("%s-r%d", 
pkg.Version, pkg.Epoch),
                config.SubstitutionPackageSrcdir:      "/home/build",
                config.SubstitutionTargetsOutdir:      
"/home/build/melange-out",
                config.SubstitutionTargetsDestdir:     
fmt.Sprintf("/home/build/melange-out/%s", pkg.Name),
@@ -207,7 +207,7 @@
 
        // Pipelines can have their own environment variables, which override 
the global ones.
        envOverride := map[string]string{
-               "PATH": 
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+               "PATH": 
"/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin",
        }
 
        for k, v := range pipeline.Environment {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipelines/cmake/build.yaml 
new/melange-0.23.6/pkg/build/pipelines/cmake/build.yaml
--- old/melange-0.23.5/pkg/build/pipelines/cmake/build.yaml     2025-04-04 
16:33:33.000000000 +0200
+++ new/melange-0.23.6/pkg/build/pipelines/cmake/build.yaml     2025-04-11 
21:28:28.000000000 +0200
@@ -13,4 +13,4 @@
 
 pipeline:
   - runs: |
-      cmake --build ${{inputs.output-dir}}
+      VERBOSE=1 cmake --build ${{inputs.output-dir}}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/build/pipelines/git-checkout.yaml 
new/melange-0.23.6/pkg/build/pipelines/git-checkout.yaml
--- old/melange-0.23.5/pkg/build/pipelines/git-checkout.yaml    2025-04-04 
16:33:33.000000000 +0200
+++ new/melange-0.23.6/pkg/build/pipelines/git-checkout.yaml    2025-04-11 
21:28:28.000000000 +0200
@@ -187,7 +187,7 @@
 
           vr cd "$workdir"
           msg "tar -c . | tar -C \"$dest_fullpath\" -x"
-          ( tar -c . ; echo $? > "$rcfile") | tar -C "$dest_fullpath" -x
+          ( tar -c . ; echo $? > "$rcfile") | tar -C "$dest_fullpath" -x 
--no-same-owner
           read rc < "$rcfile" || fail "failed to read rc file"
           [ $rc -eq 0 ] || fail "tar creation in $workdir failed"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/melange-0.23.5/pkg/build/pipelines/goreleaser/README.md 
new/melange-0.23.6/pkg/build/pipelines/goreleaser/README.md
--- old/melange-0.23.5/pkg/build/pipelines/goreleaser/README.md 2025-04-04 
16:33:33.000000000 +0200
+++ new/melange-0.23.6/pkg/build/pipelines/goreleaser/README.md 1970-01-01 
01:00:00.000000000 +0100
@@ -1,23 +0,0 @@
-<!-- start:pipeline-reference-gen -->
-# Pipeline Reference
-
-
-- [goreleaser/build](#goreleaserbuild)
-
-## goreleaser/build
-
-Run a build using the GoReleaser
-
-### Inputs
-
-| Name | Required | Description | Default |
-| ---- | -------- | ----------- | ------- |
-| args | false | List of space-separated args to pass to the GoReleaser 
`release` command.  |  |
-| config-file | false | Path to the GoReleaser config file. If not specified, 
the default config file will be used.  |  |
-| output | true | Filename to use when writing the binary. The final install 
location inside the apk will be in /usr/bin by default.  | 
${{targets.contextdir}}/usr/bin/${{package.name}} |
-| skip | true | List of comma-separated skip values to pass to the GoReleaser 
`release` command.  | docker,ko,publish |
-| snapshot | false | If true, the GoReleaser `release` command will be run 
with the `--snapshot` flag.  | false |
-| working-dir | false | Top directory of the go module, this is where go.mod 
lives. Before buiding the go pipeline wil cd into this directory.  | . |
-
-
-<!-- end:pipeline-reference-gen -->
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/melange-0.23.5/pkg/build/pipelines/goreleaser/build.yaml 
new/melange-0.23.6/pkg/build/pipelines/goreleaser/build.yaml
--- old/melange-0.23.5/pkg/build/pipelines/goreleaser/build.yaml        
2025-04-04 16:33:33.000000000 +0200
+++ new/melange-0.23.6/pkg/build/pipelines/goreleaser/build.yaml        
1970-01-01 01:00:00.000000000 +0100
@@ -1,77 +0,0 @@
-name: Run a build using the GoReleaser
-
-needs:
-  packages:
-    - busybox
-    - ca-certificates-bundle
-    - goreleaser
-
-inputs:
-  args:
-    description: |
-      List of space-separated args to pass to the GoReleaser `release` command.
-    required: false
-  skip:
-    description: |
-      List of comma-separated skip values to pass to the GoReleaser `release` 
command.
-    required: true
-    default: "docker,ko,publish"
-  
-  output:
-    description: |
-      Filename to use when writing the binary. The final install location 
inside
-      the apk will be in /usr/bin by default.
-    required: true
-    default: "${{targets.contextdir}}/usr/bin/${{package.name}}"
-
-  snapshot:
-    description: |
-      If true, the GoReleaser `release` command will be run with the 
`--snapshot`
-      flag.
-    default: "false"
-
-  config-file: 
-    description: |
-      Path to the GoReleaser config file. If not specified, the default config
-      file will be used.
-    required: false
-
-  working-dir:
-    default: "."
-    required: false
-    description: |
-      Top directory of the go module, this is where go.mod lives. Before 
buiding
-      the go pipeline wil cd into this directory.
-
-pipeline:
-  - runs: |
-      #!/bin/sh
-      set -eux -o pipefail
-      goreleaser_flags="--clean --skip=${{inputs.skip}} ${{inputs.args}}"
-
-      DIR="$(dirname '${{inputs.output}}')"
-      mkdir -p $DIR
-      BASENAME="$(basename '${{inputs.output}}')"
-
-      # if working-dir is rather than "." cd into that directory
-      if [ "${{inputs.working-dir}}" != "." ]; then
-        cd ${{inputs.working-dir}}
-      fi
-
-      if [ "${{inputs.snapshot}}" = "true" ]; then
-        goreleaser_flags="$goreleaser_flags --snapshot"
-      fi
-
-      if [ -n "${{inputs.config-file}}" ]; then
-        goreleaser_flags="$goreleaser_flags --config ${{inputs.config-file}}"
-      fi
-    
-      goreleaser release $goreleaser_flags
-      echo "Copying binary to ${{inputs.output}}"
-      build_arch="${{build.arch}}"
-      if [ "$build_arch" = "aarch64" ]; then
-        build_arch="arm64"
-      elif [ "$build_arch" = "x86_64" ]; then
-        build_arch="amd64"
-      fi
-      install -Dm755 ./dist/${BASENAME}_linux_$build_arch*/* 
"${{inputs.output}}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/build/test.go 
new/melange-0.23.6/pkg/build/test.go
--- old/melange-0.23.5/pkg/build/test.go        2025-04-04 16:33:33.000000000 
+0200
+++ new/melange-0.23.6/pkg/build/test.go        2025-04-11 21:28:28.000000000 
+0200
@@ -311,7 +311,11 @@
 
        log.Infof("populating workspace %s from %s", t.WorkspaceDir, 
t.SourceDir)
 
-       fsys := apkofs.DirFS(t.SourceDir)
+       fsys := apkofs.DirFS(t.SourceDir, apkofs.WithCreateDir())
+
+       if fsys == nil {
+               return fmt.Errorf("unable to create/use directory %s", 
t.SourceDir)
+       }
 
        return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err 
error) error {
                if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/linter/linter.go 
new/melange-0.23.6/pkg/linter/linter.go
--- old/melange-0.23.5/pkg/linter/linter.go     2025-04-04 16:33:33.000000000 
+0200
+++ new/melange-0.23.6/pkg/linter/linter.go     2025-04-11 21:28:28.000000000 
+0200
@@ -794,7 +794,7 @@
                // without special casing it with the package name.
                if path == "sbin" || path == "bin" || path == "usr/sbin" {
                        if d.IsDir() || d.Type().IsRegular() {
-                               return fmt.Errorf("package contains non-symlink 
file at /sbin or /bin in violation of usrmerge")
+                               return fmt.Errorf("package contains non-symlink 
file at /sbin, /bin or /usr/sbin in violation of usrmerge")
                        } else {
                                return nil
                        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/sca/sca.go 
new/melange-0.23.6/pkg/sca/sca.go
--- old/melange-0.23.5/pkg/sca/sca.go   2025-04-04 16:33:33.000000000 +0200
+++ new/melange-0.23.6/pkg/sca/sca.go   2025-04-11 21:28:28.000000000 +0200
@@ -822,7 +822,9 @@
                        return nil
                }
 
-               if !strings.HasPrefix(path, "usr/bin/") && 
!strings.HasPrefix(path, "bin/") {
+               if !strings.HasPrefix(path, "usr/bin/") &&
+                       !strings.HasPrefix(path, "usr/local/bin/") &&
+                       !strings.HasPrefix(path, "usr/local/sbin/") {
                        return nil
                }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/sign/index.go 
new/melange-0.23.6/pkg/sign/index.go
--- old/melange-0.23.5/pkg/sign/index.go        1970-01-01 01:00:00.000000000 
+0100
+++ new/melange-0.23.6/pkg/sign/index.go        2025-04-11 21:28:28.000000000 
+0200
@@ -0,0 +1,146 @@
+// Copyright 2023 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 sign
+
+import (
+       "archive/tar"
+       "bytes"
+       "context"
+       "crypto"
+       "errors"
+       "fmt"
+       "io"
+       "os"
+       "path/filepath"
+       "strings"
+
+       "github.com/chainguard-dev/clog"
+       "github.com/klauspost/compress/gzip"
+       "github.com/psanford/memfs"
+
+       "chainguard.dev/apko/pkg/apk/signature"
+       "chainguard.dev/melange/pkg/tarball"
+)
+
+func SignIndex(ctx context.Context, signingKey string, indexFile string) error 
{
+       log := clog.FromContext(ctx)
+       is, err := indexIsAlreadySigned(indexFile)
+       if err != nil {
+               return err
+       }
+       if is {
+               log.Infof("index %s is already signed, doing nothing", 
indexFile)
+               return nil
+       }
+
+       log.Infof("signing index %s with key %s", indexFile, signingKey)
+
+       indexData, err := os.ReadFile(indexFile)
+       if err != nil {
+               return fmt.Errorf("unable to read index for signing: %w", err)
+       }
+
+       sigFS := memfs.New()
+       indexDigest, err := HashData(indexData, crypto.SHA256)
+       if err != nil {
+               return err
+       }
+
+       sigData, err := signature.RSASignDigest(indexDigest, crypto.SHA256, 
signingKey, "")
+       if err != nil {
+               return fmt.Errorf("unable to sign index: %w", err)
+       }
+
+       log.Infof("appending signature RSA256 to index %s", indexFile)
+
+       if err := sigFS.WriteFile(fmt.Sprintf(".SIGN.RSA256.%s.pub", 
filepath.Base(signingKey)), sigData, 0644); err != nil {
+               return fmt.Errorf("unable to append signature: %w", err)
+       }
+
+       // prepare control.tar.gz
+       multitarctx, err := tarball.NewContext(
+               tarball.WithOverrideUIDGID(0, 0),
+               tarball.WithOverrideUname("root"),
+               tarball.WithOverrideGname("root"),
+               tarball.WithSkipClose(true),
+       )
+       if err != nil {
+               return fmt.Errorf("unable to build tarball context: %w", err)
+       }
+
+       log.Infof("writing signed index to %s", indexFile)
+
+       var sigBuffer bytes.Buffer
+       if err := multitarctx.WriteTargz(ctx, &sigBuffer, sigFS, sigFS); err != 
nil {
+               return fmt.Errorf("unable to write signature tarball: %w", err)
+       }
+
+       idx, err := os.Create(indexFile)
+       if err != nil {
+               return fmt.Errorf("unable to open index for writing: %w", err)
+       }
+       defer idx.Close()
+
+       if _, err := io.Copy(idx, &sigBuffer); err != nil {
+               return fmt.Errorf("unable to write index signature: %w", err)
+       }
+
+       if _, err := idx.Write(indexData); err != nil {
+               return fmt.Errorf("unable to write index data: %w", err)
+       }
+
+       log.Infof("signed index %s with key %s", indexFile, signingKey)
+
+       return nil
+}
+
+func indexIsAlreadySigned(indexFile string) (bool, error) {
+       index, err := os.Open(indexFile)
+       if err != nil {
+               return false, fmt.Errorf("cannot open index file %s: %w", 
indexFile, err)
+       }
+       defer index.Close()
+
+       gzi, err := gzip.NewReader(index)
+       if err != nil {
+               return false, fmt.Errorf("cannot open index file %s as gzip: 
%w", indexFile, err)
+       }
+       defer gzi.Close()
+
+       tari := tar.NewReader(gzi)
+       for {
+               hdr, err := tari.Next()
+               if errors.Is(err, io.EOF) {
+                       break
+               }
+               if err != nil {
+                       return false, fmt.Errorf("cannot read tar index %s: 
%w", indexFile, err)
+               }
+
+               if strings.HasPrefix(hdr.Name, ".SIGN.RSA") {
+                       return true, nil
+               }
+       }
+
+       return false, nil
+}
+
+func HashData(data []byte, digestType crypto.Hash) ([]byte, error) {
+       digest := digestType.New()
+       if n, err := digest.Write(data); err != nil || n != len(data) {
+               return nil, fmt.Errorf("unable to hash data: %w", err)
+       }
+       return digest.Sum(nil), nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/tarball.go 
new/melange-0.23.6/pkg/tarball/tarball.go
--- old/melange-0.23.5/pkg/tarball/tarball.go   1970-01-01 01:00:00.000000000 
+0100
+++ new/melange-0.23.6/pkg/tarball/tarball.go   2025-04-11 21:28:28.000000000 
+0200
@@ -0,0 +1,132 @@
+// Copyright 2022, 2023 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 tarball
+
+import (
+       "archive/tar"
+       "time"
+)
+
+type Context struct {
+       SourceDateEpoch time.Time
+
+       OverrideUIDGID bool
+       UID            int
+       GID            int
+       OverrideUname  string
+       OverrideGname  string
+       SkipClose      bool
+       UseChecksums   bool
+       remapUIDs      map[int]int
+       remapGIDs      map[int]int
+       overridePerms  map[string]tar.Header
+}
+
+type Option func(*Context) error
+
+// Generates a new context from a set of options.
+func NewContext(opts ...Option) (*Context, error) {
+       ctx := Context{}
+
+       for _, opt := range opts {
+               if err := opt(&ctx); err != nil {
+                       return nil, err
+               }
+       }
+
+       return &ctx, nil
+}
+
+// Sets SourceDateEpoch for Context.
+func WithSourceDateEpoch(t time.Time) Option {
+       return func(ctx *Context) error {
+               ctx.SourceDateEpoch = t
+
+               return nil
+       }
+}
+
+// WithOverrideUIDGID sets the UID/GID to override with for all files for 
Context.
+func WithOverrideUIDGID(uid, gid int) Option {
+       return func(ctx *Context) error {
+               ctx.OverrideUIDGID = true
+               ctx.UID = uid
+               ctx.GID = gid
+               return nil
+       }
+}
+
+// WithOverridePerms sets the UID/GID and file permissions to override with 
for specific files Context.
+func WithOverridePerms(files []tar.Header) Option {
+       return func(ctx *Context) error {
+               overrides := map[string]tar.Header{}
+               for _, f := range files {
+                       overrides[f.Name] = f
+               }
+               ctx.overridePerms = overrides
+               return nil
+       }
+}
+
+// WithOverrideUname sets the Uname to use with Context.
+func WithOverrideUname(uname string) Option {
+       return func(ctx *Context) error {
+               ctx.OverrideUname = uname
+               return nil
+       }
+}
+
+// WithRemapUIDs sets a UID remapping in the Context.
+func WithRemapUIDs(uids map[int]int) Option {
+       return func(ctx *Context) error {
+               ctx.remapUIDs = uids
+               return nil
+       }
+}
+
+// WithRemapGIDs sets a GID remapping in the Context.
+func WithRemapGIDs(gids map[int]int) Option {
+       return func(ctx *Context) error {
+               ctx.remapGIDs = gids
+               return nil
+       }
+}
+
+// WithOverrideGname sets the Gname to use with Context.
+func WithOverrideGname(gname string) Option {
+       return func(ctx *Context) error {
+               ctx.OverrideGname = gname
+               return nil
+       }
+}
+
+// WithSkipClose is used to determine whether the tar stream
+// should be closed.  For concatenated tar streams such as APKv2
+// containers, only the final tar stream should be closed.
+func WithSkipClose(skipClose bool) Option {
+       return func(ctx *Context) error {
+               ctx.SkipClose = skipClose
+               return nil
+       }
+}
+
+// WithUseChecksums is used to determine whether the tar stream
+// should have the APK-TOOLS.checksum.SHA1 extension.
+func WithUseChecksums(useChecksums bool) Option {
+       return func(ctx *Context) error {
+               ctx.UseChecksums = useChecksums
+               return nil
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/testdata/bar/bar.txt 
new/melange-0.23.6/pkg/tarball/testdata/bar/bar.txt
--- old/melange-0.23.5/pkg/tarball/testdata/bar/bar.txt 1970-01-01 
01:00:00.000000000 +0100
+++ new/melange-0.23.6/pkg/tarball/testdata/bar/bar.txt 2025-04-11 
21:28:28.000000000 +0200
@@ -0,0 +1 @@
+bar
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/testdata/foo/foo.txt 
new/melange-0.23.6/pkg/tarball/testdata/foo/foo.txt
--- old/melange-0.23.5/pkg/tarball/testdata/foo/foo.txt 1970-01-01 
01:00:00.000000000 +0100
+++ new/melange-0.23.6/pkg/tarball/testdata/foo/foo.txt 2025-04-11 
21:28:28.000000000 +0200
@@ -0,0 +1 @@
+foo
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/write.go 
new/melange-0.23.6/pkg/tarball/write.go
--- old/melange-0.23.5/pkg/tarball/write.go     1970-01-01 01:00:00.000000000 
+0100
+++ new/melange-0.23.6/pkg/tarball/write.go     2025-04-11 21:28:28.000000000 
+0200
@@ -0,0 +1,339 @@
+// Copyright 2022, 2023 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 tarball
+
+import (
+       "archive/tar"
+       "compress/gzip"
+       "context"
+       "crypto/sha1" //nolint:gosec
+       "encoding/hex"
+       "fmt"
+       "io"
+       "io/fs"
+       "os"
+       "syscall"
+
+       "go.opentelemetry.io/otel"
+       "golang.org/x/sys/unix"
+
+       apkfs "chainguard.dev/apko/pkg/apk/fs"
+       "chainguard.dev/apko/pkg/apk/passwd"
+)
+
+const xattrTarPAXRecordsPrefix = "SCHILY.xattr."
+
+func hasHardlinks(fi fs.FileInfo) bool {
+       if stat := fi.Sys(); stat != nil {
+               si, ok := stat.(*syscall.Stat_t)
+               if !ok {
+                       return false
+               }
+
+               // if we don't have inodes, we just assume the filesystem
+               // does not support hardlinks
+               if si == nil {
+                       return false
+               }
+
+               return si.Nlink > 1
+       }
+
+       return false
+}
+
+func getInodeFromFileInfo(fi fs.FileInfo) (uint64, error) {
+       if stat := fi.Sys(); stat != nil {
+               si, ok := stat.(*syscall.Stat_t)
+               if !ok {
+                       return 0, fmt.Errorf("unable to stat underlying file")
+               }
+
+               // if we don't have inodes, we just assume the filesystem
+               // does not support hardlinks
+               if si == nil {
+                       return 0, fmt.Errorf("unable to stat underlying file")
+               }
+
+               return si.Ino, nil
+       }
+
+       return 0, fmt.Errorf("unable to stat underlying file")
+}
+
+func (c *Context) writeTar(ctx context.Context, tw *tar.Writer, fsys fs.FS, 
users, groups map[int]string) error { //nolint:gocyclo
+       if users == nil {
+               users = map[int]string{}
+       }
+       if groups == nil {
+               groups = map[int]string{}
+       }
+       seenFiles := map[uint64]string{}
+       // set this once, to make it easy to look up later
+       if c.overridePerms == nil {
+               c.overridePerms = map[string]tar.Header{}
+       }
+
+       buf := make([]byte, 1<<20)
+
+       if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err 
error) error {
+               if err := ctx.Err(); err != nil {
+                       return err
+               }
+               // skip the root path, superfluous
+               if path == "." {
+                       return nil
+               }
+
+               if err != nil {
+                       return err
+               }
+
+               info, err := d.Info()
+               if err != nil {
+                       return err
+               }
+
+               var (
+                       link         string
+                       major, minor uint32
+                       isCharDevice bool
+               )
+               if info.Mode()&os.ModeSymlink == os.ModeSymlink {
+                       rlfs, ok := fsys.(apkfs.ReadLinkFS)
+                       if !ok {
+                               return fmt.Errorf("readlink not supported by 
this fs: path (%s)", path)
+                       }
+
+                       if link, err = rlfs.Readlink(path); err != nil {
+                               return err
+                       }
+               }
+
+               if info.Mode()&os.ModeCharDevice == os.ModeCharDevice {
+                       rlfs, ok := fsys.(apkfs.ReadnodFS)
+                       if !ok {
+                               return fmt.Errorf("read character device not 
supported by this fs: path (%s) %#v %#v", path, info, fsys)
+                       }
+                       isCharDevice = true
+                       dev, err := rlfs.Readnod(path)
+                       if err != nil {
+                               return err
+                       }
+                       major = unix.Major(uint64(dev))
+                       minor = unix.Minor(uint64(dev))
+               }
+
+               header, err := tar.FileInfoHeader(info, link)
+               if err != nil {
+                       return err
+               }
+               // devices
+               if isCharDevice {
+                       header.Devmajor = int64(major)
+                       header.Devminor = int64(minor)
+               }
+               // work around some weirdness, without this we wind up with 
just the basename
+               header.Name = path
+
+               // if SourceDateEpoch is set explicitly, overwrite the 
timestamps, otherwise propagate modtime
+               // (this option is unused in apko but melange uses this, so we 
support it for now)
+               header.AccessTime = c.SourceDateEpoch
+               header.ModTime = c.SourceDateEpoch
+               header.ChangeTime = c.SourceDateEpoch
+
+               if uid, ok := c.remapUIDs[header.Uid]; ok {
+                       header.Uid = uid
+               }
+
+               if gid, ok := c.remapGIDs[header.Gid]; ok {
+                       header.Gid = gid
+               }
+
+               if name, ok := users[header.Uid]; ok {
+                       header.Uname = name
+               }
+               if name, ok := groups[header.Gid]; ok {
+                       header.Gname = name
+               }
+
+               if c.OverrideUIDGID {
+                       header.Uid = c.UID
+                       header.Gid = c.GID
+               }
+
+               if c.OverrideUname != "" {
+                       header.Uname = c.OverrideUname
+               }
+
+               if c.OverrideGname != "" {
+                       header.Gname = c.OverrideGname
+               }
+
+               // look for the override perms with or without the leading /
+               if h, ok := c.overridePerms[header.Name]; ok {
+                       header.Mode = h.Mode
+                       header.Uid = h.Uid
+                       header.Gid = h.Gid
+                       header.Uname = h.Uname
+                       header.Gname = h.Gname
+               }
+               if h, ok := c.overridePerms["/"+header.Name]; ok {
+                       header.Mode = h.Mode
+                       header.Uid = h.Uid
+                       header.Gid = h.Gid
+                       header.Uname = h.Uname
+                       header.Gname = h.Gname
+               }
+
+               if link != "" {
+                       header.Typeflag = tar.TypeSymlink
+               }
+               if !info.IsDir() && hasHardlinks(info) {
+                       inode, err := getInodeFromFileInfo(info)
+                       if err != nil {
+                               return err
+                       }
+
+                       if oldpath, ok := seenFiles[inode]; ok {
+                               header.Typeflag = tar.TypeLink
+                               header.Linkname = oldpath
+                               header.Size = 0
+                       } else {
+                               seenFiles[inode] = header.Name
+                       }
+               }
+
+               if header.PAXRecords == nil {
+                       header.PAXRecords = map[string]string{}
+               }
+               if c.UseChecksums {
+                       if link != "" {
+                               linkDigest := sha1.Sum([]byte(link)) 
//nolint:gosec
+                               linkChecksum := 
hex.EncodeToString(linkDigest[:])
+                               header.PAXRecords["APK-TOOLS.checksum.SHA1"] = 
linkChecksum
+                       } else if info.Mode().IsRegular() {
+                               data, err := fsys.Open(path)
+                               if err != nil {
+                                       return err
+                               }
+                               defer data.Close()
+
+                               fileDigest := sha1.New() //nolint:gosec
+                               if _, err := io.CopyBuffer(fileDigest, data, 
buf); err != nil {
+                                       return err
+                               }
+
+                               fileChecksum := 
hex.EncodeToString(fileDigest.Sum(nil))
+                               header.PAXRecords["APK-TOOLS.checksum.SHA1"] = 
fileChecksum
+                       }
+               }
+
+               // only capture xattrs for real objects in the FS
+               if header.Typeflag == tar.TypeReg || header.Typeflag == 
tar.TypeDir {
+                       xfs, ok := fsys.(apkfs.XattrFS)
+                       if ok {
+                               xattrs, err := xfs.ListXattrs(path)
+                               // we can ignore errors
+                               if err == nil && xattrs != nil {
+                                       for name, value := range xattrs {
+                                               
header.PAXRecords[xattrTarPAXRecordsPrefix+name] = string(value)
+                                       }
+                               }
+                       }
+               }
+
+               if err := tw.WriteHeader(header); err != nil {
+                       return err
+               }
+
+               if info.Mode().IsRegular() && header.Size > 0 {
+                       data, err := fsys.Open(path)
+                       if err != nil {
+                               return err
+                       }
+
+                       defer data.Close()
+
+                       if _, err := io.CopyBuffer(tw, data, buf); err != nil {
+                               return err
+                       }
+               }
+
+               return nil
+       }); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// WriteArchive writes a tarball to the provided io.Writer from the provided 
fs.FS.
+// To override permissions, set the OverridePerms when creating the Context.
+// If you need to get multiple filesystems, merge them prior to calling 
WriteArchive.
+//
+// Deprecated: Use WriteTargz or WriteTar instead.
+func (c *Context) WriteArchive(dst io.Writer, src fs.FS) error {
+       return c.WriteTargz(context.Background(), dst, src, src)
+}
+
+// WriteTargz writes a gzipped tarball to the provided io.Writer from the 
provided fs.FS.
+// To override permissions, set the OverridePerms when creating the Context.
+// If you need to get multiple filesystems, merge them prior to calling 
WriteArchive.
+// userinfosrc should be a fs which can provide an optionally provide an 
etc/passwd and etc/group file.
+// The etc/passwd and etc/group file provide username and group name mappings 
for the tar.
+func (c *Context) WriteTargz(ctx context.Context, dst io.Writer, src fs.FS, 
userinfofs fs.FS) error {
+       ctx, span := otel.Tracer("go-apk").Start(ctx, "WriteTargz")
+       defer span.End()
+
+       gzw := gzip.NewWriter(dst)
+       defer gzw.Close()
+
+       return c.WriteTar(ctx, gzw, src, userinfofs)
+}
+
+// WriteTar writes a tarball to the provided io.Writer from the provided fs.FS.
+// To override permissions, set the OverridePerms when creating the Context.
+// If you need to get multiple filesystems, merge them prior to calling 
WriteArchive.
+// userinfosrc should be a fs which can provide an optionally provide an 
etc/passwd and etc/group file.
+// The etc/passwd and etc/group file provide username and group name mappings 
for the tar.
+func (c *Context) WriteTar(ctx context.Context, dst io.Writer, src fs.FS, 
userinfosrc fs.FS) error {
+       ctx, span := otel.Tracer("go-apk").Start(ctx, "WriteTar")
+       defer span.End()
+
+       tw := tar.NewWriter(dst)
+       if !c.SkipClose {
+               defer tw.Close()
+       } else {
+               defer tw.Flush()
+       }
+
+       // get the uname and gname maps
+       usersFile, _ := passwd.ReadUserFile(userinfosrc, "etc/passwd")
+       groupsFile, _ := passwd.ReadGroupFile(userinfosrc, "etc/group")
+       users := map[int]string{}
+       groups := map[int]string{}
+       for _, u := range usersFile.Entries {
+               users[int(u.UID)] = u.UserName
+       }
+       for _, g := range groupsFile.Entries {
+               groups[int(g.GID)] = g.GroupName
+       }
+       if err := c.writeTar(ctx, tw, src, users, groups); err != nil {
+               return fmt.Errorf("writing TAR archive failed: %w", err)
+       }
+
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/melange-0.23.5/pkg/tarball/write_test.go 
new/melange-0.23.6/pkg/tarball/write_test.go
--- old/melange-0.23.5/pkg/tarball/write_test.go        1970-01-01 
01:00:00.000000000 +0100
+++ new/melange-0.23.6/pkg/tarball/write_test.go        2025-04-11 
21:28:28.000000000 +0200
@@ -0,0 +1,49 @@
+package tarball
+
+import (
+       "archive/tar"
+       "bytes"
+       "context"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+
+       "chainguard.dev/apko/pkg/apk/fs"
+)
+
+func TestWriteTar(t *testing.T) {
+       var buf bytes.Buffer
+       var (
+               m    = fs.NewMemFS()
+               dir  = "a"
+               file = "a/b"
+       )
+       err := m.MkdirAll(dir, 0o755)
+       require.NoError(t, err, "error creating dir %s", dir)
+       err = m.WriteFile(file, []byte("hello world"), 0o644)
+       require.NoError(t, err, "error creating file %s", file)
+
+       // set xattrs, then see if the tar gets it
+       err = m.SetXattr(dir, "user.dir", []byte("foo"))
+       require.NoError(t, err, "error setting xattr on %s", dir)
+       err = m.SetXattr(file, "user.file", []byte("bar"))
+       require.NoError(t, err, "error setting xattr on %s", file)
+       ctx := Context{}
+       tw := tar.NewWriter(&buf)
+       err = ctx.writeTar(context.TODO(), tw, m, nil, nil)
+       require.NoError(t, err, "error writing tar")
+       err = tw.Close()
+       require.NoError(t, err, "error closing tar writer")
+
+       // now should be able to read the tar and check the xattrs
+       tr := tar.NewReader(&buf)
+       hdr, err := tr.Next()
+       require.NoError(t, err, "error reading dir tar header")
+       require.Equal(t, dir, hdr.Name, "tar dir header name mismatch")
+       require.Equal(t, "foo", 
hdr.PAXRecords[xattrTarPAXRecordsPrefix+"user.dir"], "tar header for dir xattr 
mismatch")
+
+       hdr, err = tr.Next()
+       require.NoError(t, err, "error reading file tar header")
+       require.Equal(t, file, hdr.Name, "tar file header name mismatch")
+       require.Equal(t, "bar", 
hdr.PAXRecords[xattrTarPAXRecordsPrefix+"user.file"], "tar header for file 
xattr mismatch")
+}

++++++ melange.obsinfo ++++++
--- /var/tmp/diff_new_pack.k57TwO/_old  2025-04-14 12:59:25.514876992 +0200
+++ /var/tmp/diff_new_pack.k57TwO/_new  2025-04-14 12:59:25.518877160 +0200
@@ -1,5 +1,5 @@
 name: melange
-version: 0.23.5
-mtime: 1743777213
-commit: a5d28473b9091cf7a45a0864bb149ae9fddfff99
+version: 0.23.6
+mtime: 1744399708
+commit: 1c075e12cf123600b57cfb8bb66b9aa107a37e08
 

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

Reply via email to