+1
-1
config/branch.go
+1
-1
config/branch.go
+2
-1
config/config.go
+2
-1
config/config.go
···
13
13
14
14
"github.com/go-git/go-billy/v5/osfs"
15
15
"github.com/go-git/go-git/v5/internal/url"
16
+
"github.com/go-git/go-git/v5/plumbing"
16
17
format "github.com/go-git/go-git/v5/plumbing/format/config"
17
18
)
18
19
···
614
615
c.Fetch = []RefSpec{RefSpec(fmt.Sprintf(DefaultFetchRefSpec, c.Name))}
615
616
}
616
617
617
-
return nil
618
+
return plumbing.NewRemoteHEADReferenceName(c.Name).Validate()
618
619
}
619
620
620
621
func (c *RemoteConfig) unmarshal(s *format.Subsection) error {
+89
plumbing/reference.go
+89
plumbing/reference.go
···
3
3
import (
4
4
"errors"
5
5
"fmt"
6
+
"regexp"
6
7
"strings"
7
8
)
8
9
···
29
30
30
31
var (
31
32
ErrReferenceNotFound = errors.New("reference not found")
33
+
34
+
// ErrInvalidReferenceName is returned when a reference name is invalid.
35
+
ErrInvalidReferenceName = errors.New("invalid reference name")
32
36
)
33
37
34
38
// ReferenceType reference type's
···
122
126
}
123
127
124
128
return res
129
+
}
130
+
131
+
var (
132
+
ctrlSeqs = regexp.MustCompile(`[\000-\037\177]`)
133
+
)
134
+
135
+
// Validate validates a reference name.
136
+
// This follows the git-check-ref-format rules.
137
+
// See https://git-scm.com/docs/git-check-ref-format
138
+
//
139
+
// It is important to note that this function does not check if the reference
140
+
// exists in the repository.
141
+
// It only checks if the reference name is valid.
142
+
// This functions does not support the --refspec-pattern, --normalize, and
143
+
// --allow-onelevel options.
144
+
//
145
+
// Git imposes the following rules on how references are named:
146
+
//
147
+
// 1. They can include slash / for hierarchical (directory) grouping, but no
148
+
// slash-separated component can begin with a dot . or end with the
149
+
// sequence .lock.
150
+
// 2. They must contain at least one /. This enforces the presence of a
151
+
// category like heads/, tags/ etc. but the actual names are not
152
+
// restricted. If the --allow-onelevel option is used, this rule is
153
+
// waived.
154
+
// 3. They cannot have two consecutive dots .. anywhere.
155
+
// 4. They cannot have ASCII control characters (i.e. bytes whose values are
156
+
// lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon :
157
+
// anywhere.
158
+
// 5. They cannot have question-mark ?, asterisk *, or open bracket [
159
+
// anywhere. See the --refspec-pattern option below for an exception to this
160
+
// rule.
161
+
// 6. They cannot begin or end with a slash / or contain multiple consecutive
162
+
// slashes (see the --normalize option below for an exception to this rule).
163
+
// 7. They cannot end with a dot ..
164
+
// 8. They cannot contain a sequence @{.
165
+
// 9. They cannot be the single character @.
166
+
// 10. They cannot contain a \.
167
+
func (r ReferenceName) Validate() error {
168
+
s := string(r)
169
+
if len(s) == 0 {
170
+
return ErrInvalidReferenceName
171
+
}
172
+
173
+
// HEAD is a special case
174
+
if r == HEAD {
175
+
return nil
176
+
}
177
+
178
+
// rule 7
179
+
if strings.HasSuffix(s, ".") {
180
+
return ErrInvalidReferenceName
181
+
}
182
+
183
+
// rule 2
184
+
parts := strings.Split(s, "/")
185
+
if len(parts) < 2 {
186
+
return ErrInvalidReferenceName
187
+
}
188
+
189
+
isBranch := r.IsBranch()
190
+
isTag := r.IsTag()
191
+
for _, part := range parts {
192
+
// rule 6
193
+
if len(part) == 0 {
194
+
return ErrInvalidReferenceName
195
+
}
196
+
197
+
if strings.HasPrefix(part, ".") || // rule 1
198
+
strings.Contains(part, "..") || // rule 3
199
+
ctrlSeqs.MatchString(part) || // rule 4
200
+
strings.ContainsAny(part, "~^:?*[ \t\n") || // rule 4 & 5
201
+
strings.Contains(part, "@{") || // rule 8
202
+
part == "@" || // rule 9
203
+
strings.Contains(part, "\\") || // rule 10
204
+
strings.HasSuffix(part, ".lock") { // rule 1
205
+
return ErrInvalidReferenceName
206
+
}
207
+
208
+
if (isBranch || isTag) && strings.HasPrefix(part, "-") { // branches & tags can't start with -
209
+
return ErrInvalidReferenceName
210
+
}
211
+
}
212
+
213
+
return nil
125
214
}
126
215
127
216
const (
+59
plumbing/reference_test.go
+59
plumbing/reference_test.go
···
103
103
c.Assert(r.IsTag(), Equals, true)
104
104
}
105
105
106
+
func (s *ReferenceSuite) TestValidReferenceNames(c *C) {
107
+
valid := []ReferenceName{
108
+
"refs/heads/master",
109
+
"refs/notes/commits",
110
+
"refs/remotes/origin/master",
111
+
"HEAD",
112
+
"refs/tags/v3.1.1",
113
+
"refs/pulls/1/head",
114
+
"refs/pulls/1/merge",
115
+
"refs/pulls/1/abc.123",
116
+
"refs/pulls",
117
+
"refs/-", // should this be allowed?
118
+
}
119
+
for _, v := range valid {
120
+
c.Assert(v.Validate(), IsNil)
121
+
}
122
+
123
+
invalid := []ReferenceName{
124
+
"refs",
125
+
"refs/",
126
+
"refs//",
127
+
"refs/heads/\\",
128
+
"refs/heads/\\foo",
129
+
"refs/heads/\\foo/bar",
130
+
"abc",
131
+
"",
132
+
"refs/heads/ ",
133
+
"refs/heads/ /",
134
+
"refs/heads/ /foo",
135
+
"refs/heads/.",
136
+
"refs/heads/..",
137
+
"refs/heads/foo..",
138
+
"refs/heads/foo.lock",
139
+
"refs/heads/foo@{bar}",
140
+
"refs/heads/foo[",
141
+
"refs/heads/foo~",
142
+
"refs/heads/foo^",
143
+
"refs/heads/foo:",
144
+
"refs/heads/foo?",
145
+
"refs/heads/foo*",
146
+
"refs/heads/foo[bar",
147
+
"refs/heads/foo\t",
148
+
"refs/heads/@",
149
+
"refs/heads/@{bar}",
150
+
"refs/heads/\n",
151
+
"refs/heads/-foo",
152
+
"refs/heads/foo..bar",
153
+
"refs/heads/-",
154
+
"refs/tags/-",
155
+
"refs/tags/-foo",
156
+
}
157
+
158
+
for i, v := range invalid {
159
+
comment := Commentf("invalid reference name case %d: %s", i, v)
160
+
c.Assert(v.Validate(), NotNil, comment)
161
+
c.Assert(v.Validate(), ErrorMatches, "invalid reference name", comment)
162
+
}
163
+
}
164
+
106
165
func benchMarkReferenceString(r *Reference, b *testing.B) {
107
166
for n := 0; n < b.N; n++ {
108
167
_ = r.String()
+8
-1
repository.go
+8
-1
repository.go
···
98
98
options.DefaultBranch = plumbing.Master
99
99
}
100
100
101
+
if err := options.DefaultBranch.Validate(); err != nil {
102
+
return nil, err
103
+
}
104
+
101
105
r := newRepository(s, worktree)
102
106
_, err := r.Reference(plumbing.HEAD, false)
103
107
switch err {
···
724
728
// CreateTag creates a tag. If opts is included, the tag is an annotated tag,
725
729
// otherwise a lightweight tag is created.
726
730
func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error) {
727
-
rname := plumbing.ReferenceName(path.Join("refs", "tags", name))
731
+
rname := plumbing.NewTagReferenceName(name)
732
+
if err := rname.Validate(); err != nil {
733
+
return nil, err
734
+
}
728
735
729
736
_, err := r.Storer.Reference(rname)
730
737
switch err {
+37
repository_test.go
+37
repository_test.go
···
75
75
76
76
}
77
77
78
+
func (s *RepositorySuite) TestInitWithInvalidDefaultBranch(c *C) {
79
+
_, err := InitWithOptions(memory.NewStorage(), memfs.New(), InitOptions{
80
+
DefaultBranch: "foo",
81
+
})
82
+
c.Assert(err, NotNil)
83
+
}
84
+
78
85
func createCommit(c *C, r *Repository) {
79
86
// Create a commit so there is a HEAD to check
80
87
wt, err := r.Worktree()
···
389
396
alt, err := r.Remote("foo")
390
397
c.Assert(err, Equals, ErrRemoteNotFound)
391
398
c.Assert(alt, IsNil)
399
+
}
400
+
401
+
func (s *RepositorySuite) TestEmptyCreateBranch(c *C) {
402
+
r, _ := Init(memory.NewStorage(), nil)
403
+
err := r.CreateBranch(&config.Branch{})
404
+
405
+
c.Assert(err, NotNil)
406
+
}
407
+
408
+
func (s *RepositorySuite) TestInvalidCreateBranch(c *C) {
409
+
r, _ := Init(memory.NewStorage(), nil)
410
+
err := r.CreateBranch(&config.Branch{
411
+
Name: "-foo",
412
+
})
413
+
414
+
c.Assert(err, NotNil)
392
415
}
393
416
394
417
func (s *RepositorySuite) TestCreateBranchAndBranch(c *C) {
···
2795
2818
obj, err = r.TagObject(ref.Hash())
2796
2819
c.Assert(obj, IsNil)
2797
2820
c.Assert(err, Equals, plumbing.ErrObjectNotFound)
2821
+
}
2822
+
2823
+
func (s *RepositorySuite) TestInvalidTagName(c *C) {
2824
+
r, err := Init(memory.NewStorage(), nil)
2825
+
c.Assert(err, IsNil)
2826
+
for i, name := range []string{
2827
+
"",
2828
+
"foo bar",
2829
+
"foo\tbar",
2830
+
"foo\nbar",
2831
+
} {
2832
+
_, err = r.CreateTag(name, plumbing.ZeroHash, nil)
2833
+
c.Assert(err, NotNil, Commentf("case %d %q", i, name))
2834
+
}
2798
2835
}
2799
2836
2800
2837
func (s *RepositorySuite) TestBranches(c *C) {
+4
worktree.go
+4
worktree.go
···
189
189
return w.Reset(ro)
190
190
}
191
191
func (w *Worktree) createBranch(opts *CheckoutOptions) error {
192
+
if err := opts.Branch.Validate(); err != nil {
193
+
return err
194
+
}
195
+
192
196
_, err := w.r.Storer.Reference(opts.Branch)
193
197
if err == nil {
194
198
return fmt.Errorf("a branch named %q already exists", opts.Branch)
+24
worktree_test.go
+24
worktree_test.go
···
785
785
c.Assert(err, Equals, ErrCreateRequiresBranch)
786
786
}
787
787
788
+
func (s *WorktreeSuite) TestCheckoutCreateInvalidBranch(c *C) {
789
+
w := &Worktree{
790
+
r: s.Repository,
791
+
Filesystem: memfs.New(),
792
+
}
793
+
794
+
for _, name := range []plumbing.ReferenceName{
795
+
"foo",
796
+
"-",
797
+
"-foo",
798
+
"refs/heads//",
799
+
"refs/heads/..",
800
+
"refs/heads/a..b",
801
+
"refs/heads/.",
802
+
} {
803
+
err := w.Checkout(&CheckoutOptions{
804
+
Create: true,
805
+
Branch: name,
806
+
})
807
+
808
+
c.Assert(err, Equals, plumbing.ErrInvalidReferenceName)
809
+
}
810
+
}
811
+
788
812
func (s *WorktreeSuite) TestCheckoutTag(c *C) {
789
813
f := fixtures.ByTag("tags").One()
790
814
r := s.NewRepositoryWithEmptyWorktree(f)