ronantakizawa/codereview-bench huggingface dataset:
Repository: go-gitea/gitea
File: modules/packages/swift/metadata.go (Go)

Code before:
		}

		p.Metadata.Manifests[swiftVersion] = manifest
	}

	if _, found := p.Metadata.Manifests[""]; !found {
		return nil, ErrMissingManifestFile
	}

	if mr != nil {
		var ssc *SoftwareSourceCode
		if err := json.NewDecoder(mr).Decode(&ssc); err != nil {
			return nil, err
		}

		p.Metadata.Description = ssc.Description
		p.Metadata.Keywords = ssc.Keywords
		p.Metadata.License = ssc.License
		author := Person{
			Name:       ssc.Author.Name,
			GivenName:  ssc.Author.GivenName,
			MiddleName: ssc.Author.MiddleName,
			FamilyName: ssc.Author.FamilyName,
		}
		// If Name is not provided but individual name components are, generate it
		if author.Name == "" && (author.GivenName != "" || author.MiddleName != "" || author.FamilyName != "") {
			author.Name = author.String()
		}
		p.Metadata.Author = author

		p.Metadata.RepositoryURL = ssc.CodeRepository
		if !validation.IsValidURL(p.Metadata.RepositoryURL) {
			p.Metadata.RepositoryURL = ""
		}

		p.RepositoryURLs = ssc.RepositoryURLs
	}

	return p, nil
}

// TrimmedVersionString returns the version string without the patch segment if it is zero
func TrimmedVersionString(v *version.Version) string {
	segments := v.Segments64()

	var b strings.Builder
	fmt.Fprintf(&b, "%d.%d", segments[0], segments[1])
	if segments[2] != 0 {
		fmt.Fprintf(&b, ".%d", segments[2])
	}
	return b.String()

Review comment (security):
```suggestion
		if author.Name == "" {
```

Maybe we can simplify the `if` check to this? If `author.Name` and `author.String()` are both empty, then it is still fine to assign the "Name" field?

Code after:
		}

		p.Metadata.Manifests[swiftVersion] = manifest
	}

	if _, found := p.Metadata.Manifests[""]; !found {
		return nil, ErrMissingManifestFile
	}

	if mr != nil {
		var ssc *SoftwareSourceCode
		if err := json.NewDecoder(mr).Decode(&ssc); err != nil {
			return nil, err
		}

		p.Metadata.Description = ssc.Description
		p.Metadata.Keywords = ssc.Keywords
		p.Metadata.License = ssc.License
		author := Person{
			Name:       ssc.Author.Name,
			GivenName:  ssc.Author.GivenName,
			MiddleName: ssc.Author.MiddleName,
			FamilyName: ssc.Author.FamilyName,
		}
		// If Name is not provided, generate it from individual name components
		if author.Name == "" {
			author.Name = author.String()
		}
		p.Metadata.Author = author

		p.Metadata.RepositoryURL = ssc.CodeRepository
		if !validation.IsValidURL(p.Metadata.RepositoryURL) {
			p.Metadata.RepositoryURL = ""
		}

		p.RepositoryURLs = ssc.RepositoryURLs
	}

	return p, nil
}

// TrimmedVersionString returns the version string without the patch segment if it is zero
func TrimmedVersionString(v *version.Version) string {
	segments := v.Segments64()

	var b strings.Builder
	fmt.Fprintf(&b, "%d.%d", segments[0], segments[1])
	if segments[2] != 0 {
		fmt.Fprintf(&b, ".%d", segments[2])
	}
	return b.String()
Repository: go-gitea/gitea
File: services/gitdiff/gitdiff.go (Go)

Code before:
	}
	tailDiffLine := &DiffLine{
		Type:    DiffLineSection,
		Content: " ",
		SectionInfo: &DiffLineSectionInfo{
			Path:         diffFile.Name,
			LastLeftIdx:  lastLine.LeftIdx,
			LastRightIdx: lastLine.RightIdx,
			LeftIdx:      leftLineCount,
			RightIdx:     rightLineCount,
		},
	}
	tailSection := &DiffSection{FileName: diffFile.Name, Lines: []*DiffLine{tailDiffLine}}
	return tailSection, diffLimitedContent
}

// GetDiffFileName returns the name of the diff file, or its old name in case it was deleted
func (diffFile *DiffFile) GetDiffFileName() string {
	if diffFile.Name == "" {
		return diffFile.OldName
	}
	return diffFile.Name
}

// GetDiffFileName returns the short name of the diff file, or its short old name in case it was deleted
func (diffFile *DiffFile) GetDiffFileShortName() string {
	if diffFile.Name == "" {
		return path.Base(diffFile.OldName)
	}
	return path.Base(diffFile.Name)
}

func (diffFile *DiffFile) ShouldBeHidden() bool {
	return diffFile.IsGenerated || diffFile.IsViewed
}

func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
	switch mode {
	case "040000":
		return "git.filemode.directory"
	case "100644":
		return "git.filemode.normal_file"
	case "100755":
		return "git.filemode.executable_file"
	case "120000":
		return "git.filemode.symbolic_link"
	case "160000":
		return "git.filemode.submodule"
	default:
		return mode
	}

Review comment (suggestion):
```suggestion
// GetDiffFileBaseName returns the short name of the diff file, or its short old name in case it was deleted
func (diffFile *DiffFile) GetDiffFileBaseName() string {
```

Code after:
	if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx {
		return nil, diffLimitedContent
	}
	tailDiffLine := &DiffLine{
		Type:    DiffLineSection,
		Content: " ",
		SectionInfo: &DiffLineSectionInfo{
			Path:         diffFile.Name,
			LastLeftIdx:  lastLine.LeftIdx,
			LastRightIdx: lastLine.RightIdx,
			LeftIdx:      leftLineCount,
			RightIdx:     rightLineCount,
		},
	}
	tailSection := &DiffSection{FileName: diffFile.Name, Lines: []*DiffLine{tailDiffLine}}
	return tailSection, diffLimitedContent
}

// GetDiffFileName returns the name of the diff file, or its old name in case it was deleted
func (diffFile *DiffFile) GetDiffFileName() string {
	if diffFile.Name == "" {
		return diffFile.OldName
	}
	return diffFile.Name
}

// GetDiffFileBaseName returns the short name of the diff file, or its short old name in case it was deleted
func (diffFile *DiffFile) GetDiffFileBaseName() string {
	if diffFile.Name == "" {
		return path.Base(diffFile.OldName)
	}
	return path.Base(diffFile.Name)
}

func (diffFile *DiffFile) ShouldBeHidden() bool {
	return diffFile.IsGenerated || diffFile.IsViewed
}

func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
	switch mode {
	case "040000":
		return "git.filemode.directory"
	case "100644":
		return "git.filemode.normal_file"
	case "100755":
		return "git.filemode.executable_file"
	case "120000":
		return "git.filemode.symbolic_link"
	case "160000":
		return "git.filemode.submodule"
	default:
Repository: go-gitea/gitea
File: routers/api/v1/repo/release_attachment.go (Go)

Code before:
	if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
		file, header, err := ctx.Req.FormFile("attachment")
		if err != nil {
			ctx.APIErrorInternal(err)
			return
		}
		defer file.Close()

		content = file
		size = header.Size
		filename = header.Filename
		if name := ctx.FormString("name"); name != "" {
			filename = name
		}
	} else {
		content = ctx.Req.Body
		filename = ctx.FormString("name")
	}

	if filename == "" {
		ctx.APIError(http.StatusBadRequest, "Could not determine name of attachment.")
		return
	}

	// Create a new attachment and save the file
	attach, err := attachment_service.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, setting.Attachment.MaxSize<<20, size, &repo_model.Attachment{
		Name:       filename,
		UploaderID: ctx.Doer.ID,
		RepoID:     ctx.Repo.Repository.ID,
		ReleaseID:  releaseID,
	})
	if err != nil {
		if upload.IsErrFileTypeForbidden(err) {
			ctx.APIError(http.StatusBadRequest, err)
			return
		}

		if attachment_service.IsErrAttachmentSizeExceed(err) {
			ctx.APIError(http.StatusRequestEntityTooLarge, err)
			return
		}

		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
}

// EditReleaseAttachment updates the given attachment
func EditReleaseAttachment(ctx *context.APIContext) {

Review comment (bug):
There are at least 2 "attachment max size" config options.

If you always use `setting.Attachment.MaxSize`, at least one is wrong

Code after:
		file, header, err := ctx.Req.FormFile("attachment")
		if err != nil {
			ctx.APIErrorInternal(err)
			return
		}
		defer file.Close()

		filename = header.Filename
		if name := ctx.FormString("name"); name != "" {
			filename = name
		}
		uploaderFile = attachment_service.NewLimitedUploaderKnownSize(file, header.Size)
	} else {
		filename = ctx.FormString("name")
		uploaderFile = attachment_service.NewLimitedUploaderMaxBytesReader(ctx.Req.Body, ctx.Resp)
	}

	if filename == "" {
		ctx.APIError(http.StatusBadRequest, "Could not determine name of attachment.")
		return
	}

	// Create a new attachment and save the file
	attach, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Repository.Release.AllowedTypes, &repo_model.Attachment{
		Name:       filename,
		UploaderID: ctx.Doer.ID,
		RepoID:     ctx.Repo.Repository.ID,
		ReleaseID:  releaseID,
	})
	if err != nil {
		if upload.IsErrFileTypeForbidden(err) {
			ctx.APIError(http.StatusBadRequest, err)
			return
		}

		if errors.Is(err, util.ErrContentTooLarge) {
			ctx.APIError(http.StatusRequestEntityTooLarge, err)
			return
		}

		ctx.APIErrorInternal(err)
		return
	}

	ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
}

// EditReleaseAttachment updates the given attachment
func EditReleaseAttachment(ctx *context.APIContext) {
	// swagger:operation PATCH /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoEditReleaseAttachment
	// ---
Repository: go-gitea/gitea
File: modules/session/db.go (Go)

Code before:

// Regenerate regenerates a session store from old session ID to new one.
func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
	s, err := auth.RegenerateSession(dbContext(), oldsid, sid)
	if err != nil {
		return nil, err
	}

	var kv map[any]any
	if len(s.Data) == 0 || s.Expiry.Add(p.maxLifetime) <= timeutil.TimeStampNow() {
		kv = make(map[any]any)
	} else {
		kv, err = session.DecodeGob(s.Data)
		if err != nil {
			return nil, err
		}
	}

	return NewDBStore(sid, kv), nil
}

// Count counts and returns number of sessions.
func (p *DBProvider) Count() (int, error) {
	total, err := auth.CountSessions(dbContext())
	if err != nil {
		fmt.Errorf("session/DB: error counting records: %w", err)
	}
	return int(total), nil
}

// GC calls GC to clean expired sessions.
func (p *DBProvider) GC() {
	if err := auth.CleanupSessions(dbContext(), p.maxLifetime); err != nil {
		log.Printf("session/DB: error garbage collecting: %v", err)
	}
}

func init() {
	session.Register("db", &DBProvider{})
}


Review comment (suggestion):
```suggestion
		return 0, fmt.Errorf("session/DB: error counting records: %w", err)
```

Code after:

// Regenerate regenerates a session store from old session ID to new one.
func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
	s, err := auth.RegenerateSession(dbContext(), oldsid, sid)
	if err != nil {
		return nil, err
	}

	var kv map[any]any
	if len(s.Data) == 0 || s.Expiry.Add(p.maxLifetime) <= timeutil.TimeStampNow() {
		kv = make(map[any]any)
	} else {
		kv, err = session.DecodeGob(s.Data)
		if err != nil {
			return nil, err
		}
	}

	return NewDBStore(sid, kv), nil
}

// Count counts and returns number of sessions.
func (p *DBProvider) Count() (int, error) {
	total, err := auth.CountSessions(dbContext())
	if err != nil {
		return 0, fmt.Errorf("session/DB: error counting records: %w", err)
	}
	return int(total), nil
}

// GC calls GC to clean expired sessions.
func (p *DBProvider) GC() {
	if err := auth.CleanupSessions(dbContext(), p.maxLifetime); err != nil {
		log.Printf("session/DB: error garbage collecting: %v", err)
	}
}

func init() {
	session.Register("db", &DBProvider{})
}
Repository: go-gitea/gitea
File: web_src/js/modules/fomantic/base.ts (TypeScript)

Code before:
import $ from 'jquery';
import {generateElemId} from '../../utils/dom.ts';

export function linkLabelAndInput(label: Element, input: Element) {
  const labelFor = label.getAttribute('for');
  const inputId = input.getAttribute('id');

  if (inputId && !labelFor) { // missing "for"
    label.setAttribute('for', inputId);
  } else if (!inputId && !labelFor) { // missing both "id" and "for"
    const id = generateElemId('_aria_label_input_');
    input.setAttribute('id', id);
    label.setAttribute('for', id);
  }
}

export function fomanticQuery(s: string) {
  return $(document).find(s);
}


Review comment (suggestion):
It also needs to support `Element` argument. Maybe:

```ts
export function fomanticQuery(arg: string | Element) {
  return typeof arg === 'string' ? $(document).find(arg) : $(arg);
}

Code after:
import $ from 'jquery';
import {generateElemId} from '../../utils/dom.ts';

export function linkLabelAndInput(label: Element, input: Element) {
  const labelFor = label.getAttribute('for');
  const inputId = input.getAttribute('id');

  if (inputId && !labelFor) { // missing "for"
    label.setAttribute('for', inputId);
  } else if (!inputId && !labelFor) { // missing both "id" and "for"
    const id = generateElemId('_aria_label_input_');
    input.setAttribute('id', id);
    label.setAttribute('for', id);
  }
}

export function fomanticQuery(s: string | Element | NodeListOf<Element>): ReturnType<typeof $> {
  // intentionally make it only work for query selector, it isn't used for creating HTML elements (for safety)
  return typeof s === 'string' ? $(document).find(s) : $(s);
}
Repository: go-gitea/gitea
File: services/actions/run.go (Go)

Code before:
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
	"context"
	"fmt"

	actions_model "code.gitea.io/gitea/models/actions"
	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/util"
	notify_service "code.gitea.io/gitea/services/notify"

	"github.com/nektos/act/pkg/jobparser"
	"gopkg.in/yaml.v3"
)

// PrepareRun prepares a run before inserting it into the database
// It parses the workflow content, evaluates concurrency if needed, and inserts the run and its jobs into the database.
// The title will be cut off at 255 characters if it's longer than 255 characters.
func PrepareRun(ctx context.Context, content []byte, run *actions_model.ActionRun, inputsWithDefaults map[string]any) error {
	if err := run.LoadAttributes(ctx); err != nil {
		return fmt.Errorf("LoadAttributes: %w", err)
	}

	vars, err := actions_model.GetVariablesOfRun(ctx, run)
	if err != nil {
		return fmt.Errorf("GetVariablesOfRun: %w", err)
	}

	wfRawConcurrency, err := jobparser.ReadWorkflowRawConcurrency(content)
	if err != nil {
		return fmt.Errorf("ReadWorkflowRawConcurrency: %w", err)
	}

	if wfRawConcurrency != nil {
		err = EvaluateRunConcurrencyFillModel(ctx, run, wfRawConcurrency, vars)
		if err != nil {
			return fmt.Errorf("EvaluateRunConcurrencyFillModel: %w", err)
		}
	}

	giteaCtx := GenerateGiteaContext(run, nil)

Review comment (suggestion):
```suggestion
// PrepareRun prepares a run and inserts it into the database
```
Before, it read to me like `the caller should call InsertRun themselves`, and I was confused where the `InsertRun` call is…

Code after:
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
	"context"
	"fmt"

	actions_model "code.gitea.io/gitea/models/actions"
	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/util"
	notify_service "code.gitea.io/gitea/services/notify"

	"github.com/nektos/act/pkg/jobparser"
	"gopkg.in/yaml.v3"
)

// PrepareRunAndInsert prepares a run and inserts it into the database
// It parses the workflow content, evaluates concurrency if needed, and inserts the run and its jobs into the database.
// The title will be cut off at 255 characters if it's longer than 255 characters.
func PrepareRunAndInsert(ctx context.Context, content []byte, run *actions_model.ActionRun, inputsWithDefaults map[string]any) error {
	if err := run.LoadAttributes(ctx); err != nil {
		return fmt.Errorf("LoadAttributes: %w", err)
	}

	vars, err := actions_model.GetVariablesOfRun(ctx, run)
	if err != nil {
		return fmt.Errorf("GetVariablesOfRun: %w", err)
	}

	wfRawConcurrency, err := jobparser.ReadWorkflowRawConcurrency(content)
	if err != nil {
		return fmt.Errorf("ReadWorkflowRawConcurrency: %w", err)
	}

	if wfRawConcurrency != nil {
		err = EvaluateRunConcurrencyFillModel(ctx, run, wfRawConcurrency, vars)
		if err != nil {
			return fmt.Errorf("EvaluateRunConcurrencyFillModel: %w", err)
		}
	}

	giteaCtx := GenerateGiteaContext(run, nil)
Repository: go-gitea/gitea
File: modules/web/routing/logger.go (Go)

Code before:
				message,
				log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
				log.ColoredTime(time.Since(record.startTime)),
				handlerFuncInfo,
			)
			return
		}

		if panicErr != nil {
			logWarn("router: %s %v %s for %s, panic in %v @ %s, err=%v",
				failedMessage,
				log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
				log.ColoredTime(time.Since(record.startTime)),
				handlerFuncInfo,
				panicErr,
			)
			return
		}

		var status int
		if v, ok := record.responseWriter.(types.ResponseStatusProvider); ok {
			status = v.WrittenStatus()
		}
		logf := logInfo
		// lower the log level for some specific requests, in most cases these logs are not useful
		if 0 < status && status < 400 &&
			strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
			req.RequestURI == "/user/events" /* Server-Sent Events (SSE) handler */ ||
			req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" /* Actions Runner polling */ {
			logf = logTrace
		}
		message := completedMessage
		if isUnknownHandler {
			logf = logError
			message = unknownHandlerMessage
		}

		logf("router: %s %v %s for %s, %v %v in %v @ %s",
			message,
			log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
			log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(record.startTime)),
			handlerFuncInfo,
		)
	}
}


Review comment (suggestion):
```suggestion
		if status > 0 && status < 400 &&
```
reads more intuitively to me

Code after:
				message,
				log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
				log.ColoredTime(time.Since(record.startTime)),
				handlerFuncInfo,
			)
			return
		}

		if panicErr != nil {
			logWarn("router: %s %v %s for %s, panic in %v @ %s, err=%v",
				failedMessage,
				log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
				log.ColoredTime(time.Since(record.startTime)),
				handlerFuncInfo,
				panicErr,
			)
			return
		}

		var status int
		if v, ok := record.responseWriter.(types.ResponseStatusProvider); ok {
			status = v.WrittenStatus()
		}
		logf := logInfo
		// lower the log level for some specific requests, in most cases these logs are not useful
		if status > 0 && status < 400 &&
			strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
			req.RequestURI == "/user/events" /* Server-Sent Events (SSE) handler */ ||
			req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" /* Actions Runner polling */ {
			logf = logTrace
		}
		message := completedMessage
		if isUnknownHandler {
			logf = logError
			message = unknownHandlerMessage
		}

		logf("router: %s %v %s for %s, %v %v in %v @ %s",
			message,
			log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
			log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(record.startTime)),
			handlerFuncInfo,
		)
	}
}
Repository: go-gitea/gitea
File: models/git/lfs_lock.go (Go)

Code before:
	return nil
}

// LoadOwner loads owner of the lock.
func (l *LFSLock) LoadOwner(ctx context.Context) error {
	if l.Owner != nil {
		return nil
	}

	owner, err := user_model.GetUserByID(ctx, l.OwnerID)
	if err != nil {
		if user_model.IsErrUserNotExist(err) {
			l.Owner = user_model.NewGhostUser()
			return nil
		}
		return err
	}
	l.Owner = owner

	return nil
}

// CreateLFSLock creates a new lock.
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock, taskID int64) (*LFSLock, error) {
	return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
		if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite, taskID); err != nil {
			return nil, err
		}

		lock.Path = util.PathJoinRel(lock.Path)
		lock.RepoID = repo.ID

		l, err := GetLFSLock(ctx, repo, lock.Path)
		if err == nil {
			return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path}
		}
		if !IsErrLFSLockNotExist(err) {
			return nil, err
		}

		if err := db.Insert(ctx, lock); err != nil {
			return nil, err
		}

		return lock, nil
	})
}

// GetLFSLock returns release by given path.
func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) {
	path = util.PathJoinRel(path)

Review comment (security):
Here "CreateLFSLock" and "DeleteLFSLock".

I think it is unnecessary to do permission check in "model" functions (and it shouldn't, it causes unnecessary logic dependencies). Just like other "CreateXxx" functions of other models, they just create, don't check.

If I read correctly, when calling `CreateLFSLock`, the doer already has "write" permission.

Code after:
// LoadOwner loads owner of the lock.
func (l *LFSLock) LoadOwner(ctx context.Context) error {
	if l.Owner != nil {
		return nil
	}

	owner, err := user_model.GetUserByID(ctx, l.OwnerID)
	if err != nil {
		if user_model.IsErrUserNotExist(err) {
			l.Owner = user_model.NewGhostUser()
			return nil
		}
		return err
	}
	l.Owner = owner

	return nil
}

// CreateLFSLock creates a new lock.
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) {
	return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
		lock.Path = util.PathJoinRel(lock.Path)
		lock.RepoID = repo.ID

		l, err := GetLFSLock(ctx, repo, lock.Path)
		if err == nil {
			return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path}
		}
		if !IsErrLFSLockNotExist(err) {
			return nil, err
		}

		if err := db.Insert(ctx, lock); err != nil {
			return nil, err
		}

		return lock, nil
	})
}

// GetLFSLock returns release by given path.
func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) {
	path = util.PathJoinRel(path)
	rel := &LFSLock{RepoID: repo.ID}
	has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, ErrLFSLockNotExist{0, repo.ID, path}