Skip to content

Commit

Permalink
feat: custom / inline configuration
Browse files Browse the repository at this point in the history
Add `config: string[]` property to the `SimpleGitOptions` object used to configure the `simple-git` instance. When supplied, the strings are prefixed to all commands run by that instance as configuration options (ie: they are prefixed themselves with the `-c` flag).

Closes #562 - allows supplying custom proxies to the full set of API commands
Closes #559 - allows global commands
  • Loading branch information
steveukx committed Feb 4, 2021
1 parent bad7c26 commit f2c3d29
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 22 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@

# Change History & Release Notes

## 2.32.0
## 2.32.0 Per-command Configuration

- Supports passing configuration arguments to the `git` binary (via its `-c` argument as a prefix to any other
arguments). Eg: to supply some custom http proxy to a `git pull` command, use
`simpleGit('/some/path', { config: ['http.proxy=someproxy'] }).pull()`
- Add deprecation notice to `git.silent`
- Internal Updates:
- switch from `run` to `runTask` in `git` core
Expand Down
21 changes: 19 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ A lightweight interface for running `git` commands in any [node.js](https://node

# Installation

Easiest through [npm](https://npmjs.org): `npm install simple-git`
Use your favourite package manager:

# Dependencies
- [npm](https://npmjs.org): `npm install simple-git`
- [yarn](https://yarnpkg.com/): `yarn add simple-git`

# System Dependencies

Requires [git](https://git-scm.com/downloads) to be installed and that it can be called using the command `git`.

Expand Down Expand Up @@ -60,6 +63,20 @@ The first argument can be either a string (representing the working directory fo

All configuration properties are optional, the default values are shown in the example above.

## Per-command Configuration

To prefix the commands run by `simple-git` with custom configuration not saved in the git config (ie: using the
`-c` command) supply a `config` option to the instance builder:

```typescript
// configure the instance with a custom configuration property
const git: SimpleGit = simpleGit('/some/path', { config: ['http.proxy=someproxy'] });

// any command executed will be prefixed with this config
// runs: git -c http.proxy=someproxy pull
await git.pull();
```

## Using task callbacks

Each of the methods in the API listing below can be called in a chain and will be called in series after each other.
Expand Down
9 changes: 8 additions & 1 deletion src/git-factory.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const Git = require('./git');
const {GitConstructError} = require('./lib/api');
const {PluginStore} = require("./lib/plugins/plugin-store");
const {commandConfigPrefixingPlugin} = require('./lib/plugins/command-config-prefixing-plugin');
const {createInstanceConfig, folderExists} = require('./lib/utils');

const api = Object.create(null);
Expand Down Expand Up @@ -33,6 +35,7 @@ module.exports.gitExportFactory = function gitExportFactory (factory, extra) {
};

module.exports.gitInstanceFactory = function gitInstanceFactory (baseDir, options) {
const plugins = new PluginStore();
const config = createInstanceConfig(
baseDir && (typeof baseDir === 'string' ? {baseDir} : baseDir),
options
Expand All @@ -42,5 +45,9 @@ module.exports.gitInstanceFactory = function gitInstanceFactory (baseDir, option
throw new GitConstructError(config, `Cannot use simple-git on a directory that does not exist`);
}

return new Git(config);
if (config.config) {
plugins.add(commandConfigPrefixingPlugin(config.config));
}

return new Git(config, plugins);
};
25 changes: 15 additions & 10 deletions src/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@ const {GitExecutor} = require('./lib/runners/git-executor');
const {Scheduler} = require('./lib/runners/scheduler');
const {GitLogger} = require('./lib/git-logger');
const {adhocExecTask, configurationErrorTask} = require('./lib/tasks/task');
const {NOOP, asArray, filterArray, filterPrimitives, filterString, filterStringOrStringArray, filterType, folderExists, getTrailingOptions, trailingFunctionArgument, trailingOptionsArgument} = require('./lib/utils');
const {
NOOP,
asArray,
filterArray,
filterPrimitives,
filterString,
filterStringOrStringArray,
filterType,
folderExists,
getTrailingOptions,
trailingFunctionArgument,
trailingOptionsArgument
} = require('./lib/utils');
const {applyPatchTask} = require('./lib/tasks/apply-patch')
const {branchTask, branchLocalTask, deleteBranchesTask, deleteBranchTask} = require('./lib/tasks/branch');
const {taskCallback} = require('./lib/task-callback');
Expand Down Expand Up @@ -32,17 +44,10 @@ const {straightThroughBufferTask, straightThroughStringTask} = require('./lib/ta

const ChainedExecutor = Symbol('ChainedExecutor');

/**
* Git handling for node. All public functions can be chained and all `then` handlers are optional.
*
* @param {SimpleGitOptions} options Configuration settings for this instance
*
* @constructor
*/
function Git (options) {
function Git (options, plugins) {
this._executor = new GitExecutor(
options.binary, options.baseDir,
new Scheduler(options.maxConcurrentProcesses)
new Scheduler(options.maxConcurrentProcesses), plugins,
);
this._logger = new GitLogger();
}
Expand Down
13 changes: 13 additions & 0 deletions src/lib/plugins/command-config-prefixing-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { prefixedArray } from '../utils';
import { SimpleGitPlugin } from './simple-git-plugin';

export function commandConfigPrefixingPlugin(configuration: string[]): SimpleGitPlugin<'spawn.args'> {
const prefix = prefixedArray(configuration, '-c');

return {
type: 'spawn.args',
action(data) {
return [...prefix, ...data];
},
};
}
3 changes: 3 additions & 0 deletions src/lib/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './command-config-prefixing-plugin';
export * from './plugin-store';
export * from './simple-git-plugin';
27 changes: 27 additions & 0 deletions src/lib/plugins/plugin-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SimpleGitPlugin, SimpleGitPluginType, SimpleGitPluginTypes } from './simple-git-plugin';

export class PluginStore {

private plugins: Set<SimpleGitPlugin<SimpleGitPluginType>> = new Set();

public add<T extends SimpleGitPluginType>(plugin: SimpleGitPlugin<T>) {
this.plugins.add(plugin);
return () => {
this.plugins.delete(plugin);
};
}

public exec<T extends SimpleGitPluginType>(type: T, data: SimpleGitPluginTypes[T]['data'], context: SimpleGitPluginTypes[T]['context']): typeof data {
let output = data;
const contextual = Object.freeze(Object.create(context));

for (const plugin of this.plugins) {
if (plugin.type === type) {
output = plugin.action(output, contextual);
}
}

return output;
}

}
14 changes: 14 additions & 0 deletions src/lib/plugins/simple-git-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface SimpleGitPluginTypes {
'spawn.args': {
data: string[];
context: {};
};
}

export type SimpleGitPluginType = keyof SimpleGitPluginTypes;

export interface SimpleGitPlugin<T extends SimpleGitPluginType> {
action(data: SimpleGitPluginTypes[T]['data'], context: SimpleGitPluginTypes[T]['context']): typeof data;

type: T;
}
21 changes: 15 additions & 6 deletions src/lib/runners/git-executor-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
TaskResponseFormat
} from '../types';
import { callTaskParser, GitOutputStreams, objectToString } from '../utils';
import { PluginStore } from '../plugins/plugin-store';

export class GitExecutorChain implements SimpleGitExecutor {

Expand All @@ -24,10 +25,6 @@ export class GitExecutorChain implements SimpleGitExecutor {
return this._executor.binary;
}

public get outputHandler() {
return this._executor.outputHandler;
}

public get cwd() {
return this._executor.cwd;
}
Expand All @@ -36,7 +33,15 @@ export class GitExecutorChain implements SimpleGitExecutor {
return this._executor.env;
}

constructor(private _executor: SimpleGitExecutor, private _scheduler: Scheduler) {
public get outputHandler() {
return this._executor.outputHandler;
}

constructor(
private _executor: SimpleGitExecutor,
private _scheduler: Scheduler,
private _plugins: PluginStore
) {
}

public push<R>(task: SimpleGitTask<R>): Promise<void | R> {
Expand Down Expand Up @@ -73,7 +78,11 @@ export class GitExecutorChain implements SimpleGitExecutor {
}

private async attemptRemoteTask<R>(task: RunnableTask<R>, logger: OutputLogger) {
const raw = await this.gitResponse(this.binary, task.commands, this.outputHandler, logger.step('SPAWN'));
const args = this._plugins.exec('spawn.args', task.commands, {});

const raw = await this.gitResponse(
this.binary, args, this.outputHandler, logger.step('SPAWN'),
);
const outputStreams = await this.handleTaskData(task, raw, logger.step('HANDLE'));

logger(`passing response to task's parser as a %s`, task.format);
Expand Down
6 changes: 4 additions & 2 deletions src/lib/runners/git-executor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { GitExecutorEnv, outputHandler, SimpleGitExecutor, SimpleGitTask } from '../types';
import { GitExecutorChain } from './git-executor-chain';
import { Scheduler } from './scheduler';
import { PluginStore } from '../plugins/plugin-store';

export class GitExecutor implements SimpleGitExecutor {

private _chain = new GitExecutorChain(this, this._scheduler);
private _chain = new GitExecutorChain(this, this._scheduler, this._plugins);

public env: GitExecutorEnv;
public outputHandler?: outputHandler;
Expand All @@ -13,11 +14,12 @@ export class GitExecutor implements SimpleGitExecutor {
public binary: string = 'git',
public cwd: string,
private _scheduler: Scheduler,
private _plugins: PluginStore,
) {
}

chain(): SimpleGitExecutor {
return new GitExecutorChain(this, this._scheduler);
return new GitExecutorChain(this, this._scheduler, this._plugins);
}

push<R>(task: SimpleGitTask<R>): Promise<void | R> {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export type outputHandler = (
*/
export type GitExecutorEnv = NodeJS.ProcessEnv | undefined;





/**
* Public interface of the Executor
*/
Expand Down Expand Up @@ -65,6 +69,7 @@ export interface SimpleGitOptions {
baseDir: string;
binary: string;
maxConcurrentProcesses: number;
config: string[];
}

export type Maybe<T> = T | undefined;
Expand Down
1 change: 1 addition & 0 deletions src/lib/utils/simple-git-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SimpleGitOptions } from '../types';
const defaultOptions: Omit<SimpleGitOptions, 'baseDir'> = {
binary: 'git',
maxConcurrentProcesses: 5,
config: [],
};

export function createInstanceConfig(...options: Array<Partial<SimpleGitOptions>>): SimpleGitOptions {
Expand Down
8 changes: 8 additions & 0 deletions src/lib/utils/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,11 @@ export function asNumber(source: string | null | undefined, onNaN = 0) {
const num = parseInt(source, 10);
return isNaN(num) ? onNaN : num;
}

export function prefixedArray<T>(input: T[], prefix: T): T[] {
const output: T[] = [];
for (let i = 0, max = input.length; i < max; i++) {
output.push(prefix, input[i]);
}
return output;
}
16 changes: 16 additions & 0 deletions test/unit/plugins.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SimpleGit } from '../../typings';
import { assertExecutedCommands, closeWithSuccess, newSimpleGit } from './__fixtures__';

describe('plugins', () => {

let git: SimpleGit;

it('allows configuration prefixing', async () => {
git = newSimpleGit({ config: ['a', 'bcd'] });
git.raw('foo');

await closeWithSuccess();
assertExecutedCommands('-c', 'a', '-c', 'bcd', 'foo');
})

})

0 comments on commit f2c3d29

Please sign in to comment.