From 44bd4fc76bf428e094e78dd9fbc4c106982369fe Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Fri, 27 Oct 2023 13:43:33 +0200 Subject: [PATCH] Abstract hash function usage Refactor Hash interfaces and centralize hash function. This will allow easier introduction of different hash function later on. --- cmd/hook.go | 7 +- models/git/branch_test.go | 6 +- models/git/commit_status.go | 19 +- models/repo/repo.go | 7 +- modules/context/api.go | 2 +- modules/context/repo.go | 8 +- modules/git/batch_reader.go | 30 +-- modules/git/blame.go | 22 +- modules/git/blame_test.go | 4 +- modules/git/blob_gogit.go | 2 +- modules/git/blob_nogogit.go | 2 +- modules/git/commit.go | 17 +- modules/git/commit_convert_gogit.go | 4 +- modules/git/commit_info_gogit.go | 2 +- modules/git/commit_info_nogogit.go | 2 +- modules/git/commit_reader.go | 8 +- modules/git/commit_test.go | 6 +- modules/git/hash.go | 226 ++++++++++++++++++ modules/git/hash_gogit.go | 29 +++ modules/git/hash_test.go | 21 ++ modules/git/last_commit_cache.go | 6 +- modules/git/last_commit_cache_gogit.go | 3 +- modules/git/log_name_status.go | 13 +- modules/git/notes_gogit.go | 3 +- modules/git/parse_gogit.go | 16 +- modules/git/parse_gogit_test.go | 20 +- modules/git/parse_nogogit.go | 16 +- modules/git/parse_nogogit_test.go | 22 +- modules/git/pipeline/lfs.go | 14 +- modules/git/pipeline/lfs_nogogit.go | 18 +- modules/git/ref.go | 4 +- modules/git/repo.go | 31 ++- modules/git/repo_base_gogit.go | 3 + modules/git/repo_base_nogogit.go | 7 + modules/git/repo_blob.go | 2 +- modules/git/repo_blob_gogit.go | 4 +- modules/git/repo_blob_nogogit.go | 2 +- modules/git/repo_blob_test.go | 2 +- modules/git/repo_commit.go | 35 ++- modules/git/repo_commit_gogit.go | 35 +-- modules/git/repo_commit_nogogit.go | 25 +- modules/git/repo_compare.go | 2 +- modules/git/repo_compare_test.go | 6 +- modules/git/repo_gpg.go | 2 +- modules/git/repo_index.go | 14 +- modules/git/repo_language_stats_nogogit.go | 2 +- modules/git/repo_object.go | 42 +++- modules/git/repo_ref_gogit.go | 4 +- modules/git/repo_ref_nogogit.go | 2 +- modules/git/repo_tag.go | 14 +- modules/git/repo_tag_gogit.go | 12 +- modules/git/repo_tag_nogogit.go | 8 +- modules/git/repo_tag_test.go | 15 +- modules/git/repo_tree.go | 6 +- modules/git/repo_tree_gogit.go | 14 +- modules/git/repo_tree_nogogit.go | 10 +- modules/git/sha1.go | 72 ------ modules/git/sha1_gogit.go | 19 -- modules/git/sha1_nogogit.go | 61 ----- modules/git/sha1_test.go | 20 -- modules/git/tag.go | 10 +- modules/git/tag_test.go | 10 +- modules/git/tree.go | 2 +- modules/git/tree_blob_gogit.go | 2 +- modules/git/tree_entry_gogit.go | 4 +- modules/git/tree_entry_nogogit.go | 2 +- modules/git/tree_gogit.go | 10 +- modules/git/tree_nogogit.go | 10 +- modules/indexer/code/git.go | 17 +- modules/repository/commits_test.go | 7 +- modules/repository/generate.go | 6 +- modules/repository/init.go | 4 +- modules/repository/push.go | 6 +- routers/api/v1/repo/notes.go | 2 +- routers/api/v1/repo/repo.go | 1 + routers/api/v1/utils/git.go | 13 +- routers/private/hook_post_receive.go | 8 +- routers/private/hook_pre_receive.go | 6 +- routers/private/hook_verification.go | 4 +- routers/private/hook_verification_test.go | 4 +- routers/web/repo/blame.go | 9 +- routers/web/repo/branch.go | 9 +- routers/web/repo/commit.go | 2 +- routers/web/repo/compare.go | 2 +- routers/web/repo/githttp.go | 2 +- routers/web/repo/repo.go | 2 + routers/web/repo/setting/lfs.go | 7 +- routers/web/repo/setting/webhook.go | 8 +- services/actions/commit_status.go | 7 +- services/agit/agit.go | 4 +- services/convert/git_commit_test.go | 2 +- services/forms/repo_form.go | 1 + services/gitdiff/gitdiff.go | 12 +- services/migrations/common.go | 8 +- services/migrations/gitea_uploader.go | 2 +- services/migrations/gitea_uploader_test.go | 2 +- services/mirror/mirror_pull.go | 6 +- services/packages/cargo/index.go | 2 +- services/pull/check.go | 2 +- services/pull/merge.go | 2 +- services/pull/patch.go | 6 +- services/pull/pull.go | 3 +- services/pull/temp_repo.go | 10 +- services/release/release.go | 10 +- services/repository/archiver/archiver.go | 28 +-- services/repository/branch.go | 2 +- services/repository/check.go | 2 +- services/repository/create.go | 8 +- services/repository/files/cherry_pick.go | 5 +- services/repository/files/commit.go | 7 +- services/repository/files/patch.go | 2 +- services/repository/files/temp_repo.go | 4 +- services/repository/files/tree.go | 17 +- services/repository/files/update.go | 4 +- services/repository/files/upload.go | 2 +- services/repository/lfs.go | 2 +- services/repository/push.go | 21 +- services/webhook/slack.go | 1 + services/wiki/wiki.go | 2 +- services/wiki/wiki_test.go | 2 +- .../git_helper_for_declarative_test.go | 2 +- 121 files changed, 832 insertions(+), 557 deletions(-) create mode 100644 modules/git/hash.go create mode 100644 modules/git/hash_gogit.go create mode 100644 modules/git/hash_test.go delete mode 100644 modules/git/sha1.go delete mode 100644 modules/git/sha1_gogit.go delete mode 100644 modules/git/sha1_nogogit.go delete mode 100644 modules/git/sha1_test.go diff --git a/cmd/hook.go b/cmd/hook.go index f38fd8831b8d5..661093f36f2b8 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -376,7 +376,9 @@ Gitea or set your environment appropriately.`, "") oldCommitIDs[count] = string(fields[0]) newCommitIDs[count] = string(fields[1]) refFullNames[count] = git.RefName(fields[2]) - if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total { + + hash, _ := git.HashFromString(newCommitIDs[count]) + if refFullNames[count] == git.BranchPrefix+"master" && !hash.IsZero() && count == total { masterPushed = true } count++ @@ -669,7 +671,8 @@ Gitea or set your environment appropriately.`, "") if err != nil { return err } - if rs.OldOID != git.EmptySHA { + hash, _ := git.HashFromString(rs.OldOID) + if !hash.IsZero() { err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID)) if err != nil { return err diff --git a/models/git/branch_test.go b/models/git/branch_test.go index ba6902692792e..c22e91b4343dc 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -21,7 +21,9 @@ func TestAddDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) + hash, err := git.HashTypeFromString(repo.HashType) + assert.NoError(t, err) assert.True(t, firstBranch.IsDeleted) assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID)) assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1))) @@ -30,14 +32,14 @@ func TestAddDeletedBranch(t *testing.T) { assert.True(t, secondBranch.IsDeleted) commit := &git.Commit{ - ID: git.MustIDFromString(secondBranch.CommitID), + ID: hash.MustIDFromString(secondBranch.CommitID), CommitMessage: secondBranch.CommitMessage, Committer: &git.Signature{ When: secondBranch.CommitTime.AsLocalTime(), }, } - err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) + err = git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) assert.NoError(t, err) } diff --git a/models/git/commit_status.go b/models/git/commit_status.go index acb011020056e..87a8762cf6403 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -114,7 +114,8 @@ WHEN NOT MATCHED // GetNextCommitStatusIndex retried 3 times to generate a resource index func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) { - if !git.IsValidSHAPattern(sha) { + _, err := git.HashFromString(sha) + if err != nil { return 0, git.ErrInvalidSHA{SHA: sha} } @@ -423,33 +424,29 @@ func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, befor type NewCommitStatusOptions struct { Repo *repo_model.Repository Creator *user_model.User - SHA string + SHA git.Hash CommitStatus *CommitStatus } // NewCommitStatus save commit statuses into database func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error { if opts.Repo == nil { - return fmt.Errorf("NewCommitStatus[nil, %s]: no repository specified", opts.SHA) + return fmt.Errorf("NewCommitStatus[nil, %s]: no repository specified", opts.SHA.String()) } repoPath := opts.Repo.RepoPath() if opts.Creator == nil { - 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) + return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA.String()) } ctx, committer, err := db.TxContext(ctx) 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) + return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", opts.Repo.ID, opts.Creator.ID, opts.SHA.String(), err) } defer committer.Close() // Get the next Status Index - idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA) + idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA.String()) if err != nil { return fmt.Errorf("generate commit status index failed: %w", err) } @@ -457,7 +454,7 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error { opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description) opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context) opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL) - opts.CommitStatus.SHA = opts.SHA + opts.CommitStatus.SHA = opts.SHA.String() opts.CommitStatus.CreatorID = opts.Creator.ID opts.CommitStatus.RepoID = opts.Repo.ID opts.CommitStatus.Index = idx diff --git a/models/repo/repo.go b/models/repo/repo.go index c4b215e074ecd..ee0696919bfc2 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" @@ -179,6 +180,7 @@ type Repository struct { IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` Topics []string `xorm:"TEXT JSON"` + HashType string TrustModel TrustModelType @@ -274,6 +276,8 @@ func (repo *Repository) AfterLoad() { repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns + + repo.HashType = "sha1" } // LoadAttributes loads attributes of the repository. @@ -313,7 +317,8 @@ func (repo *Repository) HTMLURL() string { // CommitLink make link to by commit full ID // note: won't check whether it's an right id func (repo *Repository) CommitLink(commitID string) (result string) { - if commitID == "" || commitID == "0000000000000000000000000000000000000000" { + hash, _ := git.HashTypeFromString(repo.HashType) + if commitID == "" || commitID == hash.Empty().String() { result = "" } else { result = repo.Link() + "/commit/" + url.PathEscape(commitID) diff --git a/modules/context/api.go b/modules/context/api.go index ba35adf8310d7..d1807df080276 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -342,7 +342,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { return } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if len(refName) == git.SHAFullLength { + } else if len(refName) == ctx.Repo.GitRepo.Hash.FullLength() { 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 f91dd6aa9a83e..332bd3ce9cd57 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -827,7 +827,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { } // For legacy and API support only full commit sha parts := strings.Split(path, "/") - if len(parts) > 0 && len(parts[0]) == git.SHAFullLength { + if len(parts) > 0 && len(parts[0]) == repo.GitRepo.Hash.FullLength() { repo.TreePath = strings.Join(parts[1:], "/") return parts[0] } @@ -871,7 +871,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist) case RepoRefCommit: parts := strings.Split(path, "/") - if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength { + if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= repo.GitRepo.Hash.FullLength() { repo.TreePath = strings.Join(parts[1:], "/") return parts[0] } @@ -997,7 +997,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context return cancel } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if len(refName) >= 7 && len(refName) <= git.SHAFullLength { + } else if len(refName) >= 7 && len(refName) <= ctx.Repo.GitRepo.Hash.FullLength() { ctx.Repo.IsViewCommit = true ctx.Repo.CommitID = refName @@ -1007,7 +1007,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context return cancel } // If short commit ID add canonical link header - if len(refName) < git.SHAFullLength { + if len(refName) < ctx.Repo.GitRepo.Hash.FullLength() { 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/batch_reader.go b/modules/git/batch_reader.go index 7a44e6295c4e2..d505b1cb5cf47 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -148,7 +148,7 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi // ReadBatchLine reads the header line from cat-file --batch // We expect: // SP SP LF -// sha is a 40byte not 20byte here +// sha is a hex encoded here func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) { typ, err = rd.ReadString('\n') if err != nil { @@ -251,20 +251,19 @@ headerLoop: } // git tree files are a list: -// SP NUL <20-byte SHA> +// SP NUL // // Unfortunately this 20-byte notation is somewhat in conflict to all other git tools -// Therefore we need some method to convert these 20-byte SHAs to a 40-byte SHA +// Therefore we need some method to convert these binary hashes to hex hashes -// constant hextable to help quickly convert between 20byte and 40byte hashes +// constant hextable to help quickly convert between binary and hex representation const hextable = "0123456789abcdef" -// To40ByteSHA converts a 20-byte SHA into a 40-byte sha. Input and output can be the -// same 40 byte slice to support in place conversion without allocations. +// BinToHexHeash converts a binary Hash into a hex encoded one. Input and output can be the +// same byte slice to support in place conversion without allocations. // This is at least 100x quicker that hex.EncodeToString -// NB This requires that out is a 40-byte slice -func To40ByteSHA(sha, out []byte) []byte { - for i := 19; i >= 0; i-- { +func BinToHexHash(hash HashType, sha, out []byte) []byte { + for i := hash.FullLength()/2 - 1; i >= 0; i-- { v := sha[i] vhi, vlo := v>>4, v&0x0f shi, slo := hextable[vhi], hextable[vlo] @@ -278,10 +277,10 @@ func To40ByteSHA(sha, out []byte) []byte { // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations // // Each line is composed of: -// SP NUL <20-byte SHA> +// SP NUL // -// We don't attempt to convert the 20-byte SHA to 40-byte SHA to save a lot of time -func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { +// We don't attempt to convert the raw HASH to save a lot of time +func ParseTreeLine(hash HashType, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { var readBytes []byte // Read the Mode & fname @@ -324,11 +323,12 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn fnameBuf = fnameBuf[:len(fnameBuf)-1] fname = fnameBuf - // Deal with the 20-byte SHA + // Deal with the binary hash idx = 0 - for idx < 20 { + hashLen := hash.FullLength() / 2 + for idx < hashLen { var read int - read, err = rd.Read(shaBuf[idx:20]) + read, err = rd.Read(shaBuf[idx:hashLen]) n += read if err != nil { return mode, fname, sha, n, err diff --git a/modules/git/blame.go b/modules/git/blame.go index 6728a6bed85f1..d7389204617c7 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "os" - "regexp" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" @@ -30,14 +29,13 @@ type BlameReader struct { done chan error lastSha *string ignoreRevsFile *string + hash HashType } func (r *BlameReader) UsesIgnoreRevs() bool { return r.ignoreRevsFile != nil } -var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})") - // NextPart returns next part of blame (sequential code lines with the same commit) func (r *BlameReader) NextPart() (*BlamePart, error) { var blamePart *BlamePart @@ -61,16 +59,19 @@ func (r *BlameReader) NextPart() (*BlamePart, error) { continue } - lines := shaLineRegex.FindSubmatch(line) - if lines != nil { - sha1 := string(lines[1]) + var hash string + hashLen := r.hash.FullLength() + if len(line) > hashLen && line[hashLen] == ' ' && r.hash.IsValid(string(line[0:hashLen])) { + hash = string(line[0:hashLen]) + } + if len(hash) > 0 { if blamePart == nil { - blamePart = &BlamePart{sha1, make([]string, 0)} + blamePart = &BlamePart{hash, make([]string, 0)} } - if blamePart.Sha != sha1 { - r.lastSha = &sha1 + if blamePart.Sha != hash { + r.lastSha = &hash // need to munch to end of line... for isPrefix { _, isPrefix, err = r.bufferedReader.ReadLine() @@ -113,7 +114,7 @@ func (r *BlameReader) Close() error { } // CreateBlameReader creates reader for given repository, commit and file -func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { +func CreateBlameReader(ctx context.Context, hash HashType, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { var ignoreRevsFile *string if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) @@ -162,6 +163,7 @@ func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, fil bufferedReader: bufferedReader, done: done, ignoreRevsFile: ignoreRevsFile, + hash: hash, }, nil } diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 013350ac2f4eb..a01371cc951c6 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -37,7 +37,7 @@ func TestReadingBlameOutput(t *testing.T) { } for _, bypass := range []bool{false, true} { - blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo5_pulls", commit, "README.md", bypass) + blameReader, err := CreateBlameReader(ctx, &Sha1HashType{}, "./tests/repos/repo5_pulls", commit, "README.md", bypass) assert.NoError(t, err) assert.NotNil(t, blameReader) defer blameReader.Close() @@ -118,7 +118,7 @@ func TestReadingBlameOutput(t *testing.T) { commit, err := repo.GetCommit(c.CommitID) assert.NoError(t, err) - blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass) + blameReader, err := CreateBlameReader(ctx, repo.Hash, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass) assert.NoError(t, err) assert.NotNil(t, blameReader) defer blameReader.Close() diff --git a/modules/git/blob_gogit.go b/modules/git/blob_gogit.go index aa206409d0b6f..eb2d6043974ad 100644 --- a/modules/git/blob_gogit.go +++ b/modules/git/blob_gogit.go @@ -14,7 +14,7 @@ import ( // Blob represents a Git object. type Blob struct { - ID SHA1 + ID Hash gogitEncodedObj plumbing.EncodedObject name string diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go index 511332eb50641..2a5df7708eb9d 100644 --- a/modules/git/blob_nogogit.go +++ b/modules/git/blob_nogogit.go @@ -16,7 +16,7 @@ import ( // Blob represents a Git object. type Blob struct { - ID SHA1 + ID Hash gotSize bool size int64 diff --git a/modules/git/commit.go b/modules/git/commit.go index b09be25ba0997..794e09a6625df 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -21,13 +21,13 @@ import ( // Commit represents a git commit. type Commit struct { Tree - ID SHA1 // The ID of this commit object + ID Hash // The ID of this commit object Author *Signature Committer *Signature CommitMessage string Signature *CommitGPGSignature - Parents []SHA1 // SHA1 strings + Parents []Hash // ID strings submoduleCache *ObjectCache } @@ -49,9 +49,9 @@ func (c *Commit) Summary() string { // ParentID returns oid of n-th parent (0-based index). // It returns nil if no such parent exists. -func (c *Commit) ParentID(n int) (SHA1, error) { +func (c *Commit) ParentID(n int) (Hash, error) { if n >= len(c.Parents) { - return SHA1{}, ErrNotExist{"", ""} + return nil, ErrNotExist{"", ""} } return c.Parents[n], nil } @@ -208,7 +208,7 @@ func (c *Commit) CommitsBefore() ([]*Commit, error) { } // HasPreviousCommit returns true if a given commitHash is contained in commit's parents -func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { +func (c *Commit) HasPreviousCommit(commitHash Hash) (bool, error) { this := c.ID.String() that := commitHash.String() @@ -231,9 +231,14 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { // IsForcePush returns true if a push from oldCommitHash to this is a force push func (c *Commit) IsForcePush(oldCommitID string) (bool, error) { - if oldCommitID == EmptySHA { + h, err := c.repo.HashTypeInterface() + if err != nil { + return false, err + } + if oldCommitID == h.Empty().String() { return false, nil } + oldCommit, err := c.repo.GetCommit(oldCommitID) if err != nil { return false, err diff --git a/modules/git/commit_convert_gogit.go b/modules/git/commit_convert_gogit.go index 669f1b15d5389..819ea0d1dba3e 100644 --- a/modules/git/commit_convert_gogit.go +++ b/modules/git/commit_convert_gogit.go @@ -59,11 +59,11 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature { func convertCommit(c *object.Commit) *Commit { return &Commit{ - ID: c.Hash, + ID: ParseGogitHash(c.Hash), CommitMessage: c.Message, Committer: &c.Committer, Author: &c.Author, Signature: convertPGPSignature(c), - Parents: c.ParentHashes, + Parents: ParseGogitHashArray(c.ParentHashes), } } diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index c61d27993cd78..31ffc9aec1c38 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -29,7 +29,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath defer commitGraphFile.Close() } - c, err := commitNodeIndex.Get(commit.ID) + c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue())) if err != nil { return nil, nil, err } diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index e469d2cab6374..8cf8200c3f741 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -153,7 +153,7 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, if typ != "commit" { return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) } - c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size)) + c, err = CommitFromReader(commit.repo, commit.ID.Type().MustIDFromString(commitID), io.LimitReader(batchReader, size)) if err != nil { return nil, err } diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go index 23607c43abe25..2f60b2818c617 100644 --- a/modules/git/commit_reader.go +++ b/modules/git/commit_reader.go @@ -14,9 +14,9 @@ import ( // We need this to interpret commits from cat-file or cat-file --batch // // If used as part of a cat-file --batch stream you need to limit the reader to the correct size -func CommitFromReader(gitRepo *Repository, sha SHA1, reader io.Reader) (*Commit, error) { +func CommitFromReader(gitRepo *Repository, hash Hash, reader io.Reader) (*Commit, error) { commit := &Commit{ - ID: sha, + ID: hash, Author: &Signature{}, Committer: &Signature{}, } @@ -71,10 +71,10 @@ readLoop: switch string(split[0]) { case "tree": - commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data))) + commit.Tree = *NewTree(gitRepo, hash.Type().MustIDFromString(string(data))) _, _ = payloadSB.Write(line) case "parent": - commit.Parents = append(commit.Parents, MustIDFromString(string(data))) + commit.Parents = append(commit.Parents, hash.Type().MustIDFromString(string(data))) _, _ = payloadSB.Write(line) case "author": commit.Author = &Signature{} diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index ac586fdf09d3e..b34bd11edbe99 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -81,7 +81,7 @@ gpgsig -----BEGIN PGP SIGNATURE----- empty commit` - sha := SHA1{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2} + sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2} gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) assert.NoError(t, err) assert.NotNil(t, gitRepo) @@ -135,8 +135,8 @@ func TestHasPreviousCommit(t *testing.T) { commit, err := repo.GetCommit("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0") assert.NoError(t, err) - parentSHA := MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2") - notParentSHA := MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3") + parentSHA := repo.Hash.MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2") + notParentSHA := repo.Hash.MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3") haz, err := commit.HasPreviousCommit(parentSHA) assert.NoError(t, err) diff --git a/modules/git/hash.go b/modules/git/hash.go new file mode 100644 index 0000000000000..f5665e1489499 --- /dev/null +++ b/modules/git/hash.go @@ -0,0 +1,226 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "bytes" + "crypto/sha1" + "encoding/hex" + "errors" + "fmt" + "hash" + "regexp" + "strconv" + "strings" +) + +type HashID int + +const ( + Sha1 HashID = iota +) + +// sha1Pattern can be used to determine if a string is an valid sha +var sha1Pattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) + +type Hash interface { + String() string + IsZero() bool + RawValue() []byte + Type() HashType +} + +type HashType interface { + ID() HashID + String() string + + // Empty is the hash of empty git + Empty() Hash + // EmptyTree is the hash of an empty tree + EmptyTree() Hash + // FullLength is the length of the hash's hex string + FullLength() int + + IsValid(input string) bool + MustID(b []byte) Hash + MustIDFromString(s string) Hash + NewID(b []byte) (Hash, error) + NewIDFromString(s string) (Hash, error) + NewEmptyID() Hash + + NewHasher() HasherInterface +} + +/* SHA1 */ +type Sha1Hash [20]byte + +func (h *Sha1Hash) String() string { + return hex.EncodeToString(h[:]) +} + +func (h *Sha1Hash) IsZero() bool { + empty := Sha1Hash{} + return bytes.Equal(empty[:], h[:]) +} +func (h *Sha1Hash) RawValue() []byte { return h[:] } +func (*Sha1Hash) Type() HashType { return &Sha1HashType{} } + +/* SHA1 Type */ +type Sha1HashType struct{} + +func (*Sha1HashType) ID() HashID { return Sha1 } +func (*Sha1HashType) String() string { return "sha1" } +func (*Sha1HashType) Empty() Hash { return &Sha1Hash{} } +func (*Sha1HashType) EmptyTree() Hash { + return &Sha1Hash{ + 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, + 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04, + } +} +func (*Sha1HashType) FullLength() int { return 40 } +func (*Sha1HashType) IsValid(input string) bool { + return sha1Pattern.MatchString(input) +} + +func (*Sha1HashType) MustID(b []byte) Hash { + var id Sha1Hash + copy(id[0:20], b) + return &id +} + +func (h *Sha1HashType) MustIDFromString(s string) Hash { + return hashMustFromString(h, s) +} + +func (h *Sha1HashType) NewID(b []byte) (Hash, error) { + return hashFromRaw(h, b) +} + +func (h *Sha1HashType) NewIDFromString(s string) (Hash, error) { + return hashFromString(h, s) +} + +func (*Sha1HashType) NewEmptyID() Hash { + return NewSha1() +} + +func (h *Sha1HashType) NewHasher() HasherInterface { + return &Sha1Hasher{sha1.New()} +} + +func NewSha1() *Sha1Hash { + return &Sha1Hash{} +} + +// generic implementations +func NewHash(hash string) (Hash, error) { + hash = strings.ToLower(hash) + switch hash { + case "sha1": + return &Sha1Hash{}, nil + } + + return nil, errors.New("unsupported hash type") +} + +func hashFromRaw(h HashType, b []byte) (Hash, error) { + if len(b) != h.FullLength()/2 { + return h.Empty(), fmt.Errorf("length must be %d: %v", h.FullLength(), b) + } + return h.MustID(b), nil +} + +func hashMustFromString(h HashType, s string) Hash { + b, _ := hex.DecodeString(s) + return h.MustID(b) +} + +func hashFromString(h HashType, s string) (Hash, error) { + s = strings.TrimSpace(s) + if len(s) != h.FullLength() { + return h.Empty(), fmt.Errorf("length must be %d: %s", h.FullLength(), s) + } + b, err := hex.DecodeString(s) + if err != nil { + return h.Empty(), err + } + return h.NewID(b) +} + +// utils +func HashTypeFromID(id HashID) HashType { + switch id { + case Sha1: + return &Sha1HashType{} + } + + return nil +} + +func HashTypeFromString(hash string) (HashType, error) { + switch strings.ToLower(hash) { + case "sha1": + return &Sha1HashType{}, nil + } + + return nil, fmt.Errorf("unknown hash type: %s", hash) +} + +func HashFromString(hexHash string) (Hash, error) { + switch len(hexHash) { + case 40: + hashType := Sha1HashType{} + h, err := hashType.NewIDFromString(hexHash) + if err != nil { + return nil, err + } + return h, nil + } + + return nil, fmt.Errorf("invalid hash hex string: '%s' len: %d", hexHash, len(hexHash)) +} + +// HashInterface is a struct that will generate a Hash +type HasherInterface interface { + hash.Hash + + HashSum() Hash +} + +type Sha1Hasher struct { + hash.Hash +} +type Sha256Hasher struct { + hash.Hash +} + +// ComputeBlobHash compute the hash for a given blob content +func ComputeBlobHash(hashType HashType, content []byte) Hash { + return ComputeHash(hashType, ObjectBlob, content) +} + +// ComputeHash compute the hash for a given ObjectType and content +func ComputeHash(hashType HashType, t ObjectType, content []byte) Hash { + h := hashType.NewHasher() + _, _ = h.Write(t.Bytes()) + _, _ = h.Write([]byte(" ")) + _, _ = h.Write([]byte(strconv.FormatInt(int64(len(content)), 10))) + _, _ = h.Write([]byte{0}) + return h.HashSum() +} + +// Sum generates a SHA1 for the provided hash +func (h *Sha1Hasher) HashSum() Hash { + var sha1 Sha1Hash + copy(sha1[:], h.Hash.Sum(nil)) + return &sha1 +} + +type ErrInvalidSHA struct { + SHA string +} + +func (err ErrInvalidSHA) Error() string { + return fmt.Sprintf("invalid sha: %s", err.SHA) +} diff --git a/modules/git/hash_gogit.go b/modules/git/hash_gogit.go new file mode 100644 index 0000000000000..2bbe1b7f5417e --- /dev/null +++ b/modules/git/hash_gogit.go @@ -0,0 +1,29 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT +//go:build gogit + +package git + +import ( + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" +) + +func ParseGogitHash(h plumbing.Hash) Hash { + switch hash.Size { + case 20: + return HashTypeFromID(Sha1).MustID(h[:]) + } + + return nil +} + +func ParseGogitHashArray(hashes []plumbing.Hash) []Hash { + ret := make([]Hash, len(hashes)) + for i, h := range hashes { + ret[i] = ParseGogitHash(h) + } + + return ret +} + diff --git a/modules/git/hash_test.go b/modules/git/hash_test.go new file mode 100644 index 0000000000000..c78a215755396 --- /dev/null +++ b/modules/git/hash_test.go @@ -0,0 +1,21 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsValidSHAPattern(t *testing.T) { + h := NewSha1().Type() + assert.True(t, h.IsValid("fee1")) + assert.True(t, h.IsValid("abc000")) + assert.True(t, h.IsValid("9023902390239023902390239023902390239023")) + assert.False(t, h.IsValid("90239023902390239023902390239023902390239023")) + assert.False(t, h.IsValid("abc")) + assert.False(t, h.IsValid("123g")) + assert.False(t, h.IsValid("some random text")) +} diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go index 20bc79608557a..c73319944499c 100644 --- a/modules/git/last_commit_cache.go +++ b/modules/git/last_commit_cache.go @@ -92,17 +92,17 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) { // GetCommitByPath gets the last commit for the entry in the provided commit func (c *LastCommitCache) GetCommitByPath(commitID, entryPath string) (*Commit, error) { - sha1, err := NewIDFromString(commitID) + sha, err := c.repo.Hash.NewIDFromString(commitID) if err != nil { return nil, err } - lastCommit, err := c.Get(sha1.String(), entryPath) + lastCommit, err := c.Get(sha.String(), entryPath) if err != nil || lastCommit != nil { return lastCommit, err } - lastCommit, err = c.repo.getCommitByPathWithID(sha1, entryPath) + lastCommit, err = c.repo.getCommitByPathWithID(sha, entryPath) if err != nil { return nil, err } diff --git a/modules/git/last_commit_cache_gogit.go b/modules/git/last_commit_cache_gogit.go index 90e609fc81c7b..3afc213094225 100644 --- a/modules/git/last_commit_cache_gogit.go +++ b/modules/git/last_commit_cache_gogit.go @@ -8,6 +8,7 @@ package git import ( "context" + "github.com/go-git/go-git/v5/plumbing" cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" ) @@ -18,7 +19,7 @@ func (c *Commit) CacheCommit(ctx context.Context) error { } commitNodeIndex, _ := c.repo.CommitNodeIndex() - index, err := commitNodeIndex.Get(c.ID) + index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue())) if err != nil { return err } diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 7519e32b90027..26a0d28098a82 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -143,17 +143,20 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int } // Our "line" must look like: SP ( SP) * NUL - ret.CommitID = string(g.next[0:40]) - parents := string(g.next[41:]) + commitIds := string(g.next) if g.buffull { more, err := g.rd.ReadString('\x00') if err != nil { return nil, err } - parents += more + commitIds += more + } + commitIds = commitIds[:len(commitIds)-1] + splitIds := strings.Split(commitIds, " ") + ret.CommitID = splitIds[0] + if len(splitIds) > 1 { + ret.ParentIDs = splitIds[1:] } - parents = parents[:len(parents)-1] - ret.ParentIDs = strings.Split(parents, " ") // now read the next "line" g.buffull = false diff --git a/modules/git/notes_gogit.go b/modules/git/notes_gogit.go index c2297d89703ea..f802443b00e5b 100644 --- a/modules/git/notes_gogit.go +++ b/modules/git/notes_gogit.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/log" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) @@ -72,7 +73,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) defer commitGraphFile.Close() } - commitNode, err := commitNodeIndex.Get(notes.ID) + commitNode, err := commitNodeIndex.Get(plumbing.Hash(notes.ID.RawValue())) if err != nil { return err } diff --git a/modules/git/parse_gogit.go b/modules/git/parse_gogit.go index 226ef5df7319f..ba491acd6a60f 100644 --- a/modules/git/parse_gogit.go +++ b/modules/git/parse_gogit.go @@ -11,12 +11,14 @@ import ( "strconv" "strings" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/object" ) // ParseTreeEntries parses the output of a `git ls-tree -l` command. -func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { +func ParseTreeEntries(h HashType, data []byte) ([]*TreeEntry, error) { return parseTreeEntries(data, nil) } @@ -50,15 +52,16 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6])) } - if pos+40 > len(data) { + // in hex format, not byte format .... + if pos+hash.Size*2 > len(data) { return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) } - id, err := NewIDFromString(string(data[pos : pos+40])) + var err error + entry.ID, err = HashFromString(string(data[pos : pos+hash.Size*2])) if err != nil { - return nil, fmt.Errorf("Invalid ls-tree output: %w", err) + return nil, fmt.Errorf("invalid ls-tree hash: %s", string(data)) } - entry.ID = id - entry.gogitTreeEntry.Hash = id + entry.gogitTreeEntry.Hash = plumbing.Hash(entry.ID.RawValue()) pos += 41 // skip over sha and trailing space end := pos + bytes.IndexByte(data[pos:], '\t') @@ -77,6 +80,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { // In case entry name is surrounded by double quotes(it happens only in git-shell). if data[pos] == '"' { + var err error entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end])) if err != nil { return nil, fmt.Errorf("Invalid ls-tree output: %w", err) diff --git a/modules/git/parse_gogit_test.go b/modules/git/parse_gogit_test.go index f6e32401e66eb..2c5a96b6969b9 100644 --- a/modules/git/parse_gogit_test.go +++ b/modules/git/parse_gogit_test.go @@ -6,8 +6,10 @@ package git import ( + "fmt" "testing" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" "github.com/stretchr/testify/assert" @@ -26,9 +28,9 @@ func TestParseTreeEntries(t *testing.T) { Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n", Expected: []*TreeEntry{ { - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + ID: HashTypeFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), gogitTreeEntry: &object.TreeEntry{ - Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + Hash: plumbing.Hash(HashTypeFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()), Name: "example/file2.txt", Mode: filemode.Regular, }, @@ -42,9 +44,9 @@ func TestParseTreeEntries(t *testing.T) { "040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n", Expected: []*TreeEntry{ { - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + ID: HashTypeFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), gogitTreeEntry: &object.TreeEntry{ - Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), + Hash: plumbing.Hash(HashTypeFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()), Name: "example/\n.txt", Mode: filemode.Symlink, }, @@ -52,10 +54,10 @@ func TestParseTreeEntries(t *testing.T) { sized: true, }, { - ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), + ID: HashTypeFromID(Sha1).MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), sized: true, gogitTreeEntry: &object.TreeEntry{ - Hash: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), + Hash: plumbing.Hash(HashTypeFromID(Sha1).MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8").RawValue()), Name: "example", Mode: filemode.Dir, }, @@ -65,8 +67,12 @@ func TestParseTreeEntries(t *testing.T) { } for _, testCase := range testCases { - entries, err := ParseTreeEntries([]byte(testCase.Input)) + entries, err := ParseTreeEntries(HashTypeFromID(Sha1), []byte(testCase.Input)) assert.NoError(t, err) + if len(entries) > 1 { + fmt.Println(testCase.Expected[0].ID) + fmt.Println(entries[0].ID) + } assert.EqualValues(t, testCase.Expected, entries) } } diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_nogogit.go index 8b94c69200c01..6682794750f25 100644 --- a/modules/git/parse_nogogit.go +++ b/modules/git/parse_nogogit.go @@ -17,13 +17,13 @@ import ( ) // ParseTreeEntries parses the output of a `git ls-tree -l` command. -func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { - return parseTreeEntries(data, nil) +func ParseTreeEntries(hashType HashType, data []byte) ([]*TreeEntry, error) { + return parseTreeEntries(hashType, data, nil) } var sepSpace = []byte{' '} -func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { +func parseTreeEntries(hashType HashType, data []byte, ptree *Tree) ([]*TreeEntry, error) { var err error entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) for pos := 0; pos < len(data); { @@ -72,7 +72,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { return nil, fmt.Errorf("unknown type: %v", string(entryMode)) } - entry.ID, err = NewIDFromString(string(entryObjectID)) + entry.ID, err = hashType.NewIDFromString(string(entryObjectID)) if err != nil { return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) } @@ -92,15 +92,15 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { return entries, nil } -func catBatchParseTreeEntries(ptree *Tree, rd *bufio.Reader, sz int64) ([]*TreeEntry, error) { +func catBatchParseTreeEntries(hashType HashType, ptree *Tree, rd *bufio.Reader, sz int64) ([]*TreeEntry, error) { fnameBuf := make([]byte, 4096) modeBuf := make([]byte, 40) - shaBuf := make([]byte, 40) + shaBuf := make([]byte, hashType.FullLength()) entries := make([]*TreeEntry, 0, 10) loop: for sz > 0 { - mode, fname, sha, count, err := ParseTreeLine(rd, modeBuf, fnameBuf, shaBuf) + mode, fname, sha, count, err := ParseTreeLine(hashType, rd, modeBuf, fnameBuf, shaBuf) if err != nil { if err == io.EOF { break loop @@ -127,7 +127,7 @@ loop: return nil, fmt.Errorf("unknown mode: %v", string(mode)) } - entry.ID = MustID(sha) + entry.ID = hashType.MustID(sha) entry.name = string(fname) entries = append(entries, entry) } diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_nogogit_test.go index 23fddb014c1bf..fab2b5efb1210 100644 --- a/modules/git/parse_nogogit_test.go +++ b/modules/git/parse_nogogit_test.go @@ -12,6 +12,8 @@ import ( ) func TestParseTreeEntriesLong(t *testing.T) { + hashType := HashTypeFromID(Sha1) + testCases := []struct { Input string Expected []*TreeEntry @@ -24,28 +26,28 @@ func TestParseTreeEntriesLong(t *testing.T) { `, Expected: []*TreeEntry{ { - ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"), + ID: hashType.MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"), name: "README.md", entryMode: EntryModeBlob, size: 8218, sized: true, }, { - ID: MustIDFromString("037f27dc9d353ae4fd50f0474b2194c593914e35"), + ID: hashType.MustIDFromString("037f27dc9d353ae4fd50f0474b2194c593914e35"), name: "README_ZH.md", entryMode: EntryModeBlob, size: 4681, sized: true, }, { - ID: MustIDFromString("9846a94f7e8350a916632929d0fda38c90dd2ca8"), + ID: hashType.MustIDFromString("9846a94f7e8350a916632929d0fda38c90dd2ca8"), name: "SECURITY.md", entryMode: EntryModeBlob, size: 429, sized: true, }, { - ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"), + ID: hashType.MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"), name: "assets", entryMode: EntryModeTree, sized: true, @@ -54,7 +56,7 @@ func TestParseTreeEntriesLong(t *testing.T) { }, } for _, testCase := range testCases { - entries, err := ParseTreeEntries([]byte(testCase.Input)) + entries, err := ParseTreeEntries(hashType, []byte(testCase.Input)) assert.NoError(t, err) assert.Len(t, entries, len(testCase.Expected)) for i, entry := range entries { @@ -64,6 +66,8 @@ func TestParseTreeEntriesLong(t *testing.T) { } func TestParseTreeEntriesShort(t *testing.T) { + hashType := HashTypeFromID(Sha1) + testCases := []struct { Input string Expected []*TreeEntry @@ -74,12 +78,12 @@ func TestParseTreeEntriesShort(t *testing.T) { `, Expected: []*TreeEntry{ { - ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"), + ID: hashType.MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"), name: "README.md", entryMode: EntryModeBlob, }, { - ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"), + ID: hashType.MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"), name: "assets", entryMode: EntryModeTree, }, @@ -87,7 +91,7 @@ func TestParseTreeEntriesShort(t *testing.T) { }, } for _, testCase := range testCases { - entries, err := ParseTreeEntries([]byte(testCase.Input)) + entries, err := ParseTreeEntries(hashType, []byte(testCase.Input)) assert.NoError(t, err) assert.Len(t, entries, len(testCase.Expected)) for i, entry := range entries { @@ -98,7 +102,7 @@ func TestParseTreeEntriesShort(t *testing.T) { func TestParseTreeEntriesInvalid(t *testing.T) { // there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315 - entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af")) + entries, err := ParseTreeEntries(HashTypeFromID(Sha1), []byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af")) assert.Error(t, err) assert.Len(t, entries, 0) } diff --git a/modules/git/pipeline/lfs.go b/modules/git/pipeline/lfs.go index ee0505f29f909..6f56037223cf2 100644 --- a/modules/git/pipeline/lfs.go +++ b/modules/git/pipeline/lfs.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/git" gogit "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) @@ -26,7 +27,7 @@ type LFSResult struct { SHA string Summary string When time.Time - ParentHashes []git.SHA1 + ParentHashes []git.Hash BranchName string FullCommitName string } @@ -38,7 +39,7 @@ func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } // FindLFSFile finds commits that contain a provided pointer file hash -func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { +func FindLFSFile(repo *git.Repository, hash git.Hash) ([]*LFSResult, error) { resultsMap := map[string]*LFSResult{} results := make([]*LFSResult, 0) @@ -65,13 +66,18 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { if err == io.EOF { break } - if entry.Hash == hash { + if entry.Hash == plumbing.Hash(hash.RawValue()) { + parents := make([]git.Hash, len(gitCommit.ParentHashes)) + for i, hash := range gitCommit.ParentHashes { + parents[i] = git.ParseGogitHash(hash) + } + result := LFSResult{ Name: name, SHA: gitCommit.Hash.String(), Summary: strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0], When: gitCommit.Author.When, - ParentHashes: gitCommit.ParentHashes, + ParentHashes: parents, } resultsMap[gitCommit.Hash.String()+":"+name] = &result } diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index 49390f7c00c66..19601062ed9f5 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -24,7 +24,7 @@ type LFSResult struct { SHA string Summary string When time.Time - ParentHashes []git.SHA1 + ParentHashes []git.Hash BranchName string FullCommitName string } @@ -36,7 +36,7 @@ func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } // FindLFSFile finds commits that contain a provided pointer file hash -func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { +func FindLFSFile(repo *git.Repository, hash git.Hash) ([]*LFSResult, error) { resultsMap := map[string]*LFSResult{} results := make([]*LFSResult, 0) @@ -75,7 +75,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { fnameBuf := make([]byte, 4096) modeBuf := make([]byte, 40) - workingShaBuf := make([]byte, 20) + workingShaBuf := make([]byte, hash.Type().FullLength()/2) for scan.Scan() { // Get the next commit ID @@ -115,7 +115,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { continue case "commit": // Read in the commit to get its tree and in case this is one of the last used commits - curCommit, err = git.CommitFromReader(repo, git.MustIDFromString(string(commitID)), io.LimitReader(batchReader, size)) + curCommit, err = git.CommitFromReader(repo, repo.Hash.MustIDFromString(string(commitID)), io.LimitReader(batchReader, size)) if err != nil { return nil, err } @@ -131,12 +131,12 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { case "tree": var n int64 for n < size { - mode, fname, sha20byte, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf) + mode, fname, binHash, count, err := git.ParseTreeLine(hash.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf) if err != nil { return nil, err } n += int64(count) - if bytes.Equal(sha20byte, hash[:]) { + if bytes.Equal(binHash, hash.RawValue()) { result := LFSResult{ Name: curPath + string(fname), SHA: curCommit.ID.String(), @@ -146,9 +146,9 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { } resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result } else if string(mode) == git.EntryModeTree.String() { - sha40Byte := make([]byte, 40) - git.To40ByteSHA(sha20byte, sha40Byte) - trees = append(trees, sha40Byte) + hexHash := make([]byte, hash.Type().FullLength()) + git.BinToHexHash(hash.Type(), binHash, hexHash) + trees = append(trees, hexHash) paths = append(paths, curPath+string(fname)+"/") } } diff --git a/modules/git/ref.go b/modules/git/ref.go index ad251515e7dbb..88e22347f6a36 100644 --- a/modules/git/ref.go +++ b/modules/git/ref.go @@ -44,7 +44,7 @@ func SanitizeRefPattern(name string) string { type Reference struct { Name string repo *Repository - Object SHA1 // The id of this commit object + Object Hash // The id of this commit object Type string } @@ -205,7 +205,7 @@ func RefURL(repoURL, ref string) string { return repoURL + "/src/branch/" + refName case refFullName.IsTag(): return repoURL + "/src/tag/" + refName - case !IsValidSHAPattern(ref): + case !HashTypeFromID(Sha1).IsValid(ref): // assume they mean a branch return repoURL + "/src/branch/" + refName default: diff --git a/modules/git/repo.go b/modules/git/repo.go index 32f0e7007eb0b..7b0143e824944 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -7,6 +7,7 @@ package git import ( "bytes" "context" + "errors" "fmt" "io" "net/url" @@ -62,14 +63,40 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool { return err == nil } +// GetHashTypeOfRepo returns the hash type of a repository at a given path +func GetHashTypeOfRepo(ctx context.Context, repoPath string) (HashType, error) { + var stdout, stderr strings.Builder + + err := NewCommand(ctx, "hash-object", "--stdin").Run(&RunOpts{ + Dir: repoPath, + Stdout: &stdout, + Stderr: &stderr, + Stdin: &strings.Reader{}, + }) + if err != nil { + return nil, err + } + + if stderr.Len() > 0 { + return nil, errors.New(stderr.String()) + } + + h, err := HashFromString(strings.TrimRight(stdout.String(), "\n")) + if err != nil { + return nil, err + } + + return h.Type(), nil +} + // InitRepository initializes a new Git repository. -func InitRepository(ctx context.Context, repoPath string, bare bool) error { +func InitRepository(ctx context.Context, repoPath string, bare bool, hashType string) error { err := os.MkdirAll(repoPath, os.ModePerm) if err != nil { return err } - cmd := NewCommand(ctx, "init") + cmd := NewCommand(ctx, "init", "--object-format").AddDynamicArguments(hashType) if bare { cmd.AddArguments("--bare") } diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go index ef59ead9003b8..86a554e513d87 100644 --- a/modules/git/repo_base_gogit.go +++ b/modules/git/repo_base_gogit.go @@ -16,6 +16,7 @@ import ( "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/storage/filesystem" ) @@ -32,6 +33,7 @@ type Repository struct { Ctx context.Context LastCommitCache *LastCommitCache + Hash HashType } // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. @@ -68,6 +70,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { gogitStorage: storage, tagCache: newObjectCache(), Ctx: ctx, + Hash: ParseGogitHash(plumbing.ZeroHash).Type(), }, nil } diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go index 414e4eb1a83de..dce54fde1e96c 100644 --- a/modules/git/repo_base_nogogit.go +++ b/modules/git/repo_base_nogogit.go @@ -33,6 +33,8 @@ type Repository struct { Ctx context.Context LastCommitCache *LastCommitCache + + Hash HashType } // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. @@ -63,6 +65,11 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath) repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath) + repo.Hash, err = repo.HashTypeInterface() + if err != nil { + return nil, err + } + return repo, nil } diff --git a/modules/git/repo_blob.go b/modules/git/repo_blob.go index 698b6c7074a68..1875979a4b50c 100644 --- a/modules/git/repo_blob.go +++ b/modules/git/repo_blob.go @@ -5,7 +5,7 @@ package git // GetBlob finds the blob object in the repository. func (repo *Repository) GetBlob(idStr string) (*Blob, error) { - id, err := NewIDFromString(idStr) + id, err := repo.Hash.NewIDFromString(idStr) if err != nil { return nil, err } diff --git a/modules/git/repo_blob_gogit.go b/modules/git/repo_blob_gogit.go index 7f0892f6f5e91..7a6c67b5c4b66 100644 --- a/modules/git/repo_blob_gogit.go +++ b/modules/git/repo_blob_gogit.go @@ -9,8 +9,8 @@ import ( "github.com/go-git/go-git/v5/plumbing" ) -func (repo *Repository) getBlob(id SHA1) (*Blob, error) { - encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, id) +func (repo *Repository) getBlob(id Hash) (*Blob, error) { + encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id.RawValue())) if err != nil { return nil, ErrNotExist{id.String(), ""} } diff --git a/modules/git/repo_blob_nogogit.go b/modules/git/repo_blob_nogogit.go index 184a2bc9dd0c2..4ba8843e27761 100644 --- a/modules/git/repo_blob_nogogit.go +++ b/modules/git/repo_blob_nogogit.go @@ -5,7 +5,7 @@ package git -func (repo *Repository) getBlob(id SHA1) (*Blob, error) { +func (repo *Repository) getBlob(id Hash) (*Blob, error) { if id.IsZero() { return nil, ErrNotExist{id.String(), ""} } diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go index 026c73a283360..e122573954180 100644 --- a/modules/git/repo_blob_test.go +++ b/modules/git/repo_blob_test.go @@ -61,7 +61,7 @@ func TestRepository_GetBlob_NoId(t *testing.T) { defer r.Close() testCase := "" - testError := fmt.Errorf("Length must be 40: %s", testCase) + testError := fmt.Errorf("length must be 40: %s", testCase) blob, err := r.GetBlob(testCase) assert.Nil(t, blob) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 6b06fef656ec6..3e5c63387e942 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -6,8 +6,6 @@ package git import ( "bytes" - "encoding/hex" - "fmt" "io" "strconv" "strings" @@ -28,7 +26,7 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { // GetCommit returns commit object of by ID string. func (repo *Repository) GetCommit(commitID string) (*Commit, error) { - id, err := repo.ConvertToSHA1(commitID) + id, err := repo.ConvertToGitHash(commitID) if err != nil { return nil, err } @@ -54,7 +52,7 @@ func (repo *Repository) GetTagCommit(name string) (*Commit, error) { return repo.GetCommit(commitID) } -func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, error) { +func (repo *Repository) getCommitByPathWithID(id Hash, relpath string) (*Commit, error) { // File name starts with ':' must be escaped. if relpath[0] == ':' { relpath = `\` + relpath @@ -65,7 +63,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, return nil, runErr } - id, err := NewIDFromString(stdout) + id, err := repo.Hash.NewIDFromString(stdout) if err != nil { return nil, err } @@ -90,7 +88,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { return commits[0], nil } -func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string) ([]*Commit, error) { +func (repo *Repository) commitsByRange(id Hash, page, pageSize int, not string) ([]*Commit, error) { cmd := NewCommand(repo.Ctx, "log"). AddOptionFormat("--skip=%d", (page-1)*pageSize). AddOptionFormat("--max-count=%d", pageSize). @@ -109,7 +107,7 @@ func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string) return repo.parsePrettyFormatLogToList(stdout) } -func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) { +func (repo *Repository) searchCommits(id Hash, opts SearchCommitsOptions) ([]*Commit, error) { // add common arguments to git command addCommonSearchArgs := func(c *Command) { // ignore case @@ -164,7 +162,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co // then let's iterate over them for _, v := range opts.Keywords { // ignore anything not matching a valid sha pattern - if IsValidSHAPattern(v) { + if id.Type().IsValid(v) { // create new git log command with 1 commit limit hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat) // add previous arguments except for --grep and --all @@ -245,25 +243,22 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) } }() + hashLen := repo.Hash.FullLength() commits := []*Commit{} - shaline := [41]byte{} - var sha1 SHA1 + shaline := make([]byte, hashLen+1) for { - n, err := io.ReadFull(stdoutReader, shaline[:]) - if err != nil || n < 40 { + n, err := io.ReadFull(stdoutReader, shaline) + if err != nil || n < hashLen { if err == io.EOF { err = nil } return commits, err } - n, err = hex.Decode(sha1[:], shaline[0:40]) - if n != 20 { - err = fmt.Errorf("invalid sha %q", string(shaline[:40])) - } + hash, err := repo.Hash.NewIDFromString(string(shaline[0:hashLen])) if err != nil { return nil, err } - commit, err := repo.getCommit(sha1) + commit, err := repo.getCommit(hash) if err != nil { return nil, err } @@ -392,7 +387,7 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) { } // commitsBefore the limit is depth, not total number of returned commits. -func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) { +func (repo *Repository) commitsBefore(id Hash, limit int) ([]*Commit, error) { cmd := NewCommand(repo.Ctx, "log", prettyLogFormat) if limit > 0 { cmd.AddOptionFormat("-%d", limit) @@ -426,11 +421,11 @@ func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) { return commits, nil } -func (repo *Repository) getCommitsBefore(id SHA1) ([]*Commit, error) { +func (repo *Repository) getCommitsBefore(id Hash) ([]*Commit, error) { return repo.commitsBefore(id, 0) } -func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, error) { +func (repo *Repository) getCommitsBeforeLimit(id Hash, num int) ([]*Commit, error) { return repo.commitsBefore(id, num) } diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index ce0af936140db..be67c902c466f 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/object" ) @@ -38,40 +39,46 @@ func (repo *Repository) RemoveReference(name string) error { return repo.gogitRepo.Storer.RemoveReference(plumbing.ReferenceName(name)) } -// ConvertToSHA1 returns a Hash object from a potential ID string -func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { - if len(commitID) == SHAFullLength { - sha1, err := NewIDFromString(commitID) +// ConvertToHash returns a Hash object from a potential ID string +func (repo *Repository) ConvertToGitHash(commitID string) (Hash, error) { + h := repo.Hash + if len(commitID) == hash.HexSize && h.IsValid(commitID) { + hash, err := h.NewIDFromString(commitID) if err == nil { - return sha1, nil + return hash, nil } } actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path}) + actualCommitID = strings.TrimSpace(actualCommitID) if err != nil { if strings.Contains(err.Error(), "unknown revision or path") || strings.Contains(err.Error(), "fatal: Needed a single revision") { - return SHA1{}, ErrNotExist{commitID, ""} + return h.Empty(), ErrNotExist{commitID, ""} } - return SHA1{}, err + return h.Empty(), err } - return NewIDFromString(actualCommitID) + return HashFromString(actualCommitID) } // IsCommitExist returns true if given commit exists in current repository. func (repo *Repository) IsCommitExist(name string) bool { - hash := plumbing.NewHash(name) - _, err := repo.gogitRepo.CommitObject(hash) + hash, err := repo.ConvertToGitHash(name) + if err != nil { + return false + } + _, err = repo.gogitRepo.CommitObject(plumbing.Hash(hash.RawValue())) return err == nil } -func (repo *Repository) getCommit(id SHA1) (*Commit, error) { +func (repo *Repository) getCommit(id Hash) (*Commit, error) { var tagObject *object.Tag - gogitCommit, err := repo.gogitRepo.CommitObject(id) + hash := plumbing.Hash(id.RawValue()) + gogitCommit, err := repo.gogitRepo.CommitObject(hash) if err == plumbing.ErrObjectNotFound { - tagObject, err = repo.gogitRepo.TagObject(id) + tagObject, err = repo.gogitRepo.TagObject(hash) if err == plumbing.ErrObjectNotFound { return nil, ErrNotExist{ ID: id.String(), @@ -94,7 +101,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { return nil, err } - commit.Tree.ID = tree.Hash + commit.Tree.ID = ParseGogitHash(tree.Hash) commit.Tree.gogitTree = tree return commit, nil diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index d5eb723100a73..089de2cab90d5 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -65,7 +65,7 @@ func (repo *Repository) IsCommitExist(name string) bool { return err == nil } -func (repo *Repository) getCommit(id SHA1) (*Commit, error) { +func (repo *Repository) getCommit(id Hash) (*Commit, error) { wr, rd, cancel := repo.CatFileBatch(repo.Ctx) defer cancel() @@ -74,7 +74,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { return repo.getCommitFromBatchReader(rd, id) } -func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Commit, error) { +func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id Hash) (*Commit, error) { _, typ, size, err := ReadBatchLine(rd) if err != nil { if errors.Is(err, io.EOF) || IsErrNotExist(err) { @@ -97,7 +97,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co if err != nil { return nil, err } - tag, err := parseTagData(data) + tag, err := parseTagData(id.Type(), data) if err != nil { return nil, err } @@ -131,12 +131,13 @@ 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) == SHAFullLength && IsValidSHAPattern(commitID) { - sha1, err := NewIDFromString(commitID) +// ConvertToGitHash returns a GitHash object from a potential ID string +func (repo *Repository) ConvertToGitHash(commitID string) (Hash, error) { + hash := repo.Hash + if len(commitID) == hash.FullLength() && hash.IsValid(commitID) { + hash, err := repo.Hash.NewIDFromString(commitID) if err == nil { - return sha1, nil + return hash, nil } } @@ -144,15 +145,15 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { defer cancel() _, err := wr.Write([]byte(commitID + "\n")) if err != nil { - return SHA1{}, err + return nil, err } sha, _, _, err := ReadBatchLine(rd) if err != nil { if IsErrNotExist(err) { - return SHA1{}, ErrNotExist{commitID, ""} + return nil, ErrNotExist{commitID, ""} } - return SHA1{}, err + return nil, err } - return MustIDFromString(string(sha)), nil + return repo.Hash.MustIDFromString(string(sha)), nil } diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index aad725fa9db89..cf04acad2bdee 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -284,7 +284,7 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error { // If base is the SHA of an empty tree (EmptyTreeSHA), it returns the files changes from the initial commit to the head commit func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) { cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z") - if base == EmptySHA { + if base == repo.Hash.Empty().String() { cmd.AddDynamicArguments(head) } else { cmd.AddDynamicArguments(base, head) diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index 603aabde42a09..aaded29508e2e 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -131,12 +131,12 @@ func TestGetCommitFilesChanged(t *testing.T) { files []string }{ { - EmptySHA, + repo.Hash.Empty().String(), "95bb4d39648ee7e325106df01a621c530863a653", []string{"file1.txt"}, }, { - EmptySHA, + repo.Hash.Empty().String(), "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", []string{"file2.txt"}, }, @@ -146,7 +146,7 @@ func TestGetCommitFilesChanged(t *testing.T) { []string{"file2.txt"}, }, { - EmptyTreeSHA, + repo.Hash.EmptyTree().String(), "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", []string{"file1.txt", "file2.txt"}, }, diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go index 4803be58165f7..e2b45064fd38f 100644 --- a/modules/git/repo_gpg.go +++ b/modules/git/repo_gpg.go @@ -17,7 +17,7 @@ func (gpgSettings *GPGSettings) LoadPublicKeyContent() error { "gpg -a --export", "gpg", "-a", "--export", gpgSettings.KeyID) if err != nil { - return fmt.Errorf("Unable to get default signing key: %s, %s, %w", gpgSettings.KeyID, stderr, err) + return fmt.Errorf("unable to get default signing key: %s, %s, %w", gpgSettings.KeyID, stderr, err) } gpgSettings.PublicKeyContent = content return nil diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 34dd1e0129158..c895c9752b9b5 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) != SHAFullLength { + if len(treeish) != repo.Hash.FullLength() { res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return err @@ -25,14 +25,14 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) treeish = res[:len(res)-1] } } - id, err := NewIDFromString(treeish) + id, err := repo.Hash.NewIDFromString(treeish) if err != nil { return err } return repo.readTreeToIndex(id, indexFilename...) } -func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error { +func (repo *Repository) readTreeToIndex(id Hash, indexFilename ...string) error { var env []string if len(indexFilename) > 0 { env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) @@ -95,7 +95,9 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { buffer := new(bytes.Buffer) for _, file := range filenames { if file != "" { - buffer.WriteString("0 0000000000000000000000000000000000000000\t") + buffer.WriteString("0 ") + buffer.WriteString(repo.Hash.Empty().String()) + buffer.WriteByte('\t') buffer.WriteString(file) buffer.WriteByte('\000') } @@ -109,7 +111,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { } // AddObjectToIndex adds the provided object hash to the index at the provided filename -func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error { +func (repo *Repository) AddObjectToIndex(mode string, object Hash, filename string) error { cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, object.String(), filename) _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) return err @@ -121,7 +123,7 @@ func (repo *Repository) WriteTree() (*Tree, error) { if runErr != nil { return nil, runErr } - id, err := NewIDFromString(strings.TrimSpace(stdout)) + id, err := repo.Hash.NewIDFromString(strings.TrimSpace(stdout)) if err != nil { return nil, err } diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go index 1d94ad6c00f4b..fa4a04dbb8cd5 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/repo_language_stats_nogogit.go @@ -39,7 +39,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, ErrNotExist{commitID, ""} } - sha, err := NewIDFromString(string(shaBytes)) + sha, err := repo.Hash.NewIDFromString(string(shaBytes)) if err != nil { log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) return nil, ErrNotExist{commitID, ""} diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go index 9edc201fead7e..9813793839d4c 100644 --- a/modules/git/repo_object.go +++ b/modules/git/repo_object.go @@ -31,17 +31,45 @@ func (o ObjectType) Bytes() []byte { return []byte(o) } -// HashObject takes a reader and returns SHA1 hash for that reader -func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) { - idStr, err := repo.hashObject(reader) +type EmptyReader struct{} + +func (EmptyReader) Read(p []byte) (int, error) { + return 0, io.EOF +} + +func (repo *Repository) HashTypeInterface() (HashType, error) { + if repo != nil && repo.Hash != nil { + return repo.Hash, nil + } + + str, err := repo.hashObject(EmptyReader{}, false) + if err != nil { + return nil, err + } + hash, err := HashFromString(str) if err != nil { - return SHA1{}, err + return nil, err } - return NewIDFromString(idStr) + + return hash.Type(), nil +} + +// HashObject takes a reader and returns hash for that reader +func (repo *Repository) HashObject(reader io.Reader) (Hash, error) { + idStr, err := repo.hashObject(reader, true) + if err != nil { + return nil, err + } + return repo.Hash.NewIDFromString(idStr) } -func (repo *Repository) hashObject(reader io.Reader) (string, error) { - cmd := NewCommand(repo.Ctx, "hash-object", "-w", "--stdin") +func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) { + var cmd *Command + if save { + cmd = NewCommand(repo.Ctx, "hash-object", "-w", "--stdin") + } else { + cmd = NewCommand(repo.Ctx, "hash-object", "--stdin") + } stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) err := cmd.Run(&RunOpts{ diff --git a/modules/git/repo_ref_gogit.go b/modules/git/repo_ref_gogit.go index 8a68a4574f1bc..fc43ce5545d5b 100644 --- a/modules/git/repo_ref_gogit.go +++ b/modules/git/repo_ref_gogit.go @@ -30,13 +30,13 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { refType := string(ObjectCommit) if ref.Name().IsTag() { // tags can be of type `commit` (lightweight) or `tag` (annotated) - if tagType, _ := repo.GetTagType(ref.Hash()); err == nil { + if tagType, _ := repo.GetTagType(ParseGogitHash(ref.Hash())); err == nil { refType = tagType } } r := &Reference{ Name: ref.Name().String(), - Object: ref.Hash(), + Object: ParseGogitHash(ref.Hash()), Type: refType, repo: repo, } diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go index ac53d661b517b..21b0c0d85232c 100644 --- a/modules/git/repo_ref_nogogit.go +++ b/modules/git/repo_ref_nogogit.go @@ -75,7 +75,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { if pattern == "" || strings.HasPrefix(refName, pattern) { r := &Reference{ Name: refName, - Object: MustIDFromString(sha), + Object: repo.Hash.MustIDFromString(sha), Type: typ, repo: repo, } diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index ae877f0211002..9e1047afbd0c0 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -84,7 +84,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) { return nil, err } - id, err := NewIDFromString(idStr) + id, err := repo.Hash.NewIDFromString(idStr) if err != nil { return nil, err } @@ -98,7 +98,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) { // GetTagWithID returns a Git tag by given name and ID func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) { - id, err := NewIDFromString(idStr) + id, err := repo.Hash.NewIDFromString(idStr) if err != nil { return nil, err } @@ -139,7 +139,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { break } - tag, err := parseTagRef(ref) + tag, err := parseTagRef(repo.Hash, ref) if err != nil { return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err) } @@ -159,13 +159,13 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { } // parseTagRef parses a tag from a 'git for-each-ref'-produced reference. -func parseTagRef(ref map[string]string) (tag *Tag, err error) { +func parseTagRef(hash HashType, ref map[string]string) (tag *Tag, err error) { tag = &Tag{ Type: ref["objecttype"], Name: ref["refname:short"], } - tag.ID, err = NewIDFromString(ref["objectname"]) + tag.ID, err = hash.NewIDFromString(ref["objectname"]) if err != nil { return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err) } @@ -175,7 +175,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) { tag.Object = tag.ID } else { // annotated tag - tag.Object, err = NewIDFromString(ref["object"]) + tag.Object, err = hash.NewIDFromString(ref["object"]) if err != nil { return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err) } @@ -208,7 +208,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) { // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) { - id, err := NewIDFromString(sha) + id, err := repo.Hash.NewIDFromString(sha) if err != nil { return nil, err } diff --git a/modules/git/repo_tag_gogit.go b/modules/git/repo_tag_gogit.go index 2bc75e7cf9b1d..38b00ed775bff 100644 --- a/modules/git/repo_tag_gogit.go +++ b/modules/git/repo_tag_gogit.go @@ -55,9 +55,9 @@ func (repo *Repository) GetTags(skip, limit int) ([]string, error) { } // GetTagType gets the type of the tag, either commit (simple) or tag (annotated) -func (repo *Repository) GetTagType(id SHA1) (string, error) { +func (repo *Repository) GetTagType(id Hash) (string, error) { // Get tag type - obj, err := repo.gogitRepo.Object(plumbing.AnyObject, id) + obj, err := repo.gogitRepo.Object(plumbing.AnyObject, plumbing.Hash(id.RawValue())) if err != nil { if err == plumbing.ErrReferenceNotFound { return "", &ErrNotExist{ID: id.String()} @@ -68,7 +68,7 @@ func (repo *Repository) GetTagType(id SHA1) (string, error) { return obj.Type().String(), nil } -func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { +func (repo *Repository) getTag(tagID Hash, name string) (*Tag, error) { t, ok := repo.tagCache.Get(tagID.String()) if ok { log.Debug("Hit cache: %s", tagID) @@ -88,7 +88,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { // every tag should have a commit ID so return all errors return nil, err } - commitID, err := NewIDFromString(commitIDStr) + commitID, err := HashFromString(commitIDStr) if err != nil { return nil, err } @@ -112,7 +112,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { return tag, nil } - gogitTag, err := repo.gogitRepo.TagObject(tagID) + gogitTag, err := repo.gogitRepo.TagObject(plumbing.Hash(tagID.RawValue())) if err != nil { if err == plumbing.ErrReferenceNotFound { return nil, &ErrNotExist{ID: tagID.String()} @@ -124,7 +124,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { tag := &Tag{ Name: name, ID: tagID, - Object: gogitTag.Target, + Object: commitID.Type().MustID(gogitTag.Target[:]), Type: tp, Tagger: &gogitTag.Tagger, Message: gogitTag.Message, diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go index 9080ffcfd7820..2b77b2e083c43 100644 --- a/modules/git/repo_tag_nogogit.go +++ b/modules/git/repo_tag_nogogit.go @@ -30,7 +30,7 @@ func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { } // GetTagType gets the type of the tag, either commit (simple) or tag (annotated) -func (repo *Repository) GetTagType(id SHA1) (string, error) { +func (repo *Repository) GetTagType(id Hash) (string, error) { wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) defer cancel() _, err := wr.Write([]byte(id.String() + "\n")) @@ -44,7 +44,7 @@ func (repo *Repository) GetTagType(id SHA1) (string, error) { return typ, nil } -func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { +func (repo *Repository) getTag(tagID Hash, name string) (*Tag, error) { t, ok := repo.tagCache.Get(tagID.String()) if ok { log.Debug("Hit cache: %s", tagID) @@ -64,7 +64,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { // every tag should have a commit ID so return all errors return nil, err } - commitID, err := NewIDFromString(commitIDStr) + commitID, err := repo.Hash.NewIDFromString(commitIDStr) if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { return nil, err } - tag, err := parseTagData(data) + tag, err := parseTagData(tagID.Type(), data) if err != nil { return nil, err } diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 4d94efd1acfb1..90ae29bed44a4 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -194,6 +194,7 @@ func TestRepository_GetAnnotatedTag(t *testing.T) { } func TestRepository_parseTagRef(t *testing.T) { + sha1 := HashTypeFromID(Sha1) tests := []struct { name string @@ -223,8 +224,8 @@ func TestRepository_parseTagRef(t *testing.T) { want: &Tag{ Name: "v1.9.1", - ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), - Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), + ID: sha1.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), + Object: sha1.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Type: "commit", Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", @@ -252,8 +253,8 @@ func TestRepository_parseTagRef(t *testing.T) { want: &Tag{ Name: "v0.0.1", - ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), - Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), + ID: sha1.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), + Object: sha1.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Type: "tag", Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", @@ -310,8 +311,8 @@ qbHDASXl want: &Tag{ Name: "v0.0.1", - ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), - Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), + ID: sha1.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), + Object: sha1.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Type: "tag", Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", @@ -350,7 +351,7 @@ Add changelog of v1.9.1 (#7859) for _, test := range tests { tc := test // don't close over loop variable t.Run(tc.name, func(t *testing.T) { - got, err := parseTagRef(tc.givenRef) + got, err := parseTagRef(sha1, tc.givenRef) if tc.wantErr { require.Error(t, err) diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 63c33379bf5dc..90deeb5c0f0ca 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -21,7 +21,7 @@ type CommitTreeOpts struct { } // CommitTree creates a commit from a given tree id for the user with provided message -func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) { +func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opts CommitTreeOpts) (Hash, error) { commitTimeStr := time.Now().Format(time.RFC3339) // Because this may call hooks we should pass in the environment @@ -61,7 +61,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt Stderr: stderr, }) if err != nil { - return SHA1{}, ConcatenateError(err, stderr.String()) + return nil, ConcatenateError(err, stderr.String()) } - return NewIDFromString(strings.TrimSpace(stdout.String())) + return repo.Hash.NewIDFromString(strings.TrimSpace(stdout.String())) } diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index a7b1081b15f18..5e49b95d21966 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -6,8 +6,10 @@ package git -func (repo *Repository) getTree(id SHA1) (*Tree, error) { - gogitTree, err := repo.gogitRepo.TreeObject(id) +import "github.com/go-git/go-git/v5/plumbing" + +func (repo *Repository) getTree(id Hash) (*Tree, error) { + gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id.RawValue())) if err != nil { return nil, err } @@ -19,7 +21,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) != SHAFullLength { + if len(idStr) != repo.Hash.FullLength() { res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err @@ -28,14 +30,14 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { idStr = res[:len(res)-1] } } - id, err := NewIDFromString(idStr) + id, err := HashFromString(idStr) if err != nil { return nil, err } resolvedID := id - commitObject, err := repo.gogitRepo.CommitObject(id) + commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id.RawValue())) if err == nil { - id = SHA1(commitObject.TreeHash) + id = ParseGogitHash(commitObject.TreeHash) } treeObject, err := repo.getTree(id) if err != nil { diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go index 4fd77df2b8249..65f8a33f1c1e5 100644 --- a/modules/git/repo_tree_nogogit.go +++ b/modules/git/repo_tree_nogogit.go @@ -9,7 +9,7 @@ import ( "io" ) -func (repo *Repository) getTree(id SHA1) (*Tree, error) { +func (repo *Repository) getTree(id Hash) (*Tree, error) { wr, rd, cancel := repo.CatFileBatch(repo.Ctx) defer cancel() @@ -28,7 +28,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { if err != nil { return nil, err } - tag, err := parseTagData(data) + tag, err := parseTagData(id.Type(), data) if err != nil { return nil, err } @@ -51,7 +51,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { case "tree": tree := NewTree(repo, id) tree.ResolvedID = id - tree.entries, err = catBatchParseTreeEntries(tree, rd, size) + tree.entries, err = catBatchParseTreeEntries(repo.Hash, tree, rd, size) if err != nil { return nil, err } @@ -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) != SHAFullLength { + if len(idStr) != repo.Hash.FullLength() { res, err := repo.GetRefCommitID(idStr) if err != nil { return nil, err @@ -75,7 +75,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { idStr = res } } - id, err := NewIDFromString(idStr) + id, err := repo.Hash.NewIDFromString(idStr) if err != nil { return nil, err } diff --git a/modules/git/sha1.go b/modules/git/sha1.go deleted file mode 100644 index 8d6403e8657fb..0000000000000 --- a/modules/git/sha1.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "encoding/hex" - "fmt" - "regexp" - "strings" -) - -// EmptySHA defines empty git SHA (undefined, non-existent) -const EmptySHA = "0000000000000000000000000000000000000000" - -// EmptyTreeSHA is the SHA of an empty tree, the root of all git repositories -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}$`) - -// IsValidSHAPattern will check if the provided string matches the SHA Pattern -func IsValidSHAPattern(sha string) bool { - return shaPattern.MatchString(sha) -} - -type ErrInvalidSHA struct { - SHA string -} - -func (err ErrInvalidSHA) Error() string { - return fmt.Sprintf("invalid sha: %s", err.SHA) -} - -// MustID always creates a new SHA1 from a [20]byte array with no validation of input. -func MustID(b []byte) SHA1 { - var id SHA1 - copy(id[:], b) - return id -} - -// NewID creates a new SHA1 from a [20]byte array. -func NewID(b []byte) (SHA1, error) { - if len(b) != 20 { - return SHA1{}, fmt.Errorf("Length must be 20: %v", b) - } - return MustID(b), nil -} - -// MustIDFromString always creates a new sha from a ID with no validation of input. -func MustIDFromString(s string) SHA1 { - b, _ := hex.DecodeString(s) - return MustID(b) -} - -// NewIDFromString creates a new SHA1 from a ID string of length 40. -func NewIDFromString(s string) (SHA1, error) { - var id SHA1 - s = strings.TrimSpace(s) - if len(s) != SHAFullLength { - return id, fmt.Errorf("Length must be 40: %s", s) - } - b, err := hex.DecodeString(s) - if err != nil { - return id, err - } - return NewID(b) -} diff --git a/modules/git/sha1_gogit.go b/modules/git/sha1_gogit.go deleted file mode 100644 index 28f35d17a98e1..0000000000000 --- a/modules/git/sha1_gogit.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "github.com/go-git/go-git/v5/plumbing" -) - -// SHA1 a git commit name -type SHA1 = plumbing.Hash - -// ComputeBlobHash compute the hash for a given blob content -func ComputeBlobHash(content []byte) SHA1 { - return plumbing.ComputeHash(plumbing.BlobObject, content) -} diff --git a/modules/git/sha1_nogogit.go b/modules/git/sha1_nogogit.go deleted file mode 100644 index d818d86a3a0cc..0000000000000 --- a/modules/git/sha1_nogogit.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !gogit - -package git - -import ( - "crypto/sha1" - "encoding/hex" - "hash" - "strconv" -) - -// SHA1 a git commit name -type SHA1 [20]byte - -// String returns a string representation of the SHA -func (s SHA1) String() string { - return hex.EncodeToString(s[:]) -} - -// IsZero returns whether this SHA1 is all zeroes -func (s SHA1) IsZero() bool { - var empty SHA1 - return s == empty -} - -// ComputeBlobHash compute the hash for a given blob content -func ComputeBlobHash(content []byte) SHA1 { - return ComputeHash(ObjectBlob, content) -} - -// ComputeHash compute the hash for a given ObjectType and content -func ComputeHash(t ObjectType, content []byte) SHA1 { - h := NewHasher(t, int64(len(content))) - _, _ = h.Write(content) - return h.Sum() -} - -// Hasher is a struct that will generate a SHA1 -type Hasher struct { - hash.Hash -} - -// NewHasher takes an object type and size and creates a hasher to generate a SHA -func NewHasher(t ObjectType, size int64) Hasher { - h := Hasher{sha1.New()} - _, _ = h.Write(t.Bytes()) - _, _ = h.Write([]byte(" ")) - _, _ = h.Write([]byte(strconv.FormatInt(size, 10))) - _, _ = h.Write([]byte{0}) - return h -} - -// Sum generates a SHA1 for the provided hash -func (h Hasher) Sum() (sha1 SHA1) { - copy(sha1[:], h.Hash.Sum(nil)) - return sha1 -} diff --git a/modules/git/sha1_test.go b/modules/git/sha1_test.go deleted file mode 100644 index db2944fc53d56..0000000000000 --- a/modules/git/sha1_test.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestIsValidSHAPattern(t *testing.T) { - assert.True(t, IsValidSHAPattern("fee1")) - assert.True(t, IsValidSHAPattern("abc000")) - assert.True(t, IsValidSHAPattern("9023902390239023902390239023902390239023")) - assert.False(t, IsValidSHAPattern("90239023902390239023902390239023902390239023")) - assert.False(t, IsValidSHAPattern("abc")) - assert.False(t, IsValidSHAPattern("123g")) - assert.False(t, IsValidSHAPattern("some random text")) -} diff --git a/modules/git/tag.go b/modules/git/tag.go index d0ddef64e087d..06dae8e8d496c 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -17,8 +17,8 @@ const ( // Tag represents a Git tag. type Tag struct { Name string - ID SHA1 - Object SHA1 // The id of this commit object + ID Hash + Object Hash // The id of this commit object Type string Tagger *Signature Message string @@ -33,8 +33,10 @@ func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) { // Parse commit information from the (uncompressed) raw // data from the commit object. // \n\n separate headers from message -func parseTagData(data []byte) (*Tag, error) { +func parseTagData(hashType HashType, data []byte) (*Tag, error) { tag := new(Tag) + tag.ID = hashType.NewEmptyID() + tag.Object = hashType.NewEmptyID() tag.Tagger = &Signature{} // we now have the contents of the commit object. Let's investigate... nextline := 0 @@ -48,7 +50,7 @@ l: reftype := line[:spacepos] switch string(reftype) { case "object": - id, err := NewIDFromString(string(line[spacepos+1:])) + id, err := hashType.NewIDFromString(string(line[spacepos+1:])) if err != nil { return nil, err } diff --git a/modules/git/tag_test.go b/modules/git/tag_test.go index 2337e69c6afcb..81e4972d77665 100644 --- a/modules/git/tag_test.go +++ b/modules/git/tag_test.go @@ -22,8 +22,8 @@ tagger Lucas Michot 1484491741 +0100 `), tag: Tag{ Name: "", - ID: SHA1{}, - Object: SHA1{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a}, + ID: NewSha1(), + Object: &Sha1Hash{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a}, Type: "commit", Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0)}, Message: "", @@ -39,8 +39,8 @@ o ono`), tag: Tag{ Name: "", - ID: SHA1{}, - Object: SHA1{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc}, + ID: NewSha1(), + Object: &Sha1Hash{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc}, Type: "commit", Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0)}, Message: "test message\no\n\nono", @@ -49,7 +49,7 @@ ono`), tag: Tag{ } for _, test := range testData { - tag, err := parseTagData(test.data) + tag, err := parseTagData(HashTypeFromID(Sha1), test.data) assert.NoError(t, err) assert.EqualValues(t, test.tag.ID, tag.ID) assert.EqualValues(t, test.tag.Object, tag.Object) diff --git a/modules/git/tree.go b/modules/git/tree.go index 856b8cef53c67..a1a84e2c3fd99 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -10,7 +10,7 @@ import ( ) // NewTree create a new tree according the repository and tree id -func NewTree(repo *Repository, id SHA1) *Tree { +func NewTree(repo *Repository, id Hash) *Tree { return &Tree{ ID: id, repo: repo, diff --git a/modules/git/tree_blob_gogit.go b/modules/git/tree_blob_gogit.go index f1afc5d0a6fec..92c25cb92c12b 100644 --- a/modules/git/tree_blob_gogit.go +++ b/modules/git/tree_blob_gogit.go @@ -24,7 +24,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { gogitTreeEntry: &object.TreeEntry{ Name: "", Mode: filemode.Dir, - Hash: t.ID, + Hash: plumbing.Hash(t.ID.RawValue()), }, }, nil } diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go index 194dd12f7dbb1..931d53919fee5 100644 --- a/modules/git/tree_entry_gogit.go +++ b/modules/git/tree_entry_gogit.go @@ -14,7 +14,7 @@ import ( // TreeEntry the leaf in the git tree type TreeEntry struct { - ID SHA1 + ID Hash gogitTreeEntry *object.TreeEntry ptree *Tree @@ -88,7 +88,7 @@ func (te *TreeEntry) Blob() *Blob { } return &Blob{ - ID: te.gogitTreeEntry.Hash, + ID: ParseGogitHash(te.gogitTreeEntry.Hash), gogitEncodedObj: encodedObj, name: te.Name(), } diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go index cda755886a8e2..16e829d3efac4 100644 --- a/modules/git/tree_entry_nogogit.go +++ b/modules/git/tree_entry_nogogit.go @@ -9,7 +9,7 @@ import "code.gitea.io/gitea/modules/log" // TreeEntry the leaf in the git tree type TreeEntry struct { - ID SHA1 + ID Hash ptree *Tree diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go index fa601e6533c6b..3772e2461dd7b 100644 --- a/modules/git/tree_gogit.go +++ b/modules/git/tree_gogit.go @@ -15,8 +15,8 @@ import ( // Tree represents a flat directory listing. type Tree struct { - ID SHA1 - ResolvedID SHA1 + ID Hash + ResolvedID Hash repo *Repository gogitTree *object.Tree @@ -26,7 +26,7 @@ type Tree struct { } func (t *Tree) loadTreeObject() error { - gogitTree, err := t.repo.gogitRepo.TreeObject(t.ID) + gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue())) if err != nil { return err } @@ -47,7 +47,7 @@ func (t *Tree) ListEntries() (Entries, error) { entries := make([]*TreeEntry, len(t.gogitTree.Entries)) for i, entry := range t.gogitTree.Entries { entries[i] = &TreeEntry{ - ID: entry.Hash, + ID: ParseGogitHash(entry.Hash), gogitTreeEntry: &t.gogitTree.Entries[i], ptree: t, } @@ -81,7 +81,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { } convertedEntry := &TreeEntry{ - ID: entry.Hash, + ID: ParseGogitHash(entry.Hash), gogitTreeEntry: &entry, ptree: t, fullName: fullName, diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index ef598d7e91327..23491efb494b0 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -13,8 +13,8 @@ import ( // Tree represents a flat directory listing. type Tree struct { - ID SHA1 - ResolvedID SHA1 + ID Hash + ResolvedID Hash repo *Repository // parent tree @@ -54,7 +54,7 @@ func (t *Tree) ListEntries() (Entries, error) { } } if typ == "tree" { - t.entries, err = catBatchParseTreeEntries(t, rd, sz) + t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func (t *Tree) ListEntries() (Entries, error) { } var err error - t.entries, err = parseTreeEntries(stdout, t) + t.entries, err = parseTreeEntries(t.repo.Hash, stdout, t) if err == nil { t.entriesParsed = true } @@ -114,7 +114,7 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { } var err error - t.entriesRecursive, err = parseTreeEntries(stdout, t) + t.entriesRecursive, err = parseTreeEntries(t.repo.Hash, stdout, t) if err == nil { t.entriesRecursiveParsed = true } diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index e4686fa01f441..c975e77adc26e 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -62,8 +62,8 @@ func isIndexable(entry *git.TreeEntry) bool { } // parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command -func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) { - entries, err := git.ParseTreeEntries(stdout) +func parseGitLsTreeOutput(hash git.HashType, stdout []byte) ([]internal.FileUpdate, error) { + entries, err := git.ParseTreeEntries(hash, stdout) if err != nil { return nil, err } @@ -92,7 +92,11 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s } var err error - changes.Updates, err = parseGitLsTreeOutput(stdout) + hash, err := git.GetHashTypeOfRepo(ctx, repo.RepoPath()) + if err != nil { + return nil, err + } + changes.Updates, err = parseGitLsTreeOutput(hash, stdout) return &changes, err } @@ -169,6 +173,11 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio if err != nil { return nil, err } - changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout) + + hash, err := git.GetHashTypeOfRepo(ctx, repo.RepoPath()) + if err != nil { + return nil, err + } + changes.Updates, err = parseGitLsTreeOutput(hash, lsTreeStdout) return &changes, err } diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index 827b2a98493b0..dc0b69990f3fc 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -144,7 +144,7 @@ func TestCommitToPushCommit(t *testing.T) { When: now, } const hexString = "0123456789abcdef0123456789abcdef01234567" - sha1, err := git.NewIDFromString(hexString) + sha1, err := git.HashFromString(hexString) assert.NoError(t, err) pushCommit := CommitToPushCommit(&git.Commit{ ID: sha1, @@ -169,11 +169,12 @@ func TestListToPushCommits(t *testing.T) { When: now, } + hashType := git.HashTypeFromID(git.Sha1) const hexString1 = "0123456789abcdef0123456789abcdef01234567" - hash1, err := git.NewIDFromString(hexString1) + hash1, err := hashType.NewIDFromString(hexString1) assert.NoError(t, err) const hexString2 = "fedcba9876543210fedcba9876543210fedcba98" - hash2, err := git.NewIDFromString(hexString2) + hash2, err := hashType.NewIDFromString(hexString2) assert.NoError(t, err) l := []*git.Commit{ diff --git a/modules/repository/generate.go b/modules/repository/generate.go index 4055029d22a13..08e6c2e486342 100644 --- a/modules/repository/generate.go +++ b/modules/repository/generate.go @@ -223,7 +223,8 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } } - if err := git.InitRepository(ctx, tmpDir, false); err != nil { + // FIXME: fix the hash + if err := git.InitRepository(ctx, tmpDir, false, "sha1"); err != nil { return err } @@ -356,7 +357,8 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ } } - if err = CheckInitRepository(ctx, owner.Name, generateRepo.Name); err != nil { + // FIXME - fix the hash + if err = CheckInitRepository(ctx, owner.Name, generateRepo.Name, "sha1"); err != nil { return generateRepo, err } diff --git a/modules/repository/init.go b/modules/repository/init.go index 6f791f742b124..f7246cb795e03 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -188,7 +188,7 @@ func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi return nil } -func CheckInitRepository(ctx context.Context, owner, name string) (err error) { +func CheckInitRepository(ctx context.Context, owner, name, hashType string) (err error) { // Somehow the directory could exist. repoPath := repo_model.RepoPath(owner, name) isExist, err := util.IsExist(repoPath) @@ -204,7 +204,7 @@ func CheckInitRepository(ctx context.Context, owner, name string) (err error) { } // Init git bare new repository. - if err = git.InitRepository(ctx, repoPath, true); err != nil { + if err = git.InitRepository(ctx, repoPath, true, hashType); err != nil { return fmt.Errorf("git.InitRepository: %w", err) } else if err = CreateDelegateHooks(repoPath); err != nil { return fmt.Errorf("createDelegateHooks: %w", err) diff --git a/modules/repository/push.go b/modules/repository/push.go index ea03f9e1537f7..d0b9918240d52 100644 --- a/modules/repository/push.go +++ b/modules/repository/push.go @@ -20,12 +20,14 @@ type PushUpdateOptions struct { // IsNewRef return true if it's a first-time push to a branch, tag or etc. func (opts *PushUpdateOptions) IsNewRef() bool { - return opts.OldCommitID == git.EmptySHA + hash, err := git.HashFromString(opts.OldCommitID) + return err == nil && hash.IsZero() } // IsDelRef return true if it's a deletion to a branch or tag func (opts *PushUpdateOptions) IsDelRef() bool { - return opts.NewCommitID == git.EmptySHA + hash, err := git.HashFromString(opts.NewCommitID) + return err == nil && hash.IsZero() } // IsUpdateRef return true if it's an update operation diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go index 0b259703deaaf..b077fafee029a 100644 --- a/routers/api/v1/repo/notes.go +++ b/routers/api/v1/repo/notes.go @@ -66,7 +66,7 @@ func getNote(ctx *context.APIContext, identifier string) { return } - commitSHA, err := ctx.Repo.GitRepo.ConvertToSHA1(identifier) + commitSHA, err := ctx.Repo.GitRepo.ConvertToGitHash(identifier) if err != nil { if git.IsErrNotExist(err) { ctx.NotFound(err) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 64c41d3a97e6c..b9e10e7170dad 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -253,6 +253,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre DefaultBranch: opt.DefaultBranch, TrustModel: repo_model.ToTrustModel(opt.TrustModel), IsTemplate: opt.Template, + HashType: "sha1", }) if err != nil { if repo_model.IsErrRepoAlreadyExist(err) { diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go index 32f5c85319d46..35105f8fb3d3a 100644 --- a/routers/api/v1/utils/git.go +++ b/routers/api/v1/utils/git.go @@ -70,21 +70,22 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str } // ConvertToSHA1 returns a full-length SHA1 from a potential ID string -func ConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) (git.SHA1, error) { - if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) { - sha1, err := git.NewIDFromString(commitID) +func ConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) (git.Hash, error) { + hashType := repo.GitRepo.Hash + if len(commitID) == hashType.FullLength() && hashType.IsValid(commitID) { + sha, err := hashType.NewIDFromString(commitID) if err == nil { - return sha1, nil + return sha, nil } } gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.Repository.RepoPath()) if err != nil { - return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err) + return gitRepo.Hash.Empty(), fmt.Errorf("RepositoryFromContextOrOpen: %w", err) } defer closer.Close() - return gitRepo.ConvertToSHA1(commitID) + return gitRepo.ConvertToGitHash(commitID) } // MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1 diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index d5a46e4e7fe45..1b39d6bce20df 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -158,9 +158,13 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { continue } - // If we've pushed a branch (and not deleted it) - if newCommitID != git.EmptySHA && refFullName.IsBranch() { + hash, err := git.HashFromString(newCommitID) + if err != nil { + hash = &git.Sha1Hash{} + } + // If we've pushed a branch (and not deleted it) + if newCommitID != hash.Type().Empty().String() && refFullName.IsBranch() { // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo if repo == nil { repo = loadRepository(ctx, ownerName, repoName) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 4399e49851871..0139e5cd26a6c 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -146,7 +146,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r repo := ctx.Repo.Repository gitRepo := ctx.Repo.GitRepo - if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA { + if branchName == repo.DefaultBranch && newCommitID == gitRepo.Hash.Empty().String() { log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo) ctx.JSON(http.StatusForbidden, private.Response{ UserMsg: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName), @@ -174,7 +174,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r // First of all we need to enforce absolutely: // // 1. Detect and prevent deletion of the branch - if newCommitID == git.EmptySHA { + if newCommitID == gitRepo.Hash.Empty().String() { log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) ctx.JSON(http.StatusForbidden, private.Response{ UserMsg: fmt.Sprintf("branch %s is protected from deletion", branchName), @@ -183,7 +183,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r } // 2. Disallow force pushes to protected branches - if git.EmptySHA != oldCommitID { + if oldCommitID != gitRepo.Hash.Empty().String() { output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env}) if err != nil { log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go index 8604789529a6e..8009feda6ea78 100644 --- a/routers/private/hook_verification.go +++ b/routers/private/hook_verification.go @@ -29,7 +29,7 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env [] }() var command *git.Command - if oldCommitID == git.EmptySHA { + if oldCommitID == repo.Hash.Empty().String() { // When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all": // List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits // So, it only lists the new commits received, doesn't list the commits already present in the receiving repository @@ -82,7 +82,7 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { _ = stdoutReader.Close() _ = stdoutWriter.Close() }() - hash := git.MustIDFromString(sha) + hash := repo.Hash.MustIDFromString(sha) return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha). Run(&git.RunOpts{ diff --git a/routers/private/hook_verification_test.go b/routers/private/hook_verification_test.go index cd0c05ff50a71..b8f62ce07de31 100644 --- a/routers/private/hook_verification_test.go +++ b/routers/private/hook_verification_test.go @@ -27,9 +27,9 @@ func TestVerifyCommits(t *testing.T) { verified bool }{ {"72920278f2f999e3005801e5d5b8ab8139d3641c", "d766f2917716d45be24bfa968b8409544941be32", true}, - {git.EmptySHA, "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit + {gitRepo.Hash.Empty().String(), "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit {"9779d17a04f1e2640583d35703c62460b2d86e0a", "72920278f2f999e3005801e5d5b8ab8139d3641c", false}, - {git.EmptySHA, "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit + {gitRepo.Hash.Empty().String(), "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit } for _, tc := range testCases { diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 1f1cca897ef5c..d4f5fd36b0637 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -131,7 +131,12 @@ type blameResult struct { } func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) { - blameReader, err := git.CreateBlameReader(ctx, repoPath, commit, file, bypassBlameIgnore) + hash, err := ctx.Repo.GitRepo.HashTypeInterface() + if err != nil { + ctx.NotFound("CreateBlameReader", err) + return nil, err + } + blameReader, err := git.CreateBlameReader(ctx, hash, repoPath, commit, file, bypassBlameIgnore) if err != nil { return nil, err } @@ -147,7 +152,7 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil if len(r.Parts) == 0 && r.UsesIgnoreRevs { // try again without ignored revs - blameReader, err = git.CreateBlameReader(ctx, repoPath, commit, file, true) + blameReader, err = git.CreateBlameReader(ctx, hash, repoPath, commit, file, true) if err != nil { return nil, err } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index bc72d5f2eca17..9b55383b31486 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -147,11 +147,18 @@ func RestoreBranchPost(ctx *context.Context) { return } + hash, err := git.GetHashTypeOfRepo(ctx, ctx.Repo.Repository.RepoPath()) + if err != nil { + log.Error("RestoreBranch: CreateBranch: %v", err) + ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name)) + return + } + // Don't return error below this if err := repo_service.PushUpdate( &repo_module.PushUpdateOptions{ RefFullName: git.RefNameFromBranch(deletedBranch.Name), - OldCommitID: git.EmptySHA, + OldCommitID: hash.Empty().String(), NewCommitID: deletedBranch.CommitID, PusherID: ctx.Doer.ID, PusherName: ctx.Doer.Name, diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 3587d287fc5ef..0e9219e2813bf 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -294,7 +294,7 @@ func Diff(ctx *context.Context) { } return } - if len(commitID) != git.SHAFullLength { + if len(commitID) != gitRepo.Hash.FullLength() { commitID = commit.ID.String() } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index b69af3c61cc54..e05747f19b2d6 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -316,7 +316,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ci.BaseBranch = baseCommit.ID.String() ctx.Data["BaseBranch"] = ci.BaseBranch baseIsCommit = true - } else if ci.BaseBranch == git.EmptySHA { + } else if ci.BaseBranch == ctx.Repo.GitRepo.Hash.Empty().String() { if isSameRepo { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch)) } else { diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 6ff385f989050..d2b2b214d7638 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -329,7 +329,7 @@ func dummyInfoRefs(ctx *context.Context) { } }() - if err := git.InitRepository(ctx, tmpDir, true); err != nil { + if err := git.InitRepository(ctx, tmpDir, true, "sha1"); err != nil { log.Error("Failed to init bare repo for git-receive-pack cache: %v", err) return } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b3b6b48871a08..8ff58c3eaea46 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -159,6 +159,7 @@ func Create(ctx *context.Context) { ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate ctx.Data["default_branch"] = setting.Repository.DefaultBranch + ctx.Data["hash_type"] = "sha1" ctxUser := checkContextUser(ctx, ctx.FormInt64("org")) if ctx.Written() { @@ -288,6 +289,7 @@ func CreatePost(ctx *context.Context) { AutoInit: form.AutoInit, IsTemplate: form.Template, TrustModel: repo_model.ToTrustModel(form.TrustModel), + HashType: form.HashType, }) if err == nil { log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index d478acdde0dbe..13f7f698a5689 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -388,13 +388,14 @@ func LFSFileFind(ctx *context.Context) { sha := ctx.FormString("sha") ctx.Data["Title"] = oid ctx.Data["PageIsSettingsLFS"] = true - var hash git.SHA1 + hashType := ctx.Repo.GitRepo.Hash + var hash git.Hash if len(sha) == 0 { pointer := lfs.Pointer{Oid: oid, Size: size} - hash = git.ComputeBlobHash([]byte(pointer.StringContent())) + hash = git.ComputeBlobHash(hashType, []byte(pointer.StringContent())) sha = hash.String() } else { - hash = git.MustIDFromString(sha) + hash = hashType.MustIDFromString(sha) } ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs" ctx.Data["Oid"] = oid diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index ea5abb0579206..5c8611412a6ae 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -654,8 +654,14 @@ func TestWebhook(ctx *context.Context) { commit := ctx.Repo.Commit if commit == nil { ghost := user_model.NewGhostUser() + hash, err := git.GetHashTypeOfRepo(ctx, ctx.Repo.Repository.RepoPath()) + if err != nil { + ctx.Flash.Error("GetHashTypeOfRepo: " + err.Error()) + ctx.Status(http.StatusInternalServerError) + return + } commit = &git.Commit{ - ID: git.MustIDFromString(git.EmptySHA), + ID: hash.NewEmptyID(), Author: ghost.NewGitSig(), Committer: ghost.NewGitSig(), CommitMessage: "This is a fake commit", diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 08a7dde67c85b..fea40253adb20 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" user_model "code.gitea.io/gitea/models/user" + git "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -114,9 +115,13 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er } creator := user_model.NewActionsUser() + hash, err := git.HashFromString(sha) + if err != nil { + return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err) + } if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ Repo: repo, - SHA: sha, + SHA: hash, Creator: creator, CommitStatus: &git_model.CommitStatus{ SHA: sha, diff --git a/services/agit/agit.go b/services/agit/agit.go index acfedf09d425d..f54912319b745 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -38,7 +38,7 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. _, forcePush = opts.GitPushOptions["force-push"] for i := range opts.OldCommitIDs { - if opts.NewCommitIDs[i] == git.EmptySHA { + if opts.NewCommitIDs[i] == gitRepo.Hash.Empty().String() { results = append(results, private.HookProcReceiveRefResult{ OriginalRef: opts.RefFullNames[i], OldOID: opts.OldCommitIDs[i], @@ -151,7 +151,7 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. results = append(results, private.HookProcReceiveRefResult{ Ref: pr.GetGitRefName(), OriginalRef: opts.RefFullNames[i], - OldOID: git.EmptySHA, + OldOID: gitRepo.Hash.Empty().String(), NewOID: opts.NewCommitIDs[i], }) continue diff --git a/services/convert/git_commit_test.go b/services/convert/git_commit_test.go index 8c4ef88ebe0eb..156ebfcec4927 100644 --- a/services/convert/git_commit_test.go +++ b/services/convert/git_commit_test.go @@ -19,7 +19,7 @@ import ( func TestToCommitMeta(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000") + sha1, _ := git.HashFromString("0000000000000000000000000000000000000000") signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} tag := &git.Tag{ Name: "Test Tag", diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 5df7ec8fd609a..ff5f2d7d9ac96 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -53,6 +53,7 @@ type CreateRepoForm struct { TrustModel string ForkSingleBranch string + HashType string } // Validate validates the fields diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 8bf6cba844d6b..02ad50c88cc76 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1115,10 +1115,10 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi } cmdDiff := git.NewCommand(gitRepo.Ctx) - if (len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA) && commit.ParentCount() == 0 { + if (len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == gitRepo.Hash.Empty().String()) && commit.ParentCount() == 0 { cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"). AddArguments(opts.WhitespaceBehavior...). - AddArguments("4b825dc642cb6eb9a060e54bf8d69288fbee4904"). // append empty tree ref + AddDynamicArguments(gitRepo.Hash.EmptyTree().String()). AddDynamicArguments(opts.AfterCommitID) } else { actualBeforeCommitID := opts.BeforeCommitID @@ -1224,8 +1224,8 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi } diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} - if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { - diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} + if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == gitRepo.Hash.Empty().String() { + diffPaths = []string{gitRepo.Hash.EmptyTree().String(), opts.AfterCommitID} } diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) if err != nil && strings.Contains(err.Error(), "no merge base") { @@ -1257,8 +1257,8 @@ func GetPullDiffStats(gitRepo *git.Repository, opts *DiffOptions) (*PullDiffStat } diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} - if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { - diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} + if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == gitRepo.Hash.Empty().String() { + diffPaths = []string{gitRepo.Hash.EmptyTree().String(), opts.AfterCommitID} } var err error diff --git a/services/migrations/common.go b/services/migrations/common.go index 4f9837472d9dc..8137042c7b2ea 100644 --- a/services/migrations/common.go +++ b/services/migrations/common.go @@ -48,16 +48,18 @@ func CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g bas } // SECURITY: SHAs Must be a SHA - if pr.MergeCommitSHA != "" && !git.IsValidSHAPattern(pr.MergeCommitSHA) { + // FIXME: hash only a SHA1 + hash := git.HashTypeFromID(git.Sha1) + if pr.MergeCommitSHA != "" && !hash.IsValid(pr.MergeCommitSHA) { WarnAndNotice("PR #%d in %s has invalid MergeCommitSHA: %s", pr.Number, g, pr.MergeCommitSHA) pr.MergeCommitSHA = "" } - if pr.Head.SHA != "" && !git.IsValidSHAPattern(pr.Head.SHA) { + if pr.Head.SHA != "" && !hash.IsValid(pr.Head.SHA) { WarnAndNotice("PR #%d in %s has invalid HeadSHA: %s", pr.Number, g, pr.Head.SHA) pr.Head.SHA = "" valid = false } - if pr.Base.SHA != "" && !git.IsValidSHAPattern(pr.Base.SHA) { + if pr.Base.SHA != "" && !hash.IsValid(pr.Base.SHA) { WarnAndNotice("PR #%d in %s has invalid BaseSHA: %s", pr.Number, g, pr.Base.SHA) pr.Base.SHA = "" valid = false diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index ddc2cbd4ec832..ad86961e32c55 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -892,7 +892,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { comment.UpdatedAt = comment.CreatedAt } - if !git.IsValidSHAPattern(comment.CommitID) { + if !g.gitRepo.Hash.IsValid(comment.CommitID) { log.Warn("Invalid comment CommitID[%s] on comment[%d] in PR #%d of %s/%s replaced with %s", comment.CommitID, pr.Index, g.repoOwner, g.repoName, headCommitID) comment.CommitID = headCommitID } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 84db83bc67080..df99bc3ba9e32 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -232,7 +232,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { // fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) baseRef := "master" - assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false)) + assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.HashType)) err := git.NewCommand(git.DefaultContext, "symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(&git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644)) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index be426d4312894..a3e54ea2445db 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -478,9 +478,13 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo: %-v]: unable to GetRefCommitID [ref_name: %s]: %v", m.Repo, result.refName, err) continue } + hash, err := git.GetHashTypeOfRepo(ctx, m.Repo.RepoPath()) + if err != nil { + log.Error("SyncMirrors [repo: %-v]: unable to GetHashTypeOfRepo: %v", m.Repo, err) + } notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{ RefFullName: result.refName, - OldCommitID: git.EmptySHA, + OldCommitID: hash.Empty().String(), NewCommitID: commitID, }, repo_module.NewPushCommits()) notify_service.SyncCreateRef(ctx, m.Repo.MustOwner(ctx), m.Repo, result.refName, commitID) diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index 8164ffb01cb28..7701bc31f5703 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -271,7 +271,7 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { return err } - if err := t.Init(); err != nil { + if err := t.Init(repo.HashType); err != nil { return err } } else { diff --git a/services/pull/check.go b/services/pull/check.go index b51b58f480f3d..a07b9e860e08a 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -221,7 +221,7 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) if err != nil { return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err) - } else if len(mergeCommit) < git.SHAFullLength { + } else if _, err := git.HashFromString(mergeCommit); err == nil { // PR was maybe fast-forwarded, so just use last commit of PR mergeCommit = prHeadCommitID } diff --git a/services/pull/merge.go b/services/pull/merge.go index 33c7455c08773..897b1c6cb980d 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -486,7 +486,7 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} } - if len(commitID) < git.SHAFullLength { + if len(commitID) != baseGitRepo.Hash.FullLength() { return fmt.Errorf("Wrong commit ID") } diff --git a/services/pull/patch.go b/services/pull/patch.go index 688cbcc027db2..5faa06e400df4 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -145,7 +145,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g // 2. Added in ours but not in theirs or identical in both // // Not a genuine conflict just add to the index - if err := gitRepo.AddObjectToIndex(file.stage2.mode, git.MustIDFromString(file.stage2.sha), file.stage2.path); err != nil { + if err := gitRepo.AddObjectToIndex(file.stage2.mode, gitRepo.Hash.MustIDFromString(file.stage2.sha), file.stage2.path); err != nil { return err } return nil @@ -158,7 +158,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g // 4. Added in theirs but not ours: // // Not a genuine conflict just add to the index - return gitRepo.AddObjectToIndex(file.stage3.mode, git.MustIDFromString(file.stage3.sha), file.stage3.path) + return gitRepo.AddObjectToIndex(file.stage3.mode, gitRepo.Hash.MustIDFromString(file.stage3.sha), file.stage3.path) case file.stage1 == nil: // 5. Created by new in both // @@ -219,7 +219,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g return err } hash = strings.TrimSpace(hash) - return gitRepo.AddObjectToIndex(file.stage2.mode, git.MustIDFromString(hash), file.stage2.path) + return gitRepo.AddObjectToIndex(file.stage2.mode, gitRepo.Hash.MustIDFromString(hash), file.stage2.path) default: if file.stage1 != nil { return &errMergeConflict{file.stage1.path} diff --git a/services/pull/pull.go b/services/pull/pull.go index 2f5143903aa83..74a54acb38647 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -327,7 +327,8 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, } if err == nil { for _, pr := range prs { - if newCommitID != "" && newCommitID != git.EmptySHA { + hash, _ := git.GetHashTypeOfRepo(ctx, pr.BaseRepo.RepoPath()) + if newCommitID != "" && newCommitID != hash.Empty().String() { changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID) if err != nil { log.Error("checkIfPRContentChanged: %v", err) diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index db32940e3835a..cb39ba0f034ef 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -93,8 +93,14 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) baseRepoPath := pr.BaseRepo.RepoPath() headRepoPath := pr.HeadRepo.RepoPath() + hash, err := git.GetHashTypeOfRepo(ctx, baseRepoPath) + if err != nil { + log.Error("Unable to fetch hash of repository %s: %v", baseRepoPath, err) + cancel() + return nil, nil, err + } - if err := git.InitRepository(ctx, tmpBasePath, false); err != nil { + if err := git.InitRepository(ctx, tmpBasePath, false, hash.String()); err != nil { log.Error("Unable to init tmpBasePath for %-v: %v", pr, err) cancel() return nil, nil, err @@ -172,7 +178,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) var headBranch string if pr.Flow == issues_model.PullRequestFlowGithub { headBranch = git.BranchPrefix + pr.HeadBranch - } else if len(pr.HeadCommitID) == git.SHAFullLength { // for not created pull request + } else if len(pr.HeadCommitID) == hash.FullLength() { // for not created pull request headBranch = pr.HeadCommitID } else { headBranch = pr.GetGitRefName() diff --git a/services/release/release.go b/services/release/release.go index e0035d42fc2a7..f5177bb477c70 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -88,14 +88,14 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel commits := repository.NewPushCommits() commits.HeadCommit = repository.CommitToPushCommit(commit) - commits.CompareURL = rel.Repo.ComposeCompareURL(git.EmptySHA, commit.ID.String()) + commits.CompareURL = rel.Repo.ComposeCompareURL(gitRepo.Hash.Empty().String(), commit.ID.String()) refFullName := git.RefNameFromTag(rel.TagName) notify_service.PushCommits( ctx, rel.Publisher, rel.Repo, &repository.PushUpdateOptions{ RefFullName: refFullName, - OldCommitID: git.EmptySHA, + OldCommitID: gitRepo.Hash.Empty().String(), NewCommitID: commit.ID.String(), }, commits) notify_service.CreateRef(ctx, rel.Publisher, rel.Repo, refFullName, commit.ID.String()) @@ -335,12 +335,16 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del } refName := git.RefNameFromTag(rel.TagName) + hash, err := git.GetHashTypeOfRepo(ctx, repo.RepoPath()) + if err != nil { + return err + } notify_service.PushCommits( ctx, doer, repo, &repository.PushUpdateOptions{ RefFullName: refName, OldCommitID: rel.Sha1, - NewCommitID: git.EmptySHA, + NewCommitID: hash.Empty().String(), }, repository.NewPushCommits()) notify_service.DeleteRef(ctx, doer, repo, refName) diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 9f1ea48dca400..ed0098a79638f 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "os" - "regexp" "strings" "time" @@ -36,10 +35,6 @@ type ArchiveRequest struct { CommitID string } -// SHA1 hashes will only go up to 40 characters, but SHA256 hashes will go all -// the way to 64. -var shaRegex = regexp.MustCompile(`^[0-9a-f]{4,64}$`) - // ErrUnknownArchiveFormat request archive format is not supported type ErrUnknownArchiveFormat struct { RequestFormat string @@ -96,30 +91,13 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest r.refName = strings.TrimSuffix(uri, ext) - var err error // Get corresponding commit. - if repo.IsBranchExist(r.refName) { - r.CommitID, err = repo.GetBranchCommitID(r.refName) - if err != nil { - return nil, err - } - } else if repo.IsTagExist(r.refName) { - r.CommitID, err = repo.GetTagCommitID(r.refName) - if err != nil { - return nil, err - } - } else if shaRegex.MatchString(r.refName) { - if repo.IsCommitExist(r.refName) { - r.CommitID = r.refName - } else { - return nil, git.ErrNotExist{ - ID: r.refName, - } - } - } else { + CommitHash, err := repo.ConvertToGitHash(r.refName) + if err != nil { return nil, RepoRefNotFoundError{RefName: r.refName} } + r.CommitID = CommitHash.String() return r, nil } diff --git a/services/repository/branch.go b/services/repository/branch.go index 735fa1a756816..fb570fd79613a 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -378,7 +378,7 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R &repo_module.PushUpdateOptions{ RefFullName: git.RefNameFromBranch(branchName), OldCommitID: commit.ID.String(), - NewCommitID: git.EmptySHA, + NewCommitID: gitRepo.Hash.Empty().String(), PusherID: doer.ID, PusherName: doer.Name, RepoUserName: repo.OwnerName, diff --git a/services/repository/check.go b/services/repository/check.go index 2f26d030c33a2..4533996078cfd 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -192,7 +192,7 @@ func ReinitMissingRepositories(ctx context.Context) error { default: } log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) - if err := git.InitRepository(ctx, repo.RepoPath(), true); err != nil { + if err := git.InitRepository(ctx, repo.RepoPath(), true, repo.HashType); err != nil { log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err) if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil { log.Error("CreateRepositoryNotice: %v", err2) diff --git a/services/repository/create.go b/services/repository/create.go index b6b6454c44178..50bb7fa1701dd 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -43,6 +43,7 @@ type CreateRepoOptions struct { Status repo_model.RepositoryStatus TrustModel repo_model.TrustModelType MirrorInterval string + HashType string } func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { @@ -134,7 +135,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, // InitRepository initializes README and .gitignore if needed. func initRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) { - if err = repo_module.CheckInitRepository(ctx, repo.OwnerName, repo.Name); err != nil { + if err = repo_module.CheckInitRepository(ctx, repo.OwnerName, repo.Name, opts.HashType); err != nil { return err } @@ -205,6 +206,10 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt } } + if len(opts.HashType) == 0 { + opts.HashType = "sha1" + } + if len(opts.DefaultBranch) == 0 { opts.DefaultBranch = setting.Repository.DefaultBranch } @@ -234,6 +239,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt TrustModel: opts.TrustModel, IsMirror: opts.IsMirror, DefaultBranch: opts.DefaultBranch, + HashType: opts.HashType, } var rollbackRepo *repo_model.Repository diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go index c1c5bfb617689..9c488a69ba508 100644 --- a/services/repository/files/cherry_pick.go +++ b/services/repository/files/cherry_pick.go @@ -48,7 +48,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod if opts.LastCommitID == "" { opts.LastCommitID = commit.ID.String() } else { - lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID) + lastCommitID, err := t.gitRepo.ConvertToGitHash(opts.LastCommitID) if err != nil { return nil, fmt.Errorf("CherryPick: Invalid last commit ID: %w", err) } @@ -67,7 +67,8 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod } parent, err := commit.ParentID(0) if err != nil { - parent = git.MustIDFromString(git.EmptyTreeSHA) + hash, _ := git.HashTypeFromString(repo.HashType) + parent = hash.EmptyTree() } base, right := parent.String(), commit.ID.String() diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go index 3e4627487be88..7e0bfea936b3b 100644 --- a/services/repository/files/commit.go +++ b/services/repository/files/commit.go @@ -29,10 +29,11 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato } defer closer.Close() - if commit, err := gitRepo.GetCommit(sha); err != nil { + commit, err := gitRepo.GetCommit(sha) + if err != nil { gitRepo.Close() return fmt.Errorf("GetCommit[%s]: %w", sha, err) - } else if len(sha) != git.SHAFullLength { + } else if len(sha) != gitRepo.Hash.FullLength() { // use complete commit sha sha = commit.ID.String() } @@ -41,7 +42,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ Repo: repo, Creator: creator, - SHA: sha, + SHA: commit.ID, CommitStatus: status, }); err != nil { return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 5ef0619636e77..3f7fb25b2dfdd 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -130,7 +130,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user if opts.LastCommitID == "" { opts.LastCommitID = commit.ID.String() } else { - lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID) + lastCommitID, err := t.gitRepo.ConvertToGitHash(opts.LastCommitID) if err != nil { return nil, fmt.Errorf("ApplyPatch: Invalid last commit ID: %w", err) } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index be13b2016a1ec..639b073b0d341 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -77,8 +77,8 @@ func (t *TemporaryUploadRepository) Clone(branch string) error { } // Init the repository -func (t *TemporaryUploadRepository) Init() error { - if err := git.InitRepository(t.ctx, t.basePath, false); err != nil { +func (t *TemporaryUploadRepository) Init(hashType string) error { + if err := git.InitRepository(t.ctx, t.basePath, false, hashType); err != nil { return err } gitRepo, err := git.OpenRepository(t.ctx, t.basePath) diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 0b1d304845eb2..04ffbc2fb9083 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -37,19 +37,20 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git } apiURL := repo.APIURL() apiURLLen := len(apiURL) + hashLen := gitRepo.Hash.FullLength() - // 51 is len(sha1) + len("/git/blobs/"). 40 + 11. - blobURL := make([]byte, apiURLLen+51) + const gitBlobsPath = "/git/blobs/" + blobURL := make([]byte, apiURLLen+hashLen+len(gitBlobsPath)) copy(blobURL, apiURL) - copy(blobURL[apiURLLen:], "/git/blobs/") + copy(blobURL[apiURLLen:], []byte(gitBlobsPath)) - // 51 is len(sha1) + len("/git/trees/"). 40 + 11. - treeURL := make([]byte, apiURLLen+51) + const gitTreePath = "/git/trees/" + treeURL := make([]byte, apiURLLen+hashLen+len(gitTreePath)) copy(treeURL, apiURL) - copy(treeURL[apiURLLen:], "/git/trees/") + copy(treeURL[apiURLLen:], []byte(gitTreePath)) - // 40 is the size of the sha1 hash in hexadecimal format. - copyPos := len(treeURL) - git.SHAFullLength + // copyPos is at the start of the hash + copyPos := len(treeURL) - hashLen if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage { perPage = setting.API.DefaultGitTreesPerPage diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 2a08bcbaceac2..56109bc12ddce 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -155,7 +155,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { return nil, err } - if err := t.Init(); err != nil { + if err := t.Init(gitRepo.Hash.String()); err != nil { return nil, err } hasOldBranch = false @@ -202,7 +202,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use if opts.LastCommitID == "" { opts.LastCommitID = commit.ID.String() } else { - lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID) + lastCommitID, err := t.gitRepo.ConvertToGitHash(opts.LastCommitID) if err != nil { return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err) } diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index f4e1da7bb1a35..7283f234621ec 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -91,7 +91,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { return err } - if err = t.Init(); err != nil { + if err = t.Init(repo.HashType); err != nil { return err } hasOldBranch = false diff --git a/services/repository/lfs.go b/services/repository/lfs.go index 8e654b6f13bcd..777122f0e8e81 100644 --- a/services/repository/lfs.go +++ b/services/repository/lfs.go @@ -84,7 +84,7 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R return errStop } total++ - pointerSha := git.ComputeBlobHash([]byte(metaObject.Pointer.StringContent())) + pointerSha := git.ComputeBlobHash(gitRepo.Hash, []byte(metaObject.Pointer.StringContent())) if gitRepo.IsObjectExist(pointerSha.String()) { return git_model.MarkLFSMetaObject(ctx, metaObject.ID) diff --git a/services/repository/push.go b/services/repository/push.go index 97da45f52b4bf..f9c7bfe89f02f 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -63,7 +63,7 @@ func PushUpdates(opts []*repo_module.PushUpdateOptions) error { for _, opt := range opts { if opt.IsNewRef() && opt.IsDelRef() { - return fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) + return fmt.Errorf("Old and new revisions are both NULL") } } @@ -92,6 +92,11 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } defer gitRepo.Close() + hash, err := gitRepo.HashTypeInterface() + if err != nil { + return fmt.Errorf("unknown Hash type for repository [%s]: %w", repoPath, err) + } + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { return fmt.Errorf("Failed to update size for repository: %v", err) } @@ -104,7 +109,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { log.Trace("pushUpdates: %-v %s %s %s", repo, opts.OldCommitID, opts.NewCommitID, opts.RefFullName) if opts.IsNewRef() && opts.IsDelRef() { - return fmt.Errorf("old and new revisions are both %s", git.EmptySHA) + return fmt.Errorf("old and new revisions are both %s", hash.Empty()) } if opts.RefFullName.IsTag() { if pusher == nil || pusher.ID != opts.PusherID { @@ -124,7 +129,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { &repo_module.PushUpdateOptions{ RefFullName: git.RefNameFromTag(tagName), OldCommitID: opts.OldCommitID, - NewCommitID: git.EmptySHA, + NewCommitID: hash.Empty().String(), }, repo_module.NewPushCommits()) delTags = append(delTags, tagName) @@ -137,13 +142,13 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { commits := repo_module.NewPushCommits() commits.HeadCommit = repo_module.CommitToPushCommit(newCommit) - commits.CompareURL = repo.ComposeCompareURL(git.EmptySHA, opts.NewCommitID) + commits.CompareURL = repo.ComposeCompareURL(hash.Empty().String(), opts.NewCommitID) notify_service.PushCommits( ctx, pusher, repo, &repo_module.PushUpdateOptions{ RefFullName: opts.RefFullName, - OldCommitID: git.EmptySHA, + OldCommitID: hash.Empty().String(), NewCommitID: opts.NewCommitID, }, commits) @@ -227,7 +232,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } oldCommitID := opts.OldCommitID - if oldCommitID == git.EmptySHA && len(commits.Commits) > 0 { + if oldCommitID == hash.Empty().String() && len(commits.Commits) > 0 { oldCommit, err := gitRepo.GetCommit(commits.Commits[len(commits.Commits)-1].Sha1) if err != nil && !git.IsErrNotExist(err) { log.Error("unable to GetCommit %s from %-v: %v", oldCommitID, repo, err) @@ -243,11 +248,11 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } } - if oldCommitID == git.EmptySHA && repo.DefaultBranch != branch { + if oldCommitID == hash.Empty().String() && repo.DefaultBranch != branch { oldCommitID = repo.DefaultBranch } - if oldCommitID != git.EmptySHA { + if oldCommitID != hash.Empty().String() { commits.CompareURL = repo.ComposeCompareURL(oldCommitID, opts.NewCommitID) } else { commits.CompareURL = "" diff --git a/services/webhook/slack.go b/services/webhook/slack.go index ac27b5bc71bcd..945b0662d84d3 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -92,6 +92,7 @@ func SlackLinkFormatter(url, text string) string { // SlackLinkToRef slack-formatter link to a repo ref func SlackLinkToRef(repoURL, ref string) string { + // FIXME: SHA1 hardcoded here url := git.RefURL(repoURL, ref) refName := git.RefName(ref).ShortName() return SlackLinkFormatter(url, refName) diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 18371efd0988c..4cb31a86143c7 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -36,7 +36,7 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error { return nil } - if err := git.InitRepository(ctx, repo.WikiPath(), true); err != nil { + if err := git.InitRepository(ctx, repo.WikiPath(), true, "sha1"); err != nil { return fmt.Errorf("InitRepository: %w", err) } else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { return fmt.Errorf("createDelegateHooks: %w", err) diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index e8da176a08d2c..4c861dd76d6dd 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -302,7 +302,7 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) { // Now create a temporaryDirectory tmpDir := t.TempDir() - err := git.InitRepository(git.DefaultContext, tmpDir, true) + err := git.InitRepository(git.DefaultContext, tmpDir, true, "sha1") assert.NoError(t, err) gitRepo, err := git.OpenRepository(git.DefaultContext, tmpDir) diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 10cf79b9fd8b2..fae644bfc5f1b 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -120,7 +120,7 @@ func doGitCloneFail(u *url.URL) func(*testing.T) { func doGitInitTestRepository(dstPath string) func(*testing.T) { return func(t *testing.T) { // Init repository in dstPath - assert.NoError(t, git.InitRepository(git.DefaultContext, dstPath, false)) + assert.NoError(t, git.InitRepository(git.DefaultContext, dstPath, false, "sha1")) // forcibly set default branch to master _, _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err)