Skip to content

Commit ece83fc

Browse files
author
Akos Kitta
committed
GH-11: Adjusted the lookup to the latest VS Code.
- [win32]: Use `which` when cannot find Git on the `$PATH`. - Moved the Windows build to Travis. - Pinned the versions with `yarn.lock`. - Bumped up the dependencies. Closes #11. Signed-off-by: Akos Kitta <[email protected]>
1 parent 985e5da commit ece83fc

11 files changed

+996
-146
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
22
node_modules
33
lib
4-
*.log
4+
*.log
5+
package-lock.json

.travis.yml

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
language: node_js
22

3+
env:
4+
# Fix Windows build never ending in Travis CI
5+
# https://travis-ci.community/t/timeout-after-build-finished-and-succeeded/1336
6+
- YARN_GPG=no
7+
38
os:
49
- linux
510
- osx
11+
- windows
612

713
node_js:
8-
- '8'
9-
- '7'
10-
- '6'
14+
- 10
1115

1216
git:
1317
depth: 1
@@ -17,7 +21,7 @@ branches:
1721
- master
1822

1923
install:
20-
- npm i
24+
- yarn
2125

2226
script:
23-
- npm run build && npm run test
27+
- yarn build && yarn test

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
# find-git-exec
22
[![Build Status](https://travis-ci.org/TypeFox/find-git-exec.svg?branch=master)](https://travis-ci.org/TypeFox/find-git-exec)
3-
[![Build status](https://ci.appveyor.com/api/projects/status/8x9w27df2bit7jan/branch/master?svg=true)](https://ci.appveyor.com/project/kittaakos/find-git-exec/branch/master)
43

54
A lightweight library for locating the Git executable on the host system.
5+
This library is a stripped down version of the Git discovery logic [implemented and used by VS Code](https://github.com/microsoft/vscode/blob/master/extensions/git/src/git.ts#L50-L141).
66

77
## Install
88
```bash
9-
npm i -S find-git-exec
9+
yarn add find-git-exec
1010
```
1111

1212
## Build
1313
```bash
14-
npm run build
14+
yarn build
1515
```
1616

1717
## Test
1818
```bash
19-
npm run test
19+
yarn test
2020
```
2121

2222
## Example

appveyor.yml

-24
This file was deleted.

mocha.opts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
--require ts-node/register
22
--reporter spec
3-
--watch-extensions ts
3+
--watch-extensions ts

package.json

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
{
22
"name": "find-git-exec",
3-
"version": "0.0.1-alpha.2",
3+
"version": "0.0.2",
44
"description": "A lightweight library for locating the Git executable on the host system",
55
"keywords": [
66
"git"
77
],
8+
"engines": {
9+
"node": ">=10.11.0 <12"
10+
},
811
"repository": {
912
"type": "git",
1013
"url": "https://github.com/TypeFox/find-git-exec.git"
@@ -27,17 +30,19 @@
2730
"author": "typefox <[email protected]>",
2831
"license": "MIT",
2932
"dependencies": {
30-
"@types/node": "^8.0.26"
33+
"@types/node": "^10.14.22",
34+
"@types/which": "^1.3.2",
35+
"which": "^2.0.1"
3136
},
3237
"devDependencies": {
33-
"@types/chai": "^4.0.4",
34-
"@types/mocha": "^2.2.42",
35-
"chai": "^4.1.2",
36-
"mocha": "^3.5.0",
37-
"rimraf": "^2.6.1",
38-
"ts-node": "^3.3.0",
39-
"tslint": "^5.7.0",
40-
"tslint-no-unused-expression-chai": "0.0.2",
41-
"typescript": "^2.5.2"
38+
"@types/chai": "^4.2.4",
39+
"@types/mocha": "^5.2.7",
40+
"chai": "^4.2.0",
41+
"mocha": "^6.2.2",
42+
"rimraf": "^3.0.0",
43+
"ts-node": "^8.4.1",
44+
"tslint": "^6.0.0-beta0",
45+
"tslint-no-unused-expression-chai": "^0.1.4",
46+
"typescript": "^3.1.3"
4247
}
4348
}

src/find-git-exec.spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import findGit from './find-git-exec';
1010
describe('find-git-exec', async () => {
1111

1212
it('find', async () => {
13-
const git = await findGit();
13+
const git = await findGit({ hint: undefined, onLookup: (p: string) => console.log(`[TRACE]: Git discovery: ${p}`) });
1414
const { path, version, execPath } = git;
15-
expect(fs.existsSync(path)).to.be.true;
16-
expect(fs.existsSync(execPath)).to.be.true;
17-
expect(version.startsWith('2')).to.be.true;
15+
expect(fs.existsSync(path), `[path]: expected ${path} to exist on the filesystem`).to.be.true;
16+
expect(fs.existsSync(execPath), `[execPath]: expected ${execPath} to exist on the filesystem`).to.be.true;
17+
expect(version.startsWith('2'), `[version]: expected version 2.x was ${version} instead`).to.be.true;
1818
});
1919

2020
});

src/find-git-exec.ts

+103-71
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,77 @@
33
* See the LICENSE file in the project root for license information.
44
*/
55

6-
import * as fs from 'fs';
6+
// The current implementation is based on https://github.com/microsoft/vscode/blob/master/extensions/git/src/git.ts#L50-L141
7+
/*---------------------------------------------------------------------------------------------
8+
* Copyright (c) Microsoft Corporation. All rights reserved.
9+
* Licensed under the MIT License. See License.txt in the project root for license information.
10+
*--------------------------------------------------------------------------------------------*/
11+
712
import * as path from 'path';
13+
import * as which from 'which';
814
import * as cp from 'child_process';
915

1016
/**
11-
* Bare minimum information of the locally available git executable.
17+
* Bare minimum information of the locally available Git executable.
1218
*/
1319
export interface Git {
1420

1521
/**
1622
* The FS path to the Git executable.
1723
*/
18-
readonly path: string,
24+
readonly path: string;
1925
/**
2026
* The Git version. [`git --version`]
2127
*/
22-
readonly version: string,
28+
readonly version: string;
2329
/**
2430
* The path to wherever your core Git programs are installed. [`git --exec-path`]
2531
*/
26-
readonly execPath: string
32+
readonly execPath: string;
2733

2834
}
2935

3036
/**
3137
* Resolves to the path of the locally available Git executable. Will be rejected if Git cannot be found on the system.
38+
* `hint` can be provided as the initial lookup path, and `onLookup` function is used for logging during the Git discovery.
3239
*/
33-
export default function (): Promise<Git> {
34-
switch (process.platform) {
35-
case 'win32': return findGitWin32();
36-
default: return findGitNix();
37-
}
40+
export default async function ({ hint, onLookup }: { hint: string | undefined, onLookup: (path: string) => void } = { hint: undefined, onLookup: () => { } }): Promise<Git> {
41+
const first = hint ? findSpecificGit(hint, onLookup) : Promise.reject<Git>(null);
42+
return first
43+
.then(undefined, () => {
44+
switch (process.platform) {
45+
case 'darwin': return findGitDarwin(onLookup);
46+
case 'win32': return findGitWin32(onLookup);
47+
default: return which('git').then(gitOnPath => findSpecificGit(gitOnPath, onLookup));
48+
}
49+
})
50+
.then(null, () => Promise.reject(new Error('Git installation not found.')));
3851
}
3952

40-
async function findGit(path: string): Promise<Git> {
41-
return new Promise<Git>(async (resolve, reject) => {
42-
try {
43-
resolve({
44-
path,
45-
version: parseVersion(await exec(path, '--version')),
46-
execPath: normalizePath(await exec(path, '--exec-path'))
47-
});
48-
} catch (error) {
49-
reject(error);
50-
}
53+
function toUtf8String(buffers: Buffer[]): string {
54+
return Buffer.concat(buffers).toString('utf8').trim();
55+
}
56+
57+
function parseVersion(raw: string): string {
58+
return raw.replace(/^git version /, '');
59+
}
60+
61+
function normalizePath(pathToNormalize: string): string {
62+
return path.normalize(pathToNormalize);
63+
}
64+
65+
function findSpecificGit(path: string, onLookup: (path: string) => void): Promise<Git> {
66+
return new Promise<Git>((resolve, reject) => {
67+
onLookup(path);
68+
69+
Promise.all([
70+
exec(path, '--version'),
71+
exec(path, '--exec-path')
72+
]).then(([version, execPath]) => {
73+
resolve({ path, version: parseVersion(version), execPath: normalizePath(execPath) });
74+
}).catch(e => {
75+
reject(e);
76+
})
5177
});
5278
}
5379

@@ -61,69 +87,75 @@ async function exec(path: string, command: string | string[]): Promise<string> {
6187
});
6288
}
6389

64-
async function findGitNix(): Promise<Git> {
65-
return new Promise<Git>((resolve, reject) => {
66-
cp.exec('which git', (error, buffer) => {
67-
if (error) {
68-
return reject('Git not found.');
90+
function findGitDarwin(onLookup: (path: string) => void): Promise<Git> {
91+
return new Promise<Git>((c, e) => {
92+
cp.exec('which git', (err, gitPathBuffer) => {
93+
if (err) {
94+
return e('git not found');
6995
}
70-
const path = buffer.toString().replace(/^\s+|\s+$/g, '');
71-
if (path !== 'usr/bin/git' || process.platform !== 'darwin') {
72-
return resolve(findGit(path));
96+
97+
const path = gitPathBuffer.toString().replace(/^\s+|\s+$/g, '');
98+
99+
function getVersion(path: string) {
100+
onLookup(path);
101+
102+
// make sure git executes
103+
cp.exec('git --version', (err, stdout) => {
104+
105+
if (err) {
106+
return e('git not found');
107+
}
108+
109+
const version = parseVersion(stdout.trim());
110+
cp.exec('git --exec-path', (err, stdout) => {
111+
112+
if (err) {
113+
return e('git not found');
114+
}
115+
116+
const execPath = normalizePath(stdout.trim());
117+
return c({ path, version, execPath });
118+
});
119+
120+
});
73121
}
74-
cp.exec('xcode-select -p', (error: any) => {
75-
if (error && error.code === 2) {
76-
return reject(new Error('Git not found.'));
122+
123+
if (path !== '/usr/bin/git') {
124+
return getVersion(path);
125+
}
126+
127+
// must check if XCode is installed
128+
cp.exec('xcode-select -p', (err: any) => {
129+
if (err && err.code === 2) {
130+
// git is not installed, and launching /usr/bin/git
131+
// will prompt the user to install it
132+
133+
return e('git not found');
77134
}
78-
resolve(findGit(path));
135+
136+
getVersion(path);
79137
});
80138
});
81139
});
82140
}
83141

84-
async function findSystemGitWin32(base: string): Promise<Git> {
142+
function findSystemGitWin32(base: string, onLookup: (path: string) => void): Promise<Git> {
85143
if (!base) {
86-
throw new Error(`Git not found.`);
144+
return Promise.reject<Git>('Not found');
87145
}
88-
return findGit(path.join(base, 'Git', 'cmd', 'git.exe'));
89-
}
90146

91-
async function findGitHubGitWin32(): Promise<Git> {
92-
const github = path.join(env('LOCALAPPDATA'), 'GitHub');
93-
return new Promise<Git>((resolve, reject) => {
94-
fs.readdir(github, (error: NodeJS.ErrnoException, files: string[]) => {
95-
if (error) {
96-
return reject(error);
97-
}
98-
const git = files.filter(file => /^PortableGit/.test(file)).shift();
99-
if (!git) {
100-
return reject(new Error(`Git not found.`));
101-
}
102-
resolve(findGit(path.join(github, git, 'cmd', 'git.exe')));
103-
})
104-
});
105-
}
106-
107-
async function findGitWin32(): Promise<Git> {
108-
return findSystemGitWin32(env('ProgramW6432'))
109-
.then(undefined, () => findSystemGitWin32(env('ProgramFiles(x86)'))
110-
.then(undefined, () => findSystemGitWin32(env('ProgramFiles'))
111-
.then(undefined, () => findGit('git'))
112-
.then(undefined, () => findGitHubGitWin32())));
113-
}
114-
115-
function toUtf8String(buffers: Buffer[]): string {
116-
return Buffer.concat(buffers).toString('utf8').trim();
147+
return findSpecificGit(path.join(base, 'Git', 'cmd', 'git.exe'), onLookup);
117148
}
118149

119-
function parseVersion(raw: string): string {
120-
return raw.replace(/^git version /, '');
150+
async function findGitWin32InPath(onLookup: (path: string) => void): Promise<Git> {
151+
const whichPromise = new Promise<string>((c, e) => which('git.exe', (err, path) => err ? e(err) : c(path)));
152+
return whichPromise.then(path => findSpecificGit(path, onLookup));
121153
}
122154

123-
function normalizePath(pathToNormalize: string): string {
124-
return path.normalize(pathToNormalize);
155+
async function findGitWin32(onLookup: (path: string) => void): Promise<Git> {
156+
return findSystemGitWin32(process.env['ProgramW6432'] as string, onLookup)
157+
.then(undefined, () => findSystemGitWin32(process.env['ProgramFiles(x86)'] as string, onLookup))
158+
.then(undefined, () => findSystemGitWin32(process.env['ProgramFiles'] as string, onLookup))
159+
.then(undefined, () => findSystemGitWin32(path.join(process.env['LocalAppData'] as string, 'Programs'), onLookup))
160+
.then(undefined, () => findGitWin32InPath(onLookup));
125161
}
126-
127-
function env(key: string): string {
128-
return process.env[key] || '';
129-
}

0 commit comments

Comments
 (0)