Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kopia for openSUSE:Factory checked 
in at 2025-12-05 16:51:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kopia (Old)
 and      /work/SRC/openSUSE:Factory/.kopia.new.1939 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kopia"

Fri Dec  5 16:51:24 2025 rev:10 rq:1321061 version:0.22.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/kopia/kopia.changes      2025-11-27 
15:22:07.722415835 +0100
+++ /work/SRC/openSUSE:Factory/.kopia.new.1939/kopia.changes    2025-12-05 
16:52:31.562076332 +0100
@@ -1,0 +2,21 @@
+Thu Dec 04 06:26:40 UTC 2025 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.22.3:
+  * Defect Fixes
+    - Fixes regression in dependency used for compression (#5049)
+  * Snapshots
+    - New Feature localfs support for passing options (#5044) by
+      Jarek Kowalski
+  * CI/CD
+    - Remove ineffective omitempty tags (#5037) by Julio López
+  * Dependencies
+    - build(deps): bump docker/setup-qemu-action in the docker
+      group (#5054)
+    - build(deps): bump github.com/klauspost/reedsolomon from
+      1.12.5 to 1.12.6 (#5051)
+    - build(deps): bump the github-actions group with 4 updates
+      (#5053)
+    - build(deps): bump github.com/klauspost/compress from 1.18.1
+      to 1.18.2 (#5052)
+
+-------------------------------------------------------------------

Old:
----
  kopia-0.22.2.obscpio

New:
----
  kopia-0.22.3.obscpio

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

Other differences:
------------------
++++++ kopia.spec ++++++
--- /var/tmp/diff_new_pack.i1JyhY/_old  2025-12-05 16:52:33.958176336 +0100
+++ /var/tmp/diff_new_pack.i1JyhY/_new  2025-12-05 16:52:33.974177004 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           kopia
-Version:        0.22.2
+Version:        0.22.3
 Release:        0
 Summary:        Cross-platform backup tool with fast incremental backups
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.i1JyhY/_old  2025-12-05 16:52:34.130183515 +0100
+++ /var/tmp/diff_new_pack.i1JyhY/_new  2025-12-05 16:52:34.146184183 +0100
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/kopia/kopia</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v0.22.2</param>
+    <param name="revision">v0.22.3</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.i1JyhY/_old  2025-12-05 16:52:34.214187021 +0100
+++ /var/tmp/diff_new_pack.i1JyhY/_new  2025-12-05 16:52:34.238188023 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/kopia/kopia</param>
-              <param 
name="changesrevision">e456f78fa2d15b102988eee1025a2451eeaa3ebf</param></service></servicedata>
+              <param 
name="changesrevision">154bf56899228e5c95fb3176b9c6901bbe4ca97b</param></service></servicedata>
 (No newline at EOF)
 

++++++ kopia-0.22.2.obscpio -> kopia-0.22.3.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/cli/app.go new/kopia-0.22.3/cli/app.go
--- old/kopia-0.22.2/cli/app.go 2025-11-26 08:05:28.000000000 +0100
+++ new/kopia-0.22.3/cli/app.go 2025-12-02 06:27:24.000000000 +0100
@@ -122,7 +122,6 @@
 type App struct {
        // global flags
        enableAutomaticMaintenance    bool
-       pf                            profileFlags
        progress                      *cliProgress
        restoreProgress               RestoreProgress
        initialUpdateCheckDelay       time.Duration
@@ -295,7 +294,6 @@
 
        c.setupOSSpecificKeychainFlags(c, app)
 
-       c.pf.setup(app)
        c.progress.setup(c, app)
 
        c.blob.setup(c, app)
@@ -402,11 +400,7 @@
 
 func (c *App) noRepositoryAction(act func(ctx context.Context) error) func(ctx 
*kingpin.ParseContext) error {
        return func(kpc *kingpin.ParseContext) error {
-               return c.runAppWithContext(kpc.SelectedCommand, func(ctx 
context.Context) error {
-                       return c.pf.withProfiling(func() error {
-                               return act(ctx)
-                       })
-               })
+               return c.runAppWithContext(kpc.SelectedCommand, act)
        }
 }
 
@@ -524,11 +518,7 @@
 
 func (c *App) baseActionWithContext(act func(ctx context.Context) error) 
func(ctx *kingpin.ParseContext) error {
        return func(kpc *kingpin.ParseContext) error {
-               return c.runAppWithContext(kpc.SelectedCommand, func(ctx 
context.Context) error {
-                       return c.pf.withProfiling(func() error {
-                               return act(ctx)
-                       })
-               })
+               return c.runAppWithContext(kpc.SelectedCommand, act)
        }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/cli/observability_flags.go 
new/kopia-0.22.3/cli/observability_flags.go
--- old/kopia-0.22.2/cli/observability_flags.go 2025-11-26 08:05:28.000000000 
+0100
+++ new/kopia-0.22.3/cli/observability_flags.go 2025-12-02 06:27:24.000000000 
+0100
@@ -43,6 +43,8 @@
 }
 
 type observabilityFlags struct {
+       outputDirectory string
+
        dumpAllocatorStats  bool
        enablePProfEndpoint bool
        metricsListenAddr   string
@@ -53,10 +55,11 @@
        metricsPushUsername string
        metricsPushPassword string
        metricsPushFormat   string
-       metricsOutputDir    string
-       outputFilePrefix    string
+       otlpTrace           bool
+       saveMetrics         bool
+       pf                  profileFlags
 
-       otlpTrace bool
+       outputSubdirectoryName string
 
        stopPusher chan struct{}
        pusherWG   sync.WaitGroup
@@ -90,16 +93,17 @@
 
        app.Flag("metrics-push-format", "Format to use for push 
gateway").Envar(svc.EnvName("KOPIA_METRICS_FORMAT")).Hidden().EnumVar(&c.metricsPushFormat,
 formats...)
 
-       app.Flag("metrics-directory", "Directory where the metrics should be 
saved when kopia exits. A file per process execution will be created in this 
directory").Hidden().StringVar(&c.metricsOutputDir)
+       //nolint:lll
+       app.Flag("diagnostics-output-directory", "Directory where the 
diagnostics output should be stored saved when kopia exits. Diagnostics data 
includes among others: metrics, traces, profiles. The output files are stored 
in a sub-directory for each kopia (process) 
execution").Hidden().Default(filepath.Join(os.TempDir(), 
"kopia-diagnostics")).StringVar(&c.outputDirectory)
+
+       app.Flag("metrics-store-on-exit", "Writes metrics to a file in a 
sub-directory of the directory specified with the 
--diagnostics-output-directory").Hidden().BoolVar(&c.saveMetrics)
+
+       c.pf.setup(app)
 
        app.PreAction(c.initialize)
 }
 
 func (c *observabilityFlags) initialize(ctx *kingpin.ParseContext) error {
-       if c.metricsOutputDir == "" {
-               return nil
-       }
-
        // write to a separate file per command and process execution to avoid
        // conflicts with previously created files
        command := "unknown"
@@ -107,7 +111,11 @@
                command = strings.ReplaceAll(cmd.FullCommand(), " ", "-")
        }
 
-       c.outputFilePrefix = clock.Now().Format("20060102-150405-") + command
+       c.outputSubdirectoryName = clock.Now().Format("20060102-150405-") + 
command
+
+       if (c.saveMetrics || c.pf.saveProfiles || c.pf.profileCPU) && 
c.outputDirectory == "" {
+               return errors.New("writing diagnostics output requires a 
non-empty directory name (specified with the '--diagnostics-output-directory' 
flag)")
+       }
 
        return nil
 }
@@ -121,6 +129,12 @@
 
        defer c.stop(ctx)
 
+       if err := c.pf.start(ctx, filepath.Join(c.outputDirectory, 
c.outputSubdirectoryName)); err != nil {
+               return errors.Wrap(err, "failed to start profiling")
+       }
+
+       defer c.pf.stop(ctx)
+
        if spanName != "" {
                tctx, span := tracer.Start(ctx, spanName, 
oteltrace.WithSpanKind(oteltrace.SpanKindClient))
                ctx = tctx
@@ -138,18 +152,26 @@
                return err
        }
 
-       if c.metricsOutputDir != "" {
-               c.metricsOutputDir = filepath.Clean(c.metricsOutputDir)
-
+       if c.saveMetrics {
                // ensure the metrics output dir can be created
-               if err := os.MkdirAll(c.metricsOutputDir, DirMode); err != nil {
-                       return errors.Wrapf(err, "could not create metrics 
output directory: %s", c.metricsOutputDir)
+               if _, err := mkSubdirectories(c.outputDirectory, 
c.outputSubdirectoryName); err != nil {
+                       return err
                }
        }
 
        return c.maybeStartTraceExporter(ctx)
 }
 
+func mkSubdirectories(directoryNames ...string) (dirName string, err error) {
+       dirName = filepath.Join(directoryNames...)
+
+       if err := os.MkdirAll(dirName, DirMode); err != nil {
+               return "", errors.Wrapf(err, "could not create '%q' 
subdirectory to save diagnostics output", dirName)
+       }
+
+       return dirName, nil
+}
+
 // Starts observability listener when a listener address is specified.
 func (c *observabilityFlags) maybeStartListener(ctx context.Context) {
        if c.metricsListenAddr == "" {
@@ -262,11 +284,13 @@
                }
        }
 
-       if c.metricsOutputDir != "" {
-               filename := filepath.Join(c.metricsOutputDir, 
c.outputFilePrefix+".prom")
-
-               if err := prometheus.WriteToTextfile(filename, 
prometheus.DefaultGatherer); err != nil {
-                       log(ctx).Warnf("unable to write metrics file '%s': %v", 
filename, err)
+       if c.saveMetrics {
+               if metricsDir, err := mkSubdirectories(c.outputDirectory, 
c.outputSubdirectoryName); err != nil {
+                       log(ctx).Warnf("unable to create metrics output 
directory '%s': %v", metricsDir, err)
+               } else {
+                       if err := 
prometheus.WriteToTextfile(filepath.Join(metricsDir, "kopia-metrics.prom"), 
prometheus.DefaultGatherer); err != nil {
+                               log(ctx).Warnf("unable to write metrics to 
file: %v", err)
+                       }
                }
        }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/cli/observability_flags_test.go 
new/kopia-0.22.3/cli/observability_flags_test.go
--- old/kopia-0.22.2/cli/observability_flags_test.go    2025-11-26 
08:05:28.000000000 +0100
+++ new/kopia-0.22.3/cli/observability_flags_test.go    2025-12-02 
06:27:24.000000000 +0100
@@ -97,7 +97,7 @@
 
        tmp2 := testutil.TempDirectory(t)
 
-       env.RunAndExpectSuccess(t, "repo", "status", "--metrics-directory", 
tmp2)
+       env.RunAndExpectSuccess(t, "repo", "status", 
"--diagnostics-output-directory", tmp2, "--metrics-store-on-exit")
 
        entries, err := os.ReadDir(tmp2)
        require.NoError(t, err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/cli/profile.go 
new/kopia-0.22.3/cli/profile.go
--- old/kopia-0.22.2/cli/profile.go     2025-11-26 08:05:28.000000000 +0100
+++ new/kopia-0.22.3/cli/profile.go     2025-12-02 06:27:24.000000000 +0100
@@ -1,46 +1,156 @@
 package cli
 
 import (
+       "context"
+       "os"
+       "path/filepath"
+       "runtime"
+       "runtime/pprof"
+
        "github.com/alecthomas/kingpin/v2"
-       "github.com/pkg/profile"
+       "github.com/pkg/errors"
 )
 
+const profDirName = "profiles"
+
 type profileFlags struct {
-       profileDir      string
-       profileCPU      bool
-       profileMemory   int
-       profileBlocking bool
-       profileMutex    bool
+       profileGCBeforeSaving bool
+       profileCPU            bool
+       profileBlockingRate   int
+       profileMemoryRate     int
+       profileMutexFraction  int
+       saveProfiles          bool
+
+       outputDirectory  string
+       cpuProfileCloser func()
 }
 
 func (c *profileFlags) setup(app *kingpin.Application) {
-       app.Flag("profile-dir", "Write profile to the specified 
directory").Hidden().StringVar(&c.profileDir)
+       c.profileBlockingRate = -1
+       c.profileMemoryRate = -1
+       c.profileMutexFraction = -1
+
+       app.Flag("profile-store-on-exit", "Writes profiling data on exit. It 
writes a file per profile type (heap, goroutine, threadcreate, block, mutex) in 
a sub-directory in the directory specified with the 
--diagnostics-output-directory").Hidden().BoolVar(&c.saveProfiles) //nolint:lll
+       app.Flag("profile-go-gc-before-dump", "Perform a Go GC before writing 
out memory profiles").Hidden().BoolVar(&c.profileGCBeforeSaving)
+       app.Flag("profile-blocking-rate", "Blocking profiling rate, a value of 
0 turns off block profiling").Hidden().IntVar(&c.profileBlockingRate)
        app.Flag("profile-cpu", "Enable CPU 
profiling").Hidden().BoolVar(&c.profileCPU)
-       app.Flag("profile-memory", "Enable memory 
profiling").Hidden().IntVar(&c.profileMemory)
-       app.Flag("profile-blocking", "Enable block 
profiling").Hidden().BoolVar(&c.profileBlocking)
-       app.Flag("profile-mutex", "Enable mutex 
profiling").Hidden().BoolVar(&c.profileMutex)
+       app.Flag("profile-memory-rate", "Memory profiling 
rate").Hidden().IntVar(&c.profileMemoryRate)
+       app.Flag("profile-mutex-fraction", "Mutex profiling, a value of 0 turns 
off mutex profiling").Hidden().IntVar(&c.profileMutexFraction)
 }
 
-// withProfiling runs the given callback with profiling enabled, configured 
according to command line flags.
-func (c *profileFlags) withProfiling(callback func() error) error {
-       if c.profileDir != "" {
-               pp := profile.ProfilePath(c.profileDir)
-               if c.profileMemory > 0 {
-                       defer profile.Start(pp, 
profile.MemProfileRate(c.profileMemory)).Stop()
+func (c *profileFlags) start(ctx context.Context, outputDirectory string) 
error {
+       pBlockingRate := c.profileBlockingRate
+       pMemoryRate := c.profileMemoryRate
+       pMutexFraction := c.profileMutexFraction
+
+       if c.saveProfiles {
+               // when saving profiles ensure profiling parameters have 
sensible values
+               // unless explicitly modified.
+               // runtime.MemProfileRate has a default value, no need to reset 
it.
+               if pBlockingRate == -1 {
+                       pBlockingRate = 1
                }
 
-               if c.profileCPU {
-                       defer profile.Start(pp, profile.CPUProfile).Stop()
+               if pMutexFraction == -1 {
+                       pMutexFraction = 1
                }
+       }
 
-               if c.profileBlocking {
-                       defer profile.Start(pp, profile.BlockProfile).Stop()
-               }
+       // set profiling parameters if they have been changed from defaults
+       if pBlockingRate != -1 {
+               runtime.SetBlockProfileRate(pBlockingRate)
+       }
+
+       if pMemoryRate != -1 {
+               runtime.MemProfileRate = pMemoryRate
+       }
+
+       if pMutexFraction != -1 {
+               runtime.SetMutexProfileFraction(pMutexFraction)
+       }
+
+       if !c.profileCPU && !c.saveProfiles {
+               return nil
+       }
 
-               if c.profileMutex {
-                       defer profile.Start(pp, profile.MutexProfile).Stop()
+       c.outputDirectory = outputDirectory
+
+       // ensure upfront that the pprof output dir can be created.
+       profDir, err := mkSubdirectories(c.outputDirectory, profDirName)
+       if err != nil {
+               return err
+       }
+
+       if !c.profileCPU {
+               return nil
+       }
+
+       // start CPU profile dumper
+       f, err := os.Create(filepath.Join(profDir, "cpu.pprof")) //nolint:gosec
+       if err != nil {
+               return errors.Wrap(err, "could not create CPU profile output 
file")
+       }
+
+       // CPU profile closer
+       closer := func() {
+               pprof.StopCPUProfile()
+
+               if err := f.Close(); err != nil {
+                       log(ctx).Warn("error closing CPU profile output file:", 
err)
                }
        }
 
-       return callback()
+       if err := pprof.StartCPUProfile(f); err != nil {
+               closer()
+
+               return errors.Wrap(err, "could not start CPU profile")
+       }
+
+       c.cpuProfileCloser = closer
+
+       return nil
+}
+
+func (c *profileFlags) stop(ctx context.Context) {
+       if c.cpuProfileCloser != nil {
+               c.cpuProfileCloser()
+               c.cpuProfileCloser = nil
+       }
+
+       if !c.saveProfiles {
+               return
+       }
+
+       if c.profileGCBeforeSaving {
+               // update profiles, otherwise they may not include activity 
after the last GC
+               runtime.GC()
+       }
+
+       profDir, err := mkSubdirectories(c.outputDirectory, profDirName)
+       if err != nil {
+               log(ctx).Warn("cannot create directory to save profiles:", err)
+       }
+
+       for _, p := range pprof.Profiles() {
+               func() {
+                       fname := filepath.Join(profDir, p.Name()+".pprof")
+
+                       f, err := os.Create(fname) //nolint:gosec
+                       if err != nil {
+                               log(ctx).Warnf("unable to create profile output 
file '%s': %v", fname, err)
+
+                               return
+                       }
+
+                       defer func() {
+                               if err := f.Close(); err != nil {
+                                       log(ctx).Warnf("unable to close profile 
output file '%s': %v", fname, err)
+                               }
+                       }()
+
+                       if err := p.WriteTo(f, 0); err != nil {
+                               log(ctx).Warnf("unable to write profile to file 
'%s': %v", fname, err)
+                       }
+               }()
+       }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/fs/localfs/local_fs.go 
new/kopia-0.22.3/fs/localfs/local_fs.go
--- old/kopia-0.22.2/fs/localfs/local_fs.go     2025-11-26 08:05:28.000000000 
+0100
+++ new/kopia-0.22.3/fs/localfs/local_fs.go     2025-12-02 06:27:24.000000000 
+0100
@@ -13,6 +13,16 @@
 
 const numEntriesToRead = 100 // number of directory entries to read in one shot
 
+// Options contains configuration options for localfs operations.
+type Options struct {
+       // IgnoreUnreadableDirEntries, when true, causes unreadable directory 
entries
+       // to be silently skipped during directory iteration instead of causing 
errors.
+       IgnoreUnreadableDirEntries bool
+}
+
+// DefaultOptions stores the default options used by localfs functions.
+var DefaultOptions = &Options{}
+
 type filesystemEntry struct {
        name       string
        size       int64
@@ -21,7 +31,8 @@
        owner      fs.OwnerInfo
        device     fs.DeviceInfo
 
-       prefix string
+       prefix  string
+       options *Options
 }
 
 func (e *filesystemEntry) Name() string {
@@ -92,6 +103,7 @@
 
 type fileWithMetadata struct {
        *os.File
+       options *Options
 }
 
 func (f *fileWithMetadata) Entry() (fs.Entry, error) {
@@ -102,7 +114,7 @@
 
        basename, prefix := splitDirPrefix(f.Name())
 
-       return newFilesystemFile(newEntry(basename, fi, prefix)), nil
+       return newFilesystemFile(newEntry(basename, fi, prefix, f.options)), nil
 }
 
 func (fsf *filesystemFile) Open(_ context.Context) (fs.Reader, error) {
@@ -111,7 +123,7 @@
                return nil, errors.Wrap(err, "unable to open local file")
        }
 
-       return &fileWithMetadata{f}, nil
+       return &fileWithMetadata{File: f, options: fsf.options}, nil
 }
 
 func (fsl *filesystemSymlink) Readlink(_ context.Context) (string, error) {
@@ -125,7 +137,7 @@
                return nil, errors.Wrapf(err, "cannot resolve symlink for 
'%q'", fsl.fullPath())
        }
 
-       return NewEntry(target)
+       return NewEntryWithOptions(target, fsl.options)
 }
 
 func (e *filesystemErrorEntry) ErrorInfo() error {
@@ -145,8 +157,15 @@
 }
 
 // Directory returns fs.Directory for the specified path.
+// It uses DefaultOptions for configuration.
 func Directory(path string) (fs.Directory, error) {
-       e, err := NewEntry(path)
+       return DirectoryWithOptions(path, DefaultOptions)
+}
+
+// DirectoryWithOptions returns fs.Directory for the specified path.
+// It uses the provided Options for configuration.
+func DirectoryWithOptions(path string, options *Options) (fs.Directory, error) 
{
+       e, err := NewEntryWithOptions(path, options)
        if err != nil {
                return nil, err
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/fs/localfs/local_fs_os.go 
new/kopia-0.22.3/fs/localfs/local_fs_os.go
--- old/kopia-0.22.2/fs/localfs/local_fs_os.go  2025-11-26 08:05:28.000000000 
+0100
+++ new/kopia-0.22.3/fs/localfs/local_fs_os.go  2025-12-02 06:27:24.000000000 
+0100
@@ -18,6 +18,7 @@
 type filesystemDirectoryIterator struct {
        dirHandle   *os.File
        childPrefix string
+       options     *Options
 
        currentIndex int
        currentBatch []os.DirEntry
@@ -45,7 +46,7 @@
                n := it.currentIndex
                it.currentIndex++
 
-               e, err := toDirEntryOrNil(it.currentBatch[n], it.childPrefix)
+               e, err := toDirEntryOrNil(it.currentBatch[n], it.childPrefix, 
it.options)
                if err != nil {
                        // stop iteration
                        return nil, err
@@ -74,7 +75,7 @@
 
        childPrefix := fullPath + separatorStr
 
-       return &filesystemDirectoryIterator{dirHandle: d, childPrefix: 
childPrefix}, nil
+       return &filesystemDirectoryIterator{dirHandle: d, childPrefix: 
childPrefix, options: fsd.options}, nil
 }
 
 func (fsd *filesystemDirectory) Child(_ context.Context, name string) 
(fs.Entry, error) {
@@ -89,10 +90,10 @@
                return nil, errors.Wrap(err, "unable to get child")
        }
 
-       return entryFromDirEntry(name, st, fullPath+separatorStr), nil
+       return entryFromDirEntry(name, st, fullPath+separatorStr, fsd.options), 
nil
 }
 
-func toDirEntryOrNil(dirEntry os.DirEntry, prefix string) (fs.Entry, error) {
+func toDirEntryOrNil(dirEntry os.DirEntry, prefix string, options *Options) 
(fs.Entry, error) {
        n := dirEntry.Name()
 
        fi, err := os.Lstat(prefix + n)
@@ -101,15 +102,27 @@
                        return nil, nil
                }
 
+               if options != nil && options.IgnoreUnreadableDirEntries {
+                       return nil, nil
+               }
+
                return nil, errors.Wrap(err, "error reading directory")
        }
 
-       return entryFromDirEntry(n, fi, prefix), nil
+       return entryFromDirEntry(n, fi, prefix, options), nil
 }
 
 // NewEntry returns fs.Entry for the specified path, the result will be one of 
supported entry types: fs.File, fs.Directory, fs.Symlink
 // or fs.UnsupportedEntry.
+// It uses DefaultOptions for configuration.
 func NewEntry(path string) (fs.Entry, error) {
+       return NewEntryWithOptions(path, DefaultOptions)
+}
+
+// NewEntryWithOptions returns fs.Entry for the specified path, the result 
will be one of supported entry types: fs.File, fs.Directory, fs.Symlink
+// or fs.UnsupportedEntry.
+// It uses the provided Options for configuration.
+func NewEntryWithOptions(path string, options *Options) (fs.Entry, error) {
        path = filepath.Clean(path)
 
        fi, err := os.Lstat(path)
@@ -130,42 +143,42 @@
        }
 
        if path == "/" {
-               return entryFromDirEntry("/", fi, ""), nil
+               return entryFromDirEntry("/", fi, "", options), nil
        }
 
        basename, prefix := splitDirPrefix(path)
 
-       return entryFromDirEntry(basename, fi, prefix), nil
+       return entryFromDirEntry(basename, fi, prefix, options), nil
 }
 
-func entryFromDirEntry(basename string, fi os.FileInfo, prefix string) 
fs.Entry {
+func entryFromDirEntry(basename string, fi os.FileInfo, prefix string, options 
*Options) fs.Entry {
        isplaceholder := strings.HasSuffix(basename, ShallowEntrySuffix)
        maskedmode := fi.Mode() & os.ModeType
 
        switch {
        case maskedmode == os.ModeDir && !isplaceholder:
-               return newFilesystemDirectory(newEntry(basename, fi, prefix))
+               return newFilesystemDirectory(newEntry(basename, fi, prefix, 
options))
 
        case maskedmode == os.ModeDir && isplaceholder:
-               return newShallowFilesystemDirectory(newEntry(basename, fi, 
prefix))
+               return newShallowFilesystemDirectory(newEntry(basename, fi, 
prefix, options))
 
        case maskedmode == os.ModeSymlink && !isplaceholder:
-               return newFilesystemSymlink(newEntry(basename, fi, prefix))
+               return newFilesystemSymlink(newEntry(basename, fi, prefix, 
options))
 
        case maskedmode == 0 && !isplaceholder:
-               return newFilesystemFile(newEntry(basename, fi, prefix))
+               return newFilesystemFile(newEntry(basename, fi, prefix, 
options))
 
        case maskedmode == 0 && isplaceholder:
-               return newShallowFilesystemFile(newEntry(basename, fi, prefix))
+               return newShallowFilesystemFile(newEntry(basename, fi, prefix, 
options))
 
        default:
-               return newFilesystemErrorEntry(newEntry(basename, fi, prefix), 
fs.ErrUnknown)
+               return newFilesystemErrorEntry(newEntry(basename, fi, prefix, 
options), fs.ErrUnknown)
        }
 }
 
 var _ os.FileInfo = (*filesystemEntry)(nil)
 
-func newEntry(basename string, fi os.FileInfo, prefix string) filesystemEntry {
+func newEntry(basename string, fi os.FileInfo, prefix string, options 
*Options) filesystemEntry {
        return filesystemEntry{
                TrimShallowSuffix(basename),
                fi.Size(),
@@ -174,5 +187,6 @@
                platformSpecificOwnerInfo(fi),
                platformSpecificDeviceInfo(fi),
                prefix,
+               options,
        }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/fs/localfs/local_fs_test.go 
new/kopia-0.22.3/fs/localfs/local_fs_test.go
--- old/kopia-0.22.2/fs/localfs/local_fs_test.go        2025-11-26 
08:05:28.000000000 +0100
+++ new/kopia-0.22.3/fs/localfs/local_fs_test.go        2025-12-02 
06:27:24.000000000 +0100
@@ -306,3 +306,277 @@
                require.Equal(t, want.prefix, prefix, input)
        }
 }
+
+// getOptionsFromEntry extracts the options pointer from an fs.Entry by type 
assertion.
+// Returns nil if the entry doesn't have options or if type assertion fails.
+func getOptionsFromEntry(entry fs.Entry) *Options {
+       switch e := entry.(type) {
+       case *filesystemDirectory:
+               return e.options
+       case *filesystemFile:
+               return e.options
+       case *filesystemSymlink:
+               return e.options
+       case *filesystemErrorEntry:
+               return e.options
+       default:
+               return nil
+       }
+}
+
+func TestOptionsPassedToChildEntries(t *testing.T) {
+       ctx := testlogging.Context(t)
+       tmp := testutil.TempDirectory(t)
+
+       // Create a test directory structure
+       require.NoError(t, os.WriteFile(filepath.Join(tmp, "file1.txt"), 
[]byte{1, 2, 3}, 0o777))
+       require.NoError(t, os.WriteFile(filepath.Join(tmp, "file2.txt"), 
[]byte{4, 5, 6}, 0o777))
+       subdir := filepath.Join(tmp, "subdir")
+       require.NoError(t, os.Mkdir(subdir, 0o777))
+       require.NoError(t, os.WriteFile(filepath.Join(subdir, "subfile.txt"), 
[]byte{7, 8, 9}, 0o777))
+
+       // Create custom options
+       customOptions := &Options{
+               IgnoreUnreadableDirEntries: true,
+       }
+
+       // Create directory with custom options
+       dir, err := DirectoryWithOptions(tmp, customOptions)
+       require.NoError(t, err)
+
+       // Verify the directory itself has the correct options
+       dirOptions := getOptionsFromEntry(dir)
+       require.NotNil(t, dirOptions, "directory should have options")
+       require.Equal(t, customOptions, dirOptions, "directory should have the 
same options pointer")
+       require.True(t, dirOptions.IgnoreUnreadableDirEntries, "directory 
options should match")
+
+       // Test that options are passed to children via Child()
+       childFile, err := dir.Child(ctx, "file1.txt")
+       require.NoError(t, err)
+
+       childOptions := getOptionsFromEntry(childFile)
+       require.NotNil(t, childOptions, "child file should have options")
+       require.Equal(t, customOptions, childOptions, "child file should have 
the same options pointer")
+
+       // Test that options are passed to subdirectories
+       childDir, err := dir.Child(ctx, "subdir")
+       require.NoError(t, err)
+
+       subdirOptions := getOptionsFromEntry(childDir)
+       require.NotNil(t, subdirOptions, "subdirectory should have options")
+       require.Equal(t, customOptions, subdirOptions, "subdirectory should 
have the same options pointer")
+
+       // Test that options are passed to nested children
+       subdirEntry, ok := childDir.(fs.Directory)
+       require.True(t, ok, "child directory should be a directory")
+
+       nestedFile, err := subdirEntry.Child(ctx, "subfile.txt")
+       require.NoError(t, err)
+
+       nestedOptions := getOptionsFromEntry(nestedFile)
+       require.NotNil(t, nestedOptions, "nested file should have options")
+       require.Equal(t, customOptions, nestedOptions, "nested file should have 
the same options pointer")
+}
+
+func TestOptionsPassedThroughIteration(t *testing.T) {
+       ctx := testlogging.Context(t)
+       tmp := testutil.TempDirectory(t)
+
+       // Create a test directory structure
+       require.NoError(t, os.WriteFile(filepath.Join(tmp, "file1.txt"), 
[]byte{1, 2, 3}, 0o777))
+       require.NoError(t, os.WriteFile(filepath.Join(tmp, "file2.txt"), 
[]byte{4, 5, 6}, 0o777))
+       require.NoError(t, os.Mkdir(filepath.Join(tmp, "subdir"), 0o777))
+
+       // Create custom options
+       customOptions := &Options{
+               IgnoreUnreadableDirEntries: true,
+       }
+
+       // Create directory with custom options
+       dir, err := DirectoryWithOptions(tmp, customOptions)
+       require.NoError(t, err)
+
+       // Iterate through entries and verify all have the same options pointer
+       iter, err := dir.Iterate(ctx)
+       require.NoError(t, err)
+
+       defer iter.Close()
+
+       entryCount := 0
+       for {
+               entry, err := iter.Next(ctx)
+               if err != nil {
+                       t.Fatalf("iteration error: %v", err)
+               }
+
+               if entry == nil {
+                       break
+               }
+
+               entryCount++
+               entryOptions := getOptionsFromEntry(entry)
+               require.NotNil(t, entryOptions, "entry %s should have options", 
entry.Name())
+               require.Equal(t, customOptions, entryOptions, "entry %s should 
have the same options pointer", entry.Name())
+       }
+
+       require.Equal(t, 3, entryCount, "should have found 3 entries")
+}
+
+func TestOptionsPassedThroughSymlinkResolution(t *testing.T) {
+       ctx := testlogging.Context(t)
+       tmp := testutil.TempDirectory(t)
+
+       // Create a target file
+       targetFile := filepath.Join(tmp, "target.txt")
+       require.NoError(t, os.WriteFile(targetFile, []byte{1, 2, 3}, 0o777))
+
+       // Create a symlink
+       symlinkPath := filepath.Join(tmp, "link")
+       require.NoError(t, os.Symlink(targetFile, symlinkPath))
+
+       // Create custom options
+       customOptions := &Options{
+               IgnoreUnreadableDirEntries: true,
+       }
+
+       // Create symlink entry with custom options
+       symlinkEntry, err := NewEntryWithOptions(symlinkPath, customOptions)
+       require.NoError(t, err)
+
+       // Verify the symlink has the correct options
+       symlinkOptions := getOptionsFromEntry(symlinkEntry)
+       require.NotNil(t, symlinkOptions, "symlink should have options")
+       require.Equal(t, customOptions, symlinkOptions, "symlink should have 
the same options pointer")
+
+       // Resolve the symlink and verify the resolved entry has the same 
options
+       symlink, ok := symlinkEntry.(fs.Symlink)
+       require.True(t, ok, "entry should be a symlink")
+
+       resolved, err := symlink.Resolve(ctx)
+       require.NoError(t, err)
+
+       resolvedOptions := getOptionsFromEntry(resolved)
+       require.NotNil(t, resolvedOptions, "resolved entry should have options")
+       require.Equal(t, customOptions, resolvedOptions, "resolved entry should 
have the same options pointer")
+}
+
+func TestOptionsPassedToNewEntry(t *testing.T) {
+       tmp := testutil.TempDirectory(t)
+
+       // Create a file
+       filePath := filepath.Join(tmp, "testfile.txt")
+       require.NoError(t, os.WriteFile(filePath, []byte{1, 2, 3}, 0o777))
+
+       // Create custom options
+       customOptions := &Options{
+               IgnoreUnreadableDirEntries: true,
+       }
+
+       // Create entry with custom options
+       entry, err := NewEntryWithOptions(filePath, customOptions)
+       require.NoError(t, err)
+
+       // Verify the entry has the correct options
+       entryOptions := getOptionsFromEntry(entry)
+       require.NotNil(t, entryOptions, "entry should have options")
+       require.Equal(t, customOptions, entryOptions, "entry should have the 
same options pointer")
+}
+
+func TestOptionsPassedToNestedDirectories(t *testing.T) {
+       ctx := testlogging.Context(t)
+       tmp := testutil.TempDirectory(t)
+
+       // Create nested directory structure
+       level1 := filepath.Join(tmp, "level1")
+       level2 := filepath.Join(level1, "level2")
+       level3 := filepath.Join(level2, "level3")
+
+       require.NoError(t, os.MkdirAll(level3, 0o777))
+       require.NoError(t, os.WriteFile(filepath.Join(level3, "file.txt"), 
[]byte{1, 2, 3}, 0o777))
+
+       // Create custom options
+       customOptions := &Options{
+               IgnoreUnreadableDirEntries: true,
+       }
+
+       // Create root directory with custom options
+       rootDir, err := DirectoryWithOptions(tmp, customOptions)
+       require.NoError(t, err)
+
+       // Navigate through nested directories and verify options are passed
+       currentDir := rootDir
+       levels := []string{"level1", "level2", "level3"}
+
+       for _, level := range levels {
+               child, err := currentDir.Child(ctx, level)
+               require.NoError(t, err)
+
+               childOptions := getOptionsFromEntry(child)
+               require.NotNil(t, childOptions, "directory %s should have 
options", level)
+               require.Equal(t, customOptions, childOptions, "directory %s 
should have the same options pointer", level)
+
+               var ok bool
+
+               currentDir, ok = child.(fs.Directory)
+               require.True(t, ok, "child should be a directory")
+       }
+
+       // Verify the file in the deepest directory has the same options
+       file, err := currentDir.Child(ctx, "file.txt")
+       require.NoError(t, err)
+
+       fileOptions := getOptionsFromEntry(file)
+       require.NotNil(t, fileOptions, "file should have options")
+       require.Equal(t, customOptions, fileOptions, "file should have the same 
options pointer")
+}
+
+func TestDefaultOptionsUsedByDefault(t *testing.T) {
+       tmp := testutil.TempDirectory(t)
+
+       // Create a file
+       filePath := filepath.Join(tmp, "testfile.txt")
+       require.NoError(t, os.WriteFile(filePath, []byte{1, 2, 3}, 0o777))
+
+       // Use default NewEntry (should use DefaultOptions)
+       entry, err := NewEntry(filePath)
+       require.NoError(t, err)
+
+       // Verify the entry has DefaultOptions
+       entryOptions := getOptionsFromEntry(entry)
+       require.NotNil(t, entryOptions, "entry should have options")
+       require.Equal(t, DefaultOptions, entryOptions, "entry should have 
DefaultOptions pointer")
+}
+
+func TestDifferentOptionsInstances(t *testing.T) {
+       tmp := testutil.TempDirectory(t)
+
+       // Create two different files
+       filePath1 := filepath.Join(tmp, "testfile1.txt")
+       filePath2 := filepath.Join(tmp, "testfile2.txt")
+
+       require.NoError(t, os.WriteFile(filePath1, []byte{1, 2, 3}, 0o777))
+       require.NoError(t, os.WriteFile(filePath2, []byte{4, 5, 6}, 0o777))
+
+       // Create two different options instances with same values
+       options1 := &Options{IgnoreUnreadableDirEntries: true}
+       options2 := &Options{IgnoreUnreadableDirEntries: false}
+
+       // Create entries with different options instances
+       entry1, err := NewEntryWithOptions(filePath1, options1)
+       require.NoError(t, err)
+
+       entry2, err := NewEntryWithOptions(filePath2, options2)
+       require.NoError(t, err)
+
+       // Verify they have the correct options pointers
+       entry1Options := getOptionsFromEntry(entry1)
+       entry2Options := getOptionsFromEntry(entry2)
+
+       require.NotNil(t, entry1Options)
+       require.NotNil(t, entry2Options)
+       require.Equal(t, options1, entry1Options, "entry1 should have options1 
pointer")
+       require.Equal(t, options2, entry2Options, "entry2 should have options2 
pointer")
+       require.NotEqual(t, entry1Options, entry2Options, "entries should have 
different options pointers")
+       require.True(t, entry1Options.IgnoreUnreadableDirEntries, "entry1 
options should have IgnoreUnreadableDirEntries=true")
+       require.False(t, entry2Options.IgnoreUnreadableDirEntries, "entry2 
options should have IgnoreUnreadableDirEntries=false")
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/go.mod new/kopia-0.22.3/go.mod
--- old/kopia-0.22.2/go.mod     2025-11-26 08:05:28.000000000 +0100
+++ new/kopia-0.22.3/go.mod     2025-12-02 06:27:24.000000000 +0100
@@ -27,9 +27,9 @@
        github.com/gorilla/mux v1.8.1
        github.com/hanwen/go-fuse/v2 v2.9.0
        github.com/hashicorp/cronexpr v1.1.3
-       github.com/klauspost/compress v1.18.1
+       github.com/klauspost/compress v1.18.2
        github.com/klauspost/pgzip v1.2.6
-       github.com/klauspost/reedsolomon v1.12.5
+       github.com/klauspost/reedsolomon v1.12.6
        github.com/kopia/htmluibuild v0.0.1-0.20251125011029-7f1c3f84f29d
        github.com/kylelemons/godebug v1.1.0
        github.com/mattn/go-colorable v0.1.14
@@ -119,7 +119,7 @@
        github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
        github.com/googleapis/gax-go/v2 v2.15.0 // indirect
        github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
-       github.com/klauspost/cpuid/v2 v2.2.11 // indirect
+       github.com/klauspost/cpuid/v2 v2.3.0 // indirect
        github.com/klauspost/crc32 v1.3.0 // indirect
        github.com/kr/fs v0.1.0 // indirect
        github.com/minio/crc64nvme v1.1.0 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/go.sum new/kopia-0.22.3/go.sum
--- old/kopia-0.22.2/go.sum     2025-11-26 08:05:28.000000000 +0100
+++ new/kopia-0.22.3/go.sum     2025-12-02 06:27:24.000000000 +0100
@@ -170,17 +170,17 @@
 github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod 
h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/keybase/go-keychain v0.0.1 
h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
 github.com/keybase/go-keychain v0.0.1/go.mod 
h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
-github.com/klauspost/compress v1.18.1 
h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
-github.com/klauspost/compress v1.18.1/go.mod 
h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
+github.com/klauspost/compress v1.18.2 
h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
+github.com/klauspost/compress v1.18.2/go.mod 
h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod 
h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.11 
h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
-github.com/klauspost/cpuid/v2 v2.2.11/go.mod 
h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/klauspost/cpuid/v2 v2.3.0 
h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
+github.com/klauspost/cpuid/v2 v2.3.0/go.mod 
h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 github.com/klauspost/crc32 v1.3.0 
h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
 github.com/klauspost/crc32 v1.3.0/go.mod 
h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
 github.com/klauspost/pgzip v1.2.6 
h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
 github.com/klauspost/pgzip v1.2.6/go.mod 
h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
-github.com/klauspost/reedsolomon v1.12.5 
h1:4cJuyH926If33BeDgiZpI5OU0pE+wUHZvMSyNGqN73Y=
-github.com/klauspost/reedsolomon v1.12.5/go.mod 
h1:LkXRjLYGM8K/iQfujYnaPeDmhZLqkrGUyG9p7zs5L68=
+github.com/klauspost/reedsolomon v1.12.6 
h1:8pqE9aECQG/ZFitiUD1xK/E83zwosBAZtE3UbuZM8TQ=
+github.com/klauspost/reedsolomon v1.12.6/go.mod 
h1:ggJT9lc71Vu+cSOPBlxGvBN6TfAS77qB4fp8vJ05NSA=
 github.com/kopia/htmluibuild v0.0.1-0.20251125011029-7f1c3f84f29d 
h1:U3VB/cDMsPW4zB4JRFbVRDzIpPytt889rJUKAG40NPA=
 github.com/kopia/htmluibuild v0.0.1-0.20251125011029-7f1c3f84f29d/go.mod 
h1:h53A5JM3t2qiwxqxusBe+PFgGcgZdS+DWCQvG5PTlto=
 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/repo/format/content_format.go 
new/kopia-0.22.3/repo/format/content_format.go
--- old/kopia-0.22.2/repo/format/content_format.go      2025-11-26 
08:05:28.000000000 +0100
+++ new/kopia-0.22.3/repo/format/content_format.go      2025-12-02 
06:27:24.000000000 +0100
@@ -63,10 +63,10 @@
 // MutableParameters represents parameters of the content manager that can be 
mutated after the repository
 // is created.
 type MutableParameters struct {
-       Version         Version          `json:"version,omitempty"`         // 
version number, must be "1", "2" or "3"
-       MaxPackSize     int              `json:"maxPackSize,omitempty"`     // 
maximum size of a pack object
-       IndexVersion    int              `json:"indexVersion,omitempty"`    // 
force particular index format version (1,2,..)
-       EpochParameters epoch.Parameters `json:"epochParameters,omitempty"` // 
epoch manager parameters
+       Version         Version          `json:"version,omitempty"`      // 
version number, must be "1", "2" or "3"
+       MaxPackSize     int              `json:"maxPackSize,omitempty"`  // 
maximum size of a pack object
+       IndexVersion    int              `json:"indexVersion,omitempty"` // 
force particular index format version (1,2,..)
+       EpochParameters epoch.Parameters `json:"epochParameters"`        // 
epoch manager parameters
 }
 
 // Validate validates the parameters.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/repo/format/upgrade_lock_intent.go 
new/kopia-0.22.3/repo/format/upgrade_lock_intent.go
--- old/kopia-0.22.2/repo/format/upgrade_lock_intent.go 2025-11-26 
08:05:28.000000000 +0100
+++ new/kopia-0.22.3/repo/format/upgrade_lock_intent.go 2025-12-02 
06:27:24.000000000 +0100
@@ -13,7 +13,7 @@
 // repository.
 type UpgradeLockIntent struct {
        OwnerID                string        `json:"ownerID,omitempty"`
-       CreationTime           time.Time     `json:"creationTime,omitempty"`
+       CreationTime           time.Time     `json:"creationTime"`
        AdvanceNoticeDuration  time.Duration 
`json:"advanceNoticeDuration,omitempty"`
        IODrainTimeout         time.Duration `json:"ioDrainTimeout,omitempty"`
        StatusPollInterval     time.Duration 
`json:"statusPollInterval,omitempty"`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/repo/object/indirect.go 
new/kopia-0.22.3/repo/object/indirect.go
--- old/kopia-0.22.2/repo/object/indirect.go    2025-11-26 08:05:28.000000000 
+0100
+++ new/kopia-0.22.3/repo/object/indirect.go    2025-12-02 06:27:24.000000000 
+0100
@@ -4,7 +4,7 @@
 type IndirectObjectEntry struct {
        Start  int64 `json:"s,omitempty"`
        Length int64 `json:"l,omitempty"`
-       Object ID    `json:"o,omitempty"`
+       Object ID    `json:"o"`
 }
 
 func (i *IndirectObjectEntry) endOffset() int64 {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopia-0.22.2/snapshot/manifest.go 
new/kopia-0.22.3/snapshot/manifest.go
--- old/kopia-0.22.2/snapshot/manifest.go       2025-11-26 08:05:28.000000000 
+0100
+++ new/kopia-0.22.3/snapshot/manifest.go       2025-12-02 06:27:24.000000000 
+0100
@@ -126,7 +126,7 @@
        ModTime     fs.UTCTimestamp      `json:"mtime,omitempty"`
        UserID      uint32               `json:"uid,omitempty"`
        GroupID     uint32               `json:"gid,omitempty"`
-       ObjectID    object.ID            `json:"obj,omitempty"`
+       ObjectID    object.ID            `json:"obj"`
        DirSummary  *fs.DirectorySummary `json:"summ,omitempty"`
 }
 
@@ -187,8 +187,8 @@
 type StorageStats struct {
        // amount of new unique data in this snapshot that wasn't there before.
        // note that this depends on ordering of snapshots.
-       NewData      StorageUsageDetails `json:"newData,omitempty"`
-       RunningTotal StorageUsageDetails `json:"runningTotal,omitempty"`
+       NewData      StorageUsageDetails `json:"newData"`
+       RunningTotal StorageUsageDetails `json:"runningTotal"`
 }
 
 // StorageUsageDetails provides details about snapshot storage usage.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kopia-0.22.2/tests/end_to_end_test/profile_flags_test.go 
new/kopia-0.22.3/tests/end_to_end_test/profile_flags_test.go
--- old/kopia-0.22.2/tests/end_to_end_test/profile_flags_test.go        
1970-01-01 01:00:00.000000000 +0100
+++ new/kopia-0.22.3/tests/end_to_end_test/profile_flags_test.go        
2025-12-02 06:27:24.000000000 +0100
@@ -0,0 +1,47 @@
+package endtoend_test
+
+import (
+       "os"
+       "path/filepath"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+
+       "github.com/kopia/kopia/tests/testenv"
+)
+
+func TestProfileFlags(t *testing.T) {
+       env := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, 
testenv.NewExeRunner(t))
+
+       // contents not needed on test failure
+       diagsDir := t.TempDir()
+
+       env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", 
env.RepoDir)
+       env.RunAndExpectSuccess(t, "repo", "status",
+               "--diagnostics-output-directory", diagsDir,
+               "--profile-store-on-exit",
+               "--profile-cpu",
+               "--profile-blocking-rate=1",
+               "--profile-mutex-fraction=1",
+               "--profile-memory-rate=1",
+       )
+
+       // get per-execution directory
+       entries, err := os.ReadDir(diagsDir)
+       require.NoError(t, err)
+       require.NotEmpty(t, entries)
+
+       pprofDir := filepath.Join(diagsDir, entries[0].Name(), "profiles")
+
+       for _, name := range []string{"cpu.pprof", "allocs.pprof", 
"block.pprof", "goroutine.pprof", "mutex.pprof", "heap.pprof", 
"threadcreate.pprof"} {
+               f := filepath.Join(pprofDir, name)
+               t.Run(f, func(t *testing.T) {
+                       require.FileExists(t, f, "expected profile file")
+
+                       info, err := os.Stat(f)
+
+                       require.NoError(t, err)
+                       require.NotZero(t, info.Size(), "profile file %s should 
not be empty", f)
+               })
+       }
+}

++++++ kopia.obsinfo ++++++
--- /var/tmp/diff_new_pack.i1JyhY/_old  2025-12-05 16:52:36.502282517 +0100
+++ /var/tmp/diff_new_pack.i1JyhY/_new  2025-12-05 16:52:36.510282852 +0100
@@ -1,5 +1,5 @@
 name: kopia
-version: 0.22.2
-mtime: 1764140728
-commit: e456f78fa2d15b102988eee1025a2451eeaa3ebf
+version: 0.22.3
+mtime: 1764653244
+commit: 154bf56899228e5c95fb3176b9c6901bbe4ca97b
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/kopia/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.kopia.new.1939/vendor.tar.gz differ: char 133, line 
2

Reply via email to