From ebb349a58429219aaf2f5d9d4dab8ce94c6dd19e Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Mon, 15 Jun 2026 15:45:18 +0000 Subject: [PATCH 1/2] feat(entity): add git:// change provider and share hex SHA validation Adds entity/change/git, a git-scheme change URI parser that mirrors the github provider (same field/structure naming), differing only where the schemes genuinely differ: a fixed git scheme and a fully-qualified, percent-encoded git ref in place of pull/{pr_number}. The ref segment carries refs/heads/ or refs/tags/, percent- encoded so branches, tags, and slash-containing ref names fit a single path segment unambiguously. Extracts the lowercase-hex-SHA check into a shared entity/change/changeutil.IsFullHex(s, length) helper now used by both the github and git providers, with the expected length passed in per provider. --- entity/change/change.go | 2 +- entity/change/changeutil/BUILD.bazel | 15 +++ entity/change/changeutil/hex.go | 33 ++++++ entity/change/changeutil/hex_test.go | 43 ++++++++ entity/change/git/BUILD.bazel | 19 ++++ entity/change/git/change_id.go | 122 ++++++++++++++++++++++ entity/change/git/change_id_test.go | 148 +++++++++++++++++++++++++++ entity/change/github/BUILD.bazel | 1 + entity/change/github/change_id.go | 18 +--- 9 files changed, 385 insertions(+), 16 deletions(-) create mode 100644 entity/change/changeutil/BUILD.bazel create mode 100644 entity/change/changeutil/hex.go create mode 100644 entity/change/changeutil/hex_test.go create mode 100644 entity/change/git/BUILD.bazel create mode 100644 entity/change/git/change_id.go create mode 100644 entity/change/git/change_id_test.go diff --git a/entity/change/change.go b/entity/change/change.go index bf24dc4c..ff86c272 100644 --- a/entity/change/change.go +++ b/entity/change/change.go @@ -14,7 +14,7 @@ // Package change holds the shared code-change identity used across SubmitQueue, // Stovepipe, and other repo-local domains. A Change names code to act on via -// provider URIs; the URI parsers live in the github and phabricator subpackages. +// provider URIs; the URI parsers live in the github, phabricator, and git subpackages. package change // Change represents a code change identified by URIs from a code change provider (e.g., GitHub Pull Request, Phabricator Diff). diff --git a/entity/change/changeutil/BUILD.bazel b/entity/change/changeutil/BUILD.bazel new file mode 100644 index 00000000..46c750d5 --- /dev/null +++ b/entity/change/changeutil/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "changeutil", + srcs = ["hex.go"], + importpath = "github.com/uber/submitqueue/entity/change/changeutil", + visibility = ["//visibility:public"], +) + +go_test( + name = "changeutil_test", + srcs = ["hex_test.go"], + embed = [":changeutil"], + deps = ["@com_github_stretchr_testify//assert"], +) diff --git a/entity/change/changeutil/hex.go b/entity/change/changeutil/hex.go new file mode 100644 index 00000000..d33fe915 --- /dev/null +++ b/entity/change/changeutil/hex.go @@ -0,0 +1,33 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package changeutil holds small helpers shared across the entity/change +// provider subpackages (github, git, ...). +package changeutil + +// IsFullHex reports whether s is exactly length lowercase hex characters. +// Providers that pin a change to a commit by SHA (github, git) use this to +// validate the SHA segment of a change URI. +func IsFullHex(s string, length int) bool { + if len(s) != length { + return false + } + for i := 0; i < len(s); i++ { + c := s[i] + if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') { + return false + } + } + return true +} diff --git a/entity/change/changeutil/hex_test.go b/entity/change/changeutil/hex_test.go new file mode 100644 index 00000000..5ec12d47 --- /dev/null +++ b/entity/change/changeutil/hex_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package changeutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsFullHex(t *testing.T) { + tests := []struct { + name string + s string + length int + want bool + }{ + {name: "valid 40-char SHA", s: "abcdef0123456789abcdef0123456789abcdef01", length: 40, want: true}, + {name: "valid custom length", s: "abc123", length: 6, want: true}, + {name: "too short", s: "abc", length: 40, want: false}, + {name: "too long", s: "abcdef0123456789abcdef0123456789abcdef0101", length: 40, want: false}, + {name: "uppercase rejected", s: "ABCDEF0123456789ABCDEF0123456789ABCDEF01", length: 40, want: false}, + {name: "non-hex rejected", s: "zzzzzz0123456789abcdef0123456789abcdef01", length: 40, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, IsFullHex(tt.s, tt.length)) + }) + } +} diff --git a/entity/change/git/BUILD.bazel b/entity/change/git/BUILD.bazel new file mode 100644 index 00000000..de6889d9 --- /dev/null +++ b/entity/change/git/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "git", + srcs = ["change_id.go"], + importpath = "github.com/uber/submitqueue/entity/change/git", + visibility = ["//visibility:public"], + deps = ["//entity/change/changeutil"], +) + +go_test( + name = "git_test", + srcs = ["change_id_test.go"], + embed = [":git"], + deps = [ + "@com_github_stretchr_testify//assert", + "@com_github_stretchr_testify//require", + ], +) diff --git a/entity/change/git/change_id.go b/entity/change/git/change_id.go new file mode 100644 index 00000000..db4eb959 --- /dev/null +++ b/entity/change/git/change_id.go @@ -0,0 +1,122 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package git parses change IDs that use the git:// URI scheme. +package git + +import ( + "fmt" + "net/url" + "strings" + + "github.com/uber/submitqueue/entity/change/changeutil" +) + +// scheme is the canonical URI scheme for git-backed change identifiers. +const scheme = "git" + +// refPrefix is the namespace every fully-qualified git ref lives under +// (refs/heads/, refs/tags/, ...). Disambiguates between branches and tags +// of the same name. +const refPrefix = "refs/" + +// changeIDFormat is the expected format for change IDs, included in error messages. +const changeIDFormat = "git://{host}/{repo-path}/{ref}/{commit_sha}" + +// shaLength is the length of a git commit SHA. +const shaLength = 40 + +// ChangeID represents a parsed git:// change identifier. +// Format: git://{host}/{repo-path}/{ref}/{commit_sha} +// +// Ref is a fully-qualified, percent-encoded git ref so that branches, tags, and +// ref names containing slashes all fit a single path segment unambiguously. +type ChangeID struct { + // Scheme captures the URI scheme (always "git" in current implementation). + Scheme string + // Host is the git remote authority, "host" or "host:port" (e.g. + // "git.example.com" or "git.example.com:9418"). Unlike a forge "org", git + // addresses a repository by the remote it lives on. + Host string + // RepoPath is the path to the repository on the remote and may contain + // slashes (e.g. "uber/monorepo" or "team/group/repo.git"). + RepoPath string + // Ref is the fully-qualified git ref the change landed on, decoded from the + // URI (e.g. "refs/heads/main", "refs/tags/v1.0"). + Ref string + // CommitSHA is the commit Ref pointed at, captured at a point in time. It is + // not necessarily a branch head; it records the commit Ref resolved to when + // the change entered the pipeline. + CommitSHA string +} + +// ParseChangeID parses a raw change ID string into a ChangeID. +// Expected format: git://{host}/{repo-path}/{commit_sha} with a fully-qualified, +// percent-encoded ref as the second-to-last path segment. +func ParseChangeID(raw string) (ChangeID, error) { + u, err := url.Parse(raw) + if err != nil { + return ChangeID{}, fmt.Errorf("invalid change ID %q: %w (expected format: %s)", raw, err, changeIDFormat) + } + if u.Scheme != scheme { + return ChangeID{}, fmt.Errorf("invalid change ID %q: scheme must be %q, got %q (expected format: %s)", raw, scheme, u.Scheme, changeIDFormat) + } + if u.Host == "" { + return ChangeID{}, fmt.Errorf("invalid change ID %q: missing remote host (expected format: %s)", raw, changeIDFormat) + } + + // Split on the escaped path so the percent-encoded ref stays a single + // segment (url.URL.Path decodes %2F to "/", which would split it apart). + segments := strings.Split(strings.TrimPrefix(u.EscapedPath(), "/"), "/") + // Need at least 3 segments: {repo-path}/{ref}/{commit_sha}. + if len(segments) < 3 { + return ChangeID{}, fmt.Errorf("invalid change ID %q: need at least repo-path/ref/sha, got %d path segments (expected format: %s)", raw, len(segments), changeIDFormat) + } + + sha := segments[len(segments)-1] + encodedRef := segments[len(segments)-2] + repoPath := strings.Join(segments[:len(segments)-2], "/") + + if sha == "" { + return ChangeID{}, fmt.Errorf("invalid change ID %q: empty commit SHA (expected format: %s)", raw, changeIDFormat) + } + if !changeutil.IsFullHex(sha, shaLength) { + return ChangeID{}, fmt.Errorf("invalid change ID %q: commit SHA %q must be %d lowercase hex characters (expected format: %s)", raw, sha, shaLength, changeIDFormat) + } + + ref, err := url.PathUnescape(encodedRef) + if err != nil { + return ChangeID{}, fmt.Errorf("invalid change ID %q: ref %q is not valid percent-encoding: %w (expected format: %s)", raw, encodedRef, err, changeIDFormat) + } + if !strings.HasPrefix(ref, refPrefix) || ref == refPrefix { + return ChangeID{}, fmt.Errorf("invalid change ID %q: ref %q must be a fully-qualified git ref (e.g. refs/heads/main, refs/tags/v1.0) (expected format: %s)", raw, ref, changeIDFormat) + } + + if repoPath == "" { + return ChangeID{}, fmt.Errorf("invalid change ID %q: empty repo path (expected format: %s)", raw, changeIDFormat) + } + + return ChangeID{ + Scheme: u.Scheme, + Host: u.Host, + RepoPath: repoPath, + Ref: ref, + CommitSHA: sha, + }, nil +} + +// String returns the string representation of the change ID. +func (c ChangeID) String() string { + return fmt.Sprintf("%s://%s/%s/%s/%s", c.Scheme, c.Host, c.RepoPath, url.PathEscape(c.Ref), c.CommitSHA) +} diff --git a/entity/change/git/change_id_test.go b/entity/change/git/change_id_test.go new file mode 100644 index 00000000..b1c4a187 --- /dev/null +++ b/entity/change/git/change_id_test.go @@ -0,0 +1,148 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseChangeID(t *testing.T) { + sha := "c3a4d5e6f7890123456789abcdef0123456789ab" + + tests := []struct { + name string + raw string + want ChangeID + wantErr bool + }{ + { + name: "branch ref", + raw: "git://git.example.com/uber/monorepo/refs%2Fheads%2Fmain/" + sha, + want: ChangeID{ + Scheme: "git", + Host: "git.example.com", + RepoPath: "uber/monorepo", + Ref: "refs/heads/main", + CommitSHA: sha, + }, + }, + { + name: "host with port", + raw: "git://git.example.com:9418/uber/monorepo/refs%2Fheads%2Fmain/" + sha, + want: ChangeID{ + Scheme: "git", + Host: "git.example.com:9418", + RepoPath: "uber/monorepo", + Ref: "refs/heads/main", + CommitSHA: sha, + }, + }, + { + name: "single-segment repo path", + raw: "git://git.example.com/monorepo/refs%2Fheads%2Fmain/" + sha, + want: ChangeID{ + Scheme: "git", + Host: "git.example.com", + RepoPath: "monorepo", + Ref: "refs/heads/main", + CommitSHA: sha, + }, + }, + { + name: "branch ref with slash", + raw: "git://git.example.com/uber/monorepo/refs%2Fheads%2Ffeature%2Fx/" + sha, + want: ChangeID{ + Scheme: "git", + Host: "git.example.com", + RepoPath: "uber/monorepo", + Ref: "refs/heads/feature/x", + CommitSHA: sha, + }, + }, + { + name: "tag ref", + raw: "git://git.example.com/uber/monorepo/refs%2Ftags%2Fv1.0/" + sha, + want: ChangeID{ + Scheme: "git", + Host: "git.example.com", + RepoPath: "uber/monorepo", + Ref: "refs/tags/v1.0", + CommitSHA: sha, + }, + }, + { + name: "nested repo path", + raw: "git://git.example.com/uber/deepteam/monorepo/refs%2Fheads%2Fmain/" + sha, + want: ChangeID{ + Scheme: "git", + Host: "git.example.com", + RepoPath: "uber/deepteam/monorepo", + Ref: "refs/heads/main", + CommitSHA: sha, + }, + }, + { + name: "wrong scheme", + raw: "github://git.example.com/uber/monorepo/refs%2Fheads%2Fmain/" + sha, + wantErr: true, + }, + { + name: "missing host", + raw: "git:///uber/monorepo/refs%2Fheads%2Fmain/" + sha, + wantErr: true, + }, + { + name: "missing commit SHA", + raw: "git://git.example.com/uber/monorepo/refs%2Fheads%2Fmain", + wantErr: true, + }, + { + name: "abbreviated SHA", + raw: "git://git.example.com/uber/monorepo/refs%2Fheads%2Fmain/deadbeef", + wantErr: true, + }, + { + name: "unqualified ref", + raw: "git://git.example.com/uber/monorepo/main/" + sha, + wantErr: true, + }, + { + name: "malformed percent-encoding", + raw: "git://git.example.com/uber/monorepo/refs%2/" + sha, + wantErr: true, + }, + { + name: "empty repo path", + raw: "git://git.example.com//refs%2Fheads%2Fmain/" + sha, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseChangeID(tt.raw) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.raw, got.String()) + }) + } +} diff --git a/entity/change/github/BUILD.bazel b/entity/change/github/BUILD.bazel index 85b9c172..9311d029 100644 --- a/entity/change/github/BUILD.bazel +++ b/entity/change/github/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = ["change_id.go"], importpath = "github.com/uber/submitqueue/entity/change/github", visibility = ["//visibility:public"], + deps = ["//entity/change/changeutil"], ) go_test( diff --git a/entity/change/github/change_id.go b/entity/change/github/change_id.go index c5556ec5..3bfca416 100644 --- a/entity/change/github/change_id.go +++ b/entity/change/github/change_id.go @@ -18,6 +18,8 @@ import ( "fmt" "strconv" "strings" + + "github.com/uber/submitqueue/entity/change/changeutil" ) // changeIDFormat is the expected format for change IDs, included in error messages. @@ -91,7 +93,7 @@ func ParseChangeID(raw string) (ChangeID, error) { if sha == "" { return ChangeID{}, fmt.Errorf("invalid change ID %q: empty head commit SHA (expected format: %s)", raw, changeIDFormat) } - if !isFullHexSHA(sha) { + if !changeutil.IsFullHex(sha, shaLength) { return ChangeID{}, fmt.Errorf("invalid change ID %q: head commit SHA %q must be %d lowercase hex characters (expected format: %s)", raw, sha, shaLength, changeIDFormat) } @@ -133,17 +135,3 @@ func (c ChangeID) String() string { func (c ChangeID) OwnerRepo() string { return fmt.Sprintf("%s/%s", c.Org, c.Repo) } - -// isFullHexSHA reports whether s is exactly shaLength lowercase hex characters. -func isFullHexSHA(s string) bool { - if len(s) != shaLength { - return false - } - for i := 0; i < len(s); i++ { - c := s[i] - if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') { - return false - } - } - return true -} From 05bcab5f6c48e94b3c5c773ee50c83872ed8f6a2 Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Mon, 15 Jun 2026 18:07:05 +0000 Subject: [PATCH 2/2] Update --- entity/change/git/change_id.go | 43 ++++++++++++++--------------- entity/change/git/change_id_test.go | 24 ++++++++-------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/entity/change/git/change_id.go b/entity/change/git/change_id.go index db4eb959..aa68e30b 100644 --- a/entity/change/git/change_id.go +++ b/entity/change/git/change_id.go @@ -32,38 +32,35 @@ const scheme = "git" const refPrefix = "refs/" // changeIDFormat is the expected format for change IDs, included in error messages. -const changeIDFormat = "git://{host}/{repo-path}/{ref}/{commit_sha}" +const changeIDFormat = "git://{remote}/{repo}/{ref}/{commit_sha}" // shaLength is the length of a git commit SHA. const shaLength = 40 // ChangeID represents a parsed git:// change identifier. -// Format: git://{host}/{repo-path}/{ref}/{commit_sha} +// Format: git://{remote}/{repo}/{ref}/{commit_sha} // // Ref is a fully-qualified, percent-encoded git ref so that branches, tags, and // ref names containing slashes all fit a single path segment unambiguously. type ChangeID struct { // Scheme captures the URI scheme (always "git" in current implementation). Scheme string - // Host is the git remote authority, "host" or "host:port" (e.g. - // "git.example.com" or "git.example.com:9418"). Unlike a forge "org", git - // addresses a repository by the remote it lives on. - Host string - // RepoPath is the path to the repository on the remote and may contain - // slashes (e.g. "uber/monorepo" or "team/group/repo.git"). - RepoPath string + // Remote is the host (or host:port) of the git remote the repository lives + // on (e.g. "git.example.com" or "git.example.com:9418"). + Remote string + // Repo is the path to the repository on the remote and may contain slashes + // (e.g. "uber/monorepo" or "team/group/repo.git"). + Repo string // Ref is the fully-qualified git ref the change landed on, decoded from the // URI (e.g. "refs/heads/main", "refs/tags/v1.0"). Ref string - // CommitSHA is the commit Ref pointed at, captured at a point in time. It is - // not necessarily a branch head; it records the commit Ref resolved to when - // the change entered the pipeline. + // CommitSHA is a commit that ref has pointed to at some point in time. CommitSHA string } // ParseChangeID parses a raw change ID string into a ChangeID. -// Expected format: git://{host}/{repo-path}/{commit_sha} with a fully-qualified, -// percent-encoded ref as the second-to-last path segment. +// Expected format: git://{remote}/{repo}/{ref}/{commit_sha}, where {ref} is a +// fully-qualified, percent-encoded git ref (e.g. "refs%2Fheads%2Fmain"). func ParseChangeID(raw string) (ChangeID, error) { u, err := url.Parse(raw) if err != nil { @@ -73,20 +70,20 @@ func ParseChangeID(raw string) (ChangeID, error) { return ChangeID{}, fmt.Errorf("invalid change ID %q: scheme must be %q, got %q (expected format: %s)", raw, scheme, u.Scheme, changeIDFormat) } if u.Host == "" { - return ChangeID{}, fmt.Errorf("invalid change ID %q: missing remote host (expected format: %s)", raw, changeIDFormat) + return ChangeID{}, fmt.Errorf("invalid change ID %q: missing remote (expected format: %s)", raw, changeIDFormat) } // Split on the escaped path so the percent-encoded ref stays a single // segment (url.URL.Path decodes %2F to "/", which would split it apart). segments := strings.Split(strings.TrimPrefix(u.EscapedPath(), "/"), "/") - // Need at least 3 segments: {repo-path}/{ref}/{commit_sha}. + // Need at least 3 segments: {repo}/{ref}/{commit_sha}. if len(segments) < 3 { - return ChangeID{}, fmt.Errorf("invalid change ID %q: need at least repo-path/ref/sha, got %d path segments (expected format: %s)", raw, len(segments), changeIDFormat) + return ChangeID{}, fmt.Errorf("invalid change ID %q: need at least repo/ref/sha, got %d path segments (expected format: %s)", raw, len(segments), changeIDFormat) } sha := segments[len(segments)-1] encodedRef := segments[len(segments)-2] - repoPath := strings.Join(segments[:len(segments)-2], "/") + repo := strings.Join(segments[:len(segments)-2], "/") if sha == "" { return ChangeID{}, fmt.Errorf("invalid change ID %q: empty commit SHA (expected format: %s)", raw, changeIDFormat) @@ -103,14 +100,14 @@ func ParseChangeID(raw string) (ChangeID, error) { return ChangeID{}, fmt.Errorf("invalid change ID %q: ref %q must be a fully-qualified git ref (e.g. refs/heads/main, refs/tags/v1.0) (expected format: %s)", raw, ref, changeIDFormat) } - if repoPath == "" { - return ChangeID{}, fmt.Errorf("invalid change ID %q: empty repo path (expected format: %s)", raw, changeIDFormat) + if repo == "" { + return ChangeID{}, fmt.Errorf("invalid change ID %q: empty repo (expected format: %s)", raw, changeIDFormat) } return ChangeID{ Scheme: u.Scheme, - Host: u.Host, - RepoPath: repoPath, + Remote: u.Host, + Repo: repo, Ref: ref, CommitSHA: sha, }, nil @@ -118,5 +115,5 @@ func ParseChangeID(raw string) (ChangeID, error) { // String returns the string representation of the change ID. func (c ChangeID) String() string { - return fmt.Sprintf("%s://%s/%s/%s/%s", c.Scheme, c.Host, c.RepoPath, url.PathEscape(c.Ref), c.CommitSHA) + return fmt.Sprintf("%s://%s/%s/%s/%s", c.Scheme, c.Remote, c.Repo, url.PathEscape(c.Ref), c.CommitSHA) } diff --git a/entity/change/git/change_id_test.go b/entity/change/git/change_id_test.go index b1c4a187..f8bbdc76 100644 --- a/entity/change/git/change_id_test.go +++ b/entity/change/git/change_id_test.go @@ -35,8 +35,8 @@ func TestParseChangeID(t *testing.T) { raw: "git://git.example.com/uber/monorepo/refs%2Fheads%2Fmain/" + sha, want: ChangeID{ Scheme: "git", - Host: "git.example.com", - RepoPath: "uber/monorepo", + Remote: "git.example.com", + Repo: "uber/monorepo", Ref: "refs/heads/main", CommitSHA: sha, }, @@ -46,8 +46,8 @@ func TestParseChangeID(t *testing.T) { raw: "git://git.example.com:9418/uber/monorepo/refs%2Fheads%2Fmain/" + sha, want: ChangeID{ Scheme: "git", - Host: "git.example.com:9418", - RepoPath: "uber/monorepo", + Remote: "git.example.com:9418", + Repo: "uber/monorepo", Ref: "refs/heads/main", CommitSHA: sha, }, @@ -57,8 +57,8 @@ func TestParseChangeID(t *testing.T) { raw: "git://git.example.com/monorepo/refs%2Fheads%2Fmain/" + sha, want: ChangeID{ Scheme: "git", - Host: "git.example.com", - RepoPath: "monorepo", + Remote: "git.example.com", + Repo: "monorepo", Ref: "refs/heads/main", CommitSHA: sha, }, @@ -68,8 +68,8 @@ func TestParseChangeID(t *testing.T) { raw: "git://git.example.com/uber/monorepo/refs%2Fheads%2Ffeature%2Fx/" + sha, want: ChangeID{ Scheme: "git", - Host: "git.example.com", - RepoPath: "uber/monorepo", + Remote: "git.example.com", + Repo: "uber/monorepo", Ref: "refs/heads/feature/x", CommitSHA: sha, }, @@ -79,8 +79,8 @@ func TestParseChangeID(t *testing.T) { raw: "git://git.example.com/uber/monorepo/refs%2Ftags%2Fv1.0/" + sha, want: ChangeID{ Scheme: "git", - Host: "git.example.com", - RepoPath: "uber/monorepo", + Remote: "git.example.com", + Repo: "uber/monorepo", Ref: "refs/tags/v1.0", CommitSHA: sha, }, @@ -90,8 +90,8 @@ func TestParseChangeID(t *testing.T) { raw: "git://git.example.com/uber/deepteam/monorepo/refs%2Fheads%2Fmain/" + sha, want: ChangeID{ Scheme: "git", - Host: "git.example.com", - RepoPath: "uber/deepteam/monorepo", + Remote: "git.example.com", + Repo: "uber/deepteam/monorepo", Ref: "refs/heads/main", CommitSHA: sha, },