Repository: actions/stale
Pull request #260 (2020-12-23 14:28:02)

Clarify significance of the exempt-* config knobs
This action doesn't apply exempt labels, it reads them.


Patch:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index e67f28a..4c16ad5 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -1,16 +1,15 @@
-name: "Stale issue handler"
+name: 'Stale issue handler'
 on:
   workflow_dispatch:
   schedule:
-  - cron: "0 0 * * *"
+    - cron: '0 0 * * *'
 
 jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/stale@v3
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
-        days-before-stale: 30
-        days-before-close: 5
+      - uses: actions/stale@v3
+        with:
+          stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
+          days-before-stale: 30
+          days-before-close: 5
diff --git a/README.md b/README.md
index 7cb4c83..d9d45bd 100644
--- a/README.md
+++ b/README.md
@@ -4,108 +4,111 @@ Warns and then closes issues and PRs that have had no activity for a specified a
 
 ### Building and testing
 
-Install the dependencies  
+Install the dependencies
+
 ```bash
 $ npm install
 ```
 
 Build the typescript and package it for distribution
+
 ```bash
 $ npm run build && npm run pack
 ```
 
-Run the tests :heavy_check_mark:  
+Run the tests :heavy_check_mark:
+
 ```bash
 $ npm test
 ```
-### Arguments
 
-| Input  | Description | Usage |
-| :---:     |     :---:   |    :---:   |
-| `repo-token`  | PAT(Personal Access Token) for authorizing repository. | *Required* |
-| `days-before-stale`  | Idle number of days before marking an issue/pr as stale. *Defaults to **60*** | Optional
-| `days-before-close`  | Idle number of days before closing an stale issue/pr. *Defaults to **7*** | Optional
-| `stale-issue-message`  | Message to post on the stale issue. | Optional
-| `stale-pr-message`  | Message to post on the stale pr. | Optional
-| `close-issue-message` | Message to post on the stale issue while closing it. | Optional
-| `close-pr-message`  | Message to post on the stale pr while closing it. | Optional
-| `stale-issue-label`  | Label to apply on the stale issue. *Defaults to **stale*** | Optional
-| `close-issue-label` | Label to apply on closing issue. | Optional
-| `stale-pr-label` | Label to apply on the stale pr. | Optional
-| `close-pr-label` | Label to apply on the closing pr. | Optional
-| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional
-| `exempt-pr-labels` | Labels on the pr exempted from being marked as stale. | Optional
-| `only-labels` | Only labels checked for stale issue/pr. | Optional
-| `operations-per-run` | Maximum number of operations per run. *Defaults to **30*** | Optional
-| `remove-stale-when-updated` | Remove stale label from issue/pr on updates or comments. *Defaults to **true*** | Optional
-| `debug-only` | Dry-run on action. *Defaults to **false*** | Optional
-| `ascending` | Order to get issues/pr. *Defaults to **false*** | Optional
-| `skip-stale-issue-message` | Skip adding stale message on stale issue. *Defaults to **false*** | Optional
-| `skip-stale-pr-message` | Skip adding stale message on stale pr. *Defaults to **false*** | Optional
+### Arguments
 
+|            Input            |                                    Description                                    |  Usage   |
+| :-------------------------: | :-------------------------------------------------------------------------------: | :------: |
+|        `repo-token`         |              PAT(Personal Access Token) for authorizing repository.               | Optional |
+|     `days-before-stale`     |  Idle number of days before marking an issue/pr as stale. \*Defaults to **60\***  | Optional |
+|     `days-before-close`     |    Idle number of days before closing an stale issue/pr. \*Defaults to **7\***    | Optional |
+|    `stale-issue-message`    |                        Message to post on the stale issue.                        | Optional |
+|     `stale-pr-message`      |                         Message to post on the stale pr.                          | Optional |
+|    `close-issue-message`    |               Message to post on the stale issue while closing it.                | Optional |
+|     `close-pr-message`      |                 Message to post on the stale pr while closing it.                 | Optional |
+|     `stale-issue-label`     |           Label to apply on the stale issue. \*Defaults to **stale\***            | Optional |
+|     `close-issue-label`     |                         Label to apply on closing issue.                          | Optional |
+|      `stale-pr-label`       |                          Label to apply on the stale pr.                          | Optional |
+|      `close-pr-label`       |                         Label to apply on the closing pr.                         | Optional |
+|    `exempt-issue-labels`    |              Labels on an issue exempted from being marked as stale.              | Optional |
+|     `exempt-pr-labels`      |               Labels on the pr exempted from being marked as stale.               | Optional |
+|        `only-labels`        |                      Only labels checked for stale issue/pr.                      | Optional |
+|    `operations-per-run`     |           Maximum number of operations per run. \*Defaults to **30\***            | Optional |
+| `remove-stale-when-updated` | Remove stale label from issue/pr on updates or comments. \*Defaults to **true\*** | Optional |
+|        `debug-only`         |                   Dry-run on action. \*Defaults to **false\***                    | Optional |
+|         `ascending`         |                 Order to get issues/pr. \*Defaults to **false\***                 | Optional |
+| `skip-stale-issue-message`  |        Skip adding stale message on stale issue. \*Defaults to **false\***        | Optional |
+|   `skip-stale-pr-message`   |         Skip adding stale message on stale pr. \*Defaults to **false\***          | Optional |
 
 ### Usage
 
 See [action.yml](./action.yml) For comprehensive list of options.
- 
+
 Basic:
+
 ```yaml
-name: "Close stale issues"
+name: 'Close stale issues'
 on:
   schedule:
-  - cron: "30 1 * * *"
+    - cron: '30 1 * * *'
 
 jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/stale@v3
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'Message to comment on stale issues. If none provided, will not mark issues stale'
-        stale-pr-message: 'Message to comment on stale PRs. If none provided, will not mark PRs stale'
+      - uses: actions/stale@v3
+        with:
+          stale-issue-message: 'Message to comment on stale issues. If none provided, will not mark issues stale'
+          stale-pr-message: 'Message to comment on stale PRs. If none provided, will not mark PRs stale'
 ```
- 
+
 Configure stale timeouts:
+
 ```yaml
-name: "Close stale issues"
+name: 'Close stale issues'
 on:
   schedule:
-  - cron: "30 1 * * *"
+    - cron: '30 1 * * *'
 
 jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/stale@v3
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
-        days-before-stale: 30
-        days-before-close: 5
+      - uses: actions/stale@v3
+        with:
+          stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
+          days-before-stale: 30
+          days-before-close: 5
 ```
- 
+
 Configure labels:
+
 ```yaml
-name: "Close stale issues"
+name: 'Close stale issues'
 on:
   schedule:
-  - cron: "30 1 * * *"
+    - cron: '30 1 * * *'
 
 jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/stale@v3
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'Stale issue message'
-        stale-pr-message: 'Stale pull request message'
-        stale-issue-label: 'no-issue-activity'
-        exempt-issue-labels: 'awaiting-approval,work-in-progress'
-        stale-pr-label: 'no-pr-activity'
-        exempt-pr-labels: 'awaiting-approval,work-in-progress'
-        only-labels: 'awaiting-feedback,awaiting-answers'
+      - uses: actions/stale@v3
+        with:
+          stale-issue-message: 'Stale issue message'
+          stale-pr-message: 'Stale pull request message'
+          stale-issue-label: 'no-issue-activity'
+          exempt-issue-labels: 'awaiting-approval,work-in-progress'
+          stale-pr-label: 'no-pr-activity'
+          exempt-pr-labels: 'awaiting-approval,work-in-progress'
+          only-labels: 'awaiting-feedback,awaiting-answers'
 ```
 
 ### Debugging
diff --git a/action.yml b/action.yml
index 9b6cf43..b561962 100644
--- a/action.yml
+++ b/action.yml
@@ -4,7 +4,7 @@ author: 'GitHub'
 inputs:
   repo-token:
     description: 'Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`.'
-    required: true
+    default: ${{ github.token }}
   stale-issue-message:
     description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
   stale-pr-message:
@@ -25,7 +25,7 @@ inputs:
   close-issue-label:
     description: 'The label to apply when an issue is closed.'
   exempt-issue-labels:
-    description: 'The labels to apply when an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
+    description: 'The labels that mean an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
     default: ''
   stale-pr-label:
     description: 'The label to apply when a pull request is stale.'
@@ -33,7 +33,7 @@ inputs:
   close-pr-label:
     description: 'The label to apply when a pull request is closed.'
   exempt-pr-labels:
-    description: 'The labels to apply when a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
+    description: 'The labels that mean a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
     default: ''
   only-labels:
     description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.'
@@ -56,6 +56,9 @@ inputs:
   skip-stale-issue-message:
     description: 'Skip adding stale message when marking an issue as stale.'
     default: false
+  delete-branch:
+    description: 'Delete the git branch after closing a stale pull request.'
+    default: false
 runs:
   using: 'node12'
   main: 'dist/index.js'
diff --git a/dist/index.js b/dist/index.js
index 3377db8..e8868aa 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -481,7 +481,7 @@ function run() {
 }
 function getAndValidateArgs() {
     const args = {
-        repoToken: core.getInput('repo-token', { required: true }),
+        repoToken: core.getInput('repo-token'),
         staleIssueMessage: core.getInput('stale-issue-message'),
         stalePrMessage: core.getInput('stale-pr-message'),
         closeIssueMessage: core.getInput('close-issue-message'),
diff --git a/src/IssueProcessor.ts b/src/IssueProcessor.ts
index 77f3b09..329543a 100644
--- a/src/IssueProcessor.ts
+++ b/src/IssueProcessor.ts
@@ -1,5 +1,6 @@
 import * as core from '@actions/core';
 import {context, getOctokit} from '@actions/github';
+import {GitHub} from '@actions/github/lib/utils';
 import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
 import {isLabeled} from './functions/is-labeled';
 import {labelsToList} from './functions/labels-to-list';
@@ -14,6 +15,13 @@ export interface Issue {
   locked: boolean;
 }
 
+export interface PullRequest {
+  number: number;
+  head: {
+    ref: string;
+  };
+}
+
 export interface User {
   type: string;
   login: string;
@@ -54,18 +62,20 @@ export interface IssueProcessorOptions {
   ascending: boolean;
   skipStaleIssueMessage: boolean;
   skipStalePrMessage: boolean;
+  deleteBranch: boolean;
 }
 
 /***
  * Handle processing of issues for staleness/closure.
  */
 export class IssueProcessor {
-  readonly client: any; // need to make this the correct type
+  readonly client: InstanceType<typeof GitHub>;
   readonly options: IssueProcessorOptions;
   private operationsLeft = 0;
 
   readonly staleIssues: Issue[] = [];
   readonly closedIssues: Issue[] = [];
+  readonly deletedBranchIssues: Issue[] = [];
   readonly removedLabelIssues: Issue[] = [];
 
   constructor(
@@ -167,7 +177,13 @@ export class IssueProcessor {
       }
 
       // does this issue have a stale label?
-      let isStale = isLabeled(issue, staleLabel);
+      let isStale: boolean = isLabeled(issue, staleLabel);
+
+      if (isStale) {
+        core.info(`This issue has a stale label`);
+      } else {
+        core.info(`This issue hasn't a stale label`);
+      }
 
       // should this issue be marked stale?
       const shouldBeStale = !IssueProcessor.updatedSince(
@@ -250,6 +266,14 @@ export class IssueProcessor {
         `Closing ${issueType} because it was last updated on ${issue.updated_at}`
       );
       await this.closeIssue(issue, closeMessage, closeLabel);
+
+      if (this.options.deleteBranch && issue.pull_request) {
+        core.info(
+          `Deleting branch for #${issue.number} as delete-branch option was specified`
+        );
+        await this.deleteBranch(issue);
+        this.deletedBranchIssues.push(issue);
+      }
     } else {
       core.info(
         `Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
@@ -432,14 +456,70 @@ export class IssueProcessor {
     }
   }
 
+  private async getPullRequest(
+    pullNumber: number
+  ): Promise<PullRequest | undefined> {
+    this.operationsLeft -= 1;
+
+    try {
+      const pullRequest = await this.client.pulls.get({
+        owner: context.repo.owner,
+        repo: context.repo.repo,
+        pull_number: pullNumber
+      });
+
+      return pullRequest.data;
+    } catch (error) {
+      core.error(`Error getting pull request ${pullNumber}: ${error.message}`);
+    }
+  }
+
+  // Delete the branch on closed pull request
+  private async deleteBranch(issue: Issue): Promise<void> {
+    core.info(
+      `Delete branch from closed issue #${issue.number} - ${issue.title}`
+    );
+
+    if (this.options.debugOnly) {
+      return;
+    }
+
+    const pullRequest = await this.getPullRequest(issue.number);
+
+    if (!pullRequest) {
+      core.info(
+        `Not deleting branch as pull request not found for issue ${issue.number}`
+      );
+      return;
+    }
+
+    const branch = pullRequest.head.ref;
+    core.info(`Deleting branch ${branch} from closed issue #${issue.number}`);
+
+    this.operationsLeft -= 1;
+
+    try {
+      await this.client.git.deleteRef({
+        owner: context.repo.owner,
+        repo: context.repo.repo,
+        ref: `heads/${branch}`
+      });
+    } catch (error) {
+      core.error(
+        `Error deleting branch ${branch} from issue #${issue.number}: ${error.message}`
+      );
+    }
+  }
+
   // Remove a label from an issue
   private async removeLabel(issue: Issue, label: string): Promise<void> {
-    core.info(`Removing label from issue #${issue.number}`);
+    core.info(`Removing label "${label}" from issue #${issue.number}`);
 
     this.removedLabelIssues.push(issue);
 
     this.operationsLeft -= 1;
 
+    // @todo remove the debug only to be able to test the code below
     if (this.options.debugOnly) {
       return;
     }
@@ -449,7 +529,7 @@ export class IssueProcessor {
         owner: context.repo.owner,
         repo: context.repo.repo,
         issue_number: issue.number,
-        name: encodeURIComponent(label) // A label can have a "?" in the name
+        name: label
       });
     } catch (error) {
       core.error(`Error removing a label: ${error.message}`);
diff --git a/src/functions/is-labeled.ts b/src/functions/is-labeled.ts
index 97ee5e3..5ad332a 100644
--- a/src/functions/is-labeled.ts
+++ b/src/functions/is-labeled.ts
@@ -1,6 +1,8 @@
 import deburr from 'lodash.deburr';
 import {Issue, Label} from '../IssueProcessor';
 
+type CleanLabel = string;
+
 /**
  * @description
  * Check if the label is listed as a label of the issue
@@ -19,6 +21,6 @@ export function isLabeled(
   });
 }
 
-function cleanLabel(label: Readonly<string>): string {
+function cleanLabel(label: Readonly<string>): CleanLabel {
   return deburr(label.toLowerCase());
 }
diff --git a/src/main.ts b/src/main.ts
index b1e0dc3..17e57c1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -15,7 +15,7 @@ async function run(): Promise<void> {
 
 function getAndValidateArgs(): IssueProcessorOptions {
   const args = {
-    repoToken: core.getInput('repo-token', {required: true}),
+    repoToken: core.getInput('repo-token'),
     staleIssueMessage: core.getInput('stale-issue-message'),
     stalePrMessage: core.getInput('stale-pr-message'),
     closeIssueMessage: core.getInput('close-issue-message'),
@@ -42,7 +42,8 @@ function getAndValidateArgs(): IssueProcessorOptions {
     debugOnly: core.getInput('debug-only') === 'true',
     ascending: core.getInput('ascending') === 'true',
     skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
-    skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true'
+    skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true',
+    deleteBranch: core.getInput('delete-branch') === 'true'
   };
 
   for (const numberInput of [


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7e11219..699ef3c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,4 +1,4 @@
-name: "Build"
+name: 'Build'
 on: # rebuild any PRs and main branch changes
   pull_request:
   push:
@@ -10,17 +10,16 @@ jobs:
   build: # make sure build/ci work properly
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - run: |
-        npm install
-        npm run all
+      - uses: actions/checkout@v2
+      - run: |
+          npm install
+          npm run all
   test: # make sure the action works on a clean machine without building
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
-    - uses: ./
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'This issue is stale'
-        stale-pr-message: 'This PR is stale'
-        debug-only: true
+      - uses: actions/checkout@v2
+      - uses: ./
+        with:
+          stale-issue-message: 'This issue is stale'
+          stale-pr-message: 'This PR is stale'
+          debug-only: true
diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts
index 85fe705..495f2a9 100644
--- a/__tests__/main.test.ts
+++ b/__tests__/main.test.ts
@@ -1,11 +1,8 @@
-import * as core from '@actions/core';
 import * as github from '@actions/github';
-import {Octokit} from '@octokit/rest';
 
 import {
   IssueProcessor,
   Issue,
-  Label,
   IssueProcessorOptions
 } from '../src/IssueProcessor';
 
@@ -51,7 +48,8 @@ const DefaultProcessorOptions: IssueProcessorOptions = Object.freeze({
   removeStaleWhenUpdated: false,
   ascending: false,
   skipStaleIssueMessage: false,
-  skipStalePrMessage: false
+  skipStalePrMessage: false,
+  deleteBranch: false
 });
 
 test('empty issue list results in 1 operation', async () => {
@@ -89,6 +87,7 @@ test('processing an issue with no label will make it stale and close it, if it i
 
   expect(processor.staleIssues.length).toEqual(1);
   expect(processor.closedIssues.length).toEqual(1);
+  expect(processor.deletedBranchIssues.length).toEqual(0);
 });
 
 test('processing an issue with no label will make it stale and not close it if days-before-close is set to > 0', async () => {
@@ -187,6 +186,66 @@ test('processing a stale issue will close it', async () => {
   expect(processor.closedIssues.length).toEqual(1);
 });
 
+test('processing a stale issue containing a space in the label will close it', async () => {
+  const TestIssueList: Issue[] = [
+    generateIssue(
+      1,
+      'A stale issue that should be closed',
+      '2020-01-01T17:00:00Z',
+      false,
+      ['state: stale']
+    )
+  ];
+
+  const opts: IssueProcessorOptions = {
+    ...DefaultProcessorOptions,
+    staleIssueLabel: 'state: stale'
+  };
+
+  const processor = new IssueProcessor(
+    opts,
+    async p => (p == 1 ? TestIssueList : []),
+    async (num, dt) => [],
+    async (issue, label) => new Date().toDateString()
+  );
+
+  // process our fake issue list
+  await processor.processIssues(1);
+
+  expect(processor.staleIssues.length).toEqual(0);
+  expect(processor.closedIssues.length).toEqual(1);
+});
+
+test('processing a stale issue containing a slash in the label will close it', async () => {
+  const TestIssueList: Issue[] = [
+    generateIssue(
+      1,
+      'A stale issue that should be closed',
+      '2020-01-01T17:00:00Z',
+      false,
+      ['lifecycle/stale']
+    )
+  ];
+
+  const opts: IssueProcessorOptions = {
+    ...DefaultProcessorOptions,
+    staleIssueLabel: 'lifecycle/stale'
+  };
+
+  const processor = new IssueProcessor(
+    opts,
+    async p => (p == 1 ? TestIssueList : []),
+    async (num, dt) => [],
+    async (issue, label) => new Date().toDateString()
+  );
+
+  // process our fake issue list
+  await processor.processIssues(1);
+
+  expect(processor.staleIssues.length).toEqual(0);
+  expect(processor.closedIssues.length).toEqual(1);
+});
+
 test('processing a stale PR will close it', async () => {
   const TestIssueList: Issue[] = [
     generateIssue(
@@ -648,6 +707,38 @@ test('stale label should not be removed if a comment was added by the bot (and t
   expect(processor.removedLabelIssues.length).toEqual(0);
 });
 
+test('stale label containing a space should be removed if a comment was added to a stale issue', async () => {
+  const TestIssueList: Issue[] = [
+    generateIssue(
+      1,
+      'An issue that should un-stale',
+      '2020-01-01T17:00:00Z',
+      false,
+      ['stat: stale']
+    )
+  ];
+
+  const opts: IssueProcessorOptions = {
+    ...DefaultProcessorOptions,
+    removeStaleWhenUpdated: true,
+    staleIssueLabel: 'stat: stale'
+  };
+
+  const processor = new IssueProcessor(
+    opts,
+    async p => (p == 1 ? TestIssueList : []),
+    async (num, dt) => [{user: {login: 'notme', type: 'User'}}], // return a fake comment to indicate there was an update
+    async (issue, label) => new Date().toDateString()
+  );
+
+  // process our fake issue list
+  await processor.processIssues(1);
+
+  expect(processor.closedIssues.length).toEqual(0);
+  expect(processor.staleIssues.length).toEqual(0);
+  expect(processor.removedLabelIssues.length).toEqual(1);
+});
+
 test('stale issues should not be closed until after the closed number of days', async () => {
   let lastUpdate = new Date();
   lastUpdate.setDate(lastUpdate.getDate() - 5);
@@ -895,3 +986,59 @@ test('not providing stalePrMessage takes precedence over skipStalePrMessage', as
   expect(processor.removedLabelIssues.length).toEqual(0);
   expect(processor.staleIssues.length).toEqual(0);
 });
+
+test('git branch is deleted when option is enabled', async () => {
+  const opts = {...DefaultProcessorOptions, deleteBranch: true};
+  const isPullRequest = true;
+  const TestIssueList: Issue[] = [
+    generateIssue(
+      1,
+      'An issue that should have its branch deleted',
+      '2020-01-01T17:00:00Z',
+      isPullRequest,
+      ['Stale']
+    )
+  ];
+
+  const processor = new IssueProcessor(
+    opts,
+    async p => (p == 1 ? TestIssueList : []),
+    async (num, dt) => [],
+    async (issue, label) => new Date().toDateString()
+  );
+
+  await processor.processIssues(1);
+
+  expect(processor.closedIssues.length).toEqual(1);
+  expect(processor.removedLabelIssues.length).toEqual(0);
+  expect(processor.staleIssues.length).toEqual(0);
+  expect(processor.deletedBranchIssues.length).toEqual(1);
+});
+
+test('git branch is not deleted when issue is not pull request', async () => {
+  const opts = {...DefaultProcessorOptions, deleteBranch: true};
+  const isPullRequest = false;
+  const TestIssueList: Issue[] = [
+    generateIssue(
+      1,
+      'An issue that should not have its branch deleted',
+      '2020-01-01T17:00:00Z',
+      isPullRequest,
+      ['Stale']
+    )
+  ];
+
+  const processor = new IssueProcessor(
+    opts,
+    async p => (p == 1 ? TestIssueList : []),
+    async (num, dt) => [],
+    async (issue, label) => new Date().toDateString()
+  );
+
+  await processor.processIssues(1);
+
+  expect(processor.closedIssues.length).toEqual(1);
+  expect(processor.removedLabelIssues.length).toEqual(0);
+  expect(processor.staleIssues.length).toEqual(0);
+  expect(processor.deletedBranchIssues.length).toEqual(0);
+});