mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 06:38:31 +00:00 
			
		
		
		
	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.
This commit is contained in:
		| @@ -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 | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										103
									
								
								modules/git/object_format.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								modules/git/object_format.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								modules/git/object_id.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								modules/git/object_id.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								modules/git/object_id_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								modules/git/object_id_gogit.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								modules/git/object_id_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								modules/git/object_id_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) { | ||||||
| 	if err != nil { | 	return 0, io.EOF | ||||||
| 		return SHA1{}, err |  | ||||||
| 	} |  | ||||||
| 	return NewIDFromString(idStr) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (repo *Repository) hashObject(reader io.Reader) (string, error) { | func (repo *Repository) GetObjectFormat() (ObjectFormat, error) { | ||||||
| 	cmd := NewCommand(repo.Ctx, "hash-object", "-w", "--stdin") | 	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 { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repo.objectFormat = hash.Type() | ||||||
|  |  | ||||||
|  | 	return repo.objectFormat, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HashObject takes a reader and returns hash for that reader | ||||||
|  | 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(), ¬e); err != nil { | 	if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitID.String(), ¬e); 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
		Reference in New Issue
	
	Block a user