mirror of
https://github.com/kahole/edamagit.git
synced 2024-09-11 07:15:31 +03:00
move away from private vscode apis by absorbing their code (fixes #237)
This commit is contained in:
parent
2df2c6e9cd
commit
02841c5264
79
package-lock.json
generated
79
package-lock.json
generated
@ -6,17 +6,20 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "magit",
|
||||
"version": "0.6.34",
|
||||
"version": "0.6.35",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vscode/iconv-lite-umd": "^0.7.0",
|
||||
"date-fns": "^2.16.1",
|
||||
"jsonc-parser": "^3.0.0"
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"which": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/mocha": "^8.2.0",
|
||||
"@types/node": "^14.14.14",
|
||||
"@types/vscode": "^1.50.0",
|
||||
"@types/which": "^2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.10.0",
|
||||
"@typescript-eslint/parser": "^4.10.0",
|
||||
"eslint": "^7.16.0",
|
||||
@ -248,6 +251,12 @@
|
||||
"integrity": "sha512-Kt3bvWzAvvF/WH9YEcrCICDp0Z7aHhJGhLJ1BxeyNP6yRjonWqWnAIh35/pXAjswAnWOABrYlF7SwXR9+1nnLA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/which": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz",
|
||||
"integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.10.0.tgz",
|
||||
@ -435,6 +444,11 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/iconv-lite-umd": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz",
|
||||
"integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.1.tgz",
|
||||
@ -1065,6 +1079,21 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn/node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
|
||||
@ -2224,8 +2253,7 @@
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "26.6.2",
|
||||
@ -3887,18 +3915,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz",
|
||||
"integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
"node-which": "bin/which.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
@ -4261,6 +4288,12 @@
|
||||
"integrity": "sha512-Kt3bvWzAvvF/WH9YEcrCICDp0Z7aHhJGhLJ1BxeyNP6yRjonWqWnAIh35/pXAjswAnWOABrYlF7SwXR9+1nnLA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/which": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz",
|
||||
"integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.10.0.tgz",
|
||||
@ -4366,6 +4399,11 @@
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@vscode/iconv-lite-umd": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz",
|
||||
"integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg=="
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.1.tgz",
|
||||
@ -4892,6 +4930,17 @@
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
@ -5751,8 +5800,7 @@
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "26.6.2",
|
||||
@ -6979,10 +7027,9 @@
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz",
|
||||
"integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
|
@ -739,6 +739,7 @@
|
||||
"@types/mocha": "^8.2.0",
|
||||
"@types/node": "^14.14.14",
|
||||
"@types/vscode": "^1.50.0",
|
||||
"@types/which": "^2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.10.0",
|
||||
"@typescript-eslint/parser": "^4.10.0",
|
||||
"eslint": "^7.16.0",
|
||||
@ -752,7 +753,9 @@
|
||||
"webpack-cli": "^4.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/iconv-lite-umd": "^0.7.0",
|
||||
"date-fns": "^2.16.1",
|
||||
"jsonc-parser": "^3.0.0"
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"which": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { MenuUtil, MenuState } from '../menu/menu';
|
||||
import { window, workspace, Uri } from 'vscode';
|
||||
import { gitRun, LogLevel } from '../utils/gitRawRunner';
|
||||
import * as ProcessCommands from './processCommands';
|
||||
import { SpawnOptions } from 'child_process';
|
||||
import { SpawnOptions } from '../utils/commandRunner/command';
|
||||
|
||||
const runningMenu = {
|
||||
title: 'Running',
|
||||
|
@ -1,41 +0,0 @@
|
||||
import * as cp from 'child_process';
|
||||
|
||||
// This refers to Repository in
|
||||
// vscode/extension/git/src/git.ts
|
||||
interface BaseGitRepository {
|
||||
exec?(args: string[], options?: SpawnOptions): Promise<IExecutionResult<string>>;
|
||||
}
|
||||
|
||||
// This refers to the Repository in
|
||||
// vscode/extension/git/src/repository.ts
|
||||
interface BaseRepository {
|
||||
repository: BaseGitRepository;
|
||||
}
|
||||
|
||||
// This refers to ApiRepository from
|
||||
// vscode/extension/git/src/api/api1.ts
|
||||
declare module '../typings/git' {
|
||||
export interface Repository {
|
||||
// Breaking change in VSCode: https://github.com/microsoft/vscode/pull/154555/files#diff-b7c16e46aefbf6182f8be03b099e5c407da09bd345ff2908abddd6bfe90c34aaL65-R65
|
||||
// Going from this
|
||||
readonly _repository: BaseRepository;
|
||||
// to this:
|
||||
readonly repository: BaseRepository;
|
||||
}
|
||||
}
|
||||
|
||||
// types from /extensions/git/src/git.ts
|
||||
|
||||
export interface IExecutionResult<T extends string | Buffer> {
|
||||
exitCode: number;
|
||||
stdout: T;
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
export interface SpawnOptions extends cp.SpawnOptions {
|
||||
input?: string;
|
||||
encoding?: string;
|
||||
log?: boolean;
|
||||
// cancellationToken?: CancellationToken;
|
||||
// onSpawn?: (childProcess: cp.ChildProcess) => void;
|
||||
}
|
11
src/utils/commandRunner/README.md
Normal file
11
src/utils/commandRunner/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
In this folder the code from https://github.com/microsoft/vscode/tree/main/extensions/git has been used. Some of it has been refactored.
|
||||
|
||||
For the code in this folder, i.e. `commandRunnder`, with the following files:
|
||||
- `command.ts`
|
||||
- `disposable.ts`
|
||||
- `findGit.ts`
|
||||
the follwing applies:
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
373
src/utils/commandRunner/command.ts
Normal file
373
src/utils/commandRunner/command.ts
Normal file
@ -0,0 +1,373 @@
|
||||
import * as cp from 'child_process';
|
||||
import { CancellationToken, Disposable, Event, Uri } from 'vscode';
|
||||
import { findGit } from './findGit';
|
||||
import * as iconv from '@vscode/iconv-lite-umd';
|
||||
import { dispose, IDisposable, toDisposable } from './disposable';
|
||||
|
||||
const canceledName = 'Canceled';
|
||||
class CancellationError extends Error {
|
||||
constructor() {
|
||||
super(canceledName);
|
||||
this.name = this.message;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SpawnOptions extends Omit<cp.SpawnOptions, 'cwd'> {
|
||||
input?: string;
|
||||
encoding?: string;
|
||||
log?: boolean;
|
||||
cancellationToken?: CancellationToken;
|
||||
onSpawn?: (childProcess: cp.ChildProcess) => void;
|
||||
cwd?: string | Uri;
|
||||
}
|
||||
|
||||
|
||||
interface IGitErrorData {
|
||||
error?: Error;
|
||||
message?: string;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
exitCode?: number;
|
||||
gitErrorCode?: string;
|
||||
gitCommand?: string;
|
||||
gitArgs?: string[];
|
||||
}
|
||||
|
||||
class GitError {
|
||||
|
||||
error?: Error;
|
||||
message: string;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
exitCode?: number;
|
||||
gitErrorCode?: string;
|
||||
gitCommand?: string;
|
||||
gitArgs?: string[];
|
||||
|
||||
constructor(data: IGitErrorData) {
|
||||
if (data.error) {
|
||||
this.error = data.error;
|
||||
this.message = data.error.message;
|
||||
} else {
|
||||
this.error = undefined;
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
this.message = this.message || data.message || 'Git error';
|
||||
this.stdout = data.stdout;
|
||||
this.stderr = data.stderr;
|
||||
this.exitCode = data.exitCode;
|
||||
this.gitErrorCode = data.gitErrorCode;
|
||||
this.gitCommand = data.gitCommand;
|
||||
this.gitArgs = data.gitArgs;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let result = this.message + ' ' + JSON.stringify({
|
||||
exitCode: this.exitCode,
|
||||
gitErrorCode: this.gitErrorCode,
|
||||
gitCommand: this.gitCommand,
|
||||
stdout: this.stdout,
|
||||
stderr: this.stderr
|
||||
}, null, 2);
|
||||
|
||||
if (this.error) {
|
||||
result += (<any>this.error).stack;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExecutionResult<T extends string | Buffer> {
|
||||
exitCode: number;
|
||||
stdout: T;
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
export function cpErrorHandler(cb: (reason?: any) => void): (reason?: any) => void {
|
||||
return err => {
|
||||
if (/ENOENT/.test(err.message)) {
|
||||
err = new GitError({
|
||||
error: err,
|
||||
message: 'Failed to execute git (ENOENT)',
|
||||
gitErrorCode: GitErrorCodes.NotAGitRepository
|
||||
});
|
||||
}
|
||||
|
||||
cb(err);
|
||||
};
|
||||
}
|
||||
|
||||
export async function run(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||
const res = await _exec(args, options);
|
||||
return {
|
||||
...res,
|
||||
stdout: res.stdout.toString()
|
||||
};
|
||||
}
|
||||
|
||||
async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToken): Promise<IExecutionResult<Buffer>> {
|
||||
if (!child.stdout || !child.stderr) {
|
||||
throw new GitError({ message: 'Failed to get stdout or stderr from git process.' });
|
||||
}
|
||||
|
||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
||||
throw new CancellationError();
|
||||
}
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
const once = (ee: NodeJS.EventEmitter, name: string, fn: (...args: any[]) => void) => {
|
||||
ee.once(name, fn);
|
||||
disposables.push(toDisposable(() => ee.removeListener(name, fn)));
|
||||
};
|
||||
|
||||
const on = (ee: NodeJS.EventEmitter, name: string, fn: (...args: any[]) => void) => {
|
||||
ee.on(name, fn);
|
||||
disposables.push(toDisposable(() => ee.removeListener(name, fn)));
|
||||
};
|
||||
|
||||
let result = Promise.all<any>([
|
||||
new Promise<number>((c, e) => {
|
||||
once(child, 'error', cpErrorHandler(e));
|
||||
once(child, 'exit', c);
|
||||
}),
|
||||
new Promise<Buffer>(c => {
|
||||
const buffers: Buffer[] = [];
|
||||
on(child.stdout!, 'data', (b: Buffer) => buffers.push(b));
|
||||
once(child.stdout!, 'close', () => c(Buffer.concat(buffers)));
|
||||
}),
|
||||
new Promise<string>(c => {
|
||||
const buffers: Buffer[] = [];
|
||||
on(child.stderr!, 'data', (b: Buffer) => buffers.push(b));
|
||||
once(child.stderr!, 'close', () => c(Buffer.concat(buffers).toString('utf8')));
|
||||
})
|
||||
]) as Promise<[number, Buffer, string]>;
|
||||
|
||||
if (cancellationToken) {
|
||||
const cancellationPromise = new Promise<[number, Buffer, string]>((_, e) => {
|
||||
onceEvent(cancellationToken.onCancellationRequested)(() => {
|
||||
try {
|
||||
child.kill();
|
||||
} catch (err) {
|
||||
// noop
|
||||
}
|
||||
|
||||
e(new CancellationError());
|
||||
});
|
||||
});
|
||||
|
||||
result = Promise.race([result, cancellationPromise]);
|
||||
}
|
||||
|
||||
try {
|
||||
const [exitCode, stdout, stderr] = await result;
|
||||
return { exitCode, stdout, stderr };
|
||||
} finally {
|
||||
dispose(disposables);
|
||||
}
|
||||
}
|
||||
|
||||
async function _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||
|
||||
const git = await findGit([], () => true);
|
||||
const child = spawn(git.path, args, options);
|
||||
|
||||
// options.onSpawn?.(child);
|
||||
|
||||
if (options.input) {
|
||||
child.stdin!.end(options.input, 'utf8');
|
||||
}
|
||||
|
||||
const startExec = Date.now();
|
||||
let bufferResult: IExecutionResult<Buffer>;
|
||||
|
||||
try {
|
||||
bufferResult = await exec(child, options.cancellationToken);
|
||||
} catch (ex) {
|
||||
if (ex instanceof CancellationError) {
|
||||
// this.log(`> git ${args.join(' ')} [${Date.now() - startExec}ms] (cancelled)\n`);
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (options.log !== false) {
|
||||
// command
|
||||
// this.log(`> git ${args.join(' ')} [${Date.now() - startExec}ms]\n`);
|
||||
|
||||
// stdout
|
||||
// if (bufferResult.stdout.length > 0 && args.find(a => this.commandsToLog.includes(a))) {
|
||||
// this.log(`${bufferResult.stdout}\n`);
|
||||
// }
|
||||
|
||||
// stderr
|
||||
if (bufferResult.stderr.length > 0) {
|
||||
// this.log(`${bufferResult.stderr}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
let encoding = options.encoding || 'utf8';
|
||||
encoding = iconv.encodingExists(encoding) ? encoding : 'utf8';
|
||||
|
||||
const result: IExecutionResult<string> = {
|
||||
exitCode: bufferResult.exitCode,
|
||||
stdout: iconv.decode(bufferResult.stdout, encoding),
|
||||
stderr: bufferResult.stderr
|
||||
};
|
||||
|
||||
if (bufferResult.exitCode) {
|
||||
return Promise.reject<IExecutionResult<string>>(new GitError({
|
||||
message: 'Failed to execute git',
|
||||
stdout: result.stdout,
|
||||
stderr: result.stderr,
|
||||
exitCode: result.exitCode,
|
||||
gitErrorCode: getGitErrorCode(result.stderr),
|
||||
gitCommand: args[0],
|
||||
gitArgs: args
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/89373
|
||||
// https://github.com/git-for-windows/git/issues/2478
|
||||
function sanitizePath(path: string): string {
|
||||
return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`);
|
||||
}
|
||||
|
||||
function spawn(path: string, args: string[], options: SpawnOptions = {}): cp.ChildProcess {
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (!options.stdio && !options.input) {
|
||||
options.stdio = ['ignore', null, null]; // Unless provided, ignore stdin and leave default streams for stdout and stderr
|
||||
}
|
||||
|
||||
options.env = Object.assign({}, process.env, options.env || {}, {
|
||||
VSCODE_GIT_COMMAND: args[0],
|
||||
LC_ALL: 'en_US.UTF-8',
|
||||
LANG: 'en_US.UTF-8',
|
||||
GIT_PAGER: 'cat',
|
||||
|
||||
// TODO: add ask pass functionality
|
||||
// GIT_ASKPASS: path.join(__dirname, 'askpass.sh'),
|
||||
// VSCODE_GIT_ASKPASS_NODE: process.execPath,
|
||||
// VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js')
|
||||
});
|
||||
|
||||
const santizedOptions = sanitizeOptions(options);
|
||||
|
||||
return cp.spawn(path, args, santizedOptions);
|
||||
}
|
||||
|
||||
function sanitizeOptions(options: SpawnOptions): cp.SpawnOptions {
|
||||
let cwd = getCwd(options);
|
||||
if (cwd) {
|
||||
cwd = sanitizePath(cwd);
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
cwd: cwd
|
||||
};
|
||||
}
|
||||
|
||||
function getCwd(options: SpawnOptions): string | undefined {
|
||||
const cwd = options.cwd;
|
||||
if (typeof cwd === 'undefined' || typeof cwd === 'string') {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
if (cwd.scheme) {
|
||||
return cwd.fsPath;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function onceEvent<T>(event: Event<T>): Event<T> {
|
||||
return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
|
||||
const result = event(e => {
|
||||
result.dispose();
|
||||
return listener.call(thisArgs, e);
|
||||
}, null, disposables);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
const enum GitErrorCodes {
|
||||
BadConfigFile = 'BadConfigFile',
|
||||
AuthenticationFailed = 'AuthenticationFailed',
|
||||
NoUserNameConfigured = 'NoUserNameConfigured',
|
||||
NoUserEmailConfigured = 'NoUserEmailConfigured',
|
||||
NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified',
|
||||
NotAGitRepository = 'NotAGitRepository',
|
||||
NotAtRepositoryRoot = 'NotAtRepositoryRoot',
|
||||
Conflict = 'Conflict',
|
||||
StashConflict = 'StashConflict',
|
||||
UnmergedChanges = 'UnmergedChanges',
|
||||
PushRejected = 'PushRejected',
|
||||
RemoteConnectionError = 'RemoteConnectionError',
|
||||
DirtyWorkTree = 'DirtyWorkTree',
|
||||
CantOpenResource = 'CantOpenResource',
|
||||
GitNotFound = 'GitNotFound',
|
||||
CantCreatePipe = 'CantCreatePipe',
|
||||
PermissionDenied = 'PermissionDenied',
|
||||
CantAccessRemote = 'CantAccessRemote',
|
||||
RepositoryNotFound = 'RepositoryNotFound',
|
||||
RepositoryIsLocked = 'RepositoryIsLocked',
|
||||
BranchNotFullyMerged = 'BranchNotFullyMerged',
|
||||
NoRemoteReference = 'NoRemoteReference',
|
||||
InvalidBranchName = 'InvalidBranchName',
|
||||
BranchAlreadyExists = 'BranchAlreadyExists',
|
||||
NoLocalChanges = 'NoLocalChanges',
|
||||
NoStashFound = 'NoStashFound',
|
||||
LocalChangesOverwritten = 'LocalChangesOverwritten',
|
||||
NoUpstreamBranch = 'NoUpstreamBranch',
|
||||
IsInSubmodule = 'IsInSubmodule',
|
||||
WrongCase = 'WrongCase',
|
||||
CantLockRef = 'CantLockRef',
|
||||
CantRebaseMultipleBranches = 'CantRebaseMultipleBranches',
|
||||
PatchDoesNotApply = 'PatchDoesNotApply',
|
||||
NoPathFound = 'NoPathFound',
|
||||
UnknownPath = 'UnknownPath',
|
||||
EmptyCommitMessage = 'EmptyCommitMessage',
|
||||
BranchFastForwardRejected = 'BranchFastForwardRejected',
|
||||
TagConflict = 'TagConflict'
|
||||
}
|
||||
|
||||
function getGitErrorCode(stderr: string): string | undefined {
|
||||
if (/Another git process seems to be running in this repository|If no other git process is currently running/.test(stderr)) {
|
||||
return GitErrorCodes.RepositoryIsLocked;
|
||||
} else if (/Authentication failed/i.test(stderr)) {
|
||||
return GitErrorCodes.AuthenticationFailed;
|
||||
} else if (/Not a git repository/i.test(stderr)) {
|
||||
return GitErrorCodes.NotAGitRepository;
|
||||
} else if (/bad config file/.test(stderr)) {
|
||||
return GitErrorCodes.BadConfigFile;
|
||||
} else if (/cannot make pipe for command substitution|cannot create standard input pipe/.test(stderr)) {
|
||||
return GitErrorCodes.CantCreatePipe;
|
||||
} else if (/Repository not found/.test(stderr)) {
|
||||
return GitErrorCodes.RepositoryNotFound;
|
||||
} else if (/unable to access/.test(stderr)) {
|
||||
return GitErrorCodes.CantAccessRemote;
|
||||
} else if (/branch '.+' is not fully merged/.test(stderr)) {
|
||||
return GitErrorCodes.BranchNotFullyMerged;
|
||||
} else if (/Couldn't find remote ref/.test(stderr)) {
|
||||
return GitErrorCodes.NoRemoteReference;
|
||||
} else if (/A branch named '.+' already exists/.test(stderr)) {
|
||||
return GitErrorCodes.BranchAlreadyExists;
|
||||
} else if (/'.+' is not a valid branch name/.test(stderr)) {
|
||||
return GitErrorCodes.InvalidBranchName;
|
||||
} else if (/Please,? commit your changes or stash them/.test(stderr)) {
|
||||
return GitErrorCodes.DirtyWorkTree;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
15
src/utils/commandRunner/disposable.ts
Normal file
15
src/utils/commandRunner/disposable.ts
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
|
||||
export function dispose<T extends IDisposable>(disposables: T[]): T[] {
|
||||
disposables.forEach(d => d.dispose());
|
||||
return [];
|
||||
}
|
||||
|
||||
export function toDisposable(dispose: () => void): IDisposable {
|
||||
return { dispose };
|
||||
}
|
||||
|
114
src/utils/commandRunner/findGit.ts
Normal file
114
src/utils/commandRunner/findGit.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import * as cp from 'child_process';
|
||||
import { cpErrorHandler } from './command';
|
||||
import path = require('path');
|
||||
import * as which from 'which';
|
||||
|
||||
export interface IGit {
|
||||
path: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
function parseVersion(raw: string): string {
|
||||
return raw.replace(/^git version /, '');
|
||||
}
|
||||
|
||||
function findSpecificGit(path: string, onValidate: (path: string) => boolean): Promise<IGit> {
|
||||
return new Promise<IGit>((c, e) => {
|
||||
if (!onValidate(path)) {
|
||||
return e('git not found');
|
||||
}
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
const child = cp.spawn(path, ['--version']);
|
||||
child.stdout.on('data', (b: Buffer) => buffers.push(b));
|
||||
child.on('error', cpErrorHandler(e));
|
||||
child.on('exit', code => code ? e(new Error('Not found')) : c({ path, version: parseVersion(Buffer.concat(buffers).toString('utf8').trim()) }));
|
||||
});
|
||||
}
|
||||
|
||||
function findGitDarwin(onValidate: (path: string) => boolean): Promise<IGit> {
|
||||
return new Promise<IGit>((c, e) => {
|
||||
cp.exec('which git', (err, gitPathBuffer) => {
|
||||
if (err) {
|
||||
return e('git not found');
|
||||
}
|
||||
|
||||
const path = gitPathBuffer.toString().trim();
|
||||
|
||||
function getVersion(path: string) {
|
||||
if (!onValidate(path)) {
|
||||
return e('git not found');
|
||||
}
|
||||
|
||||
// make sure git executes
|
||||
cp.exec('git --version', (err, stdout) => {
|
||||
|
||||
if (err) {
|
||||
return e('git not found');
|
||||
}
|
||||
|
||||
return c({ path, version: parseVersion(stdout.trim()) });
|
||||
});
|
||||
}
|
||||
|
||||
if (path !== '/usr/bin/git') {
|
||||
return getVersion(path);
|
||||
}
|
||||
|
||||
// must check if XCode is installed
|
||||
cp.exec('xcode-select -p', (err: any) => {
|
||||
if (err && err.code === 2) {
|
||||
// git is not installed, and launching /usr/bin/git
|
||||
// will prompt the user to install it
|
||||
|
||||
return e('git not found');
|
||||
}
|
||||
|
||||
getVersion(path);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function findSystemGitWin32(base: string, onValidate: (path: string) => boolean): Promise<IGit> {
|
||||
if (!base) {
|
||||
return Promise.reject<IGit>('Not found');
|
||||
}
|
||||
|
||||
return findSpecificGit(path.join(base, 'Git', 'cmd', 'git.exe'), onValidate);
|
||||
}
|
||||
|
||||
function findGitWin32InPath(onValidate: (path: string) => boolean): Promise<IGit> {
|
||||
const whichPromise = new Promise<string>((c, e) => which('git.exe', (err, path) => err ? e(err) : (path ? c(path) : e(new Error('not found')))));
|
||||
return whichPromise.then(path => findSpecificGit(path, onValidate));
|
||||
}
|
||||
|
||||
function findGitWin32(onValidate: (path: string) => boolean): Promise<IGit> {
|
||||
return findSystemGitWin32(process.env['ProgramW6432'] as string, onValidate)
|
||||
.then(undefined, () => findSystemGitWin32(process.env['ProgramFiles(x86)'] as string, onValidate))
|
||||
.then(undefined, () => findSystemGitWin32(process.env['ProgramFiles'] as string, onValidate))
|
||||
.then(undefined, () => findSystemGitWin32(path.join(process.env['LocalAppData'] as string, 'Programs'), onValidate))
|
||||
.then(undefined, () => findGitWin32InPath(onValidate));
|
||||
}
|
||||
|
||||
export async function findGit(hints: string[], onValidate: (path: string) => boolean): Promise<IGit> {
|
||||
for (const hint of hints) {
|
||||
try {
|
||||
return await findSpecificGit(hint, onValidate);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch (process.platform) {
|
||||
case 'darwin': return await findGitDarwin(onValidate);
|
||||
case 'win32': return await findGitWin32(onValidate);
|
||||
default: return await findSpecificGit('git', onValidate);
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
throw new Error('Git installation not found.');
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { processLog } from '../extension';
|
||||
import { MagitProcessLogEntry } from '../models/magitProcessLogEntry';
|
||||
import { IExecutionResult } from '../common/gitApiExtensions';
|
||||
import { IExecutionResult } from './commandRunner/command';
|
||||
|
||||
export default class GitProcessLogger {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SpawnOptions } from '../common/gitApiExtensions';
|
||||
import { Repository } from '../typings/git';
|
||||
import { run, SpawnOptions } from './commandRunner/command';
|
||||
import GitProcessLogger from './gitProcessLogger';
|
||||
|
||||
export enum LogLevel {
|
||||
@ -16,9 +16,11 @@ export async function gitRun(repository: Repository, args: string[], spawnOption
|
||||
}
|
||||
|
||||
try {
|
||||
// Protect against coming breaking change in VSCode: https://github.com/microsoft/vscode/pull/154555/files#diff-b7c16e46aefbf6182f8be03b099e5c407da09bd345ff2908abddd6bfe90c34aaL65-R65
|
||||
const baseRepository = repository._repository ?? repository.repository;
|
||||
let result = await baseRepository.repository.exec!(args, spawnOptions);
|
||||
let spawnOptionsWCwd = { ...spawnOptions };
|
||||
if (!spawnOptionsWCwd.cwd) {
|
||||
spawnOptionsWCwd.cwd = repository.rootUri;
|
||||
}
|
||||
let result = await run(args, spawnOptionsWCwd);
|
||||
|
||||
if (logLevel === LogLevel.Detailed && logEntry) {
|
||||
GitProcessLogger.logGitResult(result, logEntry);
|
||||
@ -31,4 +33,4 @@ export async function gitRun(repository: Repository, args: string[], spawnOption
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MagitRepository } from '../models/magitRepository';
|
||||
import { IExecutionResult } from './commandRunner/command';
|
||||
import { gitRun } from './gitRawRunner';
|
||||
import { IExecutionResult } from '../common/gitApiExtensions';
|
||||
|
||||
export default class GitUtils {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user