-3
Makefile
-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
+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
+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
-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
-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
-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
+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
-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
-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
-2
modules/git/commit_sha256_test.go
+2
-2
modules/git/git.go
+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
+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
-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
-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
+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 := ¬es.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
-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
-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 := ¬es.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
-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
-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
-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
-2
modules/git/parse_nogogit.go
modules/git/parse.go
-2
modules/git/parse_nogogit_test.go
modules/git/parse_test.go
-2
modules/git/parse_nogogit_test.go
modules/git/parse_test.go
-32
modules/git/pipeline/lfs_common.go
-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
-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
+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
+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
-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
-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
-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
-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
-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
+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
-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
-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
+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
-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
-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
-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
+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
-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
-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
-2
modules/git/repo_language_stats_test.go
+77
modules/git/repo_ref.go
+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
-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
-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
+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
-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
-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
+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
-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
-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
+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
-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
-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
+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
+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
-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
-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
+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
-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
-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
-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
-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
-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
+1
-1
modules/git/utils_test.go
-40
modules/gitrepo/walk_gogit.go
-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
-2
modules/gitrepo/walk_nogogit.go
modules/gitrepo/walk.go
-62
modules/lfs/pointer_scanner_gogit.go
-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
-2
modules/lfs/pointer_scanner_nogogit.go
modules/lfs/pointer_scanner.go
+1
release-notes/4941.md
+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.