Repository: srvaroa/labeler (go)
Issue: Option to append to existing Labels
Please provide an option to append labels to the already existing labels on a PR.
Currently it removes all existing labels from a PR and applies those which meet the conditions. But, there are use cases where people want to manually add labels to the PR and maintain it.
Pull request description: Support Append-only mode
Details in README. Allow a configuration value that will never remove
tags, only add them.

Fixes #42

Signed-off-by: Galo Navarro <anglorvaroa@gmail.com>


Patch:
diff --git a/Dockerfile b/Dockerfile
index 1a17284..3045963 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,5 +12,6 @@ RUN apk add --no-cache git
 WORKDIR /go/src/app
 COPY . .
 ENV GO111MODULE=on
+ENV GOPROXY=https://proxy.golang.org
 RUN go build -o action ./cmd
 ENTRYPOINT ["/go/src/app/action"]
diff --git a/README.md b/README.md
index 23827a9..5e2a240 100644
--- a/README.md
+++ b/README.md
@@ -112,6 +112,44 @@ labels:
 The `WIP` label will be set if the title matches `^WIP:.*` OR the label
 is not in a mergeable state.
 
+## Append-only mode
+
+The default behaviour of this action includes *removing* labels that
+have a rule configured that does not match anymore. For example, given
+this configuration:
+
+```yaml
+version: 1
+labels:
+- label: "WIP"
+  title: "^WIP:.*"
+```
+
+A PR with title 'WIP: my feature' will get the `WIP` label.
+
+Now the title changes to `My feature`. Since the labeler configuration
+includes the `WIP` label, and its rule does not match anymore, the label
+will get removed.
+
+In some cases you would prefer that the action adds labels, but never
+removes them regardless of the matching status. To achieve this you can
+enable the `appendOnly` flag.
+
+```yaml
+version: 1
+appendOnly: true
+labels:
+- label: "WIP"
+  title: "^WIP:.*"
+```
+
+With this config, the behaviour changes:
+
+- A PR with title 'WIP: my feature' will get the `WIP` label.
+- When the title changes to `My feature`, even though the labeler has a
+  rule for the `WIP` label that does not match, the label will be
+  respected.
+
 ## Conditions
 
 Below are the conditions currently supported in label matchers.
diff --git a/pkg/labeler.go b/pkg/labeler.go
index e08c1db..f506a65 100644
--- a/pkg/labeler.go
+++ b/pkg/labeler.go
@@ -32,7 +32,11 @@ type LabelerConfigV0 map[string]LabelMatcher
 
 type LabelerConfigV1 struct {
 	Version int32
-	Labels  []LabelMatcher
+	// when set to true, we will only add labels when they match a rule
+	// but it will NOT remove labels that were previously set and stop
+	// matching a rule
+	AppendOnly bool
+	Labels     []LabelMatcher
 }
 
 // LabelUpdates Represents a request to update the set of labels
@@ -266,9 +270,19 @@ func (l *Labeler) ExecuteOn(pr *gh.PullRequest) error {
 		intentions[label] = true
 	}
 
-	// update, adding new ones and unflagging those to remove
+	// update, adding new ones and unflagging those to remove if
+	// necessary
 	for label, isDesired := range labelUpdates.set {
-		intentions[label] = isDesired
+		if config.AppendOnly {
+			// If we DO NOT allow deletions, then we will respect
+			// labels that were already set in the current set
+			// but add new ones that matched the repo
+			intentions[label] = intentions[label] || isDesired
+		} else {
+			// If we allow deletions, then we set / unset the label
+			// based on the result of the rule checks
+			intentions[label] = isDesired
+		}
 	}
 
 	// filter out only labels that must be set


Test patch:
diff --git a/pkg/labeler_test.go b/pkg/labeler_test.go
index 04382bc..6c8c2f0 100644
--- a/pkg/labeler_test.go
+++ b/pkg/labeler_test.go
@@ -358,6 +358,24 @@ func TestHandleEvent(t *testing.T) {
 			initialLabels:  []string{},
 			expectedLabels: []string{"Author"},
 		},
+		{
+			payloads: []string{"create_pr", "reopen_pr"},
+			name:     "AppendOnly enabled forbids deletions",
+			config: LabelerConfigV1{
+				Version:          1,
+				AppendOnly: true,
+				Labels: []LabelMatcher{
+					{
+						Label: "Fix",
+						Title: "THIS DOES NOT MATCH",
+					},
+				},
+			},
+			initialLabels: []string{"Fix"},
+			// We have a rule for label Fix, it does not match
+			// BUT because AppendOnly is set, we do not erase it
+			expectedLabels: []string{"Fix"},
+		},
 	}
 
 	for _, tc := range testCases {