Skip to content

Commit 4259b26

Browse files
authored
Create interface for retrieving git version information (#850)
* Create interface for retrieving git version information
1 parent 19029fc commit 4259b26

File tree

10 files changed

+170
-11
lines changed

10 files changed

+170
-11
lines changed

.changeset/silly-actors-peel.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'simple-git': minor
3+
---
4+
5+
Add `.version` to return git version information, including whether the git binary is installed.

examples/git-version.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Check if git is installed
2+
3+
To check if `git` (or the `customBinary` of your choosing) is accessible, use the
4+
`git.version()` api:
5+
6+
```typescript
7+
import { simpleGit } from 'simple-git';
8+
9+
const {installed} = await simpleGit().version();
10+
if (!installed) {
11+
throw new Error(`Exit: "git" not available.`);
12+
}
13+
14+
// ... continue using git commands here
15+
```
16+
17+
## Check for a specific version of git
18+
19+
Using the `git.version()` interface, you can query for the current `git` version
20+
information split by `major`, `minor` and `patch`:
21+
22+
```typescript
23+
import { simpleGit } from 'simple-git';
24+
import { lt } from 'semver';
25+
26+
const versionResult = await simpleGit().version();
27+
if (lt(String(versionResult), '2.1.0')) {
28+
throw new Error(`Exit: "git" must be at least version 2.1.0.`);
29+
}
30+
31+
// ... continue using git commands here compatible with 2.1.0 or higher
32+
```

simple-git/readme.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,13 @@ For type details of the response for each of the tasks, please see the [TypeScri
387387

388388
## git stash
389389

390-
- `.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.
390+
- `.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.
391391

392-
- `.stashList([ options ])` Retrieves the stash list, optional first argument can be an object in the same format as used in [git log](#git-log).
392+
- `.stashList([ options ])` Retrieves the stash list, optional first argument can be an object in the same format as used in [git log](#git-log).
393+
394+
## git version [examples](https://github.com/steveukx/git-js/blob/main/examples/git-version.md)
395+
396+
- `.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.
393397

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

simple-git/src/lib/simple-git-api.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { mergeTask } from './tasks/merge';
1111
import { pushTask } from './tasks/push';
1212
import { statusTask } from './tasks/status';
1313
import { configurationErrorTask, straightThroughStringTask } from './tasks/task';
14+
import version from './tasks/version';
1415
import { outputHandler, SimpleGitExecutor, SimpleGitTask, SimpleGitTaskCallback } from './types';
1516
import {
1617
asArray,
@@ -136,4 +137,4 @@ export class SimpleGitApi implements SimpleGitBase {
136137
}
137138
}
138139

139-
Object.assign(SimpleGitApi.prototype, commit(), config(), grep(), log());
140+
Object.assign(SimpleGitApi.prototype, commit(), config(), grep(), log(), version());

simple-git/src/lib/tasks/config.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ConfigGetResult, ConfigListSummary, SimpleGit } from '../../../typings';
1+
import type { ConfigGetResult, ConfigListSummary, SimpleGit } from '../../../typings';
22
import { configGetParser, configListParser } from '../responses/ConfigList';
3-
import { SimpleGitApi } from '../simple-git-api';
4-
import { StringTask } from '../types';
3+
import type { SimpleGitApi } from '../simple-git-api';
4+
import type { StringTask } from '../types';
55
import { trailingFunctionArgument } from '../utils';
66

77
export enum GitConfigScope {

simple-git/src/lib/tasks/version.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { SimpleGitApi } from '../simple-git-api';
2+
import type { SimpleGit } from '../../../typings';
3+
import { asNumber, ExitCodes } from '../utils';
4+
5+
export interface VersionResult {
6+
major: number;
7+
minor: number;
8+
patch: number;
9+
agent: string;
10+
installed: boolean;
11+
}
12+
13+
const NOT_INSTALLED = 'installed=false';
14+
15+
function versionResponse(
16+
major = 0,
17+
minor = 0,
18+
patch = 0,
19+
agent = '',
20+
installed = true
21+
): VersionResult {
22+
return Object.defineProperty(
23+
{
24+
major,
25+
minor,
26+
patch,
27+
agent,
28+
installed,
29+
},
30+
'toString',
31+
{
32+
value() {
33+
return `${major}.${minor}.${patch}`;
34+
},
35+
configurable: false,
36+
enumerable: false,
37+
}
38+
);
39+
}
40+
41+
function notInstalledResponse() {
42+
return versionResponse(0, 0, 0, '', false);
43+
}
44+
45+
export default function (): Pick<SimpleGit, 'version'> {
46+
return {
47+
version(this: SimpleGitApi) {
48+
return this._runTask({
49+
commands: ['--version'],
50+
format: 'utf-8',
51+
parser(stdOut) {
52+
if (stdOut === NOT_INSTALLED) {
53+
return notInstalledResponse();
54+
}
55+
56+
const version = /version (\d+)\.(\d+)\.(\d+)(?:\s*\((.+)\))?/.exec(stdOut);
57+
58+
if (!version) {
59+
return versionResponse(0, 0, 0, stdOut);
60+
}
61+
62+
return versionResponse(
63+
asNumber(version[1]),
64+
asNumber(version[2]),
65+
asNumber(version[3]),
66+
version[4] || ''
67+
);
68+
},
69+
onError(result, error, done, fail) {
70+
if (result.exitCode === ExitCodes.NOT_FOUND) {
71+
return done(Buffer.from(NOT_INSTALLED));
72+
}
73+
74+
fail(error);
75+
},
76+
});
77+
},
78+
};
79+
}

simple-git/src/lib/utils/exit-codes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
export enum ExitCodes {
66
SUCCESS,
77
ERROR,
8+
NOT_FOUND = -2,
89
UNCLEAN = 128,
910
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createTestContext, newSimpleGit, SimpleGitTestContext } from '@simple-git/test-utils';
2+
3+
describe('version', () => {
4+
let context: SimpleGitTestContext;
5+
6+
beforeEach(async () => (context = await createTestContext()));
7+
8+
it('gets the current version', async () => {
9+
const git = newSimpleGit(context.root);
10+
expect(await git.version()).toEqual({
11+
major: 2,
12+
minor: expect.any(Number),
13+
patch: expect.any(Number),
14+
agent: expect.any(String),
15+
installed: true,
16+
});
17+
});
18+
19+
it('gets the current version when the binary is not installed', async () => {
20+
const git = newSimpleGit(context.root).customBinary('bad');
21+
expect(await git.version()).toEqual({
22+
major: 0,
23+
minor: 0,
24+
patch: 0,
25+
agent: '',
26+
installed: false,
27+
});
28+
});
29+
});

simple-git/typings/simple-git.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -995,4 +995,11 @@ export interface SimpleGit extends SimpleGitBase {
995995
* Updates repository server info
996996
*/
997997
updateServerInfo(callback?: types.SimpleGitTaskCallback<string>): Response<string>;
998+
999+
/**
1000+
* Retrieves `git` version information, including whether `git` is installed on the `PATH`
1001+
*/
1002+
version(
1003+
callback?: types.SimpleGitTaskCallback<types.VersionResult>
1004+
): Response<types.VersionResult>;
9981005
}

simple-git/typings/types.d.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
export { RemoteWithoutRefs, RemoteWithRefs } from '../src/lib/responses/GetRemoteSummary';
2-
export { LogOptions, DefaultLogFields } from '../src/lib/tasks/log';
1+
export type { RemoteWithoutRefs, RemoteWithRefs } from '../src/lib/responses/GetRemoteSummary';
2+
export type { LogOptions, DefaultLogFields } from '../src/lib/tasks/log';
33

4-
export {
4+
export type {
55
outputHandler,
66
Options,
77
TaskOptions,
@@ -10,10 +10,11 @@ export {
1010
SimpleGitTaskCallback,
1111
} from '../src/lib/types';
1212

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

0 commit comments

Comments
 (0)