diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2023-06-05 13:00:51 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2023-06-05 13:41:13 +0300 |
commit | dab5f8597c483943b214ca33181ac8636df3f822 (patch) | |
tree | 2bd3fdc99e14dd538366264159d724e9b124e7fc | |
parent | 9a0bc6b03f1fd6a954f149f16fbb4bd44c6d8905 (diff) |
git: Optionally allow path-scoped revisions in `ValidateRevision()`
The `ValidateRevision()` helper function rejects all revisions that
contain a colon. This also restricts this function from being used with
path-scoped revisions like `HEAD:README.md`, which require the caller to
use a colon.
Introduce a new option `AllowPathScopedRevision()` that lets callers
accept these kinds of revisions.
-rw-r--r-- | internal/git/revision.go | 31 | ||||
-rw-r--r-- | internal/git/revision_test.go | 40 |
2 files changed, 59 insertions, 12 deletions
diff --git a/internal/git/revision.go b/internal/git/revision.go index 751317614..d239fd9f0 100644 --- a/internal/git/revision.go +++ b/internal/git/revision.go @@ -6,7 +6,8 @@ import ( ) type validateRevisionConfig struct { - allowEmpty bool + allowEmpty bool + allowPathScopedRevision bool } // ValidateRevisionOption is an option that can be passed to ValidateRevision. @@ -20,6 +21,15 @@ func AllowEmptyRevision() ValidateRevisionOption { } } +// AllowPathScopedRevision changes ValidateRevision to allow path-scoped revisions like +// `HEAD:README.md`. Note that path-scoped revisions may contain any character except for NUL bytes. +// Most importantly, a path-scoped revision may contain newlines. +func AllowPathScopedRevision() ValidateRevisionOption { + return func(cfg *validateRevisionConfig) { + cfg.allowPathScopedRevision = true + } +} + // ValidateRevision checks if a revision looks valid. The default behaviour can be changed by // passing ValidateRevisionOptions. func ValidateRevision(revision []byte, opts ...ValidateRevisionOption) error { @@ -28,18 +38,25 @@ func ValidateRevision(revision []byte, opts ...ValidateRevisionOption) error { opt(&cfg) } - if !cfg.allowEmpty && len(revision) == 0 { - return fmt.Errorf("empty revision") - } if bytes.HasPrefix(revision, []byte("-")) { return fmt.Errorf("revision can't start with '-'") } - if bytes.ContainsAny(revision, " \t\n\r") { - return fmt.Errorf("revision can't contain whitespace") - } if bytes.Contains(revision, []byte("\x00")) { return fmt.Errorf("revision can't contain NUL") } + + if cfg.allowPathScopedRevision { + // We don't need to validate the path component, if any, given that it may contain + // all bytes except for the NUL byte which we already checked for above. + revision, _, _ = bytes.Cut(revision, []byte(":")) + } + + if !cfg.allowEmpty && len(revision) == 0 { + return fmt.Errorf("empty revision") + } + if bytes.ContainsAny(revision, " \t\n\r") { + return fmt.Errorf("revision can't contain whitespace") + } if bytes.Contains(revision, []byte(":")) { return fmt.Errorf("revision can't contain ':'") } diff --git a/internal/git/revision_test.go b/internal/git/revision_test.go index f2e210fc8..e65a0aa18 100644 --- a/internal/git/revision_test.go +++ b/internal/git/revision_test.go @@ -65,15 +65,45 @@ func TestValidateRevision(t *testing.T) { expectedErr: fmt.Errorf("revision can't contain NUL"), }, { - desc: "colon", - revision: "foo/bar:baz", - expectedErr: fmt.Errorf("revision can't contain ':'"), - }, - { desc: "backslash", revision: "foo\\bar\\baz", expectedErr: fmt.Errorf("revision can't contain '\\'"), }, + { + desc: "path-scoped revisions refused by default", + revision: "refs/heads/main:path", + expectedErr: fmt.Errorf("revision can't contain ':'"), + }, + { + desc: "path-scoped revision allowed with option", + revision: "refs/heads/main:path", + opts: []ValidateRevisionOption{ + AllowPathScopedRevision(), + }, + }, + { + desc: "path-scoped revision does not allow newline in revision", + revision: "refs/heads\nmain:path", + opts: []ValidateRevisionOption{ + AllowPathScopedRevision(), + }, + expectedErr: fmt.Errorf("revision can't contain whitespace"), + }, + { + desc: "path-scoped revision must not be empty", + revision: ":path", + opts: []ValidateRevisionOption{ + AllowPathScopedRevision(), + }, + expectedErr: fmt.Errorf("empty revision"), + }, + { + desc: "path-scoped path may contain arbitrary characters", + revision: "refs/heads/main:path\n\t\r :\\", + opts: []ValidateRevisionOption{ + AllowPathScopedRevision(), + }, + }, } { t.Run(tc.desc, func(t *testing.T) { require.Equal(t, tc.expectedErr, ValidateRevision([]byte(tc.revision), tc.opts...)) |