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

gittuf / gittuf / 13145294713

04 Feb 2025 09:35PM UTC coverage: 63.124% (+0.002%) from 63.122%
13145294713

push

github

web-flow
Merge pull request #785 from gittuf/gitinterface-symref

gitinterface: Add support for setting symref

4 of 6 new or added lines in 1 file covered. (66.67%)

5702 of 9033 relevant lines covered (63.12%)

51.72 hits per line

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

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

4
package gitinterface
5

6
import (
7
        "errors"
8
        "fmt"
9
        "os"
10
        "path"
11
        "strings"
12
)
13

14
const (
15
        RefPrefix       = "refs/"
16
        BranchRefPrefix = "refs/heads/"
17
        TagRefPrefix    = "refs/tags/"
18
        RemoteRefPrefix = "refs/remotes/"
19
)
20

21
var (
22
        ErrReferenceNotFound = errors.New("requested Git reference not found")
23
)
24

25
// GetReference returns the tip of the specified Git reference.
26
func (r *Repository) GetReference(refName string) (Hash, error) {
162✔
27
        refTipID, err := r.executor("rev-parse", refName).executeString()
162✔
28
        if err != nil {
229✔
29
                if strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
134✔
30
                        return ZeroHash, ErrReferenceNotFound
67✔
31
                }
67✔
32
                return ZeroHash, fmt.Errorf("unable to read reference '%s': %w", refName, err)
×
33
        }
34

35
        hash, err := NewHash(refTipID)
95✔
36
        if err != nil {
95✔
37
                return ZeroHash, fmt.Errorf("invalid Git ID for reference '%s': %w", refName, err)
×
38
        }
×
39

40
        return hash, nil
95✔
41
}
42

43
// SetReference sets the specified reference to the provided Git ID.
44
func (r *Repository) SetReference(refName string, gitID Hash) error {
18✔
45
        _, err := r.executor("update-ref", "--create-reflog", refName, gitID.String()).executeString()
18✔
46
        if err != nil {
18✔
47
                return fmt.Errorf("unable to set Git reference '%s' to '%s': %w", refName, gitID.String(), err)
×
48
        }
×
49

50
        return nil
18✔
51
}
52

53
// DeleteReference deletes the specified Git reference.
54
func (r *Repository) DeleteReference(refName string) error {
1✔
55
        _, err := r.executor("update-ref", "-d", refName).executeString()
1✔
56
        if err != nil {
1✔
57
                return fmt.Errorf("unable to delete Git reference '%s': %w", refName, err)
×
58
        }
×
59
        return nil
1✔
60
}
61

62
// CheckAndSetReference sets the specified reference to the provided Git ID if
63
// the reference is currently set to `oldGitID`.
64
func (r *Repository) CheckAndSetReference(refName string, newGitID, oldGitID Hash) error {
109✔
65
        _, err := r.executor("update-ref", "--create-reflog", refName, newGitID.String(), oldGitID.String()).executeString()
109✔
66
        if err != nil {
109✔
67
                return fmt.Errorf("unable to set Git reference '%s' to '%s': %w", refName, newGitID.String(), err)
×
68
        }
×
69

70
        return nil
109✔
71
}
72

73
// GetSymbolicReferenceTarget returns the name of the Git reference the provided
74
// symbolic Git reference is pointing to.
75
func (r *Repository) GetSymbolicReferenceTarget(refName string) (string, error) {
3✔
76
        symTarget, err := r.executor("symbolic-ref", refName).executeString()
3✔
77
        if err != nil {
3✔
78
                return "", fmt.Errorf("unable to resolve %s: %w", refName, err)
×
79
        }
×
80

81
        return symTarget, nil
3✔
82
}
83

84
// SetSymbolicReference sets the specified symbolic reference to the specified
85
// target reference.
86
func (r *Repository) SetSymbolicReference(symRefName, targetRefName string) error {
1✔
87
        _, err := r.executor("symbolic-ref", symRefName, targetRefName).executeString()
1✔
88
        if err != nil {
1✔
NEW
89
                return fmt.Errorf("unable to set symbolic Git reference '%s' to '%s': %w", symRefName, targetRefName, err)
×
NEW
90
        }
×
91

92
        return nil
1✔
93
}
94

95
// AbsoluteReference returns the fully qualified reference path for the provided
96
// Git ref.
97
// Source: https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrefnamegtemegemmasterememheadsmasterememrefsheadsmasterem
98
func (r *Repository) AbsoluteReference(target string) (string, error) {
4✔
99
        _, err := os.Stat(path.Join(r.gitDirPath, target))
4✔
100
        if err == nil {
4✔
101
                if strings.HasPrefix(target, RefPrefix) {
×
102
                        // not symbolic ref
×
103
                        return target, nil
×
104
                }
×
105
                // symbolic ref such as .git/HEAD
106
                return r.GetSymbolicReferenceTarget(target)
×
107
        }
108

109
        // We may have a ref that isn't available locally but is still ref-prefixed.
110
        if strings.HasPrefix(target, RefPrefix) {
4✔
111
                return target, nil
×
112
        }
×
113

114
        // If target is a full ref already and it's stored in the GIT_DIR/refs
115
        // directory, we don't reach this point. Below, we handle cases where the
116
        // ref may be packed.
117

118
        // Check if custom reference
119
        customName := CustomReferenceName(target)
4✔
120
        _, err = r.GetReference(customName)
4✔
121
        if err == nil {
4✔
122
                return customName, nil
×
123
        }
×
124
        if !errors.Is(err, ErrReferenceNotFound) {
4✔
125
                return "", err
×
126
        }
×
127

128
        // Check if tag
129
        tagName := TagReferenceName(target)
4✔
130
        _, err = r.GetReference(tagName)
4✔
131
        if err == nil {
4✔
132
                return tagName, nil
×
133
        }
×
134
        if !errors.Is(err, ErrReferenceNotFound) {
4✔
135
                return "", err
×
136
        }
×
137

138
        // Check if branch
139
        branchName := BranchReferenceName(target)
4✔
140
        _, err = r.GetReference(branchName)
4✔
141
        if err == nil {
8✔
142
                return branchName, nil
4✔
143
        }
4✔
144
        if !errors.Is(err, ErrReferenceNotFound) {
×
145
                return "", err
×
146
        }
×
147

148
        // Check if remote tracker ref
149
        remoteRefName := RemoteReferenceName(target)
×
150
        _, err = r.GetReference(remoteRefName)
×
151
        if err == nil {
×
152
                return branchName, nil
×
153
        }
×
154
        if !errors.Is(err, ErrReferenceNotFound) {
×
155
                return "", err
×
156
        }
×
157

158
        remoteRefHEAD := path.Join(remoteRefName, "HEAD")
×
159
        _, err = r.GetReference(remoteRefHEAD)
×
160
        if err == nil {
×
161
                return branchName, nil
×
162
        }
×
163
        if !errors.Is(err, ErrReferenceNotFound) {
×
164
                return "", err
×
165
        }
×
166

167
        return "", ErrReferenceNotFound
×
168
}
169

170
// RefSpec creates a Git refspec for the specified ref.  For more information on
171
// the Git refspec, please consult:
172
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec.
173
func (r *Repository) RefSpec(refName, remoteName string, fastForwardOnly bool) (string, error) {
32✔
174
        var (
32✔
175
                refPath string
32✔
176
                err     error
32✔
177
        )
32✔
178

32✔
179
        refPath = refName
32✔
180
        if !strings.HasPrefix(refPath, RefPrefix) {
36✔
181
                refPath, err = r.AbsoluteReference(refName)
4✔
182
                if err != nil {
4✔
183
                        return "", err
×
184
                }
×
185
        }
186

187
        if strings.HasPrefix(refPath, TagRefPrefix) {
36✔
188
                // TODO: check if this is correct, AFAICT tags aren't tracked in the
4✔
189
                // remotes namespace.
4✔
190
                fastForwardOnly = true
4✔
191
        }
4✔
192

193
        // local is always refPath, destination depends on remoteName
194
        localPath := refPath
32✔
195
        var remotePath string
32✔
196
        if len(remoteName) > 0 {
42✔
197
                remotePath = RemoteRef(refPath, remoteName)
10✔
198
        } else {
32✔
199
                remotePath = refPath
22✔
200
        }
22✔
201

202
        refSpecString := fmt.Sprintf("%s:%s", localPath, remotePath)
32✔
203
        if !fastForwardOnly {
40✔
204
                refSpecString = fmt.Sprintf("+%s", refSpecString)
8✔
205
        }
8✔
206

207
        return refSpecString, nil
32✔
208
}
209

210
// CustomReferenceName returns the full reference name in the form
211
// `refs/<customName>`.
212
func CustomReferenceName(customName string) string {
4✔
213
        if strings.HasPrefix(customName, RefPrefix) {
4✔
214
                return customName
×
215
        }
×
216

217
        return fmt.Sprintf("%s%s", RefPrefix, customName)
4✔
218
}
219

220
// TagReferenceName returns the full reference name for the specified tag in the
221
// form `refs/tags/<tagName>`.
222
func TagReferenceName(tagName string) string {
9✔
223
        if strings.HasPrefix(tagName, TagRefPrefix) {
10✔
224
                return tagName
1✔
225
        }
1✔
226

227
        return fmt.Sprintf("%s%s", TagRefPrefix, tagName)
8✔
228
}
229

230
// BranchReferenceName returns the full reference name for the specified branch
231
// in the form `refs/heads/<branchName>`.
232
func BranchReferenceName(branchName string) string {
27✔
233
        if strings.HasPrefix(branchName, BranchRefPrefix) {
28✔
234
                return branchName
1✔
235
        }
1✔
236

237
        return fmt.Sprintf("%s%s", BranchRefPrefix, branchName)
26✔
238
}
239

240
// RemoteReferenceName returns the full reference name in the form
241
// `refs/remotes/<name>`.
242
func RemoteReferenceName(name string) string {
×
243
        if strings.HasPrefix(name, RemoteRefPrefix) {
×
244
                return name
×
245
        }
×
246

247
        return fmt.Sprintf("%s%s", RemoteRefPrefix, name)
×
248
}
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