529 lines · 14.7 KB · Go
RawDownload
// Copyright 2016 The Gogs Authors. All rights reserved.// Copyright 2020 The Gitea Authors.// SPDX-License-Identifier: MIT package issues import (	"context"	"fmt"	"slices"	"strconv"	"strings" 	"forgejo.org/models/db"	"forgejo.org/modules/label"	"forgejo.org/modules/optional"	"forgejo.org/modules/timeutil"	"forgejo.org/modules/util" 	"xorm.io/builder") // ErrRepoLabelNotExist represents a "RepoLabelNotExist" kind of error.type ErrRepoLabelNotExist struct {	LabelID int64	RepoID  int64} // IsErrRepoLabelNotExist checks if an error is a RepoErrLabelNotExist.func IsErrRepoLabelNotExist(err error) bool {	_, ok := err.(ErrRepoLabelNotExist)	return ok} func (err ErrRepoLabelNotExist) Error() string {	return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID)} func (err ErrRepoLabelNotExist) Unwrap() error {	return util.ErrNotExist} // ErrOrgLabelNotExist represents a "OrgLabelNotExist" kind of error.type ErrOrgLabelNotExist struct {	LabelID int64	OrgID   int64} // IsErrOrgLabelNotExist checks if an error is a OrgErrLabelNotExist.func IsErrOrgLabelNotExist(err error) bool {	_, ok := err.(ErrOrgLabelNotExist)	return ok} func (err ErrOrgLabelNotExist) Error() string {	return fmt.Sprintf("label does not exist [label_id: %d, org_id: %d]", err.LabelID, err.OrgID)} func (err ErrOrgLabelNotExist) Unwrap() error {	return util.ErrNotExist} // ErrLabelNotExist represents a "LabelNotExist" kind of error.type ErrLabelNotExist struct {	LabelID int64} // IsErrLabelNotExist checks if an error is a ErrLabelNotExist.func IsErrLabelNotExist(err error) bool {	_, ok := err.(ErrLabelNotExist)	return ok} func (err ErrLabelNotExist) Error() string {	return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)} func (err ErrLabelNotExist) Unwrap() error {	return util.ErrNotExist} // Label represents a label of repository for issues.type Label struct {	ID              int64 `xorm:"pk autoincr"`	RepoID          int64 `xorm:"INDEX"`	OrgID           int64 `xorm:"INDEX"`	Name            string	Exclusive       bool	Description     string	Color           string `xorm:"VARCHAR(7)"`	NumIssues       int	NumClosedIssues int	CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"`	UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"` 	NumOpenIssues     int    `xorm:"-"`	NumOpenRepoIssues int64  `xorm:"-"`	IsChecked         bool   `xorm:"-"`	QueryString       string `xorm:"-"`	IsSelected        bool   `xorm:"-"`	IsExcluded        bool   `xorm:"-"` 	ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"`} func init() {	db.RegisterModel(new(Label))	db.RegisterModel(new(IssueLabel))} // CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.func (l *Label) CalOpenIssues() {	l.NumOpenIssues = l.NumIssues - l.NumClosedIssues} // SetArchived set the label as archivedfunc (l *Label) SetArchived(isArchived bool) {	if !isArchived {		l.ArchivedUnix = timeutil.TimeStamp(0)	} else if isArchived && !l.IsArchived() {		// Only change the date when it is newly archived.		l.ArchivedUnix = timeutil.TimeStampNow()	}} // IsArchived returns true if label is an archivedfunc (l *Label) IsArchived() bool {	return !l.ArchivedUnix.IsZero()} // CalOpenOrgIssues calculates the open issues of a label for a specific repofunc (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {	counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{		RepoIDs:  []int64{repoID},		LabelIDs: []int64{labelID},		IsClosed: optional.Some(false),	}) 	for _, count := range counts {		l.NumOpenRepoIssues += count	}} // LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clickedfunc (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {	labelQuerySlice := []int64{}	labelSelected := false	exclusiveScope := l.ExclusiveScope()	for i, curSel := range currentSelectedLabels {		if curSel == l.ID {			labelSelected = true		} else if -curSel == l.ID {			labelSelected = true			l.IsExcluded = true		} else if curSel != 0 {			// Exclude other labels in the same scope from selection			if curSel < 0 || exclusiveScope == "" || exclusiveScope != currentSelectedExclusiveScopes[i] {				labelQuerySlice = append(labelQuerySlice, curSel)			}		}	} 	if !labelSelected {		labelQuerySlice = append(labelQuerySlice, l.ID)	}	l.IsSelected = labelSelected 	// Sort and deduplicate the ids to avoid the crawlers asking for the	// same thing with simply a different order of parameters	slices.Sort(labelQuerySlice)	labelQuerySlice = slices.Compact(labelQuerySlice)	// Quick conversion (strings.Join() doesn't accept slices of Int64)	labelQuerySliceStrings := make([]string, len(labelQuerySlice))	for i, x := range labelQuerySlice {		labelQuerySliceStrings[i] = strconv.FormatInt(x, 10)	}	l.QueryString = strings.Join(labelQuerySliceStrings, ",")} // BelongsToOrg returns true if label is an organization labelfunc (l *Label) BelongsToOrg() bool {	return l.OrgID > 0} // BelongsToRepo returns true if label is a repository labelfunc (l *Label) BelongsToRepo() bool {	return l.RepoID > 0} // ExclusiveScope returns scope substring of label name, or empty string if none existsfunc (l *Label) ExclusiveScope() string {	if !l.Exclusive {		return ""	}	lastIndex := strings.LastIndex(l.Name, "/")	if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 {		return ""	}	return l.Name[:lastIndex]} // NewLabel creates a new labelfunc NewLabel(ctx context.Context, l *Label) error {	color, err := label.NormalizeColor(l.Color)	if err != nil {		return err	}	l.Color = color 	return db.Insert(ctx, l)} // NewLabels creates new labelsfunc NewLabels(ctx context.Context, labels ...*Label) error {	ctx, committer, err := db.TxContext(ctx)	if err != nil {		return err	}	defer committer.Close() 	for _, l := range labels {		color, err := label.NormalizeColor(l.Color)		if err != nil {			return err		}		l.Color = color 		if err := db.Insert(ctx, l); err != nil {			return err		}	}	return committer.Commit()} // UpdateLabel updates label information.func UpdateLabel(ctx context.Context, l *Label) error {	color, err := label.NormalizeColor(l.Color)	if err != nil {		return err	}	l.Color = color 	return updateLabelCols(ctx, l, "name", "description", "color", "exclusive", "archived_unix")} // DeleteLabel delete a labelfunc DeleteLabel(ctx context.Context, id, labelID int64) error {	l, err := GetLabelByID(ctx, labelID)	if err != nil {		if IsErrLabelNotExist(err) {			return nil		}		return err	} 	ctx, committer, err := db.TxContext(ctx)	if err != nil {		return err	}	defer committer.Close() 	sess := db.GetEngine(ctx) 	if l.BelongsToOrg() && l.OrgID != id {		return nil	}	if l.BelongsToRepo() && l.RepoID != id {		return nil	} 	if _, err = db.DeleteByID[Label](ctx, labelID); err != nil {		return err	} else if _, err = sess.		Where("label_id = ?", labelID).		Delete(new(IssueLabel)); err != nil {		return err	} 	// delete comments about now deleted label_id	if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}); err != nil {		return err	} 	return committer.Commit()} // GetLabelByID returns a label by given ID.func GetLabelByID(ctx context.Context, labelID int64) (*Label, error) {	if labelID <= 0 {		return nil, ErrLabelNotExist{labelID}	} 	l := &Label{}	has, err := db.GetEngine(ctx).ID(labelID).Get(l)	if err != nil {		return nil, err	} else if !has {		return nil, ErrLabelNotExist{l.ID}	}	return l, nil} // GetLabelsByIDs returns a list of labels by IDsfunc GetLabelsByIDs(ctx context.Context, labelIDs []int64, cols ...string) ([]*Label, error) {	labels := make([]*Label, 0, len(labelIDs))	if len(labelIDs) == 0 {		return labels, nil	}	return labels, db.GetEngine(ctx).Table("label").		In("id", labelIDs).		Asc("name").		Cols(cols...).		Find(&labels)} // GetLabelInRepoByName returns a label by name in given repository.func GetLabelInRepoByName(ctx context.Context, repoID int64, labelName string) (*Label, error) {	if len(labelName) == 0 || repoID <= 0 {		return nil, ErrRepoLabelNotExist{0, repoID}	} 	l, exist, err := db.Get[Label](ctx, builder.Eq{"name": labelName, "repo_id": repoID})	if err != nil {		return nil, err	} else if !exist {		return nil, ErrRepoLabelNotExist{0, repoID}	}	return l, nil} // GetLabelInRepoByID returns a label by ID in given repository.func GetLabelInRepoByID(ctx context.Context, repoID, labelID int64) (*Label, error) {	if labelID <= 0 || repoID <= 0 {		return nil, ErrRepoLabelNotExist{labelID, repoID}	} 	l, exist, err := db.Get[Label](ctx, builder.Eq{"id": labelID, "repo_id": repoID})	if err != nil {		return nil, err	} else if !exist {		return nil, ErrRepoLabelNotExist{labelID, repoID}	}	return l, nil} // GetLabelIDsInRepoByNames returns a list of labelIDs by names in a given// repository.// it silently ignores label names that do not belong to the repository.func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []string) ([]int64, error) {	labelIDs := make([]int64, 0, len(labelNames))	return labelIDs, db.GetEngine(ctx).Table("label").		Where("repo_id = ?", repoID).		In("name", labelNames).		Asc("name").		Cols("id").		Find(&labelIDs)} // GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org.func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {	labelIDs := make([]int64, 0, len(labelNames))	return labelIDs, db.GetEngine(ctx).Table("label").		Where("org_id = ?", orgID).		In("name", labelNames).		Asc("name").		Cols("id").		Find(&labelIDs)} // BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label namesfunc BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {	return builder.Select("issue_label.issue_id").		From("issue_label").		InnerJoin("label", "label.id = issue_label.label_id").		Where(			builder.In("label.name", labelNames),		).		GroupBy("issue_label.issue_id")} // GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,// it silently ignores label IDs that do not belong to the repository.func GetLabelsInRepoByIDs(ctx context.Context, repoID int64, labelIDs []int64) ([]*Label, error) {	labels := make([]*Label, 0, len(labelIDs))	if len(labelIDs) == 0 {		return labels, nil	}	return labels, db.GetEngine(ctx).		Where("repo_id = ?", repoID).		In("id", labelIDs).		Asc("name").		Find(&labels)} // GetLabelsByRepoID returns all labels that belong to given repository by ID.func GetLabelsByRepoID(ctx context.Context, repoID int64, sortType string, listOptions db.ListOptions) ([]*Label, error) {	if repoID <= 0 {		return nil, ErrRepoLabelNotExist{0, repoID}	}	labels := make([]*Label, 0, 10)	sess := db.GetEngine(ctx).Where("repo_id = ?", repoID) 	switch sortType {	case "reversealphabetically":		sess.Desc("name")	case "leastissues":		sess.Asc("num_issues")	case "mostissues":		sess.Desc("num_issues")	default:		sess.Asc("name")	} 	if listOptions.Page > 0 {		sess = db.SetSessionPagination(sess, &listOptions)	} 	return labels, sess.Find(&labels)} // CountLabelsByRepoID count number of all labels that belong to given repository by ID.func CountLabelsByRepoID(ctx context.Context, repoID int64) (int64, error) {	return db.GetEngine(ctx).Where("repo_id = ?", repoID).Count(&Label{})} // GetLabelInOrgByName returns a label by name in given organization.func GetLabelInOrgByName(ctx context.Context, orgID int64, labelName string) (*Label, error) {	if len(labelName) == 0 || orgID <= 0 {		return nil, ErrOrgLabelNotExist{0, orgID}	} 	l, exist, err := db.Get[Label](ctx, builder.Eq{"name": labelName, "org_id": orgID})	if err != nil {		return nil, err	} else if !exist {		return nil, ErrOrgLabelNotExist{0, orgID}	}	return l, nil} // GetLabelInOrgByID returns a label by ID in given organization.func GetLabelInOrgByID(ctx context.Context, orgID, labelID int64) (*Label, error) {	if labelID <= 0 || orgID <= 0 {		return nil, ErrOrgLabelNotExist{labelID, orgID}	} 	l, exist, err := db.Get[Label](ctx, builder.Eq{"id": labelID, "org_id": orgID})	if err != nil {		return nil, err	} else if !exist {		return nil, ErrOrgLabelNotExist{labelID, orgID}	}	return l, nil} // GetLabelsInOrgByIDs returns a list of labels by IDs in given organization,// it silently ignores label IDs that do not belong to the organization.func GetLabelsInOrgByIDs(ctx context.Context, orgID int64, labelIDs []int64) ([]*Label, error) {	labels := make([]*Label, 0, len(labelIDs))	if len(labelIDs) == 0 {		return labels, nil	}	return labels, db.GetEngine(ctx).		Where("org_id = ?", orgID).		In("id", labelIDs).		Asc("name").		Find(&labels)} // GetLabelsByOrgID returns all labels that belong to given organization by ID.func GetLabelsByOrgID(ctx context.Context, orgID int64, sortType string, listOptions db.ListOptions) ([]*Label, error) {	if orgID <= 0 {		return nil, ErrOrgLabelNotExist{0, orgID}	}	labels := make([]*Label, 0, 10)	sess := db.GetEngine(ctx).Where("org_id = ?", orgID) 	switch sortType {	case "reversealphabetically":		sess.Desc("name")	case "leastissues":		sess.Asc("num_issues")	case "mostissues":		sess.Desc("num_issues")	default:		sess.Asc("name")	} 	if listOptions.Page > 0 {		sess = db.SetSessionPagination(sess, &listOptions)	} 	return labels, sess.Find(&labels)} // GetLabelIDsByNames returns a list of labelIDs by names.// It doesn't filter them by repo or org, so it could return labels belonging to different repos/orgs.// It's used for filtering issues via indexer, otherwise it would be useless.// Since it could return labels with the same name, so the length of returned ids could be more than the length of names.func GetLabelIDsByNames(ctx context.Context, labelNames []string) ([]int64, error) {

Lines 1–500 of 529.