Repository: steveukx/git-js (ts)

Issue:
The first commit returns more than the ID in the CommitSummary.commit
This following behavior is an edge case only for a first (root) commit.  

**Expected:**
`const co = await git.add(".").commit("first commit");`
should return the commitId in the CommitSummary.commit field.
```
CommitSummary {
  branch: 'master',
  commit: 'a78979d',
  summary: { changes: 2, insertions: 2, deletions: 0 },
  author: null
}
```

**Actual:**
```
CommitSummary {
  branch: 'master',
  commit: '(root-commit) a78979d',
  summary: { changes: 2, insertions: 2, deletions: 0 },
  author: null
}
```

The reason is the git output:
```
09:36 # git commit -m "hui"
[master (root-commit) 1153df9] hui
 1 file changed, 0 insertions(+), 0 deletions(-)
```

Feature or bug?

Pull request description:
Feature: handle initial commit
- Adds support for detecting the `(root-commit)` token in response to a commit, representing a commit with no parent (ie: the initial commit).
- Renames `CommitSummary` to `CommitResult` (`CommitSummary` is now a deprecated alias of `CommitResult`)

Closes #520 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2743b3e..d720e52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
 
 # Change History & Release Notes
 
+## 2.31.0 Handle 'root' commit syntax  
+
+- Adds a `root: boolean` property to the `CommitResult` interface representing whether the commit was a 'root' commit
+  (which is a commit that has no parent, most commonly the first commit in a repo).
+
 ## 2.30.0 Restore compatibility with Node.js v10
 
 - Reinstates native support for node.js v10 by removing use of ES6 constructs
diff --git a/promise.d.ts b/promise.d.ts
index 6fe4326..ebecc0d 100644
--- a/promise.d.ts
+++ b/promise.d.ts
@@ -2,7 +2,6 @@ import * as errors from './typings/errors';
 import * as types from './typings/types';
 import * as resp from './typings/response';
 import * as simpleGit from './typings/simple-git';
-import { MergeResult } from './typings/response';
 
 declare const simplegit: simplegit.SimpleGitExport;
 
@@ -26,7 +25,7 @@ declare namespace simplegit {
    type CleanMode = types.CleanMode;
    type DiffResult = resp.DiffResult;
    type FetchResult = resp.FetchResult;
-   type CommitSummary = resp.CommitSummary;
+   type CommitResult = resp.CommitResult;
    type MergeResult = resp.MergeResult;
    type PullResult = resp.PullResult;
    type StatusResult = resp.StatusResult;
@@ -40,6 +39,8 @@ declare namespace simplegit {
    // deprecated
    /** @deprecated use MergeResult */
    type MergeSummary = resp.MergeSummary;
+   /** @deprecated use CommitResult */
+   type CommitSummary = resp.CommitResult;
 }
 
 export = simplegit;
diff --git a/src/lib/parsers/parse-commit.ts b/src/lib/parsers/parse-commit.ts
index cd40114..206d4d8 100644
--- a/src/lib/parsers/parse-commit.ts
+++ b/src/lib/parsers/parse-commit.ts
@@ -1,10 +1,11 @@
-import { CommitSummary } from '../../../typings';
+import { CommitResult } from '../../../typings';
 import { LineParser, parseStringResponse } from '../utils';
 
-const parsers: LineParser<CommitSummary>[] = [
-   new LineParser(/\[([^\s]+) ([^\]]+)/, (result, [branch, commit]) => {
+const parsers: LineParser<CommitResult>[] = [
+   new LineParser(/\[([^\s]+)( \([^)]+\))? ([^\]]+)/, (result, [branch, root, commit]) => {
       result.branch = branch;
       result.commit = commit;
+      result.root = !!root;
    }),
    new LineParser(/\s*Author:\s(.+)/i, (result, [author]) => {
       const parts = author.split('<');
@@ -29,18 +30,18 @@ const parsers: LineParser<CommitSummary>[] = [
       const count = parseInt(lines, 10) || 0;
       if (direction === '-') {
          result.summary.deletions = count;
-      }
-      else if (direction === '+') {
+      } else if (direction === '+') {
          result.summary.insertions = count;
       }
    }),
 ];
 
-export function parseCommitResult(stdOut: string): CommitSummary {
-   const result: CommitSummary = {
+export function parseCommitResult(stdOut: string): CommitResult {
+   const result: CommitResult = {
       author: null,
       branch: '',
       commit: '',
+      root: false,
       summary: {
          changes: 0,
          insertions: 0,
diff --git a/src/lib/tasks/commit.ts b/src/lib/tasks/commit.ts
index 1ead1c9..783beb2 100644
--- a/src/lib/tasks/commit.ts
+++ b/src/lib/tasks/commit.ts
@@ -1,8 +1,8 @@
-import { CommitSummary } from '../../../typings';
+import { CommitResult } from '../../../typings';
 import { parseCommitResult } from '../parsers/parse-commit';
 import { StringTask } from '../types';
 
-export function commitTask(message: string[], files: string[], customArgs: string[]): StringTask<CommitSummary> {
+export function commitTask(message: string[], files: string[], customArgs: string[]): StringTask<CommitResult> {
    const commands: string[] = ['commit'];
 
    message.forEach((m) => commands.push('-m', m));
diff --git a/typings/response.d.ts b/typings/response.d.ts
index 9bb9bc2..8f3f4e6 100644
--- a/typings/response.d.ts
+++ b/typings/response.d.ts
@@ -68,13 +68,14 @@ export interface CleanSummary {
    folders: string[];
 }
 
-export interface CommitSummary {
+export interface CommitResult {
    author: null | {
       email: string;
       name: string;
    };
    branch: string;
    commit: string;
+   root: boolean;
    summary: {
       changes: number;
       insertions: number;
@@ -422,6 +423,12 @@ export interface PushDetail {
 export interface PushResult extends PushDetail, RemoteMessageResult<PushResultRemoteMessages> {
 }
 
+/**
+ * @deprecated
+ * For consistent naming, please use `CommitResult` instead of `CommitSummary`
+ */
+export type CommitSummary = CommitResult;
+
 /**
  * @deprecated
  * For consistent naming, please use `MergeResult` instead of `MergeSummary`
diff --git a/typings/simple-git.d.ts b/typings/simple-git.d.ts
index 0573e22..d39f4ce 100644
--- a/typings/simple-git.d.ts
+++ b/typings/simple-git.d.ts
@@ -184,21 +184,21 @@ export interface SimpleGit {
       message: string | string[],
       files?: string | string[],
       options?: types.Options,
-      callback?: types.SimpleGitTaskCallback<resp.CommitSummary>): Response<resp.CommitSummary>;
+      callback?: types.SimpleGitTaskCallback<resp.CommitResult>): Response<resp.CommitResult>;
 
    commit(
       message: string | string[],
       options?: types.TaskOptions,
-      callback?: types.SimpleGitTaskCallback<resp.CommitSummary>): Response<resp.CommitSummary>;
+      callback?: types.SimpleGitTaskCallback<resp.CommitResult>): Response<resp.CommitResult>;
 
    commit(
       message: string | string[],
       files?: string | string[],
-      callback?: types.SimpleGitTaskCallback<resp.CommitSummary>): Response<resp.CommitSummary>;
+      callback?: types.SimpleGitTaskCallback<resp.CommitResult>): Response<resp.CommitResult>;
 
    commit(
       message: string | string[],
-      callback?: types.SimpleGitTaskCallback<resp.CommitSummary>): Response<resp.CommitSummary>;
+      callback?: types.SimpleGitTaskCallback<resp.CommitResult>): Response<resp.CommitResult>;
 
    /**
     * Sets the path to a custom git binary, should either be `git` when there is an installation of git available on


Test patch:
diff --git a/test/unit/__fixtures__/index.ts b/test/unit/__fixtures__/index.ts
index ce4c1d2..9d5590d 100644
--- a/test/unit/__fixtures__/index.ts
+++ b/test/unit/__fixtures__/index.ts
@@ -2,6 +2,7 @@
 export * from './child-processes';
 export * from './expectations';
 
+export * from './responses/commit';
 export * from './responses/diff';
 export * from './responses/merge';
 export * from './responses/remote-messages';
diff --git a/test/unit/__fixtures__/responses/commit.ts b/test/unit/__fixtures__/responses/commit.ts
new file mode 100644
index 0000000..8048d61
--- /dev/null
+++ b/test/unit/__fixtures__/responses/commit.ts
@@ -0,0 +1,26 @@
+export const commitResultSingleFile = `
+
+[foo 8f7d107] done
+Author: Some Author <some@author.com>
+1 files changed, 2 deletions(-)
+
+      `;
+
+export const commitResultNoneStaged = `
+On branch master
+        Your branch is ahead of 'origin/master' by 1 commit.
+           (use "git push" to publish your local commits)
+
+        Changes not staged for commit:
+           modified:   src/some-file.js
+           modified:   src/another-file.js
+        no changes added to commit
+        `;
+
+export function commitToRepoRoot ({message = 'Commit Message', hash = 'b13bdd8', fileName = 'file-name'} = {}) {
+   return `
+[master (root-commit) ${hash}] ${message}
+ 1 file changed, 1 insertion(+)
+ create mode 100644 ${fileName}
+`;
+}
diff --git a/test/unit/commit.spec.ts b/test/unit/commit.spec.ts
index 52c0791..6e97fc5 100644
--- a/test/unit/commit.spec.ts
+++ b/test/unit/commit.spec.ts
@@ -1,4 +1,12 @@
-import { assertExecutedCommands, closeWithSuccess, like, newSimpleGit } from './__fixtures__';
+import {
+   assertExecutedCommands,
+   closeWithSuccess,
+   commitResultNoneStaged,
+   commitResultSingleFile,
+   commitToRepoRoot,
+   like,
+   newSimpleGit
+} from './__fixtures__';
 import { SimpleGit } from '../../typings';
 import { parseCommitResult } from '../../src/lib/parsers/parse-commit';
 
@@ -72,29 +80,13 @@ describe('commit', () => {
    });
 
    describe('parsing', () => {
-      const commitResultSingleFile = `
-
-[foo 8f7d107] done
-Author: Some Author <some@author.com>
-1 files changed, 2 deletions(-)
-
-      `;
-
-      const commitResultNoneStaged = `On branch master
-        Your branch is ahead of 'origin/master' by 1 commit.
-           (use "git push" to publish your local commits)
-
-        Changes not staged for commit:
-           modified:   src/some-file.js
-           modified:   src/another-file.js
-        no changes added to commit
-        `;
 
       it('handles no files staged', () => {
          expect(parseCommitResult(commitResultNoneStaged)).toEqual({
             author: null,
             branch: '',
             commit: '',
+            root: false,
             summary: {
                changes: 0,
                insertions: 0,
@@ -132,9 +124,19 @@ Author: Some Author <some@author.com>
          expect(parseCommitResult(`[branchNameInHere CommitHash] Add nodeunit test runner`)).toEqual(like({
             branch: 'branchNameInHere',
             commit: 'CommitHash',
+            root: false,
          }));
       });
 
+      it('handles the root commit', () => {
+         const actual = parseCommitResult(commitToRepoRoot({hash: 'foo', message: 'bar'}));
+         expect(actual).toEqual(like({
+            branch: 'master',
+            commit: 'foo',
+            root: true
+         }));
+      })
+
    });
 
 });