Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package werf for openSUSE:Factory checked in 
at 2026-07-31 15:29:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/werf (Old)
 and      /work/SRC/openSUSE:Factory/.werf.new.2004 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "werf"

Fri Jul 31 15:29:42 2026 rev:80 rq:1368472 version:2.74.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/werf/werf.changes        2026-07-21 
23:11:41.260938246 +0200
+++ /work/SRC/openSUSE:Factory/.werf.new.2004/werf.changes      2026-07-31 
16:10:55.811990786 +0200
@@ -1,0 +2,17 @@
+Thu Jul 30 05:35:05 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 2.74.0:
+  * Features
+    - deploy: add werf.io/resource-policy annotation (#7613)
+      (72ae0f2)
+  * Bug Fixes
+    - build: forward include.path to submodule sync/update commands
+      (#7660) (5ab32d2)
+    - build: send valid empty tar for docker from:scratch import
+      (#7609) (1c59585)
+    - bundle: update .Values.global.werf.images during bundle copy
+      (#7600) (cf2fb96)
+    - deploy: werf.io/resource-policy should only respect
+      skip-delete from live (#7623) (f261028)
+
+-------------------------------------------------------------------

Old:
----
  werf-2.73.2.obscpio

New:
----
  werf-2.74.0.obscpio

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

Other differences:
------------------
++++++ werf.spec ++++++
--- /var/tmp/diff_new_pack.CYJ1Gd/_old  2026-07-31 16:10:56.544016251 +0200
+++ /var/tmp/diff_new_pack.CYJ1Gd/_new  2026-07-31 16:10:56.548016389 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           werf
-Version:        2.73.2
+Version:        2.74.0
 Release:        0
 Summary:        CLI for the Werf CI/CD system
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.CYJ1Gd/_old  2026-07-31 16:10:56.592017920 +0200
+++ /var/tmp/diff_new_pack.CYJ1Gd/_new  2026-07-31 16:10:56.604018338 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/werf/werf.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v2.73.2</param>
+    <param name="revision">refs/tags/v2.74.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.CYJ1Gd/_old  2026-07-31 16:10:56.636019451 +0200
+++ /var/tmp/diff_new_pack.CYJ1Gd/_new  2026-07-31 16:10:56.648019868 +0200
@@ -3,6 +3,6 @@
                 <param name="url">https://github.com/werf/werf</param>
               <param 
name="changesrevision">508fb9aee29baca228674ba7a3d2d4fa1f5844d5</param></service><service
 name="tar_scm">
                 <param name="url">https://github.com/werf/werf.git</param>
-              <param 
name="changesrevision">0b7ec198dac3e05320e8d77cd17492ed5fc18750</param></service></servicedata>
+              <param 
name="changesrevision">7585cfbf15796fbe86e33df695a3c5259aa40382</param></service></servicedata>
 (No newline at EOF)
 

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

++++++ werf-2.73.2.obscpio -> werf-2.74.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/werf-2.73.2/.agents/skills/pull-request-name/SKILL.md 
new/werf-2.74.0/.agents/skills/pull-request-name/SKILL.md
--- old/werf-2.73.2/.agents/skills/pull-request-name/SKILL.md   2026-07-02 
10:37:28.000000000 +0200
+++ new/werf-2.74.0/.agents/skills/pull-request-name/SKILL.md   2026-07-14 
22:07:35.000000000 +0200
@@ -1,15 +1,56 @@
 ---
 name: pull-request-name
-description: Generates Pull Request titles according to werf conventions. Use 
when creating or updating a PR.
+description: Generates Pull Request titles and descriptions according to werf 
conventions. Use when creating or updating a PR.
 ---
 
-# Pull Request Naming
+# Pull Request Conventions
 
-## Instructions
+## Defaults
+
+- Always create PRs as draft (`gh pr create --draft`). The author marks it 
ready for review manually.
+
+## Title
 
 1. Read types, scopes, and formatting rules from `CONTRIBUTING.md#conventions`.
 2. The PR title should mirror the header of the main commit in the PR.
 3. Format the title as `<type>(<scope>): <subject>`.
 4. Keep the total length ≤ 72 characters.
 5. Nested scopes are allowed and encouraged.
-6. Output ONLY the title, with no additional text, quotes, or formatting.
\ No newline at end of file
+
+## Description
+
+Use the following structure. Every section is **mandatory** — omit a section 
only when it genuinely does not apply (e.g. single-line typo fix may skip 
*Review focus / risks*).
+
+```
+## Summary
+
+<1-3 sentence high-level overview of what the PR does and why it exists.>
+
+## Key changes
+
+- <concrete change 1>
+- <concrete change 2>
+- …
+
+## Why
+
+<Motivation: what problem this solves, what maintenance/UX/perf gain it 
brings.>
+
+## Review focus / risks
+
+- <area or file that deserves careful review>
+- <potential risk or side-effect>
+```
+
+### Rules
+
+- Language: match the project's primary language (English by default).
+- Be specific: name files, modules, functions — not "updated some code".
+- *Key changes*: group related items; use sub-bullets for detail when helpful.
+- *Why*: explain the reason, not what changed (that's *Key changes*).
+- *Review focus / risks*: guide the reviewer — call out non-obvious 
consequences, large generated diffs, breaking changes.
+- No AI-slop filler ("This PR improves the codebase…"). Every sentence must 
carry information.
+
+## Output
+
+When generating only the title (e.g. for `gh pr edit --title`), output ONLY 
the title, with no additional text, quotes, or formatting.
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/CHANGELOG.md new/werf-2.74.0/CHANGELOG.md
--- old/werf-2.73.2/CHANGELOG.md        2026-07-02 10:37:28.000000000 +0200
+++ new/werf-2.74.0/CHANGELOG.md        2026-07-14 22:07:35.000000000 +0200
@@ -1,5 +1,20 @@
 # Changelog
 
+## [2.74.0](https://github.com/werf/werf/compare/v2.73.2...v2.74.0) 
(2026-07-14)
+
+
+### Features
+
+* **deploy:** add `werf.io/resource-policy` annotation 
([#7613](https://github.com/werf/werf/issues/7613)) 
([72ae0f2](https://github.com/werf/werf/commit/72ae0f24ea745a81c61656d1c25df8dace2538f6))
+
+
+### Bug Fixes
+
+* **build:** forward include.path to submodule sync/update commands 
([#7660](https://github.com/werf/werf/issues/7660)) 
([5ab32d2](https://github.com/werf/werf/commit/5ab32d2fd7598bfcdb598f1f1217138e15a9e4b2))
+* **build:** send valid empty tar for docker from:scratch import 
([#7609](https://github.com/werf/werf/issues/7609)) 
([1c59585](https://github.com/werf/werf/commit/1c5958506a960167c76dede2cfa88ed130db1d12))
+* **bundle:** update .Values.global.werf.images during bundle copy 
([#7600](https://github.com/werf/werf/issues/7600)) 
([cf2fb96](https://github.com/werf/werf/commit/cf2fb96d2de01c13d8c733589cb02262eb9521b1))
+* **deploy:** `werf.io/resource-policy` should only respect skip-delete from 
live ([#7623](https://github.com/werf/werf/issues/7623)) 
([f261028](https://github.com/werf/werf/commit/f261028ca4b92b48ef9e26c353c0adade1e331ea))
+
 ## [2.73.2](https://github.com/werf/werf/compare/v2.73.1...v2.73.2) 
(2026-07-01)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/go.mod new/werf-2.74.0/go.mod
--- old/werf-2.73.2/go.mod      2026-07-02 10:37:28.000000000 +0200
+++ new/werf-2.74.0/go.mod      2026-07-14 22:07:35.000000000 +0200
@@ -65,7 +65,7 @@
        github.com/werf/kubedog-for-werf-helm v0.0.0-20241217155728-9d45c48b82b6
        github.com/werf/lockgate v0.1.1
        github.com/werf/logboek v0.7.1
-       github.com/werf/nelm v1.24.4-0.20260618090926-7f5713b27219
+       github.com/werf/nelm v1.26.1-0.20260706091654-ddfb620f23e2
        github.com/werf/nelm-for-werf-helm v0.0.0-20241217155925-b0e6734d1dbf
        go.opentelemetry.io/otel v1.24.0
        go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/go.sum new/werf-2.74.0/go.sum
--- old/werf-2.73.2/go.sum      2026-07-02 10:37:28.000000000 +0200
+++ new/werf-2.74.0/go.sum      2026-07-14 22:07:35.000000000 +0200
@@ -1416,8 +1416,8 @@
 github.com/werf/lockgate v0.1.1/go.mod 
h1:0yIFSLq9ausy6ejNxF5uUBf/Ib6daMAfXuCaTMZJzIE=
 github.com/werf/logboek v0.7.1 h1:Ck8L7LVmj/wyrW/jk+EkkpGwdmpkOrsWAwoIgLHupc8=
 github.com/werf/logboek v0.7.1/go.mod 
h1:Gez5J4bxekyr6MxTmIJyId1F61rpO+0/V4vjCIEIZmk=
-github.com/werf/nelm v1.24.4-0.20260618090926-7f5713b27219 
h1:wZjBQJnQPMZRdhtS1/4vOQfuoFSybY1BHtbkY+AUg+E=
-github.com/werf/nelm v1.24.4-0.20260618090926-7f5713b27219/go.mod 
h1:D3uU5e1dSBc0l0oo/iB6SjM2sLaXICqRDSWXAYF+vpk=
+github.com/werf/nelm v1.26.1-0.20260706091654-ddfb620f23e2 
h1:k72lZ9gEm459QHVhdDfz7Uxp8FJdlp9sBJjPKfJFISc=
+github.com/werf/nelm v1.26.1-0.20260706091654-ddfb620f23e2/go.mod 
h1:D3uU5e1dSBc0l0oo/iB6SjM2sLaXICqRDSWXAYF+vpk=
 github.com/werf/nelm-for-werf-helm v0.0.0-20241217155925-b0e6734d1dbf 
h1:K51qz209c1yJgKzPw8AeS72T21F/ACp0VI3RJvT4THA=
 github.com/werf/nelm-for-werf-helm v0.0.0-20241217155925-b0e6734d1dbf/go.mod 
h1:7RJXSGPKKPEvfPqrTwNA8jT7y52O0ebwhSbSn29ESMA=
 github.com/xanzy/go-gitlab v0.31.0/go.mod 
h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/deploy/bundles/copy_test.go 
new/werf-2.74.0/pkg/deploy/bundles/copy_test.go
--- old/werf-2.73.2/pkg/deploy/bundles/copy_test.go     2026-07-02 
10:37:28.000000000 +0200
+++ new/werf-2.74.0/pkg/deploy/bundles/copy_test.go     2026-07-14 
22:07:35.000000000 +0200
@@ -442,6 +442,118 @@
                        
Expect(registryClient.ImagesByReference["registry2.example.com/group2/testproject2:tag-3"]).To(Equal([]byte(`image-3-bytes`)))
                }
        })
+
+       It("should copy remote to remote updating global werf values", func() {
+               ch := &chart.Chart{
+                       Metadata: &chart.Metadata{
+                               APIVersion: "v2",
+                               Name:       "testproject",
+                               Version:    "1.2.3",
+                               Type:       "application",
+                       },
+                       Values: map[string]interface{}{
+                               "werf": map[string]interface{}{
+                                       "image": map[string]interface{}{
+                                               "myapp": 
"registry.example.com/group/testproject:tag-1",
+                                       },
+                                       "repo": 
"registry.example.com/group/testproject",
+                               },
+                               "global": map[string]interface{}{
+                                       "werf": map[string]interface{}{
+                                               "repo": 
"registry.example.com/group/testproject",
+                                               "images": 
map[string]interface{}{
+                                                       "myapp": 
map[string]interface{}{
+                                                               "registry":     
  "registry.example.com",
+                                                               "namespace":    
  "group",
+                                                               "name":         
  "testproject",
+                                                               "tag":          
  "tag-1",
+                                                               "digest":       
  "sha256:abcdef1234567890",
+                                                               "tag_digest":   
  "tag-1@sha256:abcdef1234567890",
+                                                               "image":        
  "registry.example.com/group/testproject",
+                                                               "repository":   
  "group/testproject",
+                                                               "ref":          
  "registry.example.com/group/testproject:tag-1@sha256:abcdef1234567890",
+                                                               "ref_tag":      
  "registry.example.com/group/testproject:tag-1",
+                                                               
"repository_ref": "group/testproject:tag-1@sha256:abcdef1234567890",
+                                                               
"repository_tag": "group/testproject:tag-1",
+                                                               "name_ref":     
  "testproject:tag-1@sha256:abcdef1234567890",
+                                                               "name_tag":     
  "testproject:tag-1",
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       Raw: []*chart.File{
+                               {
+                                       Name: "values.yaml",
+                                       Data: []byte(`
+werf:
+  image:
+    myapp: registry.example.com/group/testproject:tag-1
+  repo: registry.example.com/group/testproject
+global:
+  werf:
+    repo: registry.example.com/group/testproject
+    images:
+      myapp:
+        registry: registry.example.com
+        namespace: group
+        name: testproject
+        tag: tag-1
+        digest: "sha256:abcdef1234567890"
+        tag_digest: "tag-1@sha256:abcdef1234567890"
+        image: registry.example.com/group/testproject
+        repository: group/testproject
+        ref: 
"registry.example.com/group/testproject:tag-1@sha256:abcdef1234567890"
+        ref_tag: "registry.example.com/group/testproject:tag-1"
+        repository_ref: "group/testproject:tag-1@sha256:abcdef1234567890"
+        repository_tag: "group/testproject:tag-1"
+        name_ref: "testproject:tag-1@sha256:abcdef1234567890"
+        name_tag: "testproject:tag-1"
+`),
+                               },
+                       },
+               }
+
+               bundlesRegistryClient := NewBundlesRegistryClientStub()
+               registryClient := NewDockerRegistryStub()
+
+               fromAddr, err := 
bundles_registry.ParseAddr("registry.example.com/group/testproject:1.2.3")
+               Expect(err).NotTo(HaveOccurred())
+               from := NewRemoteBundle(fromAddr.RegistryAddress, 
bundlesRegistryClient, registryClient)
+               
bundlesRegistryClient.StubCharts[fromAddr.RegistryAddress.FullName()] = ch
+               
registryClient.ImagesByReference["registry.example.com/group/testproject:tag-1"]
 = []byte(`image-1-bytes`)
+
+               toAddr, err := 
bundles_registry.ParseAddr("registry2.example.com/org/newproject:4.5.6")
+               Expect(err).NotTo(HaveOccurred())
+               to := NewRemoteBundle(toAddr.RegistryAddress, 
bundlesRegistryClient, registryClient)
+
+               Expect(from.CopyTo(ctx, to, copyToOptions{})).To(Succeed())
+
+               newCh := 
bundlesRegistryClient.StubCharts[toAddr.RegistryAddress.FullName()]
+               Expect(newCh).NotTo(BeNil())
+
+               globalVals := newCh.Values["global"].(map[string]interface{})
+               globalWerfVals := globalVals["werf"].(map[string]interface{})
+               
Expect(globalWerfVals["repo"]).To(Equal("registry2.example.com/org/newproject"))
+
+               imagesVals := globalWerfVals["images"].(map[string]interface{})
+               myappVals := imagesVals["myapp"].(map[string]interface{})
+
+               Expect(myappVals["registry"]).To(Equal("registry2.example.com"))
+               Expect(myappVals["namespace"]).To(Equal("org"))
+               Expect(myappVals["name"]).To(Equal("newproject"))
+               Expect(myappVals["tag"]).To(Equal("tag-1"))
+               Expect(myappVals["digest"]).To(Equal("sha256:abcdef1234567890"))
+               
Expect(myappVals["tag_digest"]).To(Equal("tag-1@sha256:abcdef1234567890"))
+               
Expect(myappVals["image"]).To(Equal("registry2.example.com/org/newproject"))
+               Expect(myappVals["repository"]).To(Equal("org/newproject"))
+               
Expect(myappVals["ref"]).To(Equal("registry2.example.com/org/newproject:tag-1@sha256:abcdef1234567890"))
+               
Expect(myappVals["ref_tag"]).To(Equal("registry2.example.com/org/newproject:tag-1"))
+               
Expect(myappVals["repository_ref"]).To(Equal("org/newproject:tag-1@sha256:abcdef1234567890"))
+               
Expect(myappVals["repository_tag"]).To(Equal("org/newproject:tag-1"))
+               
Expect(myappVals["name_ref"]).To(Equal("newproject:tag-1@sha256:abcdef1234567890"))
+               Expect(myappVals["name_tag"]).To(Equal("newproject:tag-1"))
+       })
 })
 
 type BundleArchiveStubReader struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/deploy/bundles/global_values.go 
new/werf-2.74.0/pkg/deploy/bundles/global_values.go
--- old/werf-2.73.2/pkg/deploy/bundles/global_values.go 1970-01-01 
01:00:00.000000000 +0100
+++ new/werf-2.74.0/pkg/deploy/bundles/global_values.go 2026-07-14 
22:07:35.000000000 +0200
@@ -0,0 +1,49 @@
+package bundles
+
+import (
+       "fmt"
+
+       "github.com/werf/werf/v2/pkg/image"
+)
+
+func updateGlobalWerfValues(values map[string]interface{}, newRepo string, 
newImageRefs map[string]string) error {
+       globalVals, ok := values["global"].(map[string]interface{})
+       if !ok {
+               return nil
+       }
+
+       werfVals, ok := globalVals["werf"].(map[string]interface{})
+       if !ok {
+               return nil
+       }
+
+       werfVals["repo"] = newRepo
+
+       imagesVals, ok := werfVals["images"].(map[string]interface{})
+       if !ok {
+               return nil
+       }
+
+       for imageName, imageVals := range imagesVals {
+               vals, ok := imageVals.(map[string]interface{})
+               if !ok {
+                       continue
+               }
+
+               newRef, hasRef := newImageRefs[imageName]
+               if !hasRef {
+                       continue
+               }
+
+               digest, _ := vals["digest"].(string)
+
+               rebuilt, err := image.RebuildImageValuesMap(newRef, digest)
+               if err != nil {
+                       return fmt.Errorf("update global werf image values for 
%q: %w", imageName, err)
+               }
+
+               imagesVals[imageName] = rebuilt
+       }
+
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/deploy/bundles/remote_bundle.go 
new/werf-2.74.0/pkg/deploy/bundles/remote_bundle.go
--- old/werf-2.73.2/pkg/deploy/bundles/remote_bundle.go 2026-07-02 
10:37:28.000000000 +0200
+++ new/werf-2.74.0/pkg/deploy/bundles/remote_bundle.go 2026-07-14 
22:07:35.000000000 +0200
@@ -89,6 +89,8 @@
                return fmt.Errorf("unable to read chart from the bundle archive 
%q: %w", fromArchive.Reader.String(), err)
        }
 
+       newImageRefs := make(map[string]string)
+
        if err := logboek.Context(ctx).LogProcess("Copy images from bundle 
archive").DoError(func() error {
                if werfVals, ok := ch.Values["werf"].(map[string]interface{}); 
ok {
                        if imageVals, ok := 
werfVals["image"].(map[string]interface{}); ok {
@@ -113,6 +115,7 @@
                                                }
 
                                                newImageVals[imageName] = 
ref.FullName()
+                                               newImageRefs[imageName] = 
ref.FullName()
                                        } else {
                                                return fmt.Errorf("unexpected 
value .Values.werf.image.%s=%v", imageName, v)
                                        }
@@ -129,6 +132,10 @@
                return err
        }
 
+       if err := updateGlobalWerfValues(ch.Values, 
bundle.RegistryAddress.Repo, newImageRefs); err != nil {
+               return err
+       }
+
        if err := SaveChartValues(ctx, ch); err != nil {
                return err
        }
@@ -161,6 +168,8 @@
                logboek.Context(ctx).Debug().LogF("Values before change 
(%v):\n%s\n---\n", err, d)
        }
 
+       newImageRefs := make(map[string]string)
+
        if err := logboek.Context(ctx).LogProcess("Copy images from remote 
bundle").DoError(func() error {
                if werfVals, ok := ch.Values["werf"].(map[string]interface{}); 
ok {
                        if imageVals, ok := 
werfVals["image"].(map[string]interface{}); ok {
@@ -186,6 +195,7 @@
                                                }
 
                                                newImageVals[imageName] = 
ref.FullName()
+                                               newImageRefs[imageName] = 
ref.FullName()
                                        } else {
                                                return fmt.Errorf("unexpected 
value .Values.werf.image.%s=%v", imageName, v)
                                        }
@@ -201,6 +211,10 @@
                return err
        }
 
+       if err := updateGlobalWerfValues(ch.Values, 
bundle.RegistryAddress.Repo, newImageRefs); err != nil {
+               return err
+       }
+
        if err := SaveChartValues(ctx, ch); err != nil {
                return err
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/docker/image.go 
new/werf-2.74.0/pkg/docker/image.go
--- old/werf-2.73.2/pkg/docker/image.go 2026-07-02 10:37:28.000000000 +0200
+++ new/werf-2.74.0/pkg/docker/image.go 2026-07-14 22:07:35.000000000 +0200
@@ -1,6 +1,7 @@
 package docker
 
 import (
+       "archive/tar"
        "bytes"
        "fmt"
        "io"
@@ -43,8 +44,42 @@
        if opts.TargetPlatform != "" {
                importOpts.Platform = opts.TargetPlatform
        }
-       _, err := apiCli(ctx).ImageImport(ctx, 
types.ImageImportSource{SourceName: "-"}, ref, importOpts)
-       return err
+
+       // The Docker daemon reads the request body as the rootfs tar when 
fromSrc is "-".
+       // A nil/empty body makes the daemon register a malformed empty layer 
that some
+       // consumers (e.g. `dive`, `docker save` readers) fail to parse on 
Linux, so we
+       // send a valid empty tar archive instead. This yields a single empty 
rootfs
+       // layer, which is fine for `from: scratch`: later stapel stages copy 
imports on
+       // top of it, and the empty layer is deterministic so it does not shift 
caching.
+       emptyTar, err := emptyTarArchive()
+       if err != nil {
+               return fmt.Errorf("unable to build empty rootfs tar for image 
%q: %w", ref, err)
+       }
+
+       resp, err := apiCli(ctx).ImageImport(ctx, 
types.ImageImportSource{Source: emptyTar, SourceName: "-"}, ref, importOpts)
+       if err != nil {
+               return fmt.Errorf("unable to import image %q: %w", ref, err)
+       }
+       defer resp.Close()
+
+       // Drain the response body so the daemon fully finalizes image creation 
before we return.
+       if _, err := io.Copy(io.Discard, resp); err != nil {
+               return fmt.Errorf("unable to read import response for image %q: 
%w", ref, err)
+       }
+
+       return nil
+}
+
+// emptyTarArchive returns a reader over a valid, empty tar archive (containing
+// only the end-of-archive marker). It is used as the rootfs body for scratch
+// images created via the Docker Engine import API.
+func emptyTarArchive() (io.Reader, error) {
+       var buf bytes.Buffer
+       tw := tar.NewWriter(&buf)
+       if err := tw.Close(); err != nil {
+               return nil, fmt.Errorf("unable to close tar writer: %w", err)
+       }
+       return &buf, nil
 }
 
 func Images(ctx context.Context, options types.ImageListOptions) 
([]types.ImageSummary, error) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/docker/image_test.go 
new/werf-2.74.0/pkg/docker/image_test.go
--- old/werf-2.73.2/pkg/docker/image_test.go    2026-07-02 10:37:28.000000000 
+0200
+++ new/werf-2.74.0/pkg/docker/image_test.go    2026-07-14 22:07:35.000000000 
+0200
@@ -1,6 +1,10 @@
 package docker
 
 import (
+       "archive/tar"
+       "bytes"
+       "io"
+
        "github.com/docker/docker/api/types/filters"
        . "github.com/onsi/ginkgo/v2"
        . "github.com/onsi/gomega"
@@ -41,4 +45,25 @@
                        
"dff4d2bea666c3cdf6c24b6da06118a8a4f1658b1def9e50c55a1eb0c77eeaba",
                ),
        )
+
+       // emptyTarArchive provides the rootfs body sent to the Docker Engine 
import
+       // API for `from: scratch` images. A malformed/empty body was the root 
cause
+       // of unreadable scratch images on Linux, so the body must be a valid 
tar.
+       Describe("emptyTarArchive", func() {
+               It("returns a valid, non-nil, empty tar archive", func() {
+                       r, err := emptyTarArchive()
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(r).NotTo(BeNil())
+
+                       data, err := io.ReadAll(r)
+                       Expect(err).NotTo(HaveOccurred())
+                       // A valid empty tar is not a zero-byte stream: it 
contains the
+                       // end-of-archive marker (two 512-byte zero blocks).
+                       Expect(len(data)).To(BeNumerically(">", 0))
+
+                       tr := tar.NewReader(bytes.NewReader(data))
+                       _, err = tr.Next()
+                       Expect(err).To(Equal(io.EOF), "expected a well-formed 
empty tar with no entries")
+               })
+       })
 })
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/image/image_values.go 
new/werf-2.74.0/pkg/image/image_values.go
--- old/werf-2.73.2/pkg/image/image_values.go   2026-07-02 10:37:28.000000000 
+0200
+++ new/werf-2.74.0/pkg/image/image_values.go   2026-07-14 22:07:35.000000000 
+0200
@@ -24,6 +24,23 @@
        ), nil
 }
 
+func RebuildImageValuesMap(newRef, digest string) (map[string]interface{}, 
error) {
+       tag, err := name.NewTag(newRef)
+       if err != nil {
+               return nil, fmt.Errorf("rebuild image values map: %w", err)
+       }
+
+       return buildValuesMap(
+               tag.RegistryStr(),
+               tag.RepositoryStr(),
+               path.Base(tag.RepositoryStr()),
+               tag.TagStr(),
+               digest,
+               tag.Context().Name(),
+               newRef,
+       ), nil
+}
+
 func BuildStubImageValuesMap(repo, tag string) map[string]interface{} {
        return buildValuesMap(
                "REGISTRY",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/true_git/common.go 
new/werf-2.74.0/pkg/true_git/common.go
--- old/werf-2.73.2/pkg/true_git/common.go      2026-07-02 10:37:28.000000000 
+0200
+++ new/werf-2.74.0/pkg/true_git/common.go      2026-07-14 22:07:35.000000000 
+0200
@@ -1,13 +1,63 @@
 package true_git
 
 import (
+       "context"
+       "errors"
+       "fmt"
        "os"
+       "os/exec"
+       "path/filepath"
+       "strings"
 )
 
 func getCommonGitOptions() []string {
        return []string{"-c", "core.autocrlf=false", "-c", "gc.auto=0", "-c", 
"commit.gpgsign=false", "-c", "core.untrackedCache=false", "-c", 
"core.splitIndex=false"}
 }
 
+func getIncludePathOptions(ctx context.Context, repoDir string) ([]string, 
error) {
+       configCmd := NewGitCmd(ctx, &GitCmdOptions{RepoDir: repoDir}, "config", 
"--show-origin", "--get-all", "include.path")
+       if err := configCmd.Run(ctx); err != nil {
+               var exitErr *exec.ExitError
+               if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 && 
configCmd.OutBuf.String() == "" && configCmd.ErrBuf.String() == "" {
+                       return nil, nil
+               }
+               return nil, fmt.Errorf("get git include.path from %s: %w", 
repoDir, err)
+       }
+
+       var opts []string
+       for _, line := range strings.Split(configCmd.OutBuf.String(), "\n") {
+               line = strings.TrimSpace(line)
+               if line == "" {
+                       continue
+               }
+
+               origin, includePath, found := strings.Cut(line, "\t")
+               if !found {
+                       continue
+               }
+
+               // include.path passed via -c has no declaring config file, so 
relative values are resolved against their origin up front
+               if !strings.HasPrefix(includePath, "~") && 
!filepath.IsAbs(includePath) {
+                       if originFile, ok := strings.CutPrefix(origin, 
"file:"); ok {
+                               if !filepath.IsAbs(originFile) {
+                                       originFile = filepath.Join(repoDir, 
originFile)
+                               }
+                               includePath = 
filepath.Join(filepath.Dir(originFile), includePath)
+                       } else {
+                               absIncludePath, err := filepath.Abs(includePath)
+                               if err != nil {
+                                       return nil, fmt.Errorf("resolve 
include.path %q: %w", includePath, err)
+                               }
+                               includePath = absIncludePath
+                       }
+               }
+
+               opts = append(opts, "-c", "include.path="+includePath)
+       }
+
+       return opts, nil
+}
+
 func debug() bool {
        return os.Getenv("WERF_DEBUG_TRUE_GIT") == "1"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/true_git/helpers_ai_test.go 
new/werf-2.74.0/pkg/true_git/helpers_ai_test.go
--- old/werf-2.73.2/pkg/true_git/helpers_ai_test.go     1970-01-01 
01:00:00.000000000 +0100
+++ new/werf-2.74.0/pkg/true_git/helpers_ai_test.go     2026-07-14 
22:07:35.000000000 +0200
@@ -0,0 +1,24 @@
+package true_git
+
+import (
+       "os/exec"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+)
+
+func runGitAI(t *testing.T, dir string, args ...string) string {
+       t.Helper()
+       cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
+       out, err := cmd.CombinedOutput()
+       require.NoError(t, err, "git %v: %s", args, out)
+       return string(out)
+}
+
+func initGitRepoAI(t *testing.T, dir string) {
+       t.Helper()
+       runGitAI(t, dir, "init")
+       runGitAI(t, dir, "config", "user.email", "[email protected]")
+       runGitAI(t, dir, "config", "user.name", "test")
+       runGitAI(t, dir, "commit", "--allow-empty", "-m", "init")
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/true_git/submodule.go 
new/werf-2.74.0/pkg/true_git/submodule.go
--- old/werf-2.73.2/pkg/true_git/submodule.go   2026-07-02 10:37:28.000000000 
+0200
+++ new/werf-2.74.0/pkg/true_git/submodule.go   2026-07-14 22:07:35.000000000 
+0200
@@ -10,7 +10,12 @@
 func syncSubmodules(ctx context.Context, repoDir, workTreeDir string) error {
        logProcessMsg := fmt.Sprintf("Sync submodules in work tree %q", 
workTreeDir)
        return 
logboek.Context(ctx).Info().LogProcess(logProcessMsg).DoError(func() error {
-               submSyncCmd := NewGitCmd(ctx, &GitCmdOptions{RepoDir: 
workTreeDir}, "submodule", "sync", "--recursive")
+               includePathOpts, err := getIncludePathOptions(ctx, repoDir)
+               if err != nil {
+                       return err
+               }
+
+               submSyncCmd := NewGitCmd(ctx, &GitCmdOptions{RepoDir: 
workTreeDir}, append(includePathOpts, "submodule", "sync", "--recursive")...)
                if err := submSyncCmd.Run(ctx); err != nil {
                        return fmt.Errorf("submodule sync command failed: %w", 
err)
                }
@@ -22,7 +27,12 @@
 func updateSubmodules(ctx context.Context, repoDir, workTreeDir string) error {
        logProcessMsg := fmt.Sprintf("Update submodules in work tree %q", 
workTreeDir)
        return 
logboek.Context(ctx).Info().LogProcess(logProcessMsg).DoError(func() error {
-               submUpdateCmd := NewGitCmd(ctx, &GitCmdOptions{RepoDir: 
workTreeDir}, "submodule", "update", "--checkout", "--force", "--init", 
"--recursive")
+               includePathOpts, err := getIncludePathOptions(ctx, repoDir)
+               if err != nil {
+                       return err
+               }
+
+               submUpdateCmd := NewGitCmd(ctx, &GitCmdOptions{RepoDir: 
workTreeDir}, append(includePathOpts, "submodule", "update", "--checkout", 
"--force", "--init", "--recursive")...)
                if err := submUpdateCmd.Run(ctx); err != nil {
                        return fmt.Errorf("submodule update command failed: 
%w", err)
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/pkg/true_git/submodule_ai_test.go 
new/werf-2.74.0/pkg/true_git/submodule_ai_test.go
--- old/werf-2.73.2/pkg/true_git/submodule_ai_test.go   1970-01-01 
01:00:00.000000000 +0100
+++ new/werf-2.74.0/pkg/true_git/submodule_ai_test.go   2026-07-14 
22:07:35.000000000 +0200
@@ -0,0 +1,80 @@
+package true_git
+
+import (
+       "context"
+       "os"
+       "path/filepath"
+       "strings"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+)
+
+func TestAI_GetIncludePathOptions(t *testing.T) {
+       ctx := context.Background()
+
+       repoDir := t.TempDir()
+       initGitRepoAI(t, repoDir)
+       gitDir := filepath.Join(repoDir, ".git")
+
+       opts, err := getIncludePathOptions(ctx, gitDir)
+       require.NoError(t, err)
+       require.Empty(t, opts)
+
+       runGitAI(t, repoDir, "config", "--add", "include.path", "/abs/ext.conf")
+       runGitAI(t, repoDir, "config", "--add", "include.path", "rel/ext.conf")
+       runGitAI(t, repoDir, "config", "--add", "include.path", 
"~/tilde-ext.conf")
+
+       opts, err = getIncludePathOptions(ctx, gitDir)
+       require.NoError(t, err)
+       require.Len(t, opts, 6)
+       require.Equal(t, "-c", opts[0])
+       require.Equal(t, "include.path=/abs/ext.conf", opts[1])
+       require.Equal(t, "-c", opts[2])
+       require.True(t, strings.HasPrefix(opts[3], "include.path="))
+       relResolved := strings.TrimPrefix(opts[3], "include.path=")
+       require.True(t, filepath.IsAbs(relResolved))
+       require.True(t, strings.HasSuffix(relResolved, filepath.Join(".git", 
"rel", "ext.conf")))
+       require.Equal(t, "-c", opts[4])
+       require.Equal(t, "include.path=~/tilde-ext.conf", opts[5])
+}
+
+func TestAI_UpdateSubmodulesForwardsIncludePath(t *testing.T) {
+       ctx := context.Background()
+
+       repoDir := t.TempDir()
+       initGitRepoAI(t, repoDir)
+       headSHA := strings.TrimSpace(runGitAI(t, repoDir, "rev-parse", "HEAD"))
+
+       gitmodules := "[submodule \"sub\"]\n\tpath = sub\n\turl = 
https://werf-test-nonexistent.invalid/sub.git\n";
+       require.NoError(t, os.WriteFile(filepath.Join(repoDir, ".gitmodules"), 
[]byte(gitmodules), 0o644))
+       runGitAI(t, repoDir, "update-index", "--add", "--cacheinfo", 
"160000,"+headSHA+",sub")
+       runGitAI(t, repoDir, "add", ".gitmodules")
+       runGitAI(t, repoDir, "commit", "-m", "add submodule")
+
+       stubPath := filepath.Join(t.TempDir(), "ext.conf")
+       stub := "[url \"werf-test-marker://rewritten/\"]\n\tinsteadOf = 
https://\n";
+       require.NoError(t, os.WriteFile(stubPath, []byte(stub), 0o644))
+       runGitAI(t, repoDir, "config", "include.path", stubPath)
+
+       err := updateSubmodules(ctx, filepath.Join(repoDir, ".git"), repoDir)
+       require.Error(t, err)
+       require.Contains(t, err.Error(), "werf-test-marker")
+}
+
+func TestAI_SwitchWorkTreeNonSubmodulesUnaffectedByIncludePath(t *testing.T) {
+       ctx := context.Background()
+
+       repoDir := t.TempDir()
+       initGitRepoAI(t, repoDir)
+       headSHA := strings.TrimSpace(runGitAI(t, repoDir, "rev-parse", "HEAD"))
+
+       stubPath := filepath.Join(t.TempDir(), "ext.conf")
+       stub := "[url \"werf-test-marker://rewritten/\"]\n\tinsteadOf = 
https://\n";
+       require.NoError(t, os.WriteFile(stubPath, []byte(stub), 0o644))
+       runGitAI(t, repoDir, "config", "include.path", stubPath)
+
+       workTreeDir := filepath.Join(t.TempDir(), "worktree")
+       err := switchWorkTree(ctx, filepath.Join(repoDir, ".git"), workTreeDir, 
headSHA, false)
+       require.NoError(t, err)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/test/e2e/build/scratch_test.go 
new/werf-2.74.0/test/e2e/build/scratch_test.go
--- old/werf-2.73.2/test/e2e/build/scratch_test.go      2026-07-02 
10:37:28.000000000 +0200
+++ new/werf-2.74.0/test/e2e/build/scratch_test.go      2026-07-14 
22:07:35.000000000 +0200
@@ -42,6 +42,7 @@
 
                        By("checking scratch image contents and labels")
                        imageName := 
buildReport.Images["stapel-scratch"].DockerImageName
+                       utils.ExpectImageIsReadable(ctx, 
testOpts.ContainerBackendMode, imageName)
                        utils.ExpectFileContentInImage(ctx, 
testOpts.ContainerBackendMode, imageName, "etc/werf-test-scratch-import", 
"werf-test-scratch-import\n")
                        utils.ExpectImageHasNonEmptyLabels(ctx, 
testOpts.ContainerBackendMode, imageName,
                                image.WerfLabel,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/test/pkg/utils/image.go 
new/werf-2.74.0/test/pkg/utils/image.go
--- old/werf-2.73.2/test/pkg/utils/image.go     2026-07-02 10:37:28.000000000 
+0200
+++ new/werf-2.74.0/test/pkg/utils/image.go     2026-07-14 22:07:35.000000000 
+0200
@@ -41,6 +41,38 @@
        Expect(fileContent).To(Equal(expectedContent), "expected image file 
content to match for %s", filePath)
 }
 
+// ExpectImageIsReadable asserts that the image can be saved and fully parsed 
by
+// go-containerregistry: its manifest, config and every layer must be readable
+// and the whole rootfs must extract without EOF/malformed-tar errors. This
+// guards against malformed images (e.g. a broken `from: scratch` base layer)
+// that Docker can still list/tag but that tools like `dive` fail to read.
+func ExpectImageIsReadable(ctx context.Context, backendMode, imageName string) 
{
+       img, cleanup := loadLocalImage(ctx, backendMode, imageName)
+       defer cleanup()
+
+       _, err := img.ConfigFile()
+       Expect(err).NotTo(HaveOccurred(), "expected image config to be readable 
for %s", imageName)
+
+       _, err = img.Manifest()
+       Expect(err).NotTo(HaveOccurred(), "expected image manifest to be 
readable for %s", imageName)
+
+       layers, err := img.Layers()
+       Expect(err).NotTo(HaveOccurred(), "expected image layers to be readable 
for %s", imageName)
+
+       for i, layer := range layers {
+               rc, err := layer.Uncompressed()
+               Expect(err).NotTo(HaveOccurred(), "expected layer %d to be 
readable for %s", i, imageName)
+               _, err = io.Copy(io.Discard, rc)
+               Expect(err).NotTo(HaveOccurred(), "expected layer %d to be 
fully readable for %s", i, imageName)
+               Expect(rc.Close()).To(Succeed())
+       }
+
+       rc := mutate.Extract(img)
+       defer rc.Close()
+       _, err = io.Copy(io.Discard, rc)
+       Expect(err).NotTo(HaveOccurred(), "expected image rootfs to fully 
extract for %s", imageName)
+}
+
 func ExpectImageHasNonEmptyLabels(ctx context.Context, backendMode, imageName 
string, labelKeys ...string) {
        img, cleanup := loadLocalImage(ctx, backendMode, imageName)
        defer cleanup()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/werf-2.73.2/trdl_channels.yaml 
new/werf-2.74.0/trdl_channels.yaml
--- old/werf-2.73.2/trdl_channels.yaml  2026-07-02 10:37:28.000000000 +0200
+++ new/werf-2.74.0/trdl_channels.yaml  2026-07-14 22:07:35.000000000 +0200
@@ -38,12 +38,16 @@
   - name: "2"
     channels:
       - name: alpha
-        version: 2.73.1
+        version: 2.73.2
       - name: beta
-        version: 2.73.0
+        version: 2.73.1
       - name: ea
-        version: 2.72.2
+        version: 2.73.1
       - name: stable
-        version: 2.71.0
+        version: 2.72.2
       - name: rock-solid
-        version: 2.70.0
+        version: 2.71.0
+  - name: "3"
+    channels:
+      - name: dev
+        version: 3.0.0-alpha.1

++++++ werf.obsinfo ++++++
--- /var/tmp/diff_new_pack.CYJ1Gd/_old  2026-07-31 16:10:59.476118249 +0200
+++ /var/tmp/diff_new_pack.CYJ1Gd/_new  2026-07-31 16:10:59.480118388 +0200
@@ -1,5 +1,5 @@
 name: werf
-version: 2.73.2
-mtime: 1782981448
-commit: 0b7ec198dac3e05320e8d77cd17492ed5fc18750
+version: 2.74.0
+mtime: 1784059655
+commit: 7585cfbf15796fbe86e33df695a3c5259aa40382
 

Reply via email to