cmd/vulnreport: fix two bugs in "vulnreport duplicates"

- Don't error when an issue is already marked "duplicate"
- Don't mark an issue as a duplicate if it already has a report
(corresponding to the issue itself)

Change-Id: I8909a0963727b070484993fd9e6324ada3c828f5
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/584377
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/cmd/vulnreport/create.go b/cmd/vulnreport/create.go
index 9b30192..60ff3d2 100644
--- a/cmd/vulnreport/create.go
+++ b/cmd/vulnreport/create.go
@@ -168,7 +168,15 @@
 }
 
 func createReport(ctx context.Context, iss *issues.Issue, pc *proxy.Client, gc *ghsa.Client, ac *genai.GeminiClient, allowClosed bool) (r *report.Report, err error) {
-	parsed, err := parseGithubIssue(iss, pc, allowClosed)
+	if iss.HasLabel(labelDuplicate) {
+		return nil, fmt.Errorf("duplicate issue")
+	}
+
+	if !allowClosed && iss.State == "closed" {
+		return nil, errors.New("issue is closed")
+	}
+
+	parsed, err := parseGithubIssue(iss, pc)
 	if err != nil {
 		return nil, err
 	}
@@ -270,16 +278,17 @@
 	return r
 }
 
-func parseGithubIssue(iss *issues.Issue, pc *proxy.Client, allowClosed bool) (*parsedIssue, error) {
+const (
+	labelDuplicate = "duplicate"
+	labelDirect    = "Direct External Report"
+)
+
+func parseGithubIssue(iss *issues.Issue, pc *proxy.Client) (*parsedIssue, error) {
 	parsed := &parsedIssue{
 		id: iss.NewGoID(),
 	}
 
-	if !allowClosed && iss.State == "closed" {
-		return nil, errors.New("issue is closed")
-	}
-
-	// Parse labels for excluded and duplicate issues.
+	// Find any excluded labels.
 	for _, label := range iss.Labels {
 		if reason, ok := report.FromLabel(label); ok {
 			if parsed.excluded == "" {
@@ -288,9 +297,6 @@
 				return nil, fmt.Errorf("issue has multiple excluded reasons")
 			}
 		}
-		if label == "duplicate" {
-			return nil, fmt.Errorf("duplicate issue")
-		}
 	}
 
 	// Parse elements from GitHub issue title.
diff --git a/cmd/vulnreport/duplicates.go b/cmd/vulnreport/duplicates.go
index a312f3e..90704b7 100644
--- a/cmd/vulnreport/duplicates.go
+++ b/cmd/vulnreport/duplicates.go
@@ -108,7 +108,12 @@
 		}
 	}
 
-	parsed, err := parseGithubIssue(iss, d.pc, false)
+	if iss.HasLabel(labelDuplicate) {
+		log.Infof("issue #%d is already marked duplicate, skipping", iss.Number)
+		return
+	}
+
+	parsed, err := parseGithubIssue(iss, d.pc)
 	if err != nil {
 		return err
 	}
@@ -130,6 +135,12 @@
 				if err != nil {
 					fname = r.ID
 				}
+				// Skip the report if it corresponds to the issue number.
+				// (This happens when there is an unsubmitted report for the issue).
+				_, _, in, _ := report.ParseFilepath(fname)
+				if in == iss.Number {
+					continue
+				}
 				xrefs = append(xrefs, fname)
 			}
 		}
diff --git a/internal/issues/issues.go b/internal/issues/issues.go
index b7a88b4..df4c1c0 100644
--- a/internal/issues/issues.go
+++ b/internal/issues/issues.go
@@ -10,6 +10,7 @@
 	"context"
 	"fmt"
 	"net/url"
+	"slices"
 	"time"
 
 	"github.com/google/go-github/v41/github"
@@ -212,3 +213,7 @@
 	}
 	return fmt.Sprintf("GO-%04d-%04d", year, iss.Number)
 }
+
+func (iss *Issue) HasLabel(label string) bool {
+	return slices.Contains(iss.Labels, label)
+}