diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml
index ab4d5ef944..2242b90dcd 100644
--- a/models/fixtures/label.yml
+++ b/models/fixtures/label.yml
@@ -7,6 +7,7 @@
exclusive: false
num_issues: 2
num_closed_issues: 0
+ archived_unix: 0
-
id: 2
@@ -17,6 +18,7 @@
exclusive: false
num_issues: 1
num_closed_issues: 1
+ archived_unix: 0
-
id: 3
@@ -27,6 +29,7 @@
exclusive: false
num_issues: 0
num_closed_issues: 0
+ archived_unix: 0
-
id: 4
@@ -37,6 +40,7 @@
exclusive: false
num_issues: 1
num_closed_issues: 0
+ archived_unix: 0
-
id: 5
@@ -47,6 +51,7 @@
exclusive: false
num_issues: 0
num_closed_issues: 0
+ archived_unix: 0
-
id: 6
@@ -57,6 +62,7 @@
exclusive: false
num_issues: 0
num_closed_issues: 0
+ archived_unix: 0
-
id: 7
@@ -67,6 +73,7 @@
exclusive: true
num_issues: 0
num_closed_issues: 0
+ archived_unix: 0
-
id: 8
@@ -77,6 +84,7 @@
exclusive: true
num_issues: 0
num_closed_issues: 0
+ archived_unix: 0
-
id: 9
@@ -87,3 +95,4 @@
exclusive: true
num_issues: 0
num_closed_issues: 0
+ archived_unix: 0
diff --git a/models/issues/label.go b/models/issues/label.go
index 57a2e67f8c..70906efb47 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -97,6 +97,8 @@ type Label struct {
QueryString string `xorm:"-"`
IsSelected bool `xorm:"-"`
IsExcluded bool `xorm:"-"`
+
+ ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"`
}
func init() {
@@ -109,6 +111,15 @@ func (l *Label) CalOpenIssues() {
l.NumOpenIssues = l.NumIssues - l.NumClosedIssues
}
+// SetArchived set the label as archived
+func (l *Label) SetArchived(isArchived bool) {
+ if isArchived && l.ArchivedUnix.IsZero() {
+ l.ArchivedUnix = timeutil.TimeStampNow()
+ } else {
+ l.ArchivedUnix = timeutil.TimeStamp(0)
+ }
+}
+
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
@@ -153,6 +164,11 @@ func (l *Label) BelongsToOrg() bool {
return l.OrgID > 0
}
+// IsArchived returns true if label is an archived
+func (l *Label) IsArchived() bool {
+ return l.ArchivedUnix > 0
+}
+
// BelongsToRepo returns true if label is a repository label
func (l *Label) BelongsToRepo() bool {
return l.RepoID > 0
@@ -211,7 +227,7 @@ func UpdateLabel(l *Label) error {
}
l.Color = color
- return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
+ return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive", "archived_unix")
}
// DeleteLabel delete a label
diff --git a/models/issues/label_test.go b/models/issues/label_test.go
index 1bc5a1a935..3f0e980b31 100644
--- a/models/issues/label_test.go
+++ b/models/issues/label_test.go
@@ -11,6 +11,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
@@ -259,11 +260,12 @@ func TestUpdateLabel(t *testing.T) {
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
// make sure update wont overwrite it
update := &issues_model.Label{
- ID: label.ID,
- Color: "#ffff00",
- Name: "newLabelName",
- Description: label.Description,
- Exclusive: false,
+ ID: label.ID,
+ Color: "#ffff00",
+ Name: "newLabelName",
+ Description: label.Description,
+ Exclusive: false,
+ ArchivedUnix: timeutil.TimeStamp(0),
}
label.Color = update.Color
label.Name = update.Name
@@ -273,6 +275,7 @@ func TestUpdateLabel(t *testing.T) {
assert.EqualValues(t, label.Color, newLabel.Color)
assert.EqualValues(t, label.Name, newLabel.Name)
assert.EqualValues(t, label.Description, newLabel.Description)
+ assert.EqualValues(t, newLabel.ArchivedUnix, 0)
unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 55107439b0..7a126593d1 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -522,6 +522,8 @@ var migrations = []Migration{
NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable),
// v270 -> v271
NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo),
+ // v271 -> v272
+ NewMigration("Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go
new file mode 100644
index 0000000000..098f6499d5
--- /dev/null
+++ b/models/migrations/v1_21/v271.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+import (
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+func AddArchivedUnixColumInLabelTable(x *xorm.Engine) error {
+ type Label struct {
+ ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"`
+ }
+ return x.Sync(new(Label))
+}
diff --git a/modules/structs/issue_label.go b/modules/structs/issue_label.go
index 2610d3e93f..bf68726d79 100644
--- a/modules/structs/issue_label.go
+++ b/modules/structs/issue_label.go
@@ -11,6 +11,8 @@ type Label struct {
Name string `json:"name"`
// example: false
Exclusive bool `json:"exclusive"`
+ // example: false
+ IsArchived bool `json:"is_archived"`
// example: 00aabb
Color string `json:"color"`
Description string `json:"description"`
@@ -27,6 +29,8 @@ type CreateLabelOption struct {
// example: #00aabb
Color string `json:"color" binding:"Required"`
Description string `json:"description"`
+ // example: false
+ IsArchived bool `json:"is_archived"`
}
// EditLabelOption options for editing a label
@@ -37,6 +41,8 @@ type EditLabelOption struct {
// example: #00aabb
Color *string `json:"color"`
Description *string `json:"description"`
+ // example: false
+ IsArchived *bool `json:"is_archived"`
}
// IssueLabelsOption a collection of labels
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 30fa899c9d..daf22d9fea 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1491,6 +1491,8 @@ issues.label_title = Name
issues.label_description = Description
issues.label_color = Color
issues.label_exclusive = Exclusive
+issues.label_archive = Archive Label
+issues.label_archive_tooltip= Archived labels are excluded from the label search when applying labels to an issue. Existing labels on issues remain unaffected, allowing you to retire obsolete labels without losing information.
issues.label_exclusive_desc = Name the label scope/item
to make it mutually exclusive with other scope/
labels.
issues.label_exclusive_warning = Any conflicting scoped labels will be removed when editing the labels of an issue or pull request.
issues.label_count = %d labels
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 183c1e6cc8..9ef28d4db9 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -209,6 +209,7 @@ func EditLabel(ctx *context.APIContext) {
if form.Description != nil {
l.Description = *form.Description
}
+ l.SetArchived(form.IsArchived != nil && *form.IsArchived)
if err := issues_model.UpdateLabel(l); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
index 6cb231f596..fc9a16b58a 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -151,7 +151,6 @@ func CreateLabel(ctx *context.APIContext) {
return
}
form.Color = color
-
l := &issues_model.Label{
Name: form.Name,
Exclusive: form.Exclusive,
@@ -159,6 +158,7 @@ func CreateLabel(ctx *context.APIContext) {
RepoID: ctx.Repo.Repository.ID,
Description: form.Description,
}
+ l.SetArchived(form.IsArchived)
if err := issues_model.NewLabel(ctx, l); err != nil {
ctx.Error(http.StatusInternalServerError, "NewLabel", err)
return
@@ -231,6 +231,7 @@ func EditLabel(ctx *context.APIContext) {
if form.Description != nil {
l.Description = *form.Description
}
+ l.SetArchived(form.IsArchived != nil && *form.IsArchived)
if err := issues_model.UpdateLabel(l); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return
diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go
index a9f9e963d4..2c7725e38d 100644
--- a/routers/web/org/org_labels.go
+++ b/routers/web/org/org_labels.go
@@ -75,6 +75,7 @@ func UpdateLabel(ctx *context.Context) {
l.Exclusive = form.Exclusive
l.Description = form.Description
l.Color = form.Color
+ l.SetArchived(form.IsArchived)
if err := issues_model.UpdateLabel(l); err != nil {
ctx.ServerError("UpdateLabel", err)
return
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index 5d326bab58..257610d3af 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
@@ -111,11 +112,12 @@ func NewLabel(ctx *context.Context) {
}
l := &issues_model.Label{
- RepoID: ctx.Repo.Repository.ID,
- Name: form.Title,
- Exclusive: form.Exclusive,
- Description: form.Description,
- Color: form.Color,
+ RepoID: ctx.Repo.Repository.ID,
+ Name: form.Title,
+ Exclusive: form.Exclusive,
+ Description: form.Description,
+ Color: form.Color,
+ ArchivedUnix: timeutil.TimeStamp(0),
}
if err := issues_model.NewLabel(ctx, l); err != nil {
ctx.ServerError("NewLabel", err)
@@ -137,11 +139,12 @@ func UpdateLabel(ctx *context.Context) {
}
return
}
-
l.Name = form.Title
l.Exclusive = form.Exclusive
l.Description = form.Description
l.Color = form.Color
+
+ l.SetArchived(form.IsArchived)
if err := issues_model.UpdateLabel(l); err != nil {
ctx.ServerError("UpdateLabel", err)
return
diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go
index 4c9a359438..e29582f968 100644
--- a/routers/web/repo/issue_label_test.go
+++ b/routers/web/repo/issue_label_test.go
@@ -97,9 +97,10 @@ func TestUpdateLabel(t *testing.T) {
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
web.SetForm(ctx, &forms.CreateLabelForm{
- ID: 2,
- Title: "newnameforlabel",
- Color: "#abcdef",
+ ID: 2,
+ Title: "newnameforlabel",
+ Color: "#abcdef",
+ IsArchived: true,
})
UpdateLabel(ctx)
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
diff --git a/services/convert/issue.go b/services/convert/issue.go
index d81840f025..33fad31d48 100644
--- a/services/convert/issue.go
+++ b/services/convert/issue.go
@@ -208,6 +208,7 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
Exclusive: label.Exclusive,
Color: strings.TrimLeft(label.Color, "#"),
Description: label.Description,
+ IsArchived: label.IsArchived(),
}
// calculate URL
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 8c763e17cb..b36c8cc9b6 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -569,6 +569,7 @@ type CreateLabelForm struct {
ID int64
Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
Exclusive bool `form:"exclusive"`
+ IsArchived bool `form:"is_archived"`
Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"`
}
diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl
index b4eb6be7fc..d64782090c 100644
--- a/templates/repo/issue/labels/edit_delete_label.tmpl
+++ b/templates/repo/issue/labels/edit_delete_label.tmpl
@@ -33,6 +33,16 @@