Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package asdf for openSUSE:Factory checked in 
at 2026-07-03 16:19:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/asdf (Old)
 and      /work/SRC/openSUSE:Factory/.asdf.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "asdf"

Fri Jul  3 16:19:23 2026 rev:8 rq:1363666 version:0.19.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/asdf/asdf.changes        2026-03-16 
14:21:05.338654110 +0100
+++ /work/SRC/openSUSE:Factory/.asdf.new.1982/asdf.changes      2026-07-03 
16:19:30.187679149 +0200
@@ -1,0 +2,28 @@
+Fri Jul 03 11:21:17 UTC 2026 - Robert Munteanu <[email protected]>
+
+- Update to version 0.19.0:
+  * chore: release 0.19.0 (#2261)
+  * fix: make plugin test command resolve latest version properly
+  * feat: don't override semver ordering during completion (#2263)
+  * fix: set ASDF_INSTALL_* environment variables for reshim command
+  * chore(deps): bump github.com/go-git/go-git/v5 from 5.17.1 to 5.18.0 (#2270)
+  * fix: convert hyphens to underscores when converting plugin names to 
generate valid environment variables
+  * chore: update asdf install action to fix lint workflow (#2264)
+  * chore(golang-rewrite): remove old Bash implementation code (#1822)
+  * fix: use os.PathListSeparator (#2195)
+  * fix: return error from repo.RemoteUrl function
+  * fix: nushell completions --ignore-errors deprecation (#2157)
+  * chore(docs): bump @types/node from 25.3.3 to 25.5.0 in /docs in the docs 
group (#2260)
+  * chore(deps): bump github.com/go-git/go-git/v5 from 5.16.5 to 5.17.1 (#2259)
+  * chore(deps): bump dorny/paths-filter from 3 to 4 (#2257)
+  * chore(deps): bump crazy-max/ghaction-github-pages from 4 to 5 (#2251)
+  * TrimSpace on the URL returned by RemoteURL()
+  * docs: update nodejs script for formatting the documentation to be the 
correct `npm run fmt` from the package, as the `npm run format` does not exist
+  * docs: remove `The Oh-My-ZSH asdf plugin is yet to be updated, see 
[ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837).` from the 
docs for zsh as it has been 
[updated](https://github.com/ohmyzsh/ohmyzsh/pull/8837#issuecomment-2174169900)
+
+-------------------------------------------------------------------
+Fri Jul  3 11:16:15 UTC 2026 - Robert Munteanu <[email protected]>
+
+- Add completion packages 
+
+-------------------------------------------------------------------

Old:
----
  asdf-0.18.1.obscpio

New:
----
  asdf-0.19.0.obscpio

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

Other differences:
------------------
++++++ asdf.spec ++++++
--- /var/tmp/diff_new_pack.daxAbV/_old  2026-07-03 16:19:33.067779401 +0200
+++ /var/tmp/diff_new_pack.daxAbV/_new  2026-07-03 16:19:33.067779401 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           asdf
-Version:        0.18.1
+Version:        0.19.0
 Release:        0
 Summary:        Extendable version manager
 License:        MIT
@@ -25,6 +25,9 @@
 Source:         %{name}-%{version}.tar.gz
 Source1:        vendor.tar.gz
 BuildRequires:  golang(API) >= 1.23
+BuildRequires:  bash-completion
+BuildRequires:  fish
+BuildRequires:  zsh
 
 %description
 Extendable version manager with support for
@@ -33,6 +36,33 @@
 Manage multiple runtime versions with a single CLI tool,
 extendable via plugins.
 
+%package bash-completion
+Summary:        Bash Completion for %{name}
+Requires:       %{name} = %{version}
+Supplements:    (%{name} and bash-completion)
+BuildArch:      noarch
+
+%description bash-completion
+Bash command line completion support for %{name}.
+
+%package zsh-completion
+Summary:        Zsh Completion for %{name}
+Requires:       %{name} = %{version}
+Supplements:    (%{name} and zsh)
+BuildArch:      noarch
+
+%description zsh-completion
+Zsh command line completion support for %{name}.
+
+%package fish-completion
+Summary:        Fish Completion for %{name}
+Requires:       %{name} = %{version}
+Supplements:    (%{name} and fish)
+BuildArch:      noarch
+
+%description fish-completion
+Fish command line completion support for %{name}.
+
 %prep
 %autosetup -a 1
 
@@ -44,9 +74,27 @@
 
 %install
 install -D -m 0755 %{name} "%{buildroot}/%{_bindir}/%{name}"
+install -d %{buildroot}%{_datadir}/bash-completion/completions
+%{buildroot}%{_bindir}/%{name} completion bash > 
%{buildroot}%{_datarootdir}/bash-completion/completions/%{name}
+install -d %{buildroot}%{_datarootdir}/zsh/site-functions
+%{buildroot}%{_bindir}/%{name} completion zsh > 
%{buildroot}%{_datarootdir}/zsh/site-functions/_%{name}
+install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
+%{buildroot}%{_bindir}/%{name} completion fish > 
%{buildroot}%{_datadir}/fish/vendor_completions.d/%{name}.fish
 
 %files
 %doc README.md
 %license LICENSE
 %{_bindir}/%{name}
 
+%files bash-completion
+%defattr(-,root,root)
+%{_datarootdir}/bash-completion/completions/%{name}
+
+%files zsh-completion
+%defattr(-,root,root)
+%{_datarootdir}/zsh/site-functions/_%{name}
+
+%files fish-completion
+%defattr(-,root,root)
+%{_datadir}/fish/vendor_completions.d/%{name}.fish
+

++++++ _service ++++++
--- /var/tmp/diff_new_pack.daxAbV/_old  2026-07-03 16:19:33.127781489 +0200
+++ /var/tmp/diff_new_pack.daxAbV/_new  2026-07-03 16:19:33.131781629 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/asdf-vm/asdf/</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v0.18.1</param>
+    <param name="revision">v0.19.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="changesgenerate">enable</param>
     <param name="versionrewrite-pattern">v(.*)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.daxAbV/_old  2026-07-03 16:19:33.159782603 +0200
+++ /var/tmp/diff_new_pack.daxAbV/_new  2026-07-03 16:19:33.163782742 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/asdf-vm/asdf/</param>
-              <param 
name="changesrevision">6a97697d3d88d6b7ad19c4bd3157679b052ab657</param></service></servicedata>
+              <param 
name="changesrevision">b0ae66dc5131aaca03324a1f0eff4d09242c29e1</param></service></servicedata>
 (No newline at EOF)
 

++++++ asdf-0.18.1.obscpio -> asdf-0.19.0.obscpio ++++++
++++ 4612 lines of diff (skipped)

++++++ asdf.obsinfo ++++++
--- /var/tmp/diff_new_pack.daxAbV/_old  2026-07-03 16:19:33.887807945 +0200
+++ /var/tmp/diff_new_pack.daxAbV/_new  2026-07-03 16:19:33.907808641 +0200
@@ -1,5 +1,5 @@
 name: asdf
-version: 0.18.1
-mtime: 1772590085
-commit: 6a97697d3d88d6b7ad19c4bd3157679b052ab657
+version: 0.19.0
+mtime: 1776997685
+commit: b0ae66dc5131aaca03324a1f0eff4d09242c29e1
 

++++++ vendor.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go 
new/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go
--- old/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go       
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go       
2026-04-24 04:28:05.000000000 +0200
@@ -14,14 +14,14 @@
 type storageBuf [maxRate / 8]uint64
 
 func (b *storageBuf) asBytes() *[maxRate]byte {
-       return (*[maxRate]byte)(unsafe.Pointer(b))
+       return (*[maxRate]byte)(unsafe.Pointer(b)) //nolint:gosec
 }
 
 // xorInuses unaligned reads and writes to update d.a to contain d.a
 // XOR buf.
 func xorIn(d *State, buf []byte) {
        n := len(buf)
-       bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8]
+       bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] 
//nolint:gosec
        if n >= 72 {
                d.a[0] ^= bw[0]
                d.a[1] ^= bw[1]
@@ -56,6 +56,6 @@
 }
 
 func copyOut(d *State, buf []byte) {
-       ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
+       ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) //nolint:gosec
        copy(buf, ab[:])
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/github.com/cloudflare/circl/sign/sign.go 
new/vendor/github.com/cloudflare/circl/sign/sign.go
--- old/vendor/github.com/cloudflare/circl/sign/sign.go 2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/cloudflare/circl/sign/sign.go 2026-04-24 
04:28:05.000000000 +0200
@@ -38,6 +38,12 @@
        encoding.BinaryMarshaler
 }
 
+// A private key that retains the seed with which it was generated.
+type Seeded interface {
+       // returns the seed if retained, otherwise nil
+       Seed() []byte
+}
+
 // A Scheme represents a specific instance of a signature scheme.
 type Scheme interface {
        // Name of the scheme.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/github.com/go-git/go-billy/v5/fs.go 
new/vendor/github.com/go-git/go-billy/v5/fs.go
--- old/vendor/github.com/go-git/go-billy/v5/fs.go      2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/fs.go      2026-04-24 
04:28:05.000000000 +0200
@@ -128,12 +128,18 @@
        Readlink(link string) (string, error)
 }
 
-// Change abstract the FileInfo change related operations in a storage-agnostic
-// interface as an extension to the Basic interface
-type Change interface {
+// Chmod abstracts the logic around changing file modes.
+type Chmod interface {
        // Chmod changes the mode of the named file to mode. If the file is a
        // symbolic link, it changes the mode of the link's target.
        Chmod(name string, mode os.FileMode) error
+}
+
+// Change abstract the FileInfo change related operations in a storage-agnostic
+// interface as an extension to the Basic interface
+type Change interface {
+       Chmod
+
        // Lchown changes the numeric uid and gid of the named file. If the 
file is
        // a symbolic link, it changes the uid and gid of the link itself.
        Lchown(name string, uid, gid int) error
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go 
new/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go
--- old/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go    
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go    
2026-04-24 04:28:05.000000000 +0200
@@ -1,6 +1,7 @@
 package chroot
 
 import (
+       "errors"
        "os"
        "path/filepath"
        "strings"
@@ -200,6 +201,19 @@
        return string(os.PathSeparator) + target, nil
 }
 
+func (fs *ChrootHelper) Chmod(path string, mode os.FileMode) error {
+       fullpath, err := fs.underlyingPath(path)
+       if err != nil {
+               return err
+       }
+
+       c, ok := fs.underlying.(billy.Chmod)
+       if !ok {
+               return errors.New("underlying fs does not implement 
billy.Chmod")
+       }
+       return c.Chmod(fullpath, mode)
+}
+
 func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) {
        fullpath, err := fs.underlyingPath(path)
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go 
new/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go
--- old/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go        
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go        
2026-04-24 04:28:05.000000000 +0200
@@ -13,7 +13,7 @@
        c capabilities
 }
 
-type capabilities struct{ tempfile, dir, symlink, chroot bool }
+type capabilities struct{ tempfile, dir, symlink, chroot, chmod bool }
 
 // New creates a new filesystem wrapping up 'fs' the intercepts all the calls
 // made and errors if fs doesn't implement any of the billy interfaces.
@@ -28,6 +28,7 @@
        _, h.c.dir = h.Basic.(billy.Dir)
        _, h.c.symlink = h.Basic.(billy.Symlink)
        _, h.c.chroot = h.Basic.(billy.Chroot)
+       _, h.c.chmod = h.Basic.(billy.Chmod)
        return h
 }
 
@@ -87,6 +88,14 @@
        return h.Basic.(billy.Chroot).Chroot(path)
 }
 
+func (h *Polyfill) Chmod(path string, mode os.FileMode) error {
+       if !h.c.chmod {
+               return billy.ErrNotSupported
+       }
+
+       return h.Basic.(billy.Chmod).Chmod(path, mode)
+}
+
 func (h *Polyfill) Root() string {
        if !h.c.chroot {
                return string(filepath.Separator)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/github.com/go-git/go-billy/v5/memfs/memory.go 
new/vendor/github.com/go-git/go-billy/v5/memfs/memory.go
--- old/vendor/github.com/go-git/go-billy/v5/memfs/memory.go    2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/memfs/memory.go    2026-04-24 
04:28:05.000000000 +0200
@@ -177,6 +177,10 @@
        return fs.s.Remove(filename)
 }
 
+func (fs *Memory) Chmod(path string, mode os.FileMode) error {
+       return fs.s.Chmod(path, mode)
+}
+
 // Falls back to Go's filepath.Join, which works differently depending on the
 // OS where the code is being executed.
 func (fs *Memory) Join(elem ...string) string {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-billy/v5/memfs/storage.go 
new/vendor/github.com/go-git/go-billy/v5/memfs/storage.go
--- old/vendor/github.com/go-git/go-billy/v5/memfs/storage.go   2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/memfs/storage.go   2026-04-24 
04:28:05.000000000 +0200
@@ -169,6 +169,18 @@
        return nil
 }
 
+func (s *storage) Chmod(path string, mode os.FileMode) error {
+       path = clean(path)
+
+       f, has := s.Get(path)
+       if !has {
+               return os.ErrNotExist
+       }
+
+       f.mode = mode
+       return nil
+}
+
 func clean(path string) string {
        return filepath.Clean(filepath.FromSlash(path))
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go 
new/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go
--- old/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go   2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go   2026-04-24 
04:28:05.000000000 +0200
@@ -126,6 +126,14 @@
                if err != nil {
                        return nil, err
                }
+
+               _, err = os.Stat(dir)
+               if err != nil && os.IsNotExist(err) {
+                       err = os.MkdirAll(dir, defaultDirectoryMode)
+                       if err != nil {
+                               return nil, err
+                       }
+               }
        }
 
        return tempFile(dir, prefix)
@@ -176,6 +184,14 @@
        return os.Readlink(link)
 }
 
+func (fs *BoundOS) Chmod(path string, mode os.FileMode) error {
+       abspath, err := fs.abs(path)
+       if err != nil {
+               return err
+       }
+       return os.Chmod(abspath, mode)
+}
+
 // Chroot returns a new OS filesystem, with the base dir set to the
 // result of joining the provided path with the underlying base dir.
 func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go 
new/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go
--- old/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go  2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go  2026-04-24 
04:28:05.000000000 +0200
@@ -74,6 +74,10 @@
        return os.Remove(filename)
 }
 
+func (fs *ChrootOS) Chmod(path string, mode os.FileMode) error {
+       return os.Chmod(path, mode)
+}
+
 func (fs *ChrootOS) TempFile(dir, prefix string) (billy.File, error) {
        if err := fs.createDir(dir + string(os.PathSeparator)); err != nil {
                return nil, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/plumbing/format/idxfile/decoder.go 
new/vendor/github.com/go-git/go-git/v5/plumbing/format/idxfile/decoder.go
--- old/vendor/github.com/go-git/go-git/v5/plumbing/format/idxfile/decoder.go   
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/plumbing/format/idxfile/decoder.go   
2026-04-24 04:28:05.000000000 +0200
@@ -91,8 +91,8 @@
                return err
        }
 
-       if v > VersionSupported {
-               return ErrUnsupportedVersion
+       if v != VersionSupported {
+               return fmt.Errorf("%w: v%d", ErrUnsupportedVersion, v)
        }
 
        idx.Version = v
@@ -106,6 +106,10 @@
                        return err
                }
 
+               if k > 0 && n < idx.Fanout[k-1] {
+                       return fmt.Errorf("%w: fanout table is not 
monotonically non-decreasing at entry %d", ErrMalformedIdxFile, k)
+               }
+
                idx.Fanout[k] = n
                idx.FanoutMapping[k] = noMapping
        }
@@ -155,7 +159,7 @@
 }
 
 func readOffsets(idx *MemoryIndex, r io.Reader) error {
-       var o64cnt int
+       var o64cnt int64
        for k := 0; k < fanout; k++ {
                if pos := idx.FanoutMapping[k]; pos != noMapping {
                        if _, err := io.ReadFull(r, idx.Offset32[pos]); err != 
nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/plumbing/format/index/decoder.go 
new/vendor/github.com/go-git/go-git/v5/plumbing/format/index/decoder.go
--- old/vendor/github.com/go-git/go-git/v5/plumbing/format/index/decoder.go     
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/plumbing/format/index/decoder.go     
2026-04-24 04:28:05.000000000 +0200
@@ -4,8 +4,8 @@
        "bufio"
        "bytes"
        "errors"
+       "fmt"
        "io"
-
        "strconv"
        "time"
 
@@ -26,12 +26,14 @@
        ErrInvalidChecksum = errors.New("invalid checksum")
        // ErrUnknownExtension is returned when an index extension is 
encountered that is considered mandatory
        ErrUnknownExtension = errors.New("unknown extension")
+       // ErrMalformedIndexFile is returned when the index file contents are
+       // structurally invalid.
+       ErrMalformedIndexFile = errors.New("index decoder: malformed index 
file")
 )
 
 const (
        entryHeaderLength = 62
        entryExtended     = 0x4000
-       entryValid        = 0x8000
        nameMask          = 0xfff
        intentToAddMask   = 1 << 13
        skipWorkTreeMask  = 1 << 14
@@ -140,33 +142,55 @@
                e.SkipWorktree = extended&skipWorkTreeMask != 0
        }
 
-       if err := d.readEntryName(idx, e, flags); err != nil {
+       nameConsumed, err := d.readEntryName(idx, e, flags)
+       if err != nil {
                return nil, err
        }
 
-       return e, d.padEntry(idx, e, read)
+       return e, d.padEntry(idx, e, read, nameConsumed)
 }
 
-func (d *Decoder) readEntryName(idx *Index, e *Entry, flags uint16) error {
-       var name string
-       var err error
-
+// readEntryName reads the entry path and sets e.Name. It returns the
+// number of bytes consumed from the stream for the name portion.
+func (d *Decoder) readEntryName(idx *Index, e *Entry, flags uint16) (int, 
error) {
        switch idx.Version {
        case 2, 3:
-               len := flags & nameMask
-               name, err = d.doReadEntryName(len)
+               nameLen := flags & nameMask
+               name, consumed, err := d.doReadEntryName(nameLen)
+               if err != nil {
+                       return 0, err
+               }
+               e.Name = name
+               return consumed, nil
        case 4:
-               name, err = d.doReadEntryNameV4()
+               name, err := d.doReadEntryNameV4()
+               if err != nil {
+                       return 0, err
+               }
+               e.Name = name
+               return 0, nil // V4 has no padding; consumed count unused
        default:
-               return ErrUnsupportedVersion
+               return 0, ErrUnsupportedVersion
        }
+}
 
-       if err != nil {
-               return err
+// doReadEntryName reads the entry path for V2/V3 indexes. It returns the
+// name, the number of bytes consumed from the stream, and any error.
+// When nameLen equals nameMask (0xFFF), the name was too long to fit in
+// the 12-bit field and the real length is found by scanning for the NUL
+// terminator — matching C Git's strlen(name) fallback in create_from_disk.
+func (d *Decoder) doReadEntryName(nameLen uint16) (string, int, error) {
+       if nameLen == nameMask {
+               name, err := binary.ReadUntil(d.r, '\x00')
+               if err != nil {
+                       return "", 0, err
+               }
+               return string(name), len(name) + 1, nil // +1 for the consumed 
NUL delimiter
        }
 
-       e.Name = name
-       return nil
+       name := make([]byte, nameLen)
+       _, err := io.ReadFull(d.r, name)
+       return string(name), int(nameLen), err
 }
 
 func (d *Decoder) doReadEntryNameV4() (string, error) {
@@ -177,7 +201,14 @@
 
        var base string
        if d.lastEntry != nil {
+               if l < 0 || int(l) > len(d.lastEntry.Name) {
+                       return "", fmt.Errorf("%w: invalid V4 entry name strip 
length %d (previous name length: %d)",
+                               ErrMalformedIndexFile, l, len(d.lastEntry.Name))
+               }
                base = d.lastEntry.Name[:len(d.lastEntry.Name)-int(l)]
+       } else if l > 0 {
+               return "", fmt.Errorf("%w: non-zero strip length %d on first V4 
entry",
+                       ErrMalformedIndexFile, l)
        }
 
        name, err := binary.ReadUntil(d.r, '\x00')
@@ -188,24 +219,23 @@
        return base + string(name), nil
 }
 
-func (d *Decoder) doReadEntryName(len uint16) (string, error) {
-       name := make([]byte, len)
-       _, err := io.ReadFull(d.r, name)
-
-       return string(name), err
-}
-
-// Index entries are padded out to the next 8 byte alignment
-// for historical reasons related to how C Git read the files.
-func (d *Decoder) padEntry(idx *Index, e *Entry, read int) error {
+// padEntry discards NUL padding bytes that follow each V2/V3 entry on
+// disk. nameConsumed is the number of stream bytes consumed while reading
+// the entry name (which may exceed len(e.Name) when a NUL terminator was
+// consumed for long names where the 12-bit length field overflowed).
+func (d *Decoder) padEntry(idx *Index, e *Entry, read, nameConsumed int) error 
{
        if idx.Version == 4 {
                return nil
        }
 
        entrySize := read + len(e.Name)
        padLen := 8 - entrySize%8
-       _, err := io.CopyN(io.Discard, d.r, int64(padLen))
-       return err
+       padLen -= nameConsumed - len(e.Name)
+       if padLen > 0 {
+               _, err := io.CopyN(io.Discard, d.r, int64(padLen))
+               return err
+       }
+       return nil
 }
 
 func (d *Decoder) readExtensions(idx *Index) error {
@@ -312,7 +342,7 @@
 }
 
 func validateHeader(r io.Reader) (version uint32, err error) {
-       var s = make([]byte, 4)
+       s := make([]byte, 4)
        if _, err := io.ReadFull(r, s); err != nil {
                return 0, err
        }
@@ -376,24 +406,26 @@
                return nil, err
        }
 
-       // An entry can be in an invalidated state and is represented by having 
a
-       // negative number in the entry_count field.
-       if i == -1 {
-               return nil, nil
-       }
-
        e.Entries = i
        trees, err := binary.ReadUntil(d.r, '\n')
        if err != nil {
                return nil, err
        }
 
-       i, err = strconv.Atoi(string(trees))
+       subtrees, err := strconv.Atoi(string(trees))
        if err != nil {
                return nil, err
        }
 
-       e.Trees = i
+       e.Trees = subtrees
+
+       // An entry can be in an invalidated state and is represented by having 
a
+       // negative number in the entry_count field. In this case, there is no
+       // object name and the next entry starts immediately after the newline.
+       if i < 0 {
+               return nil, nil
+       }
+
        _, err = io.ReadFull(d.r, e.Hash[:])
        if err != nil {
                return nil, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/plumbing/format/index/encoder.go 
new/vendor/github.com/go-git/go-git/v5/plumbing/format/index/encoder.go
--- old/vendor/github.com/go-git/go-git/v5/plumbing/format/index/encoder.go     
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/plumbing/format/index/encoder.go     
2026-04-24 04:28:05.000000000 +0200
@@ -5,9 +5,7 @@
        "errors"
        "fmt"
        "io"
-       "path"
        "sort"
-       "strings"
        "time"
 
        "github.com/go-git/go-git/v5/plumbing/hash"
@@ -160,26 +158,39 @@
 }
 
 func (e *Encoder) encodeEntryNameV4(entry *Entry) error {
-       name := entry.Name
-       l := 0
+       // V4 prefix compression: find the longest common prefix between the
+       // previous entry's name and the current one. The strip length tells
+       // the decoder how many bytes to remove from the end of the previous
+       // name, and the suffix is the remainder of the current name.
+       prefix := 0
        if e.lastEntry != nil {
-               dir := path.Dir(e.lastEntry.Name) + "/"
-               if strings.HasPrefix(entry.Name, dir) {
-                       l = len(e.lastEntry.Name) - len(dir)
-                       name = strings.TrimPrefix(entry.Name, dir)
-               } else {
-                       l = len(e.lastEntry.Name)
-               }
+               prefix = commonPrefixLen(e.lastEntry.Name, entry.Name)
+       }
+       stripLen := 0
+       if e.lastEntry != nil {
+               stripLen = len(e.lastEntry.Name) - prefix
        }
 
        e.lastEntry = entry
 
-       err := binary.WriteVariableWidthInt(e.w, int64(l))
-       if err != nil {
+       if err := binary.WriteVariableWidthInt(e.w, int64(stripLen)); err != 
nil {
                return err
        }
 
-       return binary.Write(e.w, []byte(name+string('\x00')))
+       suffix := entry.Name[prefix:]
+       return binary.Write(e.w, append([]byte(suffix), '\x00'))
+}
+
+// commonPrefixLen returns the length of the longest common byte prefix
+// between a and b.
+func commonPrefixLen(a, b string) int {
+       n := min(len(b), len(a))
+       for i := range n {
+               if a[i] != b[i] {
+                       return i
+               }
+       }
+       return n
 }
 
 func (e *Encoder) encodeRawExtension(signature string, data []byte) error {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/plumbing/format/index/index.go 
new/vendor/github.com/go-git/go-git/v5/plumbing/format/index/index.go
--- old/vendor/github.com/go-git/go-git/v5/plumbing/format/index/index.go       
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/plumbing/format/index/index.go       
2026-04-24 04:28:05.000000000 +0200
@@ -54,6 +54,8 @@
        ResolveUndo *ResolveUndo
        // EndOfIndexEntry represents the 'End of Index Entry' extension
        EndOfIndexEntry *EndOfIndexEntry
+       // ModTime is the modification time of the index file
+       ModTime time.Time
 }
 
 // Add creates a new Entry and returns it. The caller should first check that
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/plumbing/transport/http/common.go 
new/vendor/github.com/go-git/go-git/v5/plumbing/transport/http/common.go
--- old/vendor/github.com/go-git/go-git/v5/plumbing/transport/http/common.go    
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/plumbing/transport/http/common.go    
2026-04-24 04:28:05.000000000 +0200
@@ -7,7 +7,6 @@
        "crypto/tls"
        "crypto/x509"
        "fmt"
-       "net"
        "net/http"
        "net/url"
        "reflect"
@@ -24,6 +23,33 @@
        "github.com/go-git/go-git/v5/utils/ioutil"
 )
 
+type contextKey int
+
+const initialRequestKey contextKey = iota
+
+// RedirectPolicy controls how the HTTP transport follows redirects.
+//
+// The values mirror Git's http.followRedirects config:
+// "true" follows redirects for all requests, "false" treats redirects as
+// errors, and "initial" follows redirects only for the initial
+// /info/refs discovery request. The zero value defaults to "initial".
+type RedirectPolicy string
+
+const (
+       FollowInitialRedirects RedirectPolicy = "initial"
+       FollowRedirects        RedirectPolicy = "true"
+       NoFollowRedirects      RedirectPolicy = "false"
+)
+
+func withInitialRequest(ctx context.Context) context.Context {
+       return context.WithValue(ctx, initialRequestKey, true)
+}
+
+func isInitialRequest(req *http.Request) bool {
+       v, _ := req.Context().Value(initialRequestKey).(bool)
+       return v
+}
+
 // it requires a bytes.Buffer, because we need to know the length
 func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host 
string, requestType string) {
        req.Header.Add("User-Agent", capability.DefaultAgent())
@@ -54,12 +80,15 @@
 
        s.ApplyAuthToRequest(req)
        applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName)
-       res, err := s.client.Do(req.WithContext(ctx))
+       res, err := s.client.Do(req.WithContext(withInitialRequest(ctx)))
        if err != nil {
                return nil, err
        }
 
-       s.ModifyEndpointIfRedirect(res)
+       if err := s.ModifyEndpointIfRedirect(res); err != nil {
+               _ = res.Body.Close()
+               return nil, err
+       }
        defer ioutil.CheckClose(res.Body, &err)
 
        if err = NewErr(res); err != nil {
@@ -96,6 +125,7 @@
        client     *http.Client
        transports *lru.Cache
        mutex      sync.RWMutex
+       follow     RedirectPolicy
 }
 
 // ClientOptions holds user configurable options for the client.
@@ -106,6 +136,11 @@
        // size, will result in the least recently used transport getting 
deleted
        // before the provided transport is added to the cache.
        CacheMaxEntries int
+
+       // RedirectPolicy controls redirect handling. Supported values are
+       // "true", "false", and "initial". The zero value defaults to
+       // "initial", matching Git's http.followRedirects default.
+       RedirectPolicy RedirectPolicy
 }
 
 var (
@@ -150,12 +185,16 @@
        }
        cl := &client{
                client: c,
+               follow: FollowInitialRedirects,
        }
 
        if opts != nil {
                if opts.CacheMaxEntries > 0 {
                        cl.transports = lru.New(opts.CacheMaxEntries)
                }
+               if opts.RedirectPolicy != "" {
+                       cl.follow = opts.RedirectPolicy
+               }
        }
        return cl
 }
@@ -289,14 +328,9 @@
                        }
                }
 
-               httpClient = &http.Client{
-                       Transport:     transport,
-                       CheckRedirect: c.client.CheckRedirect,
-                       Jar:           c.client.Jar,
-                       Timeout:       c.client.Timeout,
-               }
+               httpClient = c.cloneHTTPClient(transport)
        } else {
-               httpClient = c.client
+               httpClient = c.cloneHTTPClient(c.client.Transport)
        }
 
        s := &session{
@@ -324,30 +358,122 @@
        s.auth.SetAuth(req)
 }
 
-func (s *session) ModifyEndpointIfRedirect(res *http.Response) {
+func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
        if res.Request == nil {
-               return
+               return nil
+       }
+       if s.endpoint == nil {
+               return fmt.Errorf("http redirect: nil endpoint")
        }
 
        r := res.Request
        if !strings.HasSuffix(r.URL.Path, infoRefsPath) {
-               return
+               return fmt.Errorf("http redirect: target %q does not end with 
%s", r.URL.Path, infoRefsPath)
+       }
+       if r.URL.Scheme != "http" && r.URL.Scheme != "https" {
+               return fmt.Errorf("http redirect: unsupported scheme %q", 
r.URL.Scheme)
+       }
+       if r.URL.Scheme != s.endpoint.Protocol &&
+               !(s.endpoint.Protocol == "http" && r.URL.Scheme == "https") {
+               return fmt.Errorf("http redirect: changes scheme from %q to 
%q", s.endpoint.Protocol, r.URL.Scheme)
        }
 
-       h, p, err := net.SplitHostPort(r.URL.Host)
+       host := endpointHost(r.URL.Hostname())
+       port, err := endpointPort(r.URL.Port())
        if err != nil {
-               h = r.URL.Host
+               return err
        }
-       if p != "" {
-               port, err := strconv.Atoi(p)
-               if err == nil {
-                       s.endpoint.Port = port
-               }
+
+       if host != s.endpoint.Host || effectivePort(r.URL.Scheme, port) != 
effectivePort(s.endpoint.Protocol, s.endpoint.Port) {
+               s.endpoint.User = ""
+               s.endpoint.Password = ""
+               s.auth = nil
        }
-       s.endpoint.Host = h
+
+       s.endpoint.Host = host
+       s.endpoint.Port = port
 
        s.endpoint.Protocol = r.URL.Scheme
        s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
+       return nil
+}
+
+func endpointHost(host string) string {
+       if strings.Contains(host, ":") {
+               return "[" + host + "]"
+       }
+
+       return host
+}
+
+func endpointPort(port string) (int, error) {
+       if port == "" {
+               return 0, nil
+       }
+
+       parsed, err := strconv.Atoi(port)
+       if err != nil {
+               return 0, fmt.Errorf("http redirect: invalid port %q", port)
+       }
+
+       return parsed, nil
+}
+
+func effectivePort(scheme string, port int) int {
+       if port != 0 {
+               return port
+       }
+
+       switch strings.ToLower(scheme) {
+       case "http":
+               return 80
+       case "https":
+               return 443
+       default:
+               return 0
+       }
+}
+
+func (c *client) cloneHTTPClient(transport http.RoundTripper) *http.Client {
+       return &http.Client{
+               Transport:     transport,
+               CheckRedirect: wrapCheckRedirect(c.follow, 
c.client.CheckRedirect),
+               Jar:           c.client.Jar,
+               Timeout:       c.client.Timeout,
+       }
+}
+
+func wrapCheckRedirect(policy RedirectPolicy, next func(*http.Request, 
[]*http.Request) error) func(*http.Request, []*http.Request) error {
+       return func(req *http.Request, via []*http.Request) error {
+               if err := checkRedirect(req, via, policy); err != nil {
+                       return err
+               }
+               if next != nil {
+                       return next(req, via)
+               }
+               return nil
+       }
+}
+
+func checkRedirect(req *http.Request, via []*http.Request, policy 
RedirectPolicy) error {
+       switch policy {
+       case FollowRedirects:
+       case NoFollowRedirects:
+               return fmt.Errorf("http redirect: redirects disabled to %s", 
req.URL)
+       case "", FollowInitialRedirects:
+               if !isInitialRequest(req) {
+                       return fmt.Errorf("http redirect: redirect on 
non-initial request to %s", req.URL)
+               }
+       default:
+               return fmt.Errorf("http redirect: invalid redirect policy %q", 
policy)
+       }
+       if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
+               return fmt.Errorf("http redirect: unsupported scheme %q", 
req.URL.Scheme)
+       }
+       if len(via) >= 10 {
+               return fmt.Errorf("http redirect: too many redirects")
+       }
+       return nil
 }
 
 func (*session) Close() error {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/github.com/go-git/go-git/v5/repository.go 
new/vendor/github.com/go-git/go-git/v5/repository.go
--- old/vendor/github.com/go-git/go-git/v5/repository.go        2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/repository.go        2026-04-24 
04:28:05.000000000 +0200
@@ -208,6 +208,12 @@
                return nil, ErrRepositoryNotExists
        }
 
+       cfg, err := s.Config()
+       if err != nil {
+               return nil, err
+       }
+
+       err = verifyExtensions(s, cfg)
        if err != nil {
                return nil, err
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/repository_extensions.go 
new/vendor/github.com/go-git/go-git/v5/repository_extensions.go
--- old/vendor/github.com/go-git/go-git/v5/repository_extensions.go     
1970-01-01 01:00:00.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/repository_extensions.go     
2026-04-24 04:28:05.000000000 +0200
@@ -0,0 +1,121 @@
+package git
+
+import (
+       "errors"
+       "fmt"
+       "strings"
+
+       "github.com/go-git/go-git/v5/config"
+       cfgformat "github.com/go-git/go-git/v5/plumbing/format/config"
+       "github.com/go-git/go-git/v5/storage"
+)
+
+var (
+       // ErrUnsupportedExtensionRepositoryFormatVersion represents when an
+       // extension being used is not compatible with the repository's
+       // core.repositoryFormatVersion.
+       ErrUnsupportedExtensionRepositoryFormatVersion = 
errors.New("core.repositoryformatversion does not support extension")
+
+       // ErrUnsupportedRepositoryFormatVersion represents when an repository
+       // is using a format version that is not supported.
+       ErrUnsupportedRepositoryFormatVersion = 
errors.New("core.repositoryformatversion not supported")
+
+       // ErrUnknownExtension represents when a repository has an extension
+       // which is unknown or unsupported by go-git.
+       ErrUnknownExtension = errors.New("unknown extension")
+
+       // builtinExtensions defines the Git extensions that are supported by
+       // the core go-git implementation.
+       //
+       // Some extensions are storage-specific, those are defined by the 
Storers
+       // themselves by implementing the ExtensionChecker interface.
+       builtinExtensions = map[string]struct{}{
+               // noop does not change git’s behavior at all.
+               // It is useful only for testing format-1 compatibility.
+               //
+               // This extension is respected regardless of the
+               // core.repositoryFormatVersion setting.
+               "noop": {},
+
+               // noop-v1 does not change git’s behavior at all.
+               // It is useful only for testing format-1 compatibility.
+               "noop-v1": {},
+       }
+
+       // Some Git extensions were supported upstream before the introduction
+       // of repositoryformatversion. These are the only extensions that can be
+       // enabled while core.repositoryformatversion is unset or set to 0.
+       extensionsValidForV0 = map[string]struct{}{
+               "noop":            {},
+               "partialClone":    {},
+               "preciousObjects": {},
+               "worktreeConfig":  {},
+       }
+)
+
+type extension struct {
+       name  string
+       value string
+}
+
+func extensions(cfg *config.Config) []extension {
+       if cfg == nil || cfg.Raw == nil {
+               return nil
+       }
+
+       if !cfg.Raw.HasSection("extensions") {
+               return nil
+       }
+
+       section := cfg.Raw.Section("extensions")
+       out := make([]extension, 0, len(section.Options))
+       for _, opt := range section.Options {
+               out = append(out, extension{name: strings.ToLower(opt.Key), 
value: strings.ToLower(opt.Value)})
+       }
+
+       return out
+}
+
+func verifyExtensions(st storage.Storer, cfg *config.Config) error {
+       needed := extensions(cfg)
+
+       switch cfg.Core.RepositoryFormatVersion {
+       case "", cfgformat.Version_0, cfgformat.Version_1:
+       default:
+               return fmt.Errorf("%w: %q",
+                       ErrUnsupportedRepositoryFormatVersion,
+                       cfg.Core.RepositoryFormatVersion)
+       }
+
+       if len(needed) > 0 {
+               if cfg.Core.RepositoryFormatVersion == cfgformat.Version_0 ||
+                       cfg.Core.RepositoryFormatVersion == "" {
+                       var unsupported []string
+                       for _, ext := range needed {
+                               if _, ok := extensionsValidForV0[ext.name]; !ok 
{
+                                       unsupported = append(unsupported, 
ext.name)
+                               }
+                       }
+                       if len(unsupported) > 0 {
+                               return fmt.Errorf("%w: %s",
+                                       
ErrUnsupportedExtensionRepositoryFormatVersion,
+                                       strings.Join(unsupported, ", "))
+                       }
+               }
+
+               var missing []string
+               for _, ext := range needed {
+                       if _, ok := builtinExtensions[ext.name]; ok {
+                               continue
+                       }
+
+                       missing = append(missing, ext.name)
+               }
+
+               if len(missing) > 0 {
+                       return fmt.Errorf("%w: %s", ErrUnknownExtension, 
strings.Join(missing, ", "))
+               }
+       }
+
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers.go 
new/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers.go
--- old/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers.go 
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers.go 
2026-04-24 04:28:05.000000000 +0200
@@ -3,6 +3,7 @@
 import (
        "fmt"
        "io"
+       "os"
        "sync/atomic"
 
        "github.com/go-git/go-git/v5/plumbing"
@@ -131,20 +132,62 @@
 
 func (w *PackWriter) save() error {
        base := w.fs.Join(objectsPath, packPath, fmt.Sprintf("pack-%s", 
w.checksum))
-       idx, err := w.fs.Create(fmt.Sprintf("%s.idx", base))
+
+       // Pack files are content addressable. Each file is checked
+       // individually — if it already exists on disk, skip creating it.
+       idxPath := fmt.Sprintf("%s.idx", base)
+       exists, err := fileExists(w.fs, idxPath)
        if err != nil {
                return err
        }
+       if !exists {
+               idx, err := w.fs.Create(idxPath)
+               if err != nil {
+                       return err
+               }
 
-       if err := w.encodeIdx(idx); err != nil {
-               return err
+               if err := w.encodeIdx(idx); err != nil {
+                       _ = idx.Close()
+                       return err
+               }
+
+               if err := idx.Close(); err != nil {
+                       return err
+               }
+               fixPermissions(w.fs, idxPath)
        }
 
-       if err := idx.Close(); err != nil {
+       packPath := fmt.Sprintf("%s.pack", base)
+       exists, err = fileExists(w.fs, packPath)
+       if err != nil {
                return err
        }
+       if !exists {
+               if err := w.fs.Rename(w.fw.Name(), packPath); err != nil {
+                       return err
+               }
+               fixPermissions(w.fs, packPath)
+       } else {
+               // Pack already exists, clean up the temp file.
+               return w.clean()
+       }
 
-       return w.fs.Rename(w.fw.Name(), fmt.Sprintf("%s.pack", base))
+       return nil
+}
+
+// fileExists checks whether path already exists as a regular file.
+// It returns (true, nil) for an existing regular file, (false, nil) when the
+// path does not exist, and (false, err) if the path exists but is not a
+// regular file (e.g. a directory or symlink).
+func fileExists(fs billy.Filesystem, path string) (bool, error) {
+       fi, err := fs.Lstat(path)
+       if err != nil {
+               return false, nil
+       }
+       if !fi.Mode().IsRegular() {
+               return false, fmt.Errorf("unexpected file type for %q: %s", 
path, fi.Mode().Type())
+       }
+       return true, nil
 }
 
 func (w *PackWriter) encodeIdx(writer io.Writer) error {
@@ -226,7 +269,6 @@
                atomic.StoreUint32(&s.blocked, 1)
                <-s.news
        }
-
 }
 
 func (s *syncedReader) Seek(offset int64, whence int) (int64, error) {
@@ -281,5 +323,17 @@
        hex := w.Hash().String()
        file := w.fs.Join(objectsPath, hex[0:2], hex[2:hash.HexSize])
 
-       return w.fs.Rename(w.f.Name(), file)
+       // Loose objects are content addressable, if they already exist
+       // we can safely delete the temporary file and short-circuit the
+       // operation.
+       if _, err := w.fs.Lstat(file); err == nil || os.IsExist(err) {
+               return w.fs.Remove(w.f.Name())
+       }
+
+       if err := w.fs.Rename(w.f.Name(), file); err != nil {
+               return err
+       }
+       fixPermissions(w.fs, file)
+
+       return nil
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_unix.go
 
new/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_unix.go
--- 
old/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_unix.go
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_unix.go
    2026-04-24 04:28:05.000000000 +0200
@@ -0,0 +1,29 @@
+//go:build !windows
+
+package dotgit
+
+import (
+       "github.com/go-git/go-billy/v5"
+       "github.com/go-git/go-git/v5/utils/trace"
+)
+
+func fixPermissions(fs billy.Filesystem, path string) {
+       if chmodFS, ok := fs.(billy.Chmod); ok {
+               if err := chmodFS.Chmod(path, 0o444); err != nil {
+                       trace.General.Printf("failed to chmod %s: %v", path, 
err)
+               }
+       }
+}
+
+func isReadOnly(fs billy.Filesystem, path string) (bool, error) {
+       fi, err := fs.Stat(path)
+       if err != nil {
+               return false, err
+       }
+
+       if fi.Mode().Perm() == 0o444 {
+               return true, nil
+       }
+
+       return false, nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_windows.go
 
new/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_windows.go
--- 
old/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_windows.go
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/vendor/github.com/go-git/go-git/v5/storage/filesystem/dotgit/writers_windows.go
 2026-04-24 04:28:05.000000000 +0200
@@ -0,0 +1,58 @@
+//go:build windows
+
+package dotgit
+
+import (
+       "fmt"
+       "path/filepath"
+
+       "github.com/go-git/go-billy/v5"
+       "github.com/go-git/go-git/v5/utils/trace"
+       "golang.org/x/sys/windows"
+)
+
+func fixPermissions(fs billy.Filesystem, path string) {
+       fullpath := filepath.Join(fs.Root(), path)
+       p, err := windows.UTF16PtrFromString(fullpath)
+       if err != nil {
+               trace.General.Printf("failed to chmod %s: %v", fullpath, err)
+               return
+       }
+
+       attrs, err := windows.GetFileAttributes(p)
+       if err != nil {
+               trace.General.Printf("failed to chmod %s: %v", fullpath, err)
+               return
+       }
+
+       if attrs&windows.FILE_ATTRIBUTE_READONLY != 0 {
+               return
+       }
+
+       err = windows.SetFileAttributes(p,
+               attrs|windows.FILE_ATTRIBUTE_READONLY,
+       )
+
+       if err != nil {
+               trace.General.Printf("failed to chmod %s: %v", fullpath, err)
+       }
+}
+
+func isReadOnly(fs billy.Filesystem, path string) (bool, error) {
+       fullpath := filepath.Join(fs.Root(), path)
+       p, err := windows.UTF16PtrFromString(fullpath)
+       if err != nil {
+               return false, fmt.Errorf("%w: %q", err, fullpath)
+       }
+
+       attrs, err := windows.GetFileAttributes(p)
+       if err != nil {
+               return false, fmt.Errorf("%w: %q", err, fullpath)
+       }
+
+       if attrs&windows.FILE_ATTRIBUTE_READONLY != 0 {
+               return true, nil
+       }
+
+       return false, nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/storage/filesystem/index.go 
new/vendor/github.com/go-git/go-git/v5/storage/filesystem/index.go
--- old/vendor/github.com/go-git/go-git/v5/storage/filesystem/index.go  
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/storage/filesystem/index.go  
2026-04-24 04:28:05.000000000 +0200
@@ -48,6 +48,11 @@
 
        defer ioutil.CheckClose(f, &err)
 
+       fi, statErr := s.dir.Fs().Stat(f.Name())
+       if statErr == nil {
+               idx.ModTime = fi.ModTime()
+       }
+
        d := index.NewDecoder(f)
        err = d.Decode(idx)
        return idx, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/storage/memory/storage.go 
new/vendor/github.com/go-git/go-git/v5/storage/memory/storage.go
--- old/vendor/github.com/go-git/go-git/v5/storage/memory/storage.go    
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/storage/memory/storage.go    
2026-04-24 04:28:05.000000000 +0200
@@ -69,7 +69,11 @@
        index *index.Index
 }
 
+// SetIndex stores the given index.
+// Note: this method sets idx.ModTime to simulate filesystem storage behavior.
 func (c *IndexStorage) SetIndex(idx *index.Index) error {
+       // Set ModTime to enable racy git detection in the metadata 
optimization.
+       idx.ModTime = time.Now()
        c.index = idx
        return nil
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/utils/merkletrie/filesystem/node.go 
new/vendor/github.com/go-git/go-git/v5/utils/merkletrie/filesystem/node.go
--- old/vendor/github.com/go-git/go-git/v5/utils/merkletrie/filesystem/node.go  
2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/utils/merkletrie/filesystem/node.go  
2026-04-24 04:28:05.000000000 +0200
@@ -4,9 +4,11 @@
        "io"
        "os"
        "path"
+       "time"
 
        "github.com/go-git/go-git/v5/plumbing"
        "github.com/go-git/go-git/v5/plumbing/filemode"
+       "github.com/go-git/go-git/v5/plumbing/format/index"
        "github.com/go-git/go-git/v5/utils/merkletrie/noder"
 
        "github.com/go-git/go-billy/v5"
@@ -16,6 +18,14 @@
        ".git": true,
 }
 
+// Options contains configuration for the filesystem node.
+type Options struct {
+       // Index is used to enable the metadata-first comparison optimization 
while
+       // correctly handling the "racy git" condition. If no index is provided,
+       // the function works without the optimization.
+       Index *index.Index
+}
+
 // The node represents a file or a directory in a billy.Filesystem. It
 // implements the interface noder.Noder of merkletrie package.
 //
@@ -24,6 +34,8 @@
 type node struct {
        fs         billy.Filesystem
        submodules map[string]plumbing.Hash
+       idx        *index.Index
+       idxMap     map[string]*index.Entry
 
        path     string
        hash     []byte
@@ -31,6 +43,7 @@
        isDir    bool
        mode     os.FileMode
        size     int64
+       modTime  time.Time
 }
 
 // NewRootNode returns the root node based on a given billy.Filesystem.
@@ -42,7 +55,41 @@
        fs billy.Filesystem,
        submodules map[string]plumbing.Hash,
 ) noder.Noder {
-       return &node{fs: fs, submodules: submodules, isDir: true}
+       return NewRootNodeWithOptions(fs, submodules, Options{})
+}
+
+// NewRootNodeWithOptions returns the root node based on a given 
billy.Filesystem
+// with options to set an index. Providing an index enables the metadata-first
+// comparison optimization while correctly handling the "racy git" condition. 
If
+// no index is provided, the function works without the optimization.
+//
+// The index's ModTime field is used to detect the racy git condition. When a 
file's
+// mtime equals or is newer than the index ModTime, we must hash the file 
content
+// even if other metadata matches, because the file may have been modified in 
the
+// same second that the index was written.
+//
+// Reference: https://git-scm.com/docs/racy-git
+func NewRootNodeWithOptions(
+       fs billy.Filesystem,
+       submodules map[string]plumbing.Hash,
+       options Options,
+) noder.Noder {
+       var idxMap map[string]*index.Entry
+
+       if options.Index != nil {
+               idxMap = make(map[string]*index.Entry, 
len(options.Index.Entries))
+               for _, entry := range options.Index.Entries {
+                       idxMap[entry.Name] = entry
+               }
+       }
+
+       return &node{
+               fs:         fs,
+               submodules: submodules,
+               idx:        options.Index,
+               idxMap:     idxMap,
+               isDir:      true,
+       }
 }
 
 // Hash the hash of a filesystem is the result of concatenating the computed
@@ -133,11 +180,14 @@
        node := &node{
                fs:         n.fs,
                submodules: n.submodules,
+               idx:        n.idx,
+               idxMap:     n.idxMap,
 
-               path:  path,
-               isDir: file.IsDir(),
-               size:  file.Size(),
-               mode:  file.Mode(),
+               path:    path,
+               isDir:   file.IsDir(),
+               size:    file.Size(),
+               mode:    file.Mode(),
+               modTime: file.ModTime(),
        }
 
        if _, isSubmodule := n.submodules[path]; isSubmodule {
@@ -161,6 +211,16 @@
                n.hash = append(submoduleHash[:], filemode.Submodule.Bytes()...)
                return
        }
+
+       if n.idxMap != nil {
+               if entry, ok := n.idxMap[n.path]; ok {
+                       if n.metadataMatches(entry) {
+                               n.hash = append(entry.Hash[:], mode.Bytes()...)
+                               return
+                       }
+               }
+       }
+
        var hash plumbing.Hash
        if n.mode&os.ModeSymlink != 0 {
                hash = n.doCalculateHashForSymlink()
@@ -170,6 +230,44 @@
        n.hash = append(hash[:], mode.Bytes()...)
 }
 
+func (n *node) metadataMatches(entry *index.Entry) bool {
+       if entry == nil {
+               return false
+       }
+
+       if uint32(n.size) != entry.Size {
+               return false
+       }
+
+       if !n.modTime.IsZero() && !n.modTime.Equal(entry.ModifiedAt) {
+               return false
+       }
+
+       mode, err := filemode.NewFromOSFileMode(n.mode)
+       if err != nil {
+               return false
+       }
+
+       if mode != entry.Mode {
+               return false
+       }
+
+       if n.idx != nil && !n.idx.ModTime.IsZero() && !n.modTime.IsZero() {
+               if !n.modTime.Before(n.idx.ModTime) {
+                       return false
+               }
+       }
+
+       // If we couldn't perform the racy git check (idx is nil or idx.ModTime 
is zero),
+       // we cannot safely rely on metadata alone — force content hashing.
+       // This can occur with in-memory storage where the index file timestamp 
is unavailable.
+       if n.idx == nil || n.idx.ModTime.IsZero() {
+               return false
+       }
+
+       return true
+}
+
 func (n *node) doCalculateHashForRegular() plumbing.Hash {
        f, err := n.fs.Open(n.path)
        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/github.com/go-git/go-git/v5/worktree.go 
new/vendor/github.com/go-git/go-git/v5/worktree.go
--- old/vendor/github.com/go-git/go-git/v5/worktree.go  2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/worktree.go  2026-04-24 
04:28:05.000000000 +0200
@@ -385,7 +385,8 @@
                return nil, err
        }
 
-       var removedFiles []string
+       removedFiles := make([]string, 0, len(changes))
+       filesMap := buildFilePathMap(files)
        for _, ch := range changes {
                a, err := ch.Action()
                if err != nil {
@@ -407,7 +408,7 @@
                }
 
                if len(files) > 0 {
-                       contains := inFiles(files, name)
+                       contains := inFiles(filesMap, name)
                        if !contains {
                                continue
                        }
@@ -436,15 +437,11 @@
        return removedFiles, w.r.Storer.SetIndex(idx)
 }
 
-func inFiles(files []string, v string) bool {
+// inFiles checks if the given file is in the list of files. The incoming 
filepaths in files should be cleaned before calling this function.
+func inFiles(files map[string]struct{}, v string) bool {
        v = filepath.Clean(v)
-       for _, s := range files {
-               if filepath.Clean(s) == v {
-                       return true
-               }
-       }
-
-       return false
+       _, exists := files[v]
+       return exists
 }
 
 func (w *Worktree) resetWorktree(t *object.Tree, files []string) error {
@@ -459,6 +456,7 @@
        }
        b := newIndexBuilder(idx)
 
+       filesMap := buildFilePathMap(files)
        for _, ch := range changes {
                if err := w.validChange(ch); err != nil {
                        return err
@@ -476,7 +474,7 @@
                                continue
                        }
 
-                       contains := inFiles(files, file)
+                       contains := inFiles(filesMap, file)
                        if !contains {
                                continue
                        }
@@ -1206,3 +1204,16 @@
 func (b *indexBuilder) Remove(name string) {
        delete(b.entries, filepath.ToSlash(name))
 }
+
+// buildFilePathMap creates a map of cleaned file paths for efficient lookup.
+// Returns nil if the input slice is empty.
+func buildFilePathMap(files []string) map[string]struct{} {
+       if len(files) == 0 {
+               return nil
+       }
+       filesMap := make(map[string]struct{}, len(files))
+       for _, f := range files {
+               filesMap[filepath.Clean(f)] = struct{}{}
+       }
+       return filesMap
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/github.com/go-git/go-git/v5/worktree_status.go 
new/vendor/github.com/go-git/go-git/v5/worktree_status.go
--- old/vendor/github.com/go-git/go-git/v5/worktree_status.go   2026-03-04 
03:08:05.000000000 +0100
+++ new/vendor/github.com/go-git/go-git/v5/worktree_status.go   2026-04-24 
04:28:05.000000000 +0200
@@ -141,7 +141,7 @@
                return nil, err
        }
 
-       to := filesystem.NewRootNode(w.Filesystem, submodules)
+       to := filesystem.NewRootNodeWithOptions(w.Filesystem, submodules, 
filesystem.Options{Index: idx})
 
        var c merkletrie.Changes
        if reverse {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/modules.txt new/vendor/modules.txt
--- old/vendor/modules.txt      2026-03-04 03:08:05.000000000 +0100
+++ new/vendor/modules.txt      2026-04-24 04:28:05.000000000 +0200
@@ -39,7 +39,7 @@
 # github.com/chavacava/garif v0.1.0
 ## explicit; go 1.16
 github.com/chavacava/garif
-# github.com/cloudflare/circl v1.6.1
+# github.com/cloudflare/circl v1.6.3
 ## explicit; go 1.22.0
 github.com/cloudflare/circl/dh/x25519
 github.com/cloudflare/circl/dh/x448
@@ -79,15 +79,15 @@
 github.com/go-git/gcfg/scanner
 github.com/go-git/gcfg/token
 github.com/go-git/gcfg/types
-# github.com/go-git/go-billy/v5 v5.6.2
-## explicit; go 1.21
+# github.com/go-git/go-billy/v5 v5.8.0
+## explicit; go 1.24.0
 github.com/go-git/go-billy/v5
 github.com/go-git/go-billy/v5/helper/chroot
 github.com/go-git/go-billy/v5/helper/polyfill
 github.com/go-git/go-billy/v5/memfs
 github.com/go-git/go-billy/v5/osfs
 github.com/go-git/go-billy/v5/util
-# github.com/go-git/go-git/v5 v5.16.5
+# github.com/go-git/go-git/v5 v5.18.0
 ## explicit; go 1.24.0
 github.com/go-git/go-git/v5
 github.com/go-git/go-git/v5/config

Reply via email to