Skip to content

Commit 2ecd714

Browse files
committed
Support run attr check on bare repository if git version >= 2.40
1 parent 29a6b64 commit 2ecd714

File tree

12 files changed

+99
-130
lines changed

12 files changed

+99
-130
lines changed

modules/git/attribute/batch.go

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ type BatchChecker struct {
2222
// params
2323
Attributes []string
2424
Repo *git.Repository
25-
IndexFile string
26-
WorkTree string
25+
Treeish string
2726

2827
stdinReader io.ReadCloser
2928
stdinWriter *os.File
@@ -36,27 +35,13 @@ type BatchChecker struct {
3635

3736
// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
3837
func NewBatchChecker(repo *git.Repository, treeish string, attributes ...string) (*BatchChecker, error) {
39-
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(treeish)
40-
if err != nil {
41-
return nil, err
42-
}
43-
44-
if len(attributes) == 0 {
45-
attributes = LinguistAttributes
46-
}
47-
4838
ctx, cancel := context.WithCancel(repo.Ctx)
49-
5039
checker := &BatchChecker{
5140
Attributes: attributes,
5241
Repo: repo,
53-
IndexFile: indexFilename,
54-
WorkTree: worktree,
42+
Treeish: treeish,
5543
ctx: ctx,
56-
cancel: func() {
57-
cancel()
58-
deleteTemporaryFile()
59-
},
44+
cancel: cancel,
6045
}
6146

6247
if err := checker.init(ctx); err != nil {
@@ -88,22 +73,19 @@ func (c *BatchChecker) init(ctx context.Context) error {
8873
return errors.New("no provided Attributes to check")
8974
}
9075

91-
c.cmd = git.NewCommand("check-attr", "--stdin", "-z")
92-
93-
if len(c.IndexFile) > 0 {
94-
c.cmd.AddArguments("--cached")
95-
c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile)
76+
cmd, envs, cancel, err := checkAttrCommand(c.Repo, c.Treeish, nil, c.Attributes)
77+
if err != nil {
78+
c.cancel()
79+
return err
9680
}
97-
98-
if len(c.WorkTree) > 0 {
99-
c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree)
81+
c.cmd = cmd
82+
c.env = envs
83+
c.cancel = func() {
84+
cancel()
85+
c.cancel()
10086
}
10187

102-
c.env = append(c.env, "GIT_FLUSH=1")
103-
104-
c.cmd.AddDynamicArguments(c.Attributes...)
105-
106-
var err error
88+
c.cmd.AddArguments("--stdin")
10789

10890
c.stdinReader, c.stdinWriter, err = os.Pipe()
10991
if err != nil {

modules/git/attribute/checker.go

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,88 +5,91 @@ package attribute
55

66
import (
77
"bytes"
8+
"context"
89
"errors"
910
"fmt"
1011
"os"
1112

1213
"code.gitea.io/gitea/modules/git"
1314
)
1415

15-
// CheckAttributeOpts represents the possible options to CheckAttribute
16-
type CheckAttributeOpts struct {
17-
CachedOnly bool
18-
AllAttributes bool
19-
Attributes []string
20-
Filenames []string
21-
IndexFile string
22-
WorkTree string
23-
}
24-
25-
// CheckAttribute return the Blame object of file
26-
func CheckAttribute(repo *git.Repository, opts CheckAttributeOpts) (map[string]Attributes, error) {
27-
env := []string{}
28-
29-
if len(opts.IndexFile) > 0 {
30-
env = append(env, "GIT_INDEX_FILE="+opts.IndexFile)
31-
}
32-
if len(opts.WorkTree) > 0 {
33-
env = append(env, "GIT_WORK_TREE="+opts.WorkTree)
16+
func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*git.Command, []string, func(), error) {
17+
cmd := git.NewCommand("check-attr", "-z")
18+
if len(attributes) == 0 {
19+
cmd.AddArguments("--all")
3420
}
35-
36-
if len(env) > 0 {
37-
env = append(os.Environ(), env...)
21+
cmd.AddDashesAndList(filenames...)
22+
if git.DefaultFeatures().SupportCheckAttrOnBare && treeish != "" {
23+
cmd.AddArguments("--source")
24+
cmd.AddDynamicArguments(treeish)
25+
cmd.AddDynamicArguments(attributes...)
26+
return cmd, []string{"GIT_FLUSH=1"}, nil, nil
3827
}
3928

40-
stdOut := new(bytes.Buffer)
41-
stdErr := new(bytes.Buffer)
42-
43-
cmd := git.NewCommand("check-attr", "-z")
29+
var cancel func()
30+
var envs []string
31+
if treeish != "" { // if it's empty, then we assume it's a worktree repository
32+
indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(treeish)
33+
if err != nil {
34+
return nil, nil, nil, err
35+
}
4436

45-
if opts.AllAttributes {
46-
cmd.AddArguments("-a")
47-
} else {
48-
for _, attribute := range opts.Attributes {
49-
if attribute != "" {
50-
cmd.AddDynamicArguments(attribute)
51-
}
37+
envs = []string{
38+
"GIT_INDEX_FILE=" + indexFilename,
39+
"GIT_WORK_TREE=" + worktree,
40+
"GIT_FLUSH=1",
5241
}
42+
cancel = deleteTemporaryFile
5343
}
44+
cmd.AddArguments("--cached")
45+
cmd.AddDynamicArguments(attributes...)
46+
return cmd, envs, cancel, nil
47+
}
48+
49+
type CheckAttributeOpts struct {
50+
Filenames []string
51+
Attributes []string
52+
}
5453

55-
if opts.CachedOnly {
56-
cmd.AddArguments("--cached")
54+
// CheckAttribute return the Blame object of file
55+
func CheckAttribute(ctx context.Context, gitRepo *git.Repository, treeish string, opts CheckAttributeOpts) (map[string]Attributes, error) {
56+
cmd, envs, cancel, err := checkAttrCommand(gitRepo, treeish, opts.Filenames, opts.Attributes)
57+
if err != nil {
58+
return nil, err
5759
}
60+
defer cancel()
5861

59-
cmd.AddDashesAndList(opts.Filenames...)
62+
stdOut := new(bytes.Buffer)
63+
stdErr := new(bytes.Buffer)
6064

61-
if err := cmd.Run(repo.Ctx, &git.RunOpts{
62-
Env: env,
63-
Dir: repo.Path,
65+
if err := cmd.Run(ctx, &git.RunOpts{
66+
Env: append(os.Environ(), envs...),
67+
Dir: gitRepo.Path,
6468
Stdout: stdOut,
6569
Stderr: stdErr,
6670
}); err != nil {
6771
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
6872
}
6973

70-
// FIXME: This is incorrect on versions < 1.8.5
7174
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
7275

7376
if len(fields)%3 != 1 {
7477
return nil, errors.New("wrong number of fields in return from check-attr")
7578
}
7679

77-
name2attribute2info := make(map[string]Attributes)
80+
attributesMap := make(map[string]Attributes)
7881

7982
for i := 0; i < (len(fields) / 3); i++ {
8083
filename := string(fields[3*i])
8184
attribute := string(fields[3*i+1])
8285
info := string(fields[3*i+2])
83-
attribute2info := name2attribute2info[filename]
86+
attribute2info := attributesMap[filename]
8487
if attribute2info == nil {
8588
attribute2info = make(Attributes)
8689
}
8790
attribute2info[attribute] = Attribute(info)
88-
name2attribute2info[filename] = attribute2info
91+
attributesMap[filename] = attribute2info
8992
}
9093

91-
return name2attribute2info, nil
94+
return attributesMap, nil
9295
}

modules/git/git.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Features struct {
3030
SupportProcReceive bool // >= 2.29
3131
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
3232
SupportedObjectFormats []ObjectFormat // sha1, sha256
33+
SupportCheckAttrOnBare bool // >= 2.40
3334
}
3435

3536
var (
@@ -77,6 +78,7 @@ func loadGitVersionFeatures() (*Features, error) {
7778
if features.SupportHashSha256 {
7879
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
7980
}
81+
features.SupportCheckAttrOnBare = features.CheckVersionAtLeast("2.40")
8082
return features, nil
8183
}
8284

modules/git/languagestats/language_stats.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
package languagestats
55

66
import (
7+
"context"
78
"strings"
89
"unicode"
10+
11+
"code.gitea.io/gitea/modules/git"
12+
"code.gitea.io/gitea/modules/git/attribute"
913
)
1014

1115
const (
@@ -46,3 +50,16 @@ func mergeLanguageStats(stats map[string]int64) map[string]int64 {
4650
}
4751
return res
4852
}
53+
54+
// GetFileLanguage tries to get the (linguist) language of the file content
55+
func GetFileLanguage(ctx context.Context, gitRepo *git.Repository, treeish, treePath string) (string, error) {
56+
attributesMap, err := attribute.CheckAttribute(ctx, gitRepo, treeish, attribute.CheckAttributeOpts{
57+
Attributes: []string{attribute.LinguistLanguage, attribute.GitlabLanguage},
58+
Filenames: []string{treePath},
59+
})
60+
if err != nil {
61+
return "", err
62+
}
63+
64+
return attributesMap[treePath].Language().Value(), nil
65+
}

modules/git/languagestats/language_stats_gogit.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"io"
1111

1212
"code.gitea.io/gitea/modules/analyze"
13+
"code.gitea.io/gitea/modules/git"
14+
"code.gitea.io/gitea/modules/git/attribute"
1315
"code.gitea.io/gitea/modules/optional"
1416

1517
"github.com/go-enry/go-enry/v2"
@@ -19,7 +21,7 @@ import (
1921
)
2022

2123
// GetLanguageStats calculates language stats for git repository at specified commit
22-
func GetLanguageStats(repo *Repository, commitID string) (map[string]int64, error) {
24+
func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64, error) {
2325
r, err := git.PlainOpen(repo.Path)
2426
if err != nil {
2527
return nil, err
@@ -40,7 +42,7 @@ func GetLanguageStats(repo *Repository, commitID string) (map[string]int64, erro
4042
return nil, err
4143
}
4244

43-
checker, deferable, err := NewAttributeChecker(repo, commitID)
45+
checker, deferable, err := attribute.NewBatchChecker(repo, commitID)
4446
if err != nil {
4547
return nil, err
4648
}

routers/web/repo/blame.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import (
1515
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/charset"
1717
"code.gitea.io/gitea/modules/git"
18+
"code.gitea.io/gitea/modules/git/languagestats"
1819
"code.gitea.io/gitea/modules/highlight"
1920
"code.gitea.io/gitea/modules/log"
2021
"code.gitea.io/gitea/modules/setting"
2122
"code.gitea.io/gitea/modules/templates"
2223
"code.gitea.io/gitea/modules/util"
2324
"code.gitea.io/gitea/services/context"
24-
files_service "code.gitea.io/gitea/services/repository/files"
2525
)
2626

2727
type blameRow struct {
@@ -234,7 +234,7 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st
234234
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
235235
repoLink := ctx.Repo.RepoLink
236236

237-
language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
237+
language, err := languagestats.GetFileLanguage(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
238238
if err != nil {
239239
log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
240240
}

routers/web/repo/setting/lfs.go

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -135,39 +135,30 @@ func LFSLocks(ctx *context.Context) {
135135
}
136136
defer gitRepo.Close()
137137

138-
filenames := make([]string, len(lfsLocks))
139-
140-
for i, lock := range lfsLocks {
141-
filenames[i] = lock.Path
142-
}
143-
144138
if err := gitRepo.ReadTreeToIndex(ctx.Repo.Repository.DefaultBranch); err != nil {
145139
log.Error("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err)
146140
ctx.ServerError("LFSLocks", fmt.Errorf("unable to read the default branch to the index: %s (%w)", ctx.Repo.Repository.DefaultBranch, err))
147141
return
148142
}
149143

150-
attributesMap, err := attribute.CheckAttribute(gitRepo, attribute.CheckAttributeOpts{
151-
Attributes: []string{"lockable"},
152-
Filenames: filenames,
153-
CachedOnly: true,
154-
})
144+
checker, err := attribute.NewBatchChecker(gitRepo, "", "lockable")
155145
if err != nil {
156146
log.Error("Unable to check attributes in %s (%v)", tmpBasePath, err)
157147
ctx.ServerError("LFSLocks", err)
158148
return
159149
}
150+
defer checker.Close()
160151

161152
lockables := make([]bool, len(lfsLocks))
153+
filenames := make([]string, len(lfsLocks))
162154
for i, lock := range lfsLocks {
163-
attribute2info, has := attributesMap[lock.Path]
164-
if !has {
165-
continue
166-
}
167-
if attribute2info["lockable"] != "set" {
155+
filenames[i] = lock.Path
156+
attrs, err := checker.CheckPath(lock.Path)
157+
if err != nil {
158+
log.Error("Unable to check attributes in %s: %s (%v)", tmpBasePath, lock.Path, err)
168159
continue
169160
}
170-
lockables[i] = true
161+
lockables[i] = attrs["lockable"] == "set"
171162
}
172163
ctx.Data["Lockables"] = lockables
173164

routers/web/repo/view_file.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ import (
1919
"code.gitea.io/gitea/modules/charset"
2020
"code.gitea.io/gitea/modules/git"
2121
"code.gitea.io/gitea/modules/git/attribute"
22+
"code.gitea.io/gitea/modules/git/languagestats"
2223
"code.gitea.io/gitea/modules/highlight"
2324
"code.gitea.io/gitea/modules/log"
2425
"code.gitea.io/gitea/modules/markup"
2526
"code.gitea.io/gitea/modules/setting"
2627
"code.gitea.io/gitea/modules/util"
2728
"code.gitea.io/gitea/services/context"
2829
issue_service "code.gitea.io/gitea/services/issue"
29-
files_service "code.gitea.io/gitea/services/repository/files"
3030

3131
"github.com/nektos/act/pkg/model"
3232
)
@@ -210,7 +210,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
210210
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
211211
}
212212

213-
language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
213+
language, err := languagestats.GetFileLanguage(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
214214
if err != nil {
215215
log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
216216
}

0 commit comments

Comments
 (0)