• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gittuf / gittuf / 14363711899

09 Apr 2025 05:49PM UTC coverage: 60.951% (+0.2%) from 60.786%
14363711899

push

github

web-flow
Merge pull request #915 from gittuf/load-repository-path-parameter

*: Add a path parameter to LoadRepository

33 of 46 new or added lines in 2 files covered. (71.74%)

1 existing line in 1 file now uncovered.

7219 of 11844 relevant lines covered (60.95%)

34.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.0
/internal/gitinterface/repository.go
1
// Copyright The gittuf Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package gitinterface
5

6
import (
7
        "bytes"
8
        "errors"
9
        "fmt"
10
        "io"
11
        "log/slog"
12
        "os"
13
        "os/exec"
14
        "path/filepath"
15
        "strings"
16

17
        "github.com/go-git/go-git/v5"
18
        "github.com/jonboulle/clockwork"
19
)
20

21
const (
22
        binary           = "git"
23
        committerTimeKey = "GIT_COMMITTER_DATE"
24
        authorTimeKey    = "GIT_AUTHOR_DATE"
25
)
26

27
var ErrRepositoryPathNotSpecified = errors.New("repository path not specified")
28

29
// Repository is a lightweight wrapper around a Git repository. It stores the
30
// location of the repository's GIT_DIR.
31
type Repository struct {
32
        gitDirPath string
33
        clock      clockwork.Clock
34
}
35

36
// GetGoGitRepository returns the go-git representation of a repository. We use
37
// this in certain signing and verifying workflows.
38
func (r *Repository) GetGoGitRepository() (*git.Repository, error) {
17✔
39
        return git.PlainOpenWithOptions(r.gitDirPath, &git.PlainOpenOptions{DetectDotGit: true})
17✔
40
}
17✔
41

42
// GetGitDir returns the GIT_DIR path for the repository.
43
func (r *Repository) GetGitDir() string {
2✔
44
        return r.gitDirPath
2✔
45
}
2✔
46

47
// IsBare returns true if the repository is a bare repository.
48
func (r *Repository) IsBare() bool {
27✔
49
        // TODO: this may not work when the repo is cloned with GIT_DIR set
27✔
50
        // elsewhere. We don't support this at the moment, so it's probably okay?
27✔
51
        return !strings.HasSuffix(r.gitDirPath, ".git")
27✔
52
}
27✔
53

54
// LoadRepository returns a Repository instance using the current working
55
// directory. It also inspects the PATH to ensure Git is installed.
56
func LoadRepository(repositoryPath string) (*Repository, error) {
2✔
57
        slog.Debug("Looking for Git binary in PATH...")
2✔
58
        _, err := exec.LookPath(binary)
2✔
59
        if err != nil {
2✔
60
                return nil, fmt.Errorf("unable to find Git binary, is Git installed?")
×
61
        }
×
62
        if repositoryPath == "" {
2✔
NEW
63
                return nil, ErrRepositoryPathNotSpecified
×
NEW
64
        }
×
65

66
        repo := &Repository{clock: clockwork.NewRealClock()}
2✔
67
        currentDir, err := os.Getwd()
2✔
68
        if err != nil {
2✔
NEW
69
                return nil, err
×
UNCOV
70
        }
×
71

72
        if err = os.Chdir(repositoryPath); err != nil {
2✔
NEW
73
                return nil, err
×
NEW
74
        }
×
75
        defer os.Chdir(currentDir) //nolint:errcheck
2✔
76

2✔
77
        slog.Debug("Identifying git directory for repository...")
2✔
78
        stdOut, stdErr, err := repo.executor("rev-parse", "--git-dir").withoutGitDir().execute()
2✔
79
        if err != nil {
2✔
80
                errContents, newErr := io.ReadAll(stdErr)
×
81
                if newErr != nil {
×
82
                        return nil, fmt.Errorf("unable to read original err '%w' when loading repository: %w", err, newErr)
×
83
                }
×
NEW
84
                return nil, fmt.Errorf("unable to identify git directory for repository: %w: %s", err, strings.TrimSpace(string(errContents)))
×
85
        }
86

87
        stdOutContents, err := io.ReadAll(stdOut)
2✔
88
        if err != nil {
2✔
NEW
89
                return nil, fmt.Errorf("unable to identify git directory for repository: %w", err)
×
90
        }
×
91

92
        // git rev-parse --git-dir returns a local path, so filepath.Abs gives us
93
        // the final path _including_ symlink follows.
94
        absPath, err := filepath.Abs(strings.TrimSpace(string(stdOutContents)))
2✔
95
        if err != nil {
2✔
NEW
96
                return nil, err
×
NEW
97
        }
×
98
        slog.Debug(fmt.Sprintf("Setting git directory for repository to '%s'...", absPath))
2✔
99
        repo.gitDirPath = absPath
2✔
100

2✔
101
        return repo, nil
2✔
102
}
103

104
// executor is a lightweight wrapper around exec.Cmd to run Git commands. It
105
// accepts the arguments to the `git` binary, but the binary itself must not be
106
// specified.
107
type executor struct {
108
        r           *Repository
109
        args        []string
110
        env         []string
111
        stdIn       io.Reader
112
        unsetGitDir bool
113
}
114

115
// executor initializes a new executor instance to run a Git command with the
116
// specified arguments.
117
func (r *Repository) executor(args ...string) *executor {
1,649✔
118
        return &executor{r: r, args: args, env: os.Environ()}
1,649✔
119
}
1,649✔
120

121
// withEnv adds the specified environment variables. Each environment variable
122
// must be specified in the form of `key=value`.
123
func (e *executor) withEnv(env ...string) *executor {
144✔
124
        e.env = append(e.env, env...)
144✔
125
        return e
144✔
126
}
144✔
127

128
// withoutGitDir ensures the executor doesn't auto-set the --git-dir flag to the
129
// executed command.
130
func (e *executor) withoutGitDir() *executor {
2✔
131
        e.unsetGitDir = true
2✔
132
        return e
2✔
133
}
2✔
134

135
// withStdIn sets the contents of stdin to be passed in to the command.
136
func (e *executor) withStdIn(stdIn *bytes.Buffer) *executor {
249✔
137
        e.stdIn = stdIn
249✔
138
        return e
249✔
139
}
249✔
140

141
// executeString runs the constructed Git command and returns the contents of
142
// stdout.  Leading and trailing spaces and newlines are removed. This function
143
// should be used almost every time; the only exception is when the output is
144
// desired without any processing such as the removal of space characters.
145
func (e *executor) executeString() (string, error) {
1,640✔
146
        stdOut, stdErr, err := e.execute()
1,640✔
147
        if err != nil {
1,745✔
148
                stdErrContents, newErr := io.ReadAll(stdErr)
105✔
149
                if newErr != nil {
105✔
150
                        return "", fmt.Errorf("unable to read stderr contents: %w; original err: %w", newErr, err)
×
151
                }
×
152
                return "", fmt.Errorf("%w when executing `git %s`: %s", err, strings.Join(e.args, " "), string(stdErrContents))
105✔
153
        }
154

155
        stdOutContents, err := io.ReadAll(stdOut)
1,535✔
156
        if err != nil {
1,535✔
157
                return "", fmt.Errorf("unable to read stdout contents: %w", err)
×
158
        }
×
159

160
        return strings.TrimSpace(string(stdOutContents)), nil
1,535✔
161
}
162

163
// execute runs the constructed Git command and returns the raw stdout and
164
// stderr contents. It adds the `--git-dir` argument if the repository has a
165
// path set.
166
func (e *executor) execute() (io.Reader, io.Reader, error) {
1,649✔
167
        if e.r.gitDirPath != "" && !e.unsetGitDir {
3,296✔
168
                e.args = append([]string{"--git-dir", e.r.gitDirPath}, e.args...)
1,647✔
169
        }
1,647✔
170
        cmd := exec.Command(binary, e.args...) //nolint:gosec
1,649✔
171
        cmd.Env = e.env
1,649✔
172
        cmd.Env = append(cmd.Env, "LC_ALL=C") // force git to the C (and thus english) locale
1,649✔
173

1,649✔
174
        var (
1,649✔
175
                stdOut bytes.Buffer
1,649✔
176
                stdErr bytes.Buffer
1,649✔
177
        )
1,649✔
178

1,649✔
179
        cmd.Stdout = &stdOut
1,649✔
180
        cmd.Stderr = &stdErr
1,649✔
181

1,649✔
182
        if e.stdIn != nil {
1,898✔
183
                cmd.Stdin = e.stdIn
249✔
184
        }
249✔
185

186
        err := cmd.Run()
1,649✔
187

1,649✔
188
        return &stdOut, &stdErr, err
1,649✔
189
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc