Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create interface for retrieving git version information #850

Merged
merged 3 commits into from
Sep 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silly-actors-peel.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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';