Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record the repository slug to support builds from forks #193

Merged
merged 14 commits into from Jan 12, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

- [233](https://github.com/chromaui/chromatic-cli/pull/233) Add `--branch-name` flag to override branch name
- [237](https://github.com/chromaui/chromatic-cli/pull/237) Avoid passing `--silent` when invoking npm through Node.js script
- [193](https://github.com/chromaui/chromatic-cli/pull/193) Record the repository slug (owner/repo) to support forks

# 5.5.0 - 2020-12-20

Expand Down
44 changes: 25 additions & 19 deletions bin/git/getCommitAndBranch.js
Expand Up @@ -62,33 +62,28 @@ export async function getCommitAndBranch({ branchName, patchBaseRef, ci, log } =
}
}

const { isCi, prBranch, branch: ciBranch, commit: ciCommit, slug } = envCi();

// On certain CI systems, a branch is not checked out
// (instead a detached head is used for the commit).
if (!notHead(branch)) {
const {
prBranch: prBranchFromEnvCi,
branch: branchFromEnvCi,
commit: commitFromEnvCi,
} = envCi();

commit = commitFromEnvCi;

// $HEAD is for netlify: https://www.netlify.com/docs/continuous-deployment/
// $GERRIT_BRANCH is for Gerrit/Jenkins: https://wiki.jenkins.io/display/JENKINS/Gerrit+Trigger
// $CI_BRANCH is a general setting that lots of systems use
commit = ciCommit;
branch =
notHead(prBranchFromEnvCi) ||
notHead(branchFromEnvCi) ||
notHead(process.env.HEAD) ||
notHead(process.env.GERRIT_BRANCH) ||
notHead(prBranch) ||
notHead(ciBranch) ||
notHead(process.env.HEAD) || // https://www.netlify.com/docs/continuous-deployment/
notHead(process.env.GERRIT_BRANCH) || // https://wiki.jenkins.io/display/JENKINS/Gerrit+Trigger
notHead(process.env.GITHUB_REF) || // https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables
notHead(process.env.CI_BRANCH) ||
notHead(process.env.GITHUB_REF) ||
'HEAD';
}

// REPOSITORY_URL is for netlify: https://www.netlify.com/docs/continuous-deployment/
const fromCI =
!!ci || !!process.env.CI || !!process.env.REPOSITORY_URL || !!process.env.GITHUB_REPOSITORY;
isCi ||
!!ci ||
!!process.env.CI ||
!!process.env.REPOSITORY_URL || // https://www.netlify.com/docs/continuous-deployment/
!!process.env.GITHUB_REPOSITORY;

log.debug(
`git info: ${JSON.stringify({
Expand All @@ -97,9 +92,20 @@ export async function getCommitAndBranch({ branchName, patchBaseRef, ci, log } =
committerEmail,
committerName,
branch,
slug,
isTravisPrBuild,
fromCI,
})}`
);
return { commit, committedAt, committerEmail, committerName, branch, isTravisPrBuild, fromCI };

return {
commit,
committedAt,
committerEmail,
committerName,
branch,
slug,
isTravisPrBuild,
fromCI,
};
}
9 changes: 9 additions & 0 deletions bin/git/git.js
Expand Up @@ -68,6 +68,15 @@ export async function getVersion() {
return result.replace('git version ', '');
}

// The slug consists of the last two parts of the URL, at least for GitHub, GitLab and Bitbucket,
// and is typically followed by `.git`. The regex matches the last two parts between slashes, and
// ignores the `.git` suffix if it exists, so it matches something like `ownername/reponame`.
export async function getSlug() {
const result = await execGitCommand(`git config --get remote.origin.url`);
const [, slug] = result.match(/([^/:]+\/[^/]+?)(\.git)?$/) || [];
return slug;
}

// NOTE: At some point we should check that the commit has been pushed to the
// remote and the branch matches with origin/REF, but for now we are naive about
// adhoc builds.
Expand Down
18 changes: 17 additions & 1 deletion bin/git/git.test.js
@@ -1,18 +1,21 @@
/* eslint-disable jest/expect-expect */
import { exec } from 'child_process';
import execa from 'execa';
import process from 'process';
import tmp from 'tmp-promise';
import { promisify } from 'util';

import generateGitRepository from './generateGitRepository';
import { getBaselineCommits } from './git';
import { getBaselineCommits, getSlug } from './git';
import longLineDescription from './mocks/long-line';
import longLoopDescription from './mocks/long-loop';
import createMockIndex from './mocks/mock-index';
import simpleLoopDescription from './mocks/simple-loop';
import threeParentsDescription from './mocks/three-parents';
import twoRootsDescription from './mocks/two-roots';

const execaCommand = jest.spyOn(execa, 'command');

// Bumping up the Jest timeout for this file because it is timing out sometimes
// I think this just a bit of a slow file due to git stuff, takes ~2-3s on my computer.
jest.setTimeout(30 * 1000);
Expand Down Expand Up @@ -415,3 +418,16 @@ describe('getBaselineCommits', () => {
});
});
});

describe('getSlug', () => {
it('returns the slug portion of the git url', async () => {
execaCommand.mockImplementation(() => ({ all: 'git@github.com:chromaui/chromatic-cli.git' }));
expect(await getSlug()).toBe('chromaui/chromatic-cli');

execaCommand.mockImplementation(() => ({ all: 'https://github.com/chromaui/chromatic-cli' }));
expect(await getSlug()).toBe('chromaui/chromatic-cli');

execaCommand.mockImplementation(() => ({ all: 'https://gitlab.com/foo/bar.baz.git' }));
expect(await getSlug()).toBe('foo/bar.baz');
});
});
7 changes: 4 additions & 3 deletions bin/lib/logSerializers.test.js
@@ -1,14 +1,15 @@
/* eslint-disable jest/no-conditional-expect, jest/no-try-expect */
import { execSync } from 'child_process';

import { errorSerializer } from './logSerializers';

it('strips off envPairs', () => {
let err;
try {
execSync('some hot garbage');
} catch (err) {
expect(errorSerializer(err).envPairs).toBeUndefined();
} catch (e) {
err = e;
}
expect(errorSerializer(err).envPairs).toBeUndefined();
});

it('does not add random things to the error', () => {
Expand Down
1 change: 1 addition & 0 deletions bin/main.test.js
Expand Up @@ -136,6 +136,7 @@ jest.mock('./git/git', () => ({
}),
getBranch: () => 'branch',
getBaselineCommits: () => ['baseline'],
getSlug: () => 'user/repo',
getVersion: () => '2.24.1',
}));

Expand Down
17 changes: 11 additions & 6 deletions bin/tasks/gitInfo.js
@@ -1,7 +1,7 @@
import picomatch from 'picomatch';

import { getCommitAndBranch } from '../git/getCommitAndBranch';
import { getBaselineCommits, getVersion } from '../git/git';
import { getBaselineCommits, getSlug, getVersion } from '../git/git';
import { createTask, transitionTo } from '../lib/tasks';
import {
initial,
Expand All @@ -19,16 +19,21 @@ const TesterSkipBuildMutation = `
`;

export const setGitInfo = async (ctx, task) => {
const { branchName, patchBaseRef, fromCI, ignoreLastBuildOnBranch, skip } = ctx.options;

ctx.git = await getCommitAndBranch({ branchName, patchBaseRef, ci: fromCI, log: ctx.log });
const { branchName, patchBaseRef, fromCI: ci } = ctx.options;
ctx.git = await getCommitAndBranch({ branchName, patchBaseRef, ci, log: ctx.log });
ctx.git.slug = ctx.git.slug || (await getSlug());
ctx.git.version = await getVersion();

if (ctx.options.ownerName) {
ctx.git.slug = ctx.git.slug.replace(/[^/]+/, ctx.options.ownerName);
}

const { branch, commit } = ctx.git;

const matchesBranch = (glob) => (glob && glob.length ? picomatch(glob)(branch) : !!glob);
ctx.git.matchesBranch = matchesBranch;

if (matchesBranch(skip)) {
if (matchesBranch(ctx.options.skip)) {
transitionTo(skippingBuild)(ctx, task);
if (await ctx.client.runQuery(TesterSkipBuildMutation, { commit })) {
ctx.skip = true;
Expand All @@ -39,7 +44,7 @@ export const setGitInfo = async (ctx, task) => {

const baselineCommits = await getBaselineCommits(ctx, {
branch,
ignoreLastBuildOnBranch: matchesBranch(ignoreLastBuildOnBranch),
ignoreLastBuildOnBranch: matchesBranch(ctx.options.ignoreLastBuildOnBranch),
});
ctx.git.baselineCommits = baselineCommits;
ctx.log.debug(`Found baselineCommits: ${baselineCommits}`);
Expand Down
20 changes: 19 additions & 1 deletion bin/tasks/gitInfo.test.js
@@ -1,5 +1,5 @@
import { getCommitAndBranch } from '../git/getCommitAndBranch';
import { getBaselineCommits, getVersion } from '../git/git';
import { getBaselineCommits, getSlug, getVersion } from '../git/git';
import { setGitInfo } from './gitInfo';

jest.mock('../git/getCommitAndBranch');
Expand All @@ -12,13 +12,31 @@ describe('setGitInfo', () => {
getCommitAndBranch.mockReturnValue({ commit: '123asdf', branch: 'something' });
getBaselineCommits.mockReturnValue(['asd2344']);
getVersion.mockReturnValue('Git v1.0.0');
getSlug.mockReturnValue('user/repo');
const ctx = { log, options: {} };
await setGitInfo(ctx, {});
expect(ctx.git).toMatchObject({
commit: '123asdf',
branch: 'something',
baselineCommits: ['asd2344'],
version: 'Git v1.0.0',
slug: 'user/repo',
});
});

it('supports overriding the owner name in the slug', async () => {
getCommitAndBranch.mockReturnValue({ commit: '123asdf', branch: 'something' });
getBaselineCommits.mockReturnValue(['asd2344']);
getVersion.mockReturnValue('Git v1.0.0');
getSlug.mockReturnValue('user/repo');
const ctx = { log, options: { ownerName: 'org' } };
await setGitInfo(ctx, {});
expect(ctx.git).toMatchObject({
commit: '123asdf',
branch: 'something',
baselineCommits: ['asd2344'],
version: 'Git v1.0.0',
slug: 'org/repo',
});
});
});
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "chromatic",
"version": "5.5.1-dev.1",
"version": "5.6.0-alpha.0",
"description": "Visual Testing for Storybook",
"homepage": "https://www.chromatic.com",
"bugs": {
Expand Down