diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 53a9393d5f..043dbb44bd 100644
--- a/modules/git/batch_reader.go
+++ b/modules/git/batch_reader.go
@@ -203,16 +203,7 @@ headerLoop:
 	}
 
 	// Discard the rest of the tag
-	discard := size - n + 1
-	for discard > math.MaxInt32 {
-		_, err := rd.Discard(math.MaxInt32)
-		if err != nil {
-			return id, err
-		}
-		discard -= math.MaxInt32
-	}
-	_, err := rd.Discard(int(discard))
-	return id, err
+	return id, DiscardFull(rd, size-n+1)
 }
 
 // ReadTreeID reads a tree ID from a cat-file --batch stream, throwing away the rest of the stream.
@@ -238,16 +229,7 @@ headerLoop:
 	}
 
 	// Discard the rest of the commit
-	discard := size - n + 1
-	for discard > math.MaxInt32 {
-		_, err := rd.Discard(math.MaxInt32)
-		if err != nil {
-			return id, err
-		}
-		discard -= math.MaxInt32
-	}
-	_, err := rd.Discard(int(discard))
-	return id, err
+	return id, DiscardFull(rd, size-n+1)
 }
 
 // git tree files are a list:
@@ -345,3 +327,21 @@ func init() {
 	_, filename, _, _ := runtime.Caller(0)
 	callerPrefix = strings.TrimSuffix(filename, "modules/git/batch_reader.go")
 }
+
+func DiscardFull(rd *bufio.Reader, discard int64) error {
+	if discard > math.MaxInt32 {
+		n, err := rd.Discard(math.MaxInt32)
+		discard -= int64(n)
+		if err != nil {
+			return err
+		}
+	}
+	for discard > 0 {
+		n, err := rd.Discard(int(discard))
+		discard -= int64(n)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go
index 6e8a48b1db..9e1c2a0376 100644
--- a/modules/git/blob_nogogit.go
+++ b/modules/git/blob_nogogit.go
@@ -9,7 +9,6 @@ import (
 	"bufio"
 	"bytes"
 	"io"
-	"math"
 
 	"code.gitea.io/gitea/modules/log"
 )
@@ -104,25 +103,6 @@ func (b *blobReader) Read(p []byte) (n int, err error) {
 // Close implements io.Closer
 func (b *blobReader) Close() error {
 	defer b.cancel()
-	if b.n > 0 {
-		for b.n > math.MaxInt32 {
-			n, err := b.rd.Discard(math.MaxInt32)
-			b.n -= int64(n)
-			if err != nil {
-				return err
-			}
-			b.n -= math.MaxInt32
-		}
-		n, err := b.rd.Discard(int(b.n))
-		b.n -= int64(n)
-		if err != nil {
-			return err
-		}
-	}
-	if b.n == 0 {
-		_, err := b.rd.Discard(1)
-		b.n--
-		return err
-	}
-	return nil
+
+	return DiscardFull(b.rd, b.n+1)
 }
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index e469d2cab6..a5d18694f7 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -151,6 +151,9 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
 			return nil, err
 		}
 		if typ != "commit" {
+			if err := DiscardFull(batchReader, size+1); err != nil {
+				return nil, err
+			}
 			return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
 		}
 		c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index a725f4799d..4c65249089 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -169,6 +169,10 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
 				} else {
 					break commitReadingLoop
 				}
+			default:
+				if err := git.DiscardFull(batchReader, size+1); err != nil {
+					return nil, err
+				}
 			}
 		}
 	}
diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go
index f0214e1ff8..a7031184e2 100644
--- a/modules/git/repo_commit_nogogit.go
+++ b/modules/git/repo_commit_nogogit.go
@@ -121,8 +121,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID)
 		return commit, nil
 	default:
 		log.Debug("Unknown typ: %s", typ)
-		_, err = rd.Discard(int(size) + 1)
-		if err != nil {
+		if err := DiscardFull(rd, size+1); err != nil {
 			return nil, err
 		}
 		return nil, ErrNotExist{
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go
index b313e2298f..1a3fdbb1f8 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/repo_language_stats_nogogit.go
@@ -7,10 +7,8 @@
 package git
 
 import (
-	"bufio"
 	"bytes"
 	"io"
-	"math"
 	"strings"
 
 	"code.gitea.io/gitea/modules/analyze"
@@ -174,8 +172,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 				return nil, err
 			}
 			content = contentBuf.Bytes()
-			err = discardFull(batchReader, discard)
-			if err != nil {
+			if err := DiscardFull(batchReader, discard); err != nil {
 				return nil, err
 			}
 		}
@@ -224,21 +221,3 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 
 	return mergeLanguageStats(sizes), nil
 }
-
-func discardFull(rd *bufio.Reader, discard int64) error {
-	if discard > math.MaxInt32 {
-		n, err := rd.Discard(math.MaxInt32)
-		discard -= int64(n)
-		if err != nil {
-			return err
-		}
-	}
-	for discard > 0 {
-		n, err := rd.Discard(int(discard))
-		discard -= int64(n)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go
index 5d98fadd54..cbab39f8c5 100644
--- a/modules/git/repo_tag_nogogit.go
+++ b/modules/git/repo_tag_nogogit.go
@@ -103,6 +103,9 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
 		return nil, err
 	}
 	if typ != "tag" {
+		if err := DiscardFull(rd, size+1); err != nil {
+			return nil, err
+		}
 		return nil, ErrNotExist{ID: tagID.String()}
 	}
 
diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go
index 20c92a79ed..582247b4a4 100644
--- a/modules/git/repo_tree_nogogit.go
+++ b/modules/git/repo_tree_nogogit.go
@@ -58,6 +58,9 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
 		tree.entriesParsed = true
 		return tree, nil
 	default:
+		if err := DiscardFull(rd, size+1); err != nil {
+			return nil, err
+		}
 		return nil, ErrNotExist{
 			ID: id.String(),
 		}
diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go
index 89d3aebbc0..28d02c7e81 100644
--- a/modules/git/tree_nogogit.go
+++ b/modules/git/tree_nogogit.go
@@ -7,7 +7,6 @@ package git
 
 import (
 	"io"
-	"math"
 	"strings"
 )
 
@@ -63,19 +62,8 @@ func (t *Tree) ListEntries() (Entries, error) {
 		}
 
 		// Not a tree just use ls-tree instead
-		for sz > math.MaxInt32 {
-			discarded, err := rd.Discard(math.MaxInt32)
-			sz -= int64(discarded)
-			if err != nil {
-				return nil, err
-			}
-		}
-		for sz > 0 {
-			discarded, err := rd.Discard(int(sz))
-			sz -= int64(discarded)
-			if err != nil {
-				return nil, err
-			}
+		if err := DiscardFull(rd, sz+1); err != nil {
+			return nil, err
 		}
 	}
 
diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go
new file mode 100644
index 0000000000..6d2b5c84d5
--- /dev/null
+++ b/modules/git/tree_test.go
@@ -0,0 +1,27 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSubTree_Issue29101(t *testing.T) {
+	repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
+	assert.NoError(t, err)
+	defer repo.Close()
+
+	commit, err := repo.GetCommit("ce064814f4a0d337b333e646ece456cd39fab612")
+	assert.NoError(t, err)
+
+	// old code could produce a different error if called multiple times
+	for i := 0; i < 10; i++ {
+		_, err = commit.SubTree("file1.txt")
+		assert.Error(t, err)
+		assert.True(t, IsErrNotExist(err))
+	}
+}