Skip to content

Commit

Permalink
Create interface for retrieving git version information (#850)
Browse files Browse the repository at this point in the history
* Create interface for retrieving git version information
  • Loading branch information
steveukx committed Sep 4, 2022
1 parent 19029fc commit 4259b26
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-actors-peel.md
@@ -0,0 +1,5 @@
---
'simple-git': minor
---

Add `.version` to return git version information, including whether the git binary is installed.
32 changes: 32 additions & 0 deletions examples/git-version.md
@@ -0,0 +1,32 @@
## Check if git is installed

To check if `git` (or the `customBinary` of your choosing) is accessible, use the
`git.version()` api:

```typescript
import { simpleGit } from 'simple-git';

const {installed} = await simpleGit().version();
if (!installed) {
throw new Error(`Exit: "git" not available.`);
}

// ... continue using git commands here
```

## Check for a specific version of git

Using the `git.version()` interface, you can query for the current `git` version
information split by `major`, `minor` and `patch`:

```typescript
import { simpleGit } from 'simple-git';
import { lt } from 'semver';

const versionResult = await simpleGit().version();
if (lt(String(versionResult), '2.1.0')) {
throw new Error(`Exit: "git" must be at least version 2.1.0.`);
}

// ... continue using git commands here compatible with 2.1.0 or higher
```
8 changes: 6 additions & 2 deletions simple-git/readme.md
Expand Up @@ -387,9 +387,13 @@ For type details of the response for each of the tasks, please see the [TypeScri

## git stash

- `.stash([ options ])` Stash the working directory, optional first argument can be an array of string arguments or [options](#how-to-specify-options) object to pass to the [git stash](https://git-scm.com/docs/git-stash) command.
- `.stash([ options ])` Stash the working directory, optional first argument can be an array of string arguments or [options](#how-to-specify-options) object to pass to the [git stash](https://git-scm.com/docs/git-stash) command.

- `.stashList([ options ])` Retrieves the stash list, optional first argument can be an object in the same format as used in [git log](#git-log).
- `.stashList([ options ])` Retrieves the stash list, optional first argument can be an object in the same format as used in [git log](#git-log).

## git version [examples](https://github.com/steveukx/git-js/blob/main/examples/git-version.md)

- `.version()` retrieve the major, minor and patch for the currently installed `git`. Use the `.installed` property of the result to determine whether `git` is accessible on the path.

## changing the working directory [examples](https://github.com/steveukx/git-js/blob/main/examples/git-change-working-directory.md)

Expand Down
3 changes: 2 additions & 1 deletion simple-git/src/lib/simple-git-api.ts
Expand Up @@ -11,6 +11,7 @@ import { mergeTask } from './tasks/merge';
import { pushTask } from './tasks/push';
import { statusTask } from './tasks/status';
import { configurationErrorTask, straightThroughStringTask } from './tasks/task';
import version from './tasks/version';
import { outputHandler, SimpleGitExecutor, SimpleGitTask, SimpleGitTaskCallback } from './types';
import {
asArray,
Expand Down Expand Up @@ -136,4 +137,4 @@ export class SimpleGitApi implements SimpleGitBase {
}
}

Object.assign(SimpleGitApi.prototype, commit(), config(), grep(), log());
Object.assign(SimpleGitApi.prototype, commit(), config(), grep(), log(), version());
6 changes: 3 additions & 3 deletions simple-git/src/lib/tasks/config.ts
@@ -1,7 +1,7 @@
import { ConfigGetResult, ConfigListSummary, SimpleGit } from '../../../typings';
import type { ConfigGetResult, ConfigListSummary, SimpleGit } from '../../../typings';
import { configGetParser, configListParser } from '../responses/ConfigList';
import { SimpleGitApi } from '../simple-git-api';
import { StringTask } from '../types';
import type { SimpleGitApi } from '../simple-git-api';
import type { StringTask } from '../types';
import { trailingFunctionArgument } from '../utils';

export enum GitConfigScope {
Expand Down
79 changes: 79 additions & 0 deletions simple-git/src/lib/tasks/version.ts
@@ -0,0 +1,79 @@
import type { SimpleGitApi } from '../simple-git-api';
import type { SimpleGit } from '../../../typings';
import { asNumber, ExitCodes } from '../utils';

export interface VersionResult {
major: number;
minor: number;
patch: number;
agent: string;
installed: boolean;
}

const NOT_INSTALLED = 'installed=false';

function versionResponse(
major = 0,
minor = 0,
patch = 0,
agent = '',
installed = true
): VersionResult {
return Object.defineProperty(
{
major,
minor,
patch,
agent,
installed,
},
'toString',
{
value() {
return `${major}.${minor}.${patch}`;
},
configurable: false,
enumerable: false,
}
);
}

function notInstalledResponse() {
return versionResponse(0, 0, 0, '', false);
}

export default function (): Pick<SimpleGit, 'version'> {
return {
version(this: SimpleGitApi) {
return this._runTask({
commands: ['--version'],
format: 'utf-8',
parser(stdOut) {
if (stdOut === NOT_INSTALLED) {
return notInstalledResponse();
}

const version = /version (\d+)\.(\d+)\.(\d+)(?:\s*\((.+)\))?/.exec(stdOut);

if (!version) {
return versionResponse(0, 0, 0, stdOut);
}

return versionResponse(
asNumber(version[1]),
asNumber(version[2]),
asNumber(version[3]),
version[4] || ''
);
},
onError(result, error, done, fail) {
if (result.exitCode === ExitCodes.NOT_FOUND) {
return done(Buffer.from(NOT_INSTALLED));
}

fail(error);
},
});
},
};
}
1 change: 1 addition & 0 deletions simple-git/src/lib/utils/exit-codes.ts
Expand Up @@ -5,5 +5,6 @@
export enum ExitCodes {
SUCCESS,
ERROR,
NOT_FOUND = -2,
UNCLEAN = 128,
}
29 changes: 29 additions & 0 deletions simple-git/test/integration/version.spec.ts
@@ -0,0 +1,29 @@
import { createTestContext, newSimpleGit, SimpleGitTestContext } from '@simple-git/test-utils';

describe('version', () => {
let context: SimpleGitTestContext;

beforeEach(async () => (context = await createTestContext()));

it('gets the current version', async () => {
const git = newSimpleGit(context.root);
expect(await git.version()).toEqual({
major: 2,
minor: expect.any(Number),
patch: expect.any(Number),
agent: expect.any(String),
installed: true,
});
});

it('gets the current version when the binary is not installed', async () => {
const git = newSimpleGit(context.root).customBinary('bad');
expect(await git.version()).toEqual({
major: 0,
minor: 0,
patch: 0,
agent: '',
installed: false,
});
});
});
7 changes: 7 additions & 0 deletions simple-git/typings/simple-git.d.ts
Expand Up @@ -995,4 +995,11 @@ export interface SimpleGit extends SimpleGitBase {
* Updates repository server info
*/
updateServerInfo(callback?: types.SimpleGitTaskCallback<string>): Response<string>;

/**
* Retrieves `git` version information, including whether `git` is installed on the `PATH`
*/
version(
callback?: types.SimpleGitTaskCallback<types.VersionResult>
): Response<types.VersionResult>;
}
11 changes: 6 additions & 5 deletions simple-git/typings/types.d.ts
@@ -1,7 +1,7 @@
export { RemoteWithoutRefs, RemoteWithRefs } from '../src/lib/responses/GetRemoteSummary';
export { LogOptions, DefaultLogFields } from '../src/lib/tasks/log';
export type { RemoteWithoutRefs, RemoteWithRefs } from '../src/lib/responses/GetRemoteSummary';
export type { LogOptions, DefaultLogFields } from '../src/lib/tasks/log';

export {
export type {
outputHandler,
Options,
TaskOptions,
Expand All @@ -10,10 +10,11 @@ export {
SimpleGitTaskCallback,
} from '../src/lib/types';

export { ApplyOptions } from '../src/lib/tasks/apply-patch';
export type { ApplyOptions } from '../src/lib/tasks/apply-patch';
export { CheckRepoActions } from '../src/lib/tasks/check-is-repo';
export { CleanOptions, CleanMode } from '../src/lib/tasks/clean';
export { CloneOptions } from '../src/lib/tasks/clone';
export type { CloneOptions } from '../src/lib/tasks/clone';
export { GitConfigScope } from '../src/lib/tasks/config';
export { GitGrepQuery, grepQueryBuilder } from '../src/lib/tasks/grep';
export { ResetOptions, ResetMode } from '../src/lib/tasks/reset';
export type { VersionResult } from '../src/lib/tasks/version';

0 comments on commit 4259b26

Please sign in to comment.