From 6cf09ccab402fe84a5313e1d3e755e284ebbc845 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 27 Dec 2022 21:12:49 +0800 Subject: [PATCH] Use complete SHA to create and query commit status (#22244) Fix #13485. Co-authored-by: delvh Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao --- models/activities/action.go | 2 +- models/git/commit_status.go | 4 ++++ modules/context/api.go | 2 +- modules/context/repo.go | 8 +++---- modules/git/repo_commit_gogit.go | 2 +- modules/git/repo_commit_nogogit.go | 2 +- modules/git/repo_index.go | 2 +- modules/git/repo_tree_gogit.go | 2 +- modules/git/repo_tree_nogogit.go | 2 +- modules/git/sha1.go | 5 ++++- routers/api/v1/repo/status.go | 1 + routers/api/v1/utils/git.go | 29 ++++++++++++++++++++++++++ routers/web/repo/commit.go | 2 +- services/pull/check.go | 12 +++++------ services/pull/merge.go | 2 +- services/pull/temp_repo.go | 2 +- services/repository/files/commit.go | 5 ++++- services/repository/files/tree.go | 2 +- tests/integration/repo_commits_test.go | 5 +++++ 19 files changed, 68 insertions(+), 23 deletions(-) diff --git a/models/activities/action.go b/models/activities/action.go index 1ac1be7135d8..4baedbfe124b 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -272,7 +272,7 @@ func (a *Action) GetRefLink() string { return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix)) case strings.HasPrefix(a.RefName, git.TagPrefix): return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix)) - case len(a.RefName) == 40 && git.IsValidSHAPattern(a.RefName): + case len(a.RefName) == git.SHAFullLength && git.IsValidSHAPattern(a.RefName): return a.GetRepoLink() + "/src/commit/" + a.RefName default: // FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here. diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 0fb0bc66af9a..07e0b9fb73b4 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -279,6 +279,10 @@ func NewCommitStatus(opts NewCommitStatusOptions) error { return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA) } + if _, err := git.NewIDFromString(opts.SHA); err != nil { + return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err) + } + ctx, committer, err := db.TxContext(db.DefaultContext) if err != nil { return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", opts.Repo.ID, opts.Creator.ID, opts.SHA, err) diff --git a/modules/context/api.go b/modules/context/api.go index f49997a7875f..1349f91ec0fb 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -387,7 +387,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { return } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if len(refName) == 40 { + } else if len(refName) == git.SHAFullLength { ctx.Repo.CommitID = refName ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) if err != nil { diff --git a/modules/context/repo.go b/modules/context/repo.go index 71a2b3c0c6ef..d15d28cab783 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -817,7 +817,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string { } // For legacy and API support only full commit sha parts := strings.Split(path, "/") - if len(parts) > 0 && len(parts[0]) == 40 { + if len(parts) > 0 && len(parts[0]) == git.SHAFullLength { ctx.Repo.TreePath = strings.Join(parts[1:], "/") return parts[0] } @@ -853,7 +853,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string { return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist) case RepoRefCommit: parts := strings.Split(path, "/") - if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= 40 { + if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength { ctx.Repo.TreePath = strings.Join(parts[1:], "/") return parts[0] } @@ -962,7 +962,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context return } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if len(refName) >= 7 && len(refName) <= 40 { + } else if len(refName) >= 7 && len(refName) <= git.SHAFullLength { ctx.Repo.IsViewCommit = true ctx.Repo.CommitID = refName @@ -972,7 +972,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context return } // If short commit ID add canonical link header - if len(refName) < 40 { + if len(refName) < git.SHAFullLength { ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1)))) } diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index b6c42a802f0d..72de158e6e11 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -41,7 +41,7 @@ func (repo *Repository) RemoveReference(name string) error { // ConvertToSHA1 returns a Hash object from a potential ID string func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { - if len(commitID) == 40 { + if len(commitID) == SHAFullLength { sha1, err := NewIDFromString(commitID) if err == nil { return sha1, nil diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index 35a705fea30f..7373d01c8efb 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -137,7 +137,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co // ConvertToSHA1 returns a Hash object from a potential ID string func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { - if len(commitID) == 40 && IsValidSHAPattern(commitID) { + if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) { sha1, err := NewIDFromString(commitID) if err == nil { return sha1, nil diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 99eb2b540b1a..5ff2a2e4fc9d 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -16,7 +16,7 @@ import ( // ReadTreeToIndex reads a treeish to the index func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error { - if len(treeish) != 40 { + if len(treeish) != SHAFullLength { res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return err diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index e0e5e73fa335..a7b1081b15f1 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -19,7 +19,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { // GetTree find the tree object in the repository. func (repo *Repository) GetTree(idStr string) (*Tree, error) { - if len(idStr) != 40 { + if len(idStr) != SHAFullLength { res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go index 16ea6bbd8a31..4fd77df2b824 100644 --- a/modules/git/repo_tree_nogogit.go +++ b/modules/git/repo_tree_nogogit.go @@ -66,7 +66,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { // GetTree find the tree object in the repository. func (repo *Repository) GetTree(idStr string) (*Tree, error) { - if len(idStr) != 40 { + if len(idStr) != SHAFullLength { res, err := repo.GetRefCommitID(idStr) if err != nil { return nil, err diff --git a/modules/git/sha1.go b/modules/git/sha1.go index 3a02484bc23b..4d69653e09ac 100644 --- a/modules/git/sha1.go +++ b/modules/git/sha1.go @@ -17,6 +17,9 @@ const EmptySHA = "0000000000000000000000000000000000000000" // EmptyTreeSHA is the SHA of an empty tree const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" +// SHAFullLength is the full length of a git SHA +const SHAFullLength = 40 + // SHAPattern can be used to determine if a string is an valid sha var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) @@ -50,7 +53,7 @@ func MustIDFromString(s string) SHA1 { func NewIDFromString(s string) (SHA1, error) { var id SHA1 s = strings.TrimSpace(s) - if len(s) != 40 { + if len(s) != SHAFullLength { return id, fmt.Errorf("Length must be 40: %s", s) } b, err := hex.DecodeString(s) diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index 0b196d162c27..3202722581ca 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -183,6 +183,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) { ctx.Error(http.StatusBadRequest, "ref/sha not given", nil) return } + sha = utils.MustConvertToSHA1(ctx.Context, sha) repo := ctx.Repo.Repository listOptions := utils.GetListOptions(ctx) diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go index 2801dee8babe..eaf0f5fd37fc 100644 --- a/routers/api/v1/utils/git.go +++ b/routers/api/v1/utils/git.go @@ -33,6 +33,8 @@ func ResolveRefOrSha(ctx *context.APIContext, ref string) string { } } + sha = MustConvertToSHA1(ctx.Context, sha) + if ctx.Repo.GitRepo != nil { err := ctx.Repo.GitRepo.AddLastCommitCache(ctx.Repo.Repository.GetCommitsCountCacheKey(ref, ref != sha), ctx.Repo.Repository.FullName(), sha) if err != nil { @@ -65,3 +67,30 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str } return "", "", nil } + +// ConvertToSHA1 returns a full-length SHA1 from a potential ID string +func ConvertToSHA1(ctx *context.Context, commitID string) (git.SHA1, error) { + if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) { + sha1, err := git.NewIDFromString(commitID) + if err == nil { + return sha1, nil + } + } + + gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, ctx.Repo.Repository.RepoPath()) + if err != nil { + return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err) + } + defer closer.Close() + + return gitRepo.ConvertToSHA1(commitID) +} + +// MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1 +func MustConvertToSHA1(ctx *context.Context, commitID string) string { + sha, err := ConvertToSHA1(ctx, commitID) + if err != nil { + return commitID + } + return sha.String() +} diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index efc216661cae..6eda3fca10b3 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -283,7 +283,7 @@ func Diff(ctx *context.Context) { } return } - if len(commitID) != 40 { + if len(commitID) != git.SHAFullLength { commitID = commit.ID.String() } diff --git a/services/pull/check.go b/services/pull/check.go index ed4b18107caf..86460cd49cad 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -199,19 +199,19 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com return nil, fmt.Errorf("ReadFile(%s): %w", headFile, err) } commitID := string(commitIDBytes) - if len(commitID) < 40 { + if len(commitID) < git.SHAFullLength { return nil, fmt.Errorf(`ReadFile(%s): invalid commit-ID "%s"`, headFile, commitID) } - cmd := commitID[:40] + ".." + pr.BaseBranch + cmd := commitID[:git.SHAFullLength] + ".." + pr.BaseBranch // Get the commit from BaseBranch where the pull request got merged mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse").AddDynamicArguments(cmd). RunStdString(&git.RunOpts{Dir: "", Env: []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}}) if err != nil { return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err) - } else if len(mergeCommit) < 40 { + } else if len(mergeCommit) < git.SHAFullLength { // PR was maybe fast-forwarded, so just use last commit of PR - mergeCommit = commitID[:40] + mergeCommit = commitID[:git.SHAFullLength] } gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) @@ -220,9 +220,9 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com } defer gitRepo.Close() - commit, err := gitRepo.GetCommit(mergeCommit[:40]) + commit, err := gitRepo.GetCommit(mergeCommit[:git.SHAFullLength]) if err != nil { - return nil, fmt.Errorf("GetMergeCommit[%v]: %w", mergeCommit[:40], err) + return nil, fmt.Errorf("GetMergeCommit[%v]: %w", mergeCommit[:git.SHAFullLength], err) } return commit, nil diff --git a/services/pull/merge.go b/services/pull/merge.go index 1c42c1c17b8b..41ba45c17762 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -839,7 +839,7 @@ func MergedManually(pr *issues_model.PullRequest, doer *user_model.User, baseGit return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} } - if len(commitID) < 40 { + if len(commitID) < git.SHAFullLength { return fmt.Errorf("Wrong commit ID") } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 842719467f36..d49a15cea00a 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -166,7 +166,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str var headBranch string if pr.Flow == issues_model.PullRequestFlowGithub { headBranch = git.BranchPrefix + pr.HeadBranch - } else if len(pr.HeadCommitID) == 40 { // for not created pull request + } else if len(pr.HeadCommitID) == git.SHAFullLength { // for not created pull request headBranch = pr.HeadCommitID } else { headBranch = pr.GetGitRefName() diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go index c49b03d60854..74f9eb868d00 100644 --- a/services/repository/files/commit.go +++ b/services/repository/files/commit.go @@ -29,9 +29,12 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato } defer closer.Close() - if _, err := gitRepo.GetCommit(sha); err != nil { + if commit, err := gitRepo.GetCommit(sha); err != nil { gitRepo.Close() return fmt.Errorf("GetCommit[%s]: %w", sha, err) + } else if len(sha) != git.SHAFullLength { + // use complete commit sha + sha = commit.ID.String() } gitRepo.Close() diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 1aa6d0df36de..f4304ea6306e 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -49,7 +49,7 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git copy(treeURL[apiURLLen:], "/git/trees/") // 40 is the size of the sha1 hash in hexadecimal format. - copyPos := len(treeURL) - 40 + copyPos := len(treeURL) - git.SHAFullLength if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage { perPage = setting.API.DefaultGitTreesPerPage diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 0d794cec757b..ab90e72877e5 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -68,6 +68,11 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { reqOne := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)+"/status") testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state) + // By short SHA + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)[:10]+"/statuses") + reqOne = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)[:10]+"/status") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state) + // By Ref req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/statuses") reqOne = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/status")