Compare commits

..

No commits in common. "5d5f907e7fcf1dee4eaca205f8e43f50e5884a58" and "ff7057a46dfc3e1db83e26ea770246a7e253f450" have entirely different histories.

17 changed files with 244 additions and 497 deletions

View File

@ -7,12 +7,12 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -78,6 +78,9 @@ func (err ErrLabelNotExist) Unwrap() error {
return util.ErrNotExist return util.ErrNotExist
} }
// LabelColorPattern is a regexp witch can validate LabelColor
var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
// Label represents a label of repository for issues. // Label represents a label of repository for issues.
type Label struct { type Label struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -106,12 +109,12 @@ func init() {
} }
// CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues. // CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.
func (l *Label) CalOpenIssues() { func (label *Label) CalOpenIssues() {
l.NumOpenIssues = l.NumIssues - l.NumClosedIssues label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
} }
// CalOpenOrgIssues calculates the open issues of a label for a specific repo // CalOpenOrgIssues calculates the open issues of a label for a specific repo
func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) { func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{ counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
RepoID: repoID, RepoID: repoID,
LabelIDs: []int64{labelID}, LabelIDs: []int64{labelID},
@ -119,22 +122,22 @@ func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
}) })
for _, count := range counts { for _, count := range counts {
l.NumOpenRepoIssues += count label.NumOpenRepoIssues += count
} }
} }
// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked // LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
func (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) { func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
var labelQuerySlice []string var labelQuerySlice []string
labelSelected := false labelSelected := false
labelID := strconv.FormatInt(l.ID, 10) labelID := strconv.FormatInt(label.ID, 10)
labelScope := l.ExclusiveScope() labelScope := label.ExclusiveScope()
for i, s := range currentSelectedLabels { for i, s := range currentSelectedLabels {
if s == l.ID { if s == label.ID {
labelSelected = true labelSelected = true
} else if -s == l.ID { } else if -s == label.ID {
labelSelected = true labelSelected = true
l.IsExcluded = true label.IsExcluded = true
} else if s != 0 { } else if s != 0 {
// Exclude other labels in the same scope from selection // Exclude other labels in the same scope from selection
if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] { if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] {
@ -145,23 +148,23 @@ func (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, curr
if !labelSelected { if !labelSelected {
labelQuerySlice = append(labelQuerySlice, labelID) labelQuerySlice = append(labelQuerySlice, labelID)
} }
l.IsSelected = labelSelected label.IsSelected = labelSelected
l.QueryString = strings.Join(labelQuerySlice, ",") label.QueryString = strings.Join(labelQuerySlice, ",")
} }
// BelongsToOrg returns true if label is an organization label // BelongsToOrg returns true if label is an organization label
func (l *Label) BelongsToOrg() bool { func (label *Label) BelongsToOrg() bool {
return l.OrgID > 0 return label.OrgID > 0
} }
// BelongsToRepo returns true if label is a repository label // BelongsToRepo returns true if label is a repository label
func (l *Label) BelongsToRepo() bool { func (label *Label) BelongsToRepo() bool {
return l.RepoID > 0 return label.RepoID > 0
} }
// Get color as RGB values in 0..255 range // Get color as RGB values in 0..255 range
func (l *Label) ColorRGB() (float64, float64, float64, error) { func (label *Label) ColorRGB() (float64, float64, float64, error) {
color, err := strconv.ParseUint(l.Color[1:], 16, 64) color, err := strconv.ParseUint(label.Color[1:], 16, 64)
if err != nil { if err != nil {
return 0, 0, 0, err return 0, 0, 0, err
} }
@ -173,9 +176,9 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) {
} }
// Determine if label text should be light or dark to be readable on background color // Determine if label text should be light or dark to be readable on background color
func (l *Label) UseLightTextColor() bool { func (label *Label) UseLightTextColor() bool {
if strings.HasPrefix(l.Color, "#") { if strings.HasPrefix(label.Color, "#") {
if r, g, b, err := l.ColorRGB(); err == nil { if r, g, b, err := label.ColorRGB(); err == nil {
// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast // Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
// In the future WCAG 3 APCA may be a better solution // In the future WCAG 3 APCA may be a better solution
brightness := (0.299*r + 0.587*g + 0.114*b) / 255 brightness := (0.299*r + 0.587*g + 0.114*b) / 255
@ -187,26 +190,40 @@ func (l *Label) UseLightTextColor() bool {
} }
// Return scope substring of label name, or empty string if none exists // Return scope substring of label name, or empty string if none exists
func (l *Label) ExclusiveScope() string { func (label *Label) ExclusiveScope() string {
if !l.Exclusive { if !label.Exclusive {
return "" return ""
} }
lastIndex := strings.LastIndex(l.Name, "/") lastIndex := strings.LastIndex(label.Name, "/")
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 { if lastIndex == -1 || lastIndex == 0 || lastIndex == len(label.Name)-1 {
return "" return ""
} }
return l.Name[:lastIndex] return label.Name[:lastIndex]
} }
// NewLabel creates a new label // NewLabel creates a new label
func NewLabel(ctx context.Context, l *Label) error { func NewLabel(ctx context.Context, label *Label) error {
color, err := label.NormalizeColor(l.Color) if !LabelColorPattern.MatchString(label.Color) {
if err != nil { return fmt.Errorf("bad color code: %s", label.Color)
return err
} }
l.Color = color
return db.Insert(ctx, l) // normalize case
label.Color = strings.ToLower(label.Color)
// add leading hash
if label.Color[0] != '#' {
label.Color = "#" + label.Color
}
// convert 3-character shorthand into 6-character version
if len(label.Color) == 4 {
r := label.Color[1]
g := label.Color[2]
b := label.Color[3]
label.Color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
}
return db.Insert(ctx, label)
} }
// NewLabels creates new labels // NewLabels creates new labels
@ -217,14 +234,11 @@ func NewLabels(labels ...*Label) error {
} }
defer committer.Close() defer committer.Close()
for _, l := range labels { for _, label := range labels {
color, err := label.NormalizeColor(l.Color) if !LabelColorPattern.MatchString(label.Color) {
if err != nil { return fmt.Errorf("bad color code: %s", label.Color)
return err
} }
l.Color = color if err := db.Insert(ctx, label); err != nil {
if err := db.Insert(ctx, l); err != nil {
return err return err
} }
} }
@ -233,18 +247,15 @@ func NewLabels(labels ...*Label) error {
// UpdateLabel updates label information. // UpdateLabel updates label information.
func UpdateLabel(l *Label) error { func UpdateLabel(l *Label) error {
color, err := label.NormalizeColor(l.Color) if !LabelColorPattern.MatchString(l.Color) {
if err != nil { return fmt.Errorf("bad color code: %s", l.Color)
return err
} }
l.Color = color
return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive") return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
} }
// DeleteLabel delete a label // DeleteLabel delete a label
func DeleteLabel(id, labelID int64) error { func DeleteLabel(id, labelID int64) error {
l, err := GetLabelByID(db.DefaultContext, labelID) label, err := GetLabelByID(db.DefaultContext, labelID)
if err != nil { if err != nil {
if IsErrLabelNotExist(err) { if IsErrLabelNotExist(err) {
return nil return nil
@ -260,10 +271,10 @@ func DeleteLabel(id, labelID int64) error {
sess := db.GetEngine(ctx) sess := db.GetEngine(ctx)
if l.BelongsToOrg() && l.OrgID != id { if label.BelongsToOrg() && label.OrgID != id {
return nil return nil
} }
if l.BelongsToRepo() && l.RepoID != id { if label.BelongsToRepo() && label.RepoID != id {
return nil return nil
} }
@ -671,14 +682,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
if err = issue.LoadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return err return err
} }
for _, l := range labels { for _, label := range labels {
// Don't add already present labels and invalid labels // Don't add already present labels and invalid labels
if HasIssueLabel(ctx, issue.ID, l.ID) || if HasIssueLabel(ctx, issue.ID, label.ID) ||
(l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) { (label.RepoID != issue.RepoID && label.OrgID != issue.Repo.OwnerID) {
continue continue
} }
if err = newIssueLabel(ctx, issue, l, doer); err != nil { if err = newIssueLabel(ctx, issue, label, doer); err != nil {
return fmt.Errorf("newIssueLabel: %w", err) return fmt.Errorf("newIssueLabel: %w", err)
} }
} }

View File

@ -15,6 +15,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// TODO TestGetLabelTemplateFile
func TestLabel_CalOpenIssues(t *testing.T) { func TestLabel_CalOpenIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})

View File

@ -1,46 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package label
import (
"fmt"
"regexp"
"strings"
)
// colorPattern is a regexp which can validate label color
var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
// Label represents label information loaded from template
type Label struct {
Name string `yaml:"name"`
Color string `yaml:"color"`
Description string `yaml:"description,omitempty"`
Exclusive bool `yaml:"exclusive,omitempty"`
}
// NormalizeColor normalizes a color string to a 6-character hex code
func NormalizeColor(color string) (string, error) {
// normalize case
color = strings.TrimSpace(strings.ToLower(color))
// add leading hash
if len(color) == 6 || len(color) == 3 {
color = "#" + color
}
if !colorPattern.MatchString(color) {
return "", fmt.Errorf("bad color code: %s", color)
}
// convert 3-character shorthand into 6-character version
if len(color) == 4 {
r := color[1]
g := color[2]
b := color[3]
color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
}
return color, nil
}

View File

@ -1,126 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package label
import (
"errors"
"fmt"
"strings"
"code.gitea.io/gitea/modules/options"
"gopkg.in/yaml.v3"
)
type labelFile struct {
Labels []*Label `yaml:"labels"`
}
// ErrTemplateLoad represents a "ErrTemplateLoad" kind of error.
type ErrTemplateLoad struct {
TemplateFile string
OriginalError error
}
// IsErrTemplateLoad checks if an error is a ErrTemplateLoad.
func IsErrTemplateLoad(err error) bool {
_, ok := err.(ErrTemplateLoad)
return ok
}
func (err ErrTemplateLoad) Error() string {
return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
}
// GetTemplateFile loads the label template file by given name,
// then parses and returns a list of name-color pairs and optionally description.
func GetTemplateFile(name string) ([]*Label, error) {
data, err := options.GetRepoInitFile("label", name+".yaml")
if err == nil && len(data) > 0 {
return parseYamlFormat(name+".yaml", data)
}
data, err = options.GetRepoInitFile("label", name+".yml")
if err == nil && len(data) > 0 {
return parseYamlFormat(name+".yml", data)
}
data, err = options.GetRepoInitFile("label", name)
if err != nil {
return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
}
return parseLegacyFormat(name, data)
}
func parseYamlFormat(name string, data []byte) ([]*Label, error) {
lf := &labelFile{}
if err := yaml.Unmarshal(data, lf); err != nil {
return nil, err
}
// Validate label data and fix colors
for _, l := range lf.Labels {
l.Color = strings.TrimSpace(l.Color)
if len(l.Name) == 0 || len(l.Color) == 0 {
return nil, ErrTemplateLoad{name, errors.New("label name and color are required fields")}
}
color, err := NormalizeColor(l.Color)
if err != nil {
return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)}
}
l.Color = color
}
return lf.Labels, nil
}
func parseLegacyFormat(name string, data []byte) ([]*Label, error) {
lines := strings.Split(string(data), "\n")
list := make([]*Label, 0, len(lines))
for i := 0; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
if len(line) == 0 {
continue
}
parts, description, _ := strings.Cut(line, ";")
color, name, ok := strings.Cut(parts, " ")
if !ok {
return nil, ErrTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
}
color, err := NormalizeColor(color)
if err != nil {
return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)}
}
list = append(list, &Label{
Name: strings.TrimSpace(name),
Color: color,
Description: strings.TrimSpace(description),
})
}
return list, nil
}
// LoadFormatted loads the labels' list of a template file as a string separated by comma
func LoadFormatted(name string) (string, error) {
var buf strings.Builder
list, err := GetTemplateFile(name)
if err != nil {
return "", err
}
for i := 0; i < len(list); i++ {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(list[i].Name)
}
return buf.String(), nil
}

View File

@ -1,72 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package label
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestYamlParser(t *testing.T) {
data := []byte(`labels:
- name: priority/low
exclusive: true
color: "#0000ee"
description: "Low priority"
- name: priority/medium
exclusive: true
color: "0e0"
description: "Medium priority"
- name: priority/high
exclusive: true
color: "#ee0000"
description: "High priority"
- name: type/bug
color: "#f00"
description: "Bug"`)
labels, err := parseYamlFormat("test", data)
require.NoError(t, err)
require.Len(t, labels, 4)
assert.Equal(t, "priority/low", labels[0].Name)
assert.True(t, labels[0].Exclusive)
assert.Equal(t, "#0000ee", labels[0].Color)
assert.Equal(t, "Low priority", labels[0].Description)
assert.Equal(t, "priority/medium", labels[1].Name)
assert.True(t, labels[1].Exclusive)
assert.Equal(t, "#00ee00", labels[1].Color)
assert.Equal(t, "Medium priority", labels[1].Description)
assert.Equal(t, "priority/high", labels[2].Name)
assert.True(t, labels[2].Exclusive)
assert.Equal(t, "#ee0000", labels[2].Color)
assert.Equal(t, "High priority", labels[2].Description)
assert.Equal(t, "type/bug", labels[3].Name)
assert.False(t, labels[3].Exclusive)
assert.Equal(t, "#ff0000", labels[3].Color)
assert.Equal(t, "Bug", labels[3].Description)
}
func TestLegacyParser(t *testing.T) {
data := []byte(`#ee0701 bug ; Something is not working
#cccccc duplicate ; This issue or pull request already exists
#84b6eb enhancement`)
labels, err := parseLegacyFormat("test", data)
require.NoError(t, err)
require.Len(t, labels, 3)
assert.Equal(t, "bug", labels[0].Name)
assert.False(t, labels[0].Exclusive)
assert.Equal(t, "#ee0701", labels[0].Color)
assert.Equal(t, "Something is not working", labels[0].Description)
assert.Equal(t, "duplicate", labels[1].Name)
assert.False(t, labels[1].Exclusive)
assert.Equal(t, "#cccccc", labels[1].Color)
assert.Equal(t, "This issue or pull request already exists", labels[1].Description)
assert.Equal(t, "enhancement", labels[2].Name)
assert.False(t, labels[2].Exclusive)
assert.Equal(t, "#84b6eb", labels[2].Color)
assert.Empty(t, labels[2].Description)
}

View File

@ -1,44 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package options
import (
"fmt"
"os"
"path"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// GetRepoInitFile returns repository init files
func GetRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName)
// Use custom file when available.
customPath := path.Join(setting.CustomPath, relPath)
isFile, err := util.IsFile(customPath)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
}
if isFile {
return os.ReadFile(customPath)
}
switch tp {
case "readme":
return Readme(cleanedName)
case "gitignore":
return Gitignore(cleanedName)
case "license":
return License(cleanedName)
case "label":
return Labels(cleanedName)
default:
return []byte{}, fmt.Errorf("Invalid init file type")
}
}

View File

@ -23,7 +23,6 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -190,7 +189,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
// Check if label template exist // Check if label template exist
if len(opts.IssueLabels) > 0 { if len(opts.IssueLabels) > 0 {
if _, err := label.GetTemplateFile(opts.IssueLabels); err != nil { if _, err := GetLabelTemplateFile(opts.IssueLabels); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -18,7 +18,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -41,6 +40,114 @@ var (
LabelTemplates map[string]string LabelTemplates map[string]string
) )
// ErrIssueLabelTemplateLoad represents a "ErrIssueLabelTemplateLoad" kind of error.
type ErrIssueLabelTemplateLoad struct {
TemplateFile string
OriginalError error
}
// IsErrIssueLabelTemplateLoad checks if an error is a ErrIssueLabelTemplateLoad.
func IsErrIssueLabelTemplateLoad(err error) bool {
_, ok := err.(ErrIssueLabelTemplateLoad)
return ok
}
func (err ErrIssueLabelTemplateLoad) Error() string {
return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
}
// GetRepoInitFile returns repository init files
func GetRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName)
// Use custom file when available.
customPath := path.Join(setting.CustomPath, relPath)
isFile, err := util.IsFile(customPath)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
}
if isFile {
return os.ReadFile(customPath)
}
switch tp {
case "readme":
return options.Readme(cleanedName)
case "gitignore":
return options.Gitignore(cleanedName)
case "license":
return options.License(cleanedName)
case "label":
return options.Labels(cleanedName)
default:
return []byte{}, fmt.Errorf("Invalid init file type")
}
}
// GetLabelTemplateFile loads the label template file by given name,
// then parses and returns a list of name-color pairs and optionally description.
func GetLabelTemplateFile(name string) ([][3]string, error) {
data, err := GetRepoInitFile("label", name)
if err != nil {
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
}
lines := strings.Split(string(data), "\n")
list := make([][3]string, 0, len(lines))
for i := 0; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
if len(line) == 0 {
continue
}
parts := strings.SplitN(line, ";", 2)
fields := strings.SplitN(parts[0], " ", 2)
if len(fields) != 2 {
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
}
color := strings.Trim(fields[0], " ")
if len(color) == 6 {
color = "#" + color
}
if !issues_model.LabelColorPattern.MatchString(color) {
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("bad HTML color code in line: %s", line)}
}
var description string
if len(parts) > 1 {
description = strings.TrimSpace(parts[1])
}
fields[1] = strings.TrimSpace(fields[1])
list = append(list, [3]string{fields[1], color, description})
}
return list, nil
}
func loadLabels(labelTemplate string) ([]string, error) {
list, err := GetLabelTemplateFile(labelTemplate)
if err != nil {
return nil, err
}
labels := make([]string, len(list))
for i := 0; i < len(list); i++ {
labels[i] = list[i][0]
}
return labels, nil
}
// LoadLabelsFormatted loads the labels' list of a template file as a string separated by comma
func LoadLabelsFormatted(labelTemplate string) (string, error) {
labels, err := loadLabels(labelTemplate)
return strings.Join(labels, ", "), err
}
// LoadRepoConfig loads the repository config // LoadRepoConfig loads the repository config
func LoadRepoConfig() { func LoadRepoConfig() {
// Load .gitignore and license files and readme templates. // Load .gitignore and license files and readme templates.
@ -51,14 +158,6 @@ func LoadRepoConfig() {
if err != nil { if err != nil {
log.Fatal("Failed to get %s files: %v", t, err) log.Fatal("Failed to get %s files: %v", t, err)
} }
if t == "label" {
for i, f := range files {
ext := strings.ToLower(filepath.Ext(f))
if ext == ".yaml" || ext == ".yml" {
files[i] = f[:len(f)-len(ext)]
}
}
}
customPath := path.Join(setting.CustomPath, "options", t) customPath := path.Join(setting.CustomPath, "options", t)
isDir, err := util.IsDir(customPath) isDir, err := util.IsDir(customPath)
if err != nil { if err != nil {
@ -91,7 +190,7 @@ func LoadRepoConfig() {
// Load label templates // Load label templates
LabelTemplates = make(map[string]string) LabelTemplates = make(map[string]string)
for _, templateFile := range LabelTemplatesFiles { for _, templateFile := range LabelTemplatesFiles {
labels, err := label.LoadFormatted(templateFile) labels, err := LoadLabelsFormatted(templateFile)
if err != nil { if err != nil {
log.Error("Failed to load labels: %v", err) log.Error("Failed to load labels: %v", err)
} }
@ -136,7 +235,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
} }
// README // README
data, err := options.GetRepoInitFile("readme", opts.Readme) data, err := GetRepoInitFile("readme", opts.Readme)
if err != nil { if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err) return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
} }
@ -164,7 +263,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
var buf bytes.Buffer var buf bytes.Buffer
names := strings.Split(opts.Gitignores, ",") names := strings.Split(opts.Gitignores, ",")
for _, name := range names { for _, name := range names {
data, err = options.GetRepoInitFile("gitignore", name) data, err = GetRepoInitFile("gitignore", name)
if err != nil { if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
} }
@ -182,7 +281,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
// LICENSE // LICENSE
if len(opts.License) > 0 { if len(opts.License) > 0 {
data, err = options.GetRepoInitFile("license", opts.License) data, err = GetRepoInitFile("license", opts.License)
if err != nil { if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err) return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err)
} }
@ -344,7 +443,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
// InitializeLabels adds a label set to a repository using a template // InitializeLabels adds a label set to a repository using a template
func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error {
list, err := label.GetTemplateFile(labelTemplate) list, err := GetLabelTemplateFile(labelTemplate)
if err != nil { if err != nil {
return err return err
} }
@ -352,10 +451,9 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg
labels := make([]*issues_model.Label, len(list)) labels := make([]*issues_model.Label, len(list))
for i := 0; i < len(list); i++ { for i := 0; i < len(list); i++ {
labels[i] = &issues_model.Label{ labels[i] = &issues_model.Label{
Name: list[i].Name, Name: list[i][0],
Exclusive: list[i].Exclusive, Description: list[i][2],
Description: list[i].Description, Color: list[i][1],
Color: list[i].Color,
} }
if isOrg { if isOrg {
labels[i].OrgID = id labels[i].OrgID = id

View File

@ -1,70 +0,0 @@
labels:
- name: "Kind/Bug"
color: ee0701
description: Something is not working
- name: "Kind/Feature"
color: 0288d1
description: New functionality
- name: "Kind/Enhancement"
color: 84b6eb
description: Improve existing functionality
- name: "Kind/Security"
color: 9c27b0
description: This is security issue
- name: "Kind/Testing"
color: 795548
description: Issue or pull request related to testing
- name: "Kind/Breaking"
color: c62828
description: Breaking change that won't be backward compatible
- name: "Kind/Documentation"
color: 37474f
description: Documentation changes
- name: "Reviewed/Duplicate"
exclusive: true
color: 616161
description: This issue or pull request already exists
- name: "Reviewed/Invalid"
exclusive: true
color: 546e7a
description: Invalid issue
- name: "Reviewed/Confirmed"
exclusive: true
color: 795548
description: Issue has been confirmed
- name: "Reviewed/Won't Fix"
exclusive: true
color: eeeeee
description: This issue won't be fixed
- name: "Status/Need More Info"
exclusive: true
color: 424242
description: Feedback is required to reproduce issue or to continue work
- name: "Status/Blocked"
exclusive: true
color: 880e4f
description: Something is blocking this issue or pull request
- name: "Status/Abandoned"
exclusive: true
color: "222222"
description: Somebody has started to work on this but abandoned work
- name: "Priority/Critical"
exclusive: true
color: b71c1c
description: The priority is critical
priority: critical
- name: "Priority/High"
exclusive: true
color: d32f2f
description: The priority is high
priority: high
- name: "Priority/Medium"
exclusive: true
color: e64a19
description: The priority is medium
priority: medium
- name: "Priority/Low"
exclusive: true
color: 4caf50
description: The priority is low
priority: low

View File

@ -4,13 +4,13 @@
package org package org
import ( import (
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
@ -84,12 +84,13 @@ func CreateLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateLabelOption) form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ") form.Color = strings.Trim(form.Color, " ")
color, err := label.NormalizeColor(form.Color) if len(form.Color) == 6 {
if err != nil { form.Color = "#" + form.Color
ctx.Error(http.StatusUnprocessableEntity, "Color", err) }
if !issues_model.LabelColorPattern.MatchString(form.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
return return
} }
form.Color = color
label := &issues_model.Label{ label := &issues_model.Label{
Name: form.Name, Name: form.Name,
@ -182,7 +183,7 @@ func EditLabel(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption) form := web.GetForm(ctx).(*api.EditLabelOption)
l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) label, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if issues_model.IsErrOrgLabelNotExist(err) { if issues_model.IsErrOrgLabelNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -193,28 +194,30 @@ func EditLabel(ctx *context.APIContext) {
} }
if form.Name != nil { if form.Name != nil {
l.Name = *form.Name label.Name = *form.Name
} }
if form.Exclusive != nil { if form.Exclusive != nil {
l.Exclusive = *form.Exclusive label.Exclusive = *form.Exclusive
} }
if form.Color != nil { if form.Color != nil {
color, err := label.NormalizeColor(*form.Color) label.Color = strings.Trim(*form.Color, " ")
if err != nil { if len(label.Color) == 6 {
ctx.Error(http.StatusUnprocessableEntity, "Color", err) label.Color = "#" + label.Color
}
if !issues_model.LabelColorPattern.MatchString(label.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
return return
} }
l.Color = color
} }
if form.Description != nil { if form.Description != nil {
l.Description = *form.Description label.Description = *form.Description
} }
if err := issues_model.UpdateLabel(l); err != nil { if err := issues_model.UpdateLabel(label); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToLabel(l, nil, ctx.Org.Organization.AsUser())) ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization.AsUser()))
} }
// DeleteLabel delete a label for an organization // DeleteLabel delete a label for an organization

View File

@ -5,12 +5,13 @@
package repo package repo
import ( import (
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
@ -92,14 +93,14 @@ func GetLabel(ctx *context.APIContext) {
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"
var ( var (
l *issues_model.Label label *issues_model.Label
err error err error
) )
strID := ctx.Params(":id") strID := ctx.Params(":id")
if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil { if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
l, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID) label, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID)
} else { } else {
l, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID) label, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID)
} }
if err != nil { if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) { if issues_model.IsErrRepoLabelNotExist(err) {
@ -110,7 +111,7 @@ func GetLabel(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil)) ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
} }
// CreateLabel create a label for a repository // CreateLabel create a label for a repository
@ -144,27 +145,28 @@ func CreateLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateLabelOption) form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ")
color, err := label.NormalizeColor(form.Color) if len(form.Color) == 6 {
if err != nil { form.Color = "#" + form.Color
ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err) }
if !issues_model.LabelColorPattern.MatchString(form.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
return return
} }
form.Color = color
l := &issues_model.Label{ label := &issues_model.Label{
Name: form.Name, Name: form.Name,
Exclusive: form.Exclusive, Exclusive: form.Exclusive,
Color: form.Color, Color: form.Color,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Description: form.Description, Description: form.Description,
} }
if err := issues_model.NewLabel(ctx, l); err != nil { if err := issues_model.NewLabel(ctx, label); err != nil {
ctx.Error(http.StatusInternalServerError, "NewLabel", err) ctx.Error(http.StatusInternalServerError, "NewLabel", err)
return return
} }
ctx.JSON(http.StatusCreated, convert.ToLabel(l, ctx.Repo.Repository, nil)) ctx.JSON(http.StatusCreated, convert.ToLabel(label, ctx.Repo.Repository, nil))
} }
// EditLabel modify a label for a repository // EditLabel modify a label for a repository
@ -204,7 +206,7 @@ func EditLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption) form := web.GetForm(ctx).(*api.EditLabelOption)
l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) label, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) { if issues_model.IsErrRepoLabelNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -215,28 +217,30 @@ func EditLabel(ctx *context.APIContext) {
} }
if form.Name != nil { if form.Name != nil {
l.Name = *form.Name label.Name = *form.Name
} }
if form.Exclusive != nil { if form.Exclusive != nil {
l.Exclusive = *form.Exclusive label.Exclusive = *form.Exclusive
} }
if form.Color != nil { if form.Color != nil {
color, err := label.NormalizeColor(*form.Color) label.Color = strings.Trim(*form.Color, " ")
if err != nil { if len(label.Color) == 6 {
ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err) label.Color = "#" + label.Color
}
if !issues_model.LabelColorPattern.MatchString(label.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
return return
} }
l.Color = color
} }
if form.Description != nil { if form.Description != nil {
l.Description = *form.Description label.Description = *form.Description
} }
if err := issues_model.UpdateLabel(l); err != nil { if err := issues_model.UpdateLabel(label); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil)) ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
} }
// DeleteLabel delete a label for a repository // DeleteLabel delete a label for a repository

View File

@ -19,7 +19,6 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -249,7 +248,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
} else if db.IsErrNameReserved(err) || } else if db.IsErrNameReserved(err) ||
db.IsErrNamePatternNotAllowed(err) || db.IsErrNamePatternNotAllowed(err) ||
label.IsErrTemplateLoad(err) { repo_module.IsErrIssueLabelTemplateLoad(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {
ctx.Error(http.StatusInternalServerError, "CreateRepository", err) ctx.Error(http.StatusInternalServerError, "CreateRepository", err)

View File

@ -9,7 +9,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
@ -104,8 +103,8 @@ func InitializeLabels(ctx *context.Context) {
} }
if err := repo_module.InitializeLabels(ctx, ctx.Org.Organization.ID, form.TemplateName, true); err != nil { if err := repo_module.InitializeLabels(ctx, ctx.Org.Organization.ID, form.TemplateName, true); err != nil {
if label.IsErrTemplateLoad(err) { if repo_module.IsErrIssueLabelTemplateLoad(err) {
originalErr := err.(label.ErrTemplateLoad).OriginalError originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") ctx.Redirect(ctx.Org.OrgLink + "/settings/labels")
return return

View File

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
@ -42,8 +41,8 @@ func InitializeLabels(ctx *context.Context) {
} }
if err := repo_module.InitializeLabels(ctx, ctx.Repo.Repository.ID, form.TemplateName, false); err != nil { if err := repo_module.InitializeLabels(ctx, ctx.Repo.Repository.ID, form.TemplateName, false); err != nil {
if label.IsErrTemplateLoad(err) { if repo_module.IsErrIssueLabelTemplateLoad(err) {
originalErr := err.(label.ErrTemplateLoad).OriginalError originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
ctx.Redirect(ctx.Repo.RepoLink + "/labels") ctx.Redirect(ctx.Repo.RepoLink + "/labels")
return return

View File

@ -21,7 +21,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
@ -218,20 +217,18 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err
// CreateLabels creates labels // CreateLabels creates labels
func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
lbs := make([]*issues_model.Label, 0, len(labels)) lbs := make([]*issues_model.Label, 0, len(labels))
for _, l := range labels { for _, label := range labels {
if color, err := label.NormalizeColor(l.Color); err != nil { // We must validate color here:
log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", l.Color, l.Name, g.repoOwner, g.repoName) if !issues_model.LabelColorPattern.MatchString("#" + label.Color) {
l.Color = "#ffffff" log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", label.Color, label.Name, g.repoOwner, g.repoName)
} else { label.Color = "ffffff"
l.Color = color
} }
lbs = append(lbs, &issues_model.Label{ lbs = append(lbs, &issues_model.Label{
RepoID: g.repo.ID, RepoID: g.repo.ID,
Name: l.Name, Name: label.Name,
Exclusive: l.Exclusive, Description: label.Description,
Description: l.Description, Color: "#" + label.Color,
Color: l.Color,
}) })
} }

View File

@ -67,12 +67,6 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str
remoteRepoName := "head_repo" remoteRepoName := "head_repo"
baseBranch := "base" baseBranch := "base"
fetchArgs := git.TrustedCmdArgs{"--no-tags"}
if git.CheckGitVersionAtLeast("2.25.0") == nil {
// Writing the commit graph can be slow and is not needed here
fetchArgs = append(fetchArgs, "--no-write-commit-graph")
}
// Add head repo remote. // Add head repo remote.
addCacheRepo := func(staging, cache string) error { addCacheRepo := func(staging, cache string) error {
p := filepath.Join(staging, ".git", "objects", "info", "alternates") p := filepath.Join(staging, ".git", "objects", "info", "alternates")
@ -114,7 +108,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str
outbuf.Reset() outbuf.Reset()
errbuf.Reset() errbuf.Reset()
if err := git.NewCommand(ctx, "fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). if err := git.NewCommand(ctx, "fetch", "origin", "--no-tags").AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch).
Run(&git.RunOpts{ Run(&git.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdout: &outbuf, Stdout: &outbuf,
@ -177,7 +171,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str
} else { } else {
headBranch = pr.GetGitRefName() headBranch = pr.GetGitRefName()
} }
if err := git.NewCommand(ctx, "fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch). if err := git.NewCommand(ctx, "fetch", "--no-tags").AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch).
Run(&git.RunOpts{ Run(&git.RunOpts{
Dir: tmpBasePath, Dir: tmpBasePath,
Stdout: &outbuf, Stdout: &outbuf,

View File

@ -232,7 +232,7 @@ export function initRepoCommentForm() {
$(this).parent().find('.item').each(function () { $(this).parent().find('.item').each(function () {
$(this).removeClass('checked'); $(this).removeClass('checked');
$(this).find('.octicon-check').addClass('invisible'); $(this).find('.octicon').addClass('invisible');
}); });
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {