Repository: go-git/go-git

Issue:
`Packfile.Close()` without `Packfile.init()` will return nil reader to the `sync.Pool`.
### Bug Description

When `Packfile.Close()` gets called while it is not initialized(`p.init()` isn't called), nil reader(`p.rbuf`) is passed to the pool. This results in panic since following calls to `sync.GetBufioReader()` could return the nil reader. This can happen if the user only invokes the methods of `idxfile.Index`, as it is embeded to the packfile.

`p.rbuf` is initialized(`L211`).
https://github.com/go-git/go-git/blob/e633db5b9a341ae93d444c1e00a2bd8c8eb5d91e/plumbing/format/packfile/packfile.go#L199-L213

`p.rbuf` returns to the pool(`L265`).
https://github.com/go-git/go-git/blob/e633db5b9a341ae93d444c1e00a2bd8c8eb5d91e/plumbing/format/packfile/packfile.go#L261-L273

For a fix, we can :
1. Add a nil check to the `Packfile.Close()`. It was tested in https://github.com/onee-only/go-git/pull/3 and all checks pass.
2. Move `p.init()` logic to the `NewPackfile()`. This is a breaking change as the error should also be returned. But this reduces the complexity of `Packfile` as it removes usage of `sync.Once` and `p.init()`.  Also this could be more natural to the users of the API since initialization errors are now exposed by `NewPackfile()`, not on methods of packfile.


### go-git Version

main(e633db5b9a341ae93d444c1e00a2bd8c8eb5d91e)

### Steps to Reproduce

1. Remove `resetGlobalSyncPools()` from `tests/pack/pack_test.go#L89`. Removal needed as we need to maintain the state of the pool. 
2. Run `TestPackfileEmbedFS` or any tests on tests/pack/pack_test.go`.
3. The test panics as `bufioReader.Get()` in `sync.GetBufioReader()` returns nil. 

The `sync.Pool` might drop the value returned to the pool. So run it several times. 

### Additional Information

Below are the debug information used to find out the issue.

I printed each time `Packfile.Close()` and `Packfile.init()` gets called. 

```
Running tool: /opt/homebrew/bin/go test -timeout 30s -run ^TestPackfileEmbedFS$ github.com/go-git/go-git/v6/tests/pack -v

=== RUN   TestPackfileEmbedFS
=== RUN   TestPackfileEmbedFS/TestFindHash
... (subtests)
2025/12/08 13:48:26 close start
2025/12/08 13:48:26 Putting: <nil>
2025/12/08 13:48:26 close end
=== RUN   TestPackfileEmbedFS/TestFindOffset
... (subtests)
2025/12/08 13:48:26 close start
2025/12/08 13:48:26 Putting: <nil>
2025/12/08 13:48:26 close end
=== RUN   TestPackfileEmbedFS/TestGet
=== RUN   TestPackfileEmbedFS/TestGet/valid_commit_hash
2025/12/08 13:48:26 init start
2025/12/08 13:48:26 Getting one
... (test results)
panic: runtime error: invalid memory address or nil pointer dereference [recovered, repanicked]
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102e36524]

goroutine 56 [running]:
... (stack trace)
FAIL	github.com/go-git/go-git/v6/tests/pack	0.488s
```
As we can see, on `TestFindHash` and `TestFindOffset`, nil reader is returned to the pool before the first call to the `sync.GetBufioReader()`. They don't call `p.init()` since they belong to `idxfile.Index`. This leads to `p.rbuf` being nil.

VSCode debugger:

<img width="421" height="201" alt="Image" src="https://github.com/user-attachments/assets/256315c0-1f01-470b-8f6c-29cfc91d1b43" />

<img width="280" height="91" alt="Image" src="https://github.com/user-attachments/assets/39b97b23-c20c-47f1-a922-19fd1ee99736" />

We can see that if `x`(value popped from the pool) isn't nil or `p.New` is nil, it returns the `x` right away without creating new.

<img width="427" height="148" alt="Image" src="https://github.com/user-attachments/assets/50b9ce50-e45d-48c3-b9b7-3812192f4da7" />

Now, `x` is an interface wrapping nil value inside, which means `x != nil`. And `p.New` is `sync.init.func1`, this means it is anonymous function inside `init()` in `sync` package (`utils/sync/bufio.go#L11-L13`), which of course means `p.New != nil`. 

This proves us that the panic was caused by `bufioReader.Get()` returning nil reader from the pool.

Discussion:
@onee-only: Another approach we can take is adding nil checks to `PutXXX` functions on `sync` package. This way we can prevent such issues like this across all other pools in `sync` package. After discussing it with @pjbgf, we decided to go with this approach.

Solution patch:
diff --git a/plumbing/format/pktline/sync.go b/plumbing/format/pktline/sync.go
index ebb29b3f..17e26d73 100644
--- a/plumbing/format/pktline/sync.go
+++ b/plumbing/format/pktline/sync.go
@@ -21,5 +21,8 @@ func GetBuffer() *[MaxSize]byte {
 
 // PutBuffer puts buf back into its sync.Pool.
 func PutBuffer(buf *[MaxSize]byte) {
+	if buf == nil {
+		return
+	}
 	pktBuffer.Put(buf)
 }
diff --git a/utils/sync/bufio.go b/utils/sync/bufio.go
index cc78e9c4..9f4aa291 100644
--- a/utils/sync/bufio.go
+++ b/utils/sync/bufio.go
@@ -26,5 +26,8 @@ func GetBufioReader(reader io.Reader) *bufio.Reader {
 
 // PutBufioReader puts reader back into its sync.Pool.
 func PutBufioReader(reader *bufio.Reader) {
+	if reader == nil {
+		return
+	}
 	bufioReader.Put(reader)
 }
diff --git a/utils/sync/zlib.go b/utils/sync/zlib.go
index 32fa4e48..89353924 100644
--- a/utils/sync/zlib.go
+++ b/utils/sync/zlib.go
@@ -64,6 +64,9 @@ func GetZlibReader(r io.Reader) (*ZLibReader, error) {
 // PutZlibReader puts z back into its sync.Pool.
 // The Byte slice dictionary is also put back into its sync.Pool.
 func PutZlibReader(z *ZLibReader) {
+	if z == nil {
+		return
+	}
 	PutByteSlice(z.dict)
 	zlibReader.Put(z)
 }
@@ -81,5 +84,8 @@ func GetZlibWriter(w io.Writer) *zlib.Writer {
 
 // PutZlibWriter puts w back into its sync.Pool.
 func PutZlibWriter(w *zlib.Writer) {
+	if w == nil {
+		return
+	}
 	zlibWriter.Put(w)
 }



Test patch:
diff --git a/tests/pack/common_test.go b/tests/pack/common_test.go
index 5869c0d4..cf0dd5a0 100644
--- a/tests/pack/common_test.go
+++ b/tests/pack/common_test.go
@@ -1,10 +1,8 @@
 package pack_test
 
 import (
-	"bufio"
 	"crypto"
 	"io"
-	"sync"
 
 	"github.com/go-git/go-billy/v6"
 
@@ -52,15 +50,3 @@ func newPackfileOpts(pack, idx billy.File, opts ...packfile.PackfileOption) pack
 	opts = append(opts, packfile.WithIdx(i))
 	return packfile.NewPackfile(pack, opts...)
 }
-
-//go:linkname bufioPool github.com/go-git/go-git/v6/utils/sync.bufioReader
-var bufioPool sync.Pool
-
-// resetGlobalSyncPools resets the global sync pools. This is needed as
-// the Suite execution ends up copying global vars by value, which in this
-// case specifically results in the `New` becoming nil.
-func resetGlobalSyncPools() {
-	bufioPool = sync.Pool{New: func() any {
-		return bufio.NewReader(nil)
-	}}
-}
diff --git a/tests/pack/pack_test.go b/tests/pack/pack_test.go
index 2dab6398..7540f0af 100644
--- a/tests/pack/pack_test.go
+++ b/tests/pack/pack_test.go
@@ -85,10 +85,6 @@ func TestPackfileWithCache(t *testing.T) {
 	})
 }
 
-func (p *PackHandlerSuite[T]) SetupTest() {
-	resetGlobalSyncPools()
-}
-
 func (p *PackHandlerSuite[T]) TestFindHash() {
 	handler := p.newPackHandler()
 	defer handler.Close()