Skip to content

Commit

Permalink
issue_close: Allow closing as duplicate of another issue
Browse files Browse the repository at this point in the history
If an issue is opened that duplicates an existing issue, it is
useful to mark it as such rather than to just close it.

Unfortunately the functionality from gitlab's web UI isn't exposed
in the API (or I'm too clumsy to find it), but we can get around
that limitation by leveraging the corresponding quick action.
  • Loading branch information
fmuellner committed Dec 19, 2020
1 parent f945aca commit c11e773
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
25 changes: 21 additions & 4 deletions cmd/issue_close.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"log"
"strings"

"github.com/rsteube/carapace"
"github.com/spf13/cobra"
Expand All @@ -17,6 +18,9 @@ var issueCloseCmd = &cobra.Command{
Long: ``,
Args: cobra.MinimumNArgs(1),
PersistentPreRun: LabPersistentPreRun,
Example: `lab issue close 1234
lab issue close --duplicate 123 1234
lab issue close --duplicate other-project#123 1234`,
Run: func(cmd *cobra.Command, args []string) {
rn, id, err := parseArgsRemoteAndID(args)
if err != nil {
Expand All @@ -28,15 +32,28 @@ var issueCloseCmd = &cobra.Command{
log.Fatal(err)
}

err = lab.IssueClose(p.ID, int(id))
if err != nil {
log.Fatal(err)
dupId, _ := cmd.Flags().GetString("duplicate")
if dupId != "" {
if !strings.Contains(dupId, "#") {
dupId = "#" + dupId
}
err = lab.IssueDuplicate(p.ID, int(id), dupId)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Issue #%d closed as duplicate of %s\n", id, dupId)
} else {
err = lab.IssueClose(p.ID, int(id))
if err != nil {
log.Fatal(err)
}
fmt.Printf("Issue #%d closed\n", id)
}
fmt.Printf("Issue #%d closed\n", id)
},
}

func init() {
issueCloseCmd.Flags().StringP("duplicate", "", "", "mark as duplicate of another issue")
issueCmd.AddCommand(issueCloseCmd)
carapace.Gen(issueCloseCmd).PositionalCompletion(
action.Remotes(),
Expand Down
42 changes: 42 additions & 0 deletions cmd/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,48 @@ func Test_issueCmd(t *testing.T) {
})
}

func Test_issueCmdDuplicate(t *testing.T) {
var issueID string
t.Run("create", func(t *testing.T) {
repo := copyTestRepo(t)
cmd := exec.Command(labBinaryPath, "issue", "create", "lab-testing",
"-m", "issue title",
"-m", "issue description",
"-l", "bug",
"-l", "critical",
"-a", "lab-testing")
cmd.Dir = repo

b, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(b))
t.Fatal(err)
}

out := string(b)
require.Contains(t, out, "https://gitlab.com/lab-testing/test/-/issues/")

i := strings.Index(out, "\n")
issueID = strings.TrimPrefix(out[:i], "https://gitlab.com/lab-testing/test/-/issues/")
t.Log(issueID)
})
t.Run("close", func(t *testing.T) {
if issueID == "" {
t.Skip("issueID is empty, create likely failed")
}
repo := copyTestRepo(t)
cmd := exec.Command(labBinaryPath, "issue", "close", "lab-testing", "--duplicate", "1", issueID)
cmd.Dir = repo

b, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(b))
t.Fatal(err)
}
require.Contains(t, string(b), fmt.Sprintf("Issue #%s closed as duplicate", issueID))
})
}

func Test_issueCmd_noArgs(t *testing.T) {
repo := copyTestRepo(t)
cmd := exec.Command(labBinaryPath, "issue")
Expand Down
19 changes: 19 additions & 0 deletions internal/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,25 @@ func IssueClose(pid interface{}, id int) error {
return nil
}

// IssueDuplicate closes an issue as duplicate of another
func IssueDuplicate(pid interface{}, id int, dupId string) error {
// Not exposed in API, go through quick action
body := "/duplicate " + dupId

_, _, err := lab.Notes.CreateIssueNote(pid, id, &gitlab.CreateIssueNoteOptions{
Body: &body,
})
if err != nil {
return errors.Errorf("Failed to close issue #%d as duplicate of %s", id, dupId)
}

issue, _, err := lab.Issues.GetIssue(pid, id)
if issue == nil || issue.State != "closed" {
return errors.Errorf("Failed to close issue #%d as duplicate of %s", id, dupId)
}
return nil
}

// IssueReopen reopens a closed issue
func IssueReopen(pid interface{}, id int) error {
issue, _, err := lab.Issues.GetIssue(pid, id)
Expand Down

0 comments on commit c11e773

Please sign in to comment.