Abstract hash function usage (#28138)

Refactor Hash interfaces and centralize hash function. This will allow
easier introduction of different hash function later on.

This forms the "no-op" part of the SHA256 enablement patch.
pull/28392/head^2
Adam Majer 12 months ago committed by GitHub
parent 064f05204c
commit cbf923e87b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      cmd/hook.go
  2. 2
      models/git/branch_test.go
  3. 13
      models/git/commit_status.go
  4. 6
      models/repo/repo.go
  5. 9
      modules/context/api.go
  6. 18
      modules/context/repo.go
  7. 30
      modules/git/batch_reader.go
  8. 39
      modules/git/blame.go
  9. 4
      modules/git/blame_test.go
  10. 2
      modules/git/blob_gogit.go
  11. 2
      modules/git/blob_nogogit.go
  12. 19
      modules/git/commit.go
  13. 4
      modules/git/commit_convert_gogit.go
  14. 2
      modules/git/commit_info_gogit.go
  15. 2
      modules/git/commit_info_nogogit.go
  16. 8
      modules/git/commit_reader.go
  17. 6
      modules/git/commit_test.go
  18. 10
      modules/git/last_commit_cache.go
  19. 3
      modules/git/last_commit_cache_gogit.go
  20. 13
      modules/git/log_name_status.go
  21. 3
      modules/git/notes_gogit.go
  22. 103
      modules/git/object_format.go
  23. 143
      modules/git/object_id.go
  24. 28
      modules/git/object_id_gogit.go
  25. 21
      modules/git/object_id_test.go
  26. 16
      modules/git/parse_gogit.go
  27. 20
      modules/git/parse_gogit_test.go
  28. 16
      modules/git/parse_nogogit.go
  29. 22
      modules/git/parse_nogogit_test.go
  30. 14
      modules/git/pipeline/lfs.go
  31. 39
      modules/git/pipeline/lfs_nogogit.go
  32. 4
      modules/git/ref.go
  33. 31
      modules/git/repo.go
  34. 3
      modules/git/repo_base_gogit.go
  35. 7
      modules/git/repo_base_nogogit.go
  36. 2
      modules/git/repo_blob.go
  37. 4
      modules/git/repo_blob_gogit.go
  38. 2
      modules/git/repo_blob_nogogit.go
  39. 2
      modules/git/repo_blob_test.go
  40. 35
      modules/git/repo_commit.go
  41. 35
      modules/git/repo_commit_gogit.go
  42. 25
      modules/git/repo_commit_nogogit.go
  43. 2
      modules/git/repo_compare.go
  44. 6
      modules/git/repo_compare_test.go
  45. 2
      modules/git/repo_gpg.go
  46. 19
      modules/git/repo_index.go
  47. 2
      modules/git/repo_language_stats_nogogit.go
  48. 44
      modules/git/repo_object.go
  49. 4
      modules/git/repo_ref_gogit.go
  50. 2
      modules/git/repo_ref_nogogit.go
  51. 14
      modules/git/repo_tag.go
  52. 12
      modules/git/repo_tag_gogit.go
  53. 8
      modules/git/repo_tag_nogogit.go
  54. 15
      modules/git/repo_tag_test.go
  55. 6
      modules/git/repo_tree.go
  56. 14
      modules/git/repo_tree_gogit.go
  57. 10
      modules/git/repo_tree_nogogit.go
  58. 72
      modules/git/sha1.go
  59. 19
      modules/git/sha1_gogit.go
  60. 61
      modules/git/sha1_nogogit.go
  61. 20
      modules/git/sha1_test.go
  62. 10
      modules/git/tag.go
  63. 10
      modules/git/tag_test.go
  64. 2
      modules/git/tree.go
  65. 2
      modules/git/tree_blob_gogit.go
  66. 4
      modules/git/tree_entry_gogit.go
  67. 2
      modules/git/tree_entry_nogogit.go
  68. 10
      modules/git/tree_gogit.go
  69. 10
      modules/git/tree_nogogit.go
  70. 17
      modules/indexer/code/git.go
  71. 7
      modules/repository/commits_test.go
  72. 6
      modules/repository/generate.go
  73. 4
      modules/repository/init.go
  74. 6
      modules/repository/push.go
  75. 4
      routers/api/v1/repo/notes.go
  76. 1
      routers/api/v1/repo/repo.go
  77. 17
      routers/api/v1/utils/git.go
  78. 3
      routers/private/hook_post_receive.go
  79. 7
      routers/private/hook_pre_receive.go
  80. 8
      routers/private/hook_verification.go
  81. 7
      routers/private/hook_verification_test.go
  82. 9
      routers/web/repo/blame.go
  83. 9
      routers/web/repo/branch.go
  84. 2
      routers/web/repo/commit.go
  85. 3
      routers/web/repo/compare.go
  86. 2
      routers/web/repo/githttp.go
  87. 2
      routers/web/repo/repo.go
  88. 11
      routers/web/repo/setting/lfs.go
  89. 8
      routers/web/repo/setting/webhook.go
  90. 7
      services/actions/commit_status.go
  91. 6
      services/agit/agit.go
  92. 10
      services/convert/git_commit_test.go
  93. 2
      services/forms/repo_form.go
  94. 24
      services/gitdiff/gitdiff.go
  95. 8
      services/migrations/common.go
  96. 3
      services/migrations/gitea_uploader.go
  97. 2
      services/migrations/gitea_uploader_test.go
  98. 6
      services/mirror/mirror_pull.go
  99. 2
      services/packages/cargo/index.go
  100. 19
      services/pull/check.go
  101. Some files were not shown because too many files have changed in this diff Show More

@ -376,7 +376,9 @@ Gitea or set your environment appropriately.`, "")
oldCommitIDs[count] = string(fields[0]) oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1]) newCommitIDs[count] = string(fields[1])
refFullNames[count] = git.RefName(fields[2]) refFullNames[count] = git.RefName(fields[2])
if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
commitID, _ := git.IDFromString(newCommitIDs[count])
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
masterPushed = true masterPushed = true
} }
count++ count++
@ -669,7 +671,8 @@ Gitea or set your environment appropriately.`, "")
if err != nil { if err != nil {
return err return err
} }
if rs.OldOID != git.EmptySHA { commitID, _ := git.IDFromString(rs.OldOID)
if !commitID.IsZero() {
err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID)) err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID))
if err != nil { if err != nil {
return err return err

@ -30,7 +30,7 @@ func TestAddDeletedBranch(t *testing.T) {
assert.True(t, secondBranch.IsDeleted) assert.True(t, secondBranch.IsDeleted)
commit := &git.Commit{ commit := &git.Commit{
ID: git.MustIDFromString(secondBranch.CommitID), ID: repo.ObjectFormat.MustIDFromString(secondBranch.CommitID),
CommitMessage: secondBranch.CommitMessage, CommitMessage: secondBranch.CommitMessage,
Committer: &git.Signature{ Committer: &git.Signature{
When: secondBranch.CommitTime.AsLocalTime(), When: secondBranch.CommitTime.AsLocalTime(),

@ -114,7 +114,8 @@ WHEN NOT MATCHED
// GetNextCommitStatusIndex retried 3 times to generate a resource index // GetNextCommitStatusIndex retried 3 times to generate a resource index
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) { func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
if !git.IsValidSHAPattern(sha) { _, err := git.IDFromString(sha)
if err != nil {
return 0, git.ErrInvalidSHA{SHA: sha} return 0, git.ErrInvalidSHA{SHA: sha}
} }
@ -425,7 +426,7 @@ func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, befor
type NewCommitStatusOptions struct { type NewCommitStatusOptions struct {
Repo *repo_model.Repository Repo *repo_model.Repository
Creator *user_model.User Creator *user_model.User
SHA string SHA git.ObjectID
CommitStatus *CommitStatus CommitStatus *CommitStatus
} }
@ -440,10 +441,6 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error {
return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA) return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
} }
if _, err := git.NewIDFromString(opts.SHA); err != nil {
return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err)
}
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
if err != nil { 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, err)
@ -451,7 +448,7 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error {
defer committer.Close() defer committer.Close()
// Get the next Status Index // 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 { if err != nil {
return fmt.Errorf("generate commit status index failed: %w", err) return fmt.Errorf("generate commit status index failed: %w", err)
} }
@ -459,7 +456,7 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error {
opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description) opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context) opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL) 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.CreatorID = opts.Creator.ID
opts.CommitStatus.RepoID = opts.Repo.ID opts.CommitStatus.RepoID = opts.Repo.ID
opts.CommitStatus.Index = idx opts.CommitStatus.Index = idx

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -179,6 +180,7 @@ type Repository struct {
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"` Topics []string `xorm:"TEXT JSON"`
ObjectFormat git.ObjectFormat `xorm:"-"`
TrustModel TrustModelType TrustModel TrustModelType
@ -274,6 +276,8 @@ func (repo *Repository) AfterLoad() {
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns
repo.ObjectFormat = git.ObjectFormatFromID(git.Sha1)
} }
// LoadAttributes loads attributes of the repository. // LoadAttributes loads attributes of the repository.
@ -313,7 +317,7 @@ func (repo *Repository) HTMLURL() string {
// CommitLink make link to by commit full ID // CommitLink make link to by commit full ID
// note: won't check whether it's an right id // note: won't check whether it's an right id
func (repo *Repository) CommitLink(commitID string) (result string) { func (repo *Repository) CommitLink(commitID string) (result string) {
if commitID == "" || commitID == "0000000000000000000000000000000000000000" { if git.IsEmptyCommitID(commitID) {
result = "" result = ""
} else { } else {
result = repo.Link() + "/commit/" + url.PathEscape(commitID) result = repo.Link() + "/commit/" + url.PathEscape(commitID)

@ -308,6 +308,12 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return return
} }
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
if ref := ctx.FormTrim("ref"); len(ref) > 0 { if ref := ctx.FormTrim("ref"); len(ref) > 0 {
commit, err := ctx.Repo.GitRepo.GetCommit(ref) commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil { if err != nil {
@ -325,7 +331,6 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return return
} }
var err error
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny) refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
if ctx.Repo.GitRepo.IsBranchExist(refName) { if ctx.Repo.GitRepo.IsBranchExist(refName) {
@ -342,7 +347,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return return
} }
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if len(refName) == git.SHAFullLength { } else if len(refName) == objectFormat.FullLength() {
ctx.Repo.CommitID = refName ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil { if err != nil {

@ -825,7 +825,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
} }
// For legacy and API support only full commit sha // For legacy and API support only full commit sha
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
if len(parts) > 0 && len(parts[0]) == git.SHAFullLength { objectFormat, _ := repo.GitRepo.GetObjectFormat()
if len(parts) > 0 && len(parts[0]) == objectFormat.FullLength() {
repo.TreePath = strings.Join(parts[1:], "/") repo.TreePath = strings.Join(parts[1:], "/")
return parts[0] return parts[0]
} }
@ -869,7 +871,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist) return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit: case RepoRefCommit:
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength { objectFormat, _ := repo.GitRepo.GetObjectFormat()
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= objectFormat.FullLength() {
repo.TreePath = strings.Join(parts[1:], "/") repo.TreePath = strings.Join(parts[1:], "/")
return parts[0] return parts[0]
} }
@ -929,6 +933,12 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
} }
} }
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
if err != nil {
log.Error("Cannot determine objectFormat for repository: %w", err)
ctx.Repo.Repository.MarkAsBrokenEmpty()
}
// Get default branch. // Get default branch.
if len(ctx.Params("*")) == 0 { if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch refName = ctx.Repo.Repository.DefaultBranch
@ -995,7 +1005,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
return cancel return cancel
} }
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if len(refName) >= 7 && len(refName) <= git.SHAFullLength { } else if len(refName) >= 7 && len(refName) <= objectFormat.FullLength() {
ctx.Repo.IsViewCommit = true ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName ctx.Repo.CommitID = refName
@ -1005,7 +1015,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
return cancel return cancel
} }
// If short commit ID add canonical link header // If short commit ID add canonical link header
if len(refName) < git.SHAFullLength { if len(refName) < objectFormat.FullLength() {
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", 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)))) util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
} }

@ -148,7 +148,7 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
// ReadBatchLine reads the header line from cat-file --batch // ReadBatchLine reads the header line from cat-file --batch
// We expect: // We expect:
// <sha> SP <type> SP <size> LF // <sha> SP <type> SP <size> 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) { func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
typ, err = rd.ReadString('\n') typ, err = rd.ReadString('\n')
if err != nil { if err != nil {
@ -251,20 +251,19 @@ headerLoop:
} }
// git tree files are a list: // git tree files are a list:
// <mode-in-ascii> SP <fname> NUL <20-byte SHA> // <mode-in-ascii> SP <fname> NUL <binary Hash>
// //
// Unfortunately this 20-byte notation is somewhat in conflict to all other git tools // 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" const hextable = "0123456789abcdef"
// To40ByteSHA converts a 20-byte SHA into a 40-byte sha. Input and output can be the // BinToHexHeash converts a binary Hash into a hex encoded one. Input and output can be the
// same 40 byte slice to support in place conversion without allocations. // same byte slice to support in place conversion without allocations.
// This is at least 100x quicker that hex.EncodeToString // This is at least 100x quicker that hex.EncodeToString
// NB This requires that out is a 40-byte slice func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
func To40ByteSHA(sha, out []byte) []byte { for i := objectFormat.FullLength()/2 - 1; i >= 0; i-- {
for i := 19; i >= 0; i-- {
v := sha[i] v := sha[i]
vhi, vlo := v>>4, v&0x0f vhi, vlo := v>>4, v&0x0f
shi, slo := hextable[vhi], hextable[vlo] 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 // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
// //
// Each line is composed of: // Each line is composed of:
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <20-byte SHA> // <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH>
// //
// We don't attempt to convert the 20-byte SHA to 40-byte SHA to save a lot of time // We don't attempt to convert the raw HASH to save a lot of time
func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
var readBytes []byte var readBytes []byte
// Read the Mode & fname // Read the Mode & fname
@ -324,11 +323,12 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn
fnameBuf = fnameBuf[:len(fnameBuf)-1] fnameBuf = fnameBuf[:len(fnameBuf)-1]
fname = fnameBuf fname = fnameBuf
// Deal with the 20-byte SHA // Deal with the binary hash
idx = 0 idx = 0
for idx < 20 { len := objectFormat.FullLength() / 2
for idx < len {
var read int var read int
read, err = rd.Read(shaBuf[idx:20]) read, err = rd.Read(shaBuf[idx:len])
n += read n += read
if err != nil { if err != nil {
return mode, fname, sha, n, err return mode, fname, sha, n, err

@ -10,8 +10,6 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"regexp"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -33,14 +31,13 @@ type BlameReader struct {
done chan error done chan error
lastSha *string lastSha *string
ignoreRevsFile *string ignoreRevsFile *string
objectFormat ObjectFormat
} }
func (r *BlameReader) UsesIgnoreRevs() bool { func (r *BlameReader) UsesIgnoreRevs() bool {
return r.ignoreRevsFile != nil 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) // NextPart returns next part of blame (sequential code lines with the same commit)
func (r *BlameReader) NextPart() (*BlamePart, error) { func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart var blamePart *BlamePart
@ -52,6 +49,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
} }
} }
const previousHeader = "previous "
var lineBytes []byte var lineBytes []byte
var isPrefix bool var isPrefix bool
var err error var err error
@ -67,21 +65,22 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
continue continue
} }
line := string(lineBytes) var objectID string
objectFormatLength := r.objectFormat.FullLength()
lines := shaLineRegex.FindStringSubmatch(line)
if lines != nil {
sha1 := lines[1]
if len(lineBytes) > objectFormatLength && lineBytes[objectFormatLength] == ' ' && r.objectFormat.IsValid(string(lineBytes[0:objectFormatLength])) {
objectID = string(lineBytes[0:objectFormatLength])
}
if len(objectID) > 0 {
if blamePart == nil { if blamePart == nil {
blamePart = &BlamePart{ blamePart = &BlamePart{
Sha: sha1, Sha: objectID,
Lines: make([]string, 0), Lines: make([]string, 0),
} }
} }
if blamePart.Sha != sha1 { if blamePart.Sha != objectID {
r.lastSha = &sha1 r.lastSha = &objectID
// need to munch to end of line... // need to munch to end of line...
for isPrefix { for isPrefix {
_, isPrefix, err = r.bufferedReader.ReadLine() _, isPrefix, err = r.bufferedReader.ReadLine()
@ -91,12 +90,13 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
} }
return blamePart, nil return blamePart, nil
} }
} else if line[0] == '\t' { } else if lineBytes[0] == '\t' {
blamePart.Lines = append(blamePart.Lines, line[1:]) blamePart.Lines = append(blamePart.Lines, string(lineBytes[1:]))
} else if strings.HasPrefix(line, "previous ") { } else if bytes.HasPrefix(lineBytes, []byte(previousHeader)) {
parts := strings.SplitN(line[len("previous "):], " ", 2) offset := len(previousHeader) // already includes a space
blamePart.PreviousSha = parts[0] blamePart.PreviousSha = string(lineBytes[offset : offset+objectFormatLength])
blamePart.PreviousPath = parts[1] offset += objectFormatLength + 1 // +1 for space
blamePart.PreviousPath = string(lineBytes[offset:])
} }
// need to munch to end of line... // need to munch to end of line...
@ -126,7 +126,7 @@ func (r *BlameReader) Close() error {
} }
// CreateBlameReader creates reader for given repository, commit and file // 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, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
var ignoreRevsFile *string var ignoreRevsFile *string
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore {
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
@ -175,6 +175,7 @@ func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, fil
bufferedReader: bufferedReader, bufferedReader: bufferedReader,
done: done, done: done,
ignoreRevsFile: ignoreRevsFile, ignoreRevsFile: ignoreRevsFile,
objectFormat: objectFormat,
}, nil }, nil
} }

@ -39,7 +39,7 @@ func TestReadingBlameOutput(t *testing.T) {
} }
for _, bypass := range []bool{false, true} { for _, bypass := range []bool{false, true} {
blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo5_pulls", commit, "README.md", bypass) blameReader, err := CreateBlameReader(ctx, &Sha1ObjectFormat{}, "./tests/repos/repo5_pulls", commit, "README.md", bypass)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, blameReader) assert.NotNil(t, blameReader)
defer blameReader.Close() defer blameReader.Close()
@ -122,7 +122,7 @@ func TestReadingBlameOutput(t *testing.T) {
commit, err := repo.GetCommit(c.CommitID) commit, err := repo.GetCommit(c.CommitID)
assert.NoError(t, err) assert.NoError(t, err)
blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass) blameReader, err := CreateBlameReader(ctx, repo.objectFormat, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, blameReader) assert.NotNil(t, blameReader)
defer blameReader.Close() defer blameReader.Close()

@ -14,7 +14,7 @@ import (
// Blob represents a Git object. // Blob represents a Git object.
type Blob struct { type Blob struct {
ID SHA1 ID ObjectID
gogitEncodedObj plumbing.EncodedObject gogitEncodedObj plumbing.EncodedObject
name string name string

@ -16,7 +16,7 @@ import (
// Blob represents a Git object. // Blob represents a Git object.
type Blob struct { type Blob struct {
ID SHA1 ID ObjectID
gotSize bool gotSize bool
size int64 size int64

@ -21,13 +21,13 @@ import (
// Commit represents a git commit. // Commit represents a git commit.
type Commit struct { type Commit struct {
Tree Tree
ID SHA1 // The ID of this commit object ID ObjectID // The ID of this commit object
Author *Signature Author *Signature
Committer *Signature Committer *Signature
CommitMessage string CommitMessage string
Signature *CommitGPGSignature Signature *CommitGPGSignature
Parents []SHA1 // SHA1 strings Parents []ObjectID // ID strings
submoduleCache *ObjectCache submoduleCache *ObjectCache
} }
@ -50,9 +50,9 @@ func (c *Commit) Summary() string {
// ParentID returns oid of n-th parent (0-based index). // ParentID returns oid of n-th parent (0-based index).
// It returns nil if no such parent exists. // It returns nil if no such parent exists.
func (c *Commit) ParentID(n int) (SHA1, error) { func (c *Commit) ParentID(n int) (ObjectID, error) {
if n >= len(c.Parents) { if n >= len(c.Parents) {
return SHA1{}, ErrNotExist{"", ""} return nil, ErrNotExist{"", ""}
} }
return c.Parents[n], nil return c.Parents[n], nil
} }
@ -209,9 +209,9 @@ func (c *Commit) CommitsBefore() ([]*Commit, error) {
} }
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents // 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(objectID ObjectID) (bool, error) {
this := c.ID.String() this := c.ID.String()
that := commitHash.String() that := objectID.String()
if this == that { if this == that {
return false, nil return false, nil
@ -232,9 +232,14 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
// IsForcePush returns true if a push from oldCommitHash to this is a force push // IsForcePush returns true if a push from oldCommitHash to this is a force push
func (c *Commit) IsForcePush(oldCommitID string) (bool, error) { func (c *Commit) IsForcePush(oldCommitID string) (bool, error) {
if oldCommitID == EmptySHA { objectFormat, err := c.repo.GetObjectFormat()
if err != nil {
return false, err
}
if oldCommitID == objectFormat.Empty().String() {
return false, nil return false, nil
} }
oldCommit, err := c.repo.GetCommit(oldCommitID) oldCommit, err := c.repo.GetCommit(oldCommitID)
if err != nil { if err != nil {
return false, err return false, err

@ -59,11 +59,11 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
func convertCommit(c *object.Commit) *Commit { func convertCommit(c *object.Commit) *Commit {
return &Commit{ return &Commit{
ID: c.Hash, ID: ParseGogitHash(c.Hash),
CommitMessage: c.Message, CommitMessage: c.Message,
Committer: &c.Committer, Committer: &c.Committer,
Author: &c.Author, Author: &c.Author,
Signature: convertPGPSignature(c), Signature: convertPGPSignature(c),
Parents: c.ParentHashes, Parents: ParseGogitHashArray(c.ParentHashes),
} }
} }

@ -29,7 +29,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
defer commitGraphFile.Close() defer commitGraphFile.Close()
} }
c, err := commitNodeIndex.Get(commit.ID) c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue()))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

@ -153,7 +153,7 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
if typ != "commit" { if typ != "commit" {
return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) 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 { if err != nil {
return nil, err return nil, err
} }

@ -14,9 +14,9 @@ import (
// We need this to interpret commits from cat-file or cat-file --batch // 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 // 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, objectID ObjectID, reader io.Reader) (*Commit, error) {
commit := &Commit{ commit := &Commit{
ID: sha, ID: objectID,
Author: &Signature{}, Author: &Signature{},
Committer: &Signature{}, Committer: &Signature{},
} }
@ -71,10 +71,10 @@ readLoop:
switch string(split[0]) { switch string(split[0]) {
case "tree": case "tree":
commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data))) commit.Tree = *NewTree(gitRepo, objectID.Type().MustIDFromString(string(data)))
_, _ = payloadSB.Write(line) _, _ = payloadSB.Write(line)
case "parent": case "parent":
commit.Parents = append(commit.Parents, MustIDFromString(string(data))) commit.Parents = append(commit.Parents, objectID.Type().MustIDFromString(string(data)))
_, _ = payloadSB.Write(line) _, _ = payloadSB.Write(line)
case "author": case "author":
commit.Author = &Signature{} commit.Author = &Signature{}

@ -81,7 +81,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
empty commit` 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")) gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, gitRepo) assert.NotNil(t, gitRepo)
@ -135,8 +135,8 @@ func TestHasPreviousCommit(t *testing.T) {
commit, err := repo.GetCommit("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0") commit, err := repo.GetCommit("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0")
assert.NoError(t, err) assert.NoError(t, err)
parentSHA := MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2") parentSHA := repo.objectFormat.MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2")
notParentSHA := MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3") notParentSHA := repo.objectFormat.MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3")
haz, err := commit.HasPreviousCommit(parentSHA) haz, err := commit.HasPreviousCommit(parentSHA)
assert.NoError(t, err) assert.NoError(t, err)

@ -92,17 +92,21 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) {
// GetCommitByPath gets the last commit for the entry in the provided commit // GetCommitByPath gets the last commit for the entry in the provided commit
func (c *LastCommitCache) GetCommitByPath(commitID, entryPath string) (*Commit, error) { func (c *LastCommitCache) GetCommitByPath(commitID, entryPath string) (*Commit, error) {
sha1, err := NewIDFromString(commitID) objectFormat, err := c.repo.GetObjectFormat()
if err != nil {
return nil, err
}
sha, err := objectFormat.NewIDFromString(commitID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
lastCommit, err := c.Get(sha1.String(), entryPath) lastCommit, err := c.Get(sha.String(), entryPath)
if err != nil || lastCommit != nil { if err != nil || lastCommit != nil {
return lastCommit, err return lastCommit, err
} }
lastCommit, err = c.repo.getCommitByPathWithID(sha1, entryPath) lastCommit, err = c.repo.getCommitByPathWithID(sha, entryPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -8,6 +8,7 @@ package git
import ( import (
"context" "context"
"github.com/go-git/go-git/v5/plumbing"
cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" 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() commitNodeIndex, _ := c.repo.CommitNodeIndex()
index, err := commitNodeIndex.Get(c.ID) index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue()))
if err != nil { if err != nil {
return err return err
} }

@ -143,17 +143,20 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int
} }
// Our "line" must look like: <commitid> SP (<parent> SP) * NUL // Our "line" must look like: <commitid> SP (<parent> SP) * NUL
ret.CommitID = string(g.next[0:40]) commitIds := string(g.next)
parents := string(g.next[41:])
if g.buffull { if g.buffull {
more, err := g.rd.ReadString('\x00') more, err := g.rd.ReadString('\x00')
if err != nil { if err != nil {
return nil, err 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" // now read the next "line"
g.buffull = false g.buffull = false

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object" "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() defer commitGraphFile.Close()
} }
commitNode, err := commitNodeIndex.Get(notes.ID) commitNode, err := commitNodeIndex.Get(plumbing.Hash(notes.ID.RawValue()))
if err != nil { if err != nil {
return err return err
} }

@ -0,0 +1,103 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"crypto/sha1"
"fmt"
"regexp"
"strings"
)
type ObjectFormatID int
const (
Sha1 ObjectFormatID = iota
)
// sha1Pattern can be used to determine if a string is an valid sha
var sha1Pattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
type ObjectFormat interface {
ID() ObjectFormatID
String() string
// Empty is the hash of empty git
Empty() ObjectID
// EmptyTree is the hash of an empty tree
EmptyTree() ObjectID
// FullLength is the length of the hash's hex string
FullLength() int
IsValid(input string) bool
MustID(b []byte) ObjectID
MustIDFromString(s string) ObjectID
NewID(b []byte) (ObjectID, error)
NewIDFromString(s string) (ObjectID, error)
NewEmptyID() ObjectID
NewHasher() HasherInterface
}
/* SHA1 Type */
type Sha1ObjectFormat struct{}
func (*Sha1ObjectFormat) ID() ObjectFormatID { return Sha1 }
func (*Sha1ObjectFormat) String() string { return "sha1" }
func (*Sha1ObjectFormat) Empty() ObjectID { return &Sha1Hash{} }
func (*Sha1ObjectFormat) EmptyTree() ObjectID {
return &Sha1Hash{
0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60,
0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04,
}
}
func (*Sha1ObjectFormat) FullLength() int { return 40 }
func (*Sha1ObjectFormat) IsValid(input string) bool {
return sha1Pattern.MatchString(input)
}
func (*Sha1ObjectFormat) MustID(b []byte) ObjectID {
var id Sha1Hash
copy(id[0:20], b)
return &id
}
func (h *Sha1ObjectFormat) MustIDFromString(s string) ObjectID {
return MustIDFromString(h, s)
}
func (h *Sha1ObjectFormat) NewID(b []byte) (ObjectID, error) {
return IDFromRaw(h, b)
}
func (h *Sha1ObjectFormat) NewIDFromString(s string) (ObjectID, error) {
return genericIDFromString(h, s)
}
func (*Sha1ObjectFormat) NewEmptyID() ObjectID {
return NewSha1()
}
func (h *Sha1ObjectFormat) NewHasher() HasherInterface {
return &Sha1Hasher{sha1.New()}
}
// utils
func ObjectFormatFromID(id ObjectFormatID) ObjectFormat {
switch id {
case Sha1:
return &Sha1ObjectFormat{}
}
return nil
}
func ObjectFormatFromString(hash string) (ObjectFormat, error) {
switch strings.ToLower(hash) {
case "sha1":
return &Sha1ObjectFormat{}, nil
}
return nil, fmt.Errorf("unknown hash type: %s", hash)
}

@ -0,0 +1,143 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"hash"
"strconv"
"strings"
)
type ObjectID interface {
String() string
IsZero() bool
RawValue() []byte
Type() ObjectFormat
}
/* 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() ObjectFormat { return &Sha1ObjectFormat{} }
func NewSha1() *Sha1Hash {
return &Sha1Hash{}
}
// generic implementations
func NewHash(hash string) (ObjectID, error) {
hash = strings.ToLower(hash)
switch hash {
case "sha1":
return &Sha1Hash{}, nil
}
return nil, errors.New("unsupported hash type")
}
func IDFromRaw(h ObjectFormat, b []byte) (ObjectID, 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 MustIDFromString(h ObjectFormat, s string) ObjectID {
b, _ := hex.DecodeString(s)
return h.MustID(b)
}
func genericIDFromString(h ObjectFormat, s string) (ObjectID, 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 IDFromString(hexHash string) (ObjectID, error) {
switch len(hexHash) {
case 40:
hashType := Sha1ObjectFormat{}
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))
}
func IsEmptyCommitID(commitID string) bool {
if commitID == "" {
return true
}
id, err := IDFromString(commitID)
if err != nil {
return false
}
return id.IsZero()
}
// HashInterface is a struct that will generate a Hash
type HasherInterface interface {
hash.Hash
HashSum() ObjectID
}
type Sha1Hasher struct {
hash.Hash
}
// ComputeBlobHash compute the hash for a given blob content
func ComputeBlobHash(hashType ObjectFormat, content []byte) ObjectID {
return ComputeHash(hashType, ObjectBlob, content)
}
// ComputeHash compute the hash for a given ObjectType and content
func ComputeHash(hashType ObjectFormat, t ObjectType, content []byte) ObjectID {
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() ObjectID {
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)
}

@ -0,0 +1,28 @@
// 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) ObjectID {
switch hash.Size {
case 20:
return ObjectFormatFromID(Sha1).MustID(h[:])
}
return nil
}
func ParseGogitHashArray(objectIDs []plumbing.Hash) []ObjectID {
ret := make([]ObjectID, len(objectIDs))
for i, h := range objectIDs {
ret[i] = ParseGogitHash(h)
}
return ret
}

@ -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"))
}

@ -11,12 +11,14 @@ import (
"strconv" "strconv"
"strings" "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/filemode"
"github.com/go-git/go-git/v5/plumbing/hash"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
) )
// ParseTreeEntries parses the output of a `git ls-tree -l` command. // ParseTreeEntries parses the output of a `git ls-tree -l` command.
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { func ParseTreeEntries(h ObjectFormat, data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(data, nil) 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])) 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)) 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 = IDFromString(string(data[pos : pos+hash.Size*2]))
if err != nil { if err != nil {
return nil, fmt.Errorf("Invalid ls-tree output: %w", err) return nil, fmt.Errorf("invalid ls-tree output: %w", err)
} }
entry.ID = id entry.gogitTreeEntry.Hash = plumbing.Hash(entry.ID.RawValue())
entry.gogitTreeEntry.Hash = id
pos += 41 // skip over sha and trailing space pos += 41 // skip over sha and trailing space
end := pos + bytes.IndexByte(data[pos:], '\t') 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). // In case entry name is surrounded by double quotes(it happens only in git-shell).
if data[pos] == '"' { if data[pos] == '"' {
var err error
entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end])) entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end]))
if err != nil { if err != nil {
return nil, fmt.Errorf("Invalid ls-tree output: %w", err) return nil, fmt.Errorf("Invalid ls-tree output: %w", err)

@ -6,8 +6,10 @@
package git package git
import ( import (
"fmt"
"testing" "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/filemode"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -26,9 +28,9 @@ func TestParseTreeEntries(t *testing.T) {
Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n", Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n",
Expected: []*TreeEntry{ Expected: []*TreeEntry{
{ {
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), ID: ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
gogitTreeEntry: &object.TreeEntry{ gogitTreeEntry: &object.TreeEntry{
Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), Hash: plumbing.Hash(ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()),
Name: "example/file2.txt", Name: "example/file2.txt",
Mode: filemode.Regular, Mode: filemode.Regular,
}, },
@ -42,9 +44,9 @@ func TestParseTreeEntries(t *testing.T) {
"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n", "040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n",
Expected: []*TreeEntry{ Expected: []*TreeEntry{
{ {
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), ID: ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
gogitTreeEntry: &object.TreeEntry{ gogitTreeEntry: &object.TreeEntry{
Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), Hash: plumbing.Hash(ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()),
Name: "example/\n.txt", Name: "example/\n.txt",
Mode: filemode.Symlink, Mode: filemode.Symlink,
}, },
@ -52,10 +54,10 @@ func TestParseTreeEntries(t *testing.T) {
sized: true, sized: true,
}, },
{ {
ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), ID: ObjectFormatFromID(Sha1).MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
sized: true, sized: true,
gogitTreeEntry: &object.TreeEntry{ gogitTreeEntry: &object.TreeEntry{
Hash: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), Hash: plumbing.Hash(ObjectFormatFromID(Sha1).MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8").RawValue()),
Name: "example", Name: "example",
Mode: filemode.Dir, Mode: filemode.Dir,
}, },
@ -65,8 +67,12 @@ func TestParseTreeEntries(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
entries, err := ParseTreeEntries([]byte(testCase.Input)) entries, err := ParseTreeEntries(ObjectFormatFromID(Sha1), []byte(testCase.Input))
assert.NoError(t, err) 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) assert.EqualValues(t, testCase.Expected, entries)
} }
} }

@ -17,13 +17,13 @@ import (
) )
// ParseTreeEntries parses the output of a `git ls-tree -l` command. // ParseTreeEntries parses the output of a `git ls-tree -l` command.
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { func ParseTreeEntries(objectFormat ObjectFormat, data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(data, nil) return parseTreeEntries(objectFormat, data, nil)
} }
var sepSpace = []byte{' '} var sepSpace = []byte{' '}
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { func parseTreeEntries(objectFormat ObjectFormat, data []byte, ptree *Tree) ([]*TreeEntry, error) {
var err error var err error
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
for pos := 0; pos < len(data); { 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)) return nil, fmt.Errorf("unknown type: %v", string(entryMode))
} }
entry.ID, err = NewIDFromString(string(entryObjectID)) entry.ID, err = objectFormat.NewIDFromString(string(entryObjectID))
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) 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 return entries, nil
} }
func catBatchParseTreeEntries(ptree *Tree, rd *bufio.Reader, sz int64) ([]*TreeEntry, error) { func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio.Reader, sz int64) ([]*TreeEntry, error) {
fnameBuf := make([]byte, 4096) fnameBuf := make([]byte, 4096)
modeBuf := make([]byte, 40) modeBuf := make([]byte, 40)
shaBuf := make([]byte, 40) shaBuf := make([]byte, objectFormat.FullLength())
entries := make([]*TreeEntry, 0, 10) entries := make([]*TreeEntry, 0, 10)
loop: loop:
for sz > 0 { for sz > 0 {
mode, fname, sha, count, err := ParseTreeLine(rd, modeBuf, fnameBuf, shaBuf) mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break loop break loop
@ -127,7 +127,7 @@ loop:
return nil, fmt.Errorf("unknown mode: %v", string(mode)) return nil, fmt.Errorf("unknown mode: %v", string(mode))
} }
entry.ID = MustID(sha) entry.ID = objectFormat.MustID(sha)
entry.name = string(fname) entry.name = string(fname)
entries = append(entries, entry) entries = append(entries, entry)
} }

@ -12,6 +12,8 @@ import (
) )
func TestParseTreeEntriesLong(t *testing.T) { func TestParseTreeEntriesLong(t *testing.T) {
objectFormat := ObjectFormatFromID(Sha1)
testCases := []struct { testCases := []struct {
Input string Input string
Expected []*TreeEntry Expected []*TreeEntry
@ -24,28 +26,28 @@ func TestParseTreeEntriesLong(t *testing.T) {
`, `,
Expected: []*TreeEntry{ Expected: []*TreeEntry{
{ {
ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"), ID: objectFormat.MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
name: "README.md", name: "README.md",
entryMode: EntryModeBlob, entryMode: EntryModeBlob,
size: 8218, size: 8218,
sized: true, sized: true,
}, },
{ {
ID: MustIDFromString("037f27dc9d353ae4fd50f0474b2194c593914e35"), ID: objectFormat.MustIDFromString("037f27dc9d353ae4fd50f0474b2194c593914e35"),
name: "README_ZH.md", name: "README_ZH.md",
entryMode: EntryModeBlob, entryMode: EntryModeBlob,
size: 4681, size: 4681,
sized: true, sized: true,
}, },
{ {
ID: MustIDFromString("9846a94f7e8350a916632929d0fda38c90dd2ca8"), ID: objectFormat.MustIDFromString("9846a94f7e8350a916632929d0fda38c90dd2ca8"),
name: "SECURITY.md", name: "SECURITY.md",
entryMode: EntryModeBlob, entryMode: EntryModeBlob,
size: 429, size: 429,
sized: true, sized: true,
}, },
{ {
ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"), ID: objectFormat.MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
name: "assets", name: "assets",
entryMode: EntryModeTree, entryMode: EntryModeTree,
sized: true, sized: true,
@ -54,7 +56,7 @@ func TestParseTreeEntriesLong(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
entries, err := ParseTreeEntries([]byte(testCase.Input)) entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input))
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, len(testCase.Expected)) assert.Len(t, entries, len(testCase.Expected))
for i, entry := range entries { for i, entry := range entries {
@ -64,6 +66,8 @@ func TestParseTreeEntriesLong(t *testing.T) {
} }
func TestParseTreeEntriesShort(t *testing.T) { func TestParseTreeEntriesShort(t *testing.T) {
objectFormat := ObjectFormatFromID(Sha1)
testCases := []struct { testCases := []struct {
Input string Input string
Expected []*TreeEntry Expected []*TreeEntry
@ -74,12 +78,12 @@ func TestParseTreeEntriesShort(t *testing.T) {
`, `,
Expected: []*TreeEntry{ Expected: []*TreeEntry{
{ {
ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"), ID: objectFormat.MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
name: "README.md", name: "README.md",
entryMode: EntryModeBlob, entryMode: EntryModeBlob,
}, },
{ {
ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"), ID: objectFormat.MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
name: "assets", name: "assets",
entryMode: EntryModeTree, entryMode: EntryModeTree,
}, },
@ -87,7 +91,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
entries, err := ParseTreeEntries([]byte(testCase.Input)) entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input))
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, len(testCase.Expected)) assert.Len(t, entries, len(testCase.Expected))
for i, entry := range entries { for i, entry := range entries {
@ -98,7 +102,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
func TestParseTreeEntriesInvalid(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 // 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(ObjectFormatFromID(Sha1), []byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, entries, 0) assert.Len(t, entries, 0)
} }

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
gogit "github.com/go-git/go-git/v5" 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" "github.com/go-git/go-git/v5/plumbing/object"
) )
@ -26,7 +27,7 @@ type LFSResult struct {
SHA string SHA string
Summary string Summary string
When time.Time When time.Time
ParentHashes []git.SHA1 ParentHashes []git.ObjectID
BranchName string BranchName string
FullCommitName 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) } 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 // 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, objectID git.ObjectID) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{} resultsMap := map[string]*LFSResult{}
results := make([]*LFSResult, 0) results := make([]*LFSResult, 0)
@ -65,13 +66,18 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
if err == io.EOF { if err == io.EOF {
break break
} }
if entry.Hash == hash { if entry.Hash == plumbing.Hash(objectID.RawValue()) {
parents := make([]git.ObjectID, len(gitCommit.ParentHashes))
for i, parentCommitID := range gitCommit.ParentHashes {
parents[i] = git.ParseGogitHash(parentCommitID)
}
result := LFSResult{ result := LFSResult{
Name: name, Name: name,
SHA: gitCommit.Hash.String(), SHA: gitCommit.Hash.String(),
Summary: strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0], Summary: strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
When: gitCommit.Author.When, When: gitCommit.Author.When,
ParentHashes: gitCommit.ParentHashes, ParentHashes: parents,
} }
resultsMap[gitCommit.Hash.String()+":"+name] = &result resultsMap[gitCommit.Hash.String()+":"+name] = &result
} }

@ -24,7 +24,7 @@ type LFSResult struct {
SHA string SHA string
Summary string Summary string
When time.Time When time.Time
ParentHashes []git.SHA1 ParentIDs []git.ObjectID
BranchName string BranchName string
FullCommitName 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) } 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 // 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, objectID git.ObjectID) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{} resultsMap := map[string]*LFSResult{}
results := make([]*LFSResult, 0) results := make([]*LFSResult, 0)
@ -75,7 +75,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
fnameBuf := make([]byte, 4096) fnameBuf := make([]byte, 4096)
modeBuf := make([]byte, 40) modeBuf := make([]byte, 40)
workingShaBuf := make([]byte, 20) workingShaBuf := make([]byte, objectID.Type().FullLength()/2)
for scan.Scan() { for scan.Scan() {
// Get the next commit ID // Get the next commit ID
@ -115,7 +115,11 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
continue continue
case "commit": case "commit":
// Read in the commit to get its tree and in case this is one of the last used commits // 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)) objectFormat, err := repo.GetObjectFormat()
if err != nil {
return nil, err
}
curCommit, err = git.CommitFromReader(repo, objectFormat.MustIDFromString(string(commitID)), io.LimitReader(batchReader, size))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -123,32 +127,31 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
return nil, err return nil, err
} }
_, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n")) if _, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n")); err != nil {
if err != nil {
return nil, err return nil, err
} }
curPath = "" curPath = ""
case "tree": case "tree":
var n int64 var n int64
for n < size { for n < size {
mode, fname, sha20byte, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf) mode, fname, binObjectID, count, err := git.ParseTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
n += int64(count) n += int64(count)
if bytes.Equal(sha20byte, hash[:]) { if bytes.Equal(binObjectID, objectID.RawValue()) {
result := LFSResult{ result := LFSResult{
Name: curPath + string(fname), Name: curPath + string(fname),
SHA: curCommit.ID.String(), SHA: curCommit.ID.String(),
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0], Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
When: curCommit.Author.When, When: curCommit.Author.When,
ParentHashes: curCommit.Parents, ParentIDs: curCommit.Parents,
} }
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
} else if string(mode) == git.EntryModeTree.String() { } else if string(mode) == git.EntryModeTree.String() {
sha40Byte := make([]byte, 40) hexObjectID := make([]byte, objectID.Type().FullLength())
git.To40ByteSHA(sha20byte, sha40Byte) git.BinToHex(objectID.Type(), binObjectID, hexObjectID)
trees = append(trees, sha40Byte) trees = append(trees, hexObjectID)
paths = append(paths, curPath+string(fname)+"/") paths = append(paths, curPath+string(fname)+"/")
} }
} }
@ -180,8 +183,8 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
for _, result := range resultsMap { for _, result := range resultsMap {
hasParent := false hasParent := false
for _, parentHash := range result.ParentHashes { for _, parentID := range result.ParentIDs {
if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent { if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
break break
} }
} }

@ -44,7 +44,7 @@ func SanitizeRefPattern(name string) string {
type Reference struct { type Reference struct {
Name string Name string
repo *Repository repo *Repository
Object SHA1 // The id of this commit object Object ObjectID // The id of this commit object
Type string Type string
} }
@ -205,7 +205,7 @@ func RefURL(repoURL, ref string) string {
return repoURL + "/src/branch/" + refName return repoURL + "/src/branch/" + refName
case refFullName.IsTag(): case refFullName.IsTag():
return repoURL + "/src/tag/" + refName return repoURL + "/src/tag/" + refName
case !IsValidSHAPattern(ref): case !ObjectFormatFromID(Sha1).IsValid(ref):
// assume they mean a branch // assume they mean a branch
return repoURL + "/src/branch/" + refName return repoURL + "/src/branch/" + refName
default: default:

@ -7,6 +7,7 @@ package git
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
@ -62,14 +63,40 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
return err == nil return err == nil
} }
// GetObjectFormatOfRepo returns the hash type of a repository at a given path
func GetObjectFormatOfRepo(ctx context.Context, repoPath string) (ObjectFormat, 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 := IDFromString(strings.TrimRight(stdout.String(), "\n"))
if err != nil {
return nil, err
}
return h.Type(), nil
}
// InitRepository initializes a new Git repository. // 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, objectFormat ObjectFormat) error {
err := os.MkdirAll(repoPath, os.ModePerm) err := os.MkdirAll(repoPath, os.ModePerm)
if err != nil { if err != nil {
return err return err
} }
cmd := NewCommand(ctx, "init") cmd := NewCommand(ctx, "init", "--object-format").AddDynamicArguments(objectFormat.String())
if bare { if bare {
cmd.AddArguments("--bare") cmd.AddArguments("--bare")
} }

@ -16,6 +16,7 @@ import (
"github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-billy/v5/osfs"
gogit "github.com/go-git/go-git/v5" 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/plumbing/cache"
"github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/filesystem"
) )
@ -32,6 +33,7 @@ type Repository struct {
Ctx context.Context Ctx context.Context
LastCommitCache *LastCommitCache LastCommitCache *LastCommitCache
objectFormat ObjectFormat
} }
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. // 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, gogitStorage: storage,
tagCache: newObjectCache(), tagCache: newObjectCache(),
Ctx: ctx, Ctx: ctx,
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
}, nil }, nil
} }

@ -33,6 +33,8 @@ type Repository struct {
Ctx context.Context Ctx context.Context
LastCommitCache *LastCommitCache LastCommitCache *LastCommitCache
objectFormat ObjectFormat
} }
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext. // 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.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath) repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
repo.objectFormat, err = repo.GetObjectFormat()
if err != nil {
return nil, err
}
return repo, nil return repo, nil
} }

@ -5,7 +5,7 @@ package git
// GetBlob finds the blob object in the repository. // GetBlob finds the blob object in the repository.
func (repo *Repository) GetBlob(idStr string) (*Blob, error) { func (repo *Repository) GetBlob(idStr string) (*Blob, error) {
id, err := NewIDFromString(idStr) id, err := repo.objectFormat.NewIDFromString(idStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -9,8 +9,8 @@ import (
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
) )
func (repo *Repository) getBlob(id SHA1) (*Blob, error) { func (repo *Repository) getBlob(id ObjectID) (*Blob, error) {
encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, id) encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id.RawValue()))
if err != nil { if err != nil {
return nil, ErrNotExist{id.String(), ""} return nil, ErrNotExist{id.String(), ""}
} }

@ -5,7 +5,7 @@
package git package git
func (repo *Repository) getBlob(id SHA1) (*Blob, error) { func (repo *Repository) getBlob(id ObjectID) (*Blob, error) {
if id.IsZero() { if id.IsZero() {
return nil, ErrNotExist{id.String(), ""} return nil, ErrNotExist{id.String(), ""}
} }

@ -61,7 +61,7 @@ func TestRepository_GetBlob_NoId(t *testing.T) {
defer r.Close() defer r.Close()
testCase := "" testCase := ""
testError := fmt.Errorf("Length must be 40: %s", testCase) testError := fmt.Errorf("length must be 40: %s", testCase)
blob, err := r.GetBlob(testCase) blob, err := r.GetBlob(testCase)
assert.Nil(t, blob) assert.Nil(t, blob)

@ -6,8 +6,6 @@ package git
import ( import (
"bytes" "bytes"
"encoding/hex"
"fmt"
"io" "io"
"strconv" "strconv"
"strings" "strings"
@ -28,7 +26,7 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) {
// GetCommit returns commit object of by ID string. // GetCommit returns commit object of by ID string.
func (repo *Repository) GetCommit(commitID string) (*Commit, error) { func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
id, err := repo.ConvertToSHA1(commitID) id, err := repo.ConvertToGitID(commitID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -54,7 +52,7 @@ func (repo *Repository) GetTagCommit(name string) (*Commit, error) {
return repo.GetCommit(commitID) return repo.GetCommit(commitID)
} }
func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, error) { func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Commit, error) {
// File name starts with ':' must be escaped. // File name starts with ':' must be escaped.
if relpath[0] == ':' { if relpath[0] == ':' {
relpath = `\` + relpath relpath = `\` + relpath
@ -65,7 +63,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
return nil, runErr return nil, runErr
} }
id, err := NewIDFromString(stdout) id, err := repo.objectFormat.NewIDFromString(stdout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,7 +88,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
return commits[0], nil return commits[0], nil
} }
func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string) ([]*Commit, error) { func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
cmd := NewCommand(repo.Ctx, "log"). cmd := NewCommand(repo.Ctx, "log").
AddOptionFormat("--skip=%d", (page-1)*pageSize). AddOptionFormat("--skip=%d", (page-1)*pageSize).
AddOptionFormat("--max-count=%d", 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) return repo.parsePrettyFormatLogToList(stdout)
} }
func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) { func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([]*Commit, error) {
// add common arguments to git command // add common arguments to git command
addCommonSearchArgs := func(c *Command) { addCommonSearchArgs := func(c *Command) {
// ignore case // ignore case
@ -164,7 +162,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
// then let's iterate over them // then let's iterate over them
for _, v := range opts.Keywords { for _, v := range opts.Keywords {
// ignore anything not matching a valid sha pattern // 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 // create new git log command with 1 commit limit
hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat) hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat)
// add previous arguments except for --grep and --all // add previous arguments except for --grep and --all
@ -245,25 +243,22 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
} }
}() }()
len := repo.objectFormat.FullLength()
commits := []*Commit{} commits := []*Commit{}
shaline := [41]byte{} shaline := make([]byte, len+1)
var sha1 SHA1
for { for {
n, err := io.ReadFull(stdoutReader, shaline[:]) n, err := io.ReadFull(stdoutReader, shaline)
if err != nil || n < 40 { if err != nil || n < len {
if err == io.EOF { if err == io.EOF {
err = nil err = nil
} }
return commits, err return commits, err
} }
n, err = hex.Decode(sha1[:], shaline[0:40]) objectID, err := repo.objectFormat.NewIDFromString(string(shaline[0:len]))
if n != 20 {
err = fmt.Errorf("invalid sha %q", string(shaline[:40]))
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
commit, err := repo.getCommit(sha1) commit, err := repo.getCommit(objectID)
if err != nil { if err != nil {
return nil, err 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. // 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 ObjectID, limit int) ([]*Commit, error) {
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat) cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
if limit > 0 { if limit > 0 {
cmd.AddOptionFormat("-%d", limit) cmd.AddOptionFormat("-%d", limit)
@ -426,11 +421,11 @@ func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
return commits, nil return commits, nil
} }
func (repo *Repository) getCommitsBefore(id SHA1) ([]*Commit, error) { func (repo *Repository) getCommitsBefore(id ObjectID) ([]*Commit, error) {
return repo.commitsBefore(id, 0) return repo.commitsBefore(id, 0)
} }
func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, error) { func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit, error) {
return repo.commitsBefore(id, num) return repo.commitsBefore(id, num)
} }

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/go-git/go-git/v5/plumbing" "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" "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)) return repo.gogitRepo.Storer.RemoveReference(plumbing.ReferenceName(name))
} }
// ConvertToSHA1 returns a Hash object from a potential ID string // ConvertToHash returns a Hash object from a potential ID string
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
if len(commitID) == SHAFullLength { objectFormat := repo.objectFormat
sha1, err := NewIDFromString(commitID) if len(commitID) == hash.HexSize && objectFormat.IsValid(commitID) {
ID, err := objectFormat.NewIDFromString(commitID)
if err == nil { if err == nil {
return sha1, nil return ID, nil
} }
} }
actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path}) actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path})
actualCommitID = strings.TrimSpace(actualCommitID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") || if strings.Contains(err.Error(), "unknown revision or path") ||
strings.Contains(err.Error(), "fatal: Needed a single revision") { strings.Contains(err.Error(), "fatal: Needed a single revision") {
return SHA1{}, ErrNotExist{commitID, ""} return objectFormat.Empty(), ErrNotExist{commitID, ""}
} }
return SHA1{}, err return objectFormat.Empty(), err
} }
return NewIDFromString(actualCommitID) return objectFormat.NewIDFromString(actualCommitID)
} }
// IsCommitExist returns true if given commit exists in current repository. // IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool { func (repo *Repository) IsCommitExist(name string) bool {
hash := plumbing.NewHash(name) hash, err := repo.ConvertToGitID(name)
_, err := repo.gogitRepo.CommitObject(hash) if err != nil {
return false
}
_, err = repo.gogitRepo.CommitObject(plumbing.Hash(hash.RawValue()))
return err == nil return err == nil
} }
func (repo *Repository) getCommit(id SHA1) (*Commit, error) { func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
var tagObject *object.Tag var tagObject *object.Tag
gogitCommit, err := repo.gogitRepo.CommitObject(id) commitID := plumbing.Hash(id.RawValue())
gogitCommit, err := repo.gogitRepo.CommitObject(commitID)
if err == plumbing.ErrObjectNotFound { if err == plumbing.ErrObjectNotFound {
tagObject, err = repo.gogitRepo.TagObject(id) tagObject, err = repo.gogitRepo.TagObject(commitID)
if err == plumbing.ErrObjectNotFound { if err == plumbing.ErrObjectNotFound {
return nil, ErrNotExist{ return nil, ErrNotExist{
ID: id.String(), ID: id.String(),
@ -94,7 +101,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
return nil, err return nil, err
} }
commit.Tree.ID = tree.Hash commit.Tree.ID = ParseGogitHash(tree.Hash)
commit.Tree.gogitTree = tree commit.Tree.gogitTree = tree
return commit, nil return commit, nil

@ -65,7 +65,7 @@ func (repo *Repository) IsCommitExist(name string) bool {
return err == nil return err == nil
} }
func (repo *Repository) getCommit(id SHA1) (*Commit, error) { func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
wr, rd, cancel := repo.CatFileBatch(repo.Ctx) wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel() defer cancel()
@ -74,7 +74,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
return repo.getCommitFromBatchReader(rd, id) return repo.getCommitFromBatchReader(rd, id)
} }
func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Commit, error) { func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) (*Commit, error) {
_, typ, size, err := ReadBatchLine(rd) _, typ, size, err := ReadBatchLine(rd)
if err != nil { if err != nil {
if errors.Is(err, io.EOF) || IsErrNotExist(err) { 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 { if err != nil {
return nil, err return nil, err
} }
tag, err := parseTagData(data) tag, err := parseTagData(id.Type(), data)
if err != nil { if err != nil {
return nil, err 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 // ConvertToGitID returns a GitHash object from a potential ID string
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) { IDType := repo.objectFormat
sha1, err := NewIDFromString(commitID) if len(commitID) == IDType.FullLength() && IDType.IsValid(commitID) {
ID, err := repo.objectFormat.NewIDFromString(commitID)
if err == nil { if err == nil {
return sha1, nil return ID, nil
} }
} }
@ -144,15 +145,15 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
defer cancel() defer cancel()
_, err := wr.Write([]byte(commitID + "\n")) _, err := wr.Write([]byte(commitID + "\n"))
if err != nil { if err != nil {
return SHA1{}, err return nil, err
} }
sha, _, _, err := ReadBatchLine(rd) sha, _, _, err := ReadBatchLine(rd)
if err != nil { if err != nil {
if IsErrNotExist(err) { if IsErrNotExist(err) {
return SHA1{}, ErrNotExist{commitID, ""} return nil, ErrNotExist{commitID, ""}
} }
return SHA1{}, err return nil, err
} }
return MustIDFromString(string(sha)), nil return repo.objectFormat.MustIDFromString(string(sha)), nil
} }

@ -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 // 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) { func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z") cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
if base == EmptySHA { if base == repo.objectFormat.Empty().String() {
cmd.AddDynamicArguments(head) cmd.AddDynamicArguments(head)
} else { } else {
cmd.AddDynamicArguments(base, head) cmd.AddDynamicArguments(base, head)

@ -131,12 +131,12 @@ func TestGetCommitFilesChanged(t *testing.T) {
files []string files []string
}{ }{
{ {
EmptySHA, repo.objectFormat.Empty().String(),
"95bb4d39648ee7e325106df01a621c530863a653", "95bb4d39648ee7e325106df01a621c530863a653",
[]string{"file1.txt"}, []string{"file1.txt"},
}, },
{ {
EmptySHA, repo.objectFormat.Empty().String(),
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
[]string{"file2.txt"}, []string{"file2.txt"},
}, },
@ -146,7 +146,7 @@ func TestGetCommitFilesChanged(t *testing.T) {
[]string{"file2.txt"}, []string{"file2.txt"},
}, },
{ {
EmptyTreeSHA, repo.objectFormat.EmptyTree().String(),
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
[]string{"file1.txt", "file2.txt"}, []string{"file1.txt", "file2.txt"},
}, },

@ -17,7 +17,7 @@ func (gpgSettings *GPGSettings) LoadPublicKeyContent() error {
"gpg -a --export", "gpg -a --export",
"gpg", "-a", "--export", gpgSettings.KeyID) "gpg", "-a", "--export", gpgSettings.KeyID)
if err != nil { 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 gpgSettings.PublicKeyContent = content
return nil return nil

@ -16,7 +16,12 @@ import (
// ReadTreeToIndex reads a treeish to the index // ReadTreeToIndex reads a treeish to the index
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error { func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
if len(treeish) != SHAFullLength { objectFormat, err := repo.GetObjectFormat()
if err != nil {
return err
}
if len(treeish) != objectFormat.FullLength() {
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path}) res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return err return err
@ -25,14 +30,14 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string)
treeish = res[:len(res)-1] treeish = res[:len(res)-1]
} }
} }
id, err := NewIDFromString(treeish) id, err := objectFormat.NewIDFromString(treeish)
if err != nil { if err != nil {
return err return err
} }
return repo.readTreeToIndex(id, indexFilename...) return repo.readTreeToIndex(id, indexFilename...)
} }
func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error { func (repo *Repository) readTreeToIndex(id ObjectID, indexFilename ...string) error {
var env []string var env []string
if len(indexFilename) > 0 { if len(indexFilename) > 0 {
env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0])
@ -95,7 +100,9 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
for _, file := range filenames { for _, file := range filenames {
if file != "" { if file != "" {
buffer.WriteString("0 0000000000000000000000000000000000000000\t") buffer.WriteString("0 ")
buffer.WriteString(repo.objectFormat.Empty().String())
buffer.WriteByte('\t')
buffer.WriteString(file) buffer.WriteString(file)
buffer.WriteByte('\000') buffer.WriteByte('\000')
} }
@ -109,7 +116,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
} }
// AddObjectToIndex adds the provided object hash to the index at the provided filename // 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 ObjectID, filename string) error {
cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, object.String(), filename) cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, object.String(), filename)
_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
return err return err
@ -121,7 +128,7 @@ func (repo *Repository) WriteTree() (*Tree, error) {
if runErr != nil { if runErr != nil {
return nil, runErr return nil, runErr
} }
id, err := NewIDFromString(strings.TrimSpace(stdout)) id, err := repo.objectFormat.NewIDFromString(strings.TrimSpace(stdout))
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -39,7 +39,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, ErrNotExist{commitID, ""} return nil, ErrNotExist{commitID, ""}
} }
sha, err := NewIDFromString(string(shaBytes)) sha, err := repo.objectFormat.NewIDFromString(string(shaBytes))
if err != nil { if err != nil {
log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) log.Debug("Unable to get commit for: %s. Err: %v", commitID, err)
return nil, ErrNotExist{commitID, ""} return nil, ErrNotExist{commitID, ""}

@ -31,17 +31,47 @@ func (o ObjectType) Bytes() []byte {
return []byte(o) return []byte(o)
} }
// HashObject takes a reader and returns SHA1 hash for that reader type EmptyReader struct{}
func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) {
idStr, err := repo.hashObject(reader) func (EmptyReader) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (repo *Repository) GetObjectFormat() (ObjectFormat, error) {
if repo != nil && repo.objectFormat != nil {
return repo.objectFormat, nil
}
str, err := repo.hashObject(EmptyReader{}, false)
if err != nil {
return nil, err
}
hash, err := IDFromString(str)
if err != nil { if err != nil {
return SHA1{}, err return nil, err
} }
return NewIDFromString(idStr)
repo.objectFormat = hash.Type()
return repo.objectFormat, nil
} }
func (repo *Repository) hashObject(reader io.Reader) (string, error) { // HashObject takes a reader and returns hash for that reader
cmd := NewCommand(repo.Ctx, "hash-object", "-w", "--stdin") func (repo *Repository) HashObject(reader io.Reader) (ObjectID, error) {
idStr, err := repo.hashObject(reader, true)
if err != nil {
return nil, err
}
return repo.objectFormat.NewIDFromString(idStr)
}
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) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := cmd.Run(&RunOpts{ err := cmd.Run(&RunOpts{

@ -30,13 +30,13 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
refType := string(ObjectCommit) refType := string(ObjectCommit)
if ref.Name().IsTag() { if ref.Name().IsTag() {
// tags can be of type `commit` (lightweight) or `tag` (annotated) // 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 refType = tagType
} }
} }
r := &Reference{ r := &Reference{
Name: ref.Name().String(), Name: ref.Name().String(),
Object: ref.Hash(), Object: ParseGogitHash(ref.Hash()),
Type: refType, Type: refType,
repo: repo, repo: repo,
} }

@ -75,7 +75,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
if pattern == "" || strings.HasPrefix(refName, pattern) { if pattern == "" || strings.HasPrefix(refName, pattern) {
r := &Reference{ r := &Reference{
Name: refName, Name: refName,
Object: MustIDFromString(sha), Object: repo.objectFormat.MustIDFromString(sha),
Type: typ, Type: typ,
repo: repo, repo: repo,
} }

@ -84,7 +84,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
return nil, err return nil, err
} }
id, err := NewIDFromString(idStr) id, err := repo.objectFormat.NewIDFromString(idStr)
if err != nil { if err != nil {
return nil, err 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 // GetTagWithID returns a Git tag by given name and ID
func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) { func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) {
id, err := NewIDFromString(idStr) id, err := repo.objectFormat.NewIDFromString(idStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -139,7 +139,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
break break
} }
tag, err := parseTagRef(ref) tag, err := parseTagRef(repo.objectFormat, ref)
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err) 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. // parseTagRef parses a tag from a 'git for-each-ref'-produced reference.
func parseTagRef(ref map[string]string) (tag *Tag, err error) { func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, err error) {
tag = &Tag{ tag = &Tag{
Type: ref["objecttype"], Type: ref["objecttype"],
Name: ref["refname:short"], Name: ref["refname:short"],
} }
tag.ID, err = NewIDFromString(ref["objectname"]) tag.ID, err = objectFormat.NewIDFromString(ref["objectname"])
if err != nil { if err != nil {
return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err) 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 tag.Object = tag.ID
} else { } else {
// annotated tag // annotated tag
tag.Object, err = NewIDFromString(ref["object"]) tag.Object, err = objectFormat.NewIDFromString(ref["object"])
if err != nil { if err != nil {
return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err) 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 // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) { func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
id, err := NewIDFromString(sha) id, err := repo.objectFormat.NewIDFromString(sha)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -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) // 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 ObjectID) (string, error) {
// Get tag type // 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 != nil {
if err == plumbing.ErrReferenceNotFound { if err == plumbing.ErrReferenceNotFound {
return "", &ErrNotExist{ID: id.String()} return "", &ErrNotExist{ID: id.String()}
@ -68,7 +68,7 @@ func (repo *Repository) GetTagType(id SHA1) (string, error) {
return obj.Type().String(), nil return obj.Type().String(), nil
} }
func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
t, ok := repo.tagCache.Get(tagID.String()) t, ok := repo.tagCache.Get(tagID.String())
if ok { if ok {
log.Debug("Hit cache: %s", tagID) 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 // every tag should have a commit ID so return all errors
return nil, err return nil, err
} }
commitID, err := NewIDFromString(commitIDStr) commitID, err := IDFromString(commitIDStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -112,7 +112,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
return tag, nil return tag, nil
} }
gogitTag, err := repo.gogitRepo.TagObject(tagID) gogitTag, err := repo.gogitRepo.TagObject(plumbing.Hash(tagID.RawValue()))
if err != nil { if err != nil {
if err == plumbing.ErrReferenceNotFound { if err == plumbing.ErrReferenceNotFound {
return nil, &ErrNotExist{ID: tagID.String()} return nil, &ErrNotExist{ID: tagID.String()}
@ -124,7 +124,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
tag := &Tag{ tag := &Tag{
Name: name, Name: name,
ID: tagID, ID: tagID,
Object: gogitTag.Target, Object: commitID.Type().MustID(gogitTag.Target[:]),
Type: tp, Type: tp,
Tagger: &gogitTag.Tagger, Tagger: &gogitTag.Tagger,
Message: gogitTag.Message, Message: gogitTag.Message,

@ -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) // 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 ObjectID) (string, error) {
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
defer cancel() defer cancel()
_, err := wr.Write([]byte(id.String() + "\n")) _, err := wr.Write([]byte(id.String() + "\n"))
@ -44,7 +44,7 @@ func (repo *Repository) GetTagType(id SHA1) (string, error) {
return typ, nil return typ, nil
} }
func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
t, ok := repo.tagCache.Get(tagID.String()) t, ok := repo.tagCache.Get(tagID.String())
if ok { if ok {
log.Debug("Hit cache: %s", tagID) 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 // every tag should have a commit ID so return all errors
return nil, err return nil, err
} }
commitID, err := NewIDFromString(commitIDStr) commitID, err := repo.objectFormat.NewIDFromString(commitIDStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,7 +117,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
return nil, err return nil, err
} }
tag, err := parseTagData(data) tag, err := parseTagData(tagID.Type(), data)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -194,6 +194,7 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
} }
func TestRepository_parseTagRef(t *testing.T) { func TestRepository_parseTagRef(t *testing.T) {
sha1 := ObjectFormatFromID(Sha1)
tests := []struct { tests := []struct {
name string name string
@ -223,8 +224,8 @@ func TestRepository_parseTagRef(t *testing.T) {
want: &Tag{ want: &Tag{
Name: "v1.9.1", Name: "v1.9.1",
ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), ID: sha1.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Object: sha1.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
Type: "commit", Type: "commit",
Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", 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{ want: &Tag{
Name: "v0.0.1", Name: "v0.0.1",
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), ID: sha1.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Object: sha1.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag", Type: "tag",
Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", 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{ want: &Tag{
Name: "v0.0.1", Name: "v0.0.1",
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), ID: sha1.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Object: sha1.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
Type: "tag", Type: "tag",
Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"), Tagger: parseAuthorLine(t, "Foo Bar <foo@bar.com> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", 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 { for _, test := range tests {
tc := test // don't close over loop variable tc := test // don't close over loop variable
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
got, err := parseTagRef(tc.givenRef) got, err := parseTagRef(sha1, tc.givenRef)
if tc.wantErr { if tc.wantErr {
require.Error(t, err) require.Error(t, err)

@ -21,7 +21,7 @@ type CommitTreeOpts struct {
} }
// CommitTree creates a commit from a given tree id for the user with provided message // 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) (ObjectID, error) {
commitTimeStr := time.Now().Format(time.RFC3339) commitTimeStr := time.Now().Format(time.RFC3339)
// Because this may call hooks we should pass in the environment // 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, Stderr: stderr,
}) })
if err != nil { if err != nil {
return SHA1{}, ConcatenateError(err, stderr.String()) return nil, ConcatenateError(err, stderr.String())
} }
return NewIDFromString(strings.TrimSpace(stdout.String())) return repo.objectFormat.NewIDFromString(strings.TrimSpace(stdout.String()))
} }

@ -6,8 +6,10 @@
package git package git
func (repo *Repository) getTree(id SHA1) (*Tree, error) { import "github.com/go-git/go-git/v5/plumbing"
gogitTree, err := repo.gogitRepo.TreeObject(id)
func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id.RawValue()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -19,7 +21,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
// GetTree find the tree object in the repository. // GetTree find the tree object in the repository.
func (repo *Repository) GetTree(idStr string) (*Tree, error) { func (repo *Repository) GetTree(idStr string) (*Tree, error) {
if len(idStr) != SHAFullLength { if len(idStr) != repo.objectFormat.FullLength() {
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path}) res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err return nil, err
@ -28,14 +30,14 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
idStr = res[:len(res)-1] idStr = res[:len(res)-1]
} }
} }
id, err := NewIDFromString(idStr) id, err := repo.objectFormat.NewIDFromString(idStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resolvedID := id resolvedID := id
commitObject, err := repo.gogitRepo.CommitObject(id) commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id.RawValue()))
if err == nil { if err == nil {
id = SHA1(commitObject.TreeHash) id = ParseGogitHash(commitObject.TreeHash)
} }
treeObject, err := repo.getTree(id) treeObject, err := repo.getTree(id)
if err != nil { if err != nil {

@ -9,7 +9,7 @@ import (
"io" "io"
) )
func (repo *Repository) getTree(id SHA1) (*Tree, error) { func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
wr, rd, cancel := repo.CatFileBatch(repo.Ctx) wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
defer cancel() defer cancel()
@ -28,7 +28,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
tag, err := parseTagData(data) tag, err := parseTagData(id.Type(), data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,7 +51,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
case "tree": case "tree":
tree := NewTree(repo, id) tree := NewTree(repo, id)
tree.ResolvedID = id tree.ResolvedID = id
tree.entries, err = catBatchParseTreeEntries(tree, rd, size) tree.entries, err = catBatchParseTreeEntries(repo.objectFormat, tree, rd, size)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -66,7 +66,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
// GetTree find the tree object in the repository. // GetTree find the tree object in the repository.
func (repo *Repository) GetTree(idStr string) (*Tree, error) { func (repo *Repository) GetTree(idStr string) (*Tree, error) {
if len(idStr) != SHAFullLength { if len(idStr) != repo.objectFormat.FullLength() {
res, err := repo.GetRefCommitID(idStr) res, err := repo.GetRefCommitID(idStr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -75,7 +75,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
idStr = res idStr = res
} }
} }
id, err := NewIDFromString(idStr) id, err := repo.objectFormat.NewIDFromString(idStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -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)
}

@ -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)
}

@ -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
}

@ -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"))
}

@ -17,8 +17,8 @@ const (
// Tag represents a Git tag. // Tag represents a Git tag.
type Tag struct { type Tag struct {
Name string Name string
ID SHA1 ID ObjectID
Object SHA1 // The id of this commit object Object ObjectID // The id of this commit object
Type string Type string
Tagger *Signature Tagger *Signature
Message string Message string
@ -33,8 +33,10 @@ func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) {
// Parse commit information from the (uncompressed) raw // Parse commit information from the (uncompressed) raw
// data from the commit object. // data from the commit object.
// \n\n separate headers from message // \n\n separate headers from message
func parseTagData(data []byte) (*Tag, error) { func parseTagData(objectFormat ObjectFormat, data []byte) (*Tag, error) {
tag := new(Tag) tag := new(Tag)
tag.ID = objectFormat.NewEmptyID()
tag.Object = objectFormat.NewEmptyID()
tag.Tagger = &Signature{} tag.Tagger = &Signature{}
// we now have the contents of the commit object. Let's investigate... // we now have the contents of the commit object. Let's investigate...
nextline := 0 nextline := 0
@ -48,7 +50,7 @@ l:
reftype := line[:spacepos] reftype := line[:spacepos]
switch string(reftype) { switch string(reftype) {
case "object": case "object":
id, err := NewIDFromString(string(line[spacepos+1:])) id, err := objectFormat.NewIDFromString(string(line[spacepos+1:]))
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -22,8 +22,8 @@ tagger Lucas Michot <lucas@semalead.com> 1484491741 +0100
`), tag: Tag{ `), tag: Tag{
Name: "", Name: "",
ID: SHA1{}, ID: NewSha1(),
Object: SHA1{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a}, Object: &Sha1Hash{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a},
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0)}, Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0)},
Message: "", Message: "",
@ -39,8 +39,8 @@ o
ono`), tag: Tag{ ono`), tag: Tag{
Name: "", Name: "",
ID: SHA1{}, ID: NewSha1(),
Object: SHA1{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc}, Object: &Sha1Hash{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc},
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0)}, Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0)},
Message: "test message\no\n\nono", Message: "test message\no\n\nono",
@ -49,7 +49,7 @@ ono`), tag: Tag{
} }
for _, test := range testData { for _, test := range testData {
tag, err := parseTagData(test.data) tag, err := parseTagData(ObjectFormatFromID(Sha1), test.data)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, test.tag.ID, tag.ID) assert.EqualValues(t, test.tag.ID, tag.ID)
assert.EqualValues(t, test.tag.Object, tag.Object) assert.EqualValues(t, test.tag.Object, tag.Object)

@ -10,7 +10,7 @@ import (
) )
// NewTree create a new tree according the repository and tree id // NewTree create a new tree according the repository and tree id
func NewTree(repo *Repository, id SHA1) *Tree { func NewTree(repo *Repository, id ObjectID) *Tree {
return &Tree{ return &Tree{
ID: id, ID: id,
repo: repo, repo: repo,

@ -24,7 +24,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
gogitTreeEntry: &object.TreeEntry{ gogitTreeEntry: &object.TreeEntry{
Name: "", Name: "",
Mode: filemode.Dir, Mode: filemode.Dir,
Hash: t.ID, Hash: plumbing.Hash(t.ID.RawValue()),
}, },
}, nil }, nil
} }

@ -14,7 +14,7 @@ import (
// TreeEntry the leaf in the git tree // TreeEntry the leaf in the git tree
type TreeEntry struct { type TreeEntry struct {
ID SHA1 ID ObjectID
gogitTreeEntry *object.TreeEntry gogitTreeEntry *object.TreeEntry
ptree *Tree ptree *Tree
@ -88,7 +88,7 @@ func (te *TreeEntry) Blob() *Blob {
} }
return &Blob{ return &Blob{
ID: te.gogitTreeEntry.Hash, ID: ParseGogitHash(te.gogitTreeEntry.Hash),
gogitEncodedObj: encodedObj, gogitEncodedObj: encodedObj,
name: te.Name(), name: te.Name(),
} }

@ -9,7 +9,7 @@ import "code.gitea.io/gitea/modules/log"
// TreeEntry the leaf in the git tree // TreeEntry the leaf in the git tree
type TreeEntry struct { type TreeEntry struct {
ID SHA1 ID ObjectID
ptree *Tree ptree *Tree

@ -15,8 +15,8 @@ import (
// Tree represents a flat directory listing. // Tree represents a flat directory listing.
type Tree struct { type Tree struct {
ID SHA1 ID ObjectID
ResolvedID SHA1 ResolvedID ObjectID
repo *Repository repo *Repository
gogitTree *object.Tree gogitTree *object.Tree
@ -26,7 +26,7 @@ type Tree struct {
} }
func (t *Tree) loadTreeObject() error { 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 { if err != nil {
return err return err
} }
@ -47,7 +47,7 @@ func (t *Tree) ListEntries() (Entries, error) {
entries := make([]*TreeEntry, len(t.gogitTree.Entries)) entries := make([]*TreeEntry, len(t.gogitTree.Entries))
for i, entry := range t.gogitTree.Entries { for i, entry := range t.gogitTree.Entries {
entries[i] = &TreeEntry{ entries[i] = &TreeEntry{
ID: entry.Hash, ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &t.gogitTree.Entries[i], gogitTreeEntry: &t.gogitTree.Entries[i],
ptree: t, ptree: t,
} }
@ -81,7 +81,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
} }
convertedEntry := &TreeEntry{ convertedEntry := &TreeEntry{
ID: entry.Hash, ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &entry, gogitTreeEntry: &entry,
ptree: t, ptree: t,
fullName: fullName, fullName: fullName,

@ -13,8 +13,8 @@ import (
// Tree represents a flat directory listing. // Tree represents a flat directory listing.
type Tree struct { type Tree struct {
ID SHA1 ID ObjectID
ResolvedID SHA1 ResolvedID ObjectID
repo *Repository repo *Repository
// parent tree // parent tree
@ -54,7 +54,7 @@ func (t *Tree) ListEntries() (Entries, error) {
} }
} }
if typ == "tree" { if typ == "tree" {
t.entries, err = catBatchParseTreeEntries(t, rd, sz) t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,7 +90,7 @@ func (t *Tree) ListEntries() (Entries, error) {
} }
var err error var err error
t.entries, err = parseTreeEntries(stdout, t) t.entries, err = parseTreeEntries(t.repo.objectFormat, stdout, t)
if err == nil { if err == nil {
t.entriesParsed = true t.entriesParsed = true
} }
@ -114,7 +114,7 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
} }
var err error var err error
t.entriesRecursive, err = parseTreeEntries(stdout, t) t.entriesRecursive, err = parseTreeEntries(t.repo.objectFormat, stdout, t)
if err == nil { if err == nil {
t.entriesRecursiveParsed = true t.entriesRecursiveParsed = true
} }

@ -62,8 +62,8 @@ func isIndexable(entry *git.TreeEntry) bool {
} }
// parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command // parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command
func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) { func parseGitLsTreeOutput(objectFormat git.ObjectFormat, stdout []byte) ([]internal.FileUpdate, error) {
entries, err := git.ParseTreeEntries(stdout) entries, err := git.ParseTreeEntries(objectFormat, stdout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -92,7 +92,11 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
} }
var err error var err error
changes.Updates, err = parseGitLsTreeOutput(stdout) objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
if err != nil {
return nil, err
}
changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout)
return &changes, err return &changes, err
} }
@ -169,6 +173,11 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
if err != nil { if err != nil {
return nil, err return nil, err
} }
changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout)
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
if err != nil {
return nil, err
}
changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout)
return &changes, err return &changes, err
} }

@ -144,7 +144,7 @@ func TestCommitToPushCommit(t *testing.T) {
When: now, When: now,
} }
const hexString = "0123456789abcdef0123456789abcdef01234567" const hexString = "0123456789abcdef0123456789abcdef01234567"
sha1, err := git.NewIDFromString(hexString) sha1, err := git.IDFromString(hexString)
assert.NoError(t, err) assert.NoError(t, err)
pushCommit := CommitToPushCommit(&git.Commit{ pushCommit := CommitToPushCommit(&git.Commit{
ID: sha1, ID: sha1,
@ -169,11 +169,12 @@ func TestListToPushCommits(t *testing.T) {
When: now, When: now,
} }
hashType := git.ObjectFormatFromID(git.Sha1)
const hexString1 = "0123456789abcdef0123456789abcdef01234567" const hexString1 = "0123456789abcdef0123456789abcdef01234567"
hash1, err := git.NewIDFromString(hexString1) hash1, err := hashType.NewIDFromString(hexString1)
assert.NoError(t, err) assert.NoError(t, err)
const hexString2 = "fedcba9876543210fedcba9876543210fedcba98" const hexString2 = "fedcba9876543210fedcba9876543210fedcba98"
hash2, err := git.NewIDFromString(hexString2) hash2, err := hashType.NewIDFromString(hexString2)
assert.NoError(t, err) assert.NoError(t, err)
l := []*git.Commit{ l := []*git.Commit{

@ -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, git.ObjectFormatFromID(git.Sha1)); err != nil {
return err 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, git.ObjectFormatFromID(git.Sha1)); err != nil {
return generateRepo, err return generateRepo, err
} }

@ -188,7 +188,7 @@ func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
return nil return nil
} }
func CheckInitRepository(ctx context.Context, owner, name string) (err error) { func CheckInitRepository(ctx context.Context, owner, name string, objectFormat git.ObjectFormat) (err error) {
// Somehow the directory could exist. // Somehow the directory could exist.
repoPath := repo_model.RepoPath(owner, name) repoPath := repo_model.RepoPath(owner, name)
isExist, err := util.IsExist(repoPath) isExist, err := util.IsExist(repoPath)
@ -204,7 +204,7 @@ func CheckInitRepository(ctx context.Context, owner, name string) (err error) {
} }
// Init git bare new repository. // Init git bare new repository.
if err = git.InitRepository(ctx, repoPath, true); err != nil { if err = git.InitRepository(ctx, repoPath, true, objectFormat); err != nil {
return fmt.Errorf("git.InitRepository: %w", err) return fmt.Errorf("git.InitRepository: %w", err)
} else if err = CreateDelegateHooks(repoPath); err != nil { } else if err = CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err) return fmt.Errorf("createDelegateHooks: %w", err)

@ -20,12 +20,14 @@ type PushUpdateOptions struct {
// IsNewRef return true if it's a first-time push to a branch, tag or etc. // IsNewRef return true if it's a first-time push to a branch, tag or etc.
func (opts *PushUpdateOptions) IsNewRef() bool { func (opts *PushUpdateOptions) IsNewRef() bool {
return opts.OldCommitID == git.EmptySHA commitID, err := git.IDFromString(opts.OldCommitID)
return err == nil && commitID.IsZero()
} }
// IsDelRef return true if it's a deletion to a branch or tag // IsDelRef return true if it's a deletion to a branch or tag
func (opts *PushUpdateOptions) IsDelRef() bool { func (opts *PushUpdateOptions) IsDelRef() bool {
return opts.NewCommitID == git.EmptySHA commitID, err := git.IDFromString(opts.NewCommitID)
return err == nil && commitID.IsZero()
} }
// IsUpdateRef return true if it's an update operation // IsUpdateRef return true if it's an update operation

@ -66,7 +66,7 @@ func getNote(ctx *context.APIContext, identifier string) {
return return
} }
commitSHA, err := ctx.Repo.GitRepo.ConvertToSHA1(identifier) commitID, err := ctx.Repo.GitRepo.ConvertToGitID(identifier)
if err != nil { if err != nil {
if git.IsErrNotExist(err) { if git.IsErrNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
@ -77,7 +77,7 @@ func getNote(ctx *context.APIContext, identifier string) {
} }
var note git.Note var note git.Note
if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitSHA.String(), &note); err != nil { if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitID.String(), &note); err != nil {
if git.IsErrNotExist(err) { if git.IsErrNotExist(err) {
ctx.NotFound(identifier) ctx.NotFound(identifier)
return return

@ -253,6 +253,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
DefaultBranch: opt.DefaultBranch, DefaultBranch: opt.DefaultBranch,
TrustModel: repo_model.ToTrustModel(opt.TrustModel), TrustModel: repo_model.ToTrustModel(opt.TrustModel),
IsTemplate: opt.Template, IsTemplate: opt.Template,
ObjectFormat: git.ObjectFormatFromID(git.Sha1),
}) })
if err != nil { if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) { if repo_model.IsErrRepoAlreadyExist(err) {

@ -69,27 +69,28 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
return "", "", nil return "", "", nil
} }
// ConvertToSHA1 returns a full-length SHA1 from a potential ID string // ConvertToObjectID returns a full-length SHA1 from a potential ID string
func ConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) (git.SHA1, error) { func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) { objectFormat, _ := repo.GitRepo.GetObjectFormat()
sha1, err := git.NewIDFromString(commitID) if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
sha, err := objectFormat.NewIDFromString(commitID)
if err == nil { if err == nil {
return sha1, nil return sha, nil
} }
} }
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.Repository.RepoPath()) gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.Repository.RepoPath())
if err != nil { if err != nil {
return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err) return objectFormat.Empty(), fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
} }
defer closer.Close() defer closer.Close()
return gitRepo.ConvertToSHA1(commitID) return gitRepo.ConvertToGitID(commitID)
} }
// MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1 // MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1
func MustConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) string { func MustConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) string {
sha, err := ConvertToSHA1(ctx, repo, commitID) sha, err := ConvertToObjectID(ctx, repo, commitID)
if err != nil { if err != nil {
return commitID return commitID
} }

@ -159,8 +159,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
} }
// If we've pushed a branch (and not deleted it) // If we've pushed a branch (and not deleted it)
if newCommitID != git.EmptySHA && refFullName.IsBranch() { if git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() {
// First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
if repo == nil { if repo == nil {
repo = loadRepository(ctx, ownerName, repoName) repo = loadRepository(ctx, ownerName, repoName)

@ -145,8 +145,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
gitRepo := ctx.Repo.GitRepo gitRepo := ctx.Repo.GitRepo
objectFormat, _ := gitRepo.GetObjectFormat()
if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA { if branchName == repo.DefaultBranch && newCommitID == objectFormat.Empty().String() {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo) log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{ ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName), UserMsg: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName),
@ -174,7 +175,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
// First of all we need to enforce absolutely: // First of all we need to enforce absolutely:
// //
// 1. Detect and prevent deletion of the branch // 1. Detect and prevent deletion of the branch
if newCommitID == git.EmptySHA { if newCommitID == objectFormat.Empty().String() {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{ ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from deletion", branchName), UserMsg: fmt.Sprintf("branch %s is protected from deletion", branchName),
@ -183,7 +184,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
} }
// 2. Disallow force pushes to protected branches // 2. Disallow force pushes to protected branches
if git.EmptySHA != oldCommitID { if oldCommitID != objectFormat.Empty().String() {
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env}) 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 { if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)

@ -29,7 +29,8 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
}() }()
var command *git.Command var command *git.Command
if oldCommitID == git.EmptySHA { objectFormat, _ := repo.GetObjectFormat()
if oldCommitID == objectFormat.Empty().String() {
// When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all": // 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 // 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 // So, it only lists the new commits received, doesn't list the commits already present in the receiving repository
@ -82,7 +83,8 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
_ = stdoutReader.Close() _ = stdoutReader.Close()
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
}() }()
hash := git.MustIDFromString(sha) objectFormat, _ := repo.GetObjectFormat()
commitID := objectFormat.MustIDFromString(sha)
return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha). return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha).
Run(&git.RunOpts{ Run(&git.RunOpts{
@ -91,7 +93,7 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
Stdout: stdoutWriter, Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close() _ = stdoutWriter.Close()
commit, err := git.CommitFromReader(repo, hash, stdoutReader) commit, err := git.CommitFromReader(repo, commitID, stdoutReader)
if err != nil { if err != nil {
return err return err
} }

@ -22,14 +22,17 @@ func TestVerifyCommits(t *testing.T) {
defer gitRepo.Close() defer gitRepo.Close()
assert.NoError(t, err) assert.NoError(t, err)
objectFormat, err := gitRepo.GetObjectFormat()
assert.NoError(t, err)
testCases := []struct { testCases := []struct {
base, head string base, head string
verified bool verified bool
}{ }{
{"72920278f2f999e3005801e5d5b8ab8139d3641c", "d766f2917716d45be24bfa968b8409544941be32", true}, {"72920278f2f999e3005801e5d5b8ab8139d3641c", "d766f2917716d45be24bfa968b8409544941be32", true},
{git.EmptySHA, "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit {objectFormat.Empty().String(), "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit
{"9779d17a04f1e2640583d35703c62460b2d86e0a", "72920278f2f999e3005801e5d5b8ab8139d3641c", false}, {"9779d17a04f1e2640583d35703c62460b2d86e0a", "72920278f2f999e3005801e5d5b8ab8139d3641c", false},
{git.EmptySHA, "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit {objectFormat.Empty().String(), "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit
} }
for _, tc := range testCases { for _, tc := range testCases {

@ -131,7 +131,12 @@ type blameResult struct {
} }
func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) { 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) objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
if err != nil {
ctx.NotFound("CreateBlameReader", err)
return nil, err
}
blameReader, err := git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, bypassBlameIgnore)
if err != nil { if err != nil {
return nil, err 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 { if len(r.Parts) == 0 && r.UsesIgnoreRevs {
// try again without ignored revs // try again without ignored revs
blameReader, err = git.CreateBlameReader(ctx, repoPath, commit, file, true) blameReader, err = git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -147,11 +147,18 @@ func RestoreBranchPost(ctx *context.Context) {
return return
} }
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
if err != nil {
log.Error("RestoreBranch: CreateBranch: %w", err)
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
return
}
// Don't return error below this // Don't return error below this
if err := repo_service.PushUpdate( if err := repo_service.PushUpdate(
&repo_module.PushUpdateOptions{ &repo_module.PushUpdateOptions{
RefFullName: git.RefNameFromBranch(deletedBranch.Name), RefFullName: git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: git.EmptySHA, OldCommitID: objectFormat.Empty().String(),
NewCommitID: deletedBranch.CommitID, NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID, PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name, PusherName: ctx.Doer.Name,

@ -294,7 +294,7 @@ func Diff(ctx *context.Context) {
} }
return return
} }
if len(commitID) != git.SHAFullLength { if len(commitID) != commit.ID.Type().FullLength() {
commitID = commit.ID.String() commitID = commit.ID.String()
} }

@ -310,13 +310,14 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch) baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch) baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch) baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch)
objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
if !baseIsCommit && !baseIsBranch && !baseIsTag { if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash // Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil { if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil {
ci.BaseBranch = baseCommit.ID.String() ci.BaseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = ci.BaseBranch ctx.Data["BaseBranch"] = ci.BaseBranch
baseIsCommit = true baseIsCommit = true
} else if ci.BaseBranch == git.EmptySHA { } else if ci.BaseBranch == objectFormat.Empty().String() {
if isSameRepo { if isSameRepo {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch)) ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch))
} else { } else {

@ -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, git.ObjectFormatFromID(git.Sha1)); err != nil {
log.Error("Failed to init bare repo for git-receive-pack cache: %v", err) log.Error("Failed to init bare repo for git-receive-pack cache: %v", err)
return return
} }

@ -159,6 +159,7 @@ func Create(ctx *context.Context) {
ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
ctx.Data["default_branch"] = setting.Repository.DefaultBranch ctx.Data["default_branch"] = setting.Repository.DefaultBranch
ctx.Data["hash_type"] = "sha1"
ctxUser := checkContextUser(ctx, ctx.FormInt64("org")) ctxUser := checkContextUser(ctx, ctx.FormInt64("org"))
if ctx.Written() { if ctx.Written() {
@ -288,6 +289,7 @@ func CreatePost(ctx *context.Context) {
AutoInit: form.AutoInit, AutoInit: form.AutoInit,
IsTemplate: form.Template, IsTemplate: form.Template,
TrustModel: repo_model.ToTrustModel(form.TrustModel), TrustModel: repo_model.ToTrustModel(form.TrustModel),
ObjectFormat: form.ObjectFormat,
}) })
if err == nil { if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)

@ -388,20 +388,21 @@ func LFSFileFind(ctx *context.Context) {
sha := ctx.FormString("sha") sha := ctx.FormString("sha")
ctx.Data["Title"] = oid ctx.Data["Title"] = oid
ctx.Data["PageIsSettingsLFS"] = true ctx.Data["PageIsSettingsLFS"] = true
var hash git.SHA1 objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
var objectID git.ObjectID
if len(sha) == 0 { if len(sha) == 0 {
pointer := lfs.Pointer{Oid: oid, Size: size} pointer := lfs.Pointer{Oid: oid, Size: size}
hash = git.ComputeBlobHash([]byte(pointer.StringContent())) objectID = git.ComputeBlobHash(objectFormat, []byte(pointer.StringContent()))
sha = hash.String() sha = objectID.String()
} else { } else {
hash = git.MustIDFromString(sha) objectID = objectFormat.MustIDFromString(sha)
} }
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs" ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
ctx.Data["Oid"] = oid ctx.Data["Oid"] = oid
ctx.Data["Size"] = size ctx.Data["Size"] = size
ctx.Data["SHA"] = sha ctx.Data["SHA"] = sha
results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, hash) results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, objectID)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.Error("Failure in FindLFSFile: %v", err) log.Error("Failure in FindLFSFile: %v", err)
ctx.ServerError("LFSFind: FindLFSFile.", err) ctx.ServerError("LFSFind: FindLFSFile.", err)

@ -655,8 +655,14 @@ func TestWebhook(ctx *context.Context) {
commit := ctx.Repo.Commit commit := ctx.Repo.Commit
if commit == nil { if commit == nil {
ghost := user_model.NewGhostUser() ghost := user_model.NewGhostUser()
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
if err != nil {
ctx.Flash.Error("GetObjectFormatOfRepo: " + err.Error())
ctx.Status(http.StatusInternalServerError)
return
}
commit = &git.Commit{ commit = &git.Commit{
ID: git.MustIDFromString(git.EmptySHA), ID: objectFormat.NewEmptyID(),
Author: ghost.NewGitSig(), Author: ghost.NewGitSig(),
Committer: ghost.NewGitSig(), Committer: ghost.NewGitSig(),
CommitMessage: "This is a fake commit", CommitMessage: "This is a fake commit",

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook" 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() creator := user_model.NewActionsUser()
commitID, err := git.IDFromString(sha)
if err != nil {
return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err)
}
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
Repo: repo, Repo: repo,
SHA: sha, SHA: commitID,
Creator: creator, Creator: creator,
CommitStatus: &git_model.CommitStatus{ CommitStatus: &git_model.CommitStatus{
SHA: sha, SHA: sha,

@ -36,9 +36,10 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
topicBranch = opts.GitPushOptions["topic"] topicBranch = opts.GitPushOptions["topic"]
_, forcePush = opts.GitPushOptions["force-push"] _, forcePush = opts.GitPushOptions["force-push"]
objectFormat, _ := gitRepo.GetObjectFormat()
for i := range opts.OldCommitIDs { for i := range opts.OldCommitIDs {
if opts.NewCommitIDs[i] == git.EmptySHA { if opts.NewCommitIDs[i] == objectFormat.Empty().String() {
results = append(results, private.HookProcReceiveRefResult{ results = append(results, private.HookProcReceiveRefResult{
OriginalRef: opts.RefFullNames[i], OriginalRef: opts.RefFullNames[i],
OldOID: opts.OldCommitIDs[i], OldOID: opts.OldCommitIDs[i],
@ -148,10 +149,11 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID) log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
objectFormat, _ := gitRepo.GetObjectFormat()
results = append(results, private.HookProcReceiveRefResult{ results = append(results, private.HookProcReceiveRefResult{
Ref: pr.GetGitRefName(), Ref: pr.GetGitRefName(),
OriginalRef: opts.RefFullNames[i], OriginalRef: opts.RefFullNames[i],
OldOID: git.EmptySHA, OldOID: objectFormat.Empty().String(),
NewOID: opts.NewCommitIDs[i], NewOID: opts.NewCommitIDs[i],
}) })
continue continue

@ -19,12 +19,12 @@ import (
func TestToCommitMeta(t *testing.T) { func TestToCommitMeta(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000") sha1 := git.ObjectFormatFromID(git.Sha1)
signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)}
tag := &git.Tag{ tag := &git.Tag{
Name: "Test Tag", Name: "Test Tag",
ID: sha1, ID: sha1.Empty(),
Object: sha1, Object: sha1.Empty(),
Type: "Test Type", Type: "Test Type",
Tagger: signature, Tagger: signature,
Message: "Test Message", Message: "Test Message",
@ -34,8 +34,8 @@ func TestToCommitMeta(t *testing.T) {
assert.NotNil(t, commitMeta) assert.NotNil(t, commitMeta)
assert.EqualValues(t, &api.CommitMeta{ assert.EqualValues(t, &api.CommitMeta{
SHA: "0000000000000000000000000000000000000000", SHA: sha1.Empty().String(),
URL: util.URLJoin(headRepo.APIURL(), "git/commits", "0000000000000000000000000000000000000000"), URL: util.URLJoin(headRepo.APIURL(), "git/commits", sha1.Empty().String()),
Created: time.Unix(0, 0), Created: time.Unix(0, 0),
}, commitMeta) }, commitMeta)
} }

@ -13,6 +13,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
@ -53,6 +54,7 @@ type CreateRepoForm struct {
TrustModel string TrustModel string
ForkSingleBranch string ForkSingleBranch string
ObjectFormat git.ObjectFormat
} }
// Validate validates the fields // Validate validates the fields

@ -1115,10 +1115,15 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
} }
cmdDiff := git.NewCommand(gitRepo.Ctx) cmdDiff := git.NewCommand(gitRepo.Ctx)
if (len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA) && commit.ParentCount() == 0 { objectFormat, err := gitRepo.GetObjectFormat()
if err != nil {
return nil, err
}
if (len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == objectFormat.Empty().String()) && commit.ParentCount() == 0 {
cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"). cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M").
AddArguments(opts.WhitespaceBehavior...). AddArguments(opts.WhitespaceBehavior...).
AddArguments("4b825dc642cb6eb9a060e54bf8d69288fbee4904"). // append empty tree ref AddDynamicArguments(objectFormat.EmptyTree().String()).
AddDynamicArguments(opts.AfterCommitID) AddDynamicArguments(opts.AfterCommitID)
} else { } else {
actualBeforeCommitID := opts.BeforeCommitID actualBeforeCommitID := opts.BeforeCommitID
@ -1224,8 +1229,8 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
} }
diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID}
if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == objectFormat.Empty().String() {
diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} diffPaths = []string{objectFormat.EmptyTree().String(), opts.AfterCommitID}
} }
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {
@ -1256,12 +1261,15 @@ func GetPullDiffStats(gitRepo *git.Repository, opts *DiffOptions) (*PullDiffStat
separator = ".." separator = ".."
} }
diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} objectFormat, err := gitRepo.GetObjectFormat()
if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { if err != nil {
diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} return nil, err
} }
var err error diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID}
if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == objectFormat.Empty().String() {
diffPaths = []string{objectFormat.EmptyTree().String(), opts.AfterCommitID}
}
_, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
if err != nil && strings.Contains(err.Error(), "no merge base") { if err != nil && strings.Contains(err.Error(), "no merge base") {

@ -48,16 +48,18 @@ func CheckAndEnsureSafePR(pr *base.PullRequest, commonCloneBaseURL string, g bas
} }
// SECURITY: SHAs Must be a SHA // SECURITY: SHAs Must be a SHA
if pr.MergeCommitSHA != "" && !git.IsValidSHAPattern(pr.MergeCommitSHA) { // FIXME: hash only a SHA1
CommitType := git.ObjectFormatFromID(git.Sha1)
if pr.MergeCommitSHA != "" && !CommitType.IsValid(pr.MergeCommitSHA) {
WarnAndNotice("PR #%d in %s has invalid MergeCommitSHA: %s", pr.Number, g, pr.MergeCommitSHA) WarnAndNotice("PR #%d in %s has invalid MergeCommitSHA: %s", pr.Number, g, pr.MergeCommitSHA)
pr.MergeCommitSHA = "" pr.MergeCommitSHA = ""
} }
if pr.Head.SHA != "" && !git.IsValidSHAPattern(pr.Head.SHA) { if pr.Head.SHA != "" && !CommitType.IsValid(pr.Head.SHA) {
WarnAndNotice("PR #%d in %s has invalid HeadSHA: %s", pr.Number, g, pr.Head.SHA) WarnAndNotice("PR #%d in %s has invalid HeadSHA: %s", pr.Number, g, pr.Head.SHA)
pr.Head.SHA = "" pr.Head.SHA = ""
valid = false valid = false
} }
if pr.Base.SHA != "" && !git.IsValidSHAPattern(pr.Base.SHA) { if pr.Base.SHA != "" && !CommitType.IsValid(pr.Base.SHA) {
WarnAndNotice("PR #%d in %s has invalid BaseSHA: %s", pr.Number, g, pr.Base.SHA) WarnAndNotice("PR #%d in %s has invalid BaseSHA: %s", pr.Number, g, pr.Base.SHA)
pr.Base.SHA = "" pr.Base.SHA = ""
valid = false valid = false

@ -892,7 +892,8 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
comment.UpdatedAt = comment.CreatedAt comment.UpdatedAt = comment.CreatedAt
} }
if !git.IsValidSHAPattern(comment.CommitID) { objectFormat, _ := g.gitRepo.GetObjectFormat()
if !objectFormat.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) 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 comment.CommitID = headCommitID
} }

@ -232,7 +232,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
// //
fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
baseRef := "master" baseRef := "master"
assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false)) assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormat))
err := git.NewCommand(git.DefaultContext, "symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(&git.RunOpts{Dir: fromRepo.RepoPath()}) 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, 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)) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644))

@ -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) log.Error("SyncMirrors [repo: %-v]: unable to GetRefCommitID [ref_name: %s]: %v", m.Repo, result.refName, err)
continue continue
} }
objectFormat, err := git.GetObjectFormatOfRepo(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{ notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
RefFullName: result.refName, RefFullName: result.refName,
OldCommitID: git.EmptySHA, OldCommitID: objectFormat.Empty().String(),
NewCommitID: commitID, NewCommitID: commitID,
}, repo_module.NewPushCommits()) }, repo_module.NewPushCommits())
notify_service.SyncCreateRef(ctx, m.Repo.MustOwner(ctx), m.Repo, result.refName, commitID) notify_service.SyncCreateRef(ctx, m.Repo.MustOwner(ctx), m.Repo, result.refName, commitID)

@ -271,7 +271,7 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re
if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
return err return err
} }
if err := t.Init(); err != nil { if err := t.Init(repo.ObjectFormat); err != nil {
return err return err
} }
} else { } else {

@ -215,24 +215,29 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com
return nil, fmt.Errorf("GetFullCommitID(%s) in %s: %w", prHeadRef, pr.BaseRepo.FullName(), err) return nil, fmt.Errorf("GetFullCommitID(%s) in %s: %w", prHeadRef, pr.BaseRepo.FullName(), err)
} }
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
return nil, fmt.Errorf("%-v OpenRepository: %w", pr.BaseRepo, err)
}
defer gitRepo.Close()
objectFormat, err := gitRepo.GetObjectFormat()
if err != nil {
return nil, fmt.Errorf("%-v GetObjectFormat: %w", pr.BaseRepo, err)
}
// Get the commit from BaseBranch where the pull request got merged // Get the commit from BaseBranch where the pull request got merged
mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse"). mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse").
AddDynamicArguments(prHeadCommitID + ".." + pr.BaseBranch). AddDynamicArguments(prHeadCommitID + ".." + pr.BaseBranch).
RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()})
if err != nil { if err != nil {
return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err) return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err)
} else if len(mergeCommit) < git.SHAFullLength { } else if len(mergeCommit) < objectFormat.FullLength() {
// PR was maybe fast-forwarded, so just use last commit of PR // PR was maybe fast-forwarded, so just use last commit of PR
mergeCommit = prHeadCommitID mergeCommit = prHeadCommitID
} }
mergeCommit = strings.TrimSpace(mergeCommit) mergeCommit = strings.TrimSpace(mergeCommit)
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
return nil, fmt.Errorf("%-v OpenRepository: %w", pr.BaseRepo, err)
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(mergeCommit) commit, err := gitRepo.GetCommit(mergeCommit)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetMergeCommit[%s]: %w", mergeCommit, err) return nil, fmt.Errorf("GetMergeCommit[%s]: %w", mergeCommit, err)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save