Normalize forceBranchHeadToTempCommit & merge

Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
Erik Engervall
2021-04-15 22:40:43 +02:00
parent fae3f848e0
commit 38faab5494
7 changed files with 149 additions and 127 deletions
@@ -22,9 +22,7 @@ import {
GhCreateCommitResponse,
GhCreateReferenceResponse,
GhCreateTagObjectResponse,
GhGetCommitResponse,
GhGetReleaseResponse,
GhMergeResponse,
GhUpdateReferenceResponse,
GhUpdateReleaseResponse,
} from '../types/types';
@@ -41,6 +39,8 @@ export type ApiMethodRetval<
T extends (...args: any) => Promise<any>
> = UnboxPromise<ReturnType<T>>;
export type UnboxArray<T> = T extends (infer U)[] ? U : T;
type Todo = any; // TODO:
type PartialProject = Omit<Project, 'versioningStrategy'>;
@@ -61,6 +61,7 @@ export interface IPluginApiClient {
args: { releaseBranchName?: string } & PartialProject,
) => Promise<{
recentCommits: {
htmlUrl: string;
sha: string;
author: {
htmlUrl?: string;
@@ -69,6 +70,7 @@ export interface IPluginApiClient {
commit: {
message: string;
};
firstParentSha?: string;
}[];
}>;
@@ -99,7 +101,13 @@ export interface IPluginApiClient {
args: {
defaultBranch: string;
} & PartialProject,
) => Promise<Todo>;
) => Promise<{
sha: string;
htmlUrl: string;
commit: {
message: string;
};
}>;
getBranch: (
args: {
@@ -154,7 +162,9 @@ export interface IPluginApiClient {
args: {
tagParts: SemverTagParts | CalverTagParts;
releaseBranchTree: string;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
} & PartialProject,
) => Promise<{
message: string;
@@ -168,17 +178,30 @@ export interface IPluginApiClient {
IPluginApiClient['patch']['createTempCommit']
>;
} & PartialProject,
) => Promise<Todo>;
) => Promise<void>;
merge: ({
base,
head,
}: { base: string; head: string } & PartialProject) => Promise<Todo>;
}: {
base: string;
head: string;
} & PartialProject) => Promise<{
htmlUrl: string;
commit: {
message: string;
tree: {
sha: string;
};
};
}>;
createCherryPickCommit: (
args: {
bumpedTag: string;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
mergeTree: string;
releaseBranchSha: string;
} & PartialProject,
@@ -213,7 +236,9 @@ export interface IPluginApiClient {
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
tagParts: SemverTagParts | CalverTagParts;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
} & PartialProject,
) => Promise<Todo>;
};
@@ -344,6 +369,7 @@ export class PluginApiClient implements IPluginApiClient {
return {
recentCommits: recentCommitsResponse.data.map(commit => ({
htmlUrl: commit.html_url,
sha: commit.sha,
author: {
htmlUrl: commit.author?.html_url,
@@ -352,6 +378,7 @@ export class PluginApiClient implements IPluginApiClient {
commit: {
message: commit.commit.message,
},
firstParentSha: commit.parents?.[0].sha,
})),
};
}
@@ -407,16 +434,19 @@ export class PluginApiClient implements IPluginApiClient {
defaultBranch,
}: { defaultBranch: string } & PartialProject) {
const { octokit } = await this.getOctokit();
const latestCommit: GhGetCommitResponse = (
await octokit.request(
`/repos/${this.getRepoPath({
owner,
repo,
})}/commits/refs/heads/${defaultBranch}`,
)
).data;
const { data: latestCommit } = await octokit.repos.getCommit({
owner,
repo,
ref: defaultBranch,
});
return { latestCommit };
return {
sha: latestCommit.sha,
htmlUrl: latestCommit.html_url,
commit: {
message: latestCommit.commit.message,
},
};
}
async getBranch({
@@ -534,7 +564,9 @@ export class PluginApiClient implements IPluginApiClient {
}: {
tagParts: SemverTagParts | CalverTagParts;
releaseBranchTree: string;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
} & PartialProject) => {
const { octokit } = await this.getOctokit();
const { data: tempCommit } = await octokit.git.createCommit({
@@ -542,7 +574,7 @@ export class PluginApiClient implements IPluginApiClient {
repo,
message: `Temporary commit for patch ${tagParts.patch}`,
tree: releaseBranchTree,
parents: [selectedPatchCommit.parents[0].sha],
parents: [selectedPatchCommit.firstParentSha ?? ''], // TODO: Avoid `??`
});
return {
@@ -563,20 +595,13 @@ export class PluginApiClient implements IPluginApiClient {
>;
} & PartialProject) => {
const { octokit } = await this.getOctokit();
await octokit.request(
`/repos/${this.getRepoPath({
owner,
repo,
})}/git/refs/heads/${releaseBranchName}`,
{
method: 'PATCH',
data: {
sha: tempCommit.sha,
force: true,
},
},
);
await octokit.git.updateRef({
owner,
repo,
ref: releaseBranchName,
sha: tempCommit.sha,
force: true,
});
},
merge: async ({
@@ -586,18 +611,22 @@ export class PluginApiClient implements IPluginApiClient {
head,
}: { base: string; head: string } & PartialProject) => {
const { octokit } = await this.getOctokit();
const { data: merge } = await octokit.repos.merge({
owner,
repo,
base,
head,
});
const merge: GhMergeResponse = (
await octokit.request(
`/repos/${this.getRepoPath({ owner, repo })}/merges`,
{
method: 'POST',
data: { base, head },
return {
htmlUrl: merge.html_url,
commit: {
message: merge.commit.message,
tree: {
sha: merge.commit.tree.sha,
},
)
).data;
return { merge };
},
};
},
createCherryPickCommit: async ({
@@ -609,7 +638,9 @@ export class PluginApiClient implements IPluginApiClient {
releaseBranchSha,
}: {
bumpedTag: string;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
mergeTree: string;
releaseBranchSha: string;
} & PartialProject) => {
@@ -732,7 +763,9 @@ export class PluginApiClient implements IPluginApiClient {
ApiMethodRetval<IPluginApiClient['getLatestRelease']>['latestRelease']
>;
tagParts: SemverTagParts | CalverTagParts;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
} & PartialProject) => {
const { octokit } = await this.getOctokit();
@@ -747,7 +780,7 @@ export class PluginApiClient implements IPluginApiClient {
tag_name: bumpedTag,
body: `${latestRelease.body}
#### [Patch ${tagParts.patch}](${selectedPatchCommit.html_url})
#### [Patch ${tagParts.patch}](${selectedPatchCommit.htmlUrl})
${selectedPatchCommit.commit.message}`,
},
@@ -38,9 +38,9 @@ describe('createRc', () => {
expect(result).toMatchInlineSnapshot(`
Array [
Object {
"link": "mock_latestCommit_html_url",
"link": "latestCommit.html_url",
"message": "Fetched latest commit from \\"mock_defaultBranch\\"",
"secondaryMessage": "with message \\"mock_latestCommit_message\\"",
"secondaryMessage": "with message \\"latestCommit.commit.message\\"",
},
Object {
"message": "Cut Release Branch",
@@ -51,14 +51,14 @@ export async function createRc({
/**
* 1. Get the default branch's most recent commit
*/
const { latestCommit } = await pluginApiClient.getLatestCommit({
const latestCommit = await pluginApiClient.getLatestCommit({
...project,
defaultBranch,
});
responseSteps.push({
message: `Fetched latest commit from "${defaultBranch}"`,
secondaryMessage: `with message "${latestCommit.commit.message}"`,
link: latestCommit.html_url,
link: latestCommit.htmlUrl,
});
/**
@@ -33,14 +33,10 @@ import {
import FileCopyIcon from '@material-ui/icons/FileCopy';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import { Differ } from '../../components/Differ';
import {
ComponentConfigPatch,
GhGetCommitResponse,
SetRefetch,
} from '../../types/types';
import { CalverTagParts } from '../../helpers/tagParts/getCalverTagParts';
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
import { ComponentConfigPatch, SetRefetch } from '../../types/types';
import { Differ } from '../../components/Differ';
import { patch } from './sideEffects/patch';
import { ResponseStepList } from '../../components/ResponseStepList/ResponseStepList';
import { SemverTagParts } from '../../helpers/tagParts/getSemverTagParts';
@@ -48,7 +44,11 @@ import { TEST_IDS } from '../../test-helpers/test-ids';
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useStyles } from '../../styles/styles';
import { ApiMethodRetval, IPluginApiClient } from '../../api/PluginApiClient';
import {
ApiMethodRetval,
IPluginApiClient,
UnboxArray,
} from '../../api/PluginApiClient';
interface PatchBodyProps {
bumpedTag: string;
@@ -92,7 +92,9 @@ export const PatchBody = ({
});
const [patchReleaseResponse, patchReleaseFn] = useAsyncFn(async (...args) => {
const selectedPatchCommit: GhGetCommitResponse = args[0];
const selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
> = args[0];
const patchResponseSteps = await patch({
project,
pluginApiClient,
@@ -14,16 +14,13 @@
* limitations under the License.
*/
import {
ComponentConfigPatch,
GhGetCommitResponse,
ResponseStep,
} from '../../../types/types';
import { ComponentConfigPatch, ResponseStep } from '../../../types/types';
import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts';
import { GitHubReleaseManagerError } from '../../../errors/GitHubReleaseManagerError';
import {
ApiMethodRetval,
IPluginApiClient,
UnboxArray,
} from '../../../api/PluginApiClient';
import { Project } from '../../../contexts/ProjectContext';
import { SemverTagParts } from '../../../helpers/tagParts/getSemverTagParts';
@@ -35,7 +32,9 @@ interface Patch {
>;
pluginApiClient: IPluginApiClient;
project: Project;
selectedPatchCommit: GhGetCommitResponse;
selectedPatchCommit: UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>;
successCb?: ComponentConfigPatch['successCb'];
tagParts: NonNullable<CalverTagParts | SemverTagParts>;
}
@@ -55,7 +54,6 @@ export async function patch({
if (!selectedPatchCommit || !selectedPatchCommit.sha) {
throw new GitHubReleaseManagerError('Invalid commit');
}
const releaseBranchName = latestRelease.targetCommitish;
/**
* 1. Here is the branch we want to cherry-pick to:
@@ -105,7 +103,7 @@ export async function patch({
* 4. Merge the commit we want into this mess:
* > merge = POST /repos/$owner/$repo/merges { "base": branchName, "head": commit.sha }
*/
const { merge } = await pluginApiClient.patch.merge({
const merge = await pluginApiClient.patch.merge({
...project,
base: releaseBranchName,
head: selectedPatchCommit.sha,
@@ -113,7 +111,7 @@ export async function patch({
responseSteps.push({
message: `Merged temporary commit into "${releaseBranchName}"`,
secondaryMessage: `with message "${merge.commit.message}"`,
link: merge.html_url,
link: merge.htmlUrl,
});
/**
@@ -205,7 +203,7 @@ export async function patch({
updatedReleaseName: updatedRelease.name,
previousTag: latestRelease.tagName,
patchedTag: updatedRelease.tag_name,
patchCommitUrl: selectedPatchCommit.html_url,
patchCommitUrl: selectedPatchCommit.htmlUrl,
patchCommitMessage: selectedPatchCommit.commit.message,
});
@@ -69,32 +69,6 @@ describe('testHelpers', () => {
"tagName": "rc-2020.01.01_1",
"targetCommitish": "rc/1.2.3",
},
"mockRecentCommits": Array [
Object {
"author": Object {
"html_url": "mock_recentCommits_author_html_url",
"login": "mock_recentCommit_author_login",
},
"commit": Object {
"message": "mock_latestCommit_message",
},
"html_url": "mock_latestCommit_html_url",
"node_id": "1",
"sha": "mock_latestCommit_sha",
},
Object {
"author": Object {
"html_url": "mock_recentCommits_author_html_url",
"login": "mock_recentCommit_author_login",
},
"commit": Object {
"message": "mock_latestCommit_message",
},
"html_url": "mock_latestCommit_html_url",
"node_id": "2",
"sha": "mock_latestCommit_sha",
},
],
"mockReleaseBranch": Object {
"commit": Object {
"commit": Object {
@@ -118,15 +92,14 @@ describe('testHelpers', () => {
},
"mockSelectedPatchCommit": Object {
"author": Object {
"html_url": "mock_recentCommits_author_html_url",
"login": "mock_recentCommit_author_login",
"htmlUrl": "author_html_url",
"login": "author_login",
},
"commit": Object {
"message": "mock_latestCommit_message",
"message": "commit_message",
},
"html_url": "mock_latestCommit_html_url",
"node_id": "mock_selected_patch_commit",
"sha": "mock_latestCommit_sha",
"firstParentSha": "mock_first_parent_sha",
"sha": "mock_sha_selected_patch_commit",
},
"mockSemverProject": Object {
"owner": "mock_owner",
@@ -17,18 +17,19 @@
import { CalverTagParts } from '../helpers/tagParts/getCalverTagParts';
import { getRcGitHubInfo } from '../cards/createRc/getRcGitHubInfo';
import {
GhCreateCommitResponse,
GhCreateReferenceResponse,
GhCreateTagObjectResponse,
GhGetBranchResponse,
GhGetCommitResponse,
GhGetReleaseResponse,
GhMergeResponse,
GhUpdateReferenceResponse,
GhUpdateReleaseResponse,
} from '../types/types';
import { Project } from '../contexts/ProjectContext';
import { ApiMethodRetval, IPluginApiClient } from '../api/PluginApiClient';
import {
ApiMethodRetval,
IPluginApiClient,
UnboxArray,
} from '../api/PluginApiClient';
export const mockSemverProject: Project = {
owner: 'mock_owner',
@@ -109,26 +110,34 @@ export const mockReleaseBranch = createMockBranch();
/**
* MOCK COMMIT
*/
const createMockCommit = ({ node_id = '1' }: Partial<GhGetCommitResponse>) =>
const createMockCommit = ({
...rest
}: Partial<
NonNullable<
UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>
>
>) =>
({
node_id,
author: {
html_url: 'mock_recentCommits_author_html_url',
login: 'mock_recentCommit_author_login',
htmlUrl: 'author_html_url',
login: 'author_login',
},
commit: {
message: 'mock_latestCommit_message',
message: 'commit_message',
},
html_url: 'mock_latestCommit_html_url',
sha: 'mock_latestCommit_sha',
} as GhGetCommitResponse);
export const mockRecentCommits = [
createMockCommit({ node_id: '1' }),
createMockCommit({ node_id: '2' }),
] as GhGetCommitResponse[];
sha: 'mock_sha',
firstParentSha: 'mock_first_parent_sha',
...rest,
} as NonNullable<
UnboxArray<
ApiMethodRetval<IPluginApiClient['getRecentCommits']>['recentCommits']
>
>);
export const mockSelectedPatchCommit = createMockCommit({
node_id: 'mock_selected_patch_commit',
sha: 'mock_sha_selected_patch_commit',
});
/**
@@ -146,7 +155,10 @@ export const mockApiClient: IPluginApiClient = {
getUsername: jest.fn(),
getRecentCommits: jest.fn().mockResolvedValue({
recentCommits: mockRecentCommits,
recentCommits: [
createMockCommit({ sha: 'mock_sha_recent_commits_1' }),
createMockCommit({ sha: 'mock_sha_recent_commits_2' }),
],
}),
getLatestRelease: jest.fn(), // TODO:
@@ -154,8 +166,12 @@ export const mockApiClient: IPluginApiClient = {
getRepository: jest.fn(),
getLatestCommit: jest.fn().mockResolvedValue({
latestCommit: createMockCommit({ node_id: 'mock_latest_commit' }),
}),
sha: 'latestCommit.sha',
htmlUrl: 'latestCommit.html_url',
commit: {
message: 'latestCommit.commit.message',
},
} as NonNullable<ApiMethodRetval<IPluginApiClient['getLatestCommit']>>),
getBranch: jest.fn().mockResolvedValue(createMockBranch()),
@@ -202,14 +218,14 @@ export const mockApiClient: IPluginApiClient = {
} as ApiMethodRetval<IPluginApiClient['patch']['createTempCommit']>),
forceBranchHeadToTempCommit: jest.fn().mockResolvedValue(undefined),
merge: jest.fn().mockResolvedValue({
merge: {
commit: {
message: 'mock_merge_commit_message',
tree: { sha: 'mock_merge_commit_tree_sha' },
htmlUrl: 'mock_merge_html_url',
commit: {
message: 'mock_merge_commit_message',
tree: {
sha: 'mock_merge_commit_tree_sha',
},
html_url: 'mock_merge_html_url',
} as GhMergeResponse,
}),
},
} as ApiMethodRetval<IPluginApiClient['patch']['merge']>),
replaceTempCommit: jest.fn().mockResolvedValue({
updatedReference: {
ref: 'mock_reference_ref',