Repository: intuit/auto
Pull request #783 (2019-12-11 20:06:09)

omit commits that have already been released
Problem:

1. Develop on next
2. merge master into next (with new releases)
3. When merging next into master all of masters PRs/commits get included in changelog

Now:

The commits already on master will not be included in any calcs

<!-- GITHUB_RELEASE PR BODY: canary-version -->
Published PR with canary version: `8.0.0-canary.783.10271.0`
<!-- GITHUB_RELEASE PR BODY: canary-version -->



Patch:
diff --git a/package.json b/package.json
index 6253f415..076948c9 100644
--- a/package.json
+++ b/package.json
@@ -26,8 +26,7 @@
     "semver:check": "./scripts/post-install.sh",
     "build": "tsc -b tsconfig.dev.json",
     "start": "npm run build -- --watch",
-    "lint": "eslint packages plugins --ext .ts",
-    "precommit": "lint-staged",
+    "lint": "eslint packages plugins --ext .ts --cache",
     "test": "jest --runInBand",
     "test:coverage": "npm run test -- --coverage",
     "auto": "chmod +x ./packages/cli/dist/bin/auto.js && ./packages/cli/dist/bin/auto.js",
diff --git a/packages/core/src/release.ts b/packages/core/src/release.ts
index 1be5aa09..b2c3df4a 100644
--- a/packages/core/src/release.ts
+++ b/packages/core/src/release.ts
@@ -21,6 +21,7 @@ import SEMVER, { calculateSemVerBump, IVersionLabels } from './semver';
 import execPromise from './utils/exec-promise';
 import { dummyLog, ILogger } from './utils/logger';
 import { makeReleaseHooks } from './utils/make-hooks';
+import { execSync } from 'child_process';
 
 export type VersionLabel =
   | SEMVER.major
@@ -483,7 +484,16 @@ export default class Release {
     this.logger.veryVerbose.info('Got gitlog:\n', gitlog);
 
     const logParse = await this.createLogParse();
-    const commits = await logParse.normalizeCommits(gitlog);
+    const commits = (await logParse.normalizeCommits(gitlog)).filter(commit => {
+      const alreadyReleased = execSync(
+        `git merge-base --is-ancestor ${from} ${commit.hash}; echo $?`,
+        {
+          encoding: 'utf8'
+        }
+      ).trim();
+
+      return alreadyReleased !== '0';
+    });
 
     this.logger.veryVerbose.info('Added labels to commits:\n', commits);
 


Test patch:
diff --git a/packages/core/src/__tests__/__snapshots__/release.test.ts.snap b/packages/core/src/__tests__/__snapshots__/release.test.ts.snap
index 2ad945f6..7ba8c58b 100644
--- a/packages/core/src/__tests__/__snapshots__/release.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/release.test.ts.snap
@@ -288,6 +288,28 @@ Array [
 ]
 `;
 
+exports[`Release getCommits should omit commits that have already been released 1`] = `
+Array [
+  Object {
+    "authorEmail": "adam@dierkens.com",
+    "authorName": "Adam Dierkens",
+    "authors": Array [
+      Object {
+        "email": "adam@dierkens.com",
+        "name": "Adam Dierkens",
+      },
+    ],
+    "files": Array [],
+    "hash": "foo",
+    "labels": Array [],
+    "pullRequest": Object {
+      "number": 124,
+    },
+    "subject": "Feature",
+  },
+]
+`;
+
 exports[`Release getCommits should resolve authors with PR commits 1`] = `
 Array [
   Object {
diff --git a/packages/core/src/__tests__/auto-make-changelog.test.ts b/packages/core/src/__tests__/auto-make-changelog.test.ts
index 5173da6c..81ecda58 100644
--- a/packages/core/src/__tests__/auto-make-changelog.test.ts
+++ b/packages/core/src/__tests__/auto-make-changelog.test.ts
@@ -1,5 +1,12 @@
 import Auto from '../auto';
 import { dummyLog } from '../utils/logger';
+import child from 'child_process';
+
+jest
+  .spyOn(child, 'execSync')
+  .mockImplementation()
+  // @ts-ignore
+  .mockReturnValue('');
 
 const importMock = jest.fn();
 jest.mock('import-cwd', () => (path: string) => importMock(path));
diff --git a/packages/core/src/__tests__/auto.test.ts b/packages/core/src/__tests__/auto.test.ts
index c8fffefd..043fc109 100644
--- a/packages/core/src/__tests__/auto.test.ts
+++ b/packages/core/src/__tests__/auto.test.ts
@@ -4,12 +4,19 @@ import SEMVER from '../semver';
 import { dummyLog } from '../utils/logger';
 import makeCommitFromMsg from './make-commit-from-msg';
 import loadPlugin from '../utils/load-plugins';
+import child from 'child_process';
 
 const importMock = jest.fn();
 jest.mock('../utils/load-plugins.ts');
 jest.mock('import-cwd', () => (path: string) => importMock(path));
 jest.mock('env-ci', () => () => ({ isCi: false, branch: 'master' }));
 
+jest
+  .spyOn(child, 'execSync')
+  .mockImplementation()
+  // @ts-ignore
+  .mockReturnValue('');
+
 const defaults = {
   owner: 'foo',
   repo: 'bar',
diff --git a/packages/core/src/__tests__/release.test.ts b/packages/core/src/__tests__/release.test.ts
index 72ce0cbe..f26cff0b 100644
--- a/packages/core/src/__tests__/release.test.ts
+++ b/packages/core/src/__tests__/release.test.ts
@@ -10,6 +10,15 @@ import Release, {
 import SEMVER from '../semver';
 import { dummyLog } from '../utils/logger';
 import makeCommitFromMsg from './make-commit-from-msg';
+import child from 'child_process';
+
+const { execSync } = child;
+const exec = jest.fn();
+child.execSync = exec.mockReturnValue('');
+
+afterAll(() => {
+  child.execSync = execSync;
+});
 
 const constructor = jest.fn();
 const getGitLog = jest.fn();
@@ -145,16 +154,16 @@ describe('Release', () => {
     createLabel.mockClear();
   });
 
-  test('should use options owner, repo, and token', async () => {
-    const gh = new Release(git);
+  describe('getCommits', () => {
+    test('should use options owner, repo, and token', async () => {
+      const gh = new Release(git);
 
-    await gh.getCommits('12345');
+      await gh.getCommits('12345');
 
-    expect(constructor.mock.calls[0][0].owner).toBe('Andrew');
-    expect(constructor.mock.calls[0][0].repo).toBe('test');
-  });
+      expect(constructor.mock.calls[0][0].owner).toBe('Andrew');
+      expect(constructor.mock.calls[0][0].repo).toBe('test');
+    });
 
-  describe('getCommits', () => {
     test('should default to HEAD', async () => {
       const gh = new Release(git);
       await gh.getCommits('12345');
@@ -331,6 +340,34 @@ describe('Release', () => {
 
       expect(await gh.getCommits('12345', '1234')).toMatchSnapshot();
     });
+
+    test('should omit commits that have already been released', async () => {
+      const gh = new Release(git);
+
+      getLatestReleaseInfo.mockReturnValueOnce({
+        published_at: '2019-01-16'
+      });
+      searchRepo.mockReturnValueOnce({ items: [{ number: 123 }] });
+      getPullRequest.mockReturnValueOnce({
+        data: {
+          number: 123,
+          merge_commit_sha: '1a2b',
+          labels: [{ name: 'skip-release' }, { name: 'minor' }]
+        }
+      });
+      getGitLog.mockReturnValueOnce(
+        await logParse.normalizeCommits([
+          makeCommitFromMsg('Feature (#124)'),
+          makeCommitFromMsg('I was released previously', {
+            hash: '1a2b'
+          })
+        ])
+      );
+      exec.mockReturnValueOnce('1');
+      exec.mockReturnValueOnce('0');
+
+      expect(await gh.getCommits('12345', '1234')).toMatchSnapshot();
+    });
   });
 
   describe('addToChangelog', () => {