From 5ea65bccdace5da173ee621d6ac791b5c9fc406e Mon Sep 17 00:00:00 2001
From: Tim Duffy <tduffy@better.com>
Date: Mon, 5 Oct 2020 11:17:43 -0400
Subject: [PATCH 1/3] PLAT-1242 | Do not delete branches that may have a second
 open PR

---
 main.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 63 insertions(+), 10 deletions(-)

diff --git a/main.go b/main.go
index f07c896..7bb7a79 100644
--- a/main.go
+++ b/main.go
@@ -75,6 +75,7 @@ func (ex *Executor) makeRequest(method string, url string) (res *http.Response,
 	if err != nil {
 		return nil, err
 	}
+	req.Header.Add("Accept", "application/vnd.github.v3+json")
 	req.Header.Add("Authorization", "token "+ex.token)
 	res, _ = ex.client.Do(req)
 	if res.StatusCode >= 400 {
@@ -122,6 +123,36 @@ func (ex *Executor) listClosedPullRequests(user string, repo string, days int) (
 	return pullRequests, nil
 }
 
+func (ex *Executor) listOpenPullRequests(user string, repo string) ([]pullRequest, error) {
+	pullRequests := make([]pullRequest, 0, 1)
+
+	for page, keepGoing := 1, true; keepGoing; page++ {
+		res, err := ex.makeRequest("GET", "repos/"+user+"/"+repo+"/pulls?state=open&sort=updated&direction=desc&per_page=100&page="+strconv.Itoa(page))
+
+		if err != nil {
+			return pullRequests, errors.New("failed to get pull requests (" + err.Error() + ")")
+		}
+
+		d := json.NewDecoder(res.Body)
+		var prs struct {
+			PullRequests []pullRequest
+		}
+		err = d.Decode(&prs.PullRequests)
+
+		if err != nil {
+			return pullRequests, errors.New("failed to parse pull requests (" + err.Error() + ")")
+		}
+
+		pullRequests = append(pullRequests, prs.PullRequests...)
+
+		if len(prs.PullRequests) == 0 || len(prs.PullRequests) < 100 {
+			break
+		}
+	}
+
+	return pullRequests, nil
+}
+
 func (ex *Executor) listUnprotectedBranches(user string, repo string) ([]branch, error) {
 	branches := make([]branch, 0, 1)
 
@@ -174,18 +205,33 @@ func (ex *Executor) deleteBranches(user string, repo string, branches []string)
 	return deletedBranches, nil
 }
 
-func getStaleBranches(branches []branch, pullRequests []pullRequest) []string {
-	branchesByName := make(map[string]branch)
-	staleBranches := make([]string, 0, 1)
+func getStaleBranches(closedBranches []branch, closedPullRequests []pullRequest, openPullRequests []pullRequest) []string {
+	branchShaMap := make(map[string]string) // Map[branch_name]branch_SHA
+	staleBranchMap := make(map[string]bool) // Map[branch_name]is_stale
+
+	for _, b := range closedBranches {
+		branchShaMap[b.Name] = b.Commit.SHA
+	}
+
+	for _, pr := range closedPullRequests {
+		staleBranchSHA, branchExists := branchShaMap[pr.Head.Ref]
+		if branchExists && staleBranchSHA == pr.Head.SHA {
+			staleBranchMap[pr.Head.Ref] = true
+		}
+	}
 
-	for _, b := range branches {
-		branchesByName[b.Name] = b
+	// If we've marked this branch as stale, but there is another open PR tied to the branch, unmark as stale
+	for _, pr := range openPullRequests {
+		_, branchExists := staleBranchMap[pr.Head.Ref]
+		if branchExists {
+			staleBranchMap[pr.Head.Ref] = false
+		}
 	}
 
-	for _, pr := range pullRequests {
-		staleBranch, branchExists := branchesByName[pr.Head.Ref]
-		if branchExists && staleBranch.Commit.SHA == pr.Head.SHA {
-			staleBranches = append(staleBranches, pr.Head.Ref)
+	staleBranches := make([]string, 0, 1)
+	for branch, isStale := range staleBranchMap {
+		if isStale {
+			staleBranches = append(staleBranches, branch)
 		}
 	}
 
@@ -269,11 +315,18 @@ func Run(user string, repo string, days int, ex Executor) error {
 	if err != nil {
 		return err
 	}
+
+	openPullRequests, err := ex.listOpenPullRequests(user, repo)
+	if err != nil {
+		return err
+	}
+
 	unprotectedBranches, err := ex.listUnprotectedBranches(user, repo)
 	if err != nil {
 		return err
 	}
-	staleBranches := getStaleBranches(unprotectedBranches, closedPullRequests)
+
+	staleBranches := getStaleBranches(unprotectedBranches, closedPullRequests, openPullRequests)
 	db, err := ex.deleteBranches(user, repo, staleBranches)
 	if err != nil {
 		return err

From af01bd4a1bf96d36b8a09ea23e4bdccacefd0773 Mon Sep 17 00:00:00 2001
From: Tim Duffy <tduffy@better.com>
Date: Mon, 5 Oct 2020 11:34:26 -0400
Subject: [PATCH 2/3] Add mock API call for open PRs

---
 main_test.go | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/main_test.go b/main_test.go
index d0cdcfc..56632e0 100644
--- a/main_test.go
+++ b/main_test.go
@@ -208,6 +208,28 @@ func TestRun(t *testing.T) {
 						"ref": "stalebranch",
 						"sha": "1761e021e70d29619ca270046b23bd243f652b98"
 					}
+				},
+				{
+					"number": 2,
+					"updated_at": "` + now.Format(time.RFC3339) + `",
+					"head": {
+						"ref": "notstalebranch",
+						"sha": "867530990210abcdefg867530990210abcdefg"
+					}
+				}
+				]`,
+		},
+		mockHTTPResponse{
+			method: "GET",
+			URL:    "/repos/user/repo/pulls?state=open&sort=updated&direction=desc&per_page=100&page=1",
+			body: `[
+				{
+					"number": 2,
+					"updated_at": "` + now.Format(time.RFC3339) + `",
+					"head": {
+						"ref": "notstalebranch",
+						"sha": "867530990210abcdefg867530990210abcdefg"
+					}
 				}
 				]`,
 		},

From 8d3244187b7e522aabfc1cf60a9549fbe916f73b Mon Sep 17 00:00:00 2001
From: Tim Duffy <tduffy@better.com>
Date: Mon, 5 Oct 2020 11:51:39 -0400
Subject: [PATCH 3/3] Remove accept header; change goarch for darwin

---
 Makefile |  7 +++++--
 main.go  | 10 +++++-----
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/Makefile b/Makefile
index 078ffc2..c558f34 100644
--- a/Makefile
+++ b/Makefile
@@ -34,8 +34,11 @@ clean:
 .PHONY: build
 build: clean build-darwin build-linux
 
-build-%:
-	GOOS=$* GOARCH=386 go build -ldflags '${LDFLAGS}' -o ${PREFIX}${NAME}-$*
+build-linux:
+	GOOS=linux GOARCH=386 go build -ldflags '${LDFLAGS}' -o ${PREFIX}${NAME}-linux
+
+build-darwin:
+	GOOS=darwin GOARCH=amd64 go build -ldflags '${LDFLAGS}' -o ${PREFIX}${NAME}-darwin
 
 .PHONY: docker
 docker:
diff --git a/main.go b/main.go
index 7bb7a79..1293934 100644
--- a/main.go
+++ b/main.go
@@ -75,7 +75,7 @@ func (ex *Executor) makeRequest(method string, url string) (res *http.Response,
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Add("Accept", "application/vnd.github.v3+json")
+
 	req.Header.Add("Authorization", "token "+ex.token)
 	res, _ = ex.client.Do(req)
 	if res.StatusCode >= 400 {
@@ -93,7 +93,7 @@ func (ex *Executor) listClosedPullRequests(user string, repo string, days int) (
 		res, err := ex.makeRequest("GET", "repos/"+user+"/"+repo+"/pulls?state=closed&sort=updated&direction=desc&per_page=100&page="+strconv.Itoa(page))
 
 		if err != nil {
-			return pullRequests, errors.New("failed to get pull requests (" + err.Error() + ")")
+			return pullRequests, errors.New("failed to get closed pull requests (" + err.Error() + ")")
 		}
 
 		d := json.NewDecoder(res.Body)
@@ -103,7 +103,7 @@ func (ex *Executor) listClosedPullRequests(user string, repo string, days int) (
 		err = d.Decode(&prs.PullRequests)
 
 		if err != nil {
-			return pullRequests, errors.New("failed to parse pull requests (" + err.Error() + ")")
+			return pullRequests, errors.New("failed to parse closed pull requests (" + err.Error() + ")")
 		}
 
 		for _, pr := range prs.PullRequests {
@@ -130,7 +130,7 @@ func (ex *Executor) listOpenPullRequests(user string, repo string) ([]pullReques
 		res, err := ex.makeRequest("GET", "repos/"+user+"/"+repo+"/pulls?state=open&sort=updated&direction=desc&per_page=100&page="+strconv.Itoa(page))
 
 		if err != nil {
-			return pullRequests, errors.New("failed to get pull requests (" + err.Error() + ")")
+			return pullRequests, errors.New("failed to get open pull requests (" + err.Error() + ")")
 		}
 
 		d := json.NewDecoder(res.Body)
@@ -140,7 +140,7 @@ func (ex *Executor) listOpenPullRequests(user string, repo string) ([]pullReques
 		err = d.Decode(&prs.PullRequests)
 
 		if err != nil {
-			return pullRequests, errors.New("failed to parse pull requests (" + err.Error() + ")")
+			return pullRequests, errors.New("failed to parse open pull requests (" + err.Error() + ")")
 		}
 
 		pullRequests = append(pullRequests, prs.PullRequests...)