diff --git a/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts b/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts index e331215793..df80339108 100644 --- a/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts +++ b/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts @@ -34,7 +34,7 @@ import { SemverTagParts } from '../../../helpers/tagParts/getSemverTagParts'; import { TAG_OBJECT_MESSAGE } from '../../../constants/constants'; import { useUserContext } from '../../../contexts/UserContext'; import { useApi } from '@backstage/core-plugin-api'; -import { usePatchPreparation } from './usePatchPrep'; +import { usePatchValidationSequence } from './usePatchValidationSequence'; export interface UsePatch { bumpedTag: string; @@ -67,7 +67,7 @@ export function usePatch({ responseSteps, TOTAL_PATCH_PREP_STEPS, selectedPatchCommit, - } = usePatchPreparation({ + } = usePatchValidationSequence({ bumpedTag, releaseBranchName, project, diff --git a/plugins/git-release-manager/src/features/Patch/hooks/usePatchPrep.ts b/plugins/git-release-manager/src/features/Patch/hooks/usePatchValidationSequence.tsx similarity index 78% rename from plugins/git-release-manager/src/features/Patch/hooks/usePatchPrep.ts rename to plugins/git-release-manager/src/features/Patch/hooks/usePatchValidationSequence.tsx index a302c3fae8..7778fffe42 100644 --- a/plugins/git-release-manager/src/features/Patch/hooks/usePatchPrep.ts +++ b/plugins/git-release-manager/src/features/Patch/hooks/usePatchValidationSequence.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { useAsync, useAsyncFn } from 'react-use'; import { useApi } from '@backstage/core-plugin-api'; +import { useAsync, useAsyncFn } from 'react-use'; +import React from 'react'; import { GetRecentCommitsResultSingle } from '../../../api/GitReleaseClient'; import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts'; @@ -26,31 +27,49 @@ import { Project } from '../../../contexts/ProjectContext'; import { SemverTagParts } from '../../../helpers/tagParts/getSemverTagParts'; import { useResponseSteps } from '../../../hooks/useResponseSteps'; -export interface UsePatchPreparation { +export interface UsePatchValidationSequence { bumpedTag: string; releaseBranchName: string; project: Project; tagParts: NonNullable; } +const PatchValidationMessage = ({ message }: { message: string }) => ( + <> + [Patch validation] {message} + +); + // Inspiration: https://stackoverflow.com/questions/53859199/how-to-cherry-pick-through-githubs-api -export function usePatchPreparation({ +export function usePatchValidationSequence({ bumpedTag, releaseBranchName, project, tagParts, -}: UsePatchPreparation) { +}: UsePatchValidationSequence) { const pluginApiClient = useApi(gitReleaseManagerApiRef); const { responseSteps, addStepToResponseSteps, asyncCatcher, abortIfError } = useResponseSteps(); - const tempPatchBranchName = `${releaseBranchName}-temp-patch-prep`; + const tempPatchBranchName = `${releaseBranchName}-grm-temp-patch-prep`; /** * (1) Get the release branch's most recent commit */ const [latestCommitOnReleaseBranchRes, run] = useAsyncFn( async (selectedPatchCommit: GetRecentCommitsResultSingle) => { + try { + await pluginApiClient.deleteRef({ + owner: project.owner, + repo: project.repo, + ref: `heads/${tempPatchBranchName}`, + }); + } catch (error: any) { + if (error.message !== 'Reference does not exist') { + throw error; + } + } + const { commit: latestCommit } = await pluginApiClient .getCommit({ owner: project.owner, @@ -60,9 +79,11 @@ export function usePatchPreparation({ .catch(asyncCatcher); addStepToResponseSteps({ - message: `[patch prep] Fetched latest commit from "${releaseBranchName}"`, - secondaryMessage: `with message "${latestCommit.commit.message}"`, - link: latestCommit.htmlUrl, + message: ( + + ), }); return { @@ -98,8 +119,11 @@ export function usePatchPreparation({ .catch(asyncCatcher); addStepToResponseSteps({ - message: '[patch prep] Created Temp Patch Branch', - secondaryMessage: `with ref "${createdReleaseBranch.ref}"`, + message: ( + + ), }); return { @@ -131,8 +155,11 @@ export function usePatchPreparation({ .catch(asyncCatcher); addStepToResponseSteps({ - message: `[patch prep] Fetched release branch "${releaseBranch.name}"`, - link: releaseBranch.links.html, + message: ( + + ), }); return { @@ -155,7 +182,7 @@ export function usePatchPreparation({ .createCommit({ owner: project.owner, repo: project.repo, - message: `[patch prep] Temporary commit for patch ${tagParts.patch}`, + message: `[Patch validation] Temporary commit for patch ${tagParts.patch}`, parents: [ tempPatchBranchRes.value.selectedPatchCommit.firstParentSha ?? '', ], @@ -164,8 +191,7 @@ export function usePatchPreparation({ .catch(asyncCatcher); addStepToResponseSteps({ - message: '[patch prep] Created temporary commit', - secondaryMessage: `with message "${tempCommit.message}"`, + message: , }); return { @@ -192,7 +218,11 @@ export function usePatchPreparation({ .catch(asyncCatcher); addStepToResponseSteps({ - message: `[patch prep] Forced branch "${tempPatchBranchName}" to temporary commit "${tempCommitRes.value.sha}"`, + message: ( + + ), }); return { @@ -215,12 +245,33 @@ export function usePatchPreparation({ base: tempPatchBranchName, head: tempPatchBranchRes.value.selectedPatchCommit.sha, }) + .catch(async error => { + if (error?.message === 'Merge conflict') { + try { + await pluginApiClient.deleteRef({ + owner: project.owner, + repo: project.repo, + ref: `heads/${tempPatchBranchName}`, + }); + } catch (_error) { + // swallow + } + + throw new GitReleaseManagerError( + 'Patching failed due to merge conflict. Will attempt to delete temporary patch validation branch. Manual patching is recommended.', + ); + } + + throw error; + }) .catch(asyncCatcher); addStepToResponseSteps({ - message: `[patch prep] Merged temporary commit into "${tempPatchBranchName}"`, - secondaryMessage: `with message "${merge.commit.message}"`, - link: merge.htmlUrl, + message: ( + + ), }); return { @@ -243,7 +294,7 @@ export function usePatchPreparation({ const { commit: cherryPickCommit } = await pluginApiClient.createCommit({ owner: project.owner, repo: project.repo, - message: `[patch prep] [patch ${bumpedTag}] ${ + message: `[Patch validation] [patch ${bumpedTag}] ${ selectedPatchCommit.commit.message } @@ -255,8 +306,11 @@ export function usePatchPreparation({ }); addStepToResponseSteps({ - message: `[patch prep] Cherry-picked patch commit to "${releaseBranchSha}"`, - secondaryMessage: `with message "${cherryPickCommit.message}"`, + message: ( + + ), }); return { @@ -283,7 +337,11 @@ export function usePatchPreparation({ .catch(asyncCatcher); addStepToResponseSteps({ - message: `[patch prep] Updated reference "${updatedReference.ref}"`, + message: ( + + ), }); return { @@ -306,7 +364,11 @@ export function usePatchPreparation({ }); addStepToResponseSteps({ - message: `[patch prep] Deleted temporary patch prep branch "${tempPatchBranchName}"`, + message: ( + + ), }); return { diff --git a/plugins/git-release-manager/src/hooks/useResponseSteps.ts b/plugins/git-release-manager/src/hooks/useResponseSteps.tsx similarity index 84% rename from plugins/git-release-manager/src/hooks/useResponseSteps.ts rename to plugins/git-release-manager/src/hooks/useResponseSteps.tsx index d39996e2f3..3c44a34a0d 100644 --- a/plugins/git-release-manager/src/hooks/useResponseSteps.ts +++ b/plugins/git-release-manager/src/hooks/useResponseSteps.tsx @@ -14,15 +14,10 @@ * limitations under the License. */ -import { useState } from 'react'; +import React, { useState } from 'react'; import { ResponseStep } from '../types/types'; -const RESPONSE_STEP_FAILURE_ABORT: ResponseStep = { - message: 'Skipped due to error in previous step', - icon: 'failure', -}; - export function useResponseSteps() { const [responseSteps, setResponseSteps] = useState([]); @@ -32,7 +27,14 @@ export function useResponseSteps() { const asyncCatcher = (error: Error): never => { const responseStepError: ResponseStep = { - message: 'Something went wrong 🔥', + message: ( + + Something went wrong{' '} + + 🔥 + + + ), secondaryMessage: `Error message: ${ error?.message ? error.message : 'unknown' }`, @@ -45,7 +47,6 @@ export function useResponseSteps() { const abortIfError = (error?: Error) => { if (error) { - addStepToResponseSteps(RESPONSE_STEP_FAILURE_ABORT); throw error; } }; diff --git a/plugins/git-release-manager/src/types/types.ts b/plugins/git-release-manager/src/types/types.ts index 39418fa9d3..600a293deb 100644 --- a/plugins/git-release-manager/src/types/types.ts +++ b/plugins/git-release-manager/src/types/types.ts @@ -53,7 +53,7 @@ export interface PatchOnSuccessArgs { } export interface ResponseStep { - message: string | React.ReactNode; + message: React.ReactNode; secondaryMessage?: string | React.ReactNode; link?: string; icon?: 'success' | 'failure';