mirror of
https://github.com/go-gitea/gitea
synced 2025-07-23 02:38:35 +00:00
Merge branch 'main' into allow-force-push-protected-branches
This commit is contained in:
@@ -308,6 +308,12 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
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 {
|
||||
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
|
||||
if err != nil {
|
||||
@@ -325,7 +331,6 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
|
||||
|
||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
@@ -342,7 +347,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) == git.SHAFullLength {
|
||||
} else if len(refName) == objectFormat.FullLength() {
|
||||
ctx.Repo.CommitID = refName
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
|
@@ -668,11 +668,9 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
ListOptions: db.ListOptionsAll,
|
||||
}
|
||||
branchesTotal, err := git_model.CountBranches(ctx, branchOpts)
|
||||
branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
|
||||
if err != nil {
|
||||
ctx.ServerError("CountBranches", err)
|
||||
return cancel
|
||||
@@ -827,7 +825,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
||||
}
|
||||
// For legacy and API support only full commit sha
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 && len(parts[0]) == git.SHAFullLength {
|
||||
objectFormat, _ := repo.GitRepo.GetObjectFormat()
|
||||
|
||||
if len(parts) > 0 && len(parts[0]) == objectFormat.FullLength() {
|
||||
repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
@@ -871,7 +871,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
||||
return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist)
|
||||
case RepoRefCommit:
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength {
|
||||
objectFormat, _ := repo.GitRepo.GetObjectFormat()
|
||||
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= objectFormat.FullLength() {
|
||||
repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
@@ -931,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.
|
||||
if len(ctx.Params("*")) == 0 {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
@@ -997,7 +1005,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
return cancel
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) >= 7 && len(refName) <= git.SHAFullLength {
|
||||
} else if len(refName) >= 7 && len(refName) <= objectFormat.FullLength() {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
@@ -1007,7 +1015,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
return cancel
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < git.SHAFullLength {
|
||||
if len(refName) < objectFormat.FullLength() {
|
||||
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
|
||||
}
|
||||
|
@@ -79,6 +79,7 @@ var Checks []*Check
|
||||
|
||||
// RunChecks runs the doctor checks for the provided list
|
||||
func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error {
|
||||
SortChecks(checks)
|
||||
// the checks output logs by a special logger, they do not use the default logger
|
||||
logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
|
||||
loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
|
||||
@@ -104,20 +105,23 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err
|
||||
logger.Info("OK")
|
||||
}
|
||||
}
|
||||
logger.Info("\nAll done.")
|
||||
logger.Info("\nAll done (checks: %d).", len(checks))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register registers a command with the list
|
||||
func Register(command *Check) {
|
||||
Checks = append(Checks, command)
|
||||
sort.SliceStable(Checks, func(i, j int) bool {
|
||||
if Checks[i].Priority == Checks[j].Priority {
|
||||
return Checks[i].Name < Checks[j].Name
|
||||
}
|
||||
|
||||
func SortChecks(checks []*Check) {
|
||||
sort.SliceStable(checks, func(i, j int) bool {
|
||||
if checks[i].Priority == checks[j].Priority {
|
||||
return checks[i].Name < checks[j].Name
|
||||
}
|
||||
if Checks[i].Priority == 0 {
|
||||
if checks[i].Priority == 0 {
|
||||
return false
|
||||
}
|
||||
return Checks[i].Priority < Checks[j].Priority
|
||||
return checks[i].Priority < checks[j].Priority
|
||||
})
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ func handleDeleteOrphanedRepos(ctx context.Context, logger log.Logger, autofix b
|
||||
|
||||
// countOrphanedRepos count repository where user of owner_id do not exist
|
||||
func countOrphanedRepos(ctx context.Context) (int64, error) {
|
||||
return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=user.id")
|
||||
return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=`user`.id")
|
||||
}
|
||||
|
||||
// deleteOrphanedRepos delete repository where user of owner_id do not exist
|
||||
@@ -43,7 +43,7 @@ func deleteOrphanedRepos(ctx context.Context) (int64, error) {
|
||||
default:
|
||||
var ids []int64
|
||||
if err := e.Table("`repository`").
|
||||
Join("LEFT", "`user`", "repository.owner_id=user.id").
|
||||
Join("LEFT", "`user`", "repository.owner_id=`user`.id").
|
||||
Where(builder.IsNull{"`user`.id"}).
|
||||
Select("`repository`.id").Limit(batchSize).Find(&ids); err != nil {
|
||||
return deleted, err
|
||||
|
@@ -148,7 +148,7 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
|
||||
// ReadBatchLine reads the header line from cat-file --batch
|
||||
// We expect:
|
||||
// <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) {
|
||||
typ, err = rd.ReadString('\n')
|
||||
if err != nil {
|
||||
@@ -251,20 +251,19 @@ headerLoop:
|
||||
}
|
||||
|
||||
// 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
|
||||
// Therefore we need some method to convert these 20-byte SHAs to a 40-byte SHA
|
||||
// Therefore we need some method to convert these binary hashes to hex hashes
|
||||
|
||||
// constant hextable to help quickly convert between 20byte and 40byte hashes
|
||||
// constant hextable to help quickly convert between binary and hex representation
|
||||
const hextable = "0123456789abcdef"
|
||||
|
||||
// To40ByteSHA converts a 20-byte SHA into a 40-byte sha. Input and output can be the
|
||||
// same 40 byte slice to support in place conversion without allocations.
|
||||
// BinToHexHeash converts a binary Hash into a hex encoded one. Input and output can be the
|
||||
// same byte slice to support in place conversion without allocations.
|
||||
// This is at least 100x quicker that hex.EncodeToString
|
||||
// NB This requires that out is a 40-byte slice
|
||||
func To40ByteSHA(sha, out []byte) []byte {
|
||||
for i := 19; i >= 0; i-- {
|
||||
func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
|
||||
for i := objectFormat.FullLength()/2 - 1; i >= 0; i-- {
|
||||
v := sha[i]
|
||||
vhi, vlo := v>>4, v&0x0f
|
||||
shi, slo := hextable[vhi], hextable[vlo]
|
||||
@@ -278,10 +277,10 @@ func To40ByteSHA(sha, out []byte) []byte {
|
||||
// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
|
||||
//
|
||||
// Each line is composed of:
|
||||
// <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
|
||||
func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
|
||||
// We don't attempt to convert the raw HASH to save a lot of time
|
||||
func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
|
||||
var readBytes []byte
|
||||
|
||||
// Read the Mode & fname
|
||||
@@ -324,11 +323,12 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn
|
||||
fnameBuf = fnameBuf[:len(fnameBuf)-1]
|
||||
fname = fnameBuf
|
||||
|
||||
// Deal with the 20-byte SHA
|
||||
// Deal with the binary hash
|
||||
idx = 0
|
||||
for idx < 20 {
|
||||
len := objectFormat.FullLength() / 2
|
||||
for idx < len {
|
||||
var read int
|
||||
read, err = rd.Read(shaBuf[idx:20])
|
||||
read, err = rd.Read(shaBuf[idx:len])
|
||||
n += read
|
||||
if err != nil {
|
||||
return mode, fname, sha, n, err
|
||||
|
@@ -10,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -18,8 +17,10 @@ import (
|
||||
|
||||
// BlamePart represents block of blame - continuous lines with one sha
|
||||
type BlamePart struct {
|
||||
Sha string
|
||||
Lines []string
|
||||
Sha string
|
||||
Lines []string
|
||||
PreviousSha string
|
||||
PreviousPath string
|
||||
}
|
||||
|
||||
// BlameReader returns part of file blame one by one
|
||||
@@ -30,47 +31,56 @@ type BlameReader struct {
|
||||
done chan error
|
||||
lastSha *string
|
||||
ignoreRevsFile *string
|
||||
objectFormat ObjectFormat
|
||||
}
|
||||
|
||||
func (r *BlameReader) UsesIgnoreRevs() bool {
|
||||
return r.ignoreRevsFile != nil
|
||||
}
|
||||
|
||||
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
|
||||
|
||||
// NextPart returns next part of blame (sequential code lines with the same commit)
|
||||
func (r *BlameReader) NextPart() (*BlamePart, error) {
|
||||
var blamePart *BlamePart
|
||||
|
||||
if r.lastSha != nil {
|
||||
blamePart = &BlamePart{*r.lastSha, make([]string, 0)}
|
||||
blamePart = &BlamePart{
|
||||
Sha: *r.lastSha,
|
||||
Lines: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
var line []byte
|
||||
const previousHeader = "previous "
|
||||
var lineBytes []byte
|
||||
var isPrefix bool
|
||||
var err error
|
||||
|
||||
for err != io.EOF {
|
||||
line, isPrefix, err = r.bufferedReader.ReadLine()
|
||||
lineBytes, isPrefix, err = r.bufferedReader.ReadLine()
|
||||
if err != nil && err != io.EOF {
|
||||
return blamePart, err
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
if len(lineBytes) == 0 {
|
||||
// isPrefix will be false
|
||||
continue
|
||||
}
|
||||
|
||||
lines := shaLineRegex.FindSubmatch(line)
|
||||
if lines != nil {
|
||||
sha1 := string(lines[1])
|
||||
var objectID string
|
||||
objectFormatLength := r.objectFormat.FullLength()
|
||||
|
||||
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 {
|
||||
blamePart = &BlamePart{sha1, make([]string, 0)}
|
||||
blamePart = &BlamePart{
|
||||
Sha: objectID,
|
||||
Lines: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
if blamePart.Sha != sha1 {
|
||||
r.lastSha = &sha1
|
||||
if blamePart.Sha != objectID {
|
||||
r.lastSha = &objectID
|
||||
// need to munch to end of line...
|
||||
for isPrefix {
|
||||
_, isPrefix, err = r.bufferedReader.ReadLine()
|
||||
@@ -80,10 +90,13 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
|
||||
}
|
||||
return blamePart, nil
|
||||
}
|
||||
} else if line[0] == '\t' {
|
||||
code := line[1:]
|
||||
|
||||
blamePart.Lines = append(blamePart.Lines, string(code))
|
||||
} else if lineBytes[0] == '\t' {
|
||||
blamePart.Lines = append(blamePart.Lines, string(lineBytes[1:]))
|
||||
} else if bytes.HasPrefix(lineBytes, []byte(previousHeader)) {
|
||||
offset := len(previousHeader) // already includes a space
|
||||
blamePart.PreviousSha = string(lineBytes[offset : offset+objectFormatLength])
|
||||
offset += objectFormatLength + 1 // +1 for space
|
||||
blamePart.PreviousPath = string(lineBytes[offset:])
|
||||
}
|
||||
|
||||
// need to munch to end of line...
|
||||
@@ -113,7 +126,7 @@ func (r *BlameReader) Close() error {
|
||||
}
|
||||
|
||||
// CreateBlameReader creates reader for given repository, commit and file
|
||||
func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||
var ignoreRevsFile *string
|
||||
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore {
|
||||
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
|
||||
@@ -162,6 +175,7 @@ func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, fil
|
||||
bufferedReader: bufferedReader,
|
||||
done: done,
|
||||
ignoreRevsFile: ignoreRevsFile,
|
||||
objectFormat: objectFormat,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -24,20 +24,22 @@ func TestReadingBlameOutput(t *testing.T) {
|
||||
|
||||
parts := []*BlamePart{
|
||||
{
|
||||
"72866af952e98d02a73003501836074b286a78f6",
|
||||
[]string{
|
||||
Sha: "72866af952e98d02a73003501836074b286a78f6",
|
||||
Lines: []string{
|
||||
"# test_repo",
|
||||
"Test repository for testing migration from github to gitea",
|
||||
},
|
||||
},
|
||||
{
|
||||
"f32b0a9dfd09a60f616f29158f772cedd89942d2",
|
||||
[]string{"", "Do not make any changes to this repo it is used for unit testing"},
|
||||
Sha: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
|
||||
Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"},
|
||||
PreviousSha: "72866af952e98d02a73003501836074b286a78f6",
|
||||
PreviousPath: "README.md",
|
||||
},
|
||||
}
|
||||
|
||||
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.NotNil(t, blameReader)
|
||||
defer blameReader.Close()
|
||||
@@ -64,16 +66,18 @@ func TestReadingBlameOutput(t *testing.T) {
|
||||
|
||||
full := []*BlamePart{
|
||||
{
|
||||
"af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
[]string{"line", "line"},
|
||||
Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
Lines: []string{"line", "line"},
|
||||
},
|
||||
{
|
||||
"45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
|
||||
[]string{"changed line"},
|
||||
Sha: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376",
|
||||
Lines: []string{"changed line"},
|
||||
PreviousSha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
PreviousPath: "blame.txt",
|
||||
},
|
||||
{
|
||||
"af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
[]string{"line", "line", ""},
|
||||
Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
Lines: []string{"line", "line", ""},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -89,8 +93,8 @@ func TestReadingBlameOutput(t *testing.T) {
|
||||
Bypass: false,
|
||||
Parts: []*BlamePart{
|
||||
{
|
||||
"af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
[]string{"line", "line", "changed line", "line", "line", ""},
|
||||
Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93",
|
||||
Lines: []string{"line", "line", "changed line", "line", "line", ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -118,7 +122,7 @@ func TestReadingBlameOutput(t *testing.T) {
|
||||
commit, err := repo.GetCommit(c.CommitID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass)
|
||||
blameReader, err := CreateBlameReader(ctx, repo.objectFormat, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, blameReader)
|
||||
defer blameReader.Close()
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
// Blob represents a Git object.
|
||||
type Blob struct {
|
||||
ID SHA1
|
||||
ID ObjectID
|
||||
|
||||
gogitEncodedObj plumbing.EncodedObject
|
||||
name string
|
||||
|
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// Blob represents a Git object.
|
||||
type Blob struct {
|
||||
ID SHA1
|
||||
ID ObjectID
|
||||
|
||||
gotSize bool
|
||||
size int64
|
||||
|
@@ -21,13 +21,13 @@ import (
|
||||
// Commit represents a git commit.
|
||||
type Commit struct {
|
||||
Tree
|
||||
ID SHA1 // The ID of this commit object
|
||||
ID ObjectID // The ID of this commit object
|
||||
Author *Signature
|
||||
Committer *Signature
|
||||
CommitMessage string
|
||||
Signature *CommitGPGSignature
|
||||
|
||||
Parents []SHA1 // SHA1 strings
|
||||
Parents []ObjectID // ID strings
|
||||
submoduleCache *ObjectCache
|
||||
}
|
||||
|
||||
@@ -43,15 +43,16 @@ func (c *Commit) Message() string {
|
||||
}
|
||||
|
||||
// Summary returns first line of commit message.
|
||||
// The string is forced to be valid UTF8
|
||||
func (c *Commit) Summary() string {
|
||||
return strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0]
|
||||
return strings.ToValidUTF8(strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0], "?")
|
||||
}
|
||||
|
||||
// ParentID returns oid of n-th parent (0-based index).
|
||||
// It returns nil if no such parent exists.
|
||||
func (c *Commit) ParentID(n int) (SHA1, error) {
|
||||
func (c *Commit) ParentID(n int) (ObjectID, error) {
|
||||
if n >= len(c.Parents) {
|
||||
return SHA1{}, ErrNotExist{"", ""}
|
||||
return nil, ErrNotExist{"", ""}
|
||||
}
|
||||
return c.Parents[n], nil
|
||||
}
|
||||
@@ -208,9 +209,9 @@ func (c *Commit) CommitsBefore() ([]*Commit, error) {
|
||||
}
|
||||
|
||||
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
|
||||
func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
|
||||
func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
|
||||
this := c.ID.String()
|
||||
that := commitHash.String()
|
||||
that := objectID.String()
|
||||
|
||||
if this == that {
|
||||
return false, nil
|
||||
@@ -231,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
|
||||
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
|
||||
}
|
||||
|
||||
oldCommit, err := c.repo.GetCommit(oldCommitID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@@ -59,11 +59,11 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
|
||||
|
||||
func convertCommit(c *object.Commit) *Commit {
|
||||
return &Commit{
|
||||
ID: c.Hash,
|
||||
ID: ParseGogitHash(c.Hash),
|
||||
CommitMessage: c.Message,
|
||||
Committer: &c.Committer,
|
||||
Author: &c.Author,
|
||||
Signature: convertPGPSignature(c),
|
||||
Parents: c.ParentHashes,
|
||||
Parents: ParseGogitHashArray(c.ParentHashes),
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
defer commitGraphFile.Close()
|
||||
}
|
||||
|
||||
c, err := commitNodeIndex.Get(commit.ID)
|
||||
c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID.RawValue()))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@@ -153,7 +153,7 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
|
||||
if typ != "commit" {
|
||||
return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
|
||||
}
|
||||
c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
|
||||
c, err = CommitFromReader(commit.repo, commit.ID.Type().MustIDFromString(commitID), io.LimitReader(batchReader, size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -14,9 +14,9 @@ import (
|
||||
// We need this to interpret commits from cat-file or cat-file --batch
|
||||
//
|
||||
// If used as part of a cat-file --batch stream you need to limit the reader to the correct size
|
||||
func CommitFromReader(gitRepo *Repository, sha SHA1, reader io.Reader) (*Commit, error) {
|
||||
func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader) (*Commit, error) {
|
||||
commit := &Commit{
|
||||
ID: sha,
|
||||
ID: objectID,
|
||||
Author: &Signature{},
|
||||
Committer: &Signature{},
|
||||
}
|
||||
@@ -71,10 +71,10 @@ readLoop:
|
||||
|
||||
switch string(split[0]) {
|
||||
case "tree":
|
||||
commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
|
||||
commit.Tree = *NewTree(gitRepo, objectID.Type().MustIDFromString(string(data)))
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "parent":
|
||||
commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
|
||||
commit.Parents = append(commit.Parents, objectID.Type().MustIDFromString(string(data)))
|
||||
_, _ = payloadSB.Write(line)
|
||||
case "author":
|
||||
commit.Author = &Signature{}
|
||||
|
@@ -81,7 +81,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
|
||||
|
||||
empty commit`
|
||||
|
||||
sha := SHA1{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, gitRepo)
|
||||
@@ -135,8 +135,8 @@ func TestHasPreviousCommit(t *testing.T) {
|
||||
commit, err := repo.GetCommit("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0")
|
||||
assert.NoError(t, err)
|
||||
|
||||
parentSHA := MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2")
|
||||
notParentSHA := MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3")
|
||||
parentSHA := repo.objectFormat.MustIDFromString("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2")
|
||||
notParentSHA := repo.objectFormat.MustIDFromString("2839944139e0de9737a044f78b0e4b40d989a9e3")
|
||||
|
||||
haz, err := commit.HasPreviousCommit(parentSHA)
|
||||
assert.NoError(t, err)
|
||||
|
@@ -33,8 +33,8 @@ var (
|
||||
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
|
||||
DefaultContext context.Context
|
||||
|
||||
// SupportProcReceive version >= 2.29.0
|
||||
SupportProcReceive bool
|
||||
SupportProcReceive bool // >= 2.29
|
||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||
|
||||
gitVersion *version.Version
|
||||
)
|
||||
@@ -189,7 +189,7 @@ func InitFull(ctx context.Context) (err error) {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||
}
|
||||
SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
|
||||
|
||||
SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil
|
||||
if setting.LFS.StartServer {
|
||||
if CheckGitVersionAtLeast("2.1.2") != nil {
|
||||
return errors.New("LFS server support requires Git >= 2.1.2")
|
||||
|
@@ -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
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lastCommit, err := c.Get(sha1.String(), entryPath)
|
||||
lastCommit, err := c.Get(sha.String(), entryPath)
|
||||
if err != nil || lastCommit != nil {
|
||||
return lastCommit, err
|
||||
}
|
||||
|
||||
lastCommit, err = c.repo.getCommitByPathWithID(sha1, entryPath)
|
||||
lastCommit, err = c.repo.getCommitByPathWithID(sha, entryPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ package git
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
|
||||
)
|
||||
|
||||
@@ -18,7 +19,7 @@ func (c *Commit) CacheCommit(ctx context.Context) error {
|
||||
}
|
||||
commitNodeIndex, _ := c.repo.CommitNodeIndex()
|
||||
|
||||
index, err := commitNodeIndex.Get(c.ID)
|
||||
index, err := commitNodeIndex.Get(plumbing.Hash(c.ID.RawValue()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -143,17 +143,20 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int
|
||||
}
|
||||
|
||||
// Our "line" must look like: <commitid> SP (<parent> SP) * NUL
|
||||
ret.CommitID = string(g.next[0:40])
|
||||
parents := string(g.next[41:])
|
||||
commitIds := string(g.next)
|
||||
if g.buffull {
|
||||
more, err := g.rd.ReadString('\x00')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parents += more
|
||||
commitIds += more
|
||||
}
|
||||
commitIds = commitIds[:len(commitIds)-1]
|
||||
splitIds := strings.Split(commitIds, " ")
|
||||
ret.CommitID = splitIds[0]
|
||||
if len(splitIds) > 1 {
|
||||
ret.ParentIDs = splitIds[1:]
|
||||
}
|
||||
parents = parents[:len(parents)-1]
|
||||
ret.ParentIDs = strings.Split(parents, " ")
|
||||
|
||||
// now read the next "line"
|
||||
g.buffull = false
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
@@ -72,7 +73,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
|
||||
defer commitGraphFile.Close()
|
||||
}
|
||||
|
||||
commitNode, err := commitNodeIndex.Get(notes.ID)
|
||||
commitNode, err := commitNodeIndex.Get(plumbing.Hash(notes.ID.RawValue()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
101
modules/git/object_format.go
Normal file
101
modules/git/object_format.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
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()}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
141
modules/git/object_id.go
Normal file
141
modules/git/object_id.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
// NewHash is for 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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// HasherInterface 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()
|
||||
}
|
||||
|
||||
// HashSum 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"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/hash"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
// ParseTreeEntries parses the output of a `git ls-tree -l` command.
|
||||
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
|
||||
func ParseTreeEntries(h ObjectFormat, data []byte) ([]*TreeEntry, error) {
|
||||
return parseTreeEntries(data, nil)
|
||||
}
|
||||
|
||||
@@ -50,15 +52,16 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
|
||||
}
|
||||
|
||||
if pos+40 > len(data) {
|
||||
// in hex format, not byte format ....
|
||||
if pos+hash.Size*2 > len(data) {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
}
|
||||
id, err := NewIDFromString(string(data[pos : pos+40]))
|
||||
var err error
|
||||
entry.ID, err = IDFromString(string(data[pos : pos+hash.Size*2]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %w", err)
|
||||
return nil, fmt.Errorf("invalid ls-tree output: %w", err)
|
||||
}
|
||||
entry.ID = id
|
||||
entry.gogitTreeEntry.Hash = id
|
||||
entry.gogitTreeEntry.Hash = plumbing.Hash(entry.ID.RawValue())
|
||||
pos += 41 // skip over sha and trailing space
|
||||
|
||||
end := pos + bytes.IndexByte(data[pos:], '\t')
|
||||
@@ -77,6 +80,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
|
||||
// In case entry name is surrounded by double quotes(it happens only in git-shell).
|
||||
if data[pos] == '"' {
|
||||
var err error
|
||||
entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %w", err)
|
||||
|
@@ -6,8 +6,10 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -26,9 +28,9 @@ func TestParseTreeEntries(t *testing.T) {
|
||||
Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n",
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
ID: ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
Hash: plumbing.Hash(ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()),
|
||||
Name: "example/file2.txt",
|
||||
Mode: filemode.Regular,
|
||||
},
|
||||
@@ -42,9 +44,9 @@ func TestParseTreeEntries(t *testing.T) {
|
||||
"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n",
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
ID: ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Hash: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
|
||||
Hash: plumbing.Hash(ObjectFormatFromID(Sha1).MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()),
|
||||
Name: "example/\n.txt",
|
||||
Mode: filemode.Symlink,
|
||||
},
|
||||
@@ -52,10 +54,10 @@ func TestParseTreeEntries(t *testing.T) {
|
||||
sized: true,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
|
||||
ID: ObjectFormatFromID(Sha1).MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
|
||||
sized: true,
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Hash: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
|
||||
Hash: plumbing.Hash(ObjectFormatFromID(Sha1).MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8").RawValue()),
|
||||
Name: "example",
|
||||
Mode: filemode.Dir,
|
||||
},
|
||||
@@ -65,8 +67,12 @@ func TestParseTreeEntries(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
entries, err := ParseTreeEntries([]byte(testCase.Input))
|
||||
entries, err := ParseTreeEntries(ObjectFormatFromID(Sha1), []byte(testCase.Input))
|
||||
assert.NoError(t, err)
|
||||
if len(entries) > 1 {
|
||||
fmt.Println(testCase.Expected[0].ID)
|
||||
fmt.Println(entries[0].ID)
|
||||
}
|
||||
assert.EqualValues(t, testCase.Expected, entries)
|
||||
}
|
||||
}
|
||||
|
@@ -17,13 +17,13 @@ import (
|
||||
)
|
||||
|
||||
// ParseTreeEntries parses the output of a `git ls-tree -l` command.
|
||||
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
|
||||
return parseTreeEntries(data, nil)
|
||||
func ParseTreeEntries(objectFormat ObjectFormat, data []byte) ([]*TreeEntry, error) {
|
||||
return parseTreeEntries(objectFormat, data, nil)
|
||||
}
|
||||
|
||||
var sepSpace = []byte{' '}
|
||||
|
||||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
func parseTreeEntries(objectFormat ObjectFormat, data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
var err error
|
||||
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
|
||||
for pos := 0; pos < len(data); {
|
||||
@@ -72,7 +72,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
return nil, fmt.Errorf("unknown type: %v", string(entryMode))
|
||||
}
|
||||
|
||||
entry.ID, err = NewIDFromString(string(entryObjectID))
|
||||
entry.ID, err = objectFormat.NewIDFromString(string(entryObjectID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
|
||||
}
|
||||
@@ -92,15 +92,15 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func catBatchParseTreeEntries(ptree *Tree, rd *bufio.Reader, sz int64) ([]*TreeEntry, error) {
|
||||
func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio.Reader, sz int64) ([]*TreeEntry, error) {
|
||||
fnameBuf := make([]byte, 4096)
|
||||
modeBuf := make([]byte, 40)
|
||||
shaBuf := make([]byte, 40)
|
||||
shaBuf := make([]byte, objectFormat.FullLength())
|
||||
entries := make([]*TreeEntry, 0, 10)
|
||||
|
||||
loop:
|
||||
for sz > 0 {
|
||||
mode, fname, sha, count, err := ParseTreeLine(rd, modeBuf, fnameBuf, shaBuf)
|
||||
mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break loop
|
||||
@@ -127,7 +127,7 @@ loop:
|
||||
return nil, fmt.Errorf("unknown mode: %v", string(mode))
|
||||
}
|
||||
|
||||
entry.ID = MustID(sha)
|
||||
entry.ID = objectFormat.MustID(sha)
|
||||
entry.name = string(fname)
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestParseTreeEntriesLong(t *testing.T) {
|
||||
objectFormat := ObjectFormatFromID(Sha1)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Expected []*TreeEntry
|
||||
@@ -24,28 +26,28 @@ func TestParseTreeEntriesLong(t *testing.T) {
|
||||
`,
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
|
||||
ID: objectFormat.MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
|
||||
name: "README.md",
|
||||
entryMode: EntryModeBlob,
|
||||
size: 8218,
|
||||
sized: true,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("037f27dc9d353ae4fd50f0474b2194c593914e35"),
|
||||
ID: objectFormat.MustIDFromString("037f27dc9d353ae4fd50f0474b2194c593914e35"),
|
||||
name: "README_ZH.md",
|
||||
entryMode: EntryModeBlob,
|
||||
size: 4681,
|
||||
sized: true,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("9846a94f7e8350a916632929d0fda38c90dd2ca8"),
|
||||
ID: objectFormat.MustIDFromString("9846a94f7e8350a916632929d0fda38c90dd2ca8"),
|
||||
name: "SECURITY.md",
|
||||
entryMode: EntryModeBlob,
|
||||
size: 429,
|
||||
sized: true,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
|
||||
ID: objectFormat.MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
|
||||
name: "assets",
|
||||
entryMode: EntryModeTree,
|
||||
sized: true,
|
||||
@@ -54,7 +56,7 @@ func TestParseTreeEntriesLong(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
entries, err := ParseTreeEntries([]byte(testCase.Input))
|
||||
entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, entries, len(testCase.Expected))
|
||||
for i, entry := range entries {
|
||||
@@ -64,6 +66,8 @@ func TestParseTreeEntriesLong(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseTreeEntriesShort(t *testing.T) {
|
||||
objectFormat := ObjectFormatFromID(Sha1)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Expected []*TreeEntry
|
||||
@@ -74,12 +78,12 @@ func TestParseTreeEntriesShort(t *testing.T) {
|
||||
`,
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
|
||||
ID: objectFormat.MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
|
||||
name: "README.md",
|
||||
entryMode: EntryModeBlob,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
|
||||
ID: objectFormat.MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
|
||||
name: "assets",
|
||||
entryMode: EntryModeTree,
|
||||
},
|
||||
@@ -87,7 +91,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
entries, err := ParseTreeEntries([]byte(testCase.Input))
|
||||
entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, entries, len(testCase.Expected))
|
||||
for i, entry := range entries {
|
||||
@@ -98,7 +102,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
|
||||
|
||||
func TestParseTreeEntriesInvalid(t *testing.T) {
|
||||
// there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315
|
||||
entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
|
||||
entries, err := ParseTreeEntries(ObjectFormatFromID(Sha1), []byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, entries, 0)
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
@@ -26,7 +27,7 @@ type LFSResult struct {
|
||||
SHA string
|
||||
Summary string
|
||||
When time.Time
|
||||
ParentHashes []git.SHA1
|
||||
ParentHashes []git.ObjectID
|
||||
BranchName string
|
||||
FullCommitName string
|
||||
}
|
||||
@@ -38,7 +39,7 @@ func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||
|
||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||
func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||
resultsMap := map[string]*LFSResult{}
|
||||
results := make([]*LFSResult, 0)
|
||||
|
||||
@@ -65,13 +66,18 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if entry.Hash == hash {
|
||||
if entry.Hash == plumbing.Hash(objectID.RawValue()) {
|
||||
parents := make([]git.ObjectID, len(gitCommit.ParentHashes))
|
||||
for i, parentCommitID := range gitCommit.ParentHashes {
|
||||
parents[i] = git.ParseGogitHash(parentCommitID)
|
||||
}
|
||||
|
||||
result := LFSResult{
|
||||
Name: name,
|
||||
SHA: gitCommit.Hash.String(),
|
||||
Summary: strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
|
||||
When: gitCommit.Author.When,
|
||||
ParentHashes: gitCommit.ParentHashes,
|
||||
ParentHashes: parents,
|
||||
}
|
||||
resultsMap[gitCommit.Hash.String()+":"+name] = &result
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ type LFSResult struct {
|
||||
SHA string
|
||||
Summary string
|
||||
When time.Time
|
||||
ParentHashes []git.SHA1
|
||||
ParentIDs []git.ObjectID
|
||||
BranchName string
|
||||
FullCommitName string
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||
|
||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||
func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||
resultsMap := map[string]*LFSResult{}
|
||||
results := make([]*LFSResult, 0)
|
||||
|
||||
@@ -75,7 +75,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
|
||||
fnameBuf := make([]byte, 4096)
|
||||
modeBuf := make([]byte, 40)
|
||||
workingShaBuf := make([]byte, 20)
|
||||
workingShaBuf := make([]byte, objectID.Type().FullLength()/2)
|
||||
|
||||
for scan.Scan() {
|
||||
// Get the next commit ID
|
||||
@@ -115,7 +115,11 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
continue
|
||||
case "commit":
|
||||
// Read in the commit to get its tree and in case this is one of the last used commits
|
||||
curCommit, err = git.CommitFromReader(repo, git.MustIDFromString(string(commitID)), io.LimitReader(batchReader, size))
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -123,32 +127,31 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n"))
|
||||
if err != nil {
|
||||
if _, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
curPath = ""
|
||||
case "tree":
|
||||
var n int64
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
n += int64(count)
|
||||
if bytes.Equal(sha20byte, hash[:]) {
|
||||
if bytes.Equal(binObjectID, objectID.RawValue()) {
|
||||
result := LFSResult{
|
||||
Name: curPath + string(fname),
|
||||
SHA: curCommit.ID.String(),
|
||||
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
||||
When: curCommit.Author.When,
|
||||
ParentHashes: curCommit.Parents,
|
||||
Name: curPath + string(fname),
|
||||
SHA: curCommit.ID.String(),
|
||||
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
||||
When: curCommit.Author.When,
|
||||
ParentIDs: curCommit.Parents,
|
||||
}
|
||||
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
|
||||
} else if string(mode) == git.EntryModeTree.String() {
|
||||
sha40Byte := make([]byte, 40)
|
||||
git.To40ByteSHA(sha20byte, sha40Byte)
|
||||
trees = append(trees, sha40Byte)
|
||||
hexObjectID := make([]byte, objectID.Type().FullLength())
|
||||
git.BinToHex(objectID.Type(), binObjectID, hexObjectID)
|
||||
trees = append(trees, hexObjectID)
|
||||
paths = append(paths, curPath+string(fname)+"/")
|
||||
}
|
||||
}
|
||||
@@ -180,8 +183,8 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
|
||||
|
||||
for _, result := range resultsMap {
|
||||
hasParent := false
|
||||
for _, parentHash := range result.ParentHashes {
|
||||
if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
|
||||
for _, parentID := range result.ParentIDs {
|
||||
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ func SanitizeRefPattern(name string) string {
|
||||
type Reference struct {
|
||||
Name string
|
||||
repo *Repository
|
||||
Object SHA1 // The id of this commit object
|
||||
Object ObjectID // The id of this commit object
|
||||
Type string
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ func RefURL(repoURL, ref string) string {
|
||||
return repoURL + "/src/branch/" + refName
|
||||
case refFullName.IsTag():
|
||||
return repoURL + "/src/tag/" + refName
|
||||
case !IsValidSHAPattern(ref):
|
||||
case !ObjectFormatFromID(Sha1).IsValid(ref):
|
||||
// assume they mean a branch
|
||||
return repoURL + "/src/branch/" + refName
|
||||
default:
|
||||
|
@@ -7,6 +7,7 @@ package git
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
@@ -62,14 +63,43 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GetObjectFormatOfRepo returns the hash type of 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.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := NewCommand(ctx, "init")
|
||||
if SupportHashSha256 {
|
||||
cmd.AddOptionValues("--object-format", objectFormat.String())
|
||||
}
|
||||
if bare {
|
||||
cmd.AddArguments("--bare")
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
)
|
||||
@@ -32,6 +33,7 @@ type Repository struct {
|
||||
|
||||
Ctx context.Context
|
||||
LastCommitCache *LastCommitCache
|
||||
objectFormat ObjectFormat
|
||||
}
|
||||
|
||||
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
|
||||
@@ -68,6 +70,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
||||
gogitStorage: storage,
|
||||
tagCache: newObjectCache(),
|
||||
Ctx: ctx,
|
||||
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -33,6 +33,8 @@ type Repository struct {
|
||||
|
||||
Ctx context.Context
|
||||
LastCommitCache *LastCommitCache
|
||||
|
||||
objectFormat ObjectFormat
|
||||
}
|
||||
|
||||
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
|
||||
@@ -63,6 +65,11 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
||||
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
|
||||
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
|
||||
|
||||
repo.objectFormat, err = repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ package git
|
||||
|
||||
// GetBlob finds the blob object in the repository.
|
||||
func (repo *Repository) GetBlob(idStr string) (*Blob, error) {
|
||||
id, err := NewIDFromString(idStr)
|
||||
id, err := repo.objectFormat.NewIDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
|
||||
encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, id)
|
||||
func (repo *Repository) getBlob(id ObjectID) (*Blob, error) {
|
||||
encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id.RawValue()))
|
||||
if err != nil {
|
||||
return nil, ErrNotExist{id.String(), ""}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
package git
|
||||
|
||||
func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
|
||||
func (repo *Repository) getBlob(id ObjectID) (*Blob, error) {
|
||||
if id.IsZero() {
|
||||
return nil, ErrNotExist{id.String(), ""}
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ func TestRepository_GetBlob_NoId(t *testing.T) {
|
||||
defer r.Close()
|
||||
|
||||
testCase := ""
|
||||
testError := fmt.Errorf("Length must be 40: %s", testCase)
|
||||
testError := fmt.Errorf("length must be 40: %s", testCase)
|
||||
|
||||
blob, err := r.GetBlob(testCase)
|
||||
assert.Nil(t, blob)
|
||||
|
@@ -8,6 +8,7 @@ package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
@@ -52,32 +53,46 @@ func (repo *Repository) IsBranchExist(name string) bool {
|
||||
|
||||
// GetBranches returns branches from the repository, skipping "skip" initial branches and
|
||||
// returning at most "limit" branches, or all branches if "limit" is 0.
|
||||
// Branches are returned with sort of `-commiterdate` as the nogogit
|
||||
// implementation. This requires full fetch, sort and then the
|
||||
// skip/limit applies later as gogit returns in undefined order.
|
||||
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
|
||||
var branchNames []string
|
||||
type BranchData struct {
|
||||
name string
|
||||
committerDate int64
|
||||
}
|
||||
var branchData []BranchData
|
||||
|
||||
branches, err := repo.gogitRepo.Branches()
|
||||
branchIter, err := repo.gogitRepo.Branches()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
i := 0
|
||||
count := 0
|
||||
_ = branches.ForEach(func(branch *plumbing.Reference) error {
|
||||
count++
|
||||
if i < skip {
|
||||
i++
|
||||
return nil
|
||||
} else if limit != 0 && count > skip+limit {
|
||||
_ = branchIter.ForEach(func(branch *plumbing.Reference) error {
|
||||
obj, err := repo.gogitRepo.CommitObject(branch.Hash())
|
||||
if err != nil {
|
||||
// skip branch if can't find commit
|
||||
return nil
|
||||
}
|
||||
|
||||
branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix))
|
||||
branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), BranchPrefix), obj.Committer.When.Unix()})
|
||||
return nil
|
||||
})
|
||||
|
||||
// TODO: Sort?
|
||||
sort.Slice(branchData, func(i, j int) bool {
|
||||
return !(branchData[i].committerDate < branchData[j].committerDate)
|
||||
})
|
||||
|
||||
return branchNames, count, nil
|
||||
var branchNames []string
|
||||
maxPos := len(branchData)
|
||||
if limit > 0 {
|
||||
maxPos = min(skip+limit, maxPos)
|
||||
}
|
||||
for i := skip; i < maxPos; i++ {
|
||||
branchNames = append(branchNames, branchData[i].name)
|
||||
}
|
||||
|
||||
return branchNames, len(branchData), nil
|
||||
}
|
||||
|
||||
// WalkReferences walks all the references from the repository
|
||||
|
@@ -6,8 +6,6 @@ package git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -28,7 +26,7 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) {
|
||||
|
||||
// GetCommit returns commit object of by ID string.
|
||||
func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
|
||||
id, err := repo.ConvertToSHA1(commitID)
|
||||
id, err := repo.ConvertToGitID(commitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -54,7 +52,7 @@ func (repo *Repository) GetTagCommit(name string) (*Commit, error) {
|
||||
return repo.GetCommit(commitID)
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, error) {
|
||||
func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Commit, error) {
|
||||
// File name starts with ':' must be escaped.
|
||||
if relpath[0] == ':' {
|
||||
relpath = `\` + relpath
|
||||
@@ -65,7 +63,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
|
||||
return nil, runErr
|
||||
}
|
||||
|
||||
id, err := NewIDFromString(stdout)
|
||||
id, err := repo.objectFormat.NewIDFromString(stdout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -90,7 +88,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
|
||||
return commits[0], nil
|
||||
}
|
||||
|
||||
func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string) ([]*Commit, error) {
|
||||
func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
|
||||
cmd := NewCommand(repo.Ctx, "log").
|
||||
AddOptionFormat("--skip=%d", (page-1)*pageSize).
|
||||
AddOptionFormat("--max-count=%d", pageSize).
|
||||
@@ -109,7 +107,7 @@ func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string)
|
||||
return repo.parsePrettyFormatLogToList(stdout)
|
||||
}
|
||||
|
||||
func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) {
|
||||
func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([]*Commit, error) {
|
||||
// add common arguments to git command
|
||||
addCommonSearchArgs := func(c *Command) {
|
||||
// ignore case
|
||||
@@ -164,7 +162,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
|
||||
// then let's iterate over them
|
||||
for _, v := range opts.Keywords {
|
||||
// ignore anything not matching a valid sha pattern
|
||||
if IsValidSHAPattern(v) {
|
||||
if id.Type().IsValid(v) {
|
||||
// create new git log command with 1 commit limit
|
||||
hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat)
|
||||
// add previous arguments except for --grep and --all
|
||||
@@ -245,25 +243,22 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
||||
}
|
||||
}()
|
||||
|
||||
len := repo.objectFormat.FullLength()
|
||||
commits := []*Commit{}
|
||||
shaline := [41]byte{}
|
||||
var sha1 SHA1
|
||||
shaline := make([]byte, len+1)
|
||||
for {
|
||||
n, err := io.ReadFull(stdoutReader, shaline[:])
|
||||
if err != nil || n < 40 {
|
||||
n, err := io.ReadFull(stdoutReader, shaline)
|
||||
if err != nil || n < len {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return commits, err
|
||||
}
|
||||
n, err = hex.Decode(sha1[:], shaline[0:40])
|
||||
if n != 20 {
|
||||
err = fmt.Errorf("invalid sha %q", string(shaline[:40]))
|
||||
}
|
||||
objectID, err := repo.objectFormat.NewIDFromString(string(shaline[0:len]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit, err := repo.getCommit(sha1)
|
||||
commit, err := repo.getCommit(objectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -392,7 +387,7 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
|
||||
}
|
||||
|
||||
// commitsBefore the limit is depth, not total number of returned commits.
|
||||
func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
|
||||
func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) {
|
||||
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
|
||||
if limit > 0 {
|
||||
cmd.AddOptionFormat("-%d", limit)
|
||||
@@ -426,11 +421,11 @@ func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitsBefore(id SHA1) ([]*Commit, error) {
|
||||
func (repo *Repository) getCommitsBefore(id ObjectID) ([]*Commit, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/hash"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
@@ -38,40 +39,46 @@ func (repo *Repository) RemoveReference(name string) error {
|
||||
return repo.gogitRepo.Storer.RemoveReference(plumbing.ReferenceName(name))
|
||||
}
|
||||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) == SHAFullLength {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
// ConvertToHash returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
|
||||
objectFormat := repo.objectFormat
|
||||
if len(commitID) == hash.HexSize && objectFormat.IsValid(commitID) {
|
||||
ID, err := objectFormat.NewIDFromString(commitID)
|
||||
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 = strings.TrimSpace(actualCommitID)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unknown revision or path") ||
|
||||
strings.Contains(err.Error(), "fatal: Needed a single revision") {
|
||||
return SHA1{}, ErrNotExist{commitID, ""}
|
||||
return 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.
|
||||
func (repo *Repository) IsCommitExist(name string) bool {
|
||||
hash := plumbing.NewHash(name)
|
||||
_, err := repo.gogitRepo.CommitObject(hash)
|
||||
hash, err := repo.ConvertToGitID(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = repo.gogitRepo.CommitObject(plumbing.Hash(hash.RawValue()))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||
func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
|
||||
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 {
|
||||
tagObject, err = repo.gogitRepo.TagObject(id)
|
||||
tagObject, err = repo.gogitRepo.TagObject(commitID)
|
||||
if err == plumbing.ErrObjectNotFound {
|
||||
return nil, ErrNotExist{
|
||||
ID: id.String(),
|
||||
@@ -94,7 +101,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit.Tree.ID = tree.Hash
|
||||
commit.Tree.ID = ParseGogitHash(tree.Hash)
|
||||
commit.Tree.gogitTree = tree
|
||||
|
||||
return commit, nil
|
||||
|
@@ -65,7 +65,7 @@ func (repo *Repository) IsCommitExist(name string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||
func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
|
||||
wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -74,7 +74,7 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||
return repo.getCommitFromBatchReader(rd, id)
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Commit, error) {
|
||||
func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) (*Commit, error) {
|
||||
_, typ, size, err := ReadBatchLine(rd)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || IsErrNotExist(err) {
|
||||
@@ -97,7 +97,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag, err := parseTagData(data)
|
||||
tag, err := parseTagData(id.Type(), data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,12 +131,13 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
// ConvertToGitID returns a GitHash object from a potential ID string
|
||||
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
|
||||
IDType := repo.objectFormat
|
||||
if len(commitID) == IDType.FullLength() && IDType.IsValid(commitID) {
|
||||
ID, err := repo.objectFormat.NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return sha1, nil
|
||||
return ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,15 +145,15 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(commitID + "\n"))
|
||||
if err != nil {
|
||||
return SHA1{}, err
|
||||
return nil, err
|
||||
}
|
||||
sha, _, _, err := ReadBatchLine(rd)
|
||||
if err != nil {
|
||||
if IsErrNotExist(err) {
|
||||
return SHA1{}, ErrNotExist{commitID, ""}
|
||||
return nil, ErrNotExist{commitID, ""}
|
||||
}
|
||||
return SHA1{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MustIDFromString(string(sha)), nil
|
||||
return repo.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
|
||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
|
||||
if base == EmptySHA {
|
||||
if base == repo.objectFormat.Empty().String() {
|
||||
cmd.AddDynamicArguments(head)
|
||||
} else {
|
||||
cmd.AddDynamicArguments(base, head)
|
||||
|
@@ -131,12 +131,12 @@ func TestGetCommitFilesChanged(t *testing.T) {
|
||||
files []string
|
||||
}{
|
||||
{
|
||||
EmptySHA,
|
||||
repo.objectFormat.Empty().String(),
|
||||
"95bb4d39648ee7e325106df01a621c530863a653",
|
||||
[]string{"file1.txt"},
|
||||
},
|
||||
{
|
||||
EmptySHA,
|
||||
repo.objectFormat.Empty().String(),
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file2.txt"},
|
||||
},
|
||||
@@ -146,7 +146,7 @@ func TestGetCommitFilesChanged(t *testing.T) {
|
||||
[]string{"file2.txt"},
|
||||
},
|
||||
{
|
||||
EmptyTreeSHA,
|
||||
repo.objectFormat.EmptyTree().String(),
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file1.txt", "file2.txt"},
|
||||
},
|
||||
|
@@ -17,7 +17,7 @@ func (gpgSettings *GPGSettings) LoadPublicKeyContent() error {
|
||||
"gpg -a --export",
|
||||
"gpg", "-a", "--export", gpgSettings.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get default signing key: %s, %s, %w", gpgSettings.KeyID, stderr, err)
|
||||
return fmt.Errorf("unable to get default signing key: %s, %s, %w", gpgSettings.KeyID, stderr, err)
|
||||
}
|
||||
gpgSettings.PublicKeyContent = content
|
||||
return nil
|
||||
|
@@ -16,7 +16,12 @@ import (
|
||||
|
||||
// ReadTreeToIndex reads a treeish to the index
|
||||
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})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -25,14 +30,14 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string)
|
||||
treeish = res[:len(res)-1]
|
||||
}
|
||||
}
|
||||
id, err := NewIDFromString(treeish)
|
||||
id, err := objectFormat.NewIDFromString(treeish)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repo.readTreeToIndex(id, indexFilename...)
|
||||
}
|
||||
|
||||
func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error {
|
||||
func (repo *Repository) readTreeToIndex(id ObjectID, indexFilename ...string) error {
|
||||
var env []string
|
||||
if len(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)
|
||||
for _, file := range filenames {
|
||||
if file != "" {
|
||||
buffer.WriteString("0 0000000000000000000000000000000000000000\t")
|
||||
buffer.WriteString("0 ")
|
||||
buffer.WriteString(repo.objectFormat.Empty().String())
|
||||
buffer.WriteByte('\t')
|
||||
buffer.WriteString(file)
|
||||
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
|
||||
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)
|
||||
_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
|
||||
return err
|
||||
@@ -121,7 +128,7 @@ func (repo *Repository) WriteTree() (*Tree, error) {
|
||||
if runErr != nil {
|
||||
return nil, runErr
|
||||
}
|
||||
id, err := NewIDFromString(strings.TrimSpace(stdout))
|
||||
id, err := repo.objectFormat.NewIDFromString(strings.TrimSpace(stdout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
return nil, ErrNotExist{commitID, ""}
|
||||
}
|
||||
|
||||
sha, err := NewIDFromString(string(shaBytes))
|
||||
sha, err := repo.objectFormat.NewIDFromString(string(shaBytes))
|
||||
if err != nil {
|
||||
log.Debug("Unable to get commit for: %s. Err: %v", commitID, err)
|
||||
return nil, ErrNotExist{commitID, ""}
|
||||
|
@@ -31,17 +31,47 @@ func (o ObjectType) Bytes() []byte {
|
||||
return []byte(o)
|
||||
}
|
||||
|
||||
// HashObject takes a reader and returns SHA1 hash for that reader
|
||||
func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) {
|
||||
idStr, err := repo.hashObject(reader)
|
||||
if err != nil {
|
||||
return SHA1{}, err
|
||||
}
|
||||
return NewIDFromString(idStr)
|
||||
type EmptyReader struct{}
|
||||
|
||||
func (EmptyReader) Read(p []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (repo *Repository) hashObject(reader io.Reader) (string, error) {
|
||||
cmd := NewCommand(repo.Ctx, "hash-object", "-w", "--stdin")
|
||||
func (repo *Repository) GetObjectFormat() (ObjectFormat, error) {
|
||||
if repo != nil && repo.objectFormat != nil {
|
||||
return repo.objectFormat, nil
|
||||
}
|
||||
|
||||
str, err := repo.hashObject(EmptyReader{}, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash, err := IDFromString(str)
|
||||
if err != nil {
|
||||
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)
|
||||
stderr := new(bytes.Buffer)
|
||||
err := cmd.Run(&RunOpts{
|
||||
|
@@ -30,13 +30,13 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
|
||||
refType := string(ObjectCommit)
|
||||
if ref.Name().IsTag() {
|
||||
// tags can be of type `commit` (lightweight) or `tag` (annotated)
|
||||
if tagType, _ := repo.GetTagType(ref.Hash()); err == nil {
|
||||
if tagType, _ := repo.GetTagType(ParseGogitHash(ref.Hash())); err == nil {
|
||||
refType = tagType
|
||||
}
|
||||
}
|
||||
r := &Reference{
|
||||
Name: ref.Name().String(),
|
||||
Object: ref.Hash(),
|
||||
Object: ParseGogitHash(ref.Hash()),
|
||||
Type: refType,
|
||||
repo: repo,
|
||||
}
|
||||
|
@@ -75,7 +75,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
|
||||
if pattern == "" || strings.HasPrefix(refName, pattern) {
|
||||
r := &Reference{
|
||||
Name: refName,
|
||||
Object: MustIDFromString(sha),
|
||||
Object: repo.objectFormat.MustIDFromString(sha),
|
||||
Type: typ,
|
||||
repo: repo,
|
||||
}
|
||||
|
@@ -84,7 +84,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := NewIDFromString(idStr)
|
||||
id, err := repo.objectFormat.NewIDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
|
||||
|
||||
// GetTagWithID returns a Git tag by given name and ID
|
||||
func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) {
|
||||
id, err := NewIDFromString(idStr)
|
||||
id, err := repo.objectFormat.NewIDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
|
||||
break
|
||||
}
|
||||
|
||||
tag, err := parseTagRef(ref)
|
||||
tag, err := parseTagRef(repo.objectFormat, ref)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err)
|
||||
}
|
||||
@@ -159,13 +159,13 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
|
||||
}
|
||||
|
||||
// parseTagRef parses a tag from a 'git for-each-ref'-produced reference.
|
||||
func parseTagRef(ref map[string]string) (tag *Tag, err error) {
|
||||
func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, err error) {
|
||||
tag = &Tag{
|
||||
Type: ref["objecttype"],
|
||||
Name: ref["refname:short"],
|
||||
}
|
||||
|
||||
tag.ID, err = NewIDFromString(ref["objectname"])
|
||||
tag.ID, err = objectFormat.NewIDFromString(ref["objectname"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
|
||||
tag.Object = tag.ID
|
||||
} else {
|
||||
// annotated tag
|
||||
tag.Object, err = NewIDFromString(ref["object"])
|
||||
tag.Object, err = objectFormat.NewIDFromString(ref["object"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
|
||||
|
||||
// GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
|
||||
func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
|
||||
id, err := NewIDFromString(sha)
|
||||
id, err := repo.objectFormat.NewIDFromString(sha)
|
||||
if err != nil {
|
||||
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)
|
||||
func (repo *Repository) GetTagType(id SHA1) (string, error) {
|
||||
func (repo *Repository) GetTagType(id ObjectID) (string, error) {
|
||||
// Get tag type
|
||||
obj, err := repo.gogitRepo.Object(plumbing.AnyObject, id)
|
||||
obj, err := repo.gogitRepo.Object(plumbing.AnyObject, plumbing.Hash(id.RawValue()))
|
||||
if err != nil {
|
||||
if err == plumbing.ErrReferenceNotFound {
|
||||
return "", &ErrNotExist{ID: id.String()}
|
||||
@@ -68,7 +68,7 @@ func (repo *Repository) GetTagType(id SHA1) (string, error) {
|
||||
return obj.Type().String(), nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
||||
t, ok := repo.tagCache.Get(tagID.String())
|
||||
if ok {
|
||||
log.Debug("Hit cache: %s", tagID)
|
||||
@@ -88,7 +88,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
// every tag should have a commit ID so return all errors
|
||||
return nil, err
|
||||
}
|
||||
commitID, err := NewIDFromString(commitIDStr)
|
||||
commitID, err := IDFromString(commitIDStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
gogitTag, err := repo.gogitRepo.TagObject(tagID)
|
||||
gogitTag, err := repo.gogitRepo.TagObject(plumbing.Hash(tagID.RawValue()))
|
||||
if err != nil {
|
||||
if err == plumbing.ErrReferenceNotFound {
|
||||
return nil, &ErrNotExist{ID: tagID.String()}
|
||||
@@ -124,7 +124,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
tag := &Tag{
|
||||
Name: name,
|
||||
ID: tagID,
|
||||
Object: gogitTag.Target,
|
||||
Object: commitID.Type().MustID(gogitTag.Target[:]),
|
||||
Type: tp,
|
||||
Tagger: &gogitTag.Tagger,
|
||||
Message: gogitTag.Message,
|
||||
|
@@ -30,7 +30,7 @@ func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) {
|
||||
}
|
||||
|
||||
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
|
||||
func (repo *Repository) GetTagType(id SHA1) (string, error) {
|
||||
func (repo *Repository) GetTagType(id ObjectID) (string, error) {
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(id.String() + "\n"))
|
||||
@@ -44,7 +44,7 @@ func (repo *Repository) GetTagType(id SHA1) (string, error) {
|
||||
return typ, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
||||
t, ok := repo.tagCache.Get(tagID.String())
|
||||
if ok {
|
||||
log.Debug("Hit cache: %s", tagID)
|
||||
@@ -64,7 +64,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
// every tag should have a commit ID so return all errors
|
||||
return nil, err
|
||||
}
|
||||
commitID, err := NewIDFromString(commitIDStr)
|
||||
commitID, err := repo.objectFormat.NewIDFromString(commitIDStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := parseTagData(data)
|
||||
tag, err := parseTagData(tagID.Type(), data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -194,6 +194,7 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRepository_parseTagRef(t *testing.T) {
|
||||
sha1 := ObjectFormatFromID(Sha1)
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -223,8 +224,8 @@ func TestRepository_parseTagRef(t *testing.T) {
|
||||
|
||||
want: &Tag{
|
||||
Name: "v1.9.1",
|
||||
ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
|
||||
Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
|
||||
ID: sha1.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
|
||||
Object: sha1.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"),
|
||||
Type: "commit",
|
||||
Tagger: parseAuthorLine(t, "Foo Bar <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",
|
||||
@@ -252,8 +253,8 @@ func TestRepository_parseTagRef(t *testing.T) {
|
||||
|
||||
want: &Tag{
|
||||
Name: "v0.0.1",
|
||||
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
|
||||
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
|
||||
ID: sha1.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
|
||||
Object: sha1.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
|
||||
Type: "tag",
|
||||
Tagger: parseAuthorLine(t, "Foo Bar <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",
|
||||
@@ -310,8 +311,8 @@ qbHDASXl
|
||||
|
||||
want: &Tag{
|
||||
Name: "v0.0.1",
|
||||
ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
|
||||
Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
|
||||
ID: sha1.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"),
|
||||
Object: sha1.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"),
|
||||
Type: "tag",
|
||||
Tagger: parseAuthorLine(t, "Foo Bar <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",
|
||||
@@ -350,7 +351,7 @@ Add changelog of v1.9.1 (#7859)
|
||||
for _, test := range tests {
|
||||
tc := test // don't close over loop variable
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := parseTagRef(tc.givenRef)
|
||||
got, err := parseTagRef(sha1, tc.givenRef)
|
||||
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
|
@@ -21,7 +21,7 @@ type CommitTreeOpts struct {
|
||||
}
|
||||
|
||||
// CommitTree creates a commit from a given tree id for the user with provided message
|
||||
func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
|
||||
func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opts CommitTreeOpts) (ObjectID, error) {
|
||||
commitTimeStr := time.Now().Format(time.RFC3339)
|
||||
|
||||
// Because this may call hooks we should pass in the environment
|
||||
@@ -61,7 +61,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
|
||||
Stderr: stderr,
|
||||
})
|
||||
if err != nil {
|
||||
return SHA1{}, ConcatenateError(err, stderr.String())
|
||||
return nil, ConcatenateError(err, stderr.String())
|
||||
}
|
||||
return NewIDFromString(strings.TrimSpace(stdout.String()))
|
||||
return repo.objectFormat.NewIDFromString(strings.TrimSpace(stdout.String()))
|
||||
}
|
||||
|
@@ -6,8 +6,10 @@
|
||||
|
||||
package git
|
||||
|
||||
func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
gogitTree, err := repo.gogitRepo.TreeObject(id)
|
||||
import "github.com/go-git/go-git/v5/plumbing"
|
||||
|
||||
func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
||||
gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id.RawValue()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -19,7 +21,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != SHAFullLength {
|
||||
if len(idStr) != repo.objectFormat.FullLength() {
|
||||
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -28,14 +30,14 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
idStr = res[:len(res)-1]
|
||||
}
|
||||
}
|
||||
id, err := NewIDFromString(idStr)
|
||||
id, err := repo.objectFormat.NewIDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedID := id
|
||||
commitObject, err := repo.gogitRepo.CommitObject(id)
|
||||
commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id.RawValue()))
|
||||
if err == nil {
|
||||
id = SHA1(commitObject.TreeHash)
|
||||
id = ParseGogitHash(commitObject.TreeHash)
|
||||
}
|
||||
treeObject, err := repo.getTree(id)
|
||||
if err != nil {
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
||||
wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -28,7 +28,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag, err := parseTagData(data)
|
||||
tag, err := parseTagData(id.Type(), data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
case "tree":
|
||||
tree := NewTree(repo, id)
|
||||
tree.ResolvedID = id
|
||||
tree.entries, err = catBatchParseTreeEntries(tree, rd, size)
|
||||
tree.entries, err = catBatchParseTreeEntries(repo.objectFormat, tree, rd, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != SHAFullLength {
|
||||
if len(idStr) != repo.objectFormat.FullLength() {
|
||||
res, err := repo.GetRefCommitID(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -75,7 +75,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
idStr = res
|
||||
}
|
||||
}
|
||||
id, err := NewIDFromString(idStr)
|
||||
id, err := repo.objectFormat.NewIDFromString(idStr)
|
||||
if err != nil {
|
||||
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.
|
||||
type Tag struct {
|
||||
Name string
|
||||
ID SHA1
|
||||
Object SHA1 // The id of this commit object
|
||||
ID ObjectID
|
||||
Object ObjectID // The id of this commit object
|
||||
Type string
|
||||
Tagger *Signature
|
||||
Message string
|
||||
@@ -33,8 +33,10 @@ func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) {
|
||||
// Parse commit information from the (uncompressed) raw
|
||||
// data from the commit object.
|
||||
// \n\n separate headers from message
|
||||
func parseTagData(data []byte) (*Tag, error) {
|
||||
func parseTagData(objectFormat ObjectFormat, data []byte) (*Tag, error) {
|
||||
tag := new(Tag)
|
||||
tag.ID = objectFormat.NewEmptyID()
|
||||
tag.Object = objectFormat.NewEmptyID()
|
||||
tag.Tagger = &Signature{}
|
||||
// we now have the contents of the commit object. Let's investigate...
|
||||
nextline := 0
|
||||
@@ -48,7 +50,7 @@ l:
|
||||
reftype := line[:spacepos]
|
||||
switch string(reftype) {
|
||||
case "object":
|
||||
id, err := NewIDFromString(string(line[spacepos+1:]))
|
||||
id, err := objectFormat.NewIDFromString(string(line[spacepos+1:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -22,8 +22,8 @@ tagger Lucas Michot <lucas@semalead.com> 1484491741 +0100
|
||||
|
||||
`), tag: Tag{
|
||||
Name: "",
|
||||
ID: SHA1{},
|
||||
Object: SHA1{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a},
|
||||
ID: NewSha1(),
|
||||
Object: &Sha1Hash{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a},
|
||||
Type: "commit",
|
||||
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484491741, 0)},
|
||||
Message: "",
|
||||
@@ -39,8 +39,8 @@ o
|
||||
|
||||
ono`), tag: Tag{
|
||||
Name: "",
|
||||
ID: SHA1{},
|
||||
Object: SHA1{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc},
|
||||
ID: NewSha1(),
|
||||
Object: &Sha1Hash{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc},
|
||||
Type: "commit",
|
||||
Tagger: &Signature{Name: "Lucas Michot", Email: "lucas@semalead.com", When: time.Unix(1484553735, 0)},
|
||||
Message: "test message\no\n\nono",
|
||||
@@ -49,7 +49,7 @@ ono`), tag: Tag{
|
||||
}
|
||||
|
||||
for _, test := range testData {
|
||||
tag, err := parseTagData(test.data)
|
||||
tag, err := parseTagData(ObjectFormatFromID(Sha1), test.data)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, test.tag.ID, tag.ID)
|
||||
assert.EqualValues(t, test.tag.Object, tag.Object)
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// NewTree create a new tree according the repository and tree id
|
||||
func NewTree(repo *Repository, id SHA1) *Tree {
|
||||
func NewTree(repo *Repository, id ObjectID) *Tree {
|
||||
return &Tree{
|
||||
ID: id,
|
||||
repo: repo,
|
||||
|
@@ -24,7 +24,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
|
||||
gogitTreeEntry: &object.TreeEntry{
|
||||
Name: "",
|
||||
Mode: filemode.Dir,
|
||||
Hash: t.ID,
|
||||
Hash: plumbing.Hash(t.ID.RawValue()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
// TreeEntry the leaf in the git tree
|
||||
type TreeEntry struct {
|
||||
ID SHA1
|
||||
ID ObjectID
|
||||
|
||||
gogitTreeEntry *object.TreeEntry
|
||||
ptree *Tree
|
||||
@@ -88,7 +88,7 @@ func (te *TreeEntry) Blob() *Blob {
|
||||
}
|
||||
|
||||
return &Blob{
|
||||
ID: te.gogitTreeEntry.Hash,
|
||||
ID: ParseGogitHash(te.gogitTreeEntry.Hash),
|
||||
gogitEncodedObj: encodedObj,
|
||||
name: te.Name(),
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import "code.gitea.io/gitea/modules/log"
|
||||
|
||||
// TreeEntry the leaf in the git tree
|
||||
type TreeEntry struct {
|
||||
ID SHA1
|
||||
ID ObjectID
|
||||
|
||||
ptree *Tree
|
||||
|
||||
|
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
// Tree represents a flat directory listing.
|
||||
type Tree struct {
|
||||
ID SHA1
|
||||
ResolvedID SHA1
|
||||
ID ObjectID
|
||||
ResolvedID ObjectID
|
||||
repo *Repository
|
||||
|
||||
gogitTree *object.Tree
|
||||
@@ -26,7 +26,7 @@ type Tree struct {
|
||||
}
|
||||
|
||||
func (t *Tree) loadTreeObject() error {
|
||||
gogitTree, err := t.repo.gogitRepo.TreeObject(t.ID)
|
||||
gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func (t *Tree) ListEntries() (Entries, error) {
|
||||
entries := make([]*TreeEntry, len(t.gogitTree.Entries))
|
||||
for i, entry := range t.gogitTree.Entries {
|
||||
entries[i] = &TreeEntry{
|
||||
ID: entry.Hash,
|
||||
ID: ParseGogitHash(entry.Hash),
|
||||
gogitTreeEntry: &t.gogitTree.Entries[i],
|
||||
ptree: t,
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
||||
}
|
||||
|
||||
convertedEntry := &TreeEntry{
|
||||
ID: entry.Hash,
|
||||
ID: ParseGogitHash(entry.Hash),
|
||||
gogitTreeEntry: &entry,
|
||||
ptree: t,
|
||||
fullName: fullName,
|
||||
|
@@ -13,8 +13,8 @@ import (
|
||||
|
||||
// Tree represents a flat directory listing.
|
||||
type Tree struct {
|
||||
ID SHA1
|
||||
ResolvedID SHA1
|
||||
ID ObjectID
|
||||
ResolvedID ObjectID
|
||||
repo *Repository
|
||||
|
||||
// parent tree
|
||||
@@ -54,7 +54,7 @@ func (t *Tree) ListEntries() (Entries, error) {
|
||||
}
|
||||
}
|
||||
if typ == "tree" {
|
||||
t.entries, err = catBatchParseTreeEntries(t, rd, sz)
|
||||
t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func (t *Tree) ListEntries() (Entries, error) {
|
||||
}
|
||||
|
||||
var err error
|
||||
t.entries, err = parseTreeEntries(stdout, t)
|
||||
t.entries, err = parseTreeEntries(t.repo.objectFormat, stdout, t)
|
||||
if err == nil {
|
||||
t.entriesParsed = true
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
|
||||
}
|
||||
|
||||
var err error
|
||||
t.entriesRecursive, err = parseTreeEntries(stdout, t)
|
||||
t.entriesRecursive, err = parseTreeEntries(t.repo.objectFormat, stdout, t)
|
||||
if err == nil {
|
||||
t.entriesRecursiveParsed = true
|
||||
}
|
||||
|
104
modules/graceful/manager_common.go
Normal file
104
modules/graceful/manager_common.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type systemdNotifyMsg string
|
||||
|
||||
const (
|
||||
readyMsg systemdNotifyMsg = "READY=1"
|
||||
stoppingMsg systemdNotifyMsg = "STOPPING=1"
|
||||
reloadingMsg systemdNotifyMsg = "RELOADING=1"
|
||||
watchdogMsg systemdNotifyMsg = "WATCHDOG=1"
|
||||
)
|
||||
|
||||
func statusMsg(msg string) systemdNotifyMsg {
|
||||
return systemdNotifyMsg("STATUS=" + msg)
|
||||
}
|
||||
|
||||
// Manager manages the graceful shutdown process
|
||||
type Manager struct {
|
||||
ctx context.Context
|
||||
isChild bool
|
||||
forked bool
|
||||
lock sync.RWMutex
|
||||
state state
|
||||
shutdownCtx context.Context
|
||||
hammerCtx context.Context
|
||||
terminateCtx context.Context
|
||||
managerCtx context.Context
|
||||
shutdownCtxCancel context.CancelFunc
|
||||
hammerCtxCancel context.CancelFunc
|
||||
terminateCtxCancel context.CancelFunc
|
||||
managerCtxCancel context.CancelFunc
|
||||
runningServerWaitGroup sync.WaitGroup
|
||||
createServerWaitGroup sync.WaitGroup
|
||||
terminateWaitGroup sync.WaitGroup
|
||||
shutdownRequested chan struct{}
|
||||
|
||||
toRunAtShutdown []func()
|
||||
toRunAtTerminate []func()
|
||||
}
|
||||
|
||||
func newGracefulManager(ctx context.Context) *Manager {
|
||||
manager := &Manager{ctx: ctx, shutdownRequested: make(chan struct{})}
|
||||
manager.createServerWaitGroup.Add(numberOfServersToCreate)
|
||||
manager.prepare(ctx)
|
||||
manager.start()
|
||||
return manager
|
||||
}
|
||||
|
||||
func (g *Manager) prepare(ctx context.Context) {
|
||||
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
|
||||
g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(ctx)
|
||||
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
|
||||
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
|
||||
|
||||
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
|
||||
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
|
||||
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
|
||||
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
|
||||
|
||||
if !g.setStateTransition(stateInit, stateRunning) {
|
||||
panic("invalid graceful manager state: transition from init to running failed")
|
||||
}
|
||||
}
|
||||
|
||||
// DoImmediateHammer causes an immediate hammer
|
||||
func (g *Manager) DoImmediateHammer() {
|
||||
g.notify(statusMsg("Sending immediate hammer"))
|
||||
g.doHammerTime(0 * time.Second)
|
||||
}
|
||||
|
||||
// DoGracefulShutdown causes a graceful shutdown
|
||||
func (g *Manager) DoGracefulShutdown() {
|
||||
g.lock.Lock()
|
||||
select {
|
||||
case <-g.shutdownRequested:
|
||||
default:
|
||||
close(g.shutdownRequested)
|
||||
}
|
||||
forked := g.forked
|
||||
g.lock.Unlock()
|
||||
|
||||
if !forked {
|
||||
g.notify(stoppingMsg)
|
||||
} else {
|
||||
g.notify(statusMsg("Shutting down after fork"))
|
||||
}
|
||||
g.doShutdown()
|
||||
}
|
||||
|
||||
// RegisterServer registers the running of a listening server, in the case of unix this means that the parent process can now die.
|
||||
// Any call to RegisterServer must be matched by a call to ServerDone
|
||||
func (g *Manager) RegisterServer() {
|
||||
KillParent()
|
||||
g.runningServerWaitGroup.Add(1)
|
||||
}
|
@@ -12,7 +12,6 @@ import (
|
||||
"os/signal"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -22,51 +21,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// Manager manages the graceful shutdown process
|
||||
type Manager struct {
|
||||
isChild bool
|
||||
forked bool
|
||||
lock *sync.RWMutex
|
||||
state state
|
||||
shutdownCtx context.Context
|
||||
hammerCtx context.Context
|
||||
terminateCtx context.Context
|
||||
managerCtx context.Context
|
||||
shutdownCtxCancel context.CancelFunc
|
||||
hammerCtxCancel context.CancelFunc
|
||||
terminateCtxCancel context.CancelFunc
|
||||
managerCtxCancel context.CancelFunc
|
||||
runningServerWaitGroup sync.WaitGroup
|
||||
createServerWaitGroup sync.WaitGroup
|
||||
terminateWaitGroup sync.WaitGroup
|
||||
|
||||
toRunAtShutdown []func()
|
||||
toRunAtTerminate []func()
|
||||
}
|
||||
|
||||
func newGracefulManager(ctx context.Context) *Manager {
|
||||
manager := &Manager{
|
||||
isChild: len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1,
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
manager.createServerWaitGroup.Add(numberOfServersToCreate)
|
||||
manager.start(ctx)
|
||||
return manager
|
||||
}
|
||||
|
||||
type systemdNotifyMsg string
|
||||
|
||||
const (
|
||||
readyMsg systemdNotifyMsg = "READY=1"
|
||||
stoppingMsg systemdNotifyMsg = "STOPPING=1"
|
||||
reloadingMsg systemdNotifyMsg = "RELOADING=1"
|
||||
watchdogMsg systemdNotifyMsg = "WATCHDOG=1"
|
||||
)
|
||||
|
||||
func statusMsg(msg string) systemdNotifyMsg {
|
||||
return systemdNotifyMsg("STATUS=" + msg)
|
||||
}
|
||||
|
||||
func pidMsg() systemdNotifyMsg {
|
||||
return systemdNotifyMsg("MAINPID=" + strconv.Itoa(os.Getpid()))
|
||||
}
|
||||
@@ -89,27 +43,13 @@ func (g *Manager) notify(msg systemdNotifyMsg) {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Manager) start(ctx context.Context) {
|
||||
// Make contexts
|
||||
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
|
||||
g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(ctx)
|
||||
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
|
||||
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
|
||||
|
||||
// Next add pprof labels to these contexts
|
||||
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
|
||||
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
|
||||
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
|
||||
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
|
||||
|
||||
func (g *Manager) start() {
|
||||
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
|
||||
pprof.SetGoroutineLabels(g.managerCtx)
|
||||
defer pprof.SetGoroutineLabels(ctx)
|
||||
defer pprof.SetGoroutineLabels(g.ctx)
|
||||
|
||||
g.isChild = len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1
|
||||
|
||||
// Set the running state & handle signals
|
||||
if !g.setStateTransition(stateInit, stateRunning) {
|
||||
panic("invalid graceful manager state: transition from init to running failed")
|
||||
}
|
||||
g.notify(statusMsg("Starting Gitea"))
|
||||
g.notify(pidMsg())
|
||||
go g.handleSignals(g.managerCtx)
|
||||
@@ -118,11 +58,9 @@ func (g *Manager) start(ctx context.Context) {
|
||||
startupDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(startupDone)
|
||||
// Wait till we're done getting all of the listeners and then close
|
||||
// the unused ones
|
||||
// Wait till we're done getting all the listeners and then close the unused ones
|
||||
g.createServerWaitGroup.Wait()
|
||||
// Ignore the error here there's not much we can do with it
|
||||
// They're logged in the CloseProvidedListeners function
|
||||
// Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function
|
||||
_ = CloseProvidedListeners()
|
||||
g.notify(readyMsg)
|
||||
}()
|
||||
@@ -133,7 +71,7 @@ func (g *Manager) start(ctx context.Context) {
|
||||
return
|
||||
case <-g.IsShutdown():
|
||||
func() {
|
||||
// When waitgroup counter goes negative it will panic - we don't care about this so we can just ignore it.
|
||||
// When WaitGroup counter goes negative it will panic - we don't care about this so we can just ignore it.
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
@@ -255,29 +193,3 @@ func (g *Manager) DoGracefulRestart() {
|
||||
g.doShutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// DoImmediateHammer causes an immediate hammer
|
||||
func (g *Manager) DoImmediateHammer() {
|
||||
g.notify(statusMsg("Sending immediate hammer"))
|
||||
g.doHammerTime(0 * time.Second)
|
||||
}
|
||||
|
||||
// DoGracefulShutdown causes a graceful shutdown
|
||||
func (g *Manager) DoGracefulShutdown() {
|
||||
g.lock.Lock()
|
||||
if !g.forked {
|
||||
g.lock.Unlock()
|
||||
g.notify(stoppingMsg)
|
||||
} else {
|
||||
g.lock.Unlock()
|
||||
g.notify(statusMsg("Shutting down after fork"))
|
||||
}
|
||||
g.doShutdown()
|
||||
}
|
||||
|
||||
// RegisterServer registers the running of a listening server, in the case of unix this means that the parent process can now die.
|
||||
// Any call to RegisterServer must be matched by a call to ServerDone
|
||||
func (g *Manager) RegisterServer() {
|
||||
KillParent()
|
||||
g.runningServerWaitGroup.Add(1)
|
||||
}
|
||||
|
@@ -7,11 +7,9 @@
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -30,64 +28,11 @@ const (
|
||||
acceptHammerCode = svc.Accepted(hammerCode)
|
||||
)
|
||||
|
||||
// Manager manages the graceful shutdown process
|
||||
type Manager struct {
|
||||
ctx context.Context
|
||||
isChild bool
|
||||
lock *sync.RWMutex
|
||||
state state
|
||||
shutdownCtx context.Context
|
||||
hammerCtx context.Context
|
||||
terminateCtx context.Context
|
||||
managerCtx context.Context
|
||||
shutdownCtxCancel context.CancelFunc
|
||||
hammerCtxCancel context.CancelFunc
|
||||
terminateCtxCancel context.CancelFunc
|
||||
managerCtxCancel context.CancelFunc
|
||||
runningServerWaitGroup sync.WaitGroup
|
||||
createServerWaitGroup sync.WaitGroup
|
||||
terminateWaitGroup sync.WaitGroup
|
||||
shutdownRequested chan struct{}
|
||||
|
||||
toRunAtShutdown []func()
|
||||
toRunAtTerminate []func()
|
||||
}
|
||||
|
||||
func newGracefulManager(ctx context.Context) *Manager {
|
||||
manager := &Manager{
|
||||
isChild: false,
|
||||
lock: &sync.RWMutex{},
|
||||
ctx: ctx,
|
||||
}
|
||||
manager.createServerWaitGroup.Add(numberOfServersToCreate)
|
||||
manager.start()
|
||||
return manager
|
||||
}
|
||||
|
||||
func (g *Manager) start() {
|
||||
// Make contexts
|
||||
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(g.ctx)
|
||||
g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(g.ctx)
|
||||
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(g.ctx)
|
||||
g.managerCtx, g.managerCtxCancel = context.WithCancel(g.ctx)
|
||||
|
||||
// Next add pprof labels to these contexts
|
||||
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
|
||||
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
|
||||
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
|
||||
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
|
||||
|
||||
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
|
||||
pprof.SetGoroutineLabels(g.managerCtx)
|
||||
defer pprof.SetGoroutineLabels(g.ctx)
|
||||
|
||||
// Make channels
|
||||
g.shutdownRequested = make(chan struct{})
|
||||
|
||||
// Set the running state
|
||||
if !g.setStateTransition(stateInit, stateRunning) {
|
||||
panic("invalid graceful manager state: transition from init to running failed")
|
||||
}
|
||||
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
|
||||
log.Trace("Skipping SVC check as SKIP_MINWINSVC is set")
|
||||
return
|
||||
@@ -201,30 +146,6 @@ hammerLoop:
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// DoImmediateHammer causes an immediate hammer
|
||||
func (g *Manager) DoImmediateHammer() {
|
||||
g.doHammerTime(0 * time.Second)
|
||||
}
|
||||
|
||||
// DoGracefulShutdown causes a graceful shutdown
|
||||
func (g *Manager) DoGracefulShutdown() {
|
||||
g.lock.Lock()
|
||||
select {
|
||||
case <-g.shutdownRequested:
|
||||
g.lock.Unlock()
|
||||
default:
|
||||
close(g.shutdownRequested)
|
||||
g.lock.Unlock()
|
||||
g.doShutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterServer registers the running of a listening server.
|
||||
// Any call to RegisterServer must be matched by a call to ServerDone
|
||||
func (g *Manager) RegisterServer() {
|
||||
g.runningServerWaitGroup.Add(1)
|
||||
}
|
||||
|
||||
func (g *Manager) awaitServer(limit time.Duration) bool {
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -249,3 +170,11 @@ func (g *Manager) awaitServer(limit time.Duration) bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Manager) notify(msg systemdNotifyMsg) {
|
||||
// Windows doesn't use systemd to notify
|
||||
}
|
||||
|
||||
func KillParent() {
|
||||
// Windows doesn't need to "kill parent" because there is no graceful restart
|
||||
}
|
||||
|
@@ -62,8 +62,8 @@ func isIndexable(entry *git.TreeEntry) bool {
|
||||
}
|
||||
|
||||
// parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command
|
||||
func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) {
|
||||
entries, err := git.ParseTreeEntries(stdout)
|
||||
func parseGitLsTreeOutput(objectFormat git.ObjectFormat, stdout []byte) ([]internal.FileUpdate, error) {
|
||||
entries, err := git.ParseTreeEntries(objectFormat, stdout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -92,7 +92,11 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
|
||||
}
|
||||
|
||||
var err error
|
||||
changes.Updates, err = parseGitLsTreeOutput(stdout)
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout)
|
||||
return &changes, err
|
||||
}
|
||||
|
||||
@@ -169,6 +173,11 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout)
|
||||
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout)
|
||||
return &changes, err
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
|
||||
opts := &issue_model.IssuesOptions{
|
||||
Paginator: options.Paginator,
|
||||
RepoIDs: options.RepoIDs,
|
||||
AllPublic: options.AllPublic,
|
||||
RepoCond: nil,
|
||||
AssigneeID: convertID(options.AssigneeID),
|
||||
PosterID: convertID(options.PosterID),
|
||||
|
@@ -12,7 +12,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
||||
searchOpt := &SearchOptions{
|
||||
Keyword: keyword,
|
||||
RepoIDs: opts.RepoIDs,
|
||||
AllPublic: false,
|
||||
AllPublic: opts.AllPublic,
|
||||
IsPull: opts.IsPull,
|
||||
IsClosed: opts.IsClosed,
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
db_model "code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/indexer/issues/bleve"
|
||||
"code.gitea.io/gitea/modules/indexer/issues/db"
|
||||
@@ -314,30 +313,3 @@ func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) {
|
||||
_, total, err := SearchIssues(ctx, opts)
|
||||
return total, err
|
||||
}
|
||||
|
||||
// CountIssuesByRepo counts issues by options and group by repo id.
|
||||
// It's not a complete implementation, since it requires the caller should provide the repo ids.
|
||||
// That means opts.RepoIDs must be specified, and opts.AllPublic must be false.
|
||||
// It's good enough for the current usage, and it can be improved if needed.
|
||||
// TODO: use "group by" of the indexer engines to implement it.
|
||||
func CountIssuesByRepo(ctx context.Context, opts *SearchOptions) (map[int64]int64, error) {
|
||||
if len(opts.RepoIDs) == 0 {
|
||||
return nil, fmt.Errorf("opts.RepoIDs must be specified")
|
||||
}
|
||||
if opts.AllPublic {
|
||||
return nil, fmt.Errorf("opts.AllPublic must be false")
|
||||
}
|
||||
|
||||
repoIDs := container.SetOf(opts.RepoIDs...).Values()
|
||||
ret := make(map[int64]int64, len(repoIDs))
|
||||
// TODO: it could be faster if do it in parallel for some indexer engines. Improve it if users report it's slow.
|
||||
for _, repoID := range repoIDs {
|
||||
count, err := CountIssues(ctx, opts.Copy(func(o *internal.SearchOptions) { o.RepoIDs = []int64{repoID} }))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[repoID] = count
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
@@ -211,10 +211,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
||||
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
|
||||
|
||||
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(options.Keyword, &meilisearch.SearchRequest{
|
||||
Filter: query.Statement(),
|
||||
Limit: int64(limit),
|
||||
Offset: int64(skip),
|
||||
Sort: sortBy,
|
||||
Filter: query.Statement(),
|
||||
Limit: int64(limit),
|
||||
Offset: int64(skip),
|
||||
Sort: sortBy,
|
||||
MatchingStrategy: "all",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -49,11 +49,9 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
||||
|
||||
dbBranches := make(map[string]*git_model.Branch)
|
||||
{
|
||||
branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
RepoID: repo.ID,
|
||||
branches, err := db.Find[git_model.Branch](ctx, git_model.FindBranchOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
@@ -11,24 +11,27 @@ import (
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func AddCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
collaboration := &repo_model.Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: u.ID,
|
||||
}
|
||||
|
||||
has, err := db.GetByBean(ctx, collaboration)
|
||||
has, err := db.Exist[repo_model.Collaboration](ctx, builder.Eq{
|
||||
"repo_id": repo.ID,
|
||||
"user_id": u.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return nil
|
||||
}
|
||||
collaboration.Mode = perm.AccessModeWrite
|
||||
|
||||
if err = db.Insert(ctx, collaboration); err != nil {
|
||||
if err = db.Insert(ctx, &repo_model.Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: u.ID,
|
||||
Mode: perm.AccessModeWrite,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -144,7 +144,7 @@ func TestCommitToPushCommit(t *testing.T) {
|
||||
When: now,
|
||||
}
|
||||
const hexString = "0123456789abcdef0123456789abcdef01234567"
|
||||
sha1, err := git.NewIDFromString(hexString)
|
||||
sha1, err := git.IDFromString(hexString)
|
||||
assert.NoError(t, err)
|
||||
pushCommit := CommitToPushCommit(&git.Commit{
|
||||
ID: sha1,
|
||||
@@ -169,11 +169,12 @@ func TestListToPushCommits(t *testing.T) {
|
||||
When: now,
|
||||
}
|
||||
|
||||
hashType := git.ObjectFormatFromID(git.Sha1)
|
||||
const hexString1 = "0123456789abcdef0123456789abcdef01234567"
|
||||
hash1, err := git.NewIDFromString(hexString1)
|
||||
hash1, err := hashType.NewIDFromString(hexString1)
|
||||
assert.NoError(t, err)
|
||||
const hexString2 = "fedcba9876543210fedcba9876543210fedcba98"
|
||||
hash2, err := git.NewIDFromString(hexString2)
|
||||
hash2, err := hashType.NewIDFromString(hexString2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
l := []*git.Commit{
|
||||
|
@@ -160,24 +160,25 @@ const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | o
|
||||
// getDirectorySize returns the disk consumption for a given path
|
||||
func getDirectorySize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.WalkDir(path, func(_ string, info os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) { // ignore the error because the file maybe deleted during traversing.
|
||||
return nil
|
||||
}
|
||||
err := filepath.WalkDir(path, func(_ string, entry os.DirEntry, err error) error {
|
||||
if os.IsNotExist(err) { // ignore the error because some files (like temp/lock file) may be deleted during traversing.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if entry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
f, err := info.Info()
|
||||
if err != nil {
|
||||
info, err := entry.Info()
|
||||
if os.IsNotExist(err) { // ignore the error as above
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if (f.Mode() & notRegularFileMode) == 0 {
|
||||
size += f.Size()
|
||||
if (info.Mode() & notRegularFileMode) == 0 {
|
||||
size += info.Size()
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
})
|
||||
return size, err
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -188,7 +188,7 @@ func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckInitRepository(ctx context.Context, owner, name string) (err error) {
|
||||
func CheckInitRepository(ctx context.Context, owner, name string, objectFormat git.ObjectFormat) (err error) {
|
||||
// Somehow the directory could exist.
|
||||
repoPath := repo_model.RepoPath(owner, name)
|
||||
isExist, err := util.IsExist(repoPath)
|
||||
@@ -204,7 +204,7 @@ func CheckInitRepository(ctx context.Context, owner, name string) (err error) {
|
||||
}
|
||||
|
||||
// Init git bare new repository.
|
||||
if err = git.InitRepository(ctx, repoPath, true); err != nil {
|
||||
if err = git.InitRepository(ctx, repoPath, true, objectFormat); err != nil {
|
||||
return fmt.Errorf("git.InitRepository: %w", err)
|
||||
} else if err = CreateDelegateHooks(repoPath); err != nil {
|
||||
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.
|
||||
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
|
||||
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
|
||||
|
@@ -409,7 +409,7 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
|
||||
|
||||
defer content.Close()
|
||||
|
||||
_, err := git_model.NewLFSMetaObject(ctx, &git_model.LFSMetaObject{Pointer: p, RepositoryID: repo.ID})
|
||||
_, err := git_model.NewLFSMetaObject(ctx, repo.ID, p)
|
||||
if err != nil {
|
||||
log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, p, err)
|
||||
return err
|
||||
@@ -456,7 +456,7 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
|
||||
|
||||
if exist {
|
||||
log.Trace("Repo[%-v]: LFS object %-v already present; creating meta object", repo, pointerBlob.Pointer)
|
||||
_, err := git_model.NewLFSMetaObject(ctx, &git_model.LFSMetaObject{Pointer: pointerBlob.Pointer, RepositoryID: repo.ID})
|
||||
_, err := git_model.NewLFSMetaObject(ctx, repo.ID, pointerBlob.Pointer)
|
||||
if err != nil {
|
||||
log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
|
||||
return err
|
||||
|
@@ -26,7 +26,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
|
||||
}
|
||||
|
||||
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip")
|
||||
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
|
||||
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048)
|
||||
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
||||
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
|
||||
|
||||
|
@@ -9,7 +9,6 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -117,7 +116,7 @@ func DBConnStr() (string, error) {
|
||||
if !EnableSQLite3 {
|
||||
return "", errors.New("this Gitea binary was not built with SQLite3 support")
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil {
|
||||
return "", fmt.Errorf("Failed to create directories: %w", err)
|
||||
}
|
||||
journalMode := ""
|
||||
|
@@ -34,6 +34,7 @@ var (
|
||||
PasswordHashAlgo string
|
||||
PasswordCheckPwn bool
|
||||
SuccessfulTokensCacheSize int
|
||||
DisableQueryAuthToken bool
|
||||
CSRFCookieName = "_csrf"
|
||||
CSRFCookieHTTPOnly = true
|
||||
)
|
||||
@@ -157,4 +158,11 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
|
||||
PasswordComplexity = append(PasswordComplexity, name)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: default value should be true in future releases
|
||||
DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false)
|
||||
|
||||
if !DisableQueryAuthToken {
|
||||
log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will default to true in gitea 1.23 and will be removed in gitea 1.24.")
|
||||
}
|
||||
}
|
||||
|
@@ -230,6 +230,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
|
||||
output, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
URLPrefix: setting.AppSubURL,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
}, input)
|
||||
if err != nil {
|
||||
log.Error("RenderString: %v", err)
|
||||
|
Reference in New Issue
Block a user