Repository: go-git/go-git
Pull request #1362 (2025-01-06 00:10:39)

[v6] Make hash usage extensible to enable concurrent support of multiple hash types
Refactor the codebase so that it stops relying on `plumbing.Hash`'s implementation details, and instead rely on exported methods.
This is a stepping stone to enable support of multiple hash types, which will be dealt with as a follow-up PR.

The `hash` package now contains all code regarding hash, with exception to `Hasher` related logic, which is spread between `plumbing` and `hasher`. `plumbing.Hash` now redirects to `hash.SHA1Hash`, which may (or may not) be dropped at a later stage.

The previous sha256 specific tests that relied on build tags were removed as they are now redundant.

Relates to #706 #899.
Supersedes to #920.


Patch:
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
index 3e23c00a..2d7fe625 100644
--- a/.github/workflows/cifuzz.yml
+++ b/.github/workflows/cifuzz.yml
@@ -2,7 +2,7 @@ name: CIFuzz
 on: 
   pull_request:
     branches:
-      - master
+      - main
 
 permissions: {}
 
@@ -33,7 +33,7 @@ jobs:
        path: ./out/artifacts
    - name: Upload Sarif
      if: always() && steps.build.outcome == 'success'
-     uses: github/codeql-action/upload-sarif@v3
+     uses: github/codeql-action/upload-sarif@v3.28.5
      with:
       # Path to SARIF file relative to the root of the repository
       sarif_file: cifuzz-sarif/results.sarif
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 71919871..55c464db 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,9 +1,9 @@
-name: "CodeQL"
+name: CodeQL
 on:
   push:
     branches:
-    - "master"
-    - "v6-exp"
+    - releases/v5.x
+    - main
   pull_request:
 
   schedule:
@@ -32,7 +32,7 @@ jobs:
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@03e7845b7bfcd5e7fb63d1ae8c61b0e791134fab # v2.22.11
+        uses: github/codeql-action/init@e7c0c9d71b7bd108fd12e06b56fc58d3d154164d # v2.22.11
         with:
           languages: ${{ matrix.language }}
           # xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
@@ -43,6 +43,6 @@ jobs:
         run: go build ./...
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@03e7845b7bfcd5e7fb63d1ae8c61b0e791134fab # v2.22.11
+        uses: github/codeql-action/analyze@e7c0c9d71b7bd108fd12e06b56fc58d3d154164d # v2.22.11
         with:
           category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml
index 4016408a..2b6109c2 100644
--- a/.github/workflows/git.yml
+++ b/.github/workflows/git.yml
@@ -2,8 +2,8 @@ name: Git Compatibility
 on:
   push:
     branches:
-    - "master"
-    - "v6-exp"
+    - releases/v5.x
+    - main
   pull_request:
 
 permissions: {}
@@ -50,9 +50,6 @@ jobs:
     - name: Test
       run: make test-coverage
 
-    - name: Test SHA256
-      run: make test-sha256
-
     - name: Build go-git with CGO disabled
       run: go build ./...
       env:
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
index c68ee707..07b60b84 100644
--- a/.github/workflows/pr-validation.yml
+++ b/.github/workflows/pr-validation.yml
@@ -1,4 +1,4 @@
-name: 'PR Validation'
+name: PR Validation
 on:
   pull_request:
     types:
diff --git a/.github/workflows/stale-issues-bot.yaml b/.github/workflows/stale-issues-bot.yaml
index dace5d27..c5c4fc5c 100644
--- a/.github/workflows/stale-issues-bot.yaml
+++ b/.github/workflows/stale-issues-bot.yaml
@@ -1,4 +1,4 @@
-name: "stale issues bot"
+name: stale issues bot
 on:
   schedule:
   - cron: "0 7 * * *"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a5b01823..83dfc2ce 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,21 +1,22 @@
 # Contributing Guidelines
 
-source{d} go-git project is [Apache 2.0 licensed](LICENSE) and accepts
-contributions via GitHub pull requests.  This document outlines some of the
+The go-git project is [Apache 2.0 licensed](LICENSE) and accepts
+contributions via GitHub pull requests. This document outlines some of the
 conventions on development workflow, commit message formatting, contact points,
 and other resources to make it easier to get your contribution accepted.
 
 ## Support Channels
 
-The official support channels, for both users and contributors, are:
+The official support channels, for users are:
 
-- [StackOverflow go-git tag](https://stackoverflow.com/questions/tagged/go-git) for user questions.
-- GitHub [Issues](https://github.com/src-d/go-git/issues)* for bug reports and feature requests.
+- [StackOverflow go-git tag] for user questions.
+- GitHub [Issues]* for bug reports and feature requests.
 
 *Before opening a new issue or submitting a new pull request, it's helpful to
 search the project - it's likely that another user has already reported the
 issue you're facing, or it's a known issue that we're already aware of.
 
+In addition to the channels above, contributors are also able to join the go-git [discord server].
 
 ## How to Contribute
 
@@ -23,9 +24,9 @@ Pull Requests (PRs) are the main and exclusive way to contribute to the official
 In order for a PR to be accepted it needs to pass a list of requirements:
 
 - You should be able to run the same query using `git`. We don't accept features that are not implemented in the official git implementation.
-- The expected behavior must match the [official git implementation](https://github.com/git/git).
+- The expected behavior must match the [official git implementation].
 - The actual behavior must be correctly explained with natural language and providing a minimum working example in Go that reproduces it.
-- All PRs must be written in idiomatic Go, formatted according to [gofmt](https://golang.org/cmd/gofmt/), and without any warnings from [go lint](https://github.com/golang/lint) nor [go vet](https://golang.org/cmd/vet/).
+- All PRs must be written in idiomatic Go, formatted according to [gofmt], and without any warnings from [go vet].
 - They should in general include tests, and those shall pass.
 - If the PR is a bug fix, it has to include a suite of unit tests for the new functionality.
 - If the PR is a new feature, it has to come with a suite of unit tests, that tests the new functionality.
@@ -33,10 +34,16 @@ In order for a PR to be accepted it needs to pass a list of requirements:
 
 ### Branches
 
-The `master` branch is currently used for maintaining the `v5` major release only. The accepted changes would
-be dependency bumps, bug fixes and small changes that aren't needed for `v6`. New development should target the
-`v6-exp` branch, and if agreed with at least one go-git maintainer, it can be back ported to `v5` by creating 
-a new PR that targets `master`.
+The development branch is `main`, where all development takes place.
+All new features and bug fixes should target it. This was formely known as `v6-exp`,
+as it contains all the changes for `v6` - the next major release.
+
+The `releases/v5.x` branch is the branch for changes to the `v5` version,
+which is now in maintaince mode. To avoid having to divert efforts from `v6`,
+we will only be accepting bug fixes or CVE related dependency bumps for the
+`v5` release.
+
+Bug fixes that also impact `main`, should be fixed there first, and then backported to `v5`.
 
 ### Format of the commit message
 
@@ -51,3 +58,10 @@ The format can be described more formally as follows:
 ```
 <package>: <subpackage>, <what changed>. [Fixes #<issue-number>]
 ```
+
+[discord server]: https://discord.gg/8hrxYEVPE5
+[StackOverflow go-git tag]: https://stackoverflow.com/questions/tagged/go-git
+[Issues]: https://github.com/go-git/go-git/issues
+[official git implementation]: https://github.com/git/git
+[gofmt]: https://golang.org/cmd/gofmt/
+[go vet]: https://golang.org/cmd/vet/
diff --git a/Makefile b/Makefile
index 3d5b54f7..5cd13e7d 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,10 @@ GIT_REPOSITORY = http://github.com/git/git.git
 COVERAGE_REPORT = coverage.out
 COVERAGE_MODE = count
 
+# Defines the maximum time each fuzz target will be executed for.
+FUZZ_TIME ?= 10s
+FUZZ_PKGS = $(shell grep -r --include='**_test.go' --files-with-matches 'func Fuzz' . | xargs -I{} dirname {})
+
 build-git:
 	@if [ -f $(GIT_DIST_PATH)/git ]; then \
 		echo "nothing to do, using cache $(GIT_DIST_PATH)"; \
@@ -30,12 +34,6 @@ test:
 	$(GOTEST) -race ./...
 	$(GOTEST) -v _examples/common_test.go _examples/common.go --examples
 
-TEMP_REPO := $(shell mktemp)
-test-sha256:
-	$(GOCMD) run -tags sha256 _examples/sha256/main.go $(TEMP_REPO)
-	cd $(TEMP_REPO) && git fsck
-	rm -rf $(TEMP_REPO)
-
 test-coverage:
 	@echo "running against `git version`"; \
 	echo "" > $(COVERAGE_REPORT); \
@@ -45,10 +43,6 @@ clean:
 	rm -rf $(GIT_DIST_PATH)
 
 fuzz:
-	@go test -fuzz=FuzzParser				$(PWD)/internal/revision
-	@go test -fuzz=FuzzDecoder				$(PWD)/plumbing/format/config
-	@go test -fuzz=FuzzPatchDelta			$(PWD)/plumbing/format/packfile
-	@go test -fuzz=FuzzParseSignedBytes		$(PWD)/plumbing/object
-	@go test -fuzz=FuzzDecode				$(PWD)/plumbing/object
-	@go test -fuzz=FuzzDecoder				$(PWD)/plumbing/protocol/packp
-	@go test -fuzz=FuzzNewEndpoint			$(PWD)/plumbing/transport
+	@for path in $(FUZZ_PKGS); do \
+		go test -fuzz=Fuzz -fuzztime=$(FUZZ_TIME) $$path; \
+	done
diff --git a/_examples/sha256/main.go b/_examples/sha256/main.go
deleted file mode 100644
index 03927724..00000000
--- a/_examples/sha256/main.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"time"
-
-	"github.com/go-git/go-git/v5"
-	. "github.com/go-git/go-git/v5/_examples"
-	"github.com/go-git/go-git/v5/plumbing/format/config"
-	"github.com/go-git/go-git/v5/plumbing/object"
-)
-
-// This example requires building with the sha256 tag for it to work:
-// go run -tags sha256 main.go /tmp/repository
-
-// Basic example of how to initialise a repository using sha256 as the hashing algorithm.
-func main() {
-	CheckArgs("<directory>")
-	directory := os.Args[1]
-
-	os.RemoveAll(directory)
-
-	// Init a new repository using the ObjectFormat SHA256.
-	r, err := git.PlainInitWithOptions(directory, &git.PlainInitOptions{ObjectFormat: config.SHA256})
-	CheckIfError(err)
-
-	w, err := r.Worktree()
-	CheckIfError(err)
-
-	// ... we need a file to commit so let's create a new file inside of the
-	// worktree of the project using the go standard library.
-	Info("echo \"hello world!\" > example-git-file")
-	filename := filepath.Join(directory, "example-git-file")
-	err = os.WriteFile(filename, []byte("hello world!"), 0644)
-	CheckIfError(err)
-
-	// Adds the new file to the staging area.
-	Info("git add example-git-file")
-	_, err = w.Add("example-git-file")
-	CheckIfError(err)
-
-	// Commits the current staging area to the repository, with the new file
-	// just created. We should provide the object.Signature of Author of the
-	// commit Since version 5.0.1, we can omit the Author signature, being read
-	// from the git config files.
-	Info("git commit -m \"example go-git commit\"")
-	commit, err := w.Commit("example go-git commit", &git.CommitOptions{
-		Author: &object.Signature{
-			Name:  "John Doe",
-			Email: "john@doe.org",
-			When:  time.Now(),
-		},
-	})
-
-	CheckIfError(err)
-
-	// Prints the current HEAD to verify that all worked well.
-	Info("git show -s")
-	obj, err := r.CommitObject(commit)
-	CheckIfError(err)
-
-	fmt.Println(obj)
-}
diff --git a/config/refspec.go b/config/refspec.go
index e2cf8c97..f6538bfc 100644
--- a/config/refspec.go
+++ b/config/refspec.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/hash"
 )
 
 const (
@@ -61,7 +62,7 @@ func (s RefSpec) IsDelete() bool {
 
 // IsExactSHA1 returns true if the source is a SHA1 hash.
 func (s RefSpec) IsExactSHA1() bool {
-	return plumbing.IsHash(s.Src())
+	return hash.ValidHex(s.Src())
 }
 
 // Src returns the src side.
diff --git a/go.mod b/go.mod
index 0f5a692e..b2d81af8 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ replace (
 require (
 	dario.cat/mergo v1.0.1
 	github.com/Microsoft/go-winio v0.6.2
-	github.com/ProtonMail/go-crypto v1.1.3
+	github.com/ProtonMail/go-crypto v1.1.5
 	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
 	github.com/elazarl/goproxy v0.0.0-20241214220532-033b654b53fa
 	github.com/emirpasic/gods v1.18.1
@@ -26,15 +26,14 @@ require (
 	github.com/go-git/go-git-fixtures/v5 v5.0.0-20241203230421-0753e18f8f03
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8
 	github.com/kevinburke/ssh_config v1.2.0
-	github.com/pjbgf/sha1cd v0.3.0
+	github.com/pjbgf/sha1cd v0.3.2
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 	github.com/stretchr/testify v1.10.0
-	golang.org/x/crypto v0.31.0
+	golang.org/x/crypto v0.32.0
 	golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e
-	golang.org/x/net v0.32.0
-	golang.org/x/sys v0.28.0
+	golang.org/x/net v0.34.0
+	golang.org/x/sys v0.29.0
 	golang.org/x/text v0.21.0
-	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 )
 
 require (
@@ -46,5 +45,6 @@ require (
 	github.com/kr/text v0.2.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.13.1 // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index bf7693ef..498dc9ad 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
 dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
-github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
+github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -44,8 +44,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
-github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
+github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
+github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -58,16 +58,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4=
 golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
-golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
-golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/options.go b/options.go
index aba409a6..71e4e084 100644
--- a/options.go
+++ b/options.go
@@ -12,6 +12,7 @@ import (
 	"github.com/go-git/go-git/v5/plumbing"
 	formatcfg "github.com/go-git/go-git/v5/plumbing/format/config"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/plumbing/protocol/packp"
 	"github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 )
@@ -85,6 +86,9 @@ type CloneOptions struct {
 	//
 	// [Reference]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---shared
 	Shared bool
+	// Filter requests that the server to send only a subset of the objects.
+	// See https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--filterltfilter-specgtcode
+	Filter packp.Filter
 }
 
 // MergeOptions describes how a merge should be performed.
@@ -220,6 +224,9 @@ type FetchOptions struct {
 	// Prune specify that local refs that match given RefSpecs and that do
 	// not exist remotely will be removed.
 	Prune bool
+	// Filter requests that the server to send only a subset of the objects.
+	// See https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--filterltfilter-specgtcode
+	Filter packp.Filter
 }
 
 // Validate validates the fields and sets the default values.
diff --git a/oss-fuzz.sh b/oss-fuzz.sh
deleted file mode 100644
index 8d8ad2e4..00000000
--- a/oss-fuzz.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash -eu
-# Copyright 2023 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-################################################################################
-
-
-go mod download
-go get github.com/AdamKorcz/go-118-fuzz-build/testing
-
-if [ "$SANITIZER" != "coverage" ]; then
-    sed -i '/func (s \*DecoderSuite) TestDecode(/,/^}/ s/^/\/\//' plumbing/format/config/decoder_test.go
-    sed -n '29,$p' plumbing/format/packfile/common_test.go >> plumbing/format/packfile/delta_test.go
-    sed -n '20,53p' plumbing/object/object_test.go >> plumbing/object/tree_test.go
-fi
-
-compile_native_go_fuzzer $(pwd)/internal/revision                       FuzzParser              fuzz_parser
-compile_native_go_fuzzer $(pwd)/plumbing/format/config                  FuzzDecoder             fuzz_decoder_config
-compile_native_go_fuzzer $(pwd)/plumbing/format/packfile                FuzzPatchDelta          fuzz_patch_delta
-compile_native_go_fuzzer $(pwd)/plumbing/object                         FuzzParseSignedBytes    fuzz_parse_signed_bytes
-compile_native_go_fuzzer $(pwd)/plumbing/object                         FuzzDecode              fuzz_decode
-compile_native_go_fuzzer $(pwd)/plumbing/protocol/packp                 FuzzDecoder             fuzz_decoder_packp
-compile_native_go_fuzzer $(pwd)/plumbing/transport                      FuzzNewEndpoint         fuzz_new_endpoint
diff --git a/plumbing/format/commitgraph/encoder.go b/plumbing/format/commitgraph/encoder.go
index 31763538..59a8b864 100644
--- a/plumbing/format/commitgraph/encoder.go
+++ b/plumbing/format/commitgraph/encoder.go
@@ -76,7 +76,7 @@ func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[pl
 	fanout = make([]uint32, 256)
 	for i, hash := range hashes {
 		hashToIndex[hash] = uint32(i)
-		fanout[hash[0]]++
+		fanout[hash.Bytes()[0]]++
 	}
 
 	// Convert the fanout to cumulative values
@@ -136,7 +136,7 @@ func (e *Encoder) encodeFanout(fanout []uint32) (err error) {
 
 func (e *Encoder) encodeOidLookup(hashes []plumbing.Hash) (err error) {
 	for _, hash := range hashes {
-		if _, err = e.Write(hash[:]); err != nil {
+		if _, err = e.Write(hash.Bytes()); err != nil {
 			return err
 		}
 	}
@@ -147,7 +147,7 @@ func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumb
 	for _, hash := range hashes {
 		origIndex, _ := idx.GetIndexByHash(hash)
 		commitData, _ := idx.GetCommitDataByIndex(origIndex)
-		if _, err = e.Write(commitData.TreeHash[:]); err != nil {
+		if _, err = e.Write(commitData.TreeHash.Bytes()); err != nil {
 			return
 		}
 
diff --git a/plumbing/format/commitgraph/file.go b/plumbing/format/commitgraph/file.go
index ef8fb349..9b329f19 100644
--- a/plumbing/format/commitgraph/file.go
+++ b/plumbing/format/commitgraph/file.go
@@ -159,19 +159,20 @@ func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (int, error) {
 
 	// Find the hash in the oid lookup table
 	var low int
-	if h[0] == 0 {
+	first := h.Bytes()[0]
+	if first == 0 {
 		low = 0
 	} else {
-		low = fi.fanout[h[0]-1]
+		low = fi.fanout[first-1]
 	}
-	high := fi.fanout[h[0]]
+	high := fi.fanout[first]
 	for low < high {
 		mid := (low + high) >> 1
 		offset := fi.oidLookupOffset + int64(mid)*hash.Size
-		if _, err := fi.reader.ReadAt(oid[:], offset); err != nil {
+		if _, err := oid.FromReaderAt(fi.reader, offset); err != nil {
 			return 0, err
 		}
-		cmp := bytes.Compare(h[:], oid[:])
+		cmp := h.Compare(oid.Bytes())
 		if cmp < 0 {
 			high = mid
 		} else if cmp == 0 {
@@ -257,7 +258,7 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []int) ([]plumbing.Hash, error
 		}
 
 		offset := fi.oidLookupOffset + int64(idx)*hash.Size
-		if _, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil {
+		if _, err := hashes[i].FromReaderAt(fi.reader, offset); err != nil {
 			return nil, err
 		}
 	}
@@ -270,7 +271,7 @@ func (fi *fileIndex) Hashes() []plumbing.Hash {
 	hashes := make([]plumbing.Hash, fi.fanout[0xff])
 	for i := 0; i < fi.fanout[0xff]; i++ {
 		offset := fi.oidLookupOffset + int64(i)*hash.Size
-		if n, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil || n < hash.Size {
+		if n, err := hashes[i].FromReaderAt(fi.reader, offset); err != nil || n < hash.Size {
 			return nil
 		}
 	}
diff --git a/plumbing/format/commitgraph/v2/chain.go b/plumbing/format/commitgraph/v2/chain.go
index 8da60d01..11232f66 100644
--- a/plumbing/format/commitgraph/v2/chain.go
+++ b/plumbing/format/commitgraph/v2/chain.go
@@ -6,7 +6,7 @@ import (
 	"path"
 
 	"github.com/go-git/go-billy/v5"
-	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/hash"
 )
 
 // OpenChainFile reads a commit chain file and returns a slice of the hashes within it
@@ -31,7 +31,7 @@ func OpenChainFile(r io.Reader) ([]string, error) {
 		}
 
 		hashStr := string(line[:len(line)-1])
-		if !plumbing.IsHash(hashStr) {
+		if !hash.ValidHex(hashStr) || len(hashStr) != hash.SHA1HexSize {
 			return nil, ErrMalformedCommitGraphFile
 		}
 		chain = append(chain, hashStr)
diff --git a/plumbing/format/commitgraph/v2/encoder.go b/plumbing/format/commitgraph/v2/encoder.go
index b79bc77f..78a78a70 100644
--- a/plumbing/format/commitgraph/v2/encoder.go
+++ b/plumbing/format/commitgraph/v2/encoder.go
@@ -86,7 +86,7 @@ func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[pl
 	fanout = make([]uint32, lenFanout)
 	for i, hash := range hashes {
 		hashToIndex[hash] = uint32(i)
-		fanout[hash[0]]++
+		fanout[hash.Bytes()[0]]++
 	}
 
 	// Convert the fanout to cumulative values
@@ -150,7 +150,7 @@ func (e *Encoder) encodeFanout(fanout []uint32) (err error) {
 
 func (e *Encoder) encodeOidLookup(hashes []plumbing.Hash) (err error) {
 	for _, hash := range hashes {
-		if _, err = e.Write(hash[:]); err != nil {
+		if _, err = e.Write(hash.Bytes()); err != nil {
 			return err
 		}
 	}
@@ -164,7 +164,7 @@ func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumb
 	for _, hash := range hashes {
 		origIndex, _ := idx.GetIndexByHash(hash)
 		commitData, _ := idx.GetCommitDataByIndex(origIndex)
-		if _, err = e.Write(commitData.TreeHash[:]); err != nil {
+		if _, err = e.Write(commitData.TreeHash.Bytes()); err != nil {
 			return
 		}
 
diff --git a/plumbing/format/commitgraph/v2/file.go b/plumbing/format/commitgraph/v2/file.go
index c5f61e4d..92781260 100644
--- a/plumbing/format/commitgraph/v2/file.go
+++ b/plumbing/format/commitgraph/v2/file.go
@@ -190,19 +190,20 @@ func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (uint32, error) {
 
 	// Find the hash in the oid lookup table
 	var low uint32
-	if h[0] == 0 {
+	first := h.Bytes()[0]
+	if first == 0 {
 		low = 0
 	} else {
-		low = fi.fanout[h[0]-1]
+		low = fi.fanout[first-1]
 	}
-	high := fi.fanout[h[0]]
+	high := fi.fanout[first]
 	for low < high {
 		mid := (low + high) >> 1
 		offset := fi.offsets[OIDLookupChunk] + int64(mid)*hash.Size
-		if _, err := fi.reader.ReadAt(oid[:], offset); err != nil {
+		if _, err := oid.FromReaderAt(fi.reader, offset); err != nil {
 			return 0, err
 		}
-		cmp := bytes.Compare(h[:], oid[:])
+		cmp := h.Compare(oid.Bytes())
 		if cmp < 0 {
 			high = mid
 		} else if cmp == 0 {
@@ -345,7 +346,7 @@ func (fi *fileIndex) GetHashByIndex(idx uint32) (found plumbing.Hash, err error)
 	}
 
 	offset := fi.offsets[OIDLookupChunk] + int64(idx)*hash.Size
-	if _, err := fi.reader.ReadAt(found[:], offset); err != nil {
+	if _, err := found.FromReaderAt(fi.reader, offset); err != nil {
 		return found, err
 	}
 
@@ -375,7 +376,7 @@ func (fi *fileIndex) getHashesFromIndexes(indexes []uint32) ([]plumbing.Hash, er
 		}
 
 		offset := fi.offsets[OIDLookupChunk] + int64(idx)*hash.Size
-		if _, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil {
+		if _, err := hashes[i].FromReaderAt(fi.reader, offset); err != nil {
 			return nil, err
 		}
 	}
@@ -396,7 +397,7 @@ func (fi *fileIndex) Hashes() []plumbing.Hash {
 
 	for i := uint32(0); i < fi.fanout[0xff]; i++ {
 		offset := fi.offsets[OIDLookupChunk] + int64(i)*hash.Size
-		if n, err := fi.reader.ReadAt(hashes[i+fi.minimumNumberOfHashes][:], offset); err != nil || n < hash.Size {
+		if n, err := hashes[i+fi.minimumNumberOfHashes].FromReaderAt(fi.reader, offset); err != nil || n < hash.Size {
 			return nil
 		}
 	}
diff --git a/plumbing/format/config/format.go b/plumbing/format/config/format.go
index 4873ea92..ace22d3a 100644
--- a/plumbing/format/config/format.go
+++ b/plumbing/format/config/format.go
@@ -1,5 +1,7 @@
 package config
 
+import "errors"
+
 // RepositoryFormatVersion represents the repository format version,
 // as per defined at:
 //
@@ -51,3 +53,6 @@ const (
 	// DefaultObjectFormat holds the default object format.
 	DefaultObjectFormat = SHA1
 )
+
+// ErrInvalidObjectFormat is returned when an invalid ObjectFormat is used.
+var ErrInvalidObjectFormat = errors.New("invalid object format")
diff --git a/plumbing/format/idxfile/decoder.go b/plumbing/format/idxfile/decoder.go
index 9afdce30..5759b22d 100644
--- a/plumbing/format/idxfile/decoder.go
+++ b/plumbing/format/idxfile/decoder.go
@@ -166,11 +166,11 @@ func readOffsets(idx *MemoryIndex, r io.Reader) error {
 }
 
 func readChecksums(idx *MemoryIndex, r io.Reader) error {
-	if _, err := io.ReadFull(r, idx.PackfileChecksum[:]); err != nil {
+	if _, err := idx.PackfileChecksum.FromReader(r); err != nil {
 		return err
 	}
 
-	if _, err := io.ReadFull(r, idx.IdxChecksum[:]); err != nil {
+	if _, err := idx.IdxChecksum.FromReader(r); err != nil {
 		return err
 	}
 
diff --git a/plumbing/format/idxfile/encoder.go b/plumbing/format/idxfile/encoder.go
index 75147376..3bfb349c 100644
--- a/plumbing/format/idxfile/encoder.go
+++ b/plumbing/format/idxfile/encoder.go
@@ -128,12 +128,12 @@ func (e *Encoder) encodeOffsets(idx *MemoryIndex) (int, error) {
 }
 
 func (e *Encoder) encodeChecksums(idx *MemoryIndex) (int, error) {
-	if _, err := e.Write(idx.PackfileChecksum[:]); err != nil {
+	if _, err := e.Write(idx.PackfileChecksum.Bytes()); err != nil {
 		return 0, err
 	}
 
-	copy(idx.IdxChecksum[:], e.hash.Sum(nil)[:hash.Size])
-	if _, err := e.Write(idx.IdxChecksum[:]); err != nil {
+	idx.IdxChecksum.Write(e.hash.Sum(nil)[:hash.Size])
+	if _, err := e.Write(idx.IdxChecksum.Bytes()); err != nil {
 		return 0, err
 	}
 
diff --git a/plumbing/format/idxfile/idxfile.go b/plumbing/format/idxfile/idxfile.go
index cba0225a..7c40f3e0 100644
--- a/plumbing/format/idxfile/idxfile.go
+++ b/plumbing/format/idxfile/idxfile.go
@@ -1,7 +1,7 @@
 package idxfile
 
 import (
-	"bytes"
+	"fmt"
 	"io"
 	"sort"
 	"sync"
@@ -9,7 +9,6 @@ import (
 	encbin "encoding/binary"
 
 	"github.com/go-git/go-git/v5/plumbing"
-	"github.com/go-git/go-git/v5/plumbing/hash"
 )
 
 const (
@@ -55,8 +54,8 @@ type MemoryIndex struct {
 	Offset32         [][]byte
 	CRC32            [][]byte
 	Offset64         []byte
-	PackfileChecksum [hash.Size]byte
-	IdxChecksum      [hash.Size]byte
+	PackfileChecksum plumbing.Hash
+	IdxChecksum      plumbing.Hash
 
 	offsetHash       map[int64]plumbing.Hash
 	offsetHashIsFull bool
@@ -71,7 +70,7 @@ func NewMemoryIndex() *MemoryIndex {
 }
 
 func (idx *MemoryIndex) findHashIndex(h plumbing.Hash) (int, bool) {
-	k := idx.FanoutMapping[h[0]]
+	k := idx.FanoutMapping[h.Bytes()[0]]
 	if k == noMapping {
 		return 0, false
 	}
@@ -91,7 +90,7 @@ func (idx *MemoryIndex) findHashIndex(h plumbing.Hash) (int, bool) {
 		mid := (low + high) >> 1
 		offset := mid * objectIDLength
 
-		cmp := bytes.Compare(h[:], data[offset:offset+objectIDLength])
+		cmp := h.Compare(data[offset : offset+objectIDLength])
 		if cmp < 0 {
 			high = mid
 		} else if cmp == 0 {
@@ -116,11 +115,12 @@ func (idx *MemoryIndex) Contains(h plumbing.Hash) (bool, error) {
 
 // FindOffset implements the Index interface.
 func (idx *MemoryIndex) FindOffset(h plumbing.Hash) (int64, error) {
-	if len(idx.FanoutMapping) <= int(h[0]) {
+	first := h.Bytes()[0]
+	if len(idx.FanoutMapping) <= int(first) {
 		return 0, plumbing.ErrObjectNotFound
 	}
 
-	k := idx.FanoutMapping[h[0]]
+	k := idx.FanoutMapping[first]
 	i, ok := idx.findHashIndex(h)
 	if !ok {
 		return 0, plumbing.ErrObjectNotFound
@@ -158,7 +158,8 @@ func (idx *MemoryIndex) getOffset(firstLevel, secondLevel int) uint64 {
 
 // FindCRC32 implements the Index interface.
 func (idx *MemoryIndex) FindCRC32(h plumbing.Hash) (uint32, error) {
-	k := idx.FanoutMapping[h[0]]
+	first := h.Bytes()[0]
+	k := idx.FanoutMapping[first]
 	i, ok := idx.findHashIndex(h)
 	if !ok {
 		return 0, plumbing.ErrObjectNotFound
@@ -220,7 +221,10 @@ func (idx *MemoryIndex) genOffsetHash() error {
 	for firstLevel, fanoutValue := range idx.Fanout {
 		mappedFirstLevel := idx.FanoutMapping[firstLevel]
 		for secondLevel := uint32(0); i < fanoutValue; i++ {
-			copy(hash[:], idx.Names[mappedFirstLevel][secondLevel*objectIDLength:])
+			_, err = hash.Write(idx.Names[mappedFirstLevel][secondLevel*objectIDLength : (secondLevel*objectIDLength)+objectIDLength])
+			if err != nil {
+				return fmt.Errorf("failed to write hash: %w", err)
+			}
 			offset := int64(idx.getOffset(mappedFirstLevel, int(secondLevel)))
 			idx.offsetHash[offset] = hash
 			secondLevel++
@@ -298,7 +302,11 @@ func (i *idxfileEntryIter) Next() (*Entry, error) {
 
 		mappedFirstLevel := i.idx.FanoutMapping[i.firstLevel]
 		entry := new(Entry)
-		copy(entry.Hash[:], i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength:])
+
+		_, err := entry.Hash.Write(i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength : (i.secondLevel*objectIDLength)+objectIDLength])
+		if err != nil {
+			return nil, fmt.Errorf("failed to write hash: %w", err)
+		}
 		entry.Offset = i.idx.getOffset(mappedFirstLevel, i.secondLevel)
 		entry.CRC32 = i.idx.getCRC32(mappedFirstLevel, i.secondLevel)
 
diff --git a/plumbing/format/idxfile/writer.go b/plumbing/format/idxfile/writer.go
index c4c21e16..24083a59 100644
--- a/plumbing/format/idxfile/writer.go
+++ b/plumbing/format/idxfile/writer.go
@@ -110,7 +110,7 @@ func (w *Writer) createIndex() (*MemoryIndex, error) {
 	last := -1
 	bucket := -1
 	for i, o := range w.objects {
-		fan := o.Hash[0]
+		fan := o.Hash.Bytes()[0]
 
 		// fill the gaps between fans
 		for j := last + 1; j < int(fan); j++ {
@@ -132,7 +132,7 @@ func (w *Writer) createIndex() (*MemoryIndex, error) {
 			idx.CRC32 = append(idx.CRC32, make([]byte, 0))
 		}
 
-		idx.Names[bucket] = append(idx.Names[bucket], o.Hash[:]...)
+		idx.Names[bucket] = append(idx.Names[bucket], o.Hash.Bytes()...)
 
 		offset := o.Offset
 		if offset > math.MaxInt32 {
@@ -184,7 +184,7 @@ func (o objects) Len() int {
 }
 
 func (o objects) Less(i int, j int) bool {
-	cmp := bytes.Compare(o[i].Hash[:], o[j].Hash[:])
+	cmp := o[i].Hash.Compare(o[j].Hash.Bytes())
 	return cmp < 0
 }
 
diff --git a/plumbing/format/index/decoder.go b/plumbing/format/index/decoder.go
index fc25d370..f1bd3423 100644
--- a/plumbing/format/index/decoder.go
+++ b/plumbing/format/index/decoder.go
@@ -109,14 +109,20 @@ func (d *Decoder) readEntry(idx *Index) (*Entry, error) {
 		&e.UID,
 		&e.GID,
 		&e.Size,
-		&e.Hash,
-		&flags,
 	}
 
 	if err := binary.Read(d.r, flow...); err != nil {
 		return nil, err
 	}
 
+	var err error
+	if e.Hash, err = binary.ReadHash(d.r); err != nil {
+		return nil, err
+	}
+	if err := binary.Read(d.r, &flags); err != nil {
+		return nil, err
+	}
+
 	read := entryHeaderLength
 
 	if sec != 0 || nsec != 0 {
@@ -300,11 +306,11 @@ func (d *Decoder) getExtensionReader() (*bufio.Reader, error) {
 func (d *Decoder) readChecksum(expected []byte) error {
 	var h plumbing.Hash
 
-	if _, err := io.ReadFull(d.r, h[:]); err != nil {
+	if _, err := h.FromReader(d.r); err != nil {
 		return err
 	}
 
-	if !bytes.Equal(h[:], expected) {
+	if h.Compare(expected) != 0 {
 		return ErrInvalidChecksum
 	}
 
@@ -394,7 +400,7 @@ func (d *treeExtensionDecoder) readEntry() (*TreeEntry, error) {
 	}
 
 	e.Trees = i
-	_, err = io.ReadFull(d.r, e.Hash[:])
+	_, err = e.Hash.FromReader(d.r)
 	if err != nil {
 		return nil, err
 	}
@@ -440,7 +446,7 @@ func (d *resolveUndoDecoder) readEntry() (*ResolveUndoEntry, error) {
 
 	for s := range e.Stages {
 		var hash plumbing.Hash
-		if _, err := io.ReadFull(d.r, hash[:]); err != nil {
+		if _, err := hash.FromReader(d.r); err != nil {
 			return nil, err
 		}
 
@@ -479,7 +485,7 @@ func (d *endOfIndexEntryDecoder) Decode(e *EndOfIndexEntry) error {
 		return err
 	}
 
-	_, err = io.ReadFull(d.r, e.Hash[:])
+	_, err = e.Hash.FromReader(d.r)
 	return err
 }
 
diff --git a/plumbing/format/index/encoder.go b/plumbing/format/index/encoder.go
index 3fcf0f90..5c205cec 100644
--- a/plumbing/format/index/encoder.go
+++ b/plumbing/format/index/encoder.go
@@ -118,7 +118,7 @@ func (e *Encoder) encodeEntry(idx *Index, entry *Entry) error {
 		entry.UID,
 		entry.GID,
 		entry.Size,
-		entry.Hash[:],
+		entry.Hash.Bytes(),
 	}
 
 	flagsFlow := []interface{}{flags}
diff --git a/plumbing/format/packfile/scanner.go b/plumbing/format/packfile/scanner.go
index e170da67..73c8dc9e 100644
--- a/plumbing/format/packfile/scanner.go
+++ b/plumbing/format/packfile/scanner.go
@@ -448,7 +448,7 @@ func packFooter(r *Scanner) (stateFn, error) {
 		return nil, fmt.Errorf("cannot read PACK checksum: %w", ErrMalformedPackfile)
 	}
 
-	if !bytes.Equal(actual, checksum[:]) {
+	if checksum.Compare(actual) != 0 {
 		return nil, fmt.Errorf("checksum mismatch expected %q but found %q: %w",
 			hex.EncodeToString(actual), checksum, ErrMalformedPackfile)
 	}
diff --git a/plumbing/hash.go b/plumbing/hash.go
index d33bbaa3..6ef523eb 100644
--- a/plumbing/hash.go
+++ b/plumbing/hash.go
@@ -1,8 +1,6 @@
 package plumbing
 
 import (
-	"bytes"
-	"encoding/hex"
 	"sort"
 	"strconv"
 
@@ -10,7 +8,7 @@ import (
 )
 
 // Hash SHA1 hashed content
-type Hash [hash.Size]byte
+type Hash = hash.SHA1Hash
 
 // ZeroHash is Hash with value zero
 var ZeroHash Hash
@@ -24,21 +22,10 @@ func ComputeHash(t ObjectType, content []byte) Hash {
 
 // NewHash return a new Hash from a hexadecimal hash representation
 func NewHash(s string) Hash {
-	b, _ := hex.DecodeString(s)
-
-	var h Hash
-	copy(h[:], b)
-
-	return h
-}
-
-func (h Hash) IsZero() bool {
-	var empty Hash
-	return h == empty
-}
-
-func (h Hash) String() string {
-	return hex.EncodeToString(h[:])
+	if h, ok := hash.FromHex(s); ok {
+		return h.(Hash)
+	}
+	return ZeroHash
 }
 
 type Hasher struct {
@@ -60,7 +47,7 @@ func (h Hasher) Reset(t ObjectType, size int64) {
 }
 
 func (h Hasher) Sum() (hash Hash) {
-	copy(hash[:], h.Hash.Sum(nil))
+	hash.Write(h.Hash.Sum(nil))
 	return
 }
 
@@ -74,16 +61,5 @@ func HashesSort(a []Hash) {
 type HashSlice []Hash
 
 func (p HashSlice) Len() int           { return len(p) }
-func (p HashSlice) Less(i, j int) bool { return bytes.Compare(p[i][:], p[j][:]) < 0 }
+func (p HashSlice) Less(i, j int) bool { return p[i].Compare(p[j].Bytes()) < 0 }
 func (p HashSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
-
-// IsHash returns true if the given string is a valid hash.
-func IsHash(s string) bool {
-	switch len(s) {
-	case hash.HexSize:
-		_, err := hex.DecodeString(s)
-		return err == nil
-	default:
-		return false
-	}
-}
diff --git a/plumbing/hash/hash.go b/plumbing/hash/hash.go
index 8609848f..80999317 100644
--- a/plumbing/hash/hash.go
+++ b/plumbing/hash/hash.go
@@ -4,12 +4,24 @@ package hash
 
 import (
 	"crypto"
+	"errors"
 	"fmt"
 	"hash"
 
 	"github.com/pjbgf/sha1cd"
 )
 
+const (
+	SHA1Size      = 20
+	SHA1HexSize   = SHA1Size * 2
+	SHA256Size    = 32
+	SHA256HexSize = SHA256Size * 2
+)
+
+var (
+	ErrUnsupportedHashFunction = errors.New("unsupported hash function")
+)
+
 // algos is a map of hash algorithms.
 var algos = map[crypto.Hash]func() hash.Hash{}
 
@@ -38,7 +50,7 @@ func RegisterHash(h crypto.Hash, f func() hash.Hash) error {
 	case crypto.SHA256:
 		algos[h] = f
 	default:
-		return fmt.Errorf("unsupported hash function: %v", h)
+		return fmt.Errorf("%w: %v", ErrUnsupportedHashFunction, h)
 	}
 	return nil
 }
diff --git a/plumbing/hash/hash_sha1.go b/plumbing/hash/hash_sha1.go
index e3cb60fe..d51b195d 100644
--- a/plumbing/hash/hash_sha1.go
+++ b/plumbing/hash/hash_sha1.go
@@ -1,15 +1,57 @@
-//go:build !sha256
-// +build !sha256
-
 package hash
 
-import "crypto"
+import (
+	"bytes"
+	"crypto"
+	"encoding/hex"
+	"io"
+)
 
 const (
 	// CryptoType defines what hash algorithm is being used.
 	CryptoType = crypto.SHA1
 	// Size defines the amount of bytes the hash yields.
-	Size = 20
+	Size = SHA1Size
 	// HexSize defines the strings size of the hash when represented in hexadecimal.
-	HexSize = 40
+	HexSize = SHA1HexSize
 )
+
+type SHA1Hash struct {
+	hash [SHA1Size]byte
+}
+
+func (ih SHA1Hash) Size() int {
+	return len(ih.hash)
+}
+
+func (ih SHA1Hash) IsZero() bool {
+	return ih == zeroSHA1
+}
+
+func (ih SHA1Hash) String() string {
+	return hex.EncodeToString(ih.hash[:])
+}
+
+func (ih SHA1Hash) Bytes() []byte {
+	return ih.hash[:]
+}
+
+func (ih SHA1Hash) Compare(in []byte) int {
+	return bytes.Compare(ih.hash[:], in)
+}
+
+func (ih SHA1Hash) HasPrefix(prefix []byte) bool {
+	return bytes.HasPrefix(ih.hash[:], prefix)
+}
+
+func (ih *SHA1Hash) Write(in []byte) (int, error) {
+	return copy(ih.hash[:], in), nil
+}
+
+func (ih *SHA1Hash) FromReaderAt(r io.ReaderAt, off int64) (int, error) {
+	return r.ReadAt(ih.hash[:], off)
+}
+
+func (ih *SHA1Hash) FromReader(r io.Reader) (int, error) {
+	return io.ReadFull(r, ih.hash[:])
+}
diff --git a/plumbing/hash/hash_sha256.go b/plumbing/hash/hash_sha256.go
index 1c52b897..5907fa58 100644
--- a/plumbing/hash/hash_sha256.go
+++ b/plumbing/hash/hash_sha256.go
@@ -1,15 +1,47 @@
-//go:build sha256
-// +build sha256
-
 package hash
 
-import "crypto"
-
-const (
-	// CryptoType defines what hash algorithm is being used.
-	CryptoType = crypto.SHA256
-	// Size defines the amount of bytes the hash yields.
-	Size = 32
-	// HexSize defines the strings size of the hash when represented in hexadecimal.
-	HexSize = 64
+import (
+	"bytes"
+	"encoding/hex"
+	"io"
 )
+
+type SHA256Hash struct {
+	hash [SHA256Size]byte
+}
+
+func (ih SHA256Hash) Size() int {
+	return len(ih.hash)
+}
+
+func (ih SHA256Hash) IsZero() bool {
+	return ih == zeroSHA256
+}
+
+func (ih SHA256Hash) String() string {
+	return hex.EncodeToString(ih.hash[:])
+}
+
+func (ih SHA256Hash) Bytes() []byte {
+	return ih.hash[:]
+}
+
+func (ih SHA256Hash) Compare(in []byte) int {
+	return bytes.Compare(ih.hash[:], in)
+}
+
+func (ih SHA256Hash) HasPrefix(prefix []byte) bool {
+	return bytes.HasPrefix(ih.hash[:], prefix)
+}
+
+func (ih *SHA256Hash) Write(in []byte) (int, error) {
+	return copy(ih.hash[:], in), nil
+}
+
+func (ih *SHA256Hash) FromReaderAt(r io.ReaderAt, off int64) (int, error) {
+	return r.ReadAt(ih.hash[:], off)
+}
+
+func (ih *SHA256Hash) FromReader(r io.Reader) (int, error) {
+	return io.ReadFull(r, ih.hash[:])
+}
diff --git a/plumbing/hash/hash_zero.go b/plumbing/hash/hash_zero.go
new file mode 100644
index 00000000..68be2424
--- /dev/null
+++ b/plumbing/hash/hash_zero.go
@@ -0,0 +1,14 @@
+package hash
+
+var (
+	zeroSHA1   = SHA1Hash{}
+	zeroSHA256 = SHA256Hash{}
+)
+
+func Zero() SHA1Hash {
+	return zeroSHA1
+}
+
+func ZeroSHA256() SHA256Hash {
+	return zeroSHA256
+}
diff --git a/plumbing/hash/new.go b/plumbing/hash/new.go
new file mode 100644
index 00000000..42299bc2
--- /dev/null
+++ b/plumbing/hash/new.go
@@ -0,0 +1,99 @@
+package hash
+
+import (
+	"encoding/hex"
+)
+
+func MustFromHex(in string) ObjectID {
+	h, ok := FromHex(in)
+	if !ok {
+		panic("cannot create hash from " + in)
+	}
+	return h
+}
+
+func MustFromBytes(in []byte) ObjectID {
+	h, ok := FromBytes(in)
+	if !ok {
+		panic("cannot create hash from " + string(in))
+	}
+	return h
+}
+
+// ValidHex returns true if the given string is a valid hex hash.
+func ValidHex(in string) bool {
+	if len(in) != SHA1HexSize &&
+		len(in) != SHA256HexSize {
+		return false
+	}
+
+	_, err := hex.DecodeString(in)
+	if err != nil {
+		return false
+	}
+
+	return true
+}
+
+// FromHex parses a hexadecimal string and returns an ObjectID
+// and a boolean confirming whether the operation was successful.
+// The hash (and object format) is inferred from the length of the
+// input.
+//
+// If the operation was not successful, the resulting hash is nil
+// instead of a zeroed hash.
+func FromHex(in string) (ObjectID, bool) {
+	if len(in) != SHA1HexSize &&
+		len(in) != SHA256HexSize {
+		return nil, false
+	}
+
+	b, err := hex.DecodeString(in)
+	if err != nil {
+		return nil, false
+	}
+
+	switch len(in) {
+	case SHA1HexSize:
+		h := SHA1Hash{}
+		copy(h.hash[:], b)
+		return h, true
+
+	case SHA256HexSize:
+		h := SHA256Hash{}
+		copy(h.hash[:], b)
+		return h, true
+
+	default:
+		return nil, false
+	}
+}
+
+// FromBytes creates an ImmutableHash object based on the value its
+// Sum() should return.
+// The hash type (and object format) is inferred from the length of
+// the input.
+//
+// If the operation was not successful, the resulting hash is nil
+// instead of a zeroed hash.
+func FromBytes(in []byte) (ObjectID, bool) {
+	if len(in) != SHA1Size &&
+		len(in) != SHA256Size {
+		return nil, false
+	}
+
+	switch len(in) {
+	case SHA1Size:
+		h := SHA1Hash{}
+		copy(h.hash[:], in)
+		return h, true
+
+	case SHA256Size:
+		h := SHA256Hash{}
+		copy(h.hash[:], in)
+		return h, true
+
+	default:
+		return nil, false
+	}
+}
diff --git a/plumbing/hash/object.go b/plumbing/hash/object.go
new file mode 100644
index 00000000..cf712334
--- /dev/null
+++ b/plumbing/hash/object.go
@@ -0,0 +1,29 @@
+package hash
+
+import "io"
+
+// ObjectID represents a calculated hash, which is immutable by nature.
+type ObjectID interface {
+	// Size returns the length of the resulting hash.
+	Size() int
+	// IsZero returns true if the objectID only contains 0s.
+	IsZero() bool
+	// Compare compares the hash's sum with a slice of bytes.
+	Compare([]byte) int
+	// String returns the hexadecimal representation of the hash's sum.
+	String() string
+	// Bytes returns a slice of bytes containing the ObjectID.
+	Bytes() []byte
+	// HasPrefix verifies whether the objectID start with a given prefix.
+	HasPrefix([]byte) bool
+}
+
+// LazyObjectID represents an object hash which may not be known at the time
+// the object is created. Or an objectID which changes during its lifetime.
+type LazyObjectID interface {
+	ObjectID
+
+	io.Writer
+	FromReaderAt(r io.ReaderAt, off int64) (int, error)
+	FromReader(r io.Reader) (int, error)
+}
diff --git a/plumbing/hash/sort.go b/plumbing/hash/sort.go
new file mode 100644
index 00000000..dd1df42c
--- /dev/null
+++ b/plumbing/hash/sort.go
@@ -0,0 +1,19 @@
+package hash
+
+import (
+	"sort"
+	"strings"
+)
+
+// HashesSort sorts a slice of Hashes in increasing order.
+func Sort(a []ObjectID) {
+	sort.Sort(ObjectIDs(a))
+}
+
+// HashSlice attaches the methods of sort.Interface to []Hash, sorting in
+// increasing order.
+type ObjectIDs []ObjectID
+
+func (p ObjectIDs) Len() int           { return len(p) }
+func (p ObjectIDs) Less(i, j int) bool { return strings.Compare(p[i].String(), p[j].String()) < 0 }
+func (p ObjectIDs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
diff --git a/plumbing/hasher/hasher.go b/plumbing/hasher/hasher.go
new file mode 100644
index 00000000..457209f6
--- /dev/null
+++ b/plumbing/hasher/hasher.go
@@ -0,0 +1,153 @@
+package hasher
+
+import (
+	"bytes"
+	"crypto"
+	"fmt"
+	"strconv"
+	"sync"
+
+	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/hash"
+
+	format "github.com/go-git/go-git/v5/plumbing/format/config"
+)
+
+// ObjectHasher computes hashes for Git objects. A few differences
+// it has when compared to Hasher:
+//
+//   - ObjectType awareness: produces either SHA1 or SHA256 hashes
+//     depending on the format needed.
+//   - Thread-safety.
+//   - API restricts ability of generating invalid hashes.
+type ObjectHasher interface {
+	// Size returns the length of resulting hash.
+	Size() int
+	// Compute calculates the hash of a Git object. The process involves
+	// first writing the object header, which contains the object type
+	// and content size, followed by the content itself.
+	Compute(ot plumbing.ObjectType, d []byte) (hash.ObjectID, error)
+}
+
+// FromObjectFormat returns the correct ObjectHasher for the given
+// ObjectFormat.
+//
+// If the format is not recognised, an ErrInvalidObjectFormat error
+// is returned.
+func FromObjectFormat(f format.ObjectFormat) (ObjectHasher, error) {
+	switch f {
+	case format.SHA1:
+		return newHasherSHA1(), nil
+	case format.SHA256:
+		return newHasherSHA256(), nil
+	default:
+		return nil, format.ErrInvalidObjectFormat
+	}
+}
+
+// FromHash returns the correct ObjectHasher for the given
+// Hash.
+//
+// If the hash type is not recognised, an ErrUnsupportedHashFunction
+// error is returned.
+func FromHash(h hash.Hash) (ObjectHasher, error) {
+	switch h.Size() {
+	case hash.SHA1Size:
+		return newHasherSHA1(), nil
+	case hash.SHA256Size:
+		return newHasherSHA256(), nil
+	default:
+		return nil, hash.ErrUnsupportedHashFunction
+	}
+}
+
+func newHasherSHA1() *objectHasherSHA1 {
+	return &objectHasherSHA1{
+		hasher: hash.New(crypto.SHA1),
+		buf:    *bytes.NewBuffer(make([]byte, hash.SHA1Size)),
+	}
+}
+
+type objectHasherSHA1 struct {
+	hasher hash.Hash
+	m      sync.Mutex
+	// both fields below are allocation optimisations.
+	b   [hash.SHA1Size]byte
+	buf bytes.Buffer
+}
+
+func (h *objectHasherSHA1) Compute(ot plumbing.ObjectType, d []byte) (hash.ObjectID, error) {
+	h.m.Lock()
+	h.hasher.Reset()
+	h.buf.Reset()
+
+	writeHeader(h.hasher, h.buf, ot, int64(len(d)))
+	_, err := h.hasher.Write(d)
+	if err != nil {
+		h.m.Unlock()
+		return nil, fmt.Errorf("failed to compute hash: %w", err)
+	}
+
+	var out hash.SHA1Hash
+	out.Write(h.hasher.Sum(h.b[:0]))
+	h.m.Unlock()
+	return out, nil
+}
+
+func (h *objectHasherSHA1) Size() int {
+	return h.hasher.Size()
+}
+
+func (h *objectHasherSHA1) Write(p []byte) (int, error) {
+	return h.hasher.Write(p)
+}
+
+func newHasherSHA256() *objectHasherSHA256 {
+	return &objectHasherSHA256{
+		hasher: hash.New(crypto.SHA256),
+		buf:    *bytes.NewBuffer(make([]byte, hash.SHA256Size)),
+	}
+}
+
+type objectHasherSHA256 struct {
+	hasher hash.Hash
+	m      sync.Mutex
+	// both fields below are allocation optimisations.
+	b   [hash.SHA256Size]byte
+	buf bytes.Buffer
+}
+
+func (h *objectHasherSHA256) Compute(ot plumbing.ObjectType, d []byte) (hash.ObjectID, error) {
+	h.m.Lock()
+	h.hasher.Reset()
+	h.buf.Reset()
+
+	writeHeader(h.hasher, h.buf, ot, int64(len(d)))
+	_, err := h.hasher.Write(d)
+	if err != nil {
+		h.m.Unlock()
+		return nil, fmt.Errorf("failed to compute hash: %w", err)
+	}
+
+	var out hash.SHA256Hash
+	out.Write(h.hasher.Sum(h.b[:0]))
+	h.m.Unlock()
+	return out, nil
+}
+
+func (h *objectHasherSHA256) Size() int {
+	return h.hasher.Size()
+}
+
+func (h *objectHasherSHA256) Write(p []byte) (int, error) {
+	return h.hasher.Write(p)
+}
+
+func writeHeader(h hash.Hash, buf bytes.Buffer, ot plumbing.ObjectType, sz int64) {
+	buf.Write(ot.Bytes())
+	buf.Write([]byte(" "))
+	buf.Write([]byte(strconv.FormatInt(sz, 10)))
+	buf.Write([]byte{0})
+
+	h.Write(buf.Bytes())
+}
diff --git a/plumbing/memory.go b/plumbing/memory.go
index ba144559..fa683b69 100644
--- a/plumbing/memory.go
+++ b/plumbing/memory.go
@@ -3,7 +3,6 @@ package plumbing
 import (
 	"bytes"
 	"io"
-	"slices"
 )
 
 // MemoryObject on memory Object implementation
@@ -19,7 +18,7 @@ type MemoryObject struct {
 // if the type or the content have changed. The Hash is only generated if the
 // size of the content is exactly the object size.
 func (o *MemoryObject) Hash() Hash {
-	if o.h == ZeroHash && int64(len(o.cont)) == o.sz {
+	if o.h.IsZero() && int64(len(o.cont)) == o.sz {
 		o.h = ComputeHash(o.t, o.cont)
 	}
 
@@ -38,7 +37,6 @@ func (o *MemoryObject) Size() int64 { return o.sz }
 // SetSize set the object size, a content of the given size should be written
 // afterwards
 func (o *MemoryObject) SetSize(s int64) {
-	o.cont = slices.Grow(o.cont, int(s))
 	o.sz = s
 }
 
diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go
index 3d096e18..34c2711c 100644
--- a/plumbing/object/commit.go
+++ b/plumbing/object/commit.go
@@ -439,7 +439,7 @@ func (c *Commit) Less(rhs *Commit) bool {
 	return c.Committer.When.Before(rhs.Committer.When) ||
 		(c.Committer.When.Equal(rhs.Committer.When) &&
 			(c.Author.When.Before(rhs.Author.When) ||
-				(c.Author.When.Equal(rhs.Author.When) && bytes.Compare(c.Hash[:], rhs.Hash[:]) < 0)))
+				(c.Author.When.Equal(rhs.Author.When) && c.Hash.Compare(rhs.Hash.Bytes()) < 0)))
 }
 
 func indent(t string) string {
diff --git a/plumbing/object/tree.go b/plumbing/object/tree.go
index 2e1b7891..9c2b884a 100644
--- a/plumbing/object/tree.go
+++ b/plumbing/object/tree.go
@@ -257,7 +257,7 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
 		}
 
 		var hash plumbing.Hash
-		if _, err = io.ReadFull(r, hash[:]); err != nil {
+		if _, err = hash.FromReader(r); err != nil {
 			return err
 		}
 
@@ -321,7 +321,7 @@ func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
 			return err
 		}
 
-		if _, err = w.Write(entry.Hash[:]); err != nil {
+		if _, err = w.Write(entry.Hash.Bytes()); err != nil {
 			return err
 		}
 	}
diff --git a/plumbing/object/treenoder.go b/plumbing/object/treenoder.go
index 2adb6452..a2e1e1f9 100644
--- a/plumbing/object/treenoder.go
+++ b/plumbing/object/treenoder.go
@@ -52,9 +52,9 @@ func (t *treeNoder) String() string {
 
 func (t *treeNoder) Hash() []byte {
 	if t.mode == filemode.Deprecated {
-		return append(t.hash[:], filemode.Regular.Bytes()...)
+		return append(t.hash.Bytes(), filemode.Regular.Bytes()...)
 	}
-	return append(t.hash[:], t.mode.Bytes()...)
+	return append(t.hash.Bytes(), t.mode.Bytes()...)
 }
 
 func (t *treeNoder) Name() string {
diff --git a/plumbing/protocol/packp/advrefs_decode.go b/plumbing/protocol/packp/advrefs_decode.go
index c45ecf76..fb16bd0a 100644
--- a/plumbing/protocol/packp/advrefs_decode.go
+++ b/plumbing/protocol/packp/advrefs_decode.go
@@ -2,13 +2,13 @@ package packp
 
 import (
 	"bytes"
-	"encoding/hex"
 	"errors"
 	"fmt"
 	"io"
 
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/format/pktline"
+	"github.com/go-git/go-git/v5/plumbing/hash"
 )
 
 // Decode reads the next advertised-refs message form its input and
@@ -141,11 +141,13 @@ func decodeFirstHash(p *advRefsDecoder) decoderStateFn {
 		return nil
 	}
 
-	if _, err := hex.Decode(p.hash[:], p.line[:hashSize]); err != nil {
-		p.error("invalid hash text: %s", err)
+	in := string(p.line[:hashSize])
+	if !hash.ValidHex(in) {
+		p.error("invalid hash text: %s", in)
 		return nil
 	}
 
+	p.hash = plumbing.NewHash(in)
 	p.line = p.line[hashSize:]
 
 	if p.hash.IsZero() {
@@ -271,12 +273,12 @@ func decodeShallow(p *advRefsDecoder) decoderStateFn {
 	}
 
 	text := p.line[:hashSize]
-	var h plumbing.Hash
-	if _, err := hex.Decode(h[:], text); err != nil {
-		p.error("invalid hash text: %s", err)
+	if !hash.ValidHex(string(text)) {
+		p.error("invalid hash text: %s", text)
 		return nil
 	}
 
+	h := plumbing.NewHash(string(text))
 	p.data.Shallows = append(p.data.Shallows, h)
 
 	if ok := p.nextLine(); !ok {
diff --git a/plumbing/protocol/packp/ulreq_decode.go b/plumbing/protocol/packp/ulreq_decode.go
index ea736c1f..0cf7f2b1 100644
--- a/plumbing/protocol/packp/ulreq_decode.go
+++ b/plumbing/protocol/packp/ulreq_decode.go
@@ -2,7 +2,6 @@ package packp
 
 import (
 	"bytes"
-	"encoding/hex"
 	"fmt"
 	"io"
 	"strconv"
@@ -10,6 +9,7 @@ import (
 
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/format/pktline"
+	"github.com/go-git/go-git/v5/plumbing/hash"
 )
 
 // Decode reads the next upload-request form its input and
@@ -115,14 +115,16 @@ func (d *ulReqDecoder) readHash() (plumbing.Hash, bool) {
 		return plumbing.ZeroHash, false
 	}
 
-	var hash plumbing.Hash
-	if _, err := hex.Decode(hash[:], d.line[:hashSize]); err != nil {
-		d.error("invalid hash text: %s", err)
+	in := string(d.line[:hashSize])
+	if !hash.ValidHex(in) {
+		d.error("invalid hash text: %s", in)
 		return plumbing.ZeroHash, false
 	}
+
 	d.line = d.line[hashSize:]
+	h := plumbing.NewHash(in)
 
-	return hash, true
+	return h, true
 }
 
 // Expected format: sp cap1 sp cap2 sp cap3...
diff --git a/plumbing/protocol/packp/ulreq_encode.go b/plumbing/protocol/packp/ulreq_encode.go
index 87480236..0bb77046 100644
--- a/plumbing/protocol/packp/ulreq_encode.go
+++ b/plumbing/protocol/packp/ulreq_encode.go
@@ -1,7 +1,6 @@
 package packp
 
 import (
-	"bytes"
 	"fmt"
 	"io"
 	"time"
@@ -69,7 +68,7 @@ func (e *ulReqEncoder) encodeFirstWant() stateFn {
 func (e *ulReqEncoder) encodeAdditionalWants() stateFn {
 	last := e.data.Wants[0]
 	for _, w := range e.data.Wants[1:] {
-		if bytes.Equal(last[:], w[:]) {
+		if last.Compare(w.Bytes()) == 0 {
 			continue
 		}
 
@@ -89,7 +88,7 @@ func (e *ulReqEncoder) encodeShallows() stateFn {
 
 	var last plumbing.Hash
 	for _, s := range e.data.Shallows {
-		if bytes.Equal(last[:], s[:]) {
+		if last.Compare(s.Bytes()) == 0 {
 			continue
 		}
 
diff --git a/plumbing/protocol/packp/uppackreq.go b/plumbing/protocol/packp/uppackreq.go
index a8a5ad6a..6d7bfe7e 100644
--- a/plumbing/protocol/packp/uppackreq.go
+++ b/plumbing/protocol/packp/uppackreq.go
@@ -1,7 +1,6 @@
 package packp
 
 import (
-	"bytes"
 	"fmt"
 	"io"
 
@@ -89,7 +88,7 @@ func (u *UploadHaves) Encode(w io.Writer, flush bool) error {
 
 	var last plumbing.Hash
 	for _, have := range u.Haves {
-		if bytes.Equal(last[:], have[:]) {
+		if last.Compare(have.Bytes()) == 0 {
 			continue
 		}
 
diff --git a/remote.go b/remote.go
index 5b980fb6..9d6f2682 100644
--- a/remote.go
+++ b/remote.go
@@ -34,6 +34,7 @@ var (
 	ErrForceNeeded           = errors.New("some refs were not updated")
 	ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
 	ErrEmptyUrls             = errors.New("URLs cannot be empty")
+	ErrFilterNotSupported    = errors.New("server does not support filters")
 )
 
 type NoMatchingRefSpecError struct {
@@ -1168,6 +1169,16 @@ func (r *Remote) newUploadPackRequest(o *FetchOptions,
 		}
 	}
 
+	if o.Filter != "" {
+		if ar.Capabilities.Supports(capability.Filter) {
+			req.Filter = o.Filter
+			if err := req.Capabilities.Set(capability.Filter); err != nil {
+				return nil, err
+			}
+		} else {
+			return nil, ErrFilterNotSupported
+		}
+	}
 	isWildcard := true
 	for _, s := range o.RefSpecs {
 		if !s.IsWildcard() {
diff --git a/repository.go b/repository.go
index 99ca0566..afecbd92 100644
--- a/repository.go
+++ b/repository.go
@@ -951,6 +951,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
 		InsecureSkipTLS: o.InsecureSkipTLS,
 		CABundle:        o.CABundle,
 		ProxyOptions:    o.ProxyOptions,
+		Filter:          o.Filter,
 	}, o.ReferenceName)
 	if err != nil {
 		return err
@@ -1715,14 +1716,14 @@ func (r *Repository) resolveHashPrefix(hashStr string) []plumbing.Hash {
 	if hashStr == "" {
 		return nil
 	}
-	if len(hashStr) == len(plumbing.ZeroHash)*2 {
+	if len(hashStr) == plumbing.ZeroHash.Size()*2 {
 		// Only a full hash is possible.
 		hexb, err := hex.DecodeString(hashStr)
 		if err != nil {
 			return nil
 		}
 		var h plumbing.Hash
-		copy(h[:], hexb)
+		h.Write(hexb)
 		return []plumbing.Hash{h}
 	}
 
@@ -1896,7 +1897,7 @@ func expandPartialHash(st storer.EncodedObjectStorer, prefix []byte) (hashes []p
 	}
 	iter.ForEach(func(obj plumbing.EncodedObject) error {
 		h := obj.Hash()
-		if bytes.HasPrefix(h[:], prefix) {
+		if h.HasPrefix(prefix) {
 			hashes = append(hashes, h)
 		}
 		return nil
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index 80302975..2ef46036 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -3,7 +3,6 @@ package dotgit
 
 import (
 	"bufio"
-	"bytes"
 	"errors"
 	"fmt"
 	"io"
@@ -354,7 +353,7 @@ func (d *DotGit) ObjectsWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
 	// Handle edge cases.
 	if len(prefix) < 1 {
 		return d.Objects()
-	} else if len(prefix) > len(plumbing.ZeroHash) {
+	} else if len(prefix) > plumbing.ZeroHash.Size() {
 		return nil, nil
 	}
 
@@ -368,13 +367,13 @@ func (d *DotGit) ObjectsWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
 		// Figure out the half-open interval defined by the prefix.
 		first := sort.Search(len(d.objectList), func(i int) bool {
 			// Same as plumbing.HashSlice.Less.
-			return bytes.Compare(d.objectList[i][:], prefix) >= 0
+			return d.objectList[i].Compare(prefix) >= 0
 		})
 		lim := len(d.objectList)
 		if limPrefix, overflow := incBytes(prefix); !overflow {
 			lim = sort.Search(len(d.objectList), func(i int) bool {
 				// Same as plumbing.HashSlice.Less.
-				return bytes.Compare(d.objectList[i][:], limPrefix) >= 0
+				return d.objectList[i].Compare(limPrefix) >= 0
 			})
 		}
 		return d.objectList[first:lim], nil
@@ -385,7 +384,7 @@ func (d *DotGit) ObjectsWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
 	var n int
 	err := d.ForEachObjectHash(func(hash plumbing.Hash) error {
 		n++
-		if bytes.HasPrefix(hash[:], prefix) {
+		if hash.HasPrefix(prefix) {
 			objects = append(objects, hash)
 		}
 		return nil
diff --git a/storage/filesystem/object.go b/storage/filesystem/object.go
index 24757119..7d25ef50 100644
--- a/storage/filesystem/object.go
+++ b/storage/filesystem/object.go
@@ -1,7 +1,6 @@
 package filesystem
 
 import (
-	"bytes"
 	"fmt"
 	"io"
 	"os"
@@ -593,7 +592,7 @@ func (s *ObjectStorage) HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error)
 			} else if err != nil {
 				return nil, err
 			}
-			if bytes.HasPrefix(e.Hash[:], prefix) {
+			if e.Hash.HasPrefix(prefix) {
 				if _, ok := seen[e.Hash]; ok {
 					continue
 				}
diff --git a/utils/binary/read.go b/utils/binary/read.go
index b8f9df1a..008590a7 100644
--- a/utils/binary/read.go
+++ b/utils/binary/read.go
@@ -80,10 +80,9 @@ func ReadUntilFromBufioReader(r *bufio.Reader, delim byte) ([]byte, error) {
 //
 // This is how the offset is saved in C:
 //
-//     dheader[pos] = ofs & 127;
-//     while (ofs >>= 7)
-//         dheader[--pos] = 128 | (--ofs & 127);
-//
+//	dheader[pos] = ofs & 127;
+//	while (ofs >>= 7)
+//	    dheader[--pos] = 128 | (--ofs & 127);
 func ReadVariableWidthInt(r io.Reader) (int64, error) {
 	var c byte
 	if err := Read(r, &c); err != nil {
@@ -142,7 +141,12 @@ func ReadUint16(r io.Reader) (uint16, error) {
 // ReadHash reads a plumbing.Hash from r
 func ReadHash(r io.Reader) (plumbing.Hash, error) {
 	var h plumbing.Hash
-	if err := binary.Read(r, binary.BigEndian, h[:]); err != nil {
+	b := make([]byte, h.Size())
+	if err := binary.Read(r, binary.BigEndian, b[:]); err != nil {
+		return h, err
+	}
+
+	if _, err := h.Write(b); err != nil {
 		return plumbing.ZeroHash, err
 	}
 
diff --git a/utils/merkletrie/filesystem/node.go b/utils/merkletrie/filesystem/node.go
index 33800627..2cc5ec32 100644
--- a/utils/merkletrie/filesystem/node.go
+++ b/utils/merkletrie/filesystem/node.go
@@ -154,11 +154,11 @@ func (n *node) calculateHash() {
 	}
 	mode, err := filemode.NewFromOSFileMode(n.mode)
 	if err != nil {
-		n.hash = plumbing.ZeroHash[:]
+		copy(n.hash, plumbing.ZeroHash.Bytes())
 		return
 	}
 	if submoduleHash, isSubmodule := n.submodules[n.path]; isSubmodule {
-		n.hash = append(submoduleHash[:], filemode.Submodule.Bytes()...)
+		n.hash = append(submoduleHash.Bytes(), filemode.Submodule.Bytes()...)
 		return
 	}
 	var hash plumbing.Hash
@@ -167,7 +167,7 @@ func (n *node) calculateHash() {
 	} else {
 		hash = n.doCalculateHashForRegular()
 	}
-	n.hash = append(hash[:], mode.Bytes()...)
+	n.hash = append(hash.Bytes(), mode.Bytes()...)
 }
 
 func (n *node) doCalculateHashForRegular() plumbing.Hash {
diff --git a/utils/merkletrie/index/node.go b/utils/merkletrie/index/node.go
index c1809f7e..8822db57 100644
--- a/utils/merkletrie/index/node.go
+++ b/utils/merkletrie/index/node.go
@@ -75,7 +75,7 @@ func (n *node) Hash() []byte {
 		return make([]byte, 24)
 	}
 
-	return append(n.entry.Hash[:], n.entry.Mode.Bytes()...)
+	return append(n.entry.Hash.Bytes(), n.entry.Mode.Bytes()...)
 }
 
 func (n *node) Name() string {
diff --git a/worktree.go b/worktree.go
index 91748150..2a498e1f 100644
--- a/worktree.go
+++ b/worktree.go
@@ -432,8 +432,9 @@ func (w *Worktree) resetIndex(t *object.Tree, dirs []string, files []string) err
 }
 
 func inFiles(files []string, v string) bool {
+	v = filepath.Clean(v)
 	for _, s := range files {
-		if s == v {
+		if filepath.Clean(s) == v {
 			return true
 		}
 	}
diff --git a/worktree_status.go b/worktree_status.go
index 6e72db97..7870d138 100644
--- a/worktree_status.go
+++ b/worktree_status.go
@@ -370,6 +370,8 @@ func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern, skipSta
 		}
 	}
 
+	path = filepath.Clean(path)
+
 	if err != nil || !fi.IsDir() {
 		added, h, err = w.doAddFile(idx, s, path, ignorePattern)
 	} else {


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 6b52de48..0131ec2d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,8 +2,8 @@ name: Test
 on: 
   push:
     branches:
-    - "master"
-    - "v6-exp"
+    - releases/v5.x
+    - main
   pull_request:
 
 permissions: {}
diff --git a/_examples/common_test.go b/_examples/common_test.go
index 2db9dc03..6414d45a 100644
--- a/_examples/common_test.go
+++ b/_examples/common_test.go
@@ -35,7 +35,6 @@ var args = map[string][]string{
 	"push":                       {setEmptyRemote(cloneRepository(defaultURL, tempFolder()))},
 	"restore":                    {cloneRepository(defaultURL, tempFolder())},
 	"revision":                   {cloneRepository(defaultURL, tempFolder()), "master~2^"},
-	"sha256":                     {tempFolder()},
 	"showcase":                   {defaultURL, tempFolder()},
 	"sparse-checkout":            {defaultURL, "vendor", tempFolder()},
 	"tag":                        {cloneRepository(defaultURL, tempFolder())},
@@ -44,7 +43,6 @@ var args = map[string][]string{
 // tests not working / set-up
 var ignored = map[string]bool{
 	"ls":              true,
-	"sha256":          true,
 	"submodule":       true,
 	"tag-create-push": true,
 }
diff --git a/plumbing/format/commitgraph/v2/chain_test.go b/plumbing/format/commitgraph/v2/chain_test.go
index f2122052..b04cf209 100644
--- a/plumbing/format/commitgraph/v2/chain_test.go
+++ b/plumbing/format/commitgraph/v2/chain_test.go
@@ -93,6 +93,6 @@ func (s *CommitgraphSuite) TestOpenChainFile() {
 	newlineChainReader := bytes.NewReader(newlineChainData)
 
 	chain, err = commitgraph.OpenChainFile(newlineChainReader)
-	s.Equal(err, commitgraph.ErrMalformedCommitGraphFile)
+	s.ErrorIs(err, commitgraph.ErrMalformedCommitGraphFile)
 	s.Nil(chain)
 }
diff --git a/plumbing/format/idxfile/decoder_test.go b/plumbing/format/idxfile/decoder_test.go
index 02d218af..ad2f9b84 100644
--- a/plumbing/format/idxfile/decoder_test.go
+++ b/plumbing/format/idxfile/decoder_test.go
@@ -3,7 +3,6 @@ package idxfile_test
 import (
 	"bytes"
 	"encoding/base64"
-	"fmt"
 	"io"
 	"testing"
 
@@ -51,8 +50,8 @@ func (s *IdxfileSuite) TestDecode() {
 	s.NoError(err)
 	s.Equal(uint32(3645019190), crc32)
 
-	s.Equal("fb794f1ec720b9bc8e43257451bd99c4be6fa1c9", fmt.Sprintf("%x", idx.IdxChecksum))
-	s.Equal(f.PackfileHash, fmt.Sprintf("%x", idx.PackfileChecksum))
+	s.Equal("fb794f1ec720b9bc8e43257451bd99c4be6fa1c9", idx.IdxChecksum.String())
+	s.Equal(f.PackfileHash, idx.PackfileChecksum.String())
 }
 
 func (s *IdxfileSuite) TestDecode64bitsOffsets() {
diff --git a/plumbing/format/packfile/delta_selector_test.go b/plumbing/format/packfile/delta_selector_test.go
index 3d196d35..7e339b21 100644
--- a/plumbing/format/packfile/delta_selector_test.go
+++ b/plumbing/format/packfile/delta_selector_test.go
@@ -1,27 +1,31 @@
 package packfile
 
 import (
+	"testing"
+
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/storage/memory"
-
-	. "gopkg.in/check.v1"
+	"github.com/stretchr/testify/suite"
 )
 
 type DeltaSelectorSuite struct {
+	suite.Suite
 	ds     *deltaSelector
 	store  *memory.Storage
 	hashes map[string]plumbing.Hash
 }
 
-var _ = Suite(&DeltaSelectorSuite{})
+func TestDeltaSelectorSuite(t *testing.T) {
+	suite.Run(t, new(DeltaSelectorSuite))
+}
 
-func (s *DeltaSelectorSuite) SetUpTest(c *C) {
+func (s *DeltaSelectorSuite) SetupTest() {
 	s.store = memory.NewStorage()
 	s.createTestObjects()
 	s.ds = newDeltaSelector(s.store)
 }
 
-func (s *DeltaSelectorSuite) TestSort(c *C) {
+func (s *DeltaSelectorSuite) TestSort() {
 	var o1 = newObjectToPack(newObject(plumbing.BlobObject, []byte("00000")))
 	var o4 = newObjectToPack(newObject(plumbing.BlobObject, []byte("0000")))
 	var o6 = newObjectToPack(newObject(plumbing.BlobObject, []byte("00")))
@@ -35,7 +39,7 @@ func (s *DeltaSelectorSuite) TestSort(c *C) {
 	toSort := []*ObjectToPack{o1, o2, o3, o4, o5, o6, o7, o8, o9}
 	s.ds.sort(toSort)
 	expected := []*ObjectToPack{o1, o4, o6, o9, o8, o2, o3, o5, o7}
-	c.Assert(toSort, DeepEquals, expected)
+	s.Equal(expected, toSort)
 }
 
 type testObject struct {
@@ -143,42 +147,42 @@ func (s *DeltaSelectorSuite) createTestObjects() {
 	}
 }
 
-func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
+func (s *DeltaSelectorSuite) TestObjectsToPack() {
 	// Different type
 	hashes := []plumbing.Hash{s.hashes["base"], s.hashes["treeType"]}
 	deltaWindowSize := uint(10)
 	otp, err := s.ds.ObjectsToPack(hashes, deltaWindowSize)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, 2)
-	c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["base"]])
-	c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["treeType"]])
+	s.NoError(err)
+	s.Len(otp, 2)
+	s.Equal(s.store.Objects[s.hashes["base"]], otp[0].Object)
+	s.Equal(s.store.Objects[s.hashes["treeType"]], otp[1].Object)
 
 	// Size radically different
 	hashes = []plumbing.Hash{s.hashes["bigBase"], s.hashes["target"]}
 	otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, 2)
-	c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["bigBase"]])
-	c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["target"]])
+	s.NoError(err)
+	s.Len(otp, 2)
+	s.Equal(s.store.Objects[s.hashes["bigBase"]], otp[0].Object)
+	s.Equal(s.store.Objects[s.hashes["target"]], otp[1].Object)
 
 	// Delta Size Limit with no best delta yet
 	hashes = []plumbing.Hash{s.hashes["smallBase"], s.hashes["smallTarget"]}
 	otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, 2)
-	c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["smallBase"]])
-	c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["smallTarget"]])
+	s.NoError(err)
+	s.Len(otp, 2)
+	s.Equal(s.store.Objects[s.hashes["smallBase"]], otp[0].Object)
+	s.Equal(s.store.Objects[s.hashes["smallTarget"]], otp[1].Object)
 
 	// It will create the delta
 	hashes = []plumbing.Hash{s.hashes["base"], s.hashes["target"]}
 	otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, 2)
-	c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["target"]])
-	c.Assert(otp[0].IsDelta(), Equals, false)
-	c.Assert(otp[1].Original, Equals, s.store.Objects[s.hashes["base"]])
-	c.Assert(otp[1].IsDelta(), Equals, true)
-	c.Assert(otp[1].Depth, Equals, 1)
+	s.NoError(err)
+	s.Len(otp, 2)
+	s.Equal(s.store.Objects[s.hashes["target"]], otp[0].Object)
+	s.False(otp[0].IsDelta())
+	s.Equal(s.store.Objects[s.hashes["base"]], otp[1].Original)
+	s.True(otp[1].IsDelta())
+	s.Equal(1, otp[1].Depth)
 
 	// If our base is another delta, the depth will increase by one
 	hashes = []plumbing.Hash{
@@ -187,16 +191,16 @@ func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
 		s.hashes["o3"],
 	}
 	otp, err = s.ds.ObjectsToPack(hashes, deltaWindowSize)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, 3)
-	c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["o1"]])
-	c.Assert(otp[0].IsDelta(), Equals, false)
-	c.Assert(otp[1].Original, Equals, s.store.Objects[s.hashes["o2"]])
-	c.Assert(otp[1].IsDelta(), Equals, true)
-	c.Assert(otp[1].Depth, Equals, 1)
-	c.Assert(otp[2].Original, Equals, s.store.Objects[s.hashes["o3"]])
-	c.Assert(otp[2].IsDelta(), Equals, true)
-	c.Assert(otp[2].Depth, Equals, 2)
+	s.NoError(err)
+	s.Len(otp, 3)
+	s.Equal(s.store.Objects[s.hashes["o1"]], otp[0].Object)
+	s.False(otp[0].IsDelta())
+	s.Equal(s.store.Objects[s.hashes["o2"]], otp[1].Original)
+	s.True(otp[1].IsDelta())
+	s.Equal(1, otp[1].Depth)
+	s.Equal(s.store.Objects[s.hashes["o3"]], otp[2].Original)
+	s.True(otp[2].IsDelta())
+	s.Equal(2, otp[2].Depth)
 
 	// Check that objects outside of the sliding window don't produce
 	// a delta.
@@ -210,27 +214,27 @@ func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) {
 	// Don't sort so we can easily check the sliding window without
 	// creating a bunch of new objects.
 	otp, err = s.ds.objectsToPack(hashes, deltaWindowSize)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	err = s.ds.walk(otp, deltaWindowSize)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, int(deltaWindowSize)+2)
+	s.NoError(err)
+	s.Len(otp, int(deltaWindowSize)+2)
 	targetIdx := len(otp) - 1
-	c.Assert(otp[targetIdx].IsDelta(), Equals, false)
+	s.False(otp[targetIdx].IsDelta())
 
 	// Check that no deltas are created, and the objects are unsorted,
 	// if compression is off.
 	hashes = []plumbing.Hash{s.hashes["base"], s.hashes["target"]}
 	otp, err = s.ds.ObjectsToPack(hashes, 0)
-	c.Assert(err, IsNil)
-	c.Assert(len(otp), Equals, 2)
-	c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["base"]])
-	c.Assert(otp[0].IsDelta(), Equals, false)
-	c.Assert(otp[1].Original, Equals, s.store.Objects[s.hashes["target"]])
-	c.Assert(otp[1].IsDelta(), Equals, false)
-	c.Assert(otp[1].Depth, Equals, 0)
+	s.NoError(err)
+	s.Len(otp, 2)
+	s.Equal(s.store.Objects[s.hashes["base"]], otp[0].Object)
+	s.False(otp[0].IsDelta())
+	s.Equal(s.store.Objects[s.hashes["target"]], otp[1].Original)
+	s.False(otp[1].IsDelta())
+	s.Equal(0, otp[1].Depth)
 }
 
-func (s *DeltaSelectorSuite) TestMaxDepth(c *C) {
+func (s *DeltaSelectorSuite) TestMaxDepth() {
 	dsl := s.ds.deltaSizeLimit(0, 0, int(maxDepth), true)
-	c.Assert(dsl, Equals, int64(0))
+	s.Equal(int64(0), dsl)
 }
diff --git a/plumbing/format/packfile/delta_test.go b/plumbing/format/packfile/delta_test.go
index 848a7730..d1a37ef3 100644
--- a/plumbing/format/packfile/delta_test.go
+++ b/plumbing/format/packfile/delta_test.go
@@ -7,14 +7,17 @@ import (
 	"testing"
 
 	"github.com/go-git/go-git/v5/plumbing"
-	. "gopkg.in/check.v1"
+	"github.com/stretchr/testify/suite"
 )
 
 type DeltaSuite struct {
+	suite.Suite
 	testCases []deltaTest
 }
 
-var _ = Suite(&DeltaSuite{})
+func TestDeltaSuite(t *testing.T) {
+	suite.Run(t, new(DeltaSuite))
+}
 
 type deltaTest struct {
 	description string
@@ -22,7 +25,7 @@ type deltaTest struct {
 	target      []piece
 }
 
-func (s *DeltaSuite) SetUpSuite(c *C) {
+func (s *DeltaSuite) SetupSuite() {
 	s.testCases = []deltaTest{{
 		description: "distinct file",
 		base:        []piece{{"0", 300}},
@@ -88,20 +91,20 @@ func randStringBytes(n int) string {
 	return string(randBytes(n))
 }
 
-func (s *DeltaSuite) TestAddDelta(c *C) {
+func (s *DeltaSuite) TestAddDelta() {
 	for _, t := range s.testCases {
 		baseBuf := genBytes(t.base)
 		targetBuf := genBytes(t.target)
 		delta := DiffDelta(baseBuf, targetBuf)
 		result, err := PatchDelta(baseBuf, delta)
 
-		c.Log("Executing test case:", t.description)
-		c.Assert(err, IsNil)
-		c.Assert(result, DeepEquals, targetBuf)
+		s.T().Log("Executing test case:", t.description)
+		s.NoError(err)
+		s.Equal(targetBuf, result)
 	}
 }
 
-func (s *DeltaSuite) TestAddDeltaReader(c *C) {
+func (s *DeltaSuite) TestAddDeltaReader() {
 	for _, t := range s.testCases {
 		baseBuf := genBytes(t.base)
 		baseObj := &plumbing.MemoryObject{}
@@ -112,51 +115,51 @@ func (s *DeltaSuite) TestAddDeltaReader(c *C) {
 		delta := DiffDelta(baseBuf, targetBuf)
 		deltaRC := io.NopCloser(bytes.NewReader(delta))
 
-		c.Log("Executing test case:", t.description)
+		s.T().Log("Executing test case:", t.description)
 
 		resultRC, err := ReaderFromDelta(baseObj, deltaRC)
-		c.Assert(err, IsNil)
+		s.NoError(err)
 
 		result, err := io.ReadAll(resultRC)
-		c.Assert(err, IsNil)
+		s.NoError(err)
 
 		err = resultRC.Close()
-		c.Assert(err, IsNil)
+		s.NoError(err)
 
-		c.Assert(result, DeepEquals, targetBuf)
+		s.Equal(targetBuf, result)
 	}
 }
 
-func (s *DeltaSuite) TestIncompleteDelta(c *C) {
+func (s *DeltaSuite) TestIncompleteDelta() {
 	for _, t := range s.testCases {
-		c.Log("Incomplete delta on:", t.description)
+		s.T().Log("Incomplete delta on:", t.description)
 		baseBuf := genBytes(t.base)
 		targetBuf := genBytes(t.target)
 		delta := DiffDelta(baseBuf, targetBuf)
 		delta = delta[:len(delta)-2]
 		result, err := PatchDelta(baseBuf, delta)
-		c.Assert(err, NotNil)
-		c.Assert(result, IsNil)
+		s.NotNil(err)
+		s.Nil(result)
 	}
 
 	// check nil input too
 	result, err := PatchDelta(nil, nil)
-	c.Assert(err, NotNil)
-	c.Assert(result, IsNil)
+	s.NotNil(err)
+	s.Nil(result)
 }
 
-func (s *DeltaSuite) TestMaxCopySizeDelta(c *C) {
+func (s *DeltaSuite) TestMaxCopySizeDelta() {
 	baseBuf := randBytes(maxCopySize)
 	targetBuf := baseBuf[0:]
 	targetBuf = append(targetBuf, byte(1))
 
 	delta := DiffDelta(baseBuf, targetBuf)
 	result, err := PatchDelta(baseBuf, delta)
-	c.Assert(err, IsNil)
-	c.Assert(result, DeepEquals, targetBuf)
+	s.NoError(err)
+	s.Equal(targetBuf, result)
 }
 
-func (s *DeltaSuite) TestMaxCopySizeDeltaReader(c *C) {
+func (s *DeltaSuite) TestMaxCopySizeDeltaReader() {
 	baseBuf := randBytes(maxCopySize)
 	baseObj := &plumbing.MemoryObject{}
 	baseObj.Write(baseBuf)
@@ -168,14 +171,14 @@ func (s *DeltaSuite) TestMaxCopySizeDeltaReader(c *C) {
 	deltaRC := io.NopCloser(bytes.NewReader(delta))
 
 	resultRC, err := ReaderFromDelta(baseObj, deltaRC)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	result, err := io.ReadAll(resultRC)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	err = resultRC.Close()
-	c.Assert(err, IsNil)
-	c.Assert(result, DeepEquals, targetBuf)
+	s.NoError(err)
+	s.Equal(targetBuf, result)
 }
 
 func FuzzPatchDelta(f *testing.F) {
diff --git a/plumbing/format/packfile/encoder_advanced_test.go b/plumbing/format/packfile/encoder_advanced_test.go
index 6498cf42..8d22de3d 100644
--- a/plumbing/format/packfile/encoder_advanced_test.go
+++ b/plumbing/format/packfile/encoder_advanced_test.go
@@ -12,53 +12,61 @@ import (
 	. "github.com/go-git/go-git/v5/plumbing/format/packfile"
 	"github.com/go-git/go-git/v5/plumbing/storer"
 	"github.com/go-git/go-git/v5/storage/filesystem"
+	"github.com/stretchr/testify/suite"
 
 	"github.com/go-git/go-billy/v5/memfs"
 	fixtures "github.com/go-git/go-git-fixtures/v4"
-	. "gopkg.in/check.v1"
 )
 
-type EncoderAdvancedSuite struct {
+type EncoderAdvancedFixtureSuite struct {
 	fixtures.Suite
 }
 
-var _ = Suite(&EncoderAdvancedSuite{})
+type EncoderAdvancedSuite struct {
+	suite.Suite
+	EncoderAdvancedFixtureSuite
+}
+
+func TestEncoderAdvancedSuite(t *testing.T) {
+	suite.Run(t, new(EncoderAdvancedSuite))
+}
 
-func (s *EncoderAdvancedSuite) TestEncodeDecode(c *C) {
+func (s *EncoderAdvancedSuite) TestEncodeDecode() {
 	if testing.Short() {
-		c.Skip("skipping test in short mode.")
+		s.T().Skip("skipping test in short mode.")
 	}
 
 	fixs := fixtures.Basic().ByTag("packfile").ByTag(".git")
 	fixs = append(fixs, fixtures.ByURL("https://github.com/src-d/go-git.git").
 		ByTag("packfile").ByTag(".git").One())
-	fixs.Test(c, func(f *fixtures.Fixture) {
+
+	for _, f := range fixs {
 		storage := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
-		s.testEncodeDecode(c, storage, 10)
-	})
+		s.testEncodeDecode(storage, 10)
+	}
 }
 
-func (s *EncoderAdvancedSuite) TestEncodeDecodeNoDeltaCompression(c *C) {
+func (s *EncoderAdvancedSuite) TestEncodeDecodeNoDeltaCompression() {
 	if testing.Short() {
-		c.Skip("skipping test in short mode.")
+		s.T().Skip("skipping test in short mode.")
 	}
 
 	fixs := fixtures.Basic().ByTag("packfile").ByTag(".git")
 	fixs = append(fixs, fixtures.ByURL("https://github.com/src-d/go-git.git").
 		ByTag("packfile").ByTag(".git").One())
-	fixs.Test(c, func(f *fixtures.Fixture) {
+
+	for _, f := range fixs {
 		storage := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
-		s.testEncodeDecode(c, storage, 0)
-	})
+		s.testEncodeDecode(storage, 0)
+	}
 }
 
 func (s *EncoderAdvancedSuite) testEncodeDecode(
-	c *C,
 	storage storer.Storer,
 	packWindow uint,
 ) {
 	objIter, err := storage.IterEncodedObjects(plumbing.AnyObject)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	expectedObjects := map[plumbing.Hash]bool{}
 	var hashes []plumbing.Hash
@@ -68,7 +76,7 @@ func (s *EncoderAdvancedSuite) testEncodeDecode(
 		return err
 
 	})
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	// Shuffle hashes to avoid delta selector getting order right just because
 	// the initial order is correct.
@@ -81,54 +89,54 @@ func (s *EncoderAdvancedSuite) testEncodeDecode(
 	buf := bytes.NewBuffer(nil)
 	enc := NewEncoder(buf, storage, false)
 	encodeHash, err := enc.Encode(hashes, packWindow)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	fs := memfs.New()
 	f, err := fs.Create("packfile")
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	_, err = f.Write(buf.Bytes())
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	_, err = f.Seek(0, io.SeekStart)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	w := new(idxfile.Writer)
 	parser := NewParser(NewScanner(f), WithScannerObservers(w))
 
 	_, err = parser.Parse()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	index, err := w.Index()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	_, err = f.Seek(0, io.SeekStart)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	p := NewPackfile(f, WithIdx(index), WithFs(fs))
 
 	decodeHash, err := p.ID()
-	c.Assert(err, IsNil)
-	c.Assert(encodeHash, Equals, decodeHash)
+	s.NoError(err)
+	s.Equal(decodeHash, encodeHash)
 
 	objIter, err = p.GetAll()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	obtainedObjects := map[plumbing.Hash]bool{}
 	err = objIter.ForEach(func(o plumbing.EncodedObject) error {
 		obtainedObjects[o.Hash()] = true
 		return nil
 	})
-	c.Assert(err, IsNil)
-	c.Assert(obtainedObjects, DeepEquals, expectedObjects)
+	s.NoError(err)
+	s.Equal(expectedObjects, obtainedObjects)
 
 	for h := range obtainedObjects {
 		if !expectedObjects[h] {
-			c.Errorf("obtained unexpected object: %s", h)
+			s.T().Errorf("obtained unexpected object: %s", h)
 		}
 	}
 
 	for h := range expectedObjects {
 		if !obtainedObjects[h] {
-			c.Errorf("missing object: %s", h)
+			s.T().Errorf("missing object: %s", h)
 		}
 	}
 }
diff --git a/plumbing/format/packfile/encoder_test.go b/plumbing/format/packfile/encoder_test.go
index 19383538..67484601 100644
--- a/plumbing/format/packfile/encoder_test.go
+++ b/plumbing/format/packfile/encoder_test.go
@@ -3,56 +3,61 @@ package packfile
 import (
 	"bytes"
 	"io"
+	"testing"
 
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/format/idxfile"
-	"github.com/go-git/go-git/v5/plumbing/hash"
 	"github.com/go-git/go-git/v5/storage/memory"
+	"github.com/stretchr/testify/suite"
 
 	"github.com/go-git/go-billy/v5/memfs"
 	fixtures "github.com/go-git/go-git-fixtures/v4"
-	. "gopkg.in/check.v1"
 )
 
-type EncoderSuite struct {
+type EncoderFixtureSuite struct {
 	fixtures.Suite
+}
+
+type EncoderSuite struct {
+	suite.Suite
+	EncoderFixtureSuite
 	buf   *bytes.Buffer
 	store *memory.Storage
 	enc   *Encoder
 }
 
-var _ = Suite(&EncoderSuite{})
+func TestEncoderSuite(t *testing.T) {
+	suite.Run(t, new(EncoderSuite))
+}
 
-func (s *EncoderSuite) SetUpTest(c *C) {
+func (s *EncoderSuite) SetupTest() {
 	s.buf = bytes.NewBuffer(nil)
 	s.store = memory.NewStorage()
 	s.enc = NewEncoder(s.buf, s.store, false)
 }
 
-func (s *EncoderSuite) TestCorrectPackHeader(c *C) {
+func (s *EncoderSuite) TestCorrectPackHeader() {
 	h, err := s.enc.Encode([]plumbing.Hash{}, 10)
-	c.Assert(err, IsNil)
-
-	hb := [hash.Size]byte(h)
+	s.NoError(err)
 
 	// PACK + VERSION + OBJECTS + HASH
 	expectedResult := []byte{'P', 'A', 'C', 'K', 0, 0, 0, 2, 0, 0, 0, 0}
-	expectedResult = append(expectedResult, hb[:]...)
+	expectedResult = append(expectedResult, h.Bytes()...)
 
 	result := s.buf.Bytes()
 
-	c.Assert(result, DeepEquals, expectedResult)
+	s.Equal(expectedResult, result)
 }
 
-func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject(c *C) {
+func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject() {
 	o := &plumbing.MemoryObject{}
 	o.SetType(plumbing.CommitObject)
 	o.SetSize(0)
 	_, err := s.store.SetEncodedObject(o)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	h, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, 10)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	// PACK + VERSION(2) + OBJECT NUMBER(1)
 	expectedResult := []byte{'P', 'A', 'C', 'K', 0, 0, 0, 2, 0, 0, 0, 1}
@@ -64,104 +69,103 @@ func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject(c *C) {
 		[]byte{120, 156, 1, 0, 0, 255, 255, 0, 0, 0, 1}...)
 
 	// + HASH
-	hb := [hash.Size]byte(h)
-	expectedResult = append(expectedResult, hb[:]...)
+	expectedResult = append(expectedResult, h.Bytes()...)
 
 	result := s.buf.Bytes()
 
-	c.Assert(result, DeepEquals, expectedResult)
+	s.Equal(expectedResult, result)
 }
 
-func (s *EncoderSuite) TestMaxObjectSize(c *C) {
+func (s *EncoderSuite) TestMaxObjectSize() {
 	o := s.store.NewEncodedObject()
 	o.SetSize(9223372036854775807)
 	o.SetType(plumbing.CommitObject)
 	_, err := s.store.SetEncodedObject(o)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, 10)
-	c.Assert(err, IsNil)
-	c.Assert(hash.IsZero(), Not(Equals), true)
+	s.NoError(err)
+	s.NotEqual(true, hash.IsZero())
 }
 
-func (s *EncoderSuite) TestHashNotFound(c *C) {
+func (s *EncoderSuite) TestHashNotFound() {
 	h, err := s.enc.Encode([]plumbing.Hash{plumbing.NewHash("BAD")}, 10)
-	c.Assert(h, Equals, plumbing.ZeroHash)
-	c.Assert(err, NotNil)
-	c.Assert(err, Equals, plumbing.ErrObjectNotFound)
+	s.Equal(plumbing.ZeroHash, h)
+	s.NotNil(err)
+	s.ErrorIs(err, plumbing.ErrObjectNotFound)
 }
 
-func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeREF(c *C) {
+func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeREF() {
 	s.enc = NewEncoder(s.buf, s.store, true)
-	s.simpleDeltaTest(c)
+	s.simpleDeltaTest()
 }
 
-func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeOFS(c *C) {
+func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeOFS() {
 	s.enc = NewEncoder(s.buf, s.store, false)
-	s.simpleDeltaTest(c)
+	s.simpleDeltaTest()
 }
 
-func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeREF(c *C) {
+func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeREF() {
 	s.enc = NewEncoder(s.buf, s.store, true)
-	s.deltaOverDeltaTest(c)
+	s.deltaOverDeltaTest()
 }
 
-func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeOFS(c *C) {
+func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeOFS() {
 	s.enc = NewEncoder(s.buf, s.store, false)
-	s.deltaOverDeltaTest(c)
+	s.deltaOverDeltaTest()
 }
 
-func (s *EncoderSuite) TestDecodeEncodeWithCycleREF(c *C) {
+func (s *EncoderSuite) TestDecodeEncodeWithCycleREF() {
 	s.enc = NewEncoder(s.buf, s.store, true)
-	s.deltaOverDeltaCyclicTest(c)
+	s.deltaOverDeltaCyclicTest()
 }
 
-func (s *EncoderSuite) TestDecodeEncodeWithCycleOFS(c *C) {
+func (s *EncoderSuite) TestDecodeEncodeWithCycleOFS() {
 	s.enc = NewEncoder(s.buf, s.store, false)
-	s.deltaOverDeltaCyclicTest(c)
+	s.deltaOverDeltaCyclicTest()
 }
 
-func (s *EncoderSuite) simpleDeltaTest(c *C) {
+func (s *EncoderSuite) simpleDeltaTest() {
 	srcObject := newObject(plumbing.BlobObject, []byte("0"))
 	targetObject := newObject(plumbing.BlobObject, []byte("01"))
 
 	deltaObject, err := GetDelta(srcObject, targetObject)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	srcToPack := newObjectToPack(srcObject)
 	encHash, err := s.enc.encode([]*ObjectToPack{
 		srcToPack,
 		newDeltaObjectToPack(srcToPack, targetObject, deltaObject),
 	})
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	p, cleanup := packfileFromReader(c, s.buf)
+	p, cleanup := packfileFromReader(s, s.buf)
 	defer cleanup()
 	decHash, err := p.ID()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	c.Assert(encHash, Equals, decHash)
+	s.Equal(decHash, encHash)
 
 	decSrc, err := p.Get(srcObject.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decSrc, srcObject)
+	s.NoError(err)
+	objectsEqual(s, decSrc, srcObject)
 
 	decTarget, err := p.Get(targetObject.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decTarget, targetObject)
+	s.NoError(err)
+	objectsEqual(s, decTarget, targetObject)
 }
 
-func (s *EncoderSuite) deltaOverDeltaTest(c *C) {
+func (s *EncoderSuite) deltaOverDeltaTest() {
 	srcObject := newObject(plumbing.BlobObject, []byte("0"))
 	targetObject := newObject(plumbing.BlobObject, []byte("01"))
 	otherTargetObject := newObject(plumbing.BlobObject, []byte("011111"))
 
 	deltaObject, err := GetDelta(srcObject, targetObject)
-	c.Assert(err, IsNil)
-	c.Assert(deltaObject.Hash(), Not(Equals), plumbing.ZeroHash)
+	s.NoError(err)
+	s.NotEqual(plumbing.ZeroHash, deltaObject.Hash())
 
 	otherDeltaObject, err := GetDelta(targetObject, otherTargetObject)
-	c.Assert(err, IsNil)
-	c.Assert(otherDeltaObject.Hash(), Not(Equals), plumbing.ZeroHash)
+	s.NoError(err)
+	s.NotEqual(plumbing.ZeroHash, otherDeltaObject.Hash())
 
 	srcToPack := newObjectToPack(srcObject)
 	targetToPack := newObjectToPack(targetObject)
@@ -171,51 +175,51 @@ func (s *EncoderSuite) deltaOverDeltaTest(c *C) {
 		newDeltaObjectToPack(srcToPack, targetObject, deltaObject),
 		newDeltaObjectToPack(targetToPack, otherTargetObject, otherDeltaObject),
 	})
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	p, cleanup := packfileFromReader(c, s.buf)
+	p, cleanup := packfileFromReader(s, s.buf)
 	defer cleanup()
 	decHash, err := p.ID()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	c.Assert(encHash, Equals, decHash)
+	s.Equal(decHash, encHash)
 
 	decSrc, err := p.Get(srcObject.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decSrc, srcObject)
+	s.NoError(err)
+	objectsEqual(s, decSrc, srcObject)
 
 	decTarget, err := p.Get(targetObject.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decTarget, targetObject)
+	s.NoError(err)
+	objectsEqual(s, decTarget, targetObject)
 
 	decOtherTarget, err := p.Get(otherTargetObject.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decOtherTarget, otherTargetObject)
+	s.NoError(err)
+	objectsEqual(s, decOtherTarget, otherTargetObject)
 }
 
-func (s *EncoderSuite) deltaOverDeltaCyclicTest(c *C) {
+func (s *EncoderSuite) deltaOverDeltaCyclicTest() {
 	o1 := newObject(plumbing.BlobObject, []byte("0"))
 	o2 := newObject(plumbing.BlobObject, []byte("01"))
 	o3 := newObject(plumbing.BlobObject, []byte("011111"))
 	o4 := newObject(plumbing.BlobObject, []byte("01111100000"))
 
 	_, err := s.store.SetEncodedObject(o1)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	_, err = s.store.SetEncodedObject(o2)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	_, err = s.store.SetEncodedObject(o3)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 	_, err = s.store.SetEncodedObject(o4)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	d2, err := GetDelta(o1, o2)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	d3, err := GetDelta(o4, o3)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	d4, err := GetDelta(o3, o4)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	po1 := newObjectToPack(o1)
 	pd2 := newDeltaObjectToPack(po1, o2, d2)
@@ -243,68 +247,68 @@ func (s *EncoderSuite) deltaOverDeltaCyclicTest(c *C) {
 		pd3,
 		pd4,
 	})
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	p, cleanup := packfileFromReader(c, s.buf)
+	p, cleanup := packfileFromReader(s, s.buf)
 	defer cleanup()
 	decHash, err := p.ID()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	c.Assert(encHash, Equals, decHash)
+	s.Equal(decHash, encHash)
 
 	decSrc, err := p.Get(o1.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decSrc, o1)
+	s.NoError(err)
+	objectsEqual(s, decSrc, o1)
 
 	decTarget, err := p.Get(o2.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decTarget, o2)
+	s.NoError(err)
+	objectsEqual(s, decTarget, o2)
 
 	decOtherTarget, err := p.Get(o3.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decOtherTarget, o3)
+	s.NoError(err)
+	objectsEqual(s, decOtherTarget, o3)
 
 	decAnotherTarget, err := p.Get(o4.Hash())
-	c.Assert(err, IsNil)
-	objectsEqual(c, decAnotherTarget, o4)
+	s.NoError(err)
+	objectsEqual(s, decAnotherTarget, o4)
 }
 
-func objectsEqual(c *C, o1, o2 plumbing.EncodedObject) {
-	c.Assert(o1.Type(), Equals, o2.Type())
-	c.Assert(o1.Hash(), Equals, o2.Hash())
-	c.Assert(o1.Size(), Equals, o2.Size())
+func objectsEqual(s *EncoderSuite, o1, o2 plumbing.EncodedObject) {
+	s.Equal(o2.Type(), o1.Type())
+	s.Equal(o2.Hash(), o1.Hash())
+	s.Equal(o2.Size(), o1.Size())
 
 	r1, err := o1.Reader()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	b1, err := io.ReadAll(r1)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	r2, err := o2.Reader()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	b2, err := io.ReadAll(r2)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
-	c.Assert(bytes.Compare(b1, b2), Equals, 0)
+	s.Equal(0, bytes.Compare(b1, b2))
 
 	err = r2.Close()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	err = r1.Close()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 }
 
-func packfileFromReader(c *C, buf *bytes.Buffer) (*Packfile, func()) {
+func packfileFromReader(s *EncoderSuite, buf *bytes.Buffer) (*Packfile, func()) {
 	fs := memfs.New()
 	file, err := fs.Create("packfile")
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	_, err = file.Write(buf.Bytes())
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	_, err = file.Seek(0, io.SeekStart)
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	scanner := NewScanner(file)
 
@@ -312,12 +316,15 @@ func packfileFromReader(c *C, buf *bytes.Buffer) (*Packfile, func()) {
 	p := NewParser(scanner, WithScannerObservers(w))
 
 	_, err = p.Parse()
-	c.Assert(err, IsNil)
+	s.NoError(err)
 
 	index, err := w.Index()
-	c.Assert(err, IsNil)
+	s.NoError(err)
+
+	_, err = file.Seek(0, io.SeekStart)
+	s.NoError(err)
 
 	return NewPackfile(file, WithIdx(index), WithFs(fs)), func() {
-		c.Assert(file.Close(), IsNil)
+		s.NoError(file.Close())
 	}
 }
diff --git a/plumbing/format/packfile/object_pack_test.go b/plumbing/format/packfile/object_pack_test.go
index dc1a285a..6932c667 100644
--- a/plumbing/format/packfile/object_pack_test.go
+++ b/plumbing/format/packfile/object_pack_test.go
@@ -2,31 +2,35 @@ package packfile
 
 import (
 	"io"
+	"testing"
 
 	"github.com/go-git/go-git/v5/plumbing"
-
-	. "gopkg.in/check.v1"
+	"github.com/stretchr/testify/suite"
 )
 
-type ObjectToPackSuite struct{}
+type ObjectToPackSuite struct {
+	suite.Suite
+}
 
-var _ = Suite(&ObjectToPackSuite{})
+func TestObjectToPackSuite(t *testing.T) {
+	suite.Run(t, new(ObjectToPackSuite))
+}
 
-func (s *ObjectToPackSuite) TestObjectToPack(c *C) {
+func (s *ObjectToPackSuite) TestObjectToPack() {
 	obj := &dummyObject{}
 	otp := newObjectToPack(obj)
-	c.Assert(obj, Equals, otp.Object)
-	c.Assert(obj, Equals, otp.Original)
-	c.Assert(otp.Base, IsNil)
-	c.Assert(otp.IsDelta(), Equals, false)
+	s.Equal(otp.Object, obj)
+	s.Equal(otp.Original, obj)
+	s.Nil(otp.Base)
+	s.False(otp.IsDelta())
 
 	original := &dummyObject{}
 	delta := &dummyObject{}
 	deltaToPack := newDeltaObjectToPack(otp, original, delta)
-	c.Assert(obj, Equals, deltaToPack.Object)
-	c.Assert(original, Equals, deltaToPack.Original)
-	c.Assert(otp, Equals, deltaToPack.Base)
-	c.Assert(deltaToPack.IsDelta(), Equals, true)
+	s.Equal(deltaToPack.Object, obj)
+	s.Equal(deltaToPack.Original, original)
+	s.Equal(deltaToPack.Base, otp)
+	s.True(deltaToPack.IsDelta())
 }
 
 type dummyObject struct{}
diff --git a/plumbing/hash/new_test.go b/plumbing/hash/new_test.go
new file mode 100644
index 00000000..9ef2d0e6
--- /dev/null
+++ b/plumbing/hash/new_test.go
@@ -0,0 +1,128 @@
+package hash_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/go-git/go-git/v5/plumbing"
+	. "github.com/go-git/go-git/v5/plumbing/hash"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFromHex(t *testing.T) {
+	tests := []struct {
+		name  string
+		in    string
+		ok    bool
+		empty bool
+	}{
+		{"valid sha1", "8ab686eafeb1f44702738c8b0f24f2567c36da6d", true, false},
+		{"valid sha256", "edeaaff3f1774ad2888673770c6d64097e391bc362d7d6fb34982ddf0efd18cb", true, false},
+		{"empty sha1", "0000000000000000000000000000000000000000", true, true},
+		{"empty sha256", "0000000000000000000000000000000000000000000000000000000000000000", true, true},
+		{"partial sha1", "8ab686eafeb1f44702738", false, true},
+		{"partial sha256", "edeaaff3f1774ad28886", false, true},
+		{"invalid sha1", "8ab686eafeb1f44702738x", false, true},
+		{"invalid sha256", "edeaaff3f1774ad28886x", false, true},
+	}
+
+	for _, tc := range tests {
+		t.Run(fmt.Sprintf("%s:%q", tc.name, tc.in), func(t *testing.T) {
+			h, ok := FromHex(tc.in)
+
+			assert.Equal(t, tc.ok, ok, "OK did not match")
+			if tc.ok {
+				assert.Equal(t, tc.empty, h.IsZero(), "Empty did not match expectations")
+			} else {
+				assert.Nil(t, h)
+			}
+		})
+	}
+}
+
+func TestNotLeakingBackingArray(t *testing.T) {
+	tests := []struct {
+		in  string
+		sum []byte
+	}{
+		{
+			"9f361d484fcebb869e1919dc7467b82ac6ca5fad",
+			[]byte{
+				0x9f, 0x36, 0x1d, 0x48, 0x4f, 0xce, 0xbb, 0x86, 0x9e, 0x19,
+				0x19, 0xdc, 0x74, 0x67, 0xb8, 0x2a, 0xc6, 0xca, 0x5f, 0xad,
+			},
+		},
+		{
+			"2c07a4773e3a957c77810e8cc5deb52cd70493803c048e48dcc0e01f94cbe677",
+			[]byte{
+				0x2c, 0x07, 0xa4, 0x77, 0x3e, 0x3a, 0x95, 0x7c, 0x77, 0x81,
+				0x0e, 0x8c, 0xc5, 0xde, 0xb5, 0x2c, 0xd7, 0x04, 0x93, 0x80,
+				0x3c, 0x04, 0x8e, 0x48, 0xdc, 0xc0, 0xe0, 0x1f, 0x94, 0xcb,
+				0xe6, 0x77,
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		h, ok := FromHex(tc.in)
+		assert.True(t, ok)
+		assert.Equal(t, tc.in, h.String())
+		assert.Equal(t, tc.sum, h.Bytes())
+
+		sum := h.Bytes()
+		for i := range sum {
+			sum[i] = 0
+		}
+		assert.Equal(t, tc.sum, h.Bytes())
+	}
+}
+
+func BenchmarkHashFromHex(b *testing.B) {
+	tests := []struct {
+		name   string
+		sha1   string
+		sha256 string
+	}{
+		{
+			name:   "valid",
+			sha1:   "9f361d484fcebb869e1919dc7467b82ac6ca5fad",
+			sha256: "2c07a4773e3a957c77810e8cc5deb52cd70493803c048e48dcc0e01f94cbe677",
+		},
+		{
+			name:   "invalid",
+			sha1:   "9f361d484fcebb869e1919dc7467b82ac6ca5fxf",
+			sha256: "2c07a4773e3a957c77810e8cc5deb52cd70493803c048e48dcc0e01f94cbe6xd",
+		},
+		{
+			name:   "zero",
+			sha1:   "0000000000000000000000000000000000000000",
+			sha256: "0000000000000000000000000000000000000000000000000000000000000000",
+		},
+	}
+
+	for _, tc := range tests {
+		b.Run(fmt.Sprintf("hasher-parse-sha1-%s", tc.name), func(b *testing.B) {
+			benchmarkHashParse(b, tc.sha1)
+		})
+		b.Run(fmt.Sprintf("objecthash-fromhex-sha1-%s", tc.name), func(b *testing.B) {
+			benchmarkObjectHashParse(b, tc.sha1)
+		})
+		b.Run(fmt.Sprintf("objecthash-fromhex-sha256-%s", tc.name), func(b *testing.B) {
+			benchmarkObjectHashParse(b, tc.sha256)
+		})
+	}
+}
+
+func benchmarkHashParse(b *testing.B, in string) {
+	for i := 0; i < b.N; i++ {
+		_ = plumbing.NewHash(in)
+		b.SetBytes(int64(len(in)))
+	}
+}
+
+func benchmarkObjectHashParse(b *testing.B, in string) {
+	for i := 0; i < b.N; i++ {
+		_, _ = FromHex(in)
+		b.SetBytes(int64(len(in)))
+	}
+}
diff --git a/plumbing/hash_test.go b/plumbing/hash_test.go
index 243c50ca..f10fe74c 100644
--- a/plumbing/hash_test.go
+++ b/plumbing/hash_test.go
@@ -54,9 +54,3 @@ func (s *HashSuite) TestHashesSort() {
 	s.Equal(NewHash("1111111111111111111111111111111111111111"), i[0])
 	s.Equal(NewHash("2222222222222222222222222222222222222222"), i[1])
 }
-
-func (s *HashSuite) TestIsHash() {
-	s.True(IsHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d"))
-	s.False(IsHash("foo"))
-	s.False(IsHash("zab686eafeb1f44702738c8b0f24f2567c36da6d"))
-}
diff --git a/plumbing/hasher/hasher_test.go b/plumbing/hasher/hasher_test.go
new file mode 100644
index 00000000..a0fd28ae
--- /dev/null
+++ b/plumbing/hasher/hasher_test.go
@@ -0,0 +1,217 @@
+package hasher_test
+
+import (
+	"bytes"
+	"crypto"
+	"fmt"
+	"hash"
+	"strings"
+	"sync"
+	"testing"
+
+	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/format/config"
+	. "github.com/go-git/go-git/v5/plumbing/hasher"
+	"github.com/pjbgf/sha1cd"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestHasher(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name    string
+		h       hash.Hash
+		ot      plumbing.ObjectType
+		content []byte
+		want    string
+	}{
+		{
+			"blob object sha1", crypto.SHA1.New(),
+			plumbing.BlobObject,
+			[]byte("hash object sample"),
+			"9f361d484fcebb869e1919dc7467b82ac6ca5fad",
+		},
+		{
+			"blob object sha256", crypto.SHA256.New(),
+			plumbing.BlobObject,
+			[]byte("hash object sample"),
+			"2c07a4773e3a957c77810e8cc5deb52cd70493803c048e48dcc0e01f94cbe677",
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(fmt.Sprintf("%s:%q", tc.name, ""), func(t *testing.T) {
+			oh, err := FromHash(tc.h)
+			assert.NoError(t, err)
+
+			h, err := oh.Compute(tc.ot, tc.content)
+			assert.NoError(t, err)
+			assert.Equal(t, tc.want, h.String())
+		})
+	}
+}
+
+func TestMultipleHashes(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name     string
+		h        hash.Hash
+		ot       plumbing.ObjectType
+		content1 []byte
+		content2 []byte
+		want1    string
+		want2    string
+	}{
+		{
+			"reuse sha1 hasher instance for two ops", crypto.SHA1.New(),
+			plumbing.BlobObject,
+			[]byte("hash object sample"),
+			[]byte("other object content"),
+			"9f361d484fcebb869e1919dc7467b82ac6ca5fad",
+			"e8bb453830a9efdfe4785275b92eb0766da3a73d",
+		},
+		{
+			"reuse sha256 hasher instance for two ops", crypto.SHA256.New(),
+			plumbing.BlobObject,
+			[]byte("hash object sample"),
+			[]byte("other object content"),
+			"2c07a4773e3a957c77810e8cc5deb52cd70493803c048e48dcc0e01f94cbe677",
+			"2f1eb67dc531a48962e741b61e88ef94cf70969bc6442a91cdcad7f5192e8c1d",
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(fmt.Sprintf("%s:%q", tc.name, ""), func(t *testing.T) {
+			oh, err := FromHash(tc.h)
+			assert.NoError(t, err)
+
+			h, err := oh.Compute(tc.ot, tc.content1)
+			assert.NoError(t, err)
+			assert.Equal(t, tc.want1, h.String())
+
+			h, err = oh.Compute(tc.ot, tc.content2)
+			assert.NoError(t, err)
+			assert.Equal(t, tc.want2, h.String())
+		})
+	}
+}
+
+func TestThreadSatefy(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name    string
+		h       hash.Hash
+		ot      plumbing.ObjectType
+		content []byte
+		count   int
+		want    string
+	}{
+		{
+			"thread safety sha1", crypto.SHA1.New(),
+			plumbing.BlobObject,
+			bytes.Repeat([]byte{2}, 500),
+			20,
+			"147979c263be42345f0721a22c5339492aadd0bf",
+		},
+		{
+			"thread safety sha256", crypto.SHA256.New(),
+			plumbing.BlobObject,
+			bytes.Repeat([]byte{2}, 500),
+			20,
+			"43196946e1d64387caaac746132f22c2be6f9a16914dad0231b479e16b9c3a01",
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			oh, err := FromHash(tc.h)
+			assert.NoError(t, err)
+
+			var wg sync.WaitGroup
+			for i := 0; i < tc.count; i++ {
+				wg.Add(1)
+				go func() {
+					h, err := oh.Compute(tc.ot, tc.content)
+					assert.NoError(t, err)
+
+					got := h.String()
+					assert.Equal(t, tc.want, got, "resulting hash impacted by race condition")
+					wg.Done()
+				}()
+			}
+			wg.Wait()
+		})
+	}
+}
+
+func BenchmarkHasher(b *testing.B) {
+	qtds := []int64{100, 5000}
+
+	for _, q := range qtds {
+		b.Run(fmt.Sprintf("hasher-sha1-%dB", q), func(b *testing.B) {
+			benchmarkHasher(b, q)
+		})
+		b.Run(fmt.Sprintf("objecthash-sha1-%dB", q), func(b *testing.B) {
+			benchmarkObjectHash(b, sha1cd.New(), q)
+		})
+		b.Run(fmt.Sprintf("objecthash-sha256-%dB", q), func(b *testing.B) {
+			benchmarkObjectHash(b, crypto.SHA256.New(), q)
+		})
+	}
+}
+
+func benchmarkHasher(b *testing.B, sz int64) {
+	content := strings.Repeat("s", int(sz))
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		h := plumbing.NewHasher(plumbing.BlobObject, sz)
+		_, _ = h.Write([]byte(content))
+		b.SetBytes(sz)
+	}
+}
+
+func benchmarkObjectHash(b *testing.B, h hash.Hash, sz int64) {
+	content := bytes.Repeat([]byte("s"), int(sz))
+	oh, err := FromHash(h)
+	assert.NoError(b, err)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = oh.Compute(plumbing.BlobObject, content)
+		b.SetBytes(sz)
+	}
+}
+
+func BenchmarkCompute(b *testing.B) {
+	content := []byte(strings.Repeat("s", 5000))
+
+	b.Run("plumbing.compute-sha1", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			b.SetBytes(int64(len(content)))
+			plumbing.ComputeHash(plumbing.AnyObject, content)
+		}
+	})
+
+	oh, err := FromObjectFormat(config.SHA1)
+	require.NoError(b, err)
+	b.Run("hasher.compute-sha1", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			b.SetBytes(int64(len(content)))
+			oh.Compute(plumbing.AnyObject, content)
+		}
+	})
+
+	oh, err = FromObjectFormat(config.SHA256)
+	require.NoError(b, err)
+	b.Run("hasher.compute-sha256", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			b.SetBytes(int64(len(content)))
+			oh.Compute(plumbing.AnyObject, content)
+		}
+	})
+}
diff --git a/plumbing/object/change_adaptor_test.go b/plumbing/object/change_adaptor_test.go
index 8699f9ca..d331fcba 100644
--- a/plumbing/object/change_adaptor_test.go
+++ b/plumbing/object/change_adaptor_test.go
@@ -2,6 +2,7 @@ package object
 
 import (
 	"sort"
+	"strings"
 	"testing"
 
 	"github.com/go-git/go-git/v5/plumbing"
@@ -57,7 +58,7 @@ func newNoder(t *Tree, e TreeEntry) noder.Noder {
 func newPath(nn ...noder.Noder) noder.Path { return noder.Path(nn) }
 
 func (s *ChangeAdaptorSuite) TestTreeNoderHashHasMode() {
-	hash := plumbing.NewHash("aaaa")
+	hash := plumbing.NewHash("aaaa" + strings.Repeat("0", 36))
 	mode := filemode.Regular
 
 	treeNoder := &treeNoder{
diff --git a/plumbing/object/tree_test.go b/plumbing/object/tree_test.go
index ef2a0699..09be873e 100644
--- a/plumbing/object/tree_test.go
+++ b/plumbing/object/tree_test.go
@@ -1079,570 +1079,570 @@ func (s *TreeSuite) TestTreeDecodeReadBug() {
 		Entries: []TreeEntry{
 			{
 				Name: "alter.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xa4, 0x9d, 0x33, 0x49, 0xd7, 0xe2, 0x3f, 0xb5, 0x81, 0x19, 0x4f, 0x4c, 0xb5, 0x9a, 0xc0, 0xd5, 0x1b, 0x2, 0x1f, 0x78},
+				Hash: newHash([]byte{0xa4, 0x9d, 0x33, 0x49, 0xd7, 0xe2, 0x3f, 0xb5, 0x81, 0x19, 0x4f, 0x4c, 0xb5, 0x9a, 0xc0, 0xd5, 0x1b, 0x2, 0x1f, 0x78}),
 			},
 			{
 				Name: "analyze.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x9a, 0x3e, 0x95, 0x97, 0xdb, 0xb, 0x3, 0x20, 0x77, 0xc9, 0x1d, 0x96, 0x9d, 0x22, 0xc6, 0x27, 0x3f, 0x70, 0x2a, 0xc},
+				Hash: newHash([]byte{0x9a, 0x3e, 0x95, 0x97, 0xdb, 0xb, 0x3, 0x20, 0x77, 0xc9, 0x1d, 0x96, 0x9d, 0x22, 0xc6, 0x27, 0x3f, 0x70, 0x2a, 0xc}),
 			},
 			{
 				Name: "attach.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb8, 0xe1, 0x21, 0x99, 0xb5, 0x7d, 0xe8, 0x11, 0xea, 0xe0, 0xd0, 0x61, 0x42, 0xd5, 0xac, 0x4f, 0xd4, 0x30, 0xb1, 0xd8},
+				Hash: newHash([]byte{0xb8, 0xe1, 0x21, 0x99, 0xb5, 0x7d, 0xe8, 0x11, 0xea, 0xe0, 0xd0, 0x61, 0x42, 0xd5, 0xac, 0x4f, 0xd4, 0x30, 0xb1, 0xd8}),
 			},
 			{
 				Name: "auth.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd3, 0x8b, 0xb8, 0x36, 0xa7, 0x84, 0xfb, 0xfa, 0xb6, 0xab, 0x7b, 0x3, 0xd4, 0xe6, 0xdd, 0x43, 0xed, 0xc4, 0x1f, 0xa7},
+				Hash: newHash([]byte{0xd3, 0x8b, 0xb8, 0x36, 0xa7, 0x84, 0xfb, 0xfa, 0xb6, 0xab, 0x7b, 0x3, 0xd4, 0xe6, 0xdd, 0x43, 0xed, 0xc4, 0x1f, 0xa7}),
 			},
 			{
 				Name: "backup.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x25, 0x2f, 0x61, 0xcf, 0xca, 0xa8, 0xfc, 0xf3, 0x13, 0x7e, 0x8, 0xed, 0x68, 0x47, 0xdc, 0xfe, 0x1d, 0xc1, 0xde, 0x54},
+				Hash: newHash([]byte{0x25, 0x2f, 0x61, 0xcf, 0xca, 0xa8, 0xfc, 0xf3, 0x13, 0x7e, 0x8, 0xed, 0x68, 0x47, 0xdc, 0xfe, 0x1d, 0xc1, 0xde, 0x54}),
 			},
 			{
 				Name: "bitvec.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x52, 0x18, 0x4a, 0xa9, 0x64, 0xce, 0x18, 0x98, 0xf3, 0x5d, 0x1b, 0x3d, 0x87, 0x87, 0x1c, 0x2d, 0xe, 0xf4, 0xc5, 0x3d},
+				Hash: newHash([]byte{0x52, 0x18, 0x4a, 0xa9, 0x64, 0xce, 0x18, 0x98, 0xf3, 0x5d, 0x1b, 0x3d, 0x87, 0x87, 0x1c, 0x2d, 0xe, 0xf4, 0xc5, 0x3d}),
 			},
 			{
 				Name: "btmutex.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd8, 0x7d, 0x4d, 0x5f, 0xee, 0xb6, 0x30, 0x7a, 0xec, 0xdc, 0x9a, 0x83, 0x11, 0x14, 0x89, 0xab, 0x30, 0xc6, 0x78, 0xc3},
+				Hash: newHash([]byte{0xd8, 0x7d, 0x4d, 0x5f, 0xee, 0xb6, 0x30, 0x7a, 0xec, 0xdc, 0x9a, 0x83, 0x11, 0x14, 0x89, 0xab, 0x30, 0xc6, 0x78, 0xc3}),
 			},
 			{
 				Name: "btree.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x3c, 0xa6, 0x5, 0x83, 0xe3, 0xc8, 0xe3, 0x12, 0x0, 0xf9, 0x73, 0xe0, 0xe9, 0xc4, 0x53, 0x62, 0x58, 0xb2, 0x64, 0x39},
+				Hash: newHash([]byte{0x3c, 0xa6, 0x5, 0x83, 0xe3, 0xc8, 0xe3, 0x12, 0x0, 0xf9, 0x73, 0xe0, 0xe9, 0xc4, 0x53, 0x62, 0x58, 0xb2, 0x64, 0x39}),
 			},
 			{
 				Name: "btree.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xac, 0xe0, 0xf8, 0xcd, 0x21, 0x77, 0x70, 0xa2, 0xf6, 0x6b, 0x2e, 0xb8, 0x71, 0xbb, 0xc5, 0xfd, 0xc6, 0xfc, 0x2b, 0x68},
+				Hash: newHash([]byte{0xac, 0xe0, 0xf8, 0xcd, 0x21, 0x77, 0x70, 0xa2, 0xf6, 0x6b, 0x2e, 0xb8, 0x71, 0xbb, 0xc5, 0xfd, 0xc6, 0xfc, 0x2b, 0x68}),
 			},
 			{
 				Name: "btreeInt.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xce, 0x3c, 0x54, 0x93, 0xf8, 0xca, 0xd0, 0xbc, 0x54, 0x8a, 0xe8, 0xe4, 0x4e, 0x51, 0x28, 0x31, 0xd8, 0xfa, 0xc4, 0x31},
+				Hash: newHash([]byte{0xce, 0x3c, 0x54, 0x93, 0xf8, 0xca, 0xd0, 0xbc, 0x54, 0x8a, 0xe8, 0xe4, 0x4e, 0x51, 0x28, 0x31, 0xd8, 0xfa, 0xc4, 0x31}),
 			},
 			{
 				Name: "build.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x3c, 0x91, 0xcd, 0xcf, 0xdb, 0x7b, 0x1, 0x7c, 0xbc, 0x2d, 0x5c, 0x29, 0x57, 0x1a, 0x98, 0x27, 0xd, 0xe0, 0x71, 0xe6},
+				Hash: newHash([]byte{0x3c, 0x91, 0xcd, 0xcf, 0xdb, 0x7b, 0x1, 0x7c, 0xbc, 0x2d, 0x5c, 0x29, 0x57, 0x1a, 0x98, 0x27, 0xd, 0xe0, 0x71, 0xe6}),
 			},
 			{
 				Name: "callback.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd4, 0xc, 0x65, 0xcb, 0x92, 0x45, 0x80, 0x29, 0x6a, 0xd0, 0x69, 0xa0, 0x4b, 0xf9, 0xc9, 0xe9, 0x53, 0x4e, 0xca, 0xa7},
+				Hash: newHash([]byte{0xd4, 0xc, 0x65, 0xcb, 0x92, 0x45, 0x80, 0x29, 0x6a, 0xd0, 0x69, 0xa0, 0x4b, 0xf9, 0xc9, 0xe9, 0x53, 0x4e, 0xca, 0xa7}),
 			},
 			{
 				Name: "complete.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x9e, 0x91, 0x40, 0x8, 0x5c, 0x0, 0x46, 0xed, 0x3b, 0xf6, 0xf4, 0x48, 0x52, 0x20, 0x69, 0x2d, 0xca, 0x17, 0x43, 0xc5},
+				Hash: newHash([]byte{0x9e, 0x91, 0x40, 0x8, 0x5c, 0x0, 0x46, 0xed, 0x3b, 0xf6, 0xf4, 0x48, 0x52, 0x20, 0x69, 0x2d, 0xca, 0x17, 0x43, 0xc5}),
 			},
 			{
 				Name: "crypto.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x25, 0x51, 0xe6, 0xba, 0x2, 0x39, 0xf8, 0x5a, 0x35, 0x77, 0x96, 0xa8, 0xdd, 0xa8, 0xca, 0x3e, 0x29, 0x70, 0x93, 0xf8},
+				Hash: newHash([]byte{0x25, 0x51, 0xe6, 0xba, 0x2, 0x39, 0xf8, 0x5a, 0x35, 0x77, 0x96, 0xa8, 0xdd, 0xa8, 0xca, 0x3e, 0x29, 0x70, 0x93, 0xf8}),
 			},
 			{
 				Name: "crypto.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf7, 0x1f, 0x53, 0x2c, 0xdc, 0x44, 0x8f, 0xa, 0x1d, 0xd5, 0xc6, 0xef, 0xf5, 0xfb, 0xd3, 0x3a, 0x91, 0x55, 0xaa, 0x97},
+				Hash: newHash([]byte{0xf7, 0x1f, 0x53, 0x2c, 0xdc, 0x44, 0x8f, 0xa, 0x1d, 0xd5, 0xc6, 0xef, 0xf5, 0xfb, 0xd3, 0x3a, 0x91, 0x55, 0xaa, 0x97}),
 			},
 			{
 				Name: "crypto_cc.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x53, 0x7d, 0xf7, 0xe3, 0xb3, 0x6a, 0xb5, 0xcf, 0xdd, 0x6f, 0xca, 0x40, 0x28, 0xeb, 0xca, 0xe1, 0x86, 0x87, 0xd6, 0x4d},
+				Hash: newHash([]byte{0x53, 0x7d, 0xf7, 0xe3, 0xb3, 0x6a, 0xb5, 0xcf, 0xdd, 0x6f, 0xca, 0x40, 0x28, 0xeb, 0xca, 0xe1, 0x86, 0x87, 0xd6, 0x4d}),
 			},
 			{
 				Name: "crypto_impl.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xa5, 0x89, 0x27, 0xc7, 0x6e, 0xf6, 0x20, 0x56, 0x77, 0xbe, 0x5c, 0x1a, 0x8e, 0x80, 0xc9, 0x83, 0x56, 0xb3, 0xa9, 0xd3},
+				Hash: newHash([]byte{0xa5, 0x89, 0x27, 0xc7, 0x6e, 0xf6, 0x20, 0x56, 0x77, 0xbe, 0x5c, 0x1a, 0x8e, 0x80, 0xc9, 0x83, 0x56, 0xb3, 0xa9, 0xd3}),
 			},
 			{
 				Name: "crypto_libtomcrypt.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x1a, 0x33, 0x83, 0xe0, 0x1, 0xa7, 0x21, 0x11, 0xc3, 0xf6, 0x61, 0x92, 0x22, 0xb0, 0x65, 0xf4, 0xbd, 0x1, 0xb, 0xe1},
+				Hash: newHash([]byte{0x1a, 0x33, 0x83, 0xe0, 0x1, 0xa7, 0x21, 0x11, 0xc3, 0xf6, 0x61, 0x92, 0x22, 0xb0, 0x65, 0xf4, 0xbd, 0x1, 0xb, 0xe1}),
 			},
 			{
 				Name: "crypto_openssl.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd0, 0x19, 0x81, 0x3b, 0x47, 0x6c, 0x52, 0xd0, 0x20, 0xe2, 0xc0, 0xac, 0xd5, 0x24, 0xe9, 0xea, 0x3d, 0xf, 0xb9, 0xfe},
+				Hash: newHash([]byte{0xd0, 0x19, 0x81, 0x3b, 0x47, 0x6c, 0x52, 0xd0, 0x20, 0xe2, 0xc0, 0xac, 0xd5, 0x24, 0xe9, 0xea, 0x3d, 0xf, 0xb9, 0xfe}),
 			},
 			{
 				Name: "ctime.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x60, 0x59, 0x5f, 0xf8, 0x8d, 0x92, 0xf7, 0x8, 0x26, 0x4, 0xfb, 0xd9, 0xdf, 0x9a, 0xfe, 0xa1, 0x6a, 0xe8, 0x6f, 0xf},
+				Hash: newHash([]byte{0x60, 0x59, 0x5f, 0xf8, 0x8d, 0x92, 0xf7, 0x8, 0x26, 0x4, 0xfb, 0xd9, 0xdf, 0x9a, 0xfe, 0xa1, 0x6a, 0xe8, 0x6f, 0xf}),
 			},
 			{
 				Name: "date.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x75, 0x8d, 0xd7, 0xc8, 0x9b, 0xca, 0x39, 0x37, 0xa9, 0xd, 0x70, 0x6e, 0xa9, 0x82, 0xce, 0x3a, 0xcf, 0x11, 0xd1, 0x83},
+				Hash: newHash([]byte{0x75, 0x8d, 0xd7, 0xc8, 0x9b, 0xca, 0x39, 0x37, 0xa9, 0xd, 0x70, 0x6e, 0xa9, 0x82, 0xce, 0x3a, 0xcf, 0x11, 0xd1, 0x83}),
 			},
 			{
 				Name: "delete.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x63, 0x4e, 0x11, 0x55, 0x63, 0xae, 0x12, 0xba, 0x65, 0x58, 0xcc, 0xc5, 0x12, 0xae, 0xd6, 0x31, 0xc0, 0x66, 0xba, 0xd8},
+				Hash: newHash([]byte{0x63, 0x4e, 0x11, 0x55, 0x63, 0xae, 0x12, 0xba, 0x65, 0x58, 0xcc, 0xc5, 0x12, 0xae, 0xd6, 0x31, 0xc0, 0x66, 0xba, 0xd8}),
 			},
 			{
 				Name: "expr.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x66, 0x3, 0x97, 0xe0, 0x78, 0xae, 0x48, 0xb2, 0xe7, 0x17, 0x5e, 0x33, 0x85, 0x67, 0x78, 0x19, 0x72, 0x2d, 0xdd, 0x6c},
+				Hash: newHash([]byte{0x66, 0x3, 0x97, 0xe0, 0x78, 0xae, 0x48, 0xb2, 0xe7, 0x17, 0x5e, 0x33, 0x85, 0x67, 0x78, 0x19, 0x72, 0x2d, 0xdd, 0x6c}),
 			},
 			{
 				Name: "fault.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xc3, 0x2, 0x8c, 0x4f, 0x93, 0x6e, 0xdf, 0x96, 0x71, 0x2d, 0xbe, 0x73, 0xa0, 0x76, 0x62, 0xf0, 0xa2, 0x6b, 0x1d, 0xa},
+				Hash: newHash([]byte{0xc3, 0x2, 0x8c, 0x4f, 0x93, 0x6e, 0xdf, 0x96, 0x71, 0x2d, 0xbe, 0x73, 0xa0, 0x76, 0x62, 0xf0, 0xa2, 0x6b, 0x1d, 0xa}),
 			},
 			{
 				Name: "fkey.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xac, 0x35, 0xbc, 0x19, 0x4c, 0xde, 0xb1, 0x27, 0x98, 0x9b, 0x9, 0x40, 0x35, 0xce, 0xe0, 0x6f, 0x57, 0x37, 0x6f, 0x5e},
+				Hash: newHash([]byte{0xac, 0x35, 0xbc, 0x19, 0x4c, 0xde, 0xb1, 0x27, 0x98, 0x9b, 0x9, 0x40, 0x35, 0xce, 0xe0, 0x6f, 0x57, 0x37, 0x6f, 0x5e}),
 			},
 			{
 				Name: "func.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xc0, 0x2f, 0x9, 0x6a, 0xda, 0xd5, 0xbc, 0xe9, 0xac, 0x83, 0xd3, 0x5f, 0xf, 0x46, 0x9, 0xd6, 0xf6, 0xd4, 0x3b, 0xe5},
+				Hash: newHash([]byte{0xc0, 0x2f, 0x9, 0x6a, 0xda, 0xd5, 0xbc, 0xe9, 0xac, 0x83, 0xd3, 0x5f, 0xf, 0x46, 0x9, 0xd6, 0xf6, 0xd4, 0x3b, 0xe5}),
 			},
 			{
 				Name: "global.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x7b, 0x2, 0xcf, 0x21, 0x30, 0xe0, 0xd1, 0xa7, 0xb8, 0x89, 0xd8, 0x44, 0xc, 0xcc, 0x82, 0x8, 0xf7, 0xb6, 0x7b, 0xf9},
+				Hash: newHash([]byte{0x7b, 0x2, 0xcf, 0x21, 0x30, 0xe0, 0xd1, 0xa7, 0xb8, 0x89, 0xd8, 0x44, 0xc, 0xcc, 0x82, 0x8, 0xf7, 0xb6, 0x7b, 0xf9}),
 			},
 			{
 				Name: "hash.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe8, 0x1d, 0xcf, 0x95, 0xe4, 0x38, 0x48, 0xfa, 0x70, 0x86, 0xb7, 0xf7, 0x81, 0xc0, 0x90, 0xad, 0xc7, 0xe6, 0xca, 0x8e},
+				Hash: newHash([]byte{0xe8, 0x1d, 0xcf, 0x95, 0xe4, 0x38, 0x48, 0xfa, 0x70, 0x86, 0xb7, 0xf7, 0x81, 0xc0, 0x90, 0xad, 0xc7, 0xe6, 0xca, 0x8e}),
 			},
 			{
 				Name: "hash.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x82, 0xb7, 0xc5, 0x8c, 0x71, 0x9, 0xb, 0x54, 0x7e, 0x10, 0x17, 0x42, 0xaa, 0x9, 0x51, 0x73, 0x9f, 0xf2, 0xee, 0xe7},
+				Hash: newHash([]byte{0x82, 0xb7, 0xc5, 0x8c, 0x71, 0x9, 0xb, 0x54, 0x7e, 0x10, 0x17, 0x42, 0xaa, 0x9, 0x51, 0x73, 0x9f, 0xf2, 0xee, 0xe7}),
 			},
 			{
 				Name: "hwtime.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb8, 0xbc, 0x5a, 0x29, 0x5b, 0xe3, 0xfa, 0xc8, 0x35, 0x1f, 0xa9, 0xf0, 0x8a, 0x77, 0x57, 0x9d, 0x59, 0xc9, 0xa8, 0xe4},
+				Hash: newHash([]byte{0xb8, 0xbc, 0x5a, 0x29, 0x5b, 0xe3, 0xfa, 0xc8, 0x35, 0x1f, 0xa9, 0xf0, 0x8a, 0x77, 0x57, 0x9d, 0x59, 0xc9, 0xa8, 0xe4}),
 			},
 			{
 				Name: "insert.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x9a, 0x56, 0x61, 0xf5, 0x9a, 0x72, 0x95, 0x2b, 0xe6, 0xc1, 0x67, 0xa0, 0xc2, 0xdb, 0x15, 0x9b, 0x91, 0xb7, 0x1f, 0xae},
+				Hash: newHash([]byte{0x9a, 0x56, 0x61, 0xf5, 0x9a, 0x72, 0x95, 0x2b, 0xe6, 0xc1, 0x67, 0xa0, 0xc2, 0xdb, 0x15, 0x9b, 0x91, 0xb7, 0x1f, 0xae}),
 			},
 			{
 				Name: "journal.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xfe, 0xd2, 0x7b, 0xe3, 0xe3, 0x80, 0x55, 0xd2, 0x20, 0x43, 0x95, 0xcd, 0xe6, 0xff, 0xc9, 0x45, 0x89, 0xfb, 0xf5, 0xe8},
+				Hash: newHash([]byte{0xfe, 0xd2, 0x7b, 0xe3, 0xe3, 0x80, 0x55, 0xd2, 0x20, 0x43, 0x95, 0xcd, 0xe6, 0xff, 0xc9, 0x45, 0x89, 0xfb, 0xf5, 0xe8}),
 			},
 			{
 				Name: "legacy.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x94, 0x64, 0x9a, 0xe7, 0x5, 0xab, 0x93, 0x85, 0x10, 0x8d, 0xd, 0x88, 0x7a, 0xf0, 0x75, 0x92, 0x89, 0xfb, 0x23, 0xcb},
+				Hash: newHash([]byte{0x94, 0x64, 0x9a, 0xe7, 0x5, 0xab, 0x93, 0x85, 0x10, 0x8d, 0xd, 0x88, 0x7a, 0xf0, 0x75, 0x92, 0x89, 0xfb, 0x23, 0xcb}),
 			},
 			{
 				Name: "lempar.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x2a, 0xfa, 0xa6, 0xce, 0xa6, 0xd8, 0x29, 0x60, 0x2c, 0x27, 0x86, 0xc1, 0xf8, 0xa3, 0x7f, 0x56, 0x7c, 0xf6, 0xfd, 0x53},
+				Hash: newHash([]byte{0x2a, 0xfa, 0xa6, 0xce, 0xa6, 0xd8, 0x29, 0x60, 0x2c, 0x27, 0x86, 0xc1, 0xf8, 0xa3, 0x7f, 0x56, 0x7c, 0xf6, 0xfd, 0x53}),
 			},
 			{
 				Name: "loadext.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xcd, 0xcf, 0x6a, 0x93, 0xb8, 0xc4, 0xf, 0x91, 0x4b, 0x94, 0x24, 0xe, 0xf1, 0x4c, 0xb4, 0xa3, 0xa, 0x37, 0xec, 0xa1},
+				Hash: newHash([]byte{0xcd, 0xcf, 0x6a, 0x93, 0xb8, 0xc4, 0xf, 0x91, 0x4b, 0x94, 0x24, 0xe, 0xf1, 0x4c, 0xb4, 0xa3, 0xa, 0x37, 0xec, 0xa1}),
 			},
 			{
 				Name: "main.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x39, 0xf6, 0x4, 0x21, 0xe6, 0x81, 0x27, 0x7c, 0xc3, 0xdb, 0xa0, 0x9a, 0xbe, 0x7c, 0xf7, 0x90, 0xd5, 0x28, 0xf5, 0xc3},
+				Hash: newHash([]byte{0x39, 0xf6, 0x4, 0x21, 0xe6, 0x81, 0x27, 0x7c, 0xc3, 0xdb, 0xa0, 0x9a, 0xbe, 0x7c, 0xf7, 0x90, 0xd5, 0x28, 0xf5, 0xc3}),
 			},
 			{
 				Name: "malloc.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x35, 0xa4, 0x4e, 0x5f, 0x61, 0xc2, 0xe4, 0x4c, 0x48, 0x1c, 0x62, 0x51, 0xbd, 0xa, 0xae, 0x7a, 0xcd, 0xa4, 0xde, 0xb},
+				Hash: newHash([]byte{0x35, 0xa4, 0x4e, 0x5f, 0x61, 0xc2, 0xe4, 0x4c, 0x48, 0x1c, 0x62, 0x51, 0xbd, 0xa, 0xae, 0x7a, 0xcd, 0xa4, 0xde, 0xb}),
 			},
 			{
 				Name: "mem0.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd, 0xb, 0x66, 0x67, 0xd6, 0xa, 0x95, 0x5a, 0x6, 0x96, 0xdf, 0x62, 0x89, 0xb4, 0x91, 0x78, 0x96, 0x93, 0x43, 0xaa},
+				Hash: newHash([]byte{0xd, 0xb, 0x66, 0x67, 0xd6, 0xa, 0x95, 0x5a, 0x6, 0x96, 0xdf, 0x62, 0x89, 0xb4, 0x91, 0x78, 0x96, 0x93, 0x43, 0xaa}),
 			},
 			{
 				Name: "mem1.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x35, 0x78, 0x49, 0x6f, 0x33, 0x3, 0x7, 0xb2, 0x31, 0xdf, 0xb5, 0x3c, 0xc, 0x2e, 0x1c, 0x6b, 0x32, 0x3d, 0x79, 0x1e},
+				Hash: newHash([]byte{0x35, 0x78, 0x49, 0x6f, 0x33, 0x3, 0x7, 0xb2, 0x31, 0xdf, 0xb5, 0x3c, 0xc, 0x2e, 0x1c, 0x6b, 0x32, 0x3d, 0x79, 0x1e}),
 			},
 			{
 				Name: "mem2.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x26, 0x44, 0x8e, 0xa8, 0xaa, 0xe0, 0x36, 0x6a, 0xf0, 0x54, 0x1a, 0xfe, 0xa4, 0x79, 0xb, 0x42, 0xf4, 0xa6, 0x9b, 0x5a},
+				Hash: newHash([]byte{0x26, 0x44, 0x8e, 0xa8, 0xaa, 0xe0, 0x36, 0x6a, 0xf0, 0x54, 0x1a, 0xfe, 0xa4, 0x79, 0xb, 0x42, 0xf4, 0xa6, 0x9b, 0x5a}),
 			},
 			{
 				Name: "mem3.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x1a, 0x1b, 0x79, 0x1f, 0x28, 0xf8, 0xcf, 0x3c, 0xe4, 0xf9, 0xa3, 0x5c, 0xda, 0xd7, 0xb7, 0x10, 0x75, 0x68, 0xc7, 0x15},
+				Hash: newHash([]byte{0x1a, 0x1b, 0x79, 0x1f, 0x28, 0xf8, 0xcf, 0x3c, 0xe4, 0xf9, 0xa3, 0x5c, 0xda, 0xd7, 0xb7, 0x10, 0x75, 0x68, 0xc7, 0x15}),
 			},
 			{
 				Name: "mem5.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x78, 0x3c, 0xef, 0x61, 0x76, 0xc5, 0x9c, 0xbf, 0x30, 0x91, 0x46, 0x31, 0x9, 0x5a, 0x1a, 0x54, 0xf4, 0xe4, 0x2e, 0x8},
+				Hash: newHash([]byte{0x78, 0x3c, 0xef, 0x61, 0x76, 0xc5, 0x9c, 0xbf, 0x30, 0x91, 0x46, 0x31, 0x9, 0x5a, 0x1a, 0x54, 0xf4, 0xe4, 0x2e, 0x8}),
 			},
 			{
 				Name: "memjournal.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x5, 0x72, 0x59, 0x48, 0xf6, 0x5d, 0x42, 0x7b, 0x7, 0xf7, 0xf9, 0x29, 0xac, 0xa3, 0xff, 0x22, 0x4b, 0x17, 0x53, 0xdf},
+				Hash: newHash([]byte{0x5, 0x72, 0x59, 0x48, 0xf6, 0x5d, 0x42, 0x7b, 0x7, 0xf7, 0xf9, 0x29, 0xac, 0xa3, 0xff, 0x22, 0x4b, 0x17, 0x53, 0xdf}),
 			},
 			{
 				Name: "mutex.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb5, 0x67, 0xe7, 0xc2, 0x7e, 0xf2, 0x4, 0x10, 0x86, 0xaf, 0xe0, 0xf6, 0x96, 0x66, 0xe2, 0x7b, 0xf5, 0x9, 0x8a, 0x59},
+				Hash: newHash([]byte{0xb5, 0x67, 0xe7, 0xc2, 0x7e, 0xf2, 0x4, 0x10, 0x86, 0xaf, 0xe0, 0xf6, 0x96, 0x66, 0xe2, 0x7b, 0xf5, 0x9, 0x8a, 0x59}),
 			},
 			{
 				Name: "mutex.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x9, 0x78, 0x81, 0x22, 0x52, 0x77, 0x89, 0xa, 0x9c, 0x36, 0xc2, 0x4d, 0x41, 0xf6, 0x11, 0x4d, 0x64, 0xc0, 0x6d, 0xb3},
+				Hash: newHash([]byte{0x9, 0x78, 0x81, 0x22, 0x52, 0x77, 0x89, 0xa, 0x9c, 0x36, 0xc2, 0x4d, 0x41, 0xf6, 0x11, 0x4d, 0x64, 0xc0, 0x6d, 0xb3}),
 			},
 			{
 				Name: "mutex_noop.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x45, 0x6e, 0x82, 0xa2, 0x5e, 0x27, 0x1b, 0x6, 0x14, 0xe7, 0xf4, 0xf8, 0x3c, 0x22, 0x85, 0x53, 0xb7, 0xfa, 0x1, 0x58},
+				Hash: newHash([]byte{0x45, 0x6e, 0x82, 0xa2, 0x5e, 0x27, 0x1b, 0x6, 0x14, 0xe7, 0xf4, 0xf8, 0x3c, 0x22, 0x85, 0x53, 0xb7, 0xfa, 0x1, 0x58}),
 			},
 			{
 				Name: "mutex_unix.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xec, 0xa7, 0x29, 0x58, 0x31, 0xc2, 0xf0, 0xee, 0x48, 0xba, 0x54, 0xd0, 0x62, 0x91, 0x4d, 0x6, 0xa1, 0xdd, 0x8e, 0xbe},
+				Hash: newHash([]byte{0xec, 0xa7, 0x29, 0x58, 0x31, 0xc2, 0xf0, 0xee, 0x48, 0xba, 0x54, 0xd0, 0x62, 0x91, 0x4d, 0x6, 0xa1, 0xdd, 0x8e, 0xbe}),
 			},
 			{
 				Name: "mutex_w32.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x27, 0xd1, 0xa, 0xf5, 0xbd, 0x33, 0x1b, 0xdb, 0x97, 0x3f, 0x61, 0x45, 0xb7, 0x4f, 0x72, 0xb6, 0x7, 0xcf, 0xc4, 0x6e},
+				Hash: newHash([]byte{0x27, 0xd1, 0xa, 0xf5, 0xbd, 0x33, 0x1b, 0xdb, 0x97, 0x3f, 0x61, 0x45, 0xb7, 0x4f, 0x72, 0xb6, 0x7, 0xcf, 0xc4, 0x6e}),
 			},
 			{
 				Name: "notify.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xfc, 0xab, 0x5b, 0xfa, 0xf0, 0x19, 0x8, 0xd3, 0xde, 0x93, 0xfa, 0x88, 0xb5, 0xea, 0xe9, 0xe9, 0x6c, 0xa3, 0xc8, 0xe8},
+				Hash: newHash([]byte{0xfc, 0xab, 0x5b, 0xfa, 0xf0, 0x19, 0x8, 0xd3, 0xde, 0x93, 0xfa, 0x88, 0xb5, 0xea, 0xe9, 0xe9, 0x6c, 0xa3, 0xc8, 0xe8}),
 			},
 			{
 				Name: "os.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xbe, 0x2e, 0xa4, 0xcf, 0xc0, 0x19, 0x59, 0x93, 0xa3, 0x40, 0xc9, 0x2, 0xae, 0xdd, 0xf1, 0xbe, 0x4b, 0x8e, 0xd7, 0x3a},
+				Hash: newHash([]byte{0xbe, 0x2e, 0xa4, 0xcf, 0xc0, 0x19, 0x59, 0x93, 0xa3, 0x40, 0xc9, 0x2, 0xae, 0xdd, 0xf1, 0xbe, 0x4b, 0x8e, 0xd7, 0x3a}),
 			},
 			{
 				Name: "os.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x7, 0xa, 0x2d, 0xdd, 0x17, 0xf7, 0x71, 0xf9, 0x8f, 0xf8, 0xcc, 0xd6, 0xf0, 0x33, 0xbd, 0xac, 0xc5, 0xe9, 0xf6, 0xc},
+				Hash: newHash([]byte{0x7, 0xa, 0x2d, 0xdd, 0x17, 0xf7, 0x71, 0xf9, 0x8f, 0xf8, 0xcc, 0xd6, 0xf0, 0x33, 0xbd, 0xac, 0xc5, 0xe9, 0xf6, 0xc}),
 			},
 			{
 				Name: "os_common.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf6, 0xc3, 0xe7, 0xff, 0x89, 0x46, 0x30, 0x86, 0x40, 0x18, 0x22, 0xf4, 0x81, 0xe7, 0xe3, 0xb8, 0x7b, 0x2c, 0x78, 0xc7},
+				Hash: newHash([]byte{0xf6, 0xc3, 0xe7, 0xff, 0x89, 0x46, 0x30, 0x86, 0x40, 0x18, 0x22, 0xf4, 0x81, 0xe7, 0xe3, 0xb8, 0x7b, 0x2c, 0x78, 0xc7}),
 			},
 			{
 				Name: "os_unix.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xab, 0xc2, 0x3a, 0x45, 0x2e, 0x72, 0xf7, 0x1c, 0x76, 0xaf, 0xa9, 0x98, 0x3c, 0x3a, 0xd9, 0xd4, 0x25, 0x61, 0x6c, 0x6d},
+				Hash: newHash([]byte{0xab, 0xc2, 0x3a, 0x45, 0x2e, 0x72, 0xf7, 0x1c, 0x76, 0xaf, 0xa9, 0x98, 0x3c, 0x3a, 0xd9, 0xd4, 0x25, 0x61, 0x6c, 0x6d}),
 			},
 			{
 				Name: "os_win.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xae, 0xb0, 0x88, 0x14, 0xb3, 0xda, 0xbe, 0x81, 0xb8, 0x4c, 0xda, 0x91, 0x85, 0x82, 0xb0, 0xf, 0xfd, 0x86, 0xe4, 0x87},
+				Hash: newHash([]byte{0xae, 0xb0, 0x88, 0x14, 0xb3, 0xda, 0xbe, 0x81, 0xb8, 0x4c, 0xda, 0x91, 0x85, 0x82, 0xb0, 0xf, 0xfd, 0x86, 0xe4, 0x87}),
 			},
 			{
 				Name: "pager.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x61, 0x72, 0x7f, 0xaa, 0x9c, 0xf, 0x3d, 0x56, 0x62, 0x65, 0xbe, 0x7e, 0xec, 0x5b, 0x2a, 0x35, 0xf6, 0xa4, 0xbc, 0x9f},
+				Hash: newHash([]byte{0x61, 0x72, 0x7f, 0xaa, 0x9c, 0xf, 0x3d, 0x56, 0x62, 0x65, 0xbe, 0x7e, 0xec, 0x5b, 0x2a, 0x35, 0xf6, 0xa4, 0xbc, 0x9f}),
 			},
 			{
 				Name: "pager.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x6f, 0x65, 0x91, 0x36, 0xe2, 0x76, 0x7, 0x9d, 0xa4, 0x3a, 0x2e, 0x39, 0xe1, 0xb6, 0x86, 0x37, 0xec, 0xad, 0xcf, 0x68},
+				Hash: newHash([]byte{0x6f, 0x65, 0x91, 0x36, 0xe2, 0x76, 0x7, 0x9d, 0xa4, 0x3a, 0x2e, 0x39, 0xe1, 0xb6, 0x86, 0x37, 0xec, 0xad, 0xcf, 0x68}),
 			},
 			{
 				Name: "parse.y", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x83, 0x10, 0xb2, 0x69, 0x89, 0xb0, 0x5b, 0xed, 0x1e, 0x1b, 0x3, 0xda, 0x80, 0xf5, 0xc0, 0xa5, 0x2e, 0x9a, 0xd1, 0xd2},
+				Hash: newHash([]byte{0x83, 0x10, 0xb2, 0x69, 0x89, 0xb0, 0x5b, 0xed, 0x1e, 0x1b, 0x3, 0xda, 0x80, 0xf5, 0xc0, 0xa5, 0x2e, 0x9a, 0xd1, 0xd2}),
 			},
 			{
 				Name: "pcache.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x48, 0x2a, 0x18, 0x8b, 0xee, 0x19, 0x91, 0xbc, 0x8a, 0xda, 0xc9, 0x6a, 0x19, 0x3a, 0x53, 0xe5, 0x46, 0x2a, 0x8c, 0x10},
+				Hash: newHash([]byte{0x48, 0x2a, 0x18, 0x8b, 0xee, 0x19, 0x91, 0xbc, 0x8a, 0xda, 0xc9, 0x6a, 0x19, 0x3a, 0x53, 0xe5, 0x46, 0x2a, 0x8c, 0x10}),
 			},
 			{
 				Name: "pcache.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf4, 0xd4, 0xad, 0x71, 0xc1, 0xd, 0x78, 0xc6, 0xda, 0xbd, 0xe2, 0x52, 0x15, 0xcd, 0x41, 0x5a, 0x76, 0x1, 0x48, 0xca},
+				Hash: newHash([]byte{0xf4, 0xd4, 0xad, 0x71, 0xc1, 0xd, 0x78, 0xc6, 0xda, 0xbd, 0xe2, 0x52, 0x15, 0xcd, 0x41, 0x5a, 0x76, 0x1, 0x48, 0xca}),
 			},
 			{
 				Name: "pcache1.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x41, 0x47, 0xd2, 0xef, 0xf5, 0x5b, 0xdd, 0x9f, 0xf7, 0xc6, 0x86, 0xc, 0x60, 0x18, 0x10, 0x20, 0x16, 0x6c, 0x5f, 0x50},
+				Hash: newHash([]byte{0x41, 0x47, 0xd2, 0xef, 0xf5, 0x5b, 0xdd, 0x9f, 0xf7, 0xc6, 0x86, 0xc, 0x60, 0x18, 0x10, 0x20, 0x16, 0x6c, 0x5f, 0x50}),
 			},
 			{
 				Name: "pragma.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x22, 0x97, 0x71, 0x69, 0x61, 0x7d, 0x49, 0x22, 0xb3, 0x99, 0x3f, 0x76, 0x9d, 0x90, 0xfa, 0x7b, 0xc4, 0x41, 0xea, 0x50},
+				Hash: newHash([]byte{0x22, 0x97, 0x71, 0x69, 0x61, 0x7d, 0x49, 0x22, 0xb3, 0x99, 0x3f, 0x76, 0x9d, 0x90, 0xfa, 0x7b, 0xc4, 0x41, 0xea, 0x50}),
 			},
 			{
 				Name: "prepare.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd7, 0x8d, 0x83, 0xcb, 0xd8, 0x78, 0x97, 0xf5, 0x73, 0x30, 0x3f, 0x9f, 0x57, 0xab, 0x8d, 0xe0, 0x24, 0xa6, 0xe3, 0xf8},
+				Hash: newHash([]byte{0xd7, 0x8d, 0x83, 0xcb, 0xd8, 0x78, 0x97, 0xf5, 0x73, 0x30, 0x3f, 0x9f, 0x57, 0xab, 0x8d, 0xe0, 0x24, 0xa6, 0xe3, 0xf8}),
 			},
 			{
 				Name: "printf.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x9f, 0x68, 0xd2, 0x4, 0xff, 0xdc, 0x9f, 0x3d, 0x42, 0x7f, 0x80, 0xa8, 0x23, 0x9a, 0x7f, 0xa3, 0xa9, 0x8a, 0xec, 0xbd},
+				Hash: newHash([]byte{0x9f, 0x68, 0xd2, 0x4, 0xff, 0xdc, 0x9f, 0x3d, 0x42, 0x7f, 0x80, 0xa8, 0x23, 0x9a, 0x7f, 0xa3, 0xa9, 0x8a, 0xec, 0xbd}),
 			},
 			{
 				Name: "random.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x23, 0x4e, 0xbd, 0xf6, 0x58, 0xf4, 0x36, 0xcc, 0x7c, 0x68, 0xf0, 0x27, 0xc4, 0x8b, 0xe, 0x1b, 0x9b, 0xa3, 0x4e, 0x98},
+				Hash: newHash([]byte{0x23, 0x4e, 0xbd, 0xf6, 0x58, 0xf4, 0x36, 0xcc, 0x7c, 0x68, 0xf0, 0x27, 0xc4, 0x8b, 0xe, 0x1b, 0x9b, 0xa3, 0x4e, 0x98}),
 			},
 			{
 				Name: "resolve.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x91, 0xef, 0xca, 0xa1, 0xa1, 0x6b, 0xfc, 0x98, 0xfb, 0x35, 0xd8, 0x5c, 0xad, 0x15, 0x6b, 0x93, 0x53, 0x3e, 0x4e, 0x6},
+				Hash: newHash([]byte{0x91, 0xef, 0xca, 0xa1, 0xa1, 0x6b, 0xfc, 0x98, 0xfb, 0x35, 0xd8, 0x5c, 0xad, 0x15, 0x6b, 0x93, 0x53, 0x3e, 0x4e, 0x6}),
 			},
 			{
 				Name: "rowset.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x57, 0x61, 0xf9, 0x85, 0x50, 0xb1, 0x76, 0xcc, 0xe1, 0x1d, 0xcb, 0xce, 0xc9, 0x38, 0x99, 0xa0, 0x75, 0xbb, 0x64, 0xfd},
+				Hash: newHash([]byte{0x57, 0x61, 0xf9, 0x85, 0x50, 0xb1, 0x76, 0xcc, 0xe1, 0x1d, 0xcb, 0xce, 0xc9, 0x38, 0x99, 0xa0, 0x75, 0xbb, 0x64, 0xfd}),
 			},
 			{
 				Name: "select.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf3, 0xf1, 0x49, 0x9, 0x63, 0x95, 0x5b, 0x8e, 0xd0, 0xc9, 0xfe, 0x6e, 0x1e, 0xec, 0x83, 0x6c, 0x1a, 0x52, 0x94, 0xb4},
+				Hash: newHash([]byte{0xf3, 0xf1, 0x49, 0x9, 0x63, 0x95, 0x5b, 0x8e, 0xd0, 0xc9, 0xfe, 0x6e, 0x1e, 0xec, 0x83, 0x6c, 0x1a, 0x52, 0x94, 0xb4}),
 			},
 			{
 				Name: "shell.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x1b, 0xe2, 0x87, 0x1f, 0xed, 0x9a, 0x1f, 0xdf, 0x1d, 0xf7, 0x19, 0x8e, 0x11, 0x25, 0x36, 0x0, 0xec, 0xba, 0x76, 0xcc},
+				Hash: newHash([]byte{0x1b, 0xe2, 0x87, 0x1f, 0xed, 0x9a, 0x1f, 0xdf, 0x1d, 0xf7, 0x19, 0x8e, 0x11, 0x25, 0x36, 0x0, 0xec, 0xba, 0x76, 0xcc}),
 			},
 			{
 				Name: "sqlcipher.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x82, 0x75, 0x30, 0x95, 0xcd, 0x17, 0x23, 0xc5, 0xff, 0x4f, 0x11, 0x15, 0xe4, 0x97, 0x55, 0x91, 0xee, 0x34, 0xf5, 0xce},
+				Hash: newHash([]byte{0x82, 0x75, 0x30, 0x95, 0xcd, 0x17, 0x23, 0xc5, 0xff, 0x4f, 0x11, 0x15, 0xe4, 0x97, 0x55, 0x91, 0xee, 0x34, 0xf5, 0xce}),
 			},
 			{
 				Name: "sqlite.h.in", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x66, 0x8, 0x82, 0x31, 0x75, 0xde, 0x5b, 0x6a, 0xd, 0x37, 0x8f, 0xdb, 0xc, 0x38, 0x18, 0xb6, 0xab, 0x4f, 0xbf, 0x8e},
+				Hash: newHash([]byte{0x66, 0x8, 0x82, 0x31, 0x75, 0xde, 0x5b, 0x6a, 0xd, 0x37, 0x8f, 0xdb, 0xc, 0x38, 0x18, 0xb6, 0xab, 0x4f, 0xbf, 0x8e}),
 			},
 			{
 				Name: "sqlite3.rc", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x96, 0x98, 0x76, 0xda, 0x1e, 0x57, 0x14, 0x3d, 0xe0, 0xb4, 0xd1, 0xc7, 0x62, 0x9f, 0xd3, 0x35, 0x6f, 0x2e, 0x1c, 0x96},
+				Hash: newHash([]byte{0x96, 0x98, 0x76, 0xda, 0x1e, 0x57, 0x14, 0x3d, 0xe0, 0xb4, 0xd1, 0xc7, 0x62, 0x9f, 0xd3, 0x35, 0x6f, 0x2e, 0x1c, 0x96}),
 			},
 			{
 				Name: "sqlite3ext.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x92, 0x8b, 0xb3, 0xba, 0xd9, 0xdd, 0x64, 0x3c, 0x30, 0x1d, 0xd2, 0xb0, 0xac, 0x22, 0x28, 0x7a, 0x81, 0x28, 0x48, 0x84},
+				Hash: newHash([]byte{0x92, 0x8b, 0xb3, 0xba, 0xd9, 0xdd, 0x64, 0x3c, 0x30, 0x1d, 0xd2, 0xb0, 0xac, 0x22, 0x28, 0x7a, 0x81, 0x28, 0x48, 0x84}),
 			},
 			{
 				Name: "sqliteInt.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x59, 0x50, 0xf2, 0x37, 0xd9, 0xf9, 0xf2, 0xd3, 0xef, 0x6b, 0xd8, 0xbe, 0x34, 0x2d, 0xcf, 0x64, 0x89, 0x22, 0x51, 0x42},
+				Hash: newHash([]byte{0x59, 0x50, 0xf2, 0x37, 0xd9, 0xf9, 0xf2, 0xd3, 0xef, 0x6b, 0xd8, 0xbe, 0x34, 0x2d, 0xcf, 0x64, 0x89, 0x22, 0x51, 0x42}),
 			},
 			{
 				Name: "sqliteLimit.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xc7, 0xae, 0xe5, 0x3c, 0xeb, 0xca, 0x94, 0xda, 0x51, 0xe7, 0x1a, 0x82, 0x2e, 0xa5, 0xa6, 0xde, 0xb9, 0x3, 0x85, 0xdf},
+				Hash: newHash([]byte{0xc7, 0xae, 0xe5, 0x3c, 0xeb, 0xca, 0x94, 0xda, 0x51, 0xe7, 0x1a, 0x82, 0x2e, 0xa5, 0xa6, 0xde, 0xb9, 0x3, 0x85, 0xdf}),
 			},
 			{
 				Name: "status.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x28, 0x34, 0x9e, 0x6d, 0x3d, 0x20, 0x88, 0xe0, 0x0, 0x3b, 0x76, 0xf8, 0xa, 0x89, 0x54, 0xfa, 0xec, 0x59, 0x30, 0xba},
+				Hash: newHash([]byte{0x28, 0x34, 0x9e, 0x6d, 0x3d, 0x20, 0x88, 0xe0, 0x0, 0x3b, 0x76, 0xf8, 0xa, 0x89, 0x54, 0xfa, 0xec, 0x59, 0x30, 0xba}),
 			},
 			{
 				Name: "table.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x26, 0xbb, 0xfb, 0x4f, 0x45, 0x6c, 0x42, 0x98, 0x25, 0x29, 0xea, 0x1a, 0x63, 0xa0, 0x17, 0x51, 0xdd, 0x3e, 0xe9, 0x5a},
+				Hash: newHash([]byte{0x26, 0xbb, 0xfb, 0x4f, 0x45, 0x6c, 0x42, 0x98, 0x25, 0x29, 0xea, 0x1a, 0x63, 0xa0, 0x17, 0x51, 0xdd, 0x3e, 0xe9, 0x5a}),
 			},
 			{
 				Name: "tclsqlite.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf1, 0xbb, 0x29, 0x21, 0xda, 0xc, 0x68, 0xa4, 0xf1, 0xc8, 0xe1, 0x5c, 0xf5, 0x66, 0xb2, 0x33, 0xe9, 0x2a, 0x51, 0x9f},
+				Hash: newHash([]byte{0xf1, 0xbb, 0x29, 0x21, 0xda, 0xc, 0x68, 0xa4, 0xf1, 0xc8, 0xe1, 0x5c, 0xf5, 0x66, 0xb2, 0x33, 0xe9, 0x2a, 0x51, 0x9f}),
 			},
 			{
 				Name: "test1.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xa6, 0x38, 0xe4, 0x80, 0xad, 0xdf, 0x14, 0x43, 0x9c, 0xdf, 0xa4, 0xee, 0x16, 0x4d, 0xc3, 0x1b, 0x79, 0xf8, 0xbc, 0xac},
+				Hash: newHash([]byte{0xa6, 0x38, 0xe4, 0x80, 0xad, 0xdf, 0x14, 0x43, 0x9c, 0xdf, 0xa4, 0xee, 0x16, 0x4d, 0xc3, 0x1b, 0x79, 0xf8, 0xbc, 0xac}),
 			},
 			{
 				Name: "test2.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd1, 0x30, 0xe9, 0xd0, 0x1b, 0x70, 0x24, 0xa5, 0xec, 0x6d, 0x73, 0x5, 0x92, 0xee, 0x4d, 0x1f, 0xb0, 0x2c, 0xfd, 0xb4},
+				Hash: newHash([]byte{0xd1, 0x30, 0xe9, 0xd0, 0x1b, 0x70, 0x24, 0xa5, 0xec, 0x6d, 0x73, 0x5, 0x92, 0xee, 0x4d, 0x1f, 0xb0, 0x2c, 0xfd, 0xb4}),
 			},
 			{
 				Name: "test3.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe3, 0xed, 0x31, 0xc, 0x81, 0x4, 0xfe, 0x36, 0x21, 0xce, 0xbb, 0xf, 0x51, 0xd1, 0x1, 0x45, 0x1, 0x8d, 0x4f, 0xac},
+				Hash: newHash([]byte{0xe3, 0xed, 0x31, 0xc, 0x81, 0x4, 0xfe, 0x36, 0x21, 0xce, 0xbb, 0xf, 0x51, 0xd1, 0x1, 0x45, 0x1, 0x8d, 0x4f, 0xac}),
 			},
 			{
 				Name: "test4.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xa6, 0x37, 0x5c, 0x7c, 0xc4, 0x3, 0xf6, 0xc, 0xaa, 0xb7, 0xe9, 0x59, 0x53, 0x3e, 0x3d, 0xb1, 0xff, 0x75, 0xa, 0xe4},
+				Hash: newHash([]byte{0xa6, 0x37, 0x5c, 0x7c, 0xc4, 0x3, 0xf6, 0xc, 0xaa, 0xb7, 0xe9, 0x59, 0x53, 0x3e, 0x3d, 0xb1, 0xff, 0x75, 0xa, 0xe4}),
 			},
 			{
 				Name: "test5.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x30, 0x3d, 0x12, 0x5, 0xb2, 0x26, 0x28, 0x42, 0x3d, 0x98, 0x6f, 0x71, 0xe2, 0x7c, 0x7c, 0xf7, 0x14, 0xa7, 0x45, 0xa6},
+				Hash: newHash([]byte{0x30, 0x3d, 0x12, 0x5, 0xb2, 0x26, 0x28, 0x42, 0x3d, 0x98, 0x6f, 0x71, 0xe2, 0x7c, 0x7c, 0xf7, 0x14, 0xa7, 0x45, 0xa6}),
 			},
 			{
 				Name: "test6.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xc1, 0x51, 0xea, 0x42, 0x98, 0x9b, 0xb, 0xe2, 0x4e, 0xe4, 0xb9, 0xa4, 0xbe, 0x37, 0x8b, 0x4f, 0x63, 0x6d, 0xb6, 0x41},
+				Hash: newHash([]byte{0xc1, 0x51, 0xea, 0x42, 0x98, 0x9b, 0xb, 0xe2, 0x4e, 0xe4, 0xb9, 0xa4, 0xbe, 0x37, 0x8b, 0x4f, 0x63, 0x6d, 0xb6, 0x41}),
 			},
 			{
 				Name: "test7.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x3c, 0xd4, 0xa2, 0x24, 0xd7, 0xe8, 0xe1, 0x6b, 0xd7, 0xcb, 0xe4, 0x9e, 0x2d, 0x3e, 0x94, 0xce, 0x9b, 0x17, 0xbd, 0x76},
+				Hash: newHash([]byte{0x3c, 0xd4, 0xa2, 0x24, 0xd7, 0xe8, 0xe1, 0x6b, 0xd7, 0xcb, 0xe4, 0x9e, 0x2d, 0x3e, 0x94, 0xce, 0x9b, 0x17, 0xbd, 0x76}),
 			},
 			{
 				Name: "test8.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xc5, 0x73, 0x93, 0x32, 0xd4, 0x6e, 0x57, 0x12, 0x1d, 0xa2, 0x7c, 0x3e, 0x88, 0xfd, 0xe7, 0x5a, 0xeb, 0x87, 0x10, 0xf7},
+				Hash: newHash([]byte{0xc5, 0x73, 0x93, 0x32, 0xd4, 0x6e, 0x57, 0x12, 0x1d, 0xa2, 0x7c, 0x3e, 0x88, 0xfd, 0xe7, 0x5a, 0xeb, 0x87, 0x10, 0xf7}),
 			},
 			{
 				Name: "test9.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe5, 0x99, 0x3e, 0x8f, 0xf7, 0x8f, 0x61, 0xc2, 0x43, 0x5b, 0x6f, 0x97, 0xa3, 0xb4, 0x63, 0xe2, 0x27, 0xc7, 0x67, 0xac},
+				Hash: newHash([]byte{0xe5, 0x99, 0x3e, 0x8f, 0xf7, 0x8f, 0x61, 0xc2, 0x43, 0x5b, 0x6f, 0x97, 0xa3, 0xb4, 0x63, 0xe2, 0x27, 0xc7, 0x67, 0xac}),
 			},
 			{
 				Name: "test_async.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb0, 0xb9, 0x43, 0x18, 0x5b, 0xfc, 0x23, 0xc1, 0x7f, 0xd0, 0x8f, 0x55, 0x76, 0x8c, 0xac, 0x12, 0xa9, 0xf5, 0x69, 0x51},
+				Hash: newHash([]byte{0xb0, 0xb9, 0x43, 0x18, 0x5b, 0xfc, 0x23, 0xc1, 0x7f, 0xd0, 0x8f, 0x55, 0x76, 0x8c, 0xac, 0x12, 0xa9, 0xf5, 0x69, 0x51}),
 			},
 			{
 				Name: "test_autoext.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb5, 0x1, 0x3f, 0x31, 0x73, 0xa2, 0x17, 0x6e, 0x2d, 0x9f, 0xc, 0xaa, 0x99, 0x19, 0x30, 0x36, 0xbf, 0xc3, 0x7e, 0x91},
+				Hash: newHash([]byte{0xb5, 0x1, 0x3f, 0x31, 0x73, 0xa2, 0x17, 0x6e, 0x2d, 0x9f, 0xc, 0xaa, 0x99, 0x19, 0x30, 0x36, 0xbf, 0xc3, 0x7e, 0x91}),
 			},
 			{
 				Name: "test_backup.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe9, 0x67, 0x42, 0x4a, 0x29, 0xf, 0x73, 0x8a, 0xec, 0xfd, 0xac, 0x57, 0x8e, 0x9b, 0x87, 0xa4, 0xc4, 0xae, 0x8d, 0x7f},
+				Hash: newHash([]byte{0xe9, 0x67, 0x42, 0x4a, 0x29, 0xf, 0x73, 0x8a, 0xec, 0xfd, 0xac, 0x57, 0x8e, 0x9b, 0x87, 0xa4, 0xc4, 0xae, 0x8d, 0x7f}),
 			},
 			{
 				Name: "test_btree.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xdb, 0x72, 0x88, 0x9b, 0x2a, 0xfb, 0x62, 0x72, 0x82, 0x8d, 0xda, 0x86, 0x6d, 0xcc, 0xf1, 0x22, 0xa4, 0x9a, 0x72, 0x99},
+				Hash: newHash([]byte{0xdb, 0x72, 0x88, 0x9b, 0x2a, 0xfb, 0x62, 0x72, 0x82, 0x8d, 0xda, 0x86, 0x6d, 0xcc, 0xf1, 0x22, 0xa4, 0x9a, 0x72, 0x99}),
 			},
 			{
 				Name: "test_config.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x53, 0x47, 0x27, 0xa0, 0x80, 0x42, 0xb6, 0xca, 0xd6, 0x7e, 0x26, 0x7e, 0x87, 0xb4, 0x3, 0xa4, 0x1a, 0x73, 0xb2, 0x99},
+				Hash: newHash([]byte{0x53, 0x47, 0x27, 0xa0, 0x80, 0x42, 0xb6, 0xca, 0xd6, 0x7e, 0x26, 0x7e, 0x87, 0xb4, 0x3, 0xa4, 0x1a, 0x73, 0xb2, 0x99}),
 			},
 			{
 				Name: "test_demovfs.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x63, 0x76, 0x27, 0x7, 0x1d, 0x9e, 0x28, 0xf4, 0xb3, 0x45, 0x1b, 0xbb, 0xdd, 0xf8, 0x8, 0xd1, 0xa9, 0x12, 0x0, 0xf8},
+				Hash: newHash([]byte{0x63, 0x76, 0x27, 0x7, 0x1d, 0x9e, 0x28, 0xf4, 0xb3, 0x45, 0x1b, 0xbb, 0xdd, 0xf8, 0x8, 0xd1, 0xa9, 0x12, 0x0, 0xf8}),
 			},
 			{
 				Name: "test_devsym.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x21, 0xf0, 0xf6, 0x84, 0xd8, 0x61, 0x11, 0x67, 0x70, 0xde, 0xfc, 0xde, 0xcd, 0x53, 0x2b, 0xa3, 0xee, 0xab, 0xa9, 0x75},
+				Hash: newHash([]byte{0x21, 0xf0, 0xf6, 0x84, 0xd8, 0x61, 0x11, 0x67, 0x70, 0xde, 0xfc, 0xde, 0xcd, 0x53, 0x2b, 0xa3, 0xee, 0xab, 0xa9, 0x75}),
 			},
 			{
 				Name: "test_fs.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x47, 0x8c, 0xad, 0x80, 0xb1, 0x6a, 0x90, 0x9b, 0x23, 0xbd, 0x3, 0xc2, 0xda, 0xd8, 0xb4, 0x49, 0xa7, 0x45, 0x87, 0xa1},
+				Hash: newHash([]byte{0x47, 0x8c, 0xad, 0x80, 0xb1, 0x6a, 0x90, 0x9b, 0x23, 0xbd, 0x3, 0xc2, 0xda, 0xd8, 0xb4, 0x49, 0xa7, 0x45, 0x87, 0xa1}),
 			},
 			{
 				Name: "test_func.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x6f, 0x9b, 0xb0, 0x3d, 0xc8, 0x8a, 0x21, 0xd6, 0x58, 0xbf, 0x99, 0x99, 0xba, 0xf6, 0x6d, 0xc1, 0xd5, 0x2e, 0xbc, 0x54},
+				Hash: newHash([]byte{0x6f, 0x9b, 0xb0, 0x3d, 0xc8, 0x8a, 0x21, 0xd6, 0x58, 0xbf, 0x99, 0x99, 0xba, 0xf6, 0x6d, 0xc1, 0xd5, 0x2e, 0xbc, 0x54}),
 			},
 			{
 				Name: "test_hexio.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb2, 0xb, 0x5c, 0xe7, 0x30, 0xab, 0x7f, 0xa8, 0x0, 0xd2, 0xd0, 0xcc, 0x38, 0xc7, 0x72, 0x75, 0x59, 0x3e, 0xbd, 0xbb},
+				Hash: newHash([]byte{0xb2, 0xb, 0x5c, 0xe7, 0x30, 0xab, 0x7f, 0xa8, 0x0, 0xd2, 0xd0, 0xcc, 0x38, 0xc7, 0x72, 0x75, 0x59, 0x3e, 0xbd, 0xbb}),
 			},
 			{
 				Name: "test_init.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe3, 0x72, 0x4d, 0x8b, 0xe3, 0x14, 0xdb, 0x9, 0xee, 0xa8, 0x4, 0xb, 0x9d, 0xdf, 0xc8, 0xa8, 0xbe, 0xee, 0x22, 0x91},
+				Hash: newHash([]byte{0xe3, 0x72, 0x4d, 0x8b, 0xe3, 0x14, 0xdb, 0x9, 0xee, 0xa8, 0x4, 0xb, 0x9d, 0xdf, 0xc8, 0xa8, 0xbe, 0xee, 0x22, 0x91}),
 			},
 			{
 				Name: "test_intarray.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf5, 0xc3, 0xd9, 0xe4, 0x5, 0x9a, 0x16, 0x56, 0x7, 0x34, 0x7, 0xe4, 0x3a, 0x92, 0x11, 0x79, 0x99, 0x69, 0x7b, 0x93},
+				Hash: newHash([]byte{0xf5, 0xc3, 0xd9, 0xe4, 0x5, 0x9a, 0x16, 0x56, 0x7, 0x34, 0x7, 0xe4, 0x3a, 0x92, 0x11, 0x79, 0x99, 0x69, 0x7b, 0x93}),
 			},
 			{
 				Name: "test_intarray.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x69, 0x13, 0x37, 0xd1, 0xae, 0xd6, 0x37, 0x15, 0xd6, 0x2e, 0x76, 0x26, 0x6f, 0xf, 0x3b, 0x50, 0x8b, 0x1, 0xa, 0x34},
+				Hash: newHash([]byte{0x69, 0x13, 0x37, 0xd1, 0xae, 0xd6, 0x37, 0x15, 0xd6, 0x2e, 0x76, 0x26, 0x6f, 0xf, 0x3b, 0x50, 0x8b, 0x1, 0xa, 0x34}),
 			},
 			{
 				Name: "test_journal.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe8, 0x70, 0x1a, 0x4e, 0xea, 0xdb, 0x8e, 0xad, 0x16, 0x9d, 0x60, 0x6, 0x40, 0x7d, 0x54, 0xa8, 0x98, 0x59, 0x2d, 0x70},
+				Hash: newHash([]byte{0xe8, 0x70, 0x1a, 0x4e, 0xea, 0xdb, 0x8e, 0xad, 0x16, 0x9d, 0x60, 0x6, 0x40, 0x7d, 0x54, 0xa8, 0x98, 0x59, 0x2d, 0x70}),
 			},
 			{
 				Name: "test_loadext.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x11, 0x37, 0xe3, 0xa9, 0xaa, 0xe9, 0x29, 0x6, 0xb8, 0x28, 0x9f, 0x6c, 0x3d, 0xaa, 0x61, 0xf0, 0xd0, 0x70, 0xf5, 0x5a},
+				Hash: newHash([]byte{0x11, 0x37, 0xe3, 0xa9, 0xaa, 0xe9, 0x29, 0x6, 0xb8, 0x28, 0x9f, 0x6c, 0x3d, 0xaa, 0x61, 0xf0, 0xd0, 0x70, 0xf5, 0x5a}),
 			},
 			{
 				Name: "test_malloc.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xcf, 0x98, 0xa8, 0xfb, 0x21, 0x82, 0xc0, 0xba, 0xf5, 0xa, 0xd5, 0x79, 0x79, 0xb6, 0x75, 0xbb, 0x70, 0x7a, 0x93, 0xb0},
+				Hash: newHash([]byte{0xcf, 0x98, 0xa8, 0xfb, 0x21, 0x82, 0xc0, 0xba, 0xf5, 0xa, 0xd5, 0x79, 0x79, 0xb6, 0x75, 0xbb, 0x70, 0x7a, 0x93, 0xb0}),
 			},
 			{
 				Name: "test_multiplex.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x62, 0x45, 0x41, 0xb3, 0x2a, 0x10, 0xd2, 0x1a, 0x2f, 0xd1, 0xa, 0x35, 0xee, 0x66, 0x32, 0xbd, 0xac, 0x55, 0x2d, 0x41},
+				Hash: newHash([]byte{0x62, 0x45, 0x41, 0xb3, 0x2a, 0x10, 0xd2, 0x1a, 0x2f, 0xd1, 0xa, 0x35, 0xee, 0x66, 0x32, 0xbd, 0xac, 0x55, 0x2d, 0x41}),
 			},
 			{
 				Name: "test_multiplex.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xb7, 0xe1, 0xaf, 0xea, 0x5f, 0xd7, 0x8b, 0x87, 0x58, 0x2, 0x65, 0xf8, 0x4c, 0x81, 0x61, 0x2c, 0xbd, 0x2, 0x5b, 0xaf},
+				Hash: newHash([]byte{0xb7, 0xe1, 0xaf, 0xea, 0x5f, 0xd7, 0x8b, 0x87, 0x58, 0x2, 0x65, 0xf8, 0x4c, 0x81, 0x61, 0x2c, 0xbd, 0x2, 0x5b, 0xaf}),
 			},
 			{
 				Name: "test_mutex.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xc9, 0xb4, 0xa2, 0x9a, 0xb7, 0x5c, 0x77, 0xea, 0x5f, 0x36, 0xb5, 0x19, 0x32, 0x56, 0xd7, 0xf, 0xe6, 0x58, 0xe, 0x95},
+				Hash: newHash([]byte{0xc9, 0xb4, 0xa2, 0x9a, 0xb7, 0x5c, 0x77, 0xea, 0x5f, 0x36, 0xb5, 0x19, 0x32, 0x56, 0xd7, 0xf, 0xe6, 0x58, 0xe, 0x95}),
 			},
 			{
 				Name: "test_onefile.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x69, 0x86, 0x74, 0x41, 0xb8, 0xcc, 0x9a, 0x62, 0x1a, 0xf3, 0x24, 0x13, 0xfc, 0x63, 0xda, 0x80, 0x99, 0x37, 0x64, 0xf4},
+				Hash: newHash([]byte{0x69, 0x86, 0x74, 0x41, 0xb8, 0xcc, 0x9a, 0x62, 0x1a, 0xf3, 0x24, 0x13, 0xfc, 0x63, 0xda, 0x80, 0x99, 0x37, 0x64, 0xf4}),
 			},
 			{
 				Name: "test_osinst.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x53, 0x14, 0x33, 0x31, 0x3e, 0xe3, 0x6c, 0x7, 0xeb, 0x21, 0xc0, 0x2f, 0x31, 0x15, 0xcb, 0x7a, 0x37, 0x48, 0x6c, 0x79},
+				Hash: newHash([]byte{0x53, 0x14, 0x33, 0x31, 0x3e, 0xe3, 0x6c, 0x7, 0xeb, 0x21, 0xc0, 0x2f, 0x31, 0x15, 0xcb, 0x7a, 0x37, 0x48, 0x6c, 0x79}),
 			},
 			{
 				Name: "test_pcache.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x8f, 0xcf, 0xe7, 0xe2, 0x6e, 0x3f, 0xf1, 0x74, 0x96, 0xb8, 0x40, 0xf5, 0xd6, 0x3c, 0x75, 0x78, 0x3a, 0xff, 0x81, 0x62},
+				Hash: newHash([]byte{0x8f, 0xcf, 0xe7, 0xe2, 0x6e, 0x3f, 0xf1, 0x74, 0x96, 0xb8, 0x40, 0xf5, 0xd6, 0x3c, 0x75, 0x78, 0x3a, 0xff, 0x81, 0x62}),
 			},
-			{Name: "test_quota.c", Mode: filemode.Regular, Hash: plumbing.Hash{
-				0xe5, 0x90, 0x99, 0x6c, 0xa4, 0xb8, 0x57, 0x4a, 0xb1, 0xe4, 0x18, 0x5d, 0x57, 0x77, 0x56, 0x66, 0x4a, 0xd2, 0x49, 0x5f}}, {Name: "test_quota.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x2d, 0x7, 0x67, 0xa1, 0x9a, 0xb7, 0xc3, 0xa4, 0x21, 0xcd, 0xba, 0x6a, 0x3, 0x49, 0x20, 0x43, 0x67, 0xc2, 0x2c, 0x81},
+			{Name: "test_quota.c", Mode: filemode.Regular, Hash: newHash([]byte{
+				0xe5, 0x90, 0x99, 0x6c, 0xa4, 0xb8, 0x57, 0x4a, 0xb1, 0xe4, 0x18, 0x5d, 0x57, 0x77, 0x56, 0x66, 0x4a, 0xd2, 0x49, 0x5f})}, {Name: "test_quota.h", Mode: filemode.Regular,
+				Hash: newHash([]byte{0x2d, 0x7, 0x67, 0xa1, 0x9a, 0xb7, 0xc3, 0xa4, 0x21, 0xcd, 0xba, 0x6a, 0x3, 0x49, 0x20, 0x43, 0x67, 0xc2, 0x2c, 0x81}),
 			},
 			{
 				Name: "test_rtree.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf5, 0x4a, 0xe9, 0xb0, 0x63, 0xbb, 0x73, 0x71, 0x2f, 0xcf, 0xc1, 0xc6, 0x83, 0x2e, 0x2a, 0x50, 0xf6, 0x2a, 0x97, 0xe7},
+				Hash: newHash([]byte{0xf5, 0x4a, 0xe9, 0xb0, 0x63, 0xbb, 0x73, 0x71, 0x2f, 0xcf, 0xc1, 0xc6, 0x83, 0x2e, 0x2a, 0x50, 0xf6, 0x2a, 0x97, 0xe7}),
 			},
 			{
 				Name: "test_schema.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x12, 0x64, 0x44, 0x67, 0x64, 0x7d, 0x51, 0x39, 0x4a, 0x1, 0xf9, 0xfa, 0x60, 0x37, 0x62, 0x98, 0x18, 0x54, 0x66, 0xfd},
+				Hash: newHash([]byte{0x12, 0x64, 0x44, 0x67, 0x64, 0x7d, 0x51, 0x39, 0x4a, 0x1, 0xf9, 0xfa, 0x60, 0x37, 0x62, 0x98, 0x18, 0x54, 0x66, 0xfd}),
 			},
 			{
 				Name: "test_server.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xed, 0x8, 0x18, 0xe6, 0xf6, 0x5f, 0x27, 0x28, 0x2d, 0xc7, 0xb1, 0xc1, 0x90, 0xec, 0x18, 0x8c, 0x89, 0x33, 0x0, 0x2b},
+				Hash: newHash([]byte{0xed, 0x8, 0x18, 0xe6, 0xf6, 0x5f, 0x27, 0x28, 0x2d, 0xc7, 0xb1, 0xc1, 0x90, 0xec, 0x18, 0x8c, 0x89, 0x33, 0x0, 0x2b}),
 			},
 			{
 				Name: "test_sqllog.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x4a, 0xa6, 0x8b, 0x7c, 0x42, 0x93, 0x23, 0xb8, 0xee, 0xbe, 0x6c, 0x9c, 0x2d, 0x7, 0xfc, 0x66, 0xd, 0x8d, 0x47, 0xc9},
+				Hash: newHash([]byte{0x4a, 0xa6, 0x8b, 0x7c, 0x42, 0x93, 0x23, 0xb8, 0xee, 0xbe, 0x6c, 0x9c, 0x2d, 0x7, 0xfc, 0x66, 0xd, 0x8d, 0x47, 0xc9}),
 			},
 			{
 				Name: "test_stat.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd4, 0xc9, 0x2, 0xb5, 0xea, 0x11, 0x1a, 0xd5, 0x8a, 0x73, 0x71, 0x12, 0xc2, 0x8f, 0x0, 0x38, 0x43, 0x4c, 0x85, 0xc0},
+				Hash: newHash([]byte{0xd4, 0xc9, 0x2, 0xb5, 0xea, 0x11, 0x1a, 0xd5, 0x8a, 0x73, 0x71, 0x12, 0xc2, 0x8f, 0x0, 0x38, 0x43, 0x4c, 0x85, 0xc0}),
 			},
 			{
 				Name: "test_superlock.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x93, 0x6f, 0xca, 0xd0, 0xc5, 0x6f, 0x6b, 0xc8, 0x58, 0x9, 0x74, 0x2f, 0x6a, 0xe1, 0xc1, 0xee, 0xb8, 0xb7, 0xd2, 0xf1},
+				Hash: newHash([]byte{0x93, 0x6f, 0xca, 0xd0, 0xc5, 0x6f, 0x6b, 0xc8, 0x58, 0x9, 0x74, 0x2f, 0x6a, 0xe1, 0xc1, 0xee, 0xb8, 0xb7, 0xd2, 0xf1}),
 			},
 			{
 				Name: "test_syscall.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x7c, 0x8, 0x73, 0xc1, 0x6d, 0x84, 0x32, 0x2, 0xf3, 0xe, 0x2d, 0xb9, 0x45, 0x9f, 0xa2, 0x99, 0x75, 0xea, 0x5e, 0x68},
+				Hash: newHash([]byte{0x7c, 0x8, 0x73, 0xc1, 0x6d, 0x84, 0x32, 0x2, 0xf3, 0xe, 0x2d, 0xb9, 0x45, 0x9f, 0xa2, 0x99, 0x75, 0xea, 0x5e, 0x68}),
 			},
 			{
 				Name: "test_tclvar.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x12, 0x19, 0x19, 0xc, 0x3, 0x0, 0xfd, 0x5e, 0xc7, 0xa3, 0xc5, 0x84, 0x8, 0xf3, 0x38, 0x43, 0xd2, 0xe, 0xee, 0x15},
+				Hash: newHash([]byte{0x12, 0x19, 0x19, 0xc, 0x3, 0x0, 0xfd, 0x5e, 0xc7, 0xa3, 0xc5, 0x84, 0x8, 0xf3, 0x38, 0x43, 0xd2, 0xe, 0xee, 0x15}),
 			},
 			{
 				Name: "test_thread.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x2f, 0x93, 0x63, 0xb7, 0x50, 0x1e, 0x51, 0x19, 0x81, 0xfe, 0x32, 0x83, 0x1f, 0xf2, 0xe8, 0xfd, 0x2f, 0x30, 0xc4, 0x93},
+				Hash: newHash([]byte{0x2f, 0x93, 0x63, 0xb7, 0x50, 0x1e, 0x51, 0x19, 0x81, 0xfe, 0x32, 0x83, 0x1f, 0xf2, 0xe8, 0xfd, 0x2f, 0x30, 0xc4, 0x93}),
 			},
 			{
 				Name: "test_vfs.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xfc, 0xd5, 0x77, 0x43, 0x9c, 0xfd, 0x6c, 0x72, 0xdd, 0xe4, 0x83, 0x58, 0x92, 0x14, 0x20, 0xcf, 0x6e, 0xf1, 0xf8, 0x6d},
+				Hash: newHash([]byte{0xfc, 0xd5, 0x77, 0x43, 0x9c, 0xfd, 0x6c, 0x72, 0xdd, 0xe4, 0x83, 0x58, 0x92, 0x14, 0x20, 0xcf, 0x6e, 0xf1, 0xf8, 0x6d}),
 			},
 			{
 				Name: "test_vfstrace.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xa, 0xac, 0xc0, 0x1f, 0xe4, 0x2e, 0x77, 0xfe, 0xb8, 0x58, 0xe4, 0xbe, 0xd0, 0xcb, 0x7e, 0x4, 0xa4, 0x35, 0xb2, 0x10},
+				Hash: newHash([]byte{0xa, 0xac, 0xc0, 0x1f, 0xe4, 0x2e, 0x77, 0xfe, 0xb8, 0x58, 0xe4, 0xbe, 0xd0, 0xcb, 0x7e, 0x4, 0xa4, 0x35, 0xb2, 0x10}),
 			},
 			{
 				Name: "test_wsd.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x99, 0xe4, 0xa0, 0x56, 0x58, 0x1f, 0x58, 0xf4, 0x53, 0x6f, 0xdb, 0x5a, 0x5d, 0xf7, 0x5c, 0x74, 0x69, 0x8a, 0x81, 0x62},
+				Hash: newHash([]byte{0x99, 0xe4, 0xa0, 0x56, 0x58, 0x1f, 0x58, 0xf4, 0x53, 0x6f, 0xdb, 0x5a, 0x5d, 0xf7, 0x5c, 0x74, 0x69, 0x8a, 0x81, 0x62}),
 			},
 			{
 				Name: "tokenize.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xfa, 0xea, 0x5f, 0x26, 0xc7, 0x9c, 0x5e, 0x18, 0x8f, 0xa8, 0x7f, 0x2f, 0xdf, 0x6f, 0xf7, 0x6a, 0x7a, 0x60, 0x6, 0xc5},
+				Hash: newHash([]byte{0xfa, 0xea, 0x5f, 0x26, 0xc7, 0x9c, 0x5e, 0x18, 0x8f, 0xa8, 0x7f, 0x2f, 0xdf, 0x6f, 0xf7, 0x6a, 0x7a, 0x60, 0x6, 0xc5}),
 			},
 			{
 				Name: "trigger.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf1, 0xff, 0x76, 0x6e, 0x20, 0x2a, 0x45, 0x18, 0xec, 0x10, 0xe5, 0x27, 0x12, 0xc, 0xd3, 0xe, 0x83, 0xfb, 0xd0, 0x34},
+				Hash: newHash([]byte{0xf1, 0xff, 0x76, 0x6e, 0x20, 0x2a, 0x45, 0x18, 0xec, 0x10, 0xe5, 0x27, 0x12, 0xc, 0xd3, 0xe, 0x83, 0xfb, 0xd0, 0x34}),
 			},
 			{
 				Name: "update.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x3a, 0xb1, 0xab, 0x2a, 0x4b, 0x65, 0xda, 0x3f, 0x19, 0x8c, 0x15, 0x84, 0xd5, 0x4d, 0x36, 0xf1, 0x8c, 0xa1, 0x21, 0x4a},
+				Hash: newHash([]byte{0x3a, 0xb1, 0xab, 0x2a, 0x4b, 0x65, 0xda, 0x3f, 0x19, 0x8c, 0x15, 0x84, 0xd5, 0x4d, 0x36, 0xf1, 0x8c, 0xa1, 0x21, 0x4a}),
 			},
 			{
 				Name: "utf.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x6d, 0x5b, 0x1b, 0xfe, 0x40, 0xc, 0x37, 0x48, 0xaa, 0x70, 0xa3, 0xb2, 0xfd, 0x5e, 0xe, 0xac, 0x5f, 0xc0, 0x4d, 0xe2},
+				Hash: newHash([]byte{0x6d, 0x5b, 0x1b, 0xfe, 0x40, 0xc, 0x37, 0x48, 0xaa, 0x70, 0xa3, 0xb2, 0xfd, 0x5e, 0xe, 0xac, 0x5f, 0xc0, 0x4d, 0xe2}),
 			},
 			{
 				Name: "util.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xd8, 0x3a, 0x63, 0x1, 0x5f, 0xd8, 0x7d, 0xcc, 0x4f, 0xb4, 0x41, 0x66, 0xfa, 0xbf, 0x2e, 0x9b, 0xc9, 0x67, 0x1e, 0xb8},
+				Hash: newHash([]byte{0xd8, 0x3a, 0x63, 0x1, 0x5f, 0xd8, 0x7d, 0xcc, 0x4f, 0xb4, 0x41, 0x66, 0xfa, 0xbf, 0x2e, 0x9b, 0xc9, 0x67, 0x1e, 0xb8}),
 			},
 			{
 				Name: "vacuum.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x4a, 0xfb, 0x2c, 0xca, 0x64, 0xdd, 0x60, 0x76, 0x11, 0x22, 0x2c, 0x7, 0x93, 0x2d, 0x12, 0xea, 0xcf, 0xa, 0x2c, 0x22},
+				Hash: newHash([]byte{0x4a, 0xfb, 0x2c, 0xca, 0x64, 0xdd, 0x60, 0x76, 0x11, 0x22, 0x2c, 0x7, 0x93, 0x2d, 0x12, 0xea, 0xcf, 0xa, 0x2c, 0x22}),
 			},
 			{
 				Name: "vdbe.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xf3, 0x43, 0xe1, 0x3d, 0x4e, 0x91, 0x78, 0x4b, 0x15, 0x88, 0x10, 0xc5, 0xb7, 0xd4, 0x46, 0x84, 0xdf, 0xbf, 0xa2, 0xa5},
+				Hash: newHash([]byte{0xf3, 0x43, 0xe1, 0x3d, 0x4e, 0x91, 0x78, 0x4b, 0x15, 0x88, 0x10, 0xc5, 0xb7, 0xd4, 0x46, 0x84, 0xdf, 0xbf, 0xa2, 0xa5}),
 			},
 			{
 				Name: "vdbe.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xfa, 0x7b, 0x31, 0xb7, 0x27, 0xa, 0x90, 0xd4, 0xf6, 0x37, 0x36, 0x5a, 0xfc, 0xc9, 0xbd, 0xa1, 0xd1, 0xb1, 0xe1, 0xd6},
+				Hash: newHash([]byte{0xfa, 0x7b, 0x31, 0xb7, 0x27, 0xa, 0x90, 0xd4, 0xf6, 0x37, 0x36, 0x5a, 0xfc, 0xc9, 0xbd, 0xa1, 0xd1, 0xb1, 0xe1, 0xd6}),
 			},
 			{
 				Name: "vdbeInt.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x3a, 0x5b, 0x40, 0x28, 0xbb, 0xd6, 0xc9, 0x56, 0x10, 0xd7, 0xc, 0xce, 0x3, 0x69, 0xdf, 0xcd, 0x60, 0x7a, 0xa9, 0x0},
+				Hash: newHash([]byte{0x3a, 0x5b, 0x40, 0x28, 0xbb, 0xd6, 0xc9, 0x56, 0x10, 0xd7, 0xc, 0xce, 0x3, 0x69, 0xdf, 0xcd, 0x60, 0x7a, 0xa9, 0x0}),
 			},
 			{
 				Name: "vdbeapi.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x7c, 0x86, 0x1e, 0x2d, 0x47, 0x21, 0x8c, 0x91, 0x63, 0x31, 0x77, 0x77, 0xc3, 0x7, 0x21, 0x99, 0xe9, 0xb4, 0x2, 0x80},
+				Hash: newHash([]byte{0x7c, 0x86, 0x1e, 0x2d, 0x47, 0x21, 0x8c, 0x91, 0x63, 0x31, 0x77, 0x77, 0xc3, 0x7, 0x21, 0x99, 0xe9, 0xb4, 0x2, 0x80}),
 			},
 			{
 				Name: "vdbeaux.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x2c, 0x42, 0x69, 0xa5, 0x9e, 0x6d, 0xbc, 0xe8, 0x67, 0x1c, 0x47, 0x4f, 0x34, 0x61, 0x90, 0xbe, 0x2a, 0xe, 0x18, 0x51},
+				Hash: newHash([]byte{0x2c, 0x42, 0x69, 0xa5, 0x9e, 0x6d, 0xbc, 0xe8, 0x67, 0x1c, 0x47, 0x4f, 0x34, 0x61, 0x90, 0xbe, 0x2a, 0xe, 0x18, 0x51}),
 			},
 			{
 				Name: "vdbeblob.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x2e, 0x8f, 0xd8, 0xee, 0x74, 0x47, 0xe6, 0x46, 0x46, 0xe3, 0x49, 0x4b, 0x4c, 0x4, 0x1d, 0x3a, 0x4a, 0xbb, 0x8, 0x85},
+				Hash: newHash([]byte{0x2e, 0x8f, 0xd8, 0xee, 0x74, 0x47, 0xe6, 0x46, 0x46, 0xe3, 0x49, 0x4b, 0x4c, 0x4, 0x1d, 0x3a, 0x4a, 0xbb, 0x8, 0x85}),
 			},
 			{
 				Name: "vdbemem.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x8f, 0xc2, 0x22, 0xe2, 0xde, 0x20, 0x50, 0x14, 0x50, 0xec, 0xea, 0x9d, 0x4e, 0xbf, 0xaa, 0xc9, 0x81, 0x4a, 0xae, 0x59},
+				Hash: newHash([]byte{0x8f, 0xc2, 0x22, 0xe2, 0xde, 0x20, 0x50, 0x14, 0x50, 0xec, 0xea, 0x9d, 0x4e, 0xbf, 0xaa, 0xc9, 0x81, 0x4a, 0xae, 0x59}),
 			},
 			{
 				Name: "vdbesort.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xfd, 0xfc, 0x4a, 0x79, 0xdd, 0xc9, 0x6e, 0x59, 0x9b, 0x1b, 0xe, 0xeb, 0xac, 0xbd, 0xb8, 0x45, 0xc6, 0x38, 0x13, 0xb2},
+				Hash: newHash([]byte{0xfd, 0xfc, 0x4a, 0x79, 0xdd, 0xc9, 0x6e, 0x59, 0x9b, 0x1b, 0xe, 0xeb, 0xac, 0xbd, 0xb8, 0x45, 0xc6, 0x38, 0x13, 0xb2}),
 			},
 			{
 				Name: "vdbetrace.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x35, 0x62, 0x77, 0xe8, 0xd2, 0x3b, 0xca, 0xdb, 0x67, 0x6b, 0x59, 0xd1, 0xa4, 0xdc, 0xf8, 0x42, 0xfd, 0xc4, 0xc9, 0x72},
+				Hash: newHash([]byte{0x35, 0x62, 0x77, 0xe8, 0xd2, 0x3b, 0xca, 0xdb, 0x67, 0x6b, 0x59, 0xd1, 0xa4, 0xdc, 0xf8, 0x42, 0xfd, 0xc4, 0xc9, 0x72}),
 			},
 			{
 				Name: "vtab.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x95, 0x82, 0x2, 0xc3, 0x1e, 0x24, 0x15, 0xb, 0x60, 0xf1, 0xa, 0x8a, 0xf, 0x74, 0x41, 0xaf, 0xac, 0x3f, 0xbb, 0x1c},
+				Hash: newHash([]byte{0x95, 0x82, 0x2, 0xc3, 0x1e, 0x24, 0x15, 0xb, 0x60, 0xf1, 0xa, 0x8a, 0xf, 0x74, 0x41, 0xaf, 0xac, 0x3f, 0xbb, 0x1c}),
 			},
 			{
 				Name: "wal.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe6, 0x42, 0xea, 0x21, 0x5, 0xb5, 0xc5, 0x4a, 0xf3, 0x5, 0x88, 0x9, 0x62, 0x69, 0xab, 0x75, 0xcb, 0xef, 0x8f, 0xf2},
+				Hash: newHash([]byte{0xe6, 0x42, 0xea, 0x21, 0x5, 0xb5, 0xc5, 0x4a, 0xf3, 0x5, 0x88, 0x9, 0x62, 0x69, 0xab, 0x75, 0xcb, 0xef, 0x8f, 0xf2}),
 			},
 			{
 				Name: "wal.h", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0x9, 0x25, 0x46, 0x35, 0x4b, 0x34, 0xc0, 0xab, 0x3d, 0x20, 0x5, 0x6a, 0x7f, 0x8a, 0x8a, 0x52, 0xe4, 0xd0, 0xb5, 0xf5},
+				Hash: newHash([]byte{0x9, 0x25, 0x46, 0x35, 0x4b, 0x34, 0xc0, 0xab, 0x3d, 0x20, 0x5, 0x6a, 0x7f, 0x8a, 0x8a, 0x52, 0xe4, 0xd0, 0xb5, 0xf5}),
 			},
 			{
 				Name: "walker.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe7, 0x1e, 0xd2, 0xac, 0x48, 0x4c, 0x91, 0x6c, 0x1c, 0xc1, 0x0, 0x7e, 0x5e, 0x5, 0xda, 0x47, 0x1c, 0xb4, 0x95, 0x99},
+				Hash: newHash([]byte{0xe7, 0x1e, 0xd2, 0xac, 0x48, 0x4c, 0x91, 0x6c, 0x1c, 0xc1, 0x0, 0x7e, 0x5e, 0x5, 0xda, 0x47, 0x1c, 0xb4, 0x95, 0x99}),
 			},
 			{
 				Name: "where.c", Mode: filemode.Regular,
-				Hash: plumbing.Hash{0xe6, 0x14, 0xf4, 0xa6, 0xd8, 0x64, 0xe7, 0xe, 0xc4, 0x32, 0x8d, 0xb, 0xdb, 0x25, 0x4e, 0x3a, 0xc9, 0xf0, 0xd2, 0x87},
+				Hash: newHash([]byte{0xe6, 0x14, 0xf4, 0xa6, 0xd8, 0x64, 0xe7, 0xe, 0xc4, 0x32, 0x8d, 0xb, 0xdb, 0x25, 0x4e, 0x3a, 0xc9, 0xf0, 0xd2, 0x87}),
 			},
 		},
-		Hash: plumbing.Hash{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+		Hash: newHash([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}),
 		s:    (storer.EncodedObjectStorer)(nil),
 		m:    map[string]*TreeEntry(nil),
 	}
@@ -1653,18 +1653,8 @@ func (s *TreeSuite) TestTreeDecodeReadBug() {
 	s.True(entriesEquals(obtained.Entries, expected.Entries))
 }
 
-func FuzzDecode(f *testing.F) {
-
-	f.Fuzz(func(t *testing.T, input []byte) {
-
-		obj := &SortReadObject{
-			t:    plumbing.TreeObject,
-			h:    plumbing.ZeroHash,
-			cont: input,
-			sz:   int64(len(input)),
-		}
-
-		newTree := &Tree{}
-		newTree.Decode(obj)
-	})
+func newHash(in []byte) plumbing.Hash {
+	var h plumbing.Hash
+	h.Write(in)
+	return h
 }
diff --git a/plumbing/protocol/packp/ulreq_decode_test.go b/plumbing/protocol/packp/ulreq_decode_test.go
index 96f5f47d..362839d6 100644
--- a/plumbing/protocol/packp/ulreq_decode_test.go
+++ b/plumbing/protocol/packp/ulreq_decode_test.go
@@ -11,7 +11,6 @@ import (
 
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/format/pktline"
-	"github.com/go-git/go-git/v5/plumbing/hash"
 	"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
 	"github.com/stretchr/testify/suite"
 )
@@ -140,9 +139,7 @@ type byHash []plumbing.Hash
 func (a byHash) Len() int      { return len(a) }
 func (a byHash) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 func (a byHash) Less(i, j int) bool {
-	ii := [hash.Size]byte(a[i])
-	jj := [hash.Size]byte(a[j])
-	return bytes.Compare(ii[:], jj[:]) < 0
+	return a[i].Compare(a[j].Bytes()) < 0
 }
 
 func (s *UlReqDecodeSuite) TestManyWantsBadWant() {
diff --git a/repository_test.go b/repository_test.go
index 891deaf0..4562c468 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/cache"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/plumbing/protocol/packp"
 	"github.com/go-git/go-git/v5/plumbing/storer"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/storage"
@@ -1298,6 +1299,36 @@ func (s *RepositorySuite) TestFetchContext() {
 	s.NotNil(r.FetchContext(ctx, &FetchOptions{}))
 }
 
+func (s *RepositorySuite) TestFetchWithFilters() {
+	r, _ := Init(memory.NewStorage(), nil)
+	_, err := r.CreateRemote(&config.RemoteConfig{
+		Name: DefaultRemoteName,
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
+	})
+	s.NoError(err)
+
+	err = r.Fetch(&FetchOptions{
+		Filter: packp.FilterBlobNone(),
+	})
+	s.ErrorIs(err, ErrFilterNotSupported)
+
+}
+func (s *RepositorySuite) TestFetchWithFiltersReal() {
+	r, _ := Init(memory.NewStorage(), nil)
+	_, err := r.CreateRemote(&config.RemoteConfig{
+		Name: DefaultRemoteName,
+		URLs: []string{"https://github.com/git-fixtures/basic.git"},
+	})
+	s.NoError(err)
+	err = r.Fetch(&FetchOptions{
+		Filter: packp.FilterBlobNone(),
+	})
+	s.NoError(err)
+	blob, err := r.BlobObject(plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"))
+	s.NotNil(err)
+	s.Nil(blob)
+
+}
 func (s *RepositorySuite) TestCloneWithProgress() {
 	fs := memfs.New()
 
@@ -1636,6 +1667,18 @@ func (s *RepositorySuite) TestCloneDetachedHEADAnnotatedTag() {
 	s.Equal(7, count)
 }
 
+func (s *RepositorySuite) TestCloneWithFilter() {
+	r, _ := Init(memory.NewStorage(), nil)
+
+	err := r.clone(context.Background(), &CloneOptions{
+		URL:    "https://github.com/git-fixtures/basic.git",
+		Filter: packp.FilterTreeDepth(0),
+	})
+	s.NoError(err)
+	blob, err := r.BlobObject(plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"))
+	s.NotNil(err)
+	s.Nil(blob)
+}
 func (s *RepositorySuite) TestPush() {
 	url, err := os.MkdirTemp("", "")
 	s.NoError(err)
diff --git a/storage/filesystem/object_test.go b/storage/filesystem/object_test.go
index 85ca0e6f..e177eda9 100644
--- a/storage/filesystem/object_test.go
+++ b/storage/filesystem/object_test.go
@@ -426,7 +426,7 @@ func (s *FsSuite) TestHashesWithPrefixFromPackfile() {
 
 		expected := plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
 		// Only pass the first 8 bytes
-		hashes, err := o.HashesWithPrefix(expected[:8])
+		hashes, err := o.HashesWithPrefix(expected.Bytes()[:8])
 		s.NoError(err)
 		s.Len(hashes, 1)
 		s.Equal(expected, hashes[0])
diff --git a/worktree_test.go b/worktree_test.go
index 31effb2e..b976cb92 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -1283,7 +1283,7 @@ func (s *WorktreeSuite) TestResetHardSubFolders() {
 	s.NoError(err)
 	s.False(status.IsClean())
 
-	err = w.Reset(&ResetOptions{Files: []string{"dir/testfile.txt"}, Mode: HardReset})
+	err = w.Reset(&ResetOptions{Files: []string{"./dir/testfile.txt"}, Mode: HardReset})
 	s.NoError(err)
 
 	status, err = w.Status()
@@ -2019,6 +2019,65 @@ func (s *WorktreeSuite) TestAddGlob() {
 	s.Equal(Unmodified, file.Worktree)
 }
 
+func (s *WorktreeSuite) TestAddFilenameStartingWithDot() {
+	fs := memfs.New()
+	w := &Worktree{
+		r:          s.Repository,
+		Filesystem: fs,
+	}
+
+	err := w.Checkout(&CheckoutOptions{Force: true})
+	s.NoError(err)
+
+	idx, err := w.r.Storer.Index()
+	s.NoError(err)
+	s.Len(idx.Entries, 9)
+
+	err = util.WriteFile(w.Filesystem, "qux", []byte("QUX"), 0o755)
+	s.NoError(err)
+	err = util.WriteFile(w.Filesystem, "baz", []byte("BAZ"), 0o755)
+	s.NoError(err)
+	err = util.WriteFile(w.Filesystem, "foo/bar/baz", []byte("BAZ"), 0o755)
+	s.NoError(err)
+
+	_, err = w.Add("./qux")
+	s.NoError(err)
+
+	_, err = w.Add("./baz")
+	s.NoError(err)
+
+	_, err = w.Add("foo/bar/../bar/./baz")
+	s.NoError(err)
+
+	idx, err = w.r.Storer.Index()
+	s.NoError(err)
+	s.Len(idx.Entries, 12)
+
+	e, err := idx.Entry("qux")
+	s.NoError(err)
+	s.Equal(filemode.Executable, e.Mode)
+
+	e, err = idx.Entry("baz")
+	s.NoError(err)
+	s.Equal(filemode.Executable, e.Mode)
+
+	status, err := w.Status()
+	s.NoError(err)
+	s.Len(status, 3)
+
+	file := status.File("qux")
+	s.Equal(Added, file.Staging)
+	s.Equal(Unmodified, file.Worktree)
+
+	file = status.File("baz")
+	s.Equal(Added, file.Staging)
+	s.Equal(Unmodified, file.Worktree)
+
+	file = status.File("foo/bar/baz")
+	s.Equal(Added, file.Staging)
+	s.Equal(Unmodified, file.Worktree)
+}
+
 func (s *WorktreeSuite) TestAddGlobErrorNoMatches() {
 	r, _ := Init(memory.NewStorage(), memfs.New())
 	w, _ := r.Worktree()
@@ -3212,7 +3271,7 @@ func (s *WorktreeSuite) TestRestoreStaged() {
 	s.ErrorIs(err, ErrNoRestorePaths)
 
 	// Restore Staged files in 2 groups and confirm status
-	opts.Files = []string{names[0], names[1]}
+	opts.Files = []string{names[0], "./" + names[1]}
 	err = w.Restore(&opts)
 	s.NoError(err)
 	verifyStatus(s, "Restored First", w, names, []FileStatus{
@@ -3227,7 +3286,7 @@ func (s *WorktreeSuite) TestRestoreStaged() {
 	s.NoError(err)
 	s.Equal("Foo Bar:11", string(contents))
 
-	opts.Files = []string{names[2], names[3]}
+	opts.Files = []string{"./" + names[2], names[3]}
 	err = w.Restore(&opts)
 	s.NoError(err)
 	verifyStatus(s, "Restored Second", w, names, []FileStatus{