loading up the forgejo repo on tangled to test page performance

[CHORE] Drop `go-git` support

See https://codeberg.org/forgejo/discussions/issues/164 for the
rationale and discussion of this change.

Everything related to the `go-git` dependency is dropped (Only a single
instance is left in a test file to test for an XSS, it requires crafting
an commit that Git itself refuses to craft). `_gogit` files have
been removed entirely, `go:build: !gogit` is removed, `XXX_nogogit.go` files
either have been renamed or had their code being merged into the
`XXX.go` file.

Gusted a21128a7 4132b18e

-3
Makefile
··· 836 836 .PHONY: release-windows 837 837 release-windows: | $(DIST_DIRS) 838 838 CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . 839 - ifeq (,$(findstring gogit,$(TAGS))) 840 - CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . 841 - endif 842 839 843 840 .PHONY: release-linux 844 841 release-linux: | $(DIST_DIRS)
+2 -2
go.mod
··· 31 31 github.com/dustin/go-humanize v1.0.1 32 32 github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 33 33 github.com/emersion/go-imap v1.2.1 34 - github.com/emirpasic/gods v1.18.1 35 34 github.com/felixge/fgprof v0.9.4 36 35 github.com/fsnotify/fsnotify v1.7.0 37 36 github.com/gliderlabs/ssh v0.3.7 ··· 42 41 github.com/go-co-op/gocron v1.37.0 43 42 github.com/go-enry/go-enry/v2 v2.8.8 44 43 github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e 45 - github.com/go-git/go-billy/v5 v5.5.0 46 44 github.com/go-git/go-git/v5 v5.11.0 47 45 github.com/go-ldap/ldap/v3 v3.4.6 48 46 github.com/go-sql-driver/mysql v1.8.1 ··· 172 170 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 173 171 github.com/dlclark/regexp2 v1.11.0 // indirect 174 172 github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect 173 + github.com/emirpasic/gods v1.18.1 // indirect 175 174 github.com/fatih/color v1.16.0 // indirect 176 175 github.com/felixge/httpsnoop v1.0.4 // indirect 177 176 github.com/fxamacker/cbor/v2 v2.5.0 // indirect ··· 181 180 github.com/go-faster/city v1.0.1 // indirect 182 181 github.com/go-faster/errors v0.7.1 // indirect 183 182 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 183 + github.com/go-git/go-billy/v5 v5.5.0 // indirect 184 184 github.com/go-ini/ini v1.67.0 // indirect 185 185 github.com/go-openapi/analysis v0.22.2 // indirect 186 186 github.com/go-openapi/errors v0.21.0 // indirect
+120 -1
modules/git/blob.go
··· 5 5 package git 6 6 7 7 import ( 8 + "bufio" 8 9 "bytes" 9 10 "encoding/base64" 10 11 "io" 11 12 13 + "code.gitea.io/gitea/modules/log" 12 14 "code.gitea.io/gitea/modules/typesniffer" 13 15 "code.gitea.io/gitea/modules/util" 14 16 ) 15 17 16 - // This file contains common functions between the gogit and !gogit variants for git Blobs 18 + // Blob represents a Git object. 19 + type Blob struct { 20 + ID ObjectID 21 + 22 + gotSize bool 23 + size int64 24 + name string 25 + repo *Repository 26 + } 27 + 28 + // DataAsync gets a ReadCloser for the contents of a blob without reading it all. 29 + // Calling the Close function on the result will discard all unread output. 30 + func (b *Blob) DataAsync() (io.ReadCloser, error) { 31 + wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx) 32 + 33 + _, err := wr.Write([]byte(b.ID.String() + "\n")) 34 + if err != nil { 35 + cancel() 36 + return nil, err 37 + } 38 + _, _, size, err := ReadBatchLine(rd) 39 + if err != nil { 40 + cancel() 41 + return nil, err 42 + } 43 + b.gotSize = true 44 + b.size = size 45 + 46 + if size < 4096 { 47 + bs, err := io.ReadAll(io.LimitReader(rd, size)) 48 + defer cancel() 49 + if err != nil { 50 + return nil, err 51 + } 52 + _, err = rd.Discard(1) 53 + return io.NopCloser(bytes.NewReader(bs)), err 54 + } 55 + 56 + return &blobReader{ 57 + rd: rd, 58 + n: size, 59 + cancel: cancel, 60 + }, nil 61 + } 62 + 63 + // Size returns the uncompressed size of the blob 64 + func (b *Blob) Size() int64 { 65 + if b.gotSize { 66 + return b.size 67 + } 68 + 69 + wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx) 70 + defer cancel() 71 + _, err := wr.Write([]byte(b.ID.String() + "\n")) 72 + if err != nil { 73 + log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err) 74 + return 0 75 + } 76 + _, _, b.size, err = ReadBatchLine(rd) 77 + if err != nil { 78 + log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err) 79 + return 0 80 + } 81 + 82 + b.gotSize = true 83 + 84 + return b.size 85 + } 86 + 87 + type blobReader struct { 88 + rd *bufio.Reader 89 + n int64 90 + cancel func() 91 + } 92 + 93 + func (b *blobReader) Read(p []byte) (n int, err error) { 94 + if b.n <= 0 { 95 + return 0, io.EOF 96 + } 97 + if int64(len(p)) > b.n { 98 + p = p[0:b.n] 99 + } 100 + n, err = b.rd.Read(p) 101 + b.n -= int64(n) 102 + return n, err 103 + } 104 + 105 + // Close implements io.Closer 106 + func (b *blobReader) Close() error { 107 + if b.rd == nil { 108 + return nil 109 + } 110 + 111 + defer b.cancel() 112 + 113 + if err := DiscardFull(b.rd, b.n+1); err != nil { 114 + return err 115 + } 116 + 117 + b.rd = nil 118 + 119 + return nil 120 + } 17 121 18 122 // Name returns name of the tree entry this blob object was created from (or empty string) 19 123 func (b *Blob) Name() string { ··· 100 204 101 205 return typesniffer.DetectContentTypeFromReader(r) 102 206 } 207 + 208 + // GetBlob finds the blob object in the repository. 209 + func (repo *Repository) GetBlob(idStr string) (*Blob, error) { 210 + id, err := NewIDFromString(idStr) 211 + if err != nil { 212 + return nil, err 213 + } 214 + if id.IsZero() { 215 + return nil, ErrNotExist{id.String(), ""} 216 + } 217 + return &Blob{ 218 + ID: id, 219 + repo: repo, 220 + }, nil 221 + }
-32
modules/git/blob_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "io" 11 - 12 - "github.com/go-git/go-git/v5/plumbing" 13 - ) 14 - 15 - // Blob represents a Git object. 16 - type Blob struct { 17 - ID ObjectID 18 - 19 - gogitEncodedObj plumbing.EncodedObject 20 - name string 21 - } 22 - 23 - // DataAsync gets a ReadCloser for the contents of a blob without reading it all. 24 - // Calling the Close function on the result will discard all unread output. 25 - func (b *Blob) DataAsync() (io.ReadCloser, error) { 26 - return b.gogitEncodedObj.Reader() 27 - } 28 - 29 - // Size returns the uncompressed size of the blob 30 - func (b *Blob) Size() int64 { 31 - return b.gogitEncodedObj.Size() 32 - }
-118
modules/git/blob_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "bufio" 10 - "bytes" 11 - "io" 12 - 13 - "code.gitea.io/gitea/modules/log" 14 - ) 15 - 16 - // Blob represents a Git object. 17 - type Blob struct { 18 - ID ObjectID 19 - 20 - gotSize bool 21 - size int64 22 - name string 23 - repo *Repository 24 - } 25 - 26 - // DataAsync gets a ReadCloser for the contents of a blob without reading it all. 27 - // Calling the Close function on the result will discard all unread output. 28 - func (b *Blob) DataAsync() (io.ReadCloser, error) { 29 - wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx) 30 - 31 - _, err := wr.Write([]byte(b.ID.String() + "\n")) 32 - if err != nil { 33 - cancel() 34 - return nil, err 35 - } 36 - _, _, size, err := ReadBatchLine(rd) 37 - if err != nil { 38 - cancel() 39 - return nil, err 40 - } 41 - b.gotSize = true 42 - b.size = size 43 - 44 - if size < 4096 { 45 - bs, err := io.ReadAll(io.LimitReader(rd, size)) 46 - defer cancel() 47 - if err != nil { 48 - return nil, err 49 - } 50 - _, err = rd.Discard(1) 51 - return io.NopCloser(bytes.NewReader(bs)), err 52 - } 53 - 54 - return &blobReader{ 55 - rd: rd, 56 - n: size, 57 - cancel: cancel, 58 - }, nil 59 - } 60 - 61 - // Size returns the uncompressed size of the blob 62 - func (b *Blob) Size() int64 { 63 - if b.gotSize { 64 - return b.size 65 - } 66 - 67 - wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx) 68 - defer cancel() 69 - _, err := wr.Write([]byte(b.ID.String() + "\n")) 70 - if err != nil { 71 - log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err) 72 - return 0 73 - } 74 - _, _, b.size, err = ReadBatchLine(rd) 75 - if err != nil { 76 - log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err) 77 - return 0 78 - } 79 - 80 - b.gotSize = true 81 - 82 - return b.size 83 - } 84 - 85 - type blobReader struct { 86 - rd *bufio.Reader 87 - n int64 88 - cancel func() 89 - } 90 - 91 - func (b *blobReader) Read(p []byte) (n int, err error) { 92 - if b.n <= 0 { 93 - return 0, io.EOF 94 - } 95 - if int64(len(p)) > b.n { 96 - p = p[0:b.n] 97 - } 98 - n, err = b.rd.Read(p) 99 - b.n -= int64(n) 100 - return n, err 101 - } 102 - 103 - // Close implements io.Closer 104 - func (b *blobReader) Close() error { 105 - if b.rd == nil { 106 - return nil 107 - } 108 - 109 - defer b.cancel() 110 - 111 - if err := DiscardFull(b.rd, b.n+1); err != nil { 112 - return err 113 - } 114 - 115 - b.rd = nil 116 - 117 - return nil 118 - }
-75
modules/git/commit_convert_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2018 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "fmt" 11 - "strings" 12 - 13 - "github.com/go-git/go-git/v5/plumbing/object" 14 - ) 15 - 16 - func convertPGPSignature(c *object.Commit) *ObjectSignature { 17 - if c.PGPSignature == "" { 18 - return nil 19 - } 20 - 21 - var w strings.Builder 22 - var err error 23 - 24 - if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil { 25 - return nil 26 - } 27 - 28 - for _, parent := range c.ParentHashes { 29 - if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil { 30 - return nil 31 - } 32 - } 33 - 34 - if _, err = fmt.Fprint(&w, "author "); err != nil { 35 - return nil 36 - } 37 - 38 - if err = c.Author.Encode(&w); err != nil { 39 - return nil 40 - } 41 - 42 - if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil { 43 - return nil 44 - } 45 - 46 - if err = c.Committer.Encode(&w); err != nil { 47 - return nil 48 - } 49 - 50 - if c.Encoding != "" && c.Encoding != "UTF-8" { 51 - if _, err = fmt.Fprintf(&w, "\nencoding %s\n", c.Encoding); err != nil { 52 - return nil 53 - } 54 - } 55 - 56 - if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil { 57 - return nil 58 - } 59 - 60 - return &ObjectSignature{ 61 - Signature: c.PGPSignature, 62 - Payload: w.String(), 63 - } 64 - } 65 - 66 - func convertCommit(c *object.Commit) *Commit { 67 - return &Commit{ 68 - ID: ParseGogitHash(c.Hash), 69 - CommitMessage: c.Message, 70 - Committer: &c.Committer, 71 - Author: &c.Author, 72 - Signature: convertPGPSignature(c), 73 - Parents: ParseGogitHashArray(c.ParentHashes), 74 - } 75 - }
+164
modules/git/commit_info.go
··· 3 3 4 4 package git 5 5 6 + import ( 7 + "context" 8 + "fmt" 9 + "io" 10 + "path" 11 + "sort" 12 + 13 + "code.gitea.io/gitea/modules/log" 14 + ) 15 + 6 16 // CommitInfo describes the first commit with the provided entry 7 17 type CommitInfo struct { 8 18 Entry *TreeEntry 9 19 Commit *Commit 10 20 SubModuleFile *SubModuleFile 11 21 } 22 + 23 + // GetCommitsInfo gets information of all commits that are corresponding to these entries 24 + func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) { 25 + entryPaths := make([]string, len(tes)+1) 26 + // Get the commit for the treePath itself 27 + entryPaths[0] = "" 28 + for i, entry := range tes { 29 + entryPaths[i+1] = entry.Name() 30 + } 31 + 32 + var err error 33 + 34 + var revs map[string]*Commit 35 + if commit.repo.LastCommitCache != nil { 36 + var unHitPaths []string 37 + revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache) 38 + if err != nil { 39 + return nil, nil, err 40 + } 41 + if len(unHitPaths) > 0 { 42 + sort.Strings(unHitPaths) 43 + commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths) 44 + if err != nil { 45 + return nil, nil, err 46 + } 47 + 48 + for pth, found := range commits { 49 + revs[pth] = found 50 + } 51 + } 52 + } else { 53 + sort.Strings(entryPaths) 54 + revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths) 55 + } 56 + if err != nil { 57 + return nil, nil, err 58 + } 59 + 60 + commitsInfo := make([]CommitInfo, len(tes)) 61 + for i, entry := range tes { 62 + commitsInfo[i] = CommitInfo{ 63 + Entry: entry, 64 + } 65 + 66 + // Check if we have found a commit for this entry in time 67 + if entryCommit, ok := revs[entry.Name()]; ok { 68 + commitsInfo[i].Commit = entryCommit 69 + } else { 70 + log.Debug("missing commit for %s", entry.Name()) 71 + } 72 + 73 + // If the entry if a submodule add a submodule file for this 74 + if entry.IsSubModule() { 75 + subModuleURL := "" 76 + var fullPath string 77 + if len(treePath) > 0 { 78 + fullPath = treePath + "/" + entry.Name() 79 + } else { 80 + fullPath = entry.Name() 81 + } 82 + if subModule, err := commit.GetSubModule(fullPath); err != nil { 83 + return nil, nil, err 84 + } else if subModule != nil { 85 + subModuleURL = subModule.URL 86 + } 87 + subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) 88 + commitsInfo[i].SubModuleFile = subModuleFile 89 + } 90 + } 91 + 92 + // Retrieve the commit for the treePath itself (see above). We basically 93 + // get it for free during the tree traversal and it's used for listing 94 + // pages to display information about newest commit for a given path. 95 + var treeCommit *Commit 96 + var ok bool 97 + if treePath == "" { 98 + treeCommit = commit 99 + } else if treeCommit, ok = revs[""]; ok { 100 + treeCommit.repo = commit.repo 101 + } 102 + return commitsInfo, treeCommit, nil 103 + } 104 + 105 + func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) { 106 + var unHitEntryPaths []string 107 + results := make(map[string]*Commit) 108 + for _, p := range paths { 109 + lastCommit, err := cache.Get(commitID, path.Join(treePath, p)) 110 + if err != nil { 111 + return nil, nil, err 112 + } 113 + if lastCommit != nil { 114 + results[p] = lastCommit 115 + continue 116 + } 117 + 118 + unHitEntryPaths = append(unHitEntryPaths, p) 119 + } 120 + 121 + return results, unHitEntryPaths, nil 122 + } 123 + 124 + // GetLastCommitForPaths returns last commit information 125 + func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { 126 + // We read backwards from the commit to obtain all of the commits 127 + revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...) 128 + if err != nil { 129 + return nil, err 130 + } 131 + 132 + batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx) 133 + defer cancel() 134 + 135 + commitsMap := map[string]*Commit{} 136 + commitsMap[commit.ID.String()] = commit 137 + 138 + commitCommits := map[string]*Commit{} 139 + for path, commitID := range revs { 140 + c, ok := commitsMap[commitID] 141 + if ok { 142 + commitCommits[path] = c 143 + continue 144 + } 145 + 146 + if len(commitID) == 0 { 147 + continue 148 + } 149 + 150 + _, err := batchStdinWriter.Write([]byte(commitID + "\n")) 151 + if err != nil { 152 + return nil, err 153 + } 154 + _, typ, size, err := ReadBatchLine(batchReader) 155 + if err != nil { 156 + return nil, err 157 + } 158 + if typ != "commit" { 159 + if err := DiscardFull(batchReader, size+1); err != nil { 160 + return nil, err 161 + } 162 + return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) 163 + } 164 + c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size)) 165 + if err != nil { 166 + return nil, err 167 + } 168 + if _, err := batchReader.Discard(1); err != nil { 169 + return nil, err 170 + } 171 + commitCommits[path] = c 172 + } 173 + 174 + return commitCommits, nil 175 + }
-304
modules/git/commit_info_gogit.go
··· 1 - // Copyright 2017 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "context" 10 - "path" 11 - 12 - "github.com/emirpasic/gods/trees/binaryheap" 13 - "github.com/go-git/go-git/v5/plumbing" 14 - "github.com/go-git/go-git/v5/plumbing/object" 15 - cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" 16 - ) 17 - 18 - // GetCommitsInfo gets information of all commits that are corresponding to these entries 19 - func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) { 20 - entryPaths := make([]string, len(tes)+1) 21 - // Get the commit for the treePath itself 22 - entryPaths[0] = "" 23 - for i, entry := range tes { 24 - entryPaths[i+1] = entry.Name() 25 - } 26 - 27 - commitNodeIndex, commitGraphFile := commit.repo.CommitNodeIndex() 28 - if commitGraphFile != nil { 29 - defer commitGraphFile.Close() 30 - } 31 - 32 - c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue())) 33 - if err != nil { 34 - return nil, nil, err 35 - } 36 - 37 - var revs map[string]*Commit 38 - if commit.repo.LastCommitCache != nil { 39 - var unHitPaths []string 40 - revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache) 41 - if err != nil { 42 - return nil, nil, err 43 - } 44 - if len(unHitPaths) > 0 { 45 - revs2, err := GetLastCommitForPaths(ctx, commit.repo.LastCommitCache, c, treePath, unHitPaths) 46 - if err != nil { 47 - return nil, nil, err 48 - } 49 - 50 - for k, v := range revs2 { 51 - revs[k] = v 52 - } 53 - } 54 - } else { 55 - revs, err = GetLastCommitForPaths(ctx, nil, c, treePath, entryPaths) 56 - } 57 - if err != nil { 58 - return nil, nil, err 59 - } 60 - 61 - commit.repo.gogitStorage.Close() 62 - 63 - commitsInfo := make([]CommitInfo, len(tes)) 64 - for i, entry := range tes { 65 - commitsInfo[i] = CommitInfo{ 66 - Entry: entry, 67 - } 68 - 69 - // Check if we have found a commit for this entry in time 70 - if entryCommit, ok := revs[entry.Name()]; ok { 71 - commitsInfo[i].Commit = entryCommit 72 - } 73 - 74 - // If the entry if a submodule add a submodule file for this 75 - if entry.IsSubModule() { 76 - subModuleURL := "" 77 - var fullPath string 78 - if len(treePath) > 0 { 79 - fullPath = treePath + "/" + entry.Name() 80 - } else { 81 - fullPath = entry.Name() 82 - } 83 - if subModule, err := commit.GetSubModule(fullPath); err != nil { 84 - return nil, nil, err 85 - } else if subModule != nil { 86 - subModuleURL = subModule.URL 87 - } 88 - subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) 89 - commitsInfo[i].SubModuleFile = subModuleFile 90 - } 91 - } 92 - 93 - // Retrieve the commit for the treePath itself (see above). We basically 94 - // get it for free during the tree traversal and it's used for listing 95 - // pages to display information about newest commit for a given path. 96 - var treeCommit *Commit 97 - var ok bool 98 - if treePath == "" { 99 - treeCommit = commit 100 - } else if treeCommit, ok = revs[""]; ok { 101 - treeCommit.repo = commit.repo 102 - } 103 - return commitsInfo, treeCommit, nil 104 - } 105 - 106 - type commitAndPaths struct { 107 - commit cgobject.CommitNode 108 - // Paths that are still on the branch represented by commit 109 - paths []string 110 - // Set of hashes for the paths 111 - hashes map[string]plumbing.Hash 112 - } 113 - 114 - func getCommitTree(c cgobject.CommitNode, treePath string) (*object.Tree, error) { 115 - tree, err := c.Tree() 116 - if err != nil { 117 - return nil, err 118 - } 119 - 120 - // Optimize deep traversals by focusing only on the specific tree 121 - if treePath != "" { 122 - tree, err = tree.Tree(treePath) 123 - if err != nil { 124 - return nil, err 125 - } 126 - } 127 - 128 - return tree, nil 129 - } 130 - 131 - func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[string]plumbing.Hash, error) { 132 - tree, err := getCommitTree(c, treePath) 133 - if err == object.ErrDirectoryNotFound { 134 - // The whole tree didn't exist, so return empty map 135 - return make(map[string]plumbing.Hash), nil 136 - } 137 - if err != nil { 138 - return nil, err 139 - } 140 - 141 - hashes := make(map[string]plumbing.Hash) 142 - for _, path := range paths { 143 - if path != "" { 144 - entry, err := tree.FindEntry(path) 145 - if err == nil { 146 - hashes[path] = entry.Hash 147 - } 148 - } else { 149 - hashes[path] = tree.Hash 150 - } 151 - } 152 - 153 - return hashes, nil 154 - } 155 - 156 - func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) { 157 - var unHitEntryPaths []string 158 - results := make(map[string]*Commit) 159 - for _, p := range paths { 160 - lastCommit, err := cache.Get(commitID, path.Join(treePath, p)) 161 - if err != nil { 162 - return nil, nil, err 163 - } 164 - if lastCommit != nil { 165 - results[p] = lastCommit 166 - continue 167 - } 168 - 169 - unHitEntryPaths = append(unHitEntryPaths, p) 170 - } 171 - 172 - return results, unHitEntryPaths, nil 173 - } 174 - 175 - // GetLastCommitForPaths returns last commit information 176 - func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobject.CommitNode, treePath string, paths []string) (map[string]*Commit, error) { 177 - refSha := c.ID().String() 178 - 179 - // We do a tree traversal with nodes sorted by commit time 180 - heap := binaryheap.NewWith(func(a, b any) int { 181 - if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { 182 - return 1 183 - } 184 - return -1 185 - }) 186 - 187 - resultNodes := make(map[string]cgobject.CommitNode) 188 - initialHashes, err := getFileHashes(c, treePath, paths) 189 - if err != nil { 190 - return nil, err 191 - } 192 - 193 - // Start search from the root commit and with full set of paths 194 - heap.Push(&commitAndPaths{c, paths, initialHashes}) 195 - heaploop: 196 - for { 197 - select { 198 - case <-ctx.Done(): 199 - if ctx.Err() == context.DeadlineExceeded { 200 - break heaploop 201 - } 202 - return nil, ctx.Err() 203 - default: 204 - } 205 - cIn, ok := heap.Pop() 206 - if !ok { 207 - break 208 - } 209 - current := cIn.(*commitAndPaths) 210 - 211 - // Load the parent commits for the one we are currently examining 212 - numParents := current.commit.NumParents() 213 - var parents []cgobject.CommitNode 214 - for i := 0; i < numParents; i++ { 215 - parent, err := current.commit.ParentNode(i) 216 - if err != nil { 217 - break 218 - } 219 - parents = append(parents, parent) 220 - } 221 - 222 - // Examine the current commit and set of interesting paths 223 - pathUnchanged := make([]bool, len(current.paths)) 224 - parentHashes := make([]map[string]plumbing.Hash, len(parents)) 225 - for j, parent := range parents { 226 - parentHashes[j], err = getFileHashes(parent, treePath, current.paths) 227 - if err != nil { 228 - break 229 - } 230 - 231 - for i, path := range current.paths { 232 - if parentHashes[j][path] == current.hashes[path] { 233 - pathUnchanged[i] = true 234 - } 235 - } 236 - } 237 - 238 - var remainingPaths []string 239 - for i, pth := range current.paths { 240 - // The results could already contain some newer change for the same path, 241 - // so don't override that and bail out on the file early. 242 - if resultNodes[pth] == nil { 243 - if pathUnchanged[i] { 244 - // The path existed with the same hash in at least one parent so it could 245 - // not have been changed in this commit directly. 246 - remainingPaths = append(remainingPaths, pth) 247 - } else { 248 - // There are few possible cases how can we get here: 249 - // - The path didn't exist in any parent, so it must have been created by 250 - // this commit. 251 - // - The path did exist in the parent commit, but the hash of the file has 252 - // changed. 253 - // - We are looking at a merge commit and the hash of the file doesn't 254 - // match any of the hashes being merged. This is more common for directories, 255 - // but it can also happen if a file is changed through conflict resolution. 256 - resultNodes[pth] = current.commit 257 - if err := cache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil { 258 - return nil, err 259 - } 260 - } 261 - } 262 - } 263 - 264 - if len(remainingPaths) > 0 { 265 - // Add the parent nodes along with remaining paths to the heap for further 266 - // processing. 267 - for j, parent := range parents { 268 - // Combine remainingPath with paths available on the parent branch 269 - // and make union of them 270 - remainingPathsForParent := make([]string, 0, len(remainingPaths)) 271 - newRemainingPaths := make([]string, 0, len(remainingPaths)) 272 - for _, path := range remainingPaths { 273 - if parentHashes[j][path] == current.hashes[path] { 274 - remainingPathsForParent = append(remainingPathsForParent, path) 275 - } else { 276 - newRemainingPaths = append(newRemainingPaths, path) 277 - } 278 - } 279 - 280 - if remainingPathsForParent != nil { 281 - heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]}) 282 - } 283 - 284 - if len(newRemainingPaths) == 0 { 285 - break 286 - } else { 287 - remainingPaths = newRemainingPaths 288 - } 289 - } 290 - } 291 - } 292 - 293 - // Post-processing 294 - result := make(map[string]*Commit) 295 - for path, commitNode := range resultNodes { 296 - commit, err := commitNode.Commit() 297 - if err != nil { 298 - return nil, err 299 - } 300 - result[path] = convertCommit(commit) 301 - } 302 - 303 - return result, nil 304 - }
-170
modules/git/commit_info_nogogit.go
··· 1 - // Copyright 2017 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "context" 10 - "fmt" 11 - "io" 12 - "path" 13 - "sort" 14 - 15 - "code.gitea.io/gitea/modules/log" 16 - ) 17 - 18 - // GetCommitsInfo gets information of all commits that are corresponding to these entries 19 - func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) { 20 - entryPaths := make([]string, len(tes)+1) 21 - // Get the commit for the treePath itself 22 - entryPaths[0] = "" 23 - for i, entry := range tes { 24 - entryPaths[i+1] = entry.Name() 25 - } 26 - 27 - var err error 28 - 29 - var revs map[string]*Commit 30 - if commit.repo.LastCommitCache != nil { 31 - var unHitPaths []string 32 - revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache) 33 - if err != nil { 34 - return nil, nil, err 35 - } 36 - if len(unHitPaths) > 0 { 37 - sort.Strings(unHitPaths) 38 - commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths) 39 - if err != nil { 40 - return nil, nil, err 41 - } 42 - 43 - for pth, found := range commits { 44 - revs[pth] = found 45 - } 46 - } 47 - } else { 48 - sort.Strings(entryPaths) 49 - revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths) 50 - } 51 - if err != nil { 52 - return nil, nil, err 53 - } 54 - 55 - commitsInfo := make([]CommitInfo, len(tes)) 56 - for i, entry := range tes { 57 - commitsInfo[i] = CommitInfo{ 58 - Entry: entry, 59 - } 60 - 61 - // Check if we have found a commit for this entry in time 62 - if entryCommit, ok := revs[entry.Name()]; ok { 63 - commitsInfo[i].Commit = entryCommit 64 - } else { 65 - log.Debug("missing commit for %s", entry.Name()) 66 - } 67 - 68 - // If the entry if a submodule add a submodule file for this 69 - if entry.IsSubModule() { 70 - subModuleURL := "" 71 - var fullPath string 72 - if len(treePath) > 0 { 73 - fullPath = treePath + "/" + entry.Name() 74 - } else { 75 - fullPath = entry.Name() 76 - } 77 - if subModule, err := commit.GetSubModule(fullPath); err != nil { 78 - return nil, nil, err 79 - } else if subModule != nil { 80 - subModuleURL = subModule.URL 81 - } 82 - subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) 83 - commitsInfo[i].SubModuleFile = subModuleFile 84 - } 85 - } 86 - 87 - // Retrieve the commit for the treePath itself (see above). We basically 88 - // get it for free during the tree traversal and it's used for listing 89 - // pages to display information about newest commit for a given path. 90 - var treeCommit *Commit 91 - var ok bool 92 - if treePath == "" { 93 - treeCommit = commit 94 - } else if treeCommit, ok = revs[""]; ok { 95 - treeCommit.repo = commit.repo 96 - } 97 - return commitsInfo, treeCommit, nil 98 - } 99 - 100 - func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) { 101 - var unHitEntryPaths []string 102 - results := make(map[string]*Commit) 103 - for _, p := range paths { 104 - lastCommit, err := cache.Get(commitID, path.Join(treePath, p)) 105 - if err != nil { 106 - return nil, nil, err 107 - } 108 - if lastCommit != nil { 109 - results[p] = lastCommit 110 - continue 111 - } 112 - 113 - unHitEntryPaths = append(unHitEntryPaths, p) 114 - } 115 - 116 - return results, unHitEntryPaths, nil 117 - } 118 - 119 - // GetLastCommitForPaths returns last commit information 120 - func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { 121 - // We read backwards from the commit to obtain all of the commits 122 - revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...) 123 - if err != nil { 124 - return nil, err 125 - } 126 - 127 - batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx) 128 - defer cancel() 129 - 130 - commitsMap := map[string]*Commit{} 131 - commitsMap[commit.ID.String()] = commit 132 - 133 - commitCommits := map[string]*Commit{} 134 - for path, commitID := range revs { 135 - c, ok := commitsMap[commitID] 136 - if ok { 137 - commitCommits[path] = c 138 - continue 139 - } 140 - 141 - if len(commitID) == 0 { 142 - continue 143 - } 144 - 145 - _, err := batchStdinWriter.Write([]byte(commitID + "\n")) 146 - if err != nil { 147 - return nil, err 148 - } 149 - _, typ, size, err := ReadBatchLine(batchReader) 150 - if err != nil { 151 - return nil, err 152 - } 153 - if typ != "commit" { 154 - if err := DiscardFull(batchReader, size+1); err != nil { 155 - return nil, err 156 - } 157 - return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) 158 - } 159 - c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size)) 160 - if err != nil { 161 - return nil, err 162 - } 163 - if _, err := batchReader.Discard(1); err != nil { 164 - return nil, err 165 - } 166 - commitCommits[path] = c 167 - } 168 - 169 - return commitCommits, nil 170 - }
-2
modules/git/commit_sha256_test.go
··· 1 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package git 7 5 8 6 import (
+2 -2
modules/git/git.go
··· 186 186 globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") 187 187 } 188 188 SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil 189 - SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit 189 + SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil 190 190 SupportCheckAttrOnBare = CheckGitVersionAtLeast("2.40") == nil 191 191 if SupportHashSha256 { 192 192 SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat) 193 193 } else { 194 - log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported") 194 + log.Warn("sha256 hash support is disabled - requires Git >= 2.42") 195 195 } 196 196 197 197 InvertedGitFlushEnv = CheckGitVersionEqual("2.43.1") == nil
+45
modules/git/last_commit_cache.go
··· 4 4 package git 5 5 6 6 import ( 7 + "context" 7 8 "crypto/sha256" 8 9 "fmt" 9 10 ··· 112 113 113 114 return lastCommit, nil 114 115 } 116 + 117 + // CacheCommit will cache the commit from the gitRepository 118 + func (c *Commit) CacheCommit(ctx context.Context) error { 119 + if c.repo.LastCommitCache == nil { 120 + return nil 121 + } 122 + return c.recursiveCache(ctx, &c.Tree, "", 1) 123 + } 124 + 125 + func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string, level int) error { 126 + if level == 0 { 127 + return nil 128 + } 129 + 130 + entries, err := tree.ListEntries() 131 + if err != nil { 132 + return err 133 + } 134 + 135 + entryPaths := make([]string, len(entries)) 136 + for i, entry := range entries { 137 + entryPaths[i] = entry.Name() 138 + } 139 + 140 + _, err = WalkGitLog(ctx, c.repo, c, treePath, entryPaths...) 141 + if err != nil { 142 + return err 143 + } 144 + 145 + for _, treeEntry := range entries { 146 + // entryMap won't contain "" therefore skip this. 147 + if treeEntry.IsDir() { 148 + subTree, err := tree.SubTree(treeEntry.Name()) 149 + if err != nil { 150 + return err 151 + } 152 + if err := c.recursiveCache(ctx, subTree, treeEntry.Name(), level-1); err != nil { 153 + return err 154 + } 155 + } 156 + } 157 + 158 + return nil 159 + }
-65
modules/git/last_commit_cache_gogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "context" 10 - 11 - "github.com/go-git/go-git/v5/plumbing" 12 - cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" 13 - ) 14 - 15 - // CacheCommit will cache the commit from the gitRepository 16 - func (c *Commit) CacheCommit(ctx context.Context) error { 17 - if c.repo.LastCommitCache == nil { 18 - return nil 19 - } 20 - commitNodeIndex, _ := c.repo.CommitNodeIndex() 21 - 22 - index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue())) 23 - if err != nil { 24 - return err 25 - } 26 - 27 - return c.recursiveCache(ctx, index, &c.Tree, "", 1) 28 - } 29 - 30 - func (c *Commit) recursiveCache(ctx context.Context, index cgobject.CommitNode, tree *Tree, treePath string, level int) error { 31 - if level == 0 { 32 - return nil 33 - } 34 - 35 - entries, err := tree.ListEntries() 36 - if err != nil { 37 - return err 38 - } 39 - 40 - entryPaths := make([]string, len(entries)) 41 - entryMap := make(map[string]*TreeEntry) 42 - for i, entry := range entries { 43 - entryPaths[i] = entry.Name() 44 - entryMap[entry.Name()] = entry 45 - } 46 - 47 - commits, err := GetLastCommitForPaths(ctx, c.repo.LastCommitCache, index, treePath, entryPaths) 48 - if err != nil { 49 - return err 50 - } 51 - 52 - for entry := range commits { 53 - if entryMap[entry].IsDir() { 54 - subTree, err := tree.SubTree(entry) 55 - if err != nil { 56 - return err 57 - } 58 - if err := c.recursiveCache(ctx, index, subTree, entry, level-1); err != nil { 59 - return err 60 - } 61 - } 62 - } 63 - 64 - return nil 65 - }
-54
modules/git/last_commit_cache_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "context" 10 - ) 11 - 12 - // CacheCommit will cache the commit from the gitRepository 13 - func (c *Commit) CacheCommit(ctx context.Context) error { 14 - if c.repo.LastCommitCache == nil { 15 - return nil 16 - } 17 - return c.recursiveCache(ctx, &c.Tree, "", 1) 18 - } 19 - 20 - func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string, level int) error { 21 - if level == 0 { 22 - return nil 23 - } 24 - 25 - entries, err := tree.ListEntries() 26 - if err != nil { 27 - return err 28 - } 29 - 30 - entryPaths := make([]string, len(entries)) 31 - for i, entry := range entries { 32 - entryPaths[i] = entry.Name() 33 - } 34 - 35 - _, err = WalkGitLog(ctx, c.repo, c, treePath, entryPaths...) 36 - if err != nil { 37 - return err 38 - } 39 - 40 - for _, treeEntry := range entries { 41 - // entryMap won't contain "" therefore skip this. 42 - if treeEntry.IsDir() { 43 - subTree, err := tree.SubTree(treeEntry.Name()) 44 - if err != nil { 45 - return err 46 - } 47 - if err := c.recursiveCache(ctx, subTree, treeEntry.Name(), level-1); err != nil { 48 - return err 49 - } 50 - } 51 - } 52 - 53 - return nil 54 - }
+85
modules/git/notes.go
··· 3 3 4 4 package git 5 5 6 + import ( 7 + "context" 8 + "io" 9 + "strings" 10 + 11 + "code.gitea.io/gitea/modules/log" 12 + ) 13 + 6 14 // NotesRef is the git ref where Gitea will look for git-notes data. 7 15 // The value ("refs/notes/commits") is the default ref used by git-notes. 8 16 const NotesRef = "refs/notes/commits" ··· 12 20 Message []byte 13 21 Commit *Commit 14 22 } 23 + 24 + // GetNote retrieves the git-notes data for a given commit. 25 + // FIXME: Add LastCommitCache support 26 + func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { 27 + log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) 28 + notes, err := repo.GetCommit(NotesRef) 29 + if err != nil { 30 + if IsErrNotExist(err) { 31 + return err 32 + } 33 + log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) 34 + return err 35 + } 36 + 37 + path := "" 38 + 39 + tree := &notes.Tree 40 + log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID) 41 + 42 + var entry *TreeEntry 43 + originalCommitID := commitID 44 + for len(commitID) > 2 { 45 + entry, err = tree.GetTreeEntryByPath(commitID) 46 + if err == nil { 47 + path += commitID 48 + break 49 + } 50 + if IsErrNotExist(err) { 51 + tree, err = tree.SubTree(commitID[0:2]) 52 + path += commitID[0:2] + "/" 53 + commitID = commitID[2:] 54 + } 55 + if err != nil { 56 + // Err may have been updated by the SubTree we need to recheck if it's again an ErrNotExist 57 + if !IsErrNotExist(err) { 58 + log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err) 59 + } 60 + return err 61 + } 62 + } 63 + 64 + blob := entry.Blob() 65 + dataRc, err := blob.DataAsync() 66 + if err != nil { 67 + log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) 68 + return err 69 + } 70 + closed := false 71 + defer func() { 72 + if !closed { 73 + _ = dataRc.Close() 74 + } 75 + }() 76 + d, err := io.ReadAll(dataRc) 77 + if err != nil { 78 + log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) 79 + return err 80 + } 81 + _ = dataRc.Close() 82 + closed = true 83 + note.Message = d 84 + 85 + treePath := "" 86 + if idx := strings.LastIndex(path, "/"); idx > -1 { 87 + treePath = path[:idx] 88 + path = path[idx+1:] 89 + } 90 + 91 + lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path}) 92 + if err != nil { 93 + log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err) 94 + return err 95 + } 96 + note.Commit = lastCommits[path] 97 + 98 + return nil 99 + }
-89
modules/git/notes_gogit.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "context" 10 - "io" 11 - 12 - "code.gitea.io/gitea/modules/log" 13 - 14 - "github.com/go-git/go-git/v5/plumbing" 15 - "github.com/go-git/go-git/v5/plumbing/object" 16 - ) 17 - 18 - // GetNote retrieves the git-notes data for a given commit. 19 - // FIXME: Add LastCommitCache support 20 - func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { 21 - log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) 22 - notes, err := repo.GetCommit(NotesRef) 23 - if err != nil { 24 - if IsErrNotExist(err) { 25 - return err 26 - } 27 - log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) 28 - return err 29 - } 30 - 31 - remainingCommitID := commitID 32 - path := "" 33 - currentTree := notes.Tree.gogitTree 34 - log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", currentTree.Entries[0].Name, commitID) 35 - var file *object.File 36 - for len(remainingCommitID) > 2 { 37 - file, err = currentTree.File(remainingCommitID) 38 - if err == nil { 39 - path += remainingCommitID 40 - break 41 - } 42 - if err == object.ErrFileNotFound { 43 - currentTree, err = currentTree.Tree(remainingCommitID[0:2]) 44 - path += remainingCommitID[0:2] + "/" 45 - remainingCommitID = remainingCommitID[2:] 46 - } 47 - if err != nil { 48 - if err == object.ErrDirectoryNotFound { 49 - return ErrNotExist{ID: remainingCommitID, RelPath: path} 50 - } 51 - log.Error("Unable to find git note corresponding to the commit %q. Error: %v", commitID, err) 52 - return err 53 - } 54 - } 55 - 56 - blob := file.Blob 57 - dataRc, err := blob.Reader() 58 - if err != nil { 59 - log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) 60 - return err 61 - } 62 - 63 - defer dataRc.Close() 64 - d, err := io.ReadAll(dataRc) 65 - if err != nil { 66 - log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) 67 - return err 68 - } 69 - note.Message = d 70 - 71 - commitNodeIndex, commitGraphFile := repo.CommitNodeIndex() 72 - if commitGraphFile != nil { 73 - defer commitGraphFile.Close() 74 - } 75 - 76 - commitNode, err := commitNodeIndex.Get(plumbing.Hash(notes.ID.RawValue())) 77 - if err != nil { 78 - return err 79 - } 80 - 81 - lastCommits, err := GetLastCommitForPaths(ctx, nil, commitNode, "", []string{path}) 82 - if err != nil { 83 - log.Error("Unable to get the commit for the path %q. Error: %v", path, err) 84 - return err 85 - } 86 - note.Commit = lastCommits[path] 87 - 88 - return nil 89 - }
-91
modules/git/notes_nogogit.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "context" 10 - "io" 11 - "strings" 12 - 13 - "code.gitea.io/gitea/modules/log" 14 - ) 15 - 16 - // GetNote retrieves the git-notes data for a given commit. 17 - // FIXME: Add LastCommitCache support 18 - func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { 19 - log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) 20 - notes, err := repo.GetCommit(NotesRef) 21 - if err != nil { 22 - if IsErrNotExist(err) { 23 - return err 24 - } 25 - log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) 26 - return err 27 - } 28 - 29 - path := "" 30 - 31 - tree := &notes.Tree 32 - log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID) 33 - 34 - var entry *TreeEntry 35 - originalCommitID := commitID 36 - for len(commitID) > 2 { 37 - entry, err = tree.GetTreeEntryByPath(commitID) 38 - if err == nil { 39 - path += commitID 40 - break 41 - } 42 - if IsErrNotExist(err) { 43 - tree, err = tree.SubTree(commitID[0:2]) 44 - path += commitID[0:2] + "/" 45 - commitID = commitID[2:] 46 - } 47 - if err != nil { 48 - // Err may have been updated by the SubTree we need to recheck if it's again an ErrNotExist 49 - if !IsErrNotExist(err) { 50 - log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err) 51 - } 52 - return err 53 - } 54 - } 55 - 56 - blob := entry.Blob() 57 - dataRc, err := blob.DataAsync() 58 - if err != nil { 59 - log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) 60 - return err 61 - } 62 - closed := false 63 - defer func() { 64 - if !closed { 65 - _ = dataRc.Close() 66 - } 67 - }() 68 - d, err := io.ReadAll(dataRc) 69 - if err != nil { 70 - log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) 71 - return err 72 - } 73 - _ = dataRc.Close() 74 - closed = true 75 - note.Message = d 76 - 77 - treePath := "" 78 - if idx := strings.LastIndex(path, "/"); idx > -1 { 79 - treePath = path[:idx] 80 - path = path[idx+1:] 81 - } 82 - 83 - lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path}) 84 - if err != nil { 85 - log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err) 86 - return err 87 - } 88 - note.Commit = lastCommits[path] 89 - 90 - return nil 91 - }
-30
modules/git/object_id_gogit.go
··· 1 - // Copyright 2023 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - //go:build gogit 4 - 5 - package git 6 - 7 - import ( 8 - "github.com/go-git/go-git/v5/plumbing" 9 - "github.com/go-git/go-git/v5/plumbing/hash" 10 - ) 11 - 12 - func ParseGogitHash(h plumbing.Hash) ObjectID { 13 - switch hash.Size { 14 - case 20: 15 - return Sha1ObjectFormat.MustID(h[:]) 16 - case 32: 17 - return Sha256ObjectFormat.MustID(h[:]) 18 - } 19 - 20 - return nil 21 - } 22 - 23 - func ParseGogitHashArray(objectIDs []plumbing.Hash) []ObjectID { 24 - ret := make([]ObjectID, len(objectIDs)) 25 - for i, h := range objectIDs { 26 - ret[i] = ParseGogitHash(h) 27 - } 28 - 29 - return ret 30 - }
-96
modules/git/parse_gogit.go
··· 1 - // Copyright 2018 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "bytes" 10 - "fmt" 11 - "strconv" 12 - "strings" 13 - 14 - "github.com/go-git/go-git/v5/plumbing" 15 - "github.com/go-git/go-git/v5/plumbing/filemode" 16 - "github.com/go-git/go-git/v5/plumbing/hash" 17 - "github.com/go-git/go-git/v5/plumbing/object" 18 - ) 19 - 20 - // ParseTreeEntries parses the output of a `git ls-tree -l` command. 21 - func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { 22 - return parseTreeEntries(data, nil) 23 - } 24 - 25 - func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { 26 - entries := make([]*TreeEntry, 0, 10) 27 - for pos := 0; pos < len(data); { 28 - // expect line to be of the form "<mode> <type> <sha> <space-padded-size>\t<filename>" 29 - entry := new(TreeEntry) 30 - entry.gogitTreeEntry = &object.TreeEntry{} 31 - entry.ptree = ptree 32 - if pos+6 > len(data) { 33 - return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) 34 - } 35 - switch string(data[pos : pos+6]) { 36 - case "100644": 37 - entry.gogitTreeEntry.Mode = filemode.Regular 38 - pos += 12 // skip over "100644 blob " 39 - case "100755": 40 - entry.gogitTreeEntry.Mode = filemode.Executable 41 - pos += 12 // skip over "100755 blob " 42 - case "120000": 43 - entry.gogitTreeEntry.Mode = filemode.Symlink 44 - pos += 12 // skip over "120000 blob " 45 - case "160000": 46 - entry.gogitTreeEntry.Mode = filemode.Submodule 47 - pos += 14 // skip over "160000 object " 48 - case "040000": 49 - entry.gogitTreeEntry.Mode = filemode.Dir 50 - pos += 12 // skip over "040000 tree " 51 - default: 52 - return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6])) 53 - } 54 - 55 - // in hex format, not byte format .... 56 - if pos+hash.Size*2 > len(data) { 57 - return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) 58 - } 59 - var err error 60 - entry.ID, err = NewIDFromString(string(data[pos : pos+hash.Size*2])) 61 - if err != nil { 62 - return nil, fmt.Errorf("invalid ls-tree output: %w", err) 63 - } 64 - entry.gogitTreeEntry.Hash = plumbing.Hash(entry.ID.RawValue()) 65 - pos += 41 // skip over sha and trailing space 66 - 67 - end := pos + bytes.IndexByte(data[pos:], '\t') 68 - if end < pos { 69 - return nil, fmt.Errorf("Invalid ls-tree -l output: %s", string(data)) 70 - } 71 - entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(data[pos:end])), 10, 64) 72 - entry.sized = true 73 - 74 - pos = end + 1 75 - 76 - end = pos + bytes.IndexByte(data[pos:], '\n') 77 - if end < pos { 78 - return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) 79 - } 80 - 81 - // In case entry name is surrounded by double quotes(it happens only in git-shell). 82 - if data[pos] == '"' { 83 - var err error 84 - entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end])) 85 - if err != nil { 86 - return nil, fmt.Errorf("Invalid ls-tree output: %w", err) 87 - } 88 - } else { 89 - entry.gogitTreeEntry.Name = string(data[pos:end]) 90 - } 91 - 92 - pos = end + 1 93 - entries = append(entries, entry) 94 - } 95 - return entries, nil 96 - }
-79
modules/git/parse_gogit_test.go
··· 1 - // Copyright 2018 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "fmt" 10 - "testing" 11 - 12 - "github.com/go-git/go-git/v5/plumbing" 13 - "github.com/go-git/go-git/v5/plumbing/filemode" 14 - "github.com/go-git/go-git/v5/plumbing/object" 15 - "github.com/stretchr/testify/assert" 16 - "github.com/stretchr/testify/require" 17 - ) 18 - 19 - func TestParseTreeEntries(t *testing.T) { 20 - testCases := []struct { 21 - Input string 22 - Expected []*TreeEntry 23 - }{ 24 - { 25 - Input: "", 26 - Expected: []*TreeEntry{}, 27 - }, 28 - { 29 - Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n", 30 - Expected: []*TreeEntry{ 31 - { 32 - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), 33 - gogitTreeEntry: &object.TreeEntry{ 34 - Hash: plumbing.Hash(MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()), 35 - Name: "example/file2.txt", 36 - Mode: filemode.Regular, 37 - }, 38 - size: 1022, 39 - sized: true, 40 - }, 41 - }, 42 - }, 43 - { 44 - Input: "120000 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 234131\t\"example/\\n.txt\"\n" + 45 - "040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n", 46 - Expected: []*TreeEntry{ 47 - { 48 - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), 49 - gogitTreeEntry: &object.TreeEntry{ 50 - Hash: plumbing.Hash(MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()), 51 - Name: "example/\n.txt", 52 - Mode: filemode.Symlink, 53 - }, 54 - size: 234131, 55 - sized: true, 56 - }, 57 - { 58 - ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), 59 - sized: true, 60 - gogitTreeEntry: &object.TreeEntry{ 61 - Hash: plumbing.Hash(MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8").RawValue()), 62 - Name: "example", 63 - Mode: filemode.Dir, 64 - }, 65 - }, 66 - }, 67 - }, 68 - } 69 - 70 - for _, testCase := range testCases { 71 - entries, err := ParseTreeEntries([]byte(testCase.Input)) 72 - require.NoError(t, err) 73 - if len(entries) > 1 { 74 - fmt.Println(testCase.Expected[0].ID) 75 - fmt.Println(entries[0].ID) 76 - } 77 - assert.EqualValues(t, testCase.Expected, entries) 78 - } 79 - }
-2
modules/git/parse_nogogit.go modules/git/parse.go
··· 1 1 // Copyright 2018 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package git 7 5 8 6 import (
-2
modules/git/parse_nogogit_test.go modules/git/parse_test.go
··· 1 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package git 7 5 8 6 import (
-32
modules/git/pipeline/lfs_common.go
··· 1 - // Copyright 2024 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package pipeline 5 - 6 - import ( 7 - "fmt" 8 - "time" 9 - 10 - "code.gitea.io/gitea/modules/git" 11 - ) 12 - 13 - // LFSResult represents commits found using a provided pointer file hash 14 - type LFSResult struct { 15 - Name string 16 - SHA string 17 - Summary string 18 - When time.Time 19 - ParentHashes []git.ObjectID 20 - BranchName string 21 - FullCommitName string 22 - } 23 - 24 - type lfsResultSlice []*LFSResult 25 - 26 - func (a lfsResultSlice) Len() int { return len(a) } 27 - func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 28 - func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } 29 - 30 - func lfsError(msg string, err error) error { 31 - return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err) 32 - }
-146
modules/git/pipeline/lfs_gogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package pipeline 7 - 8 - import ( 9 - "bufio" 10 - "io" 11 - "sort" 12 - "strings" 13 - "sync" 14 - 15 - "code.gitea.io/gitea/modules/git" 16 - 17 - gogit "github.com/go-git/go-git/v5" 18 - "github.com/go-git/go-git/v5/plumbing" 19 - "github.com/go-git/go-git/v5/plumbing/object" 20 - ) 21 - 22 - // FindLFSFile finds commits that contain a provided pointer file hash 23 - func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { 24 - resultsMap := map[string]*LFSResult{} 25 - results := make([]*LFSResult, 0) 26 - 27 - basePath := repo.Path 28 - gogitRepo := repo.GoGitRepo() 29 - 30 - commitsIter, err := gogitRepo.Log(&gogit.LogOptions{ 31 - Order: gogit.LogOrderCommitterTime, 32 - All: true, 33 - }) 34 - if err != nil { 35 - return nil, lfsError("failed to get GoGit CommitsIter", err) 36 - } 37 - 38 - err = commitsIter.ForEach(func(gitCommit *object.Commit) error { 39 - tree, err := gitCommit.Tree() 40 - if err != nil { 41 - return err 42 - } 43 - treeWalker := object.NewTreeWalker(tree, true, nil) 44 - defer treeWalker.Close() 45 - for { 46 - name, entry, err := treeWalker.Next() 47 - if err == io.EOF { 48 - break 49 - } 50 - if entry.Hash == plumbing.Hash(objectID.RawValue()) { 51 - parents := make([]git.ObjectID, len(gitCommit.ParentHashes)) 52 - for i, parentCommitID := range gitCommit.ParentHashes { 53 - parents[i] = git.ParseGogitHash(parentCommitID) 54 - } 55 - 56 - result := LFSResult{ 57 - Name: name, 58 - SHA: gitCommit.Hash.String(), 59 - Summary: strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0], 60 - When: gitCommit.Author.When, 61 - ParentHashes: parents, 62 - } 63 - resultsMap[gitCommit.Hash.String()+":"+name] = &result 64 - } 65 - } 66 - return nil 67 - }) 68 - if err != nil && err != io.EOF { 69 - return nil, lfsError("failure in CommitIter.ForEach", err) 70 - } 71 - 72 - for _, result := range resultsMap { 73 - hasParent := false 74 - for _, parentHash := range result.ParentHashes { 75 - if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent { 76 - break 77 - } 78 - } 79 - if !hasParent { 80 - results = append(results, result) 81 - } 82 - } 83 - 84 - sort.Sort(lfsResultSlice(results)) 85 - 86 - // Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple 87 - shasToNameReader, shasToNameWriter := io.Pipe() 88 - nameRevStdinReader, nameRevStdinWriter := io.Pipe() 89 - errChan := make(chan error, 1) 90 - wg := sync.WaitGroup{} 91 - wg.Add(3) 92 - 93 - go func() { 94 - defer wg.Done() 95 - scanner := bufio.NewScanner(nameRevStdinReader) 96 - i := 0 97 - for scanner.Scan() { 98 - line := scanner.Text() 99 - if len(line) == 0 { 100 - continue 101 - } 102 - result := results[i] 103 - result.FullCommitName = line 104 - result.BranchName = strings.Split(line, "~")[0] 105 - i++ 106 - } 107 - }() 108 - go NameRevStdin(repo.Ctx, shasToNameReader, nameRevStdinWriter, &wg, basePath) 109 - go func() { 110 - defer wg.Done() 111 - defer shasToNameWriter.Close() 112 - for _, result := range results { 113 - i := 0 114 - if i < len(result.SHA) { 115 - n, err := shasToNameWriter.Write([]byte(result.SHA)[i:]) 116 - if err != nil { 117 - errChan <- err 118 - break 119 - } 120 - i += n 121 - } 122 - n := 0 123 - for n < 1 { 124 - n, err = shasToNameWriter.Write([]byte{'\n'}) 125 - if err != nil { 126 - errChan <- err 127 - break 128 - } 129 - 130 - } 131 - 132 - } 133 - }() 134 - 135 - wg.Wait() 136 - 137 - select { 138 - case err, has := <-errChan: 139 - if has { 140 - return nil, lfsError("unable to obtain name for LFS files", err) 141 - } 142 - default: 143 - } 144 - 145 - return results, nil 146 - }
+23 -2
modules/git/pipeline/lfs_nogogit.go modules/git/pipeline/lfs.go
··· 1 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package pipeline 7 5 8 6 import ( 9 7 "bufio" 10 8 "bytes" 9 + "fmt" 11 10 "io" 12 11 "sort" 13 12 "strings" 14 13 "sync" 14 + "time" 15 15 16 16 "code.gitea.io/gitea/modules/git" 17 17 ) 18 + 19 + // LFSResult represents commits found using a provided pointer file hash 20 + type LFSResult struct { 21 + Name string 22 + SHA string 23 + Summary string 24 + When time.Time 25 + ParentHashes []git.ObjectID 26 + BranchName string 27 + FullCommitName string 28 + } 29 + 30 + type lfsResultSlice []*LFSResult 31 + 32 + func (a lfsResultSlice) Len() int { return len(a) } 33 + func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 34 + func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } 35 + 36 + func lfsError(msg string, err error) error { 37 + return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err) 38 + } 18 39 19 40 // FindLFSFile finds commits that contain a provided pointer file hash 20 41 func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
+112 -2
modules/git/repo_base.go
··· 1 - // Copyright 2021 The Gitea Authors. All rights reserved. 1 + // Copyright 2015 The Gogs Authors. All rights reserved. 2 + // Copyright 2017 The Gitea Authors. All rights reserved. 2 3 // SPDX-License-Identifier: MIT 3 4 4 5 package git 5 6 6 - var isGogit bool 7 + import ( 8 + "bufio" 9 + "context" 10 + "errors" 11 + "path/filepath" 12 + 13 + "code.gitea.io/gitea/modules/log" 14 + ) 15 + 16 + // Repository represents a Git repository. 17 + type Repository struct { 18 + Path string 19 + 20 + tagCache *ObjectCache 21 + 22 + gpgSettings *GPGSettings 23 + 24 + batchInUse bool 25 + batchCancel context.CancelFunc 26 + batchReader *bufio.Reader 27 + batchWriter WriteCloserError 28 + 29 + checkInUse bool 30 + checkCancel context.CancelFunc 31 + checkReader *bufio.Reader 32 + checkWriter WriteCloserError 33 + 34 + Ctx context.Context 35 + LastCommitCache *LastCommitCache 36 + 37 + objectFormat ObjectFormat 38 + } 39 + 40 + // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. 41 + func openRepositoryWithDefaultContext(repoPath string) (*Repository, error) { 42 + return OpenRepository(DefaultContext, repoPath) 43 + } 44 + 45 + // OpenRepository opens the repository at the given path with the provided context. 46 + func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { 47 + repoPath, err := filepath.Abs(repoPath) 48 + if err != nil { 49 + return nil, err 50 + } else if !isDir(repoPath) { 51 + return nil, errors.New("no such file or directory") 52 + } 53 + 54 + // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! 55 + if err := EnsureValidGitRepository(ctx, repoPath); err != nil { 56 + return nil, err 57 + } 58 + 59 + repo := &Repository{ 60 + Path: repoPath, 61 + tagCache: newObjectCache(), 62 + Ctx: ctx, 63 + } 64 + 65 + repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath) 66 + repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath) 67 + 68 + return repo, nil 69 + } 70 + 71 + // CatFileBatch obtains a CatFileBatch for this repository 72 + func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { 73 + if repo.batchCancel == nil || repo.batchInUse { 74 + log.Debug("Opening temporary cat file batch for: %s", repo.Path) 75 + return CatFileBatch(ctx, repo.Path) 76 + } 77 + repo.batchInUse = true 78 + return repo.batchWriter, repo.batchReader, func() { 79 + repo.batchInUse = false 80 + } 81 + } 82 + 83 + // CatFileBatchCheck obtains a CatFileBatchCheck for this repository 84 + func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { 85 + if repo.checkCancel == nil || repo.checkInUse { 86 + log.Debug("Opening temporary cat file batch-check for: %s", repo.Path) 87 + return CatFileBatchCheck(ctx, repo.Path) 88 + } 89 + repo.checkInUse = true 90 + return repo.checkWriter, repo.checkReader, func() { 91 + repo.checkInUse = false 92 + } 93 + } 94 + 95 + func (repo *Repository) Close() error { 96 + if repo == nil { 97 + return nil 98 + } 99 + if repo.batchCancel != nil { 100 + repo.batchCancel() 101 + repo.batchReader = nil 102 + repo.batchWriter = nil 103 + repo.batchCancel = nil 104 + repo.batchInUse = false 105 + } 106 + if repo.checkCancel != nil { 107 + repo.checkCancel() 108 + repo.checkCancel = nil 109 + repo.checkReader = nil 110 + repo.checkWriter = nil 111 + repo.checkInUse = false 112 + } 113 + repo.LastCommitCache = nil 114 + repo.tagCache = nil 115 + return nil 116 + }
-107
modules/git/repo_base_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2017 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "context" 11 - "errors" 12 - "path/filepath" 13 - 14 - gitealog "code.gitea.io/gitea/modules/log" 15 - "code.gitea.io/gitea/modules/setting" 16 - 17 - "github.com/go-git/go-billy/v5" 18 - "github.com/go-git/go-billy/v5/osfs" 19 - gogit "github.com/go-git/go-git/v5" 20 - "github.com/go-git/go-git/v5/plumbing" 21 - "github.com/go-git/go-git/v5/plumbing/cache" 22 - "github.com/go-git/go-git/v5/storage/filesystem" 23 - ) 24 - 25 - func init() { 26 - isGogit = true 27 - } 28 - 29 - // Repository represents a Git repository. 30 - type Repository struct { 31 - Path string 32 - 33 - tagCache *ObjectCache 34 - 35 - gogitRepo *gogit.Repository 36 - gogitStorage *filesystem.Storage 37 - gpgSettings *GPGSettings 38 - 39 - Ctx context.Context 40 - LastCommitCache *LastCommitCache 41 - objectFormat ObjectFormat 42 - } 43 - 44 - // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. 45 - func openRepositoryWithDefaultContext(repoPath string) (*Repository, error) { 46 - return OpenRepository(DefaultContext, repoPath) 47 - } 48 - 49 - // OpenRepository opens the repository at the given path within the context.Context 50 - func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { 51 - repoPath, err := filepath.Abs(repoPath) 52 - if err != nil { 53 - return nil, err 54 - } else if !isDir(repoPath) { 55 - return nil, errors.New("no such file or directory") 56 - } 57 - 58 - fs := osfs.New(repoPath) 59 - _, err = fs.Stat(".git") 60 - if err == nil { 61 - fs, err = fs.Chroot(".git") 62 - if err != nil { 63 - return nil, err 64 - } 65 - } 66 - // the "clone --shared" repo doesn't work well with go-git AlternativeFS, https://github.com/go-git/go-git/issues/1006 67 - // so use "/" for AlternatesFS, I guess it is the same behavior as current nogogit (no limitation or check for the "objects/info/alternates" paths), trust the "clone" command executed by the server. 68 - var altFs billy.Filesystem 69 - if setting.IsWindows { 70 - altFs = osfs.New(filepath.VolumeName(setting.RepoRootPath) + "\\") // TODO: does it really work for Windows? Need some time to check. 71 - } else { 72 - altFs = osfs.New("/") 73 - } 74 - storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true, LargeObjectThreshold: setting.Git.LargeObjectThreshold, AlternatesFS: altFs}) 75 - gogitRepo, err := gogit.Open(storage, fs) 76 - if err != nil { 77 - return nil, err 78 - } 79 - 80 - return &Repository{ 81 - Path: repoPath, 82 - gogitRepo: gogitRepo, 83 - gogitStorage: storage, 84 - tagCache: newObjectCache(), 85 - Ctx: ctx, 86 - objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(), 87 - }, nil 88 - } 89 - 90 - // Close this repository, in particular close the underlying gogitStorage if this is not nil 91 - func (repo *Repository) Close() error { 92 - if repo == nil || repo.gogitStorage == nil { 93 - return nil 94 - } 95 - if err := repo.gogitStorage.Close(); err != nil { 96 - gitealog.Error("Error closing storage: %v", err) 97 - } 98 - repo.gogitStorage = nil 99 - repo.LastCommitCache = nil 100 - repo.tagCache = nil 101 - return nil 102 - } 103 - 104 - // GoGitRepo gets the go-git repo representation 105 - func (repo *Repository) GoGitRepo() *gogit.Repository { 106 - return repo.gogitRepo 107 - }
-122
modules/git/repo_base_nogogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2017 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build !gogit 6 - 7 - package git 8 - 9 - import ( 10 - "bufio" 11 - "context" 12 - "errors" 13 - "path/filepath" 14 - 15 - "code.gitea.io/gitea/modules/log" 16 - ) 17 - 18 - func init() { 19 - isGogit = false 20 - } 21 - 22 - // Repository represents a Git repository. 23 - type Repository struct { 24 - Path string 25 - 26 - tagCache *ObjectCache 27 - 28 - gpgSettings *GPGSettings 29 - 30 - batchInUse bool 31 - batchCancel context.CancelFunc 32 - batchReader *bufio.Reader 33 - batchWriter WriteCloserError 34 - 35 - checkInUse bool 36 - checkCancel context.CancelFunc 37 - checkReader *bufio.Reader 38 - checkWriter WriteCloserError 39 - 40 - Ctx context.Context 41 - LastCommitCache *LastCommitCache 42 - 43 - objectFormat ObjectFormat 44 - } 45 - 46 - // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. 47 - func openRepositoryWithDefaultContext(repoPath string) (*Repository, error) { 48 - return OpenRepository(DefaultContext, repoPath) 49 - } 50 - 51 - // OpenRepository opens the repository at the given path with the provided context. 52 - func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { 53 - repoPath, err := filepath.Abs(repoPath) 54 - if err != nil { 55 - return nil, err 56 - } else if !isDir(repoPath) { 57 - return nil, errors.New("no such file or directory") 58 - } 59 - 60 - // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! 61 - if err := EnsureValidGitRepository(ctx, repoPath); err != nil { 62 - return nil, err 63 - } 64 - 65 - repo := &Repository{ 66 - Path: repoPath, 67 - tagCache: newObjectCache(), 68 - Ctx: ctx, 69 - } 70 - 71 - repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath) 72 - repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath) 73 - 74 - return repo, nil 75 - } 76 - 77 - // CatFileBatch obtains a CatFileBatch for this repository 78 - func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { 79 - if repo.batchCancel == nil || repo.batchInUse { 80 - log.Debug("Opening temporary cat file batch for: %s", repo.Path) 81 - return CatFileBatch(ctx, repo.Path) 82 - } 83 - repo.batchInUse = true 84 - return repo.batchWriter, repo.batchReader, func() { 85 - repo.batchInUse = false 86 - } 87 - } 88 - 89 - // CatFileBatchCheck obtains a CatFileBatchCheck for this repository 90 - func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { 91 - if repo.checkCancel == nil || repo.checkInUse { 92 - log.Debug("Opening temporary cat file batch-check for: %s", repo.Path) 93 - return CatFileBatchCheck(ctx, repo.Path) 94 - } 95 - repo.checkInUse = true 96 - return repo.checkWriter, repo.checkReader, func() { 97 - repo.checkInUse = false 98 - } 99 - } 100 - 101 - func (repo *Repository) Close() error { 102 - if repo == nil { 103 - return nil 104 - } 105 - if repo.batchCancel != nil { 106 - repo.batchCancel() 107 - repo.batchReader = nil 108 - repo.batchWriter = nil 109 - repo.batchCancel = nil 110 - repo.batchInUse = false 111 - } 112 - if repo.checkCancel != nil { 113 - repo.checkCancel() 114 - repo.checkCancel = nil 115 - repo.checkReader = nil 116 - repo.checkWriter = nil 117 - repo.checkInUse = false 118 - } 119 - repo.LastCommitCache = nil 120 - repo.tagCache = nil 121 - return nil 122 - }
-13
modules/git/repo_blob.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package git 5 - 6 - // GetBlob finds the blob object in the repository. 7 - func (repo *Repository) GetBlob(idStr string) (*Blob, error) { 8 - id, err := NewIDFromString(idStr) 9 - if err != nil { 10 - return nil, err 11 - } 12 - return repo.getBlob(id) 13 - }
-22
modules/git/repo_blob_gogit.go
··· 1 - // Copyright 2018 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "github.com/go-git/go-git/v5/plumbing" 10 - ) 11 - 12 - func (repo *Repository) getBlob(id ObjectID) (*Blob, error) { 13 - encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id.RawValue())) 14 - if err != nil { 15 - return nil, ErrNotExist{id.String(), ""} 16 - } 17 - 18 - return &Blob{ 19 - ID: id, 20 - gogitEncodedObj: encodedObj, 21 - }, nil 22 - }
-16
modules/git/repo_blob_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - func (repo *Repository) getBlob(id ObjectID) (*Blob, error) { 9 - if id.IsZero() { 10 - return nil, ErrNotExist{id.String(), ""} 11 - } 12 - return &Blob{ 13 - ID: id, 14 - repo: repo, 15 - }, nil 16 - }
+182
modules/git/repo_branch.go
··· 5 5 package git 6 6 7 7 import ( 8 + "bufio" 9 + "bytes" 8 10 "context" 9 11 "errors" 10 12 "fmt" 13 + "io" 11 14 "strings" 15 + 16 + "code.gitea.io/gitea/modules/log" 12 17 ) 13 18 14 19 // BranchPrefix base dir of the branch information file store on git ··· 157 162 _, _, err := NewCommand(repo.Ctx, "branch", "-m").AddDynamicArguments(from, to).RunStdString(&RunOpts{Dir: repo.Path}) 158 163 return err 159 164 } 165 + 166 + // IsObjectExist returns true if given reference exists in the repository. 167 + func (repo *Repository) IsObjectExist(name string) bool { 168 + if name == "" { 169 + return false 170 + } 171 + 172 + wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 173 + defer cancel() 174 + _, err := wr.Write([]byte(name + "\n")) 175 + if err != nil { 176 + log.Debug("Error writing to CatFileBatchCheck %v", err) 177 + return false 178 + } 179 + sha, _, _, err := ReadBatchLine(rd) 180 + return err == nil && bytes.HasPrefix(sha, []byte(strings.TrimSpace(name))) 181 + } 182 + 183 + // IsReferenceExist returns true if given reference exists in the repository. 184 + func (repo *Repository) IsReferenceExist(name string) bool { 185 + if name == "" { 186 + return false 187 + } 188 + 189 + wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 190 + defer cancel() 191 + _, err := wr.Write([]byte(name + "\n")) 192 + if err != nil { 193 + log.Debug("Error writing to CatFileBatchCheck %v", err) 194 + return false 195 + } 196 + _, _, _, err = ReadBatchLine(rd) 197 + return err == nil 198 + } 199 + 200 + // IsBranchExist returns true if given branch exists in current repository. 201 + func (repo *Repository) IsBranchExist(name string) bool { 202 + if repo == nil || name == "" { 203 + return false 204 + } 205 + 206 + return repo.IsReferenceExist(BranchPrefix + name) 207 + } 208 + 209 + // GetBranchNames returns branches from the repository, skipping "skip" initial branches and 210 + // returning at most "limit" branches, or all branches if "limit" is 0. 211 + func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { 212 + return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) 213 + } 214 + 215 + // WalkReferences walks all the references from the repository 216 + // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. 217 + func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { 218 + var args TrustedCmdArgs 219 + switch refType { 220 + case ObjectTag: 221 + args = TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} 222 + case ObjectBranch: 223 + args = TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} 224 + } 225 + 226 + return WalkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) 227 + } 228 + 229 + // callShowRef return refs, if limit = 0 it will not limit 230 + func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { 231 + countAll, err = WalkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { 232 + branchName = strings.TrimPrefix(branchName, trimPrefix) 233 + branchNames = append(branchNames, branchName) 234 + 235 + return nil 236 + }) 237 + return branchNames, countAll, err 238 + } 239 + 240 + func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { 241 + stdoutReader, stdoutWriter := io.Pipe() 242 + defer func() { 243 + _ = stdoutReader.Close() 244 + _ = stdoutWriter.Close() 245 + }() 246 + 247 + go func() { 248 + stderrBuilder := &strings.Builder{} 249 + args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} 250 + args = append(args, extraArgs...) 251 + err := NewCommand(ctx, args...).Run(&RunOpts{ 252 + Dir: repoPath, 253 + Stdout: stdoutWriter, 254 + Stderr: stderrBuilder, 255 + }) 256 + if err != nil { 257 + if stderrBuilder.Len() == 0 { 258 + _ = stdoutWriter.Close() 259 + return 260 + } 261 + _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) 262 + } else { 263 + _ = stdoutWriter.Close() 264 + } 265 + }() 266 + 267 + i := 0 268 + bufReader := bufio.NewReader(stdoutReader) 269 + for i < skip { 270 + _, isPrefix, err := bufReader.ReadLine() 271 + if err == io.EOF { 272 + return i, nil 273 + } 274 + if err != nil { 275 + return 0, err 276 + } 277 + if !isPrefix { 278 + i++ 279 + } 280 + } 281 + for limit == 0 || i < skip+limit { 282 + // The output of show-ref is simply a list: 283 + // <sha> SP <ref> LF 284 + sha, err := bufReader.ReadString(' ') 285 + if err == io.EOF { 286 + return i, nil 287 + } 288 + if err != nil { 289 + return 0, err 290 + } 291 + 292 + branchName, err := bufReader.ReadString('\n') 293 + if err == io.EOF { 294 + // This shouldn't happen... but we'll tolerate it for the sake of peace 295 + return i, nil 296 + } 297 + if err != nil { 298 + return i, err 299 + } 300 + 301 + if len(branchName) > 0 { 302 + branchName = branchName[:len(branchName)-1] 303 + } 304 + 305 + if len(sha) > 0 { 306 + sha = sha[:len(sha)-1] 307 + } 308 + 309 + err = walkfn(sha, branchName) 310 + if err != nil { 311 + return i, err 312 + } 313 + i++ 314 + } 315 + // count all refs 316 + for limit != 0 { 317 + _, isPrefix, err := bufReader.ReadLine() 318 + if err == io.EOF { 319 + return i, nil 320 + } 321 + if err != nil { 322 + return 0, err 323 + } 324 + if !isPrefix { 325 + i++ 326 + } 327 + } 328 + return i, nil 329 + } 330 + 331 + // GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash 332 + func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) { 333 + var revList []string 334 + _, err := WalkShowRef(repo.Ctx, repo.Path, nil, 0, 0, func(walkSha, refname string) error { 335 + if walkSha == sha && strings.HasPrefix(refname, prefix) { 336 + revList = append(revList, refname) 337 + } 338 + return nil 339 + }) 340 + return revList, err 341 + }
-147
modules/git/repo_branch_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2018 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "sort" 11 - "strings" 12 - 13 - "github.com/go-git/go-git/v5/plumbing" 14 - "github.com/go-git/go-git/v5/plumbing/storer" 15 - ) 16 - 17 - // IsObjectExist returns true if given reference exists in the repository. 18 - func (repo *Repository) IsObjectExist(name string) bool { 19 - if name == "" { 20 - return false 21 - } 22 - 23 - _, err := repo.gogitRepo.ResolveRevision(plumbing.Revision(name)) 24 - 25 - return err == nil 26 - } 27 - 28 - // IsReferenceExist returns true if given reference exists in the repository. 29 - func (repo *Repository) IsReferenceExist(name string) bool { 30 - if name == "" { 31 - return false 32 - } 33 - 34 - reference, err := repo.gogitRepo.Reference(plumbing.ReferenceName(name), true) 35 - if err != nil { 36 - return false 37 - } 38 - return reference.Type() != plumbing.InvalidReference 39 - } 40 - 41 - // IsBranchExist returns true if given branch exists in current repository. 42 - func (repo *Repository) IsBranchExist(name string) bool { 43 - if name == "" { 44 - return false 45 - } 46 - reference, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true) 47 - if err != nil { 48 - return false 49 - } 50 - return reference.Type() != plumbing.InvalidReference 51 - } 52 - 53 - // GetBranches returns branches from the repository, skipping "skip" initial branches and 54 - // returning at most "limit" branches, or all branches if "limit" is 0. 55 - // Branches are returned with sort of `-commiterdate` as the nogogit 56 - // implementation. This requires full fetch, sort and then the 57 - // skip/limit applies later as gogit returns in undefined order. 58 - func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { 59 - type BranchData struct { 60 - name string 61 - committerDate int64 62 - } 63 - var branchData []BranchData 64 - 65 - branchIter, err := repo.gogitRepo.Branches() 66 - if err != nil { 67 - return nil, 0, err 68 - } 69 - 70 - _ = branchIter.ForEach(func(branch *plumbing.Reference) error { 71 - obj, err := repo.gogitRepo.CommitObject(branch.Hash()) 72 - if err != nil { 73 - // skip branch if can't find commit 74 - return nil 75 - } 76 - 77 - branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), BranchPrefix), obj.Committer.When.Unix()}) 78 - return nil 79 - }) 80 - 81 - sort.Slice(branchData, func(i, j int) bool { 82 - return !(branchData[i].committerDate < branchData[j].committerDate) 83 - }) 84 - 85 - var branchNames []string 86 - maxPos := len(branchData) 87 - if limit > 0 { 88 - maxPos = min(skip+limit, maxPos) 89 - } 90 - for i := skip; i < maxPos; i++ { 91 - branchNames = append(branchNames, branchData[i].name) 92 - } 93 - 94 - return branchNames, len(branchData), nil 95 - } 96 - 97 - // WalkReferences walks all the references from the repository 98 - func (repo *Repository) WalkReferences(arg ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { 99 - i := 0 100 - var iter storer.ReferenceIter 101 - var err error 102 - switch arg { 103 - case ObjectTag: 104 - iter, err = repo.gogitRepo.Tags() 105 - case ObjectBranch: 106 - iter, err = repo.gogitRepo.Branches() 107 - default: 108 - iter, err = repo.gogitRepo.References() 109 - } 110 - if err != nil { 111 - return i, err 112 - } 113 - defer iter.Close() 114 - 115 - err = iter.ForEach(func(ref *plumbing.Reference) error { 116 - if i < skip { 117 - i++ 118 - return nil 119 - } 120 - err := walkfn(ref.Hash().String(), string(ref.Name())) 121 - i++ 122 - if err != nil { 123 - return err 124 - } 125 - if limit != 0 && i >= skip+limit { 126 - return storer.ErrStop 127 - } 128 - return nil 129 - }) 130 - return i, err 131 - } 132 - 133 - // GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash 134 - func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) { 135 - var revList []string 136 - iter, err := repo.gogitRepo.References() 137 - if err != nil { 138 - return nil, err 139 - } 140 - err = iter.ForEach(func(ref *plumbing.Reference) error { 141 - if ref.Hash().String() == sha && strings.HasPrefix(string(ref.Name()), prefix) { 142 - revList = append(revList, string(ref.Name())) 143 - } 144 - return nil 145 - }) 146 - return revList, err 147 - }
-194
modules/git/repo_branch_nogogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2018 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build !gogit 6 - 7 - package git 8 - 9 - import ( 10 - "bufio" 11 - "bytes" 12 - "context" 13 - "io" 14 - "strings" 15 - 16 - "code.gitea.io/gitea/modules/log" 17 - ) 18 - 19 - // IsObjectExist returns true if given reference exists in the repository. 20 - func (repo *Repository) IsObjectExist(name string) bool { 21 - if name == "" { 22 - return false 23 - } 24 - 25 - wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 26 - defer cancel() 27 - _, err := wr.Write([]byte(name + "\n")) 28 - if err != nil { 29 - log.Debug("Error writing to CatFileBatchCheck %v", err) 30 - return false 31 - } 32 - sha, _, _, err := ReadBatchLine(rd) 33 - return err == nil && bytes.HasPrefix(sha, []byte(strings.TrimSpace(name))) 34 - } 35 - 36 - // IsReferenceExist returns true if given reference exists in the repository. 37 - func (repo *Repository) IsReferenceExist(name string) bool { 38 - if name == "" { 39 - return false 40 - } 41 - 42 - wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 43 - defer cancel() 44 - _, err := wr.Write([]byte(name + "\n")) 45 - if err != nil { 46 - log.Debug("Error writing to CatFileBatchCheck %v", err) 47 - return false 48 - } 49 - _, _, _, err = ReadBatchLine(rd) 50 - return err == nil 51 - } 52 - 53 - // IsBranchExist returns true if given branch exists in current repository. 54 - func (repo *Repository) IsBranchExist(name string) bool { 55 - if repo == nil || name == "" { 56 - return false 57 - } 58 - 59 - return repo.IsReferenceExist(BranchPrefix + name) 60 - } 61 - 62 - // GetBranchNames returns branches from the repository, skipping "skip" initial branches and 63 - // returning at most "limit" branches, or all branches if "limit" is 0. 64 - func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { 65 - return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) 66 - } 67 - 68 - // WalkReferences walks all the references from the repository 69 - // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. 70 - func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { 71 - var args TrustedCmdArgs 72 - switch refType { 73 - case ObjectTag: 74 - args = TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} 75 - case ObjectBranch: 76 - args = TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} 77 - } 78 - 79 - return WalkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) 80 - } 81 - 82 - // callShowRef return refs, if limit = 0 it will not limit 83 - func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { 84 - countAll, err = WalkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { 85 - branchName = strings.TrimPrefix(branchName, trimPrefix) 86 - branchNames = append(branchNames, branchName) 87 - 88 - return nil 89 - }) 90 - return branchNames, countAll, err 91 - } 92 - 93 - func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { 94 - stdoutReader, stdoutWriter := io.Pipe() 95 - defer func() { 96 - _ = stdoutReader.Close() 97 - _ = stdoutWriter.Close() 98 - }() 99 - 100 - go func() { 101 - stderrBuilder := &strings.Builder{} 102 - args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} 103 - args = append(args, extraArgs...) 104 - err := NewCommand(ctx, args...).Run(&RunOpts{ 105 - Dir: repoPath, 106 - Stdout: stdoutWriter, 107 - Stderr: stderrBuilder, 108 - }) 109 - if err != nil { 110 - if stderrBuilder.Len() == 0 { 111 - _ = stdoutWriter.Close() 112 - return 113 - } 114 - _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) 115 - } else { 116 - _ = stdoutWriter.Close() 117 - } 118 - }() 119 - 120 - i := 0 121 - bufReader := bufio.NewReader(stdoutReader) 122 - for i < skip { 123 - _, isPrefix, err := bufReader.ReadLine() 124 - if err == io.EOF { 125 - return i, nil 126 - } 127 - if err != nil { 128 - return 0, err 129 - } 130 - if !isPrefix { 131 - i++ 132 - } 133 - } 134 - for limit == 0 || i < skip+limit { 135 - // The output of show-ref is simply a list: 136 - // <sha> SP <ref> LF 137 - sha, err := bufReader.ReadString(' ') 138 - if err == io.EOF { 139 - return i, nil 140 - } 141 - if err != nil { 142 - return 0, err 143 - } 144 - 145 - branchName, err := bufReader.ReadString('\n') 146 - if err == io.EOF { 147 - // This shouldn't happen... but we'll tolerate it for the sake of peace 148 - return i, nil 149 - } 150 - if err != nil { 151 - return i, err 152 - } 153 - 154 - if len(branchName) > 0 { 155 - branchName = branchName[:len(branchName)-1] 156 - } 157 - 158 - if len(sha) > 0 { 159 - sha = sha[:len(sha)-1] 160 - } 161 - 162 - err = walkfn(sha, branchName) 163 - if err != nil { 164 - return i, err 165 - } 166 - i++ 167 - } 168 - // count all refs 169 - for limit != 0 { 170 - _, isPrefix, err := bufReader.ReadLine() 171 - if err == io.EOF { 172 - return i, nil 173 - } 174 - if err != nil { 175 - return 0, err 176 - } 177 - if !isPrefix { 178 - i++ 179 - } 180 - } 181 - return i, nil 182 - } 183 - 184 - // GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash 185 - func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) { 186 - var revList []string 187 - _, err := WalkShowRef(repo.Ctx, repo.Path, nil, 0, 0, func(walkSha, refname string) error { 188 - if walkSha == sha && strings.HasPrefix(refname, prefix) { 189 - revList = append(revList, refname) 190 - } 191 - return nil 192 - }) 193 - return revList, err 194 - }
+149
modules/git/repo_commit.go
··· 5 5 package git 6 6 7 7 import ( 8 + "bufio" 8 9 "bytes" 10 + "errors" 9 11 "io" 10 12 "strconv" 11 13 "strings" 12 14 13 15 "code.gitea.io/gitea/modules/cache" 16 + "code.gitea.io/gitea/modules/log" 14 17 "code.gitea.io/gitea/modules/setting" 15 18 ) 16 19 ··· 513 516 } 514 517 return nil 515 518 } 519 + 520 + // ResolveReference resolves a name to a reference 521 + func (repo *Repository) ResolveReference(name string) (string, error) { 522 + stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) 523 + if err != nil { 524 + if strings.Contains(err.Error(), "not a valid ref") { 525 + return "", ErrNotExist{name, ""} 526 + } 527 + return "", err 528 + } 529 + stdout = strings.TrimSpace(stdout) 530 + if stdout == "" { 531 + return "", ErrNotExist{name, ""} 532 + } 533 + 534 + return stdout, nil 535 + } 536 + 537 + // GetRefCommitID returns the last commit ID string of given reference (branch or tag). 538 + func (repo *Repository) GetRefCommitID(name string) (string, error) { 539 + wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 540 + defer cancel() 541 + _, err := wr.Write([]byte(name + "\n")) 542 + if err != nil { 543 + return "", err 544 + } 545 + shaBs, _, _, err := ReadBatchLine(rd) 546 + if IsErrNotExist(err) { 547 + return "", ErrNotExist{name, ""} 548 + } 549 + 550 + return string(shaBs), nil 551 + } 552 + 553 + // SetReference sets the commit ID string of given reference (e.g. branch or tag). 554 + func (repo *Repository) SetReference(name, commitID string) error { 555 + _, _, err := NewCommand(repo.Ctx, "update-ref").AddDynamicArguments(name, commitID).RunStdString(&RunOpts{Dir: repo.Path}) 556 + return err 557 + } 558 + 559 + // RemoveReference removes the given reference (e.g. branch or tag). 560 + func (repo *Repository) RemoveReference(name string) error { 561 + _, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) 562 + return err 563 + } 564 + 565 + // IsCommitExist returns true if given commit exists in current repository. 566 + func (repo *Repository) IsCommitExist(name string) bool { 567 + _, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) 568 + return err == nil 569 + } 570 + 571 + func (repo *Repository) getCommit(id ObjectID) (*Commit, error) { 572 + wr, rd, cancel := repo.CatFileBatch(repo.Ctx) 573 + defer cancel() 574 + 575 + _, _ = wr.Write([]byte(id.String() + "\n")) 576 + 577 + return repo.getCommitFromBatchReader(rd, id) 578 + } 579 + 580 + func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) (*Commit, error) { 581 + _, typ, size, err := ReadBatchLine(rd) 582 + if err != nil { 583 + if errors.Is(err, io.EOF) || IsErrNotExist(err) { 584 + return nil, ErrNotExist{ID: id.String()} 585 + } 586 + return nil, err 587 + } 588 + 589 + switch typ { 590 + case "missing": 591 + return nil, ErrNotExist{ID: id.String()} 592 + case "tag": 593 + // then we need to parse the tag 594 + // and load the commit 595 + data, err := io.ReadAll(io.LimitReader(rd, size)) 596 + if err != nil { 597 + return nil, err 598 + } 599 + _, err = rd.Discard(1) 600 + if err != nil { 601 + return nil, err 602 + } 603 + tag, err := parseTagData(id.Type(), data) 604 + if err != nil { 605 + return nil, err 606 + } 607 + 608 + commit, err := tag.Commit(repo) 609 + if err != nil { 610 + return nil, err 611 + } 612 + 613 + return commit, nil 614 + case "commit": 615 + commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size)) 616 + if err != nil { 617 + return nil, err 618 + } 619 + _, err = rd.Discard(1) 620 + if err != nil { 621 + return nil, err 622 + } 623 + 624 + return commit, nil 625 + default: 626 + log.Debug("Unknown typ: %s", typ) 627 + if err := DiscardFull(rd, size+1); err != nil { 628 + return nil, err 629 + } 630 + return nil, ErrNotExist{ 631 + ID: id.String(), 632 + } 633 + } 634 + } 635 + 636 + // ConvertToGitID returns a GitHash object from a potential ID string 637 + func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { 638 + objectFormat, err := repo.GetObjectFormat() 639 + if err != nil { 640 + return nil, err 641 + } 642 + if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) { 643 + ID, err := NewIDFromString(commitID) 644 + if err == nil { 645 + return ID, nil 646 + } 647 + } 648 + 649 + wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 650 + defer cancel() 651 + _, err = wr.Write([]byte(commitID + "\n")) 652 + if err != nil { 653 + return nil, err 654 + } 655 + sha, _, _, err := ReadBatchLine(rd) 656 + if err != nil { 657 + if IsErrNotExist(err) { 658 + return nil, ErrNotExist{commitID, ""} 659 + } 660 + return nil, err 661 + } 662 + 663 + return MustIDFromString(string(sha)), nil 664 + }
-111
modules/git/repo_commit_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "strings" 11 - 12 - "github.com/go-git/go-git/v5/plumbing" 13 - "github.com/go-git/go-git/v5/plumbing/hash" 14 - "github.com/go-git/go-git/v5/plumbing/object" 15 - ) 16 - 17 - // GetRefCommitID returns the last commit ID string of given reference (branch or tag). 18 - func (repo *Repository) GetRefCommitID(name string) (string, error) { 19 - ref, err := repo.gogitRepo.Reference(plumbing.ReferenceName(name), true) 20 - if err != nil { 21 - if err == plumbing.ErrReferenceNotFound { 22 - return "", ErrNotExist{ 23 - ID: name, 24 - } 25 - } 26 - return "", err 27 - } 28 - 29 - return ref.Hash().String(), nil 30 - } 31 - 32 - // SetReference sets the commit ID string of given reference (e.g. branch or tag). 33 - func (repo *Repository) SetReference(name, commitID string) error { 34 - return repo.gogitRepo.Storer.SetReference(plumbing.NewReferenceFromStrings(name, commitID)) 35 - } 36 - 37 - // RemoveReference removes the given reference (e.g. branch or tag). 38 - func (repo *Repository) RemoveReference(name string) error { 39 - return repo.gogitRepo.Storer.RemoveReference(plumbing.ReferenceName(name)) 40 - } 41 - 42 - // ConvertToHash returns a Hash object from a potential ID string 43 - func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { 44 - objectFormat, err := repo.GetObjectFormat() 45 - if err != nil { 46 - return nil, err 47 - } 48 - if len(commitID) == hash.HexSize && objectFormat.IsValid(commitID) { 49 - ID, err := NewIDFromString(commitID) 50 - if err == nil { 51 - return ID, nil 52 - } 53 - } 54 - 55 - actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path}) 56 - actualCommitID = strings.TrimSpace(actualCommitID) 57 - if err != nil { 58 - if strings.Contains(err.Error(), "unknown revision or path") || 59 - strings.Contains(err.Error(), "fatal: Needed a single revision") { 60 - return objectFormat.EmptyObjectID(), ErrNotExist{commitID, ""} 61 - } 62 - return objectFormat.EmptyObjectID(), err 63 - } 64 - 65 - return NewIDFromString(actualCommitID) 66 - } 67 - 68 - // IsCommitExist returns true if given commit exists in current repository. 69 - func (repo *Repository) IsCommitExist(name string) bool { 70 - hash, err := repo.ConvertToGitID(name) 71 - if err != nil { 72 - return false 73 - } 74 - _, err = repo.gogitRepo.CommitObject(plumbing.Hash(hash.RawValue())) 75 - return err == nil 76 - } 77 - 78 - func (repo *Repository) getCommit(id ObjectID) (*Commit, error) { 79 - var tagObject *object.Tag 80 - 81 - commitID := plumbing.Hash(id.RawValue()) 82 - gogitCommit, err := repo.gogitRepo.CommitObject(commitID) 83 - if err == plumbing.ErrObjectNotFound { 84 - tagObject, err = repo.gogitRepo.TagObject(commitID) 85 - if err == plumbing.ErrObjectNotFound { 86 - return nil, ErrNotExist{ 87 - ID: id.String(), 88 - } 89 - } 90 - if err == nil { 91 - gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target) 92 - } 93 - // if we get a plumbing.ErrObjectNotFound here then the repository is broken and it should be 500 94 - } 95 - if err != nil { 96 - return nil, err 97 - } 98 - 99 - commit := convertCommit(gogitCommit) 100 - commit.repo = repo 101 - 102 - tree, err := gogitCommit.Tree() 103 - if err != nil { 104 - return nil, err 105 - } 106 - 107 - commit.Tree.ID = ParseGogitHash(tree.Hash) 108 - commit.Tree.gogitTree = tree 109 - 110 - return commit, nil 111 - }
-161
modules/git/repo_commit_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "bufio" 10 - "errors" 11 - "io" 12 - "strings" 13 - 14 - "code.gitea.io/gitea/modules/log" 15 - ) 16 - 17 - // ResolveReference resolves a name to a reference 18 - func (repo *Repository) ResolveReference(name string) (string, error) { 19 - stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) 20 - if err != nil { 21 - if strings.Contains(err.Error(), "not a valid ref") { 22 - return "", ErrNotExist{name, ""} 23 - } 24 - return "", err 25 - } 26 - stdout = strings.TrimSpace(stdout) 27 - if stdout == "" { 28 - return "", ErrNotExist{name, ""} 29 - } 30 - 31 - return stdout, nil 32 - } 33 - 34 - // GetRefCommitID returns the last commit ID string of given reference (branch or tag). 35 - func (repo *Repository) GetRefCommitID(name string) (string, error) { 36 - wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 37 - defer cancel() 38 - _, err := wr.Write([]byte(name + "\n")) 39 - if err != nil { 40 - return "", err 41 - } 42 - shaBs, _, _, err := ReadBatchLine(rd) 43 - if IsErrNotExist(err) { 44 - return "", ErrNotExist{name, ""} 45 - } 46 - 47 - return string(shaBs), nil 48 - } 49 - 50 - // SetReference sets the commit ID string of given reference (e.g. branch or tag). 51 - func (repo *Repository) SetReference(name, commitID string) error { 52 - _, _, err := NewCommand(repo.Ctx, "update-ref").AddDynamicArguments(name, commitID).RunStdString(&RunOpts{Dir: repo.Path}) 53 - return err 54 - } 55 - 56 - // RemoveReference removes the given reference (e.g. branch or tag). 57 - func (repo *Repository) RemoveReference(name string) error { 58 - _, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) 59 - return err 60 - } 61 - 62 - // IsCommitExist returns true if given commit exists in current repository. 63 - func (repo *Repository) IsCommitExist(name string) bool { 64 - _, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) 65 - return err == nil 66 - } 67 - 68 - func (repo *Repository) getCommit(id ObjectID) (*Commit, error) { 69 - wr, rd, cancel := repo.CatFileBatch(repo.Ctx) 70 - defer cancel() 71 - 72 - _, _ = wr.Write([]byte(id.String() + "\n")) 73 - 74 - return repo.getCommitFromBatchReader(rd, id) 75 - } 76 - 77 - func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) (*Commit, error) { 78 - _, typ, size, err := ReadBatchLine(rd) 79 - if err != nil { 80 - if errors.Is(err, io.EOF) || IsErrNotExist(err) { 81 - return nil, ErrNotExist{ID: id.String()} 82 - } 83 - return nil, err 84 - } 85 - 86 - switch typ { 87 - case "missing": 88 - return nil, ErrNotExist{ID: id.String()} 89 - case "tag": 90 - // then we need to parse the tag 91 - // and load the commit 92 - data, err := io.ReadAll(io.LimitReader(rd, size)) 93 - if err != nil { 94 - return nil, err 95 - } 96 - _, err = rd.Discard(1) 97 - if err != nil { 98 - return nil, err 99 - } 100 - tag, err := parseTagData(id.Type(), data) 101 - if err != nil { 102 - return nil, err 103 - } 104 - 105 - commit, err := tag.Commit(repo) 106 - if err != nil { 107 - return nil, err 108 - } 109 - 110 - return commit, nil 111 - case "commit": 112 - commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size)) 113 - if err != nil { 114 - return nil, err 115 - } 116 - _, err = rd.Discard(1) 117 - if err != nil { 118 - return nil, err 119 - } 120 - 121 - return commit, nil 122 - default: 123 - log.Debug("Unknown typ: %s", typ) 124 - if err := DiscardFull(rd, size+1); err != nil { 125 - return nil, err 126 - } 127 - return nil, ErrNotExist{ 128 - ID: id.String(), 129 - } 130 - } 131 - } 132 - 133 - // ConvertToGitID returns a GitHash object from a potential ID string 134 - func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { 135 - objectFormat, err := repo.GetObjectFormat() 136 - if err != nil { 137 - return nil, err 138 - } 139 - if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) { 140 - ID, err := NewIDFromString(commitID) 141 - if err == nil { 142 - return ID, nil 143 - } 144 - } 145 - 146 - wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 147 - defer cancel() 148 - _, err = wr.Write([]byte(commitID + "\n")) 149 - if err != nil { 150 - return nil, err 151 - } 152 - sha, _, _, err := ReadBatchLine(rd) 153 - if err != nil { 154 - if IsErrNotExist(err) { 155 - return nil, ErrNotExist{commitID, ""} 156 - } 157 - return nil, err 158 - } 159 - 160 - return MustIDFromString(string(sha)), nil 161 - }
-37
modules/git/repo_commitgraph_gogit.go
··· 1 - // Copyright 2019 The Gitea Authors. 2 - // All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "os" 11 - "path" 12 - 13 - gitealog "code.gitea.io/gitea/modules/log" 14 - 15 - commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" 16 - cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" 17 - ) 18 - 19 - // CommitNodeIndex returns the index for walking commit graph 20 - func (r *Repository) CommitNodeIndex() (cgobject.CommitNodeIndex, *os.File) { 21 - indexPath := path.Join(r.Path, "objects", "info", "commit-graph") 22 - 23 - file, err := os.Open(indexPath) 24 - if err == nil { 25 - var index commitgraph.Index 26 - index, err = commitgraph.OpenFileIndex(file) 27 - if err == nil { 28 - return cgobject.NewGraphCommitNodeIndex(index, r.gogitRepo.Storer), file 29 - } 30 - } 31 - 32 - if !os.IsNotExist(err) { 33 - gitealog.Warn("Unable to read commit-graph for %s: %v", r.Path, err) 34 - } 35 - 36 - return cgobject.NewObjectCommitNodeIndex(r.gogitRepo.Storer), nil 37 - }
+200
modules/git/repo_language_stats.go
··· 4 4 package git 5 5 6 6 import ( 7 + "bytes" 8 + "cmp" 9 + "io" 7 10 "strings" 8 11 "unicode" 12 + 13 + "code.gitea.io/gitea/modules/analyze" 14 + "code.gitea.io/gitea/modules/log" 15 + "code.gitea.io/gitea/modules/optional" 16 + 17 + "github.com/go-enry/go-enry/v2" 9 18 ) 10 19 11 20 const ( ··· 46 55 } 47 56 return res 48 57 } 58 + 59 + // GetLanguageStats calculates language stats for git repository at specified commit 60 + func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { 61 + // We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary. 62 + // so let's create a batch stdin and stdout 63 + batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx) 64 + defer cancel() 65 + 66 + writeID := func(id string) error { 67 + _, err := batchStdinWriter.Write([]byte(id + "\n")) 68 + return err 69 + } 70 + 71 + if err := writeID(commitID); err != nil { 72 + return nil, err 73 + } 74 + shaBytes, typ, size, err := ReadBatchLine(batchReader) 75 + if typ != "commit" { 76 + log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) 77 + return nil, ErrNotExist{commitID, ""} 78 + } 79 + 80 + sha, err := NewIDFromString(string(shaBytes)) 81 + if err != nil { 82 + log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) 83 + return nil, ErrNotExist{commitID, ""} 84 + } 85 + 86 + commit, err := CommitFromReader(repo, sha, io.LimitReader(batchReader, size)) 87 + if err != nil { 88 + log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) 89 + return nil, err 90 + } 91 + if _, err = batchReader.Discard(1); err != nil { 92 + return nil, err 93 + } 94 + 95 + tree := commit.Tree 96 + 97 + entries, err := tree.ListEntriesRecursiveWithSize() 98 + if err != nil { 99 + return nil, err 100 + } 101 + 102 + checker, err := repo.GitAttributeChecker(commitID, LinguistAttributes...) 103 + if err != nil { 104 + return nil, err 105 + } 106 + defer checker.Close() 107 + 108 + contentBuf := bytes.Buffer{} 109 + var content []byte 110 + 111 + // sizes contains the current calculated size of all files by language 112 + sizes := make(map[string]int64) 113 + // by default we will only count the sizes of programming languages or markup languages 114 + // unless they are explicitly set using linguist-language 115 + includedLanguage := map[string]bool{} 116 + // or if there's only one language in the repository 117 + firstExcludedLanguage := "" 118 + firstExcludedLanguageSize := int64(0) 119 + 120 + isTrue := func(v optional.Option[bool]) bool { 121 + return v.ValueOrDefault(false) 122 + } 123 + isFalse := func(v optional.Option[bool]) bool { 124 + return !v.ValueOrDefault(true) 125 + } 126 + 127 + for _, f := range entries { 128 + select { 129 + case <-repo.Ctx.Done(): 130 + return sizes, repo.Ctx.Err() 131 + default: 132 + } 133 + 134 + contentBuf.Reset() 135 + content = contentBuf.Bytes() 136 + 137 + if f.Size() == 0 { 138 + continue 139 + } 140 + 141 + isVendored := optional.None[bool]() 142 + isGenerated := optional.None[bool]() 143 + isDocumentation := optional.None[bool]() 144 + isDetectable := optional.None[bool]() 145 + 146 + attrs, err := checker.CheckPath(f.Name()) 147 + if err == nil { 148 + isVendored = attrs["linguist-vendored"].Bool() 149 + isGenerated = attrs["linguist-generated"].Bool() 150 + isDocumentation = attrs["linguist-documentation"].Bool() 151 + isDetectable = attrs["linguist-detectable"].Bool() 152 + if language := cmp.Or( 153 + attrs["linguist-language"].String(), 154 + attrs["gitlab-language"].Prefix(), 155 + ); language != "" { 156 + // group languages, such as Pug -> HTML; SCSS -> CSS 157 + group := enry.GetLanguageGroup(language) 158 + if len(group) != 0 { 159 + language = group 160 + } 161 + 162 + // this language will always be added to the size 163 + sizes[language] += f.Size() 164 + continue 165 + } 166 + } 167 + 168 + if isFalse(isDetectable) || isTrue(isVendored) || isTrue(isDocumentation) || 169 + (!isFalse(isVendored) && analyze.IsVendor(f.Name())) || 170 + enry.IsDotFile(f.Name()) || 171 + enry.IsConfiguration(f.Name()) || 172 + (!isFalse(isDocumentation) && enry.IsDocumentation(f.Name())) { 173 + continue 174 + } 175 + 176 + // If content can not be read or file is too big just do detection by filename 177 + 178 + if f.Size() <= bigFileSize { 179 + if err := writeID(f.ID.String()); err != nil { 180 + return nil, err 181 + } 182 + _, _, size, err := ReadBatchLine(batchReader) 183 + if err != nil { 184 + log.Debug("Error reading blob: %s Err: %v", f.ID.String(), err) 185 + return nil, err 186 + } 187 + 188 + sizeToRead := size 189 + discard := int64(1) 190 + if size > fileSizeLimit { 191 + sizeToRead = fileSizeLimit 192 + discard = size - fileSizeLimit + 1 193 + } 194 + 195 + _, err = contentBuf.ReadFrom(io.LimitReader(batchReader, sizeToRead)) 196 + if err != nil { 197 + return nil, err 198 + } 199 + content = contentBuf.Bytes() 200 + if err := DiscardFull(batchReader, discard); err != nil { 201 + return nil, err 202 + } 203 + } 204 + if !isTrue(isGenerated) && enry.IsGenerated(f.Name(), content) { 205 + continue 206 + } 207 + 208 + // FIXME: Why can't we split this and the IsGenerated tests to avoid reading the blob unless absolutely necessary? 209 + // - eg. do the all the detection tests using filename first before reading content. 210 + language := analyze.GetCodeLanguage(f.Name(), content) 211 + if language == "" { 212 + continue 213 + } 214 + 215 + // group languages, such as Pug -> HTML; SCSS -> CSS 216 + group := enry.GetLanguageGroup(language) 217 + if group != "" { 218 + language = group 219 + } 220 + 221 + included, checked := includedLanguage[language] 222 + langType := enry.GetLanguageType(language) 223 + if !checked { 224 + included = langType == enry.Programming || langType == enry.Markup 225 + if !included && (isTrue(isDetectable) || (langType == enry.Prose && isFalse(isDocumentation))) { 226 + included = true 227 + } 228 + includedLanguage[language] = included 229 + } 230 + if included { 231 + sizes[language] += f.Size() 232 + } else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) { 233 + // Only consider Programming or Markup languages as fallback 234 + if !(langType == enry.Programming || langType == enry.Markup) { 235 + continue 236 + } 237 + firstExcludedLanguage = language 238 + firstExcludedLanguageSize += f.Size() 239 + } 240 + } 241 + 242 + // If there are no included languages add the first excluded language 243 + if len(sizes) == 0 && firstExcludedLanguage != "" { 244 + sizes[firstExcludedLanguage] = firstExcludedLanguageSize 245 + } 246 + 247 + return mergeLanguageStats(sizes), nil 248 + }
-194
modules/git/repo_language_stats_gogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "bytes" 11 - "io" 12 - "strings" 13 - 14 - "code.gitea.io/gitea/modules/analyze" 15 - "code.gitea.io/gitea/modules/optional" 16 - 17 - "github.com/go-enry/go-enry/v2" 18 - "github.com/go-git/go-git/v5" 19 - "github.com/go-git/go-git/v5/plumbing" 20 - "github.com/go-git/go-git/v5/plumbing/object" 21 - ) 22 - 23 - // GetLanguageStats calculates language stats for git repository at specified commit 24 - func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { 25 - r, err := git.PlainOpen(repo.Path) 26 - if err != nil { 27 - return nil, err 28 - } 29 - 30 - rev, err := r.ResolveRevision(plumbing.Revision(commitID)) 31 - if err != nil { 32 - return nil, err 33 - } 34 - 35 - commit, err := r.CommitObject(*rev) 36 - if err != nil { 37 - return nil, err 38 - } 39 - 40 - tree, err := commit.Tree() 41 - if err != nil { 42 - return nil, err 43 - } 44 - 45 - checker, deferable := repo.CheckAttributeReader(commitID) 46 - defer deferable() 47 - 48 - // sizes contains the current calculated size of all files by language 49 - sizes := make(map[string]int64) 50 - // by default we will only count the sizes of programming languages or markup languages 51 - // unless they are explicitly set using linguist-language 52 - includedLanguage := map[string]bool{} 53 - // or if there's only one language in the repository 54 - firstExcludedLanguage := "" 55 - firstExcludedLanguageSize := int64(0) 56 - 57 - isTrue := func(v optional.Option[bool]) bool { 58 - return v.ValueOrDefault(false) 59 - } 60 - isFalse := func(v optional.Option[bool]) bool { 61 - return !v.ValueOrDefault(true) 62 - } 63 - 64 - err = tree.Files().ForEach(func(f *object.File) error { 65 - if f.Size == 0 { 66 - return nil 67 - } 68 - 69 - isVendored := optional.None[bool]() 70 - isGenerated := optional.None[bool]() 71 - isDocumentation := optional.None[bool]() 72 - isDetectable := optional.None[bool]() 73 - 74 - if checker != nil { 75 - attrs, err := checker.CheckPath(f.Name) 76 - if err == nil { 77 - isVendored = attributeToBool(attrs, "linguist-vendored") 78 - isGenerated = attributeToBool(attrs, "linguist-generated") 79 - isDocumentation = attributeToBool(attrs, "linguist-documentation") 80 - isDetectable = attributeToBool(attrs, "linguist-detectable") 81 - if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" { 82 - // group languages, such as Pug -> HTML; SCSS -> CSS 83 - group := enry.GetLanguageGroup(language) 84 - if len(group) != 0 { 85 - language = group 86 - } 87 - 88 - // this language will always be added to the size 89 - sizes[language] += f.Size 90 - return nil 91 - } else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" { 92 - // strip off a ? if present 93 - if idx := strings.IndexByte(language, '?'); idx >= 0 { 94 - language = language[:idx] 95 - } 96 - if len(language) != 0 { 97 - // group languages, such as Pug -> HTML; SCSS -> CSS 98 - group := enry.GetLanguageGroup(language) 99 - if len(group) != 0 { 100 - language = group 101 - } 102 - 103 - // this language will always be added to the size 104 - sizes[language] += f.Size 105 - return nil 106 - } 107 - } 108 - } 109 - } 110 - 111 - if isFalse(isDetectable) || isTrue(isVendored) || isTrue(isDocumentation) || 112 - (!isFalse(isVendored) && analyze.IsVendor(f.Name)) || 113 - enry.IsDotFile(f.Name) || 114 - enry.IsConfiguration(f.Name) || 115 - (!isFalse(isDocumentation) && enry.IsDocumentation(f.Name)) { 116 - return nil 117 - } 118 - 119 - // If content can not be read or file is too big just do detection by filename 120 - var content []byte 121 - if f.Size <= bigFileSize { 122 - content, _ = readFile(f, fileSizeLimit) 123 - } 124 - if !isTrue(isGenerated) && enry.IsGenerated(f.Name, content) { 125 - return nil 126 - } 127 - 128 - // TODO: Use .gitattributes file for linguist overrides 129 - language := analyze.GetCodeLanguage(f.Name, content) 130 - if language == enry.OtherLanguage || language == "" { 131 - return nil 132 - } 133 - 134 - // group languages, such as Pug -> HTML; SCSS -> CSS 135 - group := enry.GetLanguageGroup(language) 136 - if group != "" { 137 - language = group 138 - } 139 - 140 - included, checked := includedLanguage[language] 141 - langType := enry.GetLanguageType(language) 142 - if !checked { 143 - included = langType == enry.Programming || langType == enry.Markup 144 - if !included && (isTrue(isDetectable) || (langType == enry.Prose && isFalse(isDocumentation))) { 145 - included = true 146 - } 147 - includedLanguage[language] = included 148 - } 149 - if included { 150 - sizes[language] += f.Size 151 - } else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) { 152 - // Only consider Programming or Markup languages as fallback 153 - if !(langType == enry.Programming || langType == enry.Markup) { 154 - return nil 155 - } 156 - 157 - firstExcludedLanguage = language 158 - firstExcludedLanguageSize += f.Size 159 - } 160 - 161 - return nil 162 - }) 163 - if err != nil { 164 - return nil, err 165 - } 166 - 167 - // If there are no included languages add the first excluded language 168 - if len(sizes) == 0 && firstExcludedLanguage != "" { 169 - sizes[firstExcludedLanguage] = firstExcludedLanguageSize 170 - } 171 - 172 - return mergeLanguageStats(sizes), nil 173 - } 174 - 175 - func readFile(f *object.File, limit int64) ([]byte, error) { 176 - r, err := f.Reader() 177 - if err != nil { 178 - return nil, err 179 - } 180 - defer r.Close() 181 - 182 - if limit <= 0 { 183 - return io.ReadAll(r) 184 - } 185 - 186 - size := f.Size 187 - if limit > 0 && size > limit { 188 - size = limit 189 - } 190 - buf := bytes.NewBuffer(nil) 191 - buf.Grow(int(size)) 192 - _, err = io.Copy(buf, io.LimitReader(r, limit)) 193 - return buf.Bytes(), err 194 - }
-210
modules/git/repo_language_stats_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build !gogit 6 - 7 - package git 8 - 9 - import ( 10 - "bytes" 11 - "cmp" 12 - "io" 13 - 14 - "code.gitea.io/gitea/modules/analyze" 15 - "code.gitea.io/gitea/modules/log" 16 - "code.gitea.io/gitea/modules/optional" 17 - 18 - "github.com/go-enry/go-enry/v2" 19 - ) 20 - 21 - // GetLanguageStats calculates language stats for git repository at specified commit 22 - func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { 23 - // We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary. 24 - // so let's create a batch stdin and stdout 25 - batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx) 26 - defer cancel() 27 - 28 - writeID := func(id string) error { 29 - _, err := batchStdinWriter.Write([]byte(id + "\n")) 30 - return err 31 - } 32 - 33 - if err := writeID(commitID); err != nil { 34 - return nil, err 35 - } 36 - shaBytes, typ, size, err := ReadBatchLine(batchReader) 37 - if typ != "commit" { 38 - log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) 39 - return nil, ErrNotExist{commitID, ""} 40 - } 41 - 42 - sha, err := NewIDFromString(string(shaBytes)) 43 - if err != nil { 44 - log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) 45 - return nil, ErrNotExist{commitID, ""} 46 - } 47 - 48 - commit, err := CommitFromReader(repo, sha, io.LimitReader(batchReader, size)) 49 - if err != nil { 50 - log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) 51 - return nil, err 52 - } 53 - if _, err = batchReader.Discard(1); err != nil { 54 - return nil, err 55 - } 56 - 57 - tree := commit.Tree 58 - 59 - entries, err := tree.ListEntriesRecursiveWithSize() 60 - if err != nil { 61 - return nil, err 62 - } 63 - 64 - checker, err := repo.GitAttributeChecker(commitID, LinguistAttributes...) 65 - if err != nil { 66 - return nil, err 67 - } 68 - defer checker.Close() 69 - 70 - contentBuf := bytes.Buffer{} 71 - var content []byte 72 - 73 - // sizes contains the current calculated size of all files by language 74 - sizes := make(map[string]int64) 75 - // by default we will only count the sizes of programming languages or markup languages 76 - // unless they are explicitly set using linguist-language 77 - includedLanguage := map[string]bool{} 78 - // or if there's only one language in the repository 79 - firstExcludedLanguage := "" 80 - firstExcludedLanguageSize := int64(0) 81 - 82 - isTrue := func(v optional.Option[bool]) bool { 83 - return v.ValueOrDefault(false) 84 - } 85 - isFalse := func(v optional.Option[bool]) bool { 86 - return !v.ValueOrDefault(true) 87 - } 88 - 89 - for _, f := range entries { 90 - select { 91 - case <-repo.Ctx.Done(): 92 - return sizes, repo.Ctx.Err() 93 - default: 94 - } 95 - 96 - contentBuf.Reset() 97 - content = contentBuf.Bytes() 98 - 99 - if f.Size() == 0 { 100 - continue 101 - } 102 - 103 - isVendored := optional.None[bool]() 104 - isGenerated := optional.None[bool]() 105 - isDocumentation := optional.None[bool]() 106 - isDetectable := optional.None[bool]() 107 - 108 - attrs, err := checker.CheckPath(f.Name()) 109 - if err == nil { 110 - isVendored = attrs["linguist-vendored"].Bool() 111 - isGenerated = attrs["linguist-generated"].Bool() 112 - isDocumentation = attrs["linguist-documentation"].Bool() 113 - isDetectable = attrs["linguist-detectable"].Bool() 114 - if language := cmp.Or( 115 - attrs["linguist-language"].String(), 116 - attrs["gitlab-language"].Prefix(), 117 - ); language != "" { 118 - // group languages, such as Pug -> HTML; SCSS -> CSS 119 - group := enry.GetLanguageGroup(language) 120 - if len(group) != 0 { 121 - language = group 122 - } 123 - 124 - // this language will always be added to the size 125 - sizes[language] += f.Size() 126 - continue 127 - } 128 - } 129 - 130 - if isFalse(isDetectable) || isTrue(isVendored) || isTrue(isDocumentation) || 131 - (!isFalse(isVendored) && analyze.IsVendor(f.Name())) || 132 - enry.IsDotFile(f.Name()) || 133 - enry.IsConfiguration(f.Name()) || 134 - (!isFalse(isDocumentation) && enry.IsDocumentation(f.Name())) { 135 - continue 136 - } 137 - 138 - // If content can not be read or file is too big just do detection by filename 139 - 140 - if f.Size() <= bigFileSize { 141 - if err := writeID(f.ID.String()); err != nil { 142 - return nil, err 143 - } 144 - _, _, size, err := ReadBatchLine(batchReader) 145 - if err != nil { 146 - log.Debug("Error reading blob: %s Err: %v", f.ID.String(), err) 147 - return nil, err 148 - } 149 - 150 - sizeToRead := size 151 - discard := int64(1) 152 - if size > fileSizeLimit { 153 - sizeToRead = fileSizeLimit 154 - discard = size - fileSizeLimit + 1 155 - } 156 - 157 - _, err = contentBuf.ReadFrom(io.LimitReader(batchReader, sizeToRead)) 158 - if err != nil { 159 - return nil, err 160 - } 161 - content = contentBuf.Bytes() 162 - if err := DiscardFull(batchReader, discard); err != nil { 163 - return nil, err 164 - } 165 - } 166 - if !isTrue(isGenerated) && enry.IsGenerated(f.Name(), content) { 167 - continue 168 - } 169 - 170 - // FIXME: Why can't we split this and the IsGenerated tests to avoid reading the blob unless absolutely necessary? 171 - // - eg. do the all the detection tests using filename first before reading content. 172 - language := analyze.GetCodeLanguage(f.Name(), content) 173 - if language == "" { 174 - continue 175 - } 176 - 177 - // group languages, such as Pug -> HTML; SCSS -> CSS 178 - group := enry.GetLanguageGroup(language) 179 - if group != "" { 180 - language = group 181 - } 182 - 183 - included, checked := includedLanguage[language] 184 - langType := enry.GetLanguageType(language) 185 - if !checked { 186 - included = langType == enry.Programming || langType == enry.Markup 187 - if !included && (isTrue(isDetectable) || (langType == enry.Prose && isFalse(isDocumentation))) { 188 - included = true 189 - } 190 - includedLanguage[language] = included 191 - } 192 - if included { 193 - sizes[language] += f.Size() 194 - } else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) { 195 - // Only consider Programming or Markup languages as fallback 196 - if !(langType == enry.Programming || langType == enry.Markup) { 197 - continue 198 - } 199 - firstExcludedLanguage = language 200 - firstExcludedLanguageSize += f.Size() 201 - } 202 - } 203 - 204 - // If there are no included languages add the first excluded language 205 - if len(sizes) == 0 && firstExcludedLanguage != "" { 206 - sizes[firstExcludedLanguage] = firstExcludedLanguageSize 207 - } 208 - 209 - return mergeLanguageStats(sizes), nil 210 - }
-2
modules/git/repo_language_stats_test.go
··· 1 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package git 7 5 8 6 import (
+77
modules/git/repo_ref.go
··· 4 4 package git 5 5 6 6 import ( 7 + "bufio" 7 8 "context" 8 9 "fmt" 10 + "io" 9 11 "strings" 10 12 11 13 "code.gitea.io/gitea/modules/util" ··· 78 80 } 79 81 return "", fmt.Errorf("could not expand reference '%s'", ref) 80 82 } 83 + 84 + // GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. 85 + func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { 86 + stdoutReader, stdoutWriter := io.Pipe() 87 + defer func() { 88 + _ = stdoutReader.Close() 89 + _ = stdoutWriter.Close() 90 + }() 91 + 92 + go func() { 93 + stderrBuilder := &strings.Builder{} 94 + err := NewCommand(repo.Ctx, "for-each-ref").Run(&RunOpts{ 95 + Dir: repo.Path, 96 + Stdout: stdoutWriter, 97 + Stderr: stderrBuilder, 98 + }) 99 + if err != nil { 100 + _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) 101 + } else { 102 + _ = stdoutWriter.Close() 103 + } 104 + }() 105 + 106 + refs := make([]*Reference, 0) 107 + bufReader := bufio.NewReader(stdoutReader) 108 + for { 109 + // The output of for-each-ref is simply a list: 110 + // <sha> SP <type> TAB <ref> LF 111 + sha, err := bufReader.ReadString(' ') 112 + if err == io.EOF { 113 + break 114 + } 115 + if err != nil { 116 + return nil, err 117 + } 118 + sha = sha[:len(sha)-1] 119 + 120 + typ, err := bufReader.ReadString('\t') 121 + if err == io.EOF { 122 + // This should not happen, but we'll tolerate it 123 + break 124 + } 125 + if err != nil { 126 + return nil, err 127 + } 128 + typ = typ[:len(typ)-1] 129 + 130 + refName, err := bufReader.ReadString('\n') 131 + if err == io.EOF { 132 + // This should not happen, but we'll tolerate it 133 + break 134 + } 135 + if err != nil { 136 + return nil, err 137 + } 138 + refName = refName[:len(refName)-1] 139 + 140 + // refName cannot be HEAD but can be remotes or stash 141 + if strings.HasPrefix(refName, RemotePrefix) || refName == "/refs/stash" { 142 + continue 143 + } 144 + 145 + if pattern == "" || strings.HasPrefix(refName, pattern) { 146 + r := &Reference{ 147 + Name: refName, 148 + Object: MustIDFromString(sha), 149 + Type: typ, 150 + repo: repo, 151 + } 152 + refs = append(refs, r) 153 + } 154 + } 155 + 156 + return refs, nil 157 + }
-51
modules/git/repo_ref_gogit.go
··· 1 - // Copyright 2018 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "strings" 10 - 11 - "github.com/go-git/go-git/v5" 12 - "github.com/go-git/go-git/v5/plumbing" 13 - ) 14 - 15 - // GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. 16 - func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { 17 - r, err := git.PlainOpen(repo.Path) 18 - if err != nil { 19 - return nil, err 20 - } 21 - 22 - refsIter, err := r.References() 23 - if err != nil { 24 - return nil, err 25 - } 26 - refs := make([]*Reference, 0) 27 - if err = refsIter.ForEach(func(ref *plumbing.Reference) error { 28 - if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() && 29 - (pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) { 30 - refType := string(ObjectCommit) 31 - if ref.Name().IsTag() { 32 - // tags can be of type `commit` (lightweight) or `tag` (annotated) 33 - if tagType, _ := repo.GetTagType(ParseGogitHash(ref.Hash())); err == nil { 34 - refType = tagType 35 - } 36 - } 37 - r := &Reference{ 38 - Name: ref.Name().String(), 39 - Object: ParseGogitHash(ref.Hash()), 40 - Type: refType, 41 - repo: repo, 42 - } 43 - refs = append(refs, r) 44 - } 45 - return nil 46 - }); err != nil { 47 - return nil, err 48 - } 49 - 50 - return refs, nil 51 - }
-87
modules/git/repo_ref_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "bufio" 10 - "io" 11 - "strings" 12 - ) 13 - 14 - // GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with. 15 - func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { 16 - stdoutReader, stdoutWriter := io.Pipe() 17 - defer func() { 18 - _ = stdoutReader.Close() 19 - _ = stdoutWriter.Close() 20 - }() 21 - 22 - go func() { 23 - stderrBuilder := &strings.Builder{} 24 - err := NewCommand(repo.Ctx, "for-each-ref").Run(&RunOpts{ 25 - Dir: repo.Path, 26 - Stdout: stdoutWriter, 27 - Stderr: stderrBuilder, 28 - }) 29 - if err != nil { 30 - _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) 31 - } else { 32 - _ = stdoutWriter.Close() 33 - } 34 - }() 35 - 36 - refs := make([]*Reference, 0) 37 - bufReader := bufio.NewReader(stdoutReader) 38 - for { 39 - // The output of for-each-ref is simply a list: 40 - // <sha> SP <type> TAB <ref> LF 41 - sha, err := bufReader.ReadString(' ') 42 - if err == io.EOF { 43 - break 44 - } 45 - if err != nil { 46 - return nil, err 47 - } 48 - sha = sha[:len(sha)-1] 49 - 50 - typ, err := bufReader.ReadString('\t') 51 - if err == io.EOF { 52 - // This should not happen, but we'll tolerate it 53 - break 54 - } 55 - if err != nil { 56 - return nil, err 57 - } 58 - typ = typ[:len(typ)-1] 59 - 60 - refName, err := bufReader.ReadString('\n') 61 - if err == io.EOF { 62 - // This should not happen, but we'll tolerate it 63 - break 64 - } 65 - if err != nil { 66 - return nil, err 67 - } 68 - refName = refName[:len(refName)-1] 69 - 70 - // refName cannot be HEAD but can be remotes or stash 71 - if strings.HasPrefix(refName, RemotePrefix) || refName == "/refs/stash" { 72 - continue 73 - } 74 - 75 - if pattern == "" || strings.HasPrefix(refName, pattern) { 76 - r := &Reference{ 77 - Name: refName, 78 - Object: MustIDFromString(sha), 79 - Type: typ, 80 - repo: repo, 81 - } 82 - refs = append(refs, r) 83 - } 84 - } 85 - 86 - return refs, nil 87 - }
+122
modules/git/repo_tag.go
··· 6 6 7 7 import ( 8 8 "context" 9 + "errors" 9 10 "fmt" 10 11 "io" 11 12 "strings" 12 13 13 14 "code.gitea.io/gitea/modules/git/foreachref" 15 + "code.gitea.io/gitea/modules/log" 14 16 "code.gitea.io/gitea/modules/util" 15 17 ) 16 18 ··· 236 238 } 237 239 return tag, nil 238 240 } 241 + 242 + // IsTagExist returns true if given tag exists in the repository. 243 + func (repo *Repository) IsTagExist(name string) bool { 244 + if repo == nil || name == "" { 245 + return false 246 + } 247 + 248 + return repo.IsReferenceExist(TagPrefix + name) 249 + } 250 + 251 + // GetTags returns all tags of the repository. 252 + // returning at most limit tags, or all if limit is 0. 253 + func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { 254 + tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}, skip, limit) 255 + return tags, err 256 + } 257 + 258 + // GetTagType gets the type of the tag, either commit (simple) or tag (annotated) 259 + func (repo *Repository) GetTagType(id ObjectID) (string, error) { 260 + wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 261 + defer cancel() 262 + _, err := wr.Write([]byte(id.String() + "\n")) 263 + if err != nil { 264 + return "", err 265 + } 266 + _, typ, _, err := ReadBatchLine(rd) 267 + if IsErrNotExist(err) { 268 + return "", ErrNotExist{ID: id.String()} 269 + } 270 + return typ, nil 271 + } 272 + 273 + func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) { 274 + t, ok := repo.tagCache.Get(tagID.String()) 275 + if ok { 276 + log.Debug("Hit cache: %s", tagID) 277 + tagClone := *t.(*Tag) 278 + tagClone.Name = name // This is necessary because lightweight tags may have same id 279 + return &tagClone, nil 280 + } 281 + 282 + tp, err := repo.GetTagType(tagID) 283 + if err != nil { 284 + return nil, err 285 + } 286 + 287 + // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object 288 + commitIDStr, err := repo.GetTagCommitID(name) 289 + if err != nil { 290 + // every tag should have a commit ID so return all errors 291 + return nil, err 292 + } 293 + commitID, err := NewIDFromString(commitIDStr) 294 + if err != nil { 295 + return nil, err 296 + } 297 + 298 + // If type is "commit, the tag is a lightweight tag 299 + if ObjectType(tp) == ObjectCommit { 300 + commit, err := repo.GetCommit(commitIDStr) 301 + if err != nil { 302 + return nil, err 303 + } 304 + tag := &Tag{ 305 + Name: name, 306 + ID: tagID, 307 + Object: commitID, 308 + Type: tp, 309 + Tagger: commit.Committer, 310 + Message: commit.Message(), 311 + } 312 + 313 + repo.tagCache.Set(tagID.String(), tag) 314 + return tag, nil 315 + } 316 + 317 + // The tag is an annotated tag with a message. 318 + wr, rd, cancel := repo.CatFileBatch(repo.Ctx) 319 + defer cancel() 320 + 321 + if _, err := wr.Write([]byte(tagID.String() + "\n")); err != nil { 322 + return nil, err 323 + } 324 + _, typ, size, err := ReadBatchLine(rd) 325 + if err != nil { 326 + if errors.Is(err, io.EOF) || IsErrNotExist(err) { 327 + return nil, ErrNotExist{ID: tagID.String()} 328 + } 329 + return nil, err 330 + } 331 + if typ != "tag" { 332 + if err := DiscardFull(rd, size+1); err != nil { 333 + return nil, err 334 + } 335 + return nil, ErrNotExist{ID: tagID.String()} 336 + } 337 + 338 + // then we need to parse the tag 339 + // and load the commit 340 + data, err := io.ReadAll(io.LimitReader(rd, size)) 341 + if err != nil { 342 + return nil, err 343 + } 344 + _, err = rd.Discard(1) 345 + if err != nil { 346 + return nil, err 347 + } 348 + 349 + tag, err := parseTagData(tagID.Type(), data) 350 + if err != nil { 351 + return nil, err 352 + } 353 + 354 + tag.Name = name 355 + tag.ID = tagID 356 + tag.Type = tp 357 + 358 + repo.tagCache.Set(tagID.String(), tag) 359 + return tag, nil 360 + }
-135
modules/git/repo_tag_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "strings" 11 - 12 - "code.gitea.io/gitea/modules/log" 13 - 14 - "github.com/go-git/go-git/v5/plumbing" 15 - ) 16 - 17 - // IsTagExist returns true if given tag exists in the repository. 18 - func (repo *Repository) IsTagExist(name string) bool { 19 - _, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true) 20 - return err == nil 21 - } 22 - 23 - // GetTags returns all tags of the repository. 24 - // returning at most limit tags, or all if limit is 0. 25 - func (repo *Repository) GetTags(skip, limit int) ([]string, error) { 26 - var tagNames []string 27 - 28 - tags, err := repo.gogitRepo.Tags() 29 - if err != nil { 30 - return nil, err 31 - } 32 - 33 - _ = tags.ForEach(func(tag *plumbing.Reference) error { 34 - tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix)) 35 - return nil 36 - }) 37 - 38 - // Reverse order 39 - for i := 0; i < len(tagNames)/2; i++ { 40 - j := len(tagNames) - i - 1 41 - tagNames[i], tagNames[j] = tagNames[j], tagNames[i] 42 - } 43 - 44 - // since we have to reverse order we can paginate only afterwards 45 - if len(tagNames) < skip { 46 - tagNames = []string{} 47 - } else { 48 - tagNames = tagNames[skip:] 49 - } 50 - if limit != 0 && len(tagNames) > limit { 51 - tagNames = tagNames[:limit] 52 - } 53 - 54 - return tagNames, nil 55 - } 56 - 57 - // GetTagType gets the type of the tag, either commit (simple) or tag (annotated) 58 - func (repo *Repository) GetTagType(id ObjectID) (string, error) { 59 - // Get tag type 60 - obj, err := repo.gogitRepo.Object(plumbing.AnyObject, plumbing.Hash(id.RawValue())) 61 - if err != nil { 62 - if err == plumbing.ErrReferenceNotFound { 63 - return "", &ErrNotExist{ID: id.String()} 64 - } 65 - return "", err 66 - } 67 - 68 - return obj.Type().String(), nil 69 - } 70 - 71 - func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) { 72 - t, ok := repo.tagCache.Get(tagID.String()) 73 - if ok { 74 - log.Debug("Hit cache: %s", tagID) 75 - tagClone := *t.(*Tag) 76 - tagClone.Name = name // This is necessary because lightweight tags may have same id 77 - return &tagClone, nil 78 - } 79 - 80 - tp, err := repo.GetTagType(tagID) 81 - if err != nil { 82 - return nil, err 83 - } 84 - 85 - // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object 86 - commitIDStr, err := repo.GetTagCommitID(name) 87 - if err != nil { 88 - // every tag should have a commit ID so return all errors 89 - return nil, err 90 - } 91 - commitID, err := NewIDFromString(commitIDStr) 92 - if err != nil { 93 - return nil, err 94 - } 95 - 96 - // If type is "commit, the tag is a lightweight tag 97 - if ObjectType(tp) == ObjectCommit { 98 - commit, err := repo.GetCommit(commitIDStr) 99 - if err != nil { 100 - return nil, err 101 - } 102 - tag := &Tag{ 103 - Name: name, 104 - ID: tagID, 105 - Object: commitID, 106 - Type: tp, 107 - Tagger: commit.Committer, 108 - Message: commit.Message(), 109 - } 110 - 111 - repo.tagCache.Set(tagID.String(), tag) 112 - return tag, nil 113 - } 114 - 115 - gogitTag, err := repo.gogitRepo.TagObject(plumbing.Hash(tagID.RawValue())) 116 - if err != nil { 117 - if err == plumbing.ErrReferenceNotFound { 118 - return nil, &ErrNotExist{ID: tagID.String()} 119 - } 120 - 121 - return nil, err 122 - } 123 - 124 - tag := &Tag{ 125 - Name: name, 126 - ID: tagID, 127 - Object: commitID.Type().MustID(gogitTag.Target[:]), 128 - Type: tp, 129 - Tagger: &gogitTag.Tagger, 130 - Message: gogitTag.Message, 131 - } 132 - 133 - repo.tagCache.Set(tagID.String(), tag) 134 - return tag, nil 135 - }
-134
modules/git/repo_tag_nogogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build !gogit 6 - 7 - package git 8 - 9 - import ( 10 - "errors" 11 - "io" 12 - 13 - "code.gitea.io/gitea/modules/log" 14 - ) 15 - 16 - // IsTagExist returns true if given tag exists in the repository. 17 - func (repo *Repository) IsTagExist(name string) bool { 18 - if repo == nil || name == "" { 19 - return false 20 - } 21 - 22 - return repo.IsReferenceExist(TagPrefix + name) 23 - } 24 - 25 - // GetTags returns all tags of the repository. 26 - // returning at most limit tags, or all if limit is 0. 27 - func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { 28 - tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}, skip, limit) 29 - return tags, err 30 - } 31 - 32 - // GetTagType gets the type of the tag, either commit (simple) or tag (annotated) 33 - func (repo *Repository) GetTagType(id ObjectID) (string, error) { 34 - wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) 35 - defer cancel() 36 - _, err := wr.Write([]byte(id.String() + "\n")) 37 - if err != nil { 38 - return "", err 39 - } 40 - _, typ, _, err := ReadBatchLine(rd) 41 - if IsErrNotExist(err) { 42 - return "", ErrNotExist{ID: id.String()} 43 - } 44 - return typ, nil 45 - } 46 - 47 - func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) { 48 - t, ok := repo.tagCache.Get(tagID.String()) 49 - if ok { 50 - log.Debug("Hit cache: %s", tagID) 51 - tagClone := *t.(*Tag) 52 - tagClone.Name = name // This is necessary because lightweight tags may have same id 53 - return &tagClone, nil 54 - } 55 - 56 - tp, err := repo.GetTagType(tagID) 57 - if err != nil { 58 - return nil, err 59 - } 60 - 61 - // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object 62 - commitIDStr, err := repo.GetTagCommitID(name) 63 - if err != nil { 64 - // every tag should have a commit ID so return all errors 65 - return nil, err 66 - } 67 - commitID, err := NewIDFromString(commitIDStr) 68 - if err != nil { 69 - return nil, err 70 - } 71 - 72 - // If type is "commit, the tag is a lightweight tag 73 - if ObjectType(tp) == ObjectCommit { 74 - commit, err := repo.GetCommit(commitIDStr) 75 - if err != nil { 76 - return nil, err 77 - } 78 - tag := &Tag{ 79 - Name: name, 80 - ID: tagID, 81 - Object: commitID, 82 - Type: tp, 83 - Tagger: commit.Committer, 84 - Message: commit.Message(), 85 - } 86 - 87 - repo.tagCache.Set(tagID.String(), tag) 88 - return tag, nil 89 - } 90 - 91 - // The tag is an annotated tag with a message. 92 - wr, rd, cancel := repo.CatFileBatch(repo.Ctx) 93 - defer cancel() 94 - 95 - if _, err := wr.Write([]byte(tagID.String() + "\n")); err != nil { 96 - return nil, err 97 - } 98 - _, typ, size, err := ReadBatchLine(rd) 99 - if err != nil { 100 - if errors.Is(err, io.EOF) || IsErrNotExist(err) { 101 - return nil, ErrNotExist{ID: tagID.String()} 102 - } 103 - return nil, err 104 - } 105 - if typ != "tag" { 106 - if err := DiscardFull(rd, size+1); err != nil { 107 - return nil, err 108 - } 109 - return nil, ErrNotExist{ID: tagID.String()} 110 - } 111 - 112 - // then we need to parse the tag 113 - // and load the commit 114 - data, err := io.ReadAll(io.LimitReader(rd, size)) 115 - if err != nil { 116 - return nil, err 117 - } 118 - _, err = rd.Discard(1) 119 - if err != nil { 120 - return nil, err 121 - } 122 - 123 - tag, err := parseTagData(tagID.Type(), data) 124 - if err != nil { 125 - return nil, err 126 - } 127 - 128 - tag.Name = name 129 - tag.ID = tagID 130 - tag.Type = tp 131 - 132 - repo.tagCache.Set(tagID.String(), tag) 133 - return tag, nil 134 - }
+86
modules/git/repo_tree.go
··· 6 6 7 7 import ( 8 8 "bytes" 9 + "io" 9 10 "os" 10 11 "strings" 11 12 "time" ··· 65 66 } 66 67 return NewIDFromString(strings.TrimSpace(stdout.String())) 67 68 } 69 + 70 + func (repo *Repository) getTree(id ObjectID) (*Tree, error) { 71 + wr, rd, cancel := repo.CatFileBatch(repo.Ctx) 72 + defer cancel() 73 + 74 + _, _ = wr.Write([]byte(id.String() + "\n")) 75 + 76 + // ignore the SHA 77 + _, typ, size, err := ReadBatchLine(rd) 78 + if err != nil { 79 + return nil, err 80 + } 81 + 82 + switch typ { 83 + case "tag": 84 + resolvedID := id 85 + data, err := io.ReadAll(io.LimitReader(rd, size)) 86 + if err != nil { 87 + return nil, err 88 + } 89 + tag, err := parseTagData(id.Type(), data) 90 + if err != nil { 91 + return nil, err 92 + } 93 + commit, err := tag.Commit(repo) 94 + if err != nil { 95 + return nil, err 96 + } 97 + commit.Tree.ResolvedID = resolvedID 98 + return &commit.Tree, nil 99 + case "commit": 100 + commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size)) 101 + if err != nil { 102 + return nil, err 103 + } 104 + if _, err := rd.Discard(1); err != nil { 105 + return nil, err 106 + } 107 + commit.Tree.ResolvedID = commit.ID 108 + return &commit.Tree, nil 109 + case "tree": 110 + tree := NewTree(repo, id) 111 + tree.ResolvedID = id 112 + objectFormat, err := repo.GetObjectFormat() 113 + if err != nil { 114 + return nil, err 115 + } 116 + tree.entries, err = catBatchParseTreeEntries(objectFormat, tree, rd, size) 117 + if err != nil { 118 + return nil, err 119 + } 120 + tree.entriesParsed = true 121 + return tree, nil 122 + default: 123 + if err := DiscardFull(rd, size+1); err != nil { 124 + return nil, err 125 + } 126 + return nil, ErrNotExist{ 127 + ID: id.String(), 128 + } 129 + } 130 + } 131 + 132 + // GetTree find the tree object in the repository. 133 + func (repo *Repository) GetTree(idStr string) (*Tree, error) { 134 + objectFormat, err := repo.GetObjectFormat() 135 + if err != nil { 136 + return nil, err 137 + } 138 + if len(idStr) != objectFormat.FullLength() { 139 + res, err := repo.GetRefCommitID(idStr) 140 + if err != nil { 141 + return nil, err 142 + } 143 + if len(res) > 0 { 144 + idStr = res 145 + } 146 + } 147 + id, err := NewIDFromString(idStr) 148 + if err != nil { 149 + return nil, err 150 + } 151 + 152 + return repo.getTree(id) 153 + }
-53
modules/git/repo_tree_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import "github.com/go-git/go-git/v5/plumbing" 10 - 11 - func (repo *Repository) getTree(id ObjectID) (*Tree, error) { 12 - gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id.RawValue())) 13 - if err != nil { 14 - return nil, err 15 - } 16 - 17 - tree := NewTree(repo, id) 18 - tree.gogitTree = gogitTree 19 - return tree, nil 20 - } 21 - 22 - // GetTree find the tree object in the repository. 23 - func (repo *Repository) GetTree(idStr string) (*Tree, error) { 24 - objectFormat, err := repo.GetObjectFormat() 25 - if err != nil { 26 - return nil, err 27 - } 28 - 29 - if len(idStr) != objectFormat.FullLength() { 30 - res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path}) 31 - if err != nil { 32 - return nil, err 33 - } 34 - if len(res) > 0 { 35 - idStr = res[:len(res)-1] 36 - } 37 - } 38 - id, err := NewIDFromString(idStr) 39 - if err != nil { 40 - return nil, err 41 - } 42 - resolvedID := id 43 - commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id.RawValue())) 44 - if err == nil { 45 - id = ParseGogitHash(commitObject.TreeHash) 46 - } 47 - treeObject, err := repo.getTree(id) 48 - if err != nil { 49 - return nil, err 50 - } 51 - treeObject.ResolvedID = resolvedID 52 - return treeObject, nil 53 - }
-95
modules/git/repo_tree_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "io" 10 - ) 11 - 12 - func (repo *Repository) getTree(id ObjectID) (*Tree, error) { 13 - wr, rd, cancel := repo.CatFileBatch(repo.Ctx) 14 - defer cancel() 15 - 16 - _, _ = wr.Write([]byte(id.String() + "\n")) 17 - 18 - // ignore the SHA 19 - _, typ, size, err := ReadBatchLine(rd) 20 - if err != nil { 21 - return nil, err 22 - } 23 - 24 - switch typ { 25 - case "tag": 26 - resolvedID := id 27 - data, err := io.ReadAll(io.LimitReader(rd, size)) 28 - if err != nil { 29 - return nil, err 30 - } 31 - tag, err := parseTagData(id.Type(), data) 32 - if err != nil { 33 - return nil, err 34 - } 35 - commit, err := tag.Commit(repo) 36 - if err != nil { 37 - return nil, err 38 - } 39 - commit.Tree.ResolvedID = resolvedID 40 - return &commit.Tree, nil 41 - case "commit": 42 - commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size)) 43 - if err != nil { 44 - return nil, err 45 - } 46 - if _, err := rd.Discard(1); err != nil { 47 - return nil, err 48 - } 49 - commit.Tree.ResolvedID = commit.ID 50 - return &commit.Tree, nil 51 - case "tree": 52 - tree := NewTree(repo, id) 53 - tree.ResolvedID = id 54 - objectFormat, err := repo.GetObjectFormat() 55 - if err != nil { 56 - return nil, err 57 - } 58 - tree.entries, err = catBatchParseTreeEntries(objectFormat, tree, rd, size) 59 - if err != nil { 60 - return nil, err 61 - } 62 - tree.entriesParsed = true 63 - return tree, nil 64 - default: 65 - if err := DiscardFull(rd, size+1); err != nil { 66 - return nil, err 67 - } 68 - return nil, ErrNotExist{ 69 - ID: id.String(), 70 - } 71 - } 72 - } 73 - 74 - // GetTree find the tree object in the repository. 75 - func (repo *Repository) GetTree(idStr string) (*Tree, error) { 76 - objectFormat, err := repo.GetObjectFormat() 77 - if err != nil { 78 - return nil, err 79 - } 80 - if len(idStr) != objectFormat.FullLength() { 81 - res, err := repo.GetRefCommitID(idStr) 82 - if err != nil { 83 - return nil, err 84 - } 85 - if len(res) > 0 { 86 - idStr = res 87 - } 88 - } 89 - id, err := NewIDFromString(idStr) 90 - if err != nil { 91 - return nil, err 92 - } 93 - 94 - return repo.getTree(id) 95 - }
+18
modules/git/signature.go
··· 5 5 package git 6 6 7 7 import ( 8 + "fmt" 8 9 "strconv" 9 10 "strings" 10 11 "time" 11 12 12 13 "code.gitea.io/gitea/modules/log" 14 + "code.gitea.io/gitea/modules/util" 13 15 ) 16 + 17 + // Signature represents the Author, Committer or Tagger information. 18 + type Signature struct { 19 + Name string // the committer name, it can be anything 20 + Email string // the committer email, it can be anything 21 + When time.Time // the timestamp of the signature 22 + } 23 + 24 + func (s *Signature) String() string { 25 + return fmt.Sprintf("%s <%s>", s.Name, s.Email) 26 + } 27 + 28 + // Decode decodes a byte array representing a signature to signature 29 + func (s *Signature) Decode(b []byte) { 30 + *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) 31 + } 14 32 15 33 // Helper to get a signature from the commit line, which looks like: 16 34 //
-14
modules/git/signature_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "github.com/go-git/go-git/v5/plumbing/object" 11 - ) 12 - 13 - // Signature represents the Author or Committer information. 14 - type Signature = object.Signature
-30
modules/git/signature_nogogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build !gogit 6 - 7 - package git 8 - 9 - import ( 10 - "fmt" 11 - "time" 12 - 13 - "code.gitea.io/gitea/modules/util" 14 - ) 15 - 16 - // Signature represents the Author, Committer or Tagger information. 17 - type Signature struct { 18 - Name string // the committer name, it can be anything 19 - Email string // the committer email, it can be anything 20 - When time.Time // the timestamp of the signature 21 - } 22 - 23 - func (s *Signature) String() string { 24 - return fmt.Sprintf("%s <%s>", s.Name, s.Email) 25 - } 26 - 27 - // Decode decodes a byte array representing a signature to signature 28 - func (s *Signature) Decode(b []byte) { 29 - *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) 30 - }
+111
modules/git/tree.go
··· 6 6 7 7 import ( 8 8 "bytes" 9 + "io" 9 10 "strings" 10 11 ) 11 12 13 + // Tree represents a flat directory listing. 14 + type Tree struct { 15 + ID ObjectID 16 + ResolvedID ObjectID 17 + repo *Repository 18 + 19 + // parent tree 20 + ptree *Tree 21 + 22 + entries Entries 23 + entriesParsed bool 24 + 25 + entriesRecursive Entries 26 + entriesRecursiveParsed bool 27 + } 28 + 12 29 // NewTree create a new tree according the repository and tree id 13 30 func NewTree(repo *Repository, id ObjectID) *Tree { 14 31 return &Tree{ 15 32 ID: id, 16 33 repo: repo, 17 34 } 35 + } 36 + 37 + // ListEntries returns all entries of current tree. 38 + func (t *Tree) ListEntries() (Entries, error) { 39 + if t.entriesParsed { 40 + return t.entries, nil 41 + } 42 + 43 + if t.repo != nil { 44 + wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx) 45 + defer cancel() 46 + 47 + _, _ = wr.Write([]byte(t.ID.String() + "\n")) 48 + _, typ, sz, err := ReadBatchLine(rd) 49 + if err != nil { 50 + return nil, err 51 + } 52 + if typ == "commit" { 53 + treeID, err := ReadTreeID(rd, sz) 54 + if err != nil && err != io.EOF { 55 + return nil, err 56 + } 57 + _, _ = wr.Write([]byte(treeID + "\n")) 58 + _, typ, sz, err = ReadBatchLine(rd) 59 + if err != nil { 60 + return nil, err 61 + } 62 + } 63 + if typ == "tree" { 64 + t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz) 65 + if err != nil { 66 + return nil, err 67 + } 68 + t.entriesParsed = true 69 + return t.entries, nil 70 + } 71 + 72 + // Not a tree just use ls-tree instead 73 + if err := DiscardFull(rd, sz+1); err != nil { 74 + return nil, err 75 + } 76 + } 77 + 78 + stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path}) 79 + if runErr != nil { 80 + if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { 81 + return nil, ErrNotExist{ 82 + ID: t.ID.String(), 83 + } 84 + } 85 + return nil, runErr 86 + } 87 + 88 + var err error 89 + t.entries, err = parseTreeEntries(stdout, t) 90 + if err == nil { 91 + t.entriesParsed = true 92 + } 93 + 94 + return t.entries, err 95 + } 96 + 97 + // listEntriesRecursive returns all entries of current tree recursively including all subtrees 98 + // extraArgs could be "-l" to get the size, which is slower 99 + func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { 100 + if t.entriesRecursiveParsed { 101 + return t.entriesRecursive, nil 102 + } 103 + 104 + stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r"). 105 + AddArguments(extraArgs...). 106 + AddDynamicArguments(t.ID.String()). 107 + RunStdBytes(&RunOpts{Dir: t.repo.Path}) 108 + if runErr != nil { 109 + return nil, runErr 110 + } 111 + 112 + var err error 113 + t.entriesRecursive, err = parseTreeEntries(stdout, t) 114 + if err == nil { 115 + t.entriesRecursiveParsed = true 116 + } 117 + 118 + return t.entriesRecursive, err 119 + } 120 + 121 + // ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size 122 + func (t *Tree) ListEntriesRecursiveFast() (Entries, error) { 123 + return t.listEntriesRecursive(nil) 124 + } 125 + 126 + // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size 127 + func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { 128 + return t.listEntriesRecursive(TrustedCmdArgs{"--long"}) 18 129 } 19 130 20 131 // SubTree get a sub tree by the sub dir path
+42 -1
modules/git/tree_blob.go
··· 5 5 6 6 package git 7 7 8 - import "strings" 8 + import ( 9 + "path" 10 + "strings" 11 + ) 12 + 13 + // GetTreeEntryByPath get the tree entries according the sub dir 14 + func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { 15 + if len(relpath) == 0 { 16 + return &TreeEntry{ 17 + ptree: t, 18 + ID: t.ID, 19 + name: "", 20 + fullName: "", 21 + entryMode: EntryModeTree, 22 + }, nil 23 + } 24 + 25 + // FIXME: This should probably use git cat-file --batch to be a bit more efficient 26 + relpath = path.Clean(relpath) 27 + parts := strings.Split(relpath, "/") 28 + var err error 29 + tree := t 30 + for i, name := range parts { 31 + if i == len(parts)-1 { 32 + entries, err := tree.ListEntries() 33 + if err != nil { 34 + return nil, err 35 + } 36 + for _, v := range entries { 37 + if v.Name() == name { 38 + return v, nil 39 + } 40 + } 41 + } else { 42 + tree, err = tree.SubTree(name) 43 + if err != nil { 44 + return nil, err 45 + } 46 + } 47 + } 48 + return nil, ErrNotExist{"", relpath} 49 + } 9 50 10 51 // GetBlobByPath get the blob object according the path 11 52 func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
-65
modules/git/tree_blob_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "path" 11 - "strings" 12 - 13 - "github.com/go-git/go-git/v5/plumbing" 14 - "github.com/go-git/go-git/v5/plumbing/filemode" 15 - "github.com/go-git/go-git/v5/plumbing/object" 16 - ) 17 - 18 - // GetTreeEntryByPath get the tree entries according the sub dir 19 - func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { 20 - if len(relpath) == 0 { 21 - return &TreeEntry{ 22 - ID: t.ID, 23 - // Type: ObjectTree, 24 - gogitTreeEntry: &object.TreeEntry{ 25 - Name: "", 26 - Mode: filemode.Dir, 27 - Hash: plumbing.Hash(t.ID.RawValue()), 28 - }, 29 - }, nil 30 - } 31 - 32 - relpath = path.Clean(relpath) 33 - parts := strings.Split(relpath, "/") 34 - var err error 35 - tree := t 36 - for i, name := range parts { 37 - if i == len(parts)-1 { 38 - entries, err := tree.ListEntries() 39 - if err != nil { 40 - if err == plumbing.ErrObjectNotFound { 41 - return nil, ErrNotExist{ 42 - RelPath: relpath, 43 - } 44 - } 45 - return nil, err 46 - } 47 - for _, v := range entries { 48 - if v.Name() == name { 49 - return v, nil 50 - } 51 - } 52 - } else { 53 - tree, err = tree.SubTree(name) 54 - if err != nil { 55 - if err == plumbing.ErrObjectNotFound { 56 - return nil, ErrNotExist{ 57 - RelPath: relpath, 58 - } 59 - } 60 - return nil, err 61 - } 62 - } 63 - } 64 - return nil, ErrNotExist{"", relpath} 65 - }
-49
modules/git/tree_blob_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "path" 10 - "strings" 11 - ) 12 - 13 - // GetTreeEntryByPath get the tree entries according the sub dir 14 - func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { 15 - if len(relpath) == 0 { 16 - return &TreeEntry{ 17 - ptree: t, 18 - ID: t.ID, 19 - name: "", 20 - fullName: "", 21 - entryMode: EntryModeTree, 22 - }, nil 23 - } 24 - 25 - // FIXME: This should probably use git cat-file --batch to be a bit more efficient 26 - relpath = path.Clean(relpath) 27 - parts := strings.Split(relpath, "/") 28 - var err error 29 - tree := t 30 - for i, name := range parts { 31 - if i == len(parts)-1 { 32 - entries, err := tree.ListEntries() 33 - if err != nil { 34 - return nil, err 35 - } 36 - for _, v := range entries { 37 - if v.Name() == name { 38 - return v, nil 39 - } 40 - } 41 - } else { 42 - tree, err = tree.SubTree(name) 43 - if err != nil { 44 - return nil, err 45 - } 46 - } 47 - } 48 - return nil, ErrNotExist{"", relpath} 49 - }
+90
modules/git/tree_entry.go
··· 8 8 "io" 9 9 "sort" 10 10 "strings" 11 + 12 + "code.gitea.io/gitea/modules/log" 11 13 ) 14 + 15 + // TreeEntry the leaf in the git tree 16 + type TreeEntry struct { 17 + ID ObjectID 18 + 19 + ptree *Tree 20 + 21 + entryMode EntryMode 22 + name string 23 + 24 + size int64 25 + sized bool 26 + fullName string 27 + } 28 + 29 + // Name returns the name of the entry 30 + func (te *TreeEntry) Name() string { 31 + if te.fullName != "" { 32 + return te.fullName 33 + } 34 + return te.name 35 + } 36 + 37 + // Mode returns the mode of the entry 38 + func (te *TreeEntry) Mode() EntryMode { 39 + return te.entryMode 40 + } 41 + 42 + // Size returns the size of the entry 43 + func (te *TreeEntry) Size() int64 { 44 + if te.IsDir() { 45 + return 0 46 + } else if te.sized { 47 + return te.size 48 + } 49 + 50 + wr, rd, cancel := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx) 51 + defer cancel() 52 + _, err := wr.Write([]byte(te.ID.String() + "\n")) 53 + if err != nil { 54 + log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err) 55 + return 0 56 + } 57 + _, _, te.size, err = ReadBatchLine(rd) 58 + if err != nil { 59 + log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err) 60 + return 0 61 + } 62 + 63 + te.sized = true 64 + return te.size 65 + } 66 + 67 + // IsSubModule if the entry is a sub module 68 + func (te *TreeEntry) IsSubModule() bool { 69 + return te.entryMode == EntryModeCommit 70 + } 71 + 72 + // IsDir if the entry is a sub dir 73 + func (te *TreeEntry) IsDir() bool { 74 + return te.entryMode == EntryModeTree 75 + } 76 + 77 + // IsLink if the entry is a symlink 78 + func (te *TreeEntry) IsLink() bool { 79 + return te.entryMode == EntryModeSymlink 80 + } 81 + 82 + // IsRegular if the entry is a regular file 83 + func (te *TreeEntry) IsRegular() bool { 84 + return te.entryMode == EntryModeBlob 85 + } 86 + 87 + // IsExecutable if the entry is an executable file (not necessarily binary) 88 + func (te *TreeEntry) IsExecutable() bool { 89 + return te.entryMode == EntryModeExec 90 + } 91 + 92 + // Blob returns the blob object the entry 93 + func (te *TreeEntry) Blob() *Blob { 94 + return &Blob{ 95 + ID: te.ID, 96 + name: te.Name(), 97 + size: te.size, 98 + gotSize: te.sized, 99 + repo: te.ptree.repo, 100 + } 101 + } 12 102 13 103 // Type returns the type of the entry (commit, tree, blob) 14 104 func (te *TreeEntry) Type() string {
-95
modules/git/tree_entry_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "github.com/go-git/go-git/v5/plumbing" 11 - "github.com/go-git/go-git/v5/plumbing/filemode" 12 - "github.com/go-git/go-git/v5/plumbing/object" 13 - ) 14 - 15 - // TreeEntry the leaf in the git tree 16 - type TreeEntry struct { 17 - ID ObjectID 18 - 19 - gogitTreeEntry *object.TreeEntry 20 - ptree *Tree 21 - 22 - size int64 23 - sized bool 24 - fullName string 25 - } 26 - 27 - // Name returns the name of the entry 28 - func (te *TreeEntry) Name() string { 29 - if te.fullName != "" { 30 - return te.fullName 31 - } 32 - return te.gogitTreeEntry.Name 33 - } 34 - 35 - // Mode returns the mode of the entry 36 - func (te *TreeEntry) Mode() EntryMode { 37 - return EntryMode(te.gogitTreeEntry.Mode) 38 - } 39 - 40 - // Size returns the size of the entry 41 - func (te *TreeEntry) Size() int64 { 42 - if te.IsDir() { 43 - return 0 44 - } else if te.sized { 45 - return te.size 46 - } 47 - 48 - file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry) 49 - if err != nil { 50 - return 0 51 - } 52 - 53 - te.sized = true 54 - te.size = file.Size 55 - return te.size 56 - } 57 - 58 - // IsSubModule if the entry is a sub module 59 - func (te *TreeEntry) IsSubModule() bool { 60 - return te.gogitTreeEntry.Mode == filemode.Submodule 61 - } 62 - 63 - // IsDir if the entry is a sub dir 64 - func (te *TreeEntry) IsDir() bool { 65 - return te.gogitTreeEntry.Mode == filemode.Dir 66 - } 67 - 68 - // IsLink if the entry is a symlink 69 - func (te *TreeEntry) IsLink() bool { 70 - return te.gogitTreeEntry.Mode == filemode.Symlink 71 - } 72 - 73 - // IsRegular if the entry is a regular file 74 - func (te *TreeEntry) IsRegular() bool { 75 - return te.gogitTreeEntry.Mode == filemode.Regular 76 - } 77 - 78 - // IsExecutable if the entry is an executable file (not necessarily binary) 79 - func (te *TreeEntry) IsExecutable() bool { 80 - return te.gogitTreeEntry.Mode == filemode.Executable 81 - } 82 - 83 - // Blob returns the blob object the entry 84 - func (te *TreeEntry) Blob() *Blob { 85 - encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash) 86 - if err != nil { 87 - return nil 88 - } 89 - 90 - return &Blob{ 91 - ID: ParseGogitHash(te.gogitTreeEntry.Hash), 92 - gogitEncodedObj: encodedObj, 93 - name: te.Name(), 94 - } 95 - }
-96
modules/git/tree_entry_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import "code.gitea.io/gitea/modules/log" 9 - 10 - // TreeEntry the leaf in the git tree 11 - type TreeEntry struct { 12 - ID ObjectID 13 - 14 - ptree *Tree 15 - 16 - entryMode EntryMode 17 - name string 18 - 19 - size int64 20 - sized bool 21 - fullName string 22 - } 23 - 24 - // Name returns the name of the entry 25 - func (te *TreeEntry) Name() string { 26 - if te.fullName != "" { 27 - return te.fullName 28 - } 29 - return te.name 30 - } 31 - 32 - // Mode returns the mode of the entry 33 - func (te *TreeEntry) Mode() EntryMode { 34 - return te.entryMode 35 - } 36 - 37 - // Size returns the size of the entry 38 - func (te *TreeEntry) Size() int64 { 39 - if te.IsDir() { 40 - return 0 41 - } else if te.sized { 42 - return te.size 43 - } 44 - 45 - wr, rd, cancel := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx) 46 - defer cancel() 47 - _, err := wr.Write([]byte(te.ID.String() + "\n")) 48 - if err != nil { 49 - log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err) 50 - return 0 51 - } 52 - _, _, te.size, err = ReadBatchLine(rd) 53 - if err != nil { 54 - log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err) 55 - return 0 56 - } 57 - 58 - te.sized = true 59 - return te.size 60 - } 61 - 62 - // IsSubModule if the entry is a sub module 63 - func (te *TreeEntry) IsSubModule() bool { 64 - return te.entryMode == EntryModeCommit 65 - } 66 - 67 - // IsDir if the entry is a sub dir 68 - func (te *TreeEntry) IsDir() bool { 69 - return te.entryMode == EntryModeTree 70 - } 71 - 72 - // IsLink if the entry is a symlink 73 - func (te *TreeEntry) IsLink() bool { 74 - return te.entryMode == EntryModeSymlink 75 - } 76 - 77 - // IsRegular if the entry is a regular file 78 - func (te *TreeEntry) IsRegular() bool { 79 - return te.entryMode == EntryModeBlob 80 - } 81 - 82 - // IsExecutable if the entry is an executable file (not necessarily binary) 83 - func (te *TreeEntry) IsExecutable() bool { 84 - return te.entryMode == EntryModeExec 85 - } 86 - 87 - // Blob returns the blob object the entry 88 - func (te *TreeEntry) Blob() *Blob { 89 - return &Blob{ 90 - ID: te.ID, 91 - name: te.Name(), 92 - size: te.size, 93 - gotSize: te.sized, 94 - repo: te.ptree.repo, 95 - } 96 - }
-103
modules/git/tree_entry_test.go
··· 1 - // Copyright 2017 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package git 7 - 8 - import ( 9 - "testing" 10 - 11 - "github.com/go-git/go-git/v5/plumbing/filemode" 12 - "github.com/go-git/go-git/v5/plumbing/object" 13 - "github.com/stretchr/testify/assert" 14 - "github.com/stretchr/testify/require" 15 - ) 16 - 17 - func getTestEntries() Entries { 18 - return Entries{ 19 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v1.0", Mode: filemode.Dir}}, 20 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.0", Mode: filemode.Dir}}, 21 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.1", Mode: filemode.Dir}}, 22 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.12", Mode: filemode.Dir}}, 23 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.2", Mode: filemode.Dir}}, 24 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v12.0", Mode: filemode.Dir}}, 25 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "abc", Mode: filemode.Regular}}, 26 - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "bcd", Mode: filemode.Regular}}, 27 - } 28 - } 29 - 30 - func TestEntriesSort(t *testing.T) { 31 - entries := getTestEntries() 32 - entries.Sort() 33 - assert.Equal(t, "v1.0", entries[0].Name()) 34 - assert.Equal(t, "v12.0", entries[1].Name()) 35 - assert.Equal(t, "v2.0", entries[2].Name()) 36 - assert.Equal(t, "v2.1", entries[3].Name()) 37 - assert.Equal(t, "v2.12", entries[4].Name()) 38 - assert.Equal(t, "v2.2", entries[5].Name()) 39 - assert.Equal(t, "abc", entries[6].Name()) 40 - assert.Equal(t, "bcd", entries[7].Name()) 41 - } 42 - 43 - func TestEntriesCustomSort(t *testing.T) { 44 - entries := getTestEntries() 45 - entries.CustomSort(func(s1, s2 string) bool { 46 - return s1 > s2 47 - }) 48 - assert.Equal(t, "v2.2", entries[0].Name()) 49 - assert.Equal(t, "v2.12", entries[1].Name()) 50 - assert.Equal(t, "v2.1", entries[2].Name()) 51 - assert.Equal(t, "v2.0", entries[3].Name()) 52 - assert.Equal(t, "v12.0", entries[4].Name()) 53 - assert.Equal(t, "v1.0", entries[5].Name()) 54 - assert.Equal(t, "bcd", entries[6].Name()) 55 - assert.Equal(t, "abc", entries[7].Name()) 56 - } 57 - 58 - func TestFollowLink(t *testing.T) { 59 - r, err := openRepositoryWithDefaultContext("tests/repos/repo1_bare") 60 - require.NoError(t, err) 61 - defer r.Close() 62 - 63 - commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123") 64 - require.NoError(t, err) 65 - 66 - // get the symlink 67 - lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello") 68 - require.NoError(t, err) 69 - assert.True(t, lnk.IsLink()) 70 - 71 - // should be able to dereference to target 72 - target, err := lnk.FollowLink() 73 - require.NoError(t, err) 74 - assert.Equal(t, "hello", target.Name()) 75 - assert.False(t, target.IsLink()) 76 - assert.Equal(t, "b14df6442ea5a1b382985a6549b85d435376c351", target.ID.String()) 77 - 78 - // should error when called on normal file 79 - target, err = commit.Tree.GetTreeEntryByPath("file1.txt") 80 - require.NoError(t, err) 81 - _, err = target.FollowLink() 82 - assert.EqualError(t, err, "file1.txt: not a symlink") 83 - 84 - // should error for broken links 85 - target, err = commit.Tree.GetTreeEntryByPath("foo/broken_link") 86 - require.NoError(t, err) 87 - assert.True(t, target.IsLink()) 88 - _, err = target.FollowLink() 89 - assert.EqualError(t, err, "broken_link: broken link") 90 - 91 - // should error for external links 92 - target, err = commit.Tree.GetTreeEntryByPath("foo/outside_repo") 93 - require.NoError(t, err) 94 - assert.True(t, target.IsLink()) 95 - _, err = target.FollowLink() 96 - assert.EqualError(t, err, "outside_repo: points outside of repo") 97 - 98 - // testing fix for short link bug 99 - target, err = commit.Tree.GetTreeEntryByPath("foo/link_short") 100 - require.NoError(t, err) 101 - _, err = target.FollowLink() 102 - assert.EqualError(t, err, "link_short: broken link") 103 - }
-98
modules/git/tree_gogit.go
··· 1 - // Copyright 2015 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - //go:build gogit 6 - 7 - package git 8 - 9 - import ( 10 - "io" 11 - 12 - "github.com/go-git/go-git/v5/plumbing" 13 - "github.com/go-git/go-git/v5/plumbing/object" 14 - ) 15 - 16 - // Tree represents a flat directory listing. 17 - type Tree struct { 18 - ID ObjectID 19 - ResolvedID ObjectID 20 - repo *Repository 21 - 22 - gogitTree *object.Tree 23 - 24 - // parent tree 25 - ptree *Tree 26 - } 27 - 28 - func (t *Tree) loadTreeObject() error { 29 - gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue())) 30 - if err != nil { 31 - return err 32 - } 33 - 34 - t.gogitTree = gogitTree 35 - return nil 36 - } 37 - 38 - // ListEntries returns all entries of current tree. 39 - func (t *Tree) ListEntries() (Entries, error) { 40 - if t.gogitTree == nil { 41 - err := t.loadTreeObject() 42 - if err != nil { 43 - return nil, err 44 - } 45 - } 46 - 47 - entries := make([]*TreeEntry, len(t.gogitTree.Entries)) 48 - for i, entry := range t.gogitTree.Entries { 49 - entries[i] = &TreeEntry{ 50 - ID: ParseGogitHash(entry.Hash), 51 - gogitTreeEntry: &t.gogitTree.Entries[i], 52 - ptree: t, 53 - } 54 - } 55 - 56 - return entries, nil 57 - } 58 - 59 - // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees 60 - func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { 61 - if t.gogitTree == nil { 62 - err := t.loadTreeObject() 63 - if err != nil { 64 - return nil, err 65 - } 66 - } 67 - 68 - var entries []*TreeEntry 69 - seen := map[plumbing.Hash]bool{} 70 - walker := object.NewTreeWalker(t.gogitTree, true, seen) 71 - for { 72 - fullName, entry, err := walker.Next() 73 - if err == io.EOF { 74 - break 75 - } 76 - if err != nil { 77 - return nil, err 78 - } 79 - if seen[entry.Hash] { 80 - continue 81 - } 82 - 83 - convertedEntry := &TreeEntry{ 84 - ID: ParseGogitHash(entry.Hash), 85 - gogitTreeEntry: &entry, 86 - ptree: t, 87 - fullName: fullName, 88 - } 89 - entries = append(entries, convertedEntry) 90 - } 91 - 92 - return entries, nil 93 - } 94 - 95 - // ListEntriesRecursiveFast is the alias of ListEntriesRecursiveWithSize for the gogit version 96 - func (t *Tree) ListEntriesRecursiveFast() (Entries, error) { 97 - return t.ListEntriesRecursiveWithSize() 98 - }
-121
modules/git/tree_nogogit.go
··· 1 - // Copyright 2020 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build !gogit 5 - 6 - package git 7 - 8 - import ( 9 - "io" 10 - "strings" 11 - ) 12 - 13 - // Tree represents a flat directory listing. 14 - type Tree struct { 15 - ID ObjectID 16 - ResolvedID ObjectID 17 - repo *Repository 18 - 19 - // parent tree 20 - ptree *Tree 21 - 22 - entries Entries 23 - entriesParsed bool 24 - 25 - entriesRecursive Entries 26 - entriesRecursiveParsed bool 27 - } 28 - 29 - // ListEntries returns all entries of current tree. 30 - func (t *Tree) ListEntries() (Entries, error) { 31 - if t.entriesParsed { 32 - return t.entries, nil 33 - } 34 - 35 - if t.repo != nil { 36 - wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx) 37 - defer cancel() 38 - 39 - _, _ = wr.Write([]byte(t.ID.String() + "\n")) 40 - _, typ, sz, err := ReadBatchLine(rd) 41 - if err != nil { 42 - return nil, err 43 - } 44 - if typ == "commit" { 45 - treeID, err := ReadTreeID(rd, sz) 46 - if err != nil && err != io.EOF { 47 - return nil, err 48 - } 49 - _, _ = wr.Write([]byte(treeID + "\n")) 50 - _, typ, sz, err = ReadBatchLine(rd) 51 - if err != nil { 52 - return nil, err 53 - } 54 - } 55 - if typ == "tree" { 56 - t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz) 57 - if err != nil { 58 - return nil, err 59 - } 60 - t.entriesParsed = true 61 - return t.entries, nil 62 - } 63 - 64 - // Not a tree just use ls-tree instead 65 - if err := DiscardFull(rd, sz+1); err != nil { 66 - return nil, err 67 - } 68 - } 69 - 70 - stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path}) 71 - if runErr != nil { 72 - if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { 73 - return nil, ErrNotExist{ 74 - ID: t.ID.String(), 75 - } 76 - } 77 - return nil, runErr 78 - } 79 - 80 - var err error 81 - t.entries, err = parseTreeEntries(stdout, t) 82 - if err == nil { 83 - t.entriesParsed = true 84 - } 85 - 86 - return t.entries, err 87 - } 88 - 89 - // listEntriesRecursive returns all entries of current tree recursively including all subtrees 90 - // extraArgs could be "-l" to get the size, which is slower 91 - func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { 92 - if t.entriesRecursiveParsed { 93 - return t.entriesRecursive, nil 94 - } 95 - 96 - stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r"). 97 - AddArguments(extraArgs...). 98 - AddDynamicArguments(t.ID.String()). 99 - RunStdBytes(&RunOpts{Dir: t.repo.Path}) 100 - if runErr != nil { 101 - return nil, runErr 102 - } 103 - 104 - var err error 105 - t.entriesRecursive, err = parseTreeEntries(stdout, t) 106 - if err == nil { 107 - t.entriesRecursiveParsed = true 108 - } 109 - 110 - return t.entriesRecursive, err 111 - } 112 - 113 - // ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size 114 - func (t *Tree) ListEntriesRecursiveFast() (Entries, error) { 115 - return t.listEntriesRecursive(nil) 116 - } 117 - 118 - // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size 119 - func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { 120 - return t.listEntriesRecursive(TrustedCmdArgs{"--long"}) 121 - }
+1 -1
modules/git/utils_test.go
··· 13 13 // but not in production code. 14 14 15 15 func skipIfSHA256NotSupported(t *testing.T) { 16 - if isGogit || CheckGitVersionAtLeast("2.42") != nil { 16 + if CheckGitVersionAtLeast("2.42") != nil { 17 17 t.Skip("skipping because installed Git version doesn't support SHA256") 18 18 } 19 19 }
-40
modules/gitrepo/walk_gogit.go
··· 1 - // Copyright 2024 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package gitrepo 7 - 8 - import ( 9 - "context" 10 - 11 - "github.com/go-git/go-git/v5/plumbing" 12 - ) 13 - 14 - // WalkReferences walks all the references from the repository 15 - // refname is empty, ObjectTag or ObjectBranch. All other values should be treated as equivalent to empty. 16 - func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) { 17 - gitRepo := repositoryFromContext(ctx, repo) 18 - if gitRepo == nil { 19 - var err error 20 - gitRepo, err = OpenRepository(ctx, repo) 21 - if err != nil { 22 - return 0, err 23 - } 24 - defer gitRepo.Close() 25 - } 26 - 27 - i := 0 28 - iter, err := gitRepo.GoGitRepo().References() 29 - if err != nil { 30 - return i, err 31 - } 32 - defer iter.Close() 33 - 34 - err = iter.ForEach(func(ref *plumbing.Reference) error { 35 - err := walkfn(ref.Hash().String(), string(ref.Name())) 36 - i++ 37 - return err 38 - }) 39 - return i, err 40 - }
-2
modules/gitrepo/walk_nogogit.go modules/gitrepo/walk.go
··· 1 1 // Copyright 2024 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package gitrepo 7 5 8 6 import (
-62
modules/lfs/pointer_scanner_gogit.go
··· 1 - // Copyright 2021 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - //go:build gogit 5 - 6 - package lfs 7 - 8 - import ( 9 - "context" 10 - "fmt" 11 - 12 - "code.gitea.io/gitea/modules/git" 13 - 14 - "github.com/go-git/go-git/v5/plumbing/object" 15 - ) 16 - 17 - // SearchPointerBlobs scans the whole repository for LFS pointer files 18 - func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan chan<- PointerBlob, errChan chan<- error) { 19 - gitRepo := repo.GoGitRepo() 20 - 21 - err := func() error { 22 - blobs, err := gitRepo.BlobObjects() 23 - if err != nil { 24 - return fmt.Errorf("lfs.SearchPointerBlobs BlobObjects: %w", err) 25 - } 26 - 27 - return blobs.ForEach(func(blob *object.Blob) error { 28 - select { 29 - case <-ctx.Done(): 30 - return ctx.Err() 31 - default: 32 - } 33 - 34 - if blob.Size > blobSizeCutoff { 35 - return nil 36 - } 37 - 38 - reader, err := blob.Reader() 39 - if err != nil { 40 - return fmt.Errorf("lfs.SearchPointerBlobs blob.Reader: %w", err) 41 - } 42 - defer reader.Close() 43 - 44 - pointer, _ := ReadPointer(reader) 45 - if pointer.IsValid() { 46 - pointerChan <- PointerBlob{Hash: blob.Hash.String(), Pointer: pointer} 47 - } 48 - 49 - return nil 50 - }) 51 - }() 52 - if err != nil { 53 - select { 54 - case <-ctx.Done(): 55 - default: 56 - errChan <- err 57 - } 58 - } 59 - 60 - close(pointerChan) 61 - close(errChan) 62 - }
-2
modules/lfs/pointer_scanner_nogogit.go modules/lfs/pointer_scanner.go
··· 1 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 - //go:build !gogit 5 - 6 4 package lfs 7 5 8 6 import (
+1
release-notes/4941.md
··· 1 + Drop support to build Forgejo with the optional go-git Git backend. It only affects users who built Forgejo manually using `TAGS=gogits`, which no longer has any effect. Moving forward, we only support the default backend using the git binary. Please get in touch if you used the go-git backend and require any assistance moving away from it.