Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 43 additions & 18 deletions src/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@ async function git(args, options) {
let {
cwd,
cached,
shouldUseExitCode,
} = options;

/**
* Git is inverted. A zero exit code is true.
* @param {number} exitCode
*/
function toBool(exitCode) {
return exitCode === 0;
}

let cacheKey;
let lockFilePath;

Expand Down Expand Up @@ -59,9 +68,17 @@ async function git(args, options) {
if (_cache !== null) {
debug('Git cache hit.');

cache[cacheKey] = _cache;
let normalized = (() => {
if (shouldUseExitCode) {
return _cache === 'true';
}

return _cache;
})();

cache[cacheKey] = normalized;

return _cache;
return normalized;
}
}

Expand All @@ -70,21 +87,34 @@ async function git(args, options) {

debug(args, options);

let { stdout } = await execa('git', args, {
let {
stdout,
all,
exitCode,
} = await execa('git', args, {
cwd,
reject: !shouldUseExitCode,
});

debug(all);

let result = (() => {
if (shouldUseExitCode) {
return toBool(exitCode);
}

return stdout;
})();

if (cached) {
cache[cacheKey] = stdout;
cache[cacheKey] = result;

if (cached !== true) {
await ensureWriteFile(cachedFilePath, stdout);
await ensureWriteFile(cachedFilePath, result.toString());
}
}

debug(stdout);

return stdout;
return result;
} finally {
if (lockFilePath) {
await unlock(lockFilePath);
Expand Down Expand Up @@ -117,16 +147,10 @@ function getLinesFromOutput(output) {
}

async function isCommitAncestorOf(ancestorCommit, descendantCommit, options) {
try {
await git(['merge-base', '--is-ancestor', ancestorCommit, descendantCommit], options);
} catch (err) {
let missingCommit = 128;
if (![1, missingCommit].includes(err.exitCode)) {
throw err;
}
return false;
}
return true;
return await git(['merge-base', '--is-ancestor', ancestorCommit, descendantCommit], {
shouldUseExitCode: true,
...options,
});
}

async function getCommonAncestor(commit1, commit2, options) {
Expand Down Expand Up @@ -164,6 +188,7 @@ async function getCurrentCommit(cwd) {
}

module.exports = {
getCacheKey,
git,
getCurrentBranch,
getWorkspaceCwd,
Expand Down
172 changes: 160 additions & 12 deletions test/git-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

const { describe, it, setUpTmpDir } = require('./helpers/mocha');
const { expect } = require('./helpers/chai');
const { git } = require('../src/git');
const { git, getCacheKey } = require('../src/git');
const execa = require('execa');
const { gitInit } = require('git-fixtures');
const path = require('path');
const fs = require('fs');

describe(function() {
let tmpPath;
let cwd;

beforeEach(async function() {
tmpPath = await gitInit({
cwd = await gitInit({
defaultBranchName: 'master',
});
});
Expand All @@ -22,24 +22,99 @@ describe(function() {
describe('in memory', function () {
it('works', async function() {
let oldSha = await git(['rev-parse', 'HEAD'], {
cwd: tmpPath,
cwd,
cached: true,
});

await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd: tmpPath });
await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd });

let cachedSha = await git(['rev-parse', 'HEAD'], {
cwd: tmpPath,
cwd,
cached: true,
});

let newSha = await execa('git', ['rev-parse', 'HEAD'], {
cwd: tmpPath,
cwd,
});

expect(cachedSha).to.equal(oldSha);
expect(newSha).to.not.equal(oldSha);
});

describe('exit code commands', function () {
const shouldUseExitCode = true;

it('true', async function() {
let oldSha = await git(['rev-parse', 'HEAD'], {
cwd,
});

await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd });

let newSha = await execa('git', ['rev-parse', 'HEAD'], {
cwd,
});

let cachedArgs = ['merge-base', '--is-ancestor', oldSha, newSha.stdout];

let result = await git(cachedArgs, {
cwd,
cached: true,
shouldUseExitCode,
});

expect(result).to.equal(true);

await execa('git', ['reset', '--hard', oldSha], { cwd });
await execa('git', ['reflog', 'expire', '--expire=now', '--all'], { cwd });
await execa('git', ['gc', '--prune=now'], { cwd });

result = await git(cachedArgs, {
cwd,
cached: true,
shouldUseExitCode,
});

expect(result).to.equal(true);

result = await git(cachedArgs, {
cwd,
shouldUseExitCode,
});

expect(result).to.equal(false);

});

it('false', async function() {
let cachedArgs = ['rev-parse', 'non-existent-branch'];

let result = await git(cachedArgs, {
cwd,
cached: true,
shouldUseExitCode,
});

expect(result).to.equal(false);

await execa('git', ['branch', 'non-existent-branch'], { cwd });

result = await git(cachedArgs, {
cwd,
cached: true,
shouldUseExitCode,
});

expect(result).to.equal(false);

result = await git(cachedArgs, {
cwd,
shouldUseExitCode,
});

expect(result).to.equal(true);
});
});
});

describe('on disk', function () {
Expand All @@ -48,29 +123,102 @@ describe(function() {

it('works', async function() {
let oldSha = await git(['rev-parse', 'HEAD'], {
cwd: tmpPath,
cwd,
cached: this.tmpPath,
});

await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd: tmpPath });
await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd });

let cachedSha = await git(['rev-parse', 'HEAD'], {
cwd: tmpPath,
cwd,
cached: this.tmpPath,
});

let newSha = await execa('git', ['rev-parse', 'HEAD'], {
cwd: tmpPath,
cwd,
});

expect(cachedSha).to.equal(oldSha);
expect(newSha).to.not.equal(oldSha);

let [cachedFilePath] = await fs.promises.readdir(this.tmpPath);

expect(cachedFilePath).to.not.be.undefined;
expect(cachedFilePath).to.equal(getCacheKey(['rev-parse', 'HEAD'], cwd));
expect(path.join(this.tmpPath, cachedFilePath)).to.be.a.file().with.content(oldSha);
});

describe('exit code commands', function () {
const shouldUseExitCode = true;

it('true', async function() {
let oldSha = await git(['rev-parse', 'HEAD'], {
cwd,
});

await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd });

let newSha = await execa('git', ['rev-parse', 'HEAD'], {
cwd,
});

let args = ['merge-base', '--is-ancestor', oldSha, newSha.stdout];

await git(args, {
cwd,
cached: this.tmpPath,
shouldUseExitCode,
});

let [cachedFilePath] = await fs.promises.readdir(this.tmpPath);

expect(cachedFilePath).to.equal(getCacheKey(args, cwd));
expect(path.join(this.tmpPath, cachedFilePath)).to.be.a.file().with.content('true');
});

it('false', async function() {
let oldSha = await git(['rev-parse', 'HEAD'], {
cwd,
});

await execa('git', ['commit', '-m', 'test', '--allow-empty'], { cwd });

let newSha = await execa('git', ['rev-parse', 'HEAD'], {
cwd,
});

let args = ['merge-base', '--is-ancestor', newSha.stdout, oldSha];

await git(args, {
cwd,
cached: this.tmpPath,
shouldUseExitCode,
});

let [cachedFilePath] = await fs.promises.readdir(this.tmpPath);

expect(cachedFilePath).to.equal(getCacheKey(args, cwd));
expect(path.join(this.tmpPath, cachedFilePath)).to.be.a.file().with.content('false');
});

it('error', async function() {
let sha = await git(['rev-parse', 'HEAD'], {
cwd,
});

let args = ['merge-base', '--is-ancestor', 'missing-commit', sha];

await git(args, {
cwd,
cached: this.tmpPath,
shouldUseExitCode,
});

let [cachedFilePath] = await fs.promises.readdir(this.tmpPath);

expect(cachedFilePath).to.equal(getCacheKey(args, cwd));
expect(path.join(this.tmpPath, cachedFilePath)).to.be.a.file().with.content('false');
});
});
});
});
});
Expand Down