mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
fix(label): keep exclusive and archived flags in label export
label export wrote "#7057ff good first issue ;" with a dangling separator and dropped the exclusive and archived flags, so an export and re-import lost them. Rows now omit an empty description and end in " ;exclusive", " ;archived" or both when a label has those flags; label create --file reads them back. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
3 files changed
+64
-5
No files matched your search
@@ -42,7 +42,7 @@ func TestLabelExportReadsAllPagesAndPublishesOnlyCompleteFiles(t *testing.T) {
|
|||||||
require.Equal(t, []string{"1", "2", "3"}, pages)
|
require.Equal(t, []string{"1", "2", "3"}, pages)
|
||||||
data, err := os.ReadFile(output)
|
data, err := os.ReadFile(output)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "#ff0000 good first issue ;Help wanted\n#00ff00 bug ;\n", string(data))
|
require.Equal(t, "#ff0000 good first issue ;Help wanted\n#00ff00 bug\n", string(data))
|
||||||
require.NoError(t, os.Remove(output))
|
require.NoError(t, os.Remove(output))
|
||||||
fail = true
|
fail = true
|
||||||
require.Error(t, Execute(context.Background(), deps, args))
|
require.Error(t, Execute(context.Background(), deps, args))
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import (
|
|||||||
|
|
||||||
var labelColor = regexp.MustCompile(`^[0-9a-fA-F]{6}$`)
|
var labelColor = regexp.MustCompile(`^[0-9a-fA-F]{6}$`)
|
||||||
|
|
||||||
// parseLabelFile reads "#color name ;description" rows, skipping blank and comment rows.
|
// parseLabelFile reads "#color name ;description ;flags" rows, skipping blank and comment rows. The
|
||||||
|
// description and the flags, a comma-separated list of exclusive and archived, are optional.
|
||||||
func parseLabelFile(r io.Reader) ([]services.LabelOptions, error) {
|
func parseLabelFile(r io.Reader) ([]services.LabelOptions, error) {
|
||||||
var labels []services.LabelOptions
|
var labels []services.LabelOptions
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
@@ -43,6 +44,14 @@ func parseLabelRow(text string, line int) (services.LabelOptions, bool, error) {
|
|||||||
raw := strings.TrimSpace(text)
|
raw := strings.TrimSpace(text)
|
||||||
parts := strings.SplitN(raw, ";", 2)
|
parts := strings.SplitN(raw, ";", 2)
|
||||||
fields := strings.Fields(parts[0])
|
fields := strings.Fields(parts[0])
|
||||||
|
var exclusive, archived bool
|
||||||
|
if len(parts) == 2 {
|
||||||
|
if at := strings.LastIndex(parts[1], ";"); at >= 0 {
|
||||||
|
if flags, ok := labelFlags(parts[1][at+1:]); ok {
|
||||||
|
exclusive, archived, parts[1] = flags["exclusive"], flags["archived"], parts[1][:at]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Rows start with an optional "#" before the color, as Gitea's label templates do.
|
// Rows start with an optional "#" before the color, as Gitea's label templates do.
|
||||||
if raw == "" || strings.HasPrefix(raw, "#") && !labelColor.MatchString(strings.TrimPrefix(fields[0], "#")) {
|
if raw == "" || strings.HasPrefix(raw, "#") && !labelColor.MatchString(strings.TrimPrefix(fields[0], "#")) {
|
||||||
return services.LabelOptions{}, false, nil
|
return services.LabelOptions{}, false, nil
|
||||||
@@ -57,15 +66,49 @@ func parseLabelRow(text string, line int) (services.LabelOptions, bool, error) {
|
|||||||
if !labelColor.MatchString(color) {
|
if !labelColor.MatchString(color) {
|
||||||
return services.LabelOptions{}, false, clierr.Wrap(clierr.Usage, fmt.Errorf("invalid color on row %d", line))
|
return services.LabelOptions{}, false, clierr.Wrap(clierr.Usage, fmt.Errorf("invalid color on row %d", line))
|
||||||
}
|
}
|
||||||
return services.LabelOptions{Name: &name, Color: &color, Description: &description}, true, nil
|
label := services.LabelOptions{Name: &name, Color: &color, Description: &description}
|
||||||
|
if exclusive {
|
||||||
|
label.Exclusive = &exclusive
|
||||||
|
}
|
||||||
|
if archived {
|
||||||
|
label.Archived = &archived
|
||||||
|
}
|
||||||
|
return label, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// labelFlags parses the flags field of a label row, such as "exclusive,archived".
|
||||||
|
func labelFlags(field string) (map[string]bool, bool) {
|
||||||
|
flags := map[string]bool{}
|
||||||
|
for flag := range strings.SplitSeq(field, ",") {
|
||||||
|
flag = strings.TrimSpace(flag)
|
||||||
|
if flag != "exclusive" && flag != "archived" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
flags[flag] = true
|
||||||
|
}
|
||||||
|
return flags, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatLabel renders a label as a label file row, rejecting labels the row cannot reproduce.
|
// formatLabel renders a label as a label file row, rejecting labels the row cannot reproduce.
|
||||||
func formatLabel(label services.Label) (string, error) {
|
func formatLabel(label services.Label) (string, error) {
|
||||||
color := strings.TrimPrefix(label.Color, "#")
|
color := strings.TrimPrefix(label.Color, "#")
|
||||||
row := fmt.Sprintf("#%s %s ;%s", color, label.Name, label.Description)
|
row := fmt.Sprintf("#%s %s", color, label.Name)
|
||||||
|
var flags []string
|
||||||
|
if label.Exclusive {
|
||||||
|
flags = append(flags, "exclusive")
|
||||||
|
}
|
||||||
|
if label.Archived {
|
||||||
|
flags = append(flags, "archived")
|
||||||
|
}
|
||||||
|
if label.Description != "" || len(flags) > 0 {
|
||||||
|
row += " ;" + label.Description
|
||||||
|
}
|
||||||
|
if len(flags) > 0 {
|
||||||
|
row += " ;" + strings.Join(flags, ",")
|
||||||
|
}
|
||||||
parsed, ok, err := parseLabelRow(row, 1)
|
parsed, ok, err := parseLabelRow(row, 1)
|
||||||
if err != nil || !ok || strings.ContainsAny(row, "\r\n") || *parsed.Name != label.Name || *parsed.Color != color || *parsed.Description != label.Description {
|
if err != nil || !ok || strings.ContainsAny(row, "\r\n") || *parsed.Name != label.Name || *parsed.Color != color ||
|
||||||
|
*parsed.Description != label.Description || (parsed.Exclusive != nil) != label.Exclusive || (parsed.Archived != nil) != label.Archived {
|
||||||
return "", errors.New("label cannot be represented in the import format; use label export --json to preserve its text")
|
return "", errors.New("label cannot be represented in the import format; use label export --json to preserve its text")
|
||||||
}
|
}
|
||||||
return row + "\n", nil
|
return row + "\n", nil
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ func TestLabelFileRoundTrip(t *testing.T) {
|
|||||||
{Name: "bug", Color: "ff0000", Description: "Something is broken"},
|
{Name: "bug", Color: "ff0000", Description: "Something is broken"},
|
||||||
{Name: "kind/feature request", Color: "#00FF00", Description: "Wanted; maybe"},
|
{Name: "kind/feature request", Color: "#00FF00", Description: "Wanted; maybe"},
|
||||||
{Name: "#hash", Color: "0000ff"},
|
{Name: "#hash", Color: "0000ff"},
|
||||||
|
{Name: "priority/high", Color: "7057ff", Exclusive: true},
|
||||||
|
{Name: "old", Color: "cccccc", Description: "Retired; kept", Exclusive: true, Archived: true},
|
||||||
|
{Name: "flag-like", Color: "cccccc", Description: "ends in;archived text"},
|
||||||
} {
|
} {
|
||||||
row, err := formatLabel(label)
|
row, err := formatLabel(label)
|
||||||
require.NoError(t, err, label.Name)
|
require.NoError(t, err, label.Name)
|
||||||
@@ -32,6 +35,19 @@ func TestLabelFileRoundTrip(t *testing.T) {
|
|||||||
require.Equal(t, label.Name, *parsed[0].Name)
|
require.Equal(t, label.Name, *parsed[0].Name)
|
||||||
require.Equal(t, strings.TrimPrefix(label.Color, "#"), *parsed[0].Color)
|
require.Equal(t, strings.TrimPrefix(label.Color, "#"), *parsed[0].Color)
|
||||||
require.Equal(t, label.Description, *parsed[0].Description)
|
require.Equal(t, label.Description, *parsed[0].Description)
|
||||||
|
require.Equal(t, label.Exclusive, parsed[0].Exclusive != nil && *parsed[0].Exclusive, row)
|
||||||
|
require.Equal(t, label.Archived, parsed[0].Archived != nil && *parsed[0].Archived, row)
|
||||||
|
}
|
||||||
|
for label, row := range map[services.Label]string{
|
||||||
|
{Name: "good first issue", Color: "7057ff"}: "#7057ff good first issue\n",
|
||||||
|
{Name: "bug", Color: "ee0701", Description: "Broken"}: "#ee0701 bug ;Broken\n",
|
||||||
|
{Name: "priority/high", Color: "7057ff", Exclusive: true}: "#7057ff priority/high ; ;exclusive\n",
|
||||||
|
{Name: "old", Color: "cccccc", Description: "Retired", Archived: true}: "#cccccc old ;Retired ;archived\n",
|
||||||
|
{Name: "both", Color: "cccccc", Exclusive: true, Archived: true}: "#cccccc both ; ;exclusive,archived\n",
|
||||||
|
} {
|
||||||
|
formatted, err := formatLabel(label)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, row, formatted)
|
||||||
}
|
}
|
||||||
for _, label := range []services.Label{
|
for _, label := range []services.Label{
|
||||||
{Name: "", Color: "ff0000"},
|
{Name: "", Color: "ff0000"},
|
||||||
|
|||||||
Reference in new issue
Block a user