aboutsummaryrefslogtreecommitdiffstats
path: root/modules/git/attribute/checker.go
blob: c17006a15407b491f41ad050e45c4ffbda5d3e72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package attribute

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"os"

	"code.gitea.io/gitea/modules/git"
)

func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*git.Command, []string, func(), error) {
	cancel := func() {}
	envs := []string{"GIT_FLUSH=1"}
	cmd := git.NewCommand("check-attr", "-z")
	if len(attributes) == 0 {
		cmd.AddArguments("--all")
	}

	// there is treeish, read from bare repo or temp index created by "read-tree"
	if treeish != "" {
		if git.DefaultFeatures().SupportCheckAttrOnBare {
			cmd.AddArguments("--source")
			cmd.AddDynamicArguments(treeish)
		} else {
			indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(treeish)
			if err != nil {
				return nil, nil, nil, err
			}

			cmd.AddArguments("--cached")
			envs = append(envs,
				"GIT_INDEX_FILE="+indexFilename,
				"GIT_WORK_TREE="+worktree,
			)
			cancel = deleteTemporaryFile
		}
	} // else: no treeish, assume it is a not a bare repo, read from working directory

	cmd.AddDynamicArguments(attributes...)
	if len(filenames) > 0 {
		cmd.AddDashesAndList(filenames...)
	}
	return cmd, envs, cancel, nil
}

type CheckAttributeOpts struct {
	Filenames  []string
	Attributes []string
}

// CheckAttributes return the attributes of the given filenames and attributes in the given treeish.
// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo
func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish string, opts CheckAttributeOpts) (map[string]*Attributes, error) {
	cmd, envs, cancel, err := checkAttrCommand(gitRepo, treeish, opts.Filenames, opts.Attributes)
	if err != nil {
		return nil, err
	}
	defer cancel()

	stdOut := new(bytes.Buffer)
	stdErr := new(bytes.Buffer)

	if err := cmd.Run(ctx, &git.RunOpts{
		Env:    append(os.Environ(), envs...),
		Dir:    gitRepo.Path,
		Stdout: stdOut,
		Stderr: stdErr,
	}); err != nil {
		return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
	}

	fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
	if len(fields)%3 != 1 {
		return nil, errors.New("wrong number of fields in return from check-attr")
	}

	attributesMap := make(map[string]*Attributes)
	for i := 0; i < (len(fields) / 3); i++ {
		filename := string(fields[3*i])
		attribute := string(fields[3*i+1])
		info := string(fields[3*i+2])
		attribute2info, ok := attributesMap[filename]
		if !ok {
			attribute2info = NewAttributes()
			attributesMap[filename] = attribute2info
		}
		attribute2info.m[attribute] = Attribute(info)
	}

	return attributesMap, nil
}