diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 05381bbba5..da5c69617f 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -35,8 +35,13 @@ export default class GitShellOutStrategy { constructor(workingDir, options = {}) { this.workingDir = workingDir; - const parallelism = options.parallelism || Math.max(3, os.cpus().length); - this.commandQueue = new AsyncQueue({parallelism}); + if (options.queue) { + this.commandQueue = options.queue; + } else { + const parallelism = options.parallelism || Math.max(3, os.cpus().length); + this.commandQueue = new AsyncQueue({parallelism}); + } + } // Execute a command and read the output using the embedded Git environment diff --git a/lib/helpers.js b/lib/helpers.js index 22b0e9b567..050dc3ea71 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,6 +1,23 @@ import fs from 'fs-extra'; import {ncp} from 'ncp'; +/** + * Takes an array of targets and returns a proxy. The proxy intercepts property accessor calls and + * returns the value of that property on the first object in `targets` where the target implements that property. + */ +export function firstImplementer(...targets) { + return new Proxy(targets, { + get(target, name) { + const firstValidTarget = target.find(t => Reflect.has(t, name)); + if (firstValidTarget) { + return firstValidTarget[name]; + } else { + return undefined; + } + }, + }); +} + export function readFile(absoluteFilePath, encoding = 'utf8') { return new Promise((resolve, reject) => { fs.readFile(absoluteFilePath, encoding, (err, contents) => { diff --git a/lib/views/git-timings-view.js b/lib/views/git-timings-view.js index b9faa0508b..feaff72d6a 100644 --- a/lib/views/git-timings-view.js +++ b/lib/views/git-timings-view.js @@ -138,11 +138,19 @@ class MarkerSpan extends React.Component { }); } - @autobind - handleMouseOut(e) { + closeTooltip() { this.tooltipDisposable && this.tooltipDisposable.dispose(); this.tooltipDisposable = null; } + + @autobind + handleMouseOut(e) { + this.closeTooltip(); + } + + componentWillUnmount() { + this.closeTooltip(); + } } diff --git a/package.json b/package.json index c853dfd5fb..83d2270b34 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "multi-list-selection": "^0.1.1", "ncp": "^2.0.0", "node-fetch": "^1.6.3", + "nodegit": "^0.17.0", "nsfw": "1.0.15", "react": "^15.4.1", "react-dom": "^15.4.1", diff --git a/test/git-shell-out-strategy.test.js b/test/git-shell-out-strategy.test.js index 043c189bb9..7ce73da185 100644 --- a/test/git-shell-out-strategy.test.js +++ b/test/git-shell-out-strategy.test.js @@ -528,10 +528,11 @@ describe('Git commands', function() { await git.exec(['remote', 'add', 'upstream', 'git@github.com:my/upstream.git']); await git.exec(['remote', 'add', 'another.remote', 'git@github.com:another/upstream.git']); const remotes = await git.getRemotes(); + // Note: nodegit returns remote names in alphabetical order assert.deepEqual(remotes, [ + {name: 'another.remote', url: 'git@github.com:another/upstream.git'}, {name: 'origin', url: 'git@github.com:other/origin.git'}, {name: 'upstream', url: 'git@github.com:my/upstream.git'}, - {name: 'another.remote', url: 'git@github.com:another/upstream.git'}, ]); }); diff --git a/test/helpers.test.js b/test/helpers.test.js new file mode 100644 index 0000000000..cc57b22998 --- /dev/null +++ b/test/helpers.test.js @@ -0,0 +1,20 @@ +import {firstImplementer} from '../lib/helpers'; + +class A { + one() { return 'a-one'; } + two() { return 'a-two'; } +} + +class B { + two() { return 'b-two'; } + three() { return 'b-three'; } +} + +describe('firstImplementer', function() { + it('calls methods from the first target that has the method', function() { + const target = firstImplementer(new A(), new B()); + assert.equal(target.one(), 'a-one'); + assert.equal(target.two(), 'a-two'); + assert.equal(target.three(), 'b-three'); + }); +});