mirror of
https://github.com/harness/drone.git
synced 2025-05-04 14:12:15 +08:00
feat: [CODE-2411] add support for Fast-forward merge (#2803)
* feat: [CODE-2411] add support for Fast-forward merge
This commit is contained in:
parent
bbb7bce02a
commit
f6ac58f036
@ -166,6 +166,9 @@ const BranchProtectionForm = (props: {
|
|||||||
const isRebasePresent = (rule.definition as ProtectionBranch)?.pullreq?.merge?.strategies_allowed?.includes(
|
const isRebasePresent = (rule.definition as ProtectionBranch)?.pullreq?.merge?.strategies_allowed?.includes(
|
||||||
MergeStrategy.REBASE
|
MergeStrategy.REBASE
|
||||||
)
|
)
|
||||||
|
const isFFMergePresent = (rule.definition as ProtectionBranch)?.pullreq?.merge?.strategies_allowed?.includes(
|
||||||
|
MergeStrategy.FAST_FORWARD
|
||||||
|
)
|
||||||
// List of strings to be included in the final array
|
// List of strings to be included in the final array
|
||||||
const includeList = (rule?.pattern as ProtectionPattern)?.include ?? []
|
const includeList = (rule?.pattern as ProtectionPattern)?.include ?? []
|
||||||
const excludeList = (rule?.pattern as ProtectionPattern)?.exclude ?? []
|
const excludeList = (rule?.pattern as ProtectionPattern)?.exclude ?? []
|
||||||
@ -201,6 +204,7 @@ const BranchProtectionForm = (props: {
|
|||||||
mergeCommit: isMergePresent,
|
mergeCommit: isMergePresent,
|
||||||
squashMerge: isSquashPresent,
|
squashMerge: isSquashPresent,
|
||||||
rebaseMerge: isRebasePresent,
|
rebaseMerge: isRebasePresent,
|
||||||
|
fastForwardMerge: isFFMergePresent,
|
||||||
autoDelete: (rule.definition as ProtectionBranch)?.pullreq?.merge?.delete_branch,
|
autoDelete: (rule.definition as ProtectionBranch)?.pullreq?.merge?.delete_branch,
|
||||||
blockBranchCreation: (rule.definition as ProtectionBranch)?.lifecycle?.create_forbidden,
|
blockBranchCreation: (rule.definition as ProtectionBranch)?.lifecycle?.create_forbidden,
|
||||||
blockBranchUpdate:
|
blockBranchUpdate:
|
||||||
@ -244,7 +248,8 @@ const BranchProtectionForm = (props: {
|
|||||||
const stratArray = [
|
const stratArray = [
|
||||||
formData.squashMerge && MergeStrategy.SQUASH,
|
formData.squashMerge && MergeStrategy.SQUASH,
|
||||||
formData.rebaseMerge && MergeStrategy.REBASE,
|
formData.rebaseMerge && MergeStrategy.REBASE,
|
||||||
formData.mergeCommit && MergeStrategy.MERGE
|
formData.mergeCommit && MergeStrategy.MERGE,
|
||||||
|
formData.fastForwardMerge && MergeStrategy.FAST_FORWARD
|
||||||
].filter(Boolean) as EnumMergeMethod[]
|
].filter(Boolean) as EnumMergeMethod[]
|
||||||
const includeArray =
|
const includeArray =
|
||||||
formData?.targetList?.filter(([type]) => type === 'include').map(([, value]) => value) ?? []
|
formData?.targetList?.filter(([type]) => type === 'include').map(([, value]) => value) ?? []
|
||||||
|
@ -269,6 +269,11 @@ const ProtectionRulesForm = (props: {
|
|||||||
<FormInput.CheckBox className={css.minText} label={getString('mergeCommit')} name={'mergeCommit'} />
|
<FormInput.CheckBox className={css.minText} label={getString('mergeCommit')} name={'mergeCommit'} />
|
||||||
<FormInput.CheckBox className={css.minText} label={getString('squashMerge')} name={'squashMerge'} />
|
<FormInput.CheckBox className={css.minText} label={getString('squashMerge')} name={'squashMerge'} />
|
||||||
<FormInput.CheckBox className={css.minText} label={getString('rebaseMerge')} name={'rebaseMerge'} />
|
<FormInput.CheckBox className={css.minText} label={getString('rebaseMerge')} name={'rebaseMerge'} />
|
||||||
|
<FormInput.CheckBox
|
||||||
|
className={css.minText}
|
||||||
|
label={getString('fastForwardMerge')}
|
||||||
|
name={'fastForwardMerge'}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
@ -424,6 +424,7 @@ export interface StringsMap {
|
|||||||
failedToFetchFileContent: string
|
failedToFetchFileContent: string
|
||||||
failedToImportSpace: string
|
failedToImportSpace: string
|
||||||
failedToSavePipeline: string
|
failedToSavePipeline: string
|
||||||
|
fastForwardMerge: string
|
||||||
featureRoadmap: string
|
featureRoadmap: string
|
||||||
fileDeleted: string
|
fileDeleted: string
|
||||||
fileTooLarge: string
|
fileTooLarge: string
|
||||||
@ -801,6 +802,8 @@ export interface StringsMap {
|
|||||||
'pr.mergeOptions.createAMergeCommit': string
|
'pr.mergeOptions.createAMergeCommit': string
|
||||||
'pr.mergeOptions.createMergeCommit': string
|
'pr.mergeOptions.createMergeCommit': string
|
||||||
'pr.mergeOptions.createMergeCommitDesc': string
|
'pr.mergeOptions.createMergeCommitDesc': string
|
||||||
|
'pr.mergeOptions.fastForwardMerge': string
|
||||||
|
'pr.mergeOptions.fastForwardMergeDesc': string
|
||||||
'pr.mergeOptions.rebaseAndMerge': string
|
'pr.mergeOptions.rebaseAndMerge': string
|
||||||
'pr.mergeOptions.rebaseAndMergeDesc': string
|
'pr.mergeOptions.rebaseAndMergeDesc': string
|
||||||
'pr.mergeOptions.squashAndMerge': string
|
'pr.mergeOptions.squashAndMerge': string
|
||||||
@ -825,6 +828,7 @@ export interface StringsMap {
|
|||||||
'pr.prStateChanged': string
|
'pr.prStateChanged': string
|
||||||
'pr.prStateChangedDraft': string
|
'pr.prStateChangedDraft': string
|
||||||
'pr.readyForReview': string
|
'pr.readyForReview': string
|
||||||
|
'pr.rebaseMergePossible': string
|
||||||
'pr.removeSuggestion': string
|
'pr.removeSuggestion': string
|
||||||
'pr.requestSubmitted': string
|
'pr.requestSubmitted': string
|
||||||
'pr.requestedChanges': string
|
'pr.requestedChanges': string
|
||||||
@ -995,6 +999,7 @@ export interface StringsMap {
|
|||||||
'securitySettings.vulnerabilityScanning': string
|
'securitySettings.vulnerabilityScanning': string
|
||||||
'securitySettings.vulnerabilityScanningDesc': string
|
'securitySettings.vulnerabilityScanningDesc': string
|
||||||
seeNMoreMatches: string
|
seeNMoreMatches: string
|
||||||
|
selectAuthor: string
|
||||||
selectBranchPlaceHolder: string
|
selectBranchPlaceHolder: string
|
||||||
selectLanguagePlaceholder: string
|
selectLanguagePlaceholder: string
|
||||||
selectMergeStrat: string
|
selectMergeStrat: string
|
||||||
|
@ -302,6 +302,7 @@ pr:
|
|||||||
reviewChanges: Review changes
|
reviewChanges: Review changes
|
||||||
mergePR: Merge pull request
|
mergePR: Merge pull request
|
||||||
branchHasNoConflicts: Pull request can be merged
|
branchHasNoConflicts: Pull request can be merged
|
||||||
|
rebaseMergePossible: Pull request can be merged after rebase
|
||||||
checkingToMerge: Checking for ability to merge automatically...
|
checkingToMerge: Checking for ability to merge automatically...
|
||||||
prCanBeMerged: Mergeing can be performed automatically.
|
prCanBeMerged: Mergeing can be performed automatically.
|
||||||
enterDesc: Enter description here
|
enterDesc: Enter description here
|
||||||
@ -341,6 +342,8 @@ pr:
|
|||||||
createMergeCommitDesc: All commits from this branch will be added to the base branch via a merge commit.
|
createMergeCommitDesc: All commits from this branch will be added to the base branch via a merge commit.
|
||||||
rebaseAndMerge: Rebase and merge
|
rebaseAndMerge: Rebase and merge
|
||||||
rebaseAndMergeDesc: All commits from this branch will be rebased and added to the base branch.
|
rebaseAndMergeDesc: All commits from this branch will be rebased and added to the base branch.
|
||||||
|
fastForwardMerge: Fast-forward merge
|
||||||
|
fastForwardMergeDesc: All commits from this branch will be added to the base branch without a merge commit. Rebase may be required.
|
||||||
close: Close pull request
|
close: Close pull request
|
||||||
closeDesc: Close this pull request. You can still re-open the request after closing.
|
closeDesc: Close this pull request. You can still re-open the request after closing.
|
||||||
createAMergeCommit: Create a merge commit
|
createAMergeCommit: Create a merge commit
|
||||||
@ -539,6 +542,7 @@ zoomIn: Zoom In
|
|||||||
zoomOut: Zoom Out
|
zoomOut: Zoom Out
|
||||||
checks: Checks
|
checks: Checks
|
||||||
blameCommitLine: '{author} committed {timestamp}'
|
blameCommitLine: '{author} committed {timestamp}'
|
||||||
|
selectAuthor: Select Author
|
||||||
tooltipRepoEdit: You are not authorized to {PERMS}
|
tooltipRepoEdit: You are not authorized to {PERMS}
|
||||||
missingPerms: 'You are missing the following permission:'
|
missingPerms: 'You are missing the following permission:'
|
||||||
createRepoPerms: 'Create / Edit Repository'
|
createRepoPerms: 'Create / Edit Repository'
|
||||||
@ -964,6 +968,7 @@ setting: Setting
|
|||||||
mergeCommit: Merge commit
|
mergeCommit: Merge commit
|
||||||
squashMerge: Squash and merge
|
squashMerge: Squash and merge
|
||||||
rebaseMerge: Rebase and merge
|
rebaseMerge: Rebase and merge
|
||||||
|
fastForwardMerge: Fast-forward merge
|
||||||
Enable: Enable
|
Enable: Enable
|
||||||
imageUpload:
|
imageUpload:
|
||||||
title: Upload attachment
|
title: Upload attachment
|
||||||
|
@ -27,6 +27,7 @@ import { MergeStrategy } from 'utils/GitUtils'
|
|||||||
import mergeVideo from '../../../../videos/merge.mp4'
|
import mergeVideo from '../../../../videos/merge.mp4'
|
||||||
import squashVideo from '../../../../videos/squash.mp4'
|
import squashVideo from '../../../../videos/squash.mp4'
|
||||||
import rebaseVideo from '../../../../videos/rebase.mp4'
|
import rebaseVideo from '../../../../videos/rebase.mp4'
|
||||||
|
import fastForward from '../../../../videos/fastForward.mp4'
|
||||||
import css from './PullRequestActionsBox.module.scss'
|
import css from './PullRequestActionsBox.module.scss'
|
||||||
|
|
||||||
interface InlineMergeBoxProps {
|
interface InlineMergeBoxProps {
|
||||||
@ -96,6 +97,8 @@ const InlineMergeBox = (props: InlineMergeBoxProps) => {
|
|||||||
<video height={36} width={148} src={rebaseVideo} autoPlay={true} loop={false} muted={true} />
|
<video height={36} width={148} src={rebaseVideo} autoPlay={true} loop={false} muted={true} />
|
||||||
) : mergeOption.method === MergeStrategy.SQUASH ? (
|
) : mergeOption.method === MergeStrategy.SQUASH ? (
|
||||||
<video height={36} width={148} src={squashVideo} autoPlay={true} loop={false} muted={true} />
|
<video height={36} width={148} src={squashVideo} autoPlay={true} loop={false} muted={true} />
|
||||||
|
) : mergeOption.method === MergeStrategy.FAST_FORWARD ? (
|
||||||
|
<video height={36} width={148} src={fastForward} autoPlay={true} loop={false} muted={true} />
|
||||||
) : (
|
) : (
|
||||||
<video height={36} width={148} src={mergeVideo} autoPlay={true} loop={false} muted={true} />
|
<video height={36} width={148} src={mergeVideo} autoPlay={true} loop={false} muted={true} />
|
||||||
)}
|
)}
|
||||||
@ -142,7 +145,8 @@ const InlineMergeBox = (props: InlineMergeBoxProps) => {
|
|||||||
{(mergeOption.method === MergeStrategy.SQUASH || mergeOption.method === MergeStrategy.MERGE) && (
|
{(mergeOption.method === MergeStrategy.SQUASH || mergeOption.method === MergeStrategy.MERGE) && (
|
||||||
<FormInput.Text name="commitTitle"></FormInput.Text>
|
<FormInput.Text name="commitTitle"></FormInput.Text>
|
||||||
)}
|
)}
|
||||||
{mergeOption.method !== MergeStrategy.REBASE && (
|
{mergeOption.method !== MergeStrategy.REBASE &&
|
||||||
|
mergeOption.method !== MergeStrategy.FAST_FORWARD && (
|
||||||
<FormInput.TextArea
|
<FormInput.TextArea
|
||||||
placeholder={getString('addOptionalCommitMessage')}
|
placeholder={getString('addOptionalCommitMessage')}
|
||||||
name="commitMessage"></FormInput.TextArea>
|
name="commitMessage"></FormInput.TextArea>
|
||||||
|
@ -89,6 +89,8 @@
|
|||||||
&.merged {
|
&.merged {
|
||||||
font-weight: unset !important;
|
font-weight: unset !important;
|
||||||
color: var(--purple-700) !important;
|
color: var(--purple-700) !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.draft {
|
&.draft {
|
||||||
@ -108,7 +110,7 @@
|
|||||||
.boldText {
|
.boldText {
|
||||||
color: var(--purple-700) !important;
|
color: var(--purple-700) !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
font-size: 16px !important;
|
font-size: 14px !important;
|
||||||
line-height: 24px !important;
|
line-height: 24px !important;
|
||||||
}
|
}
|
||||||
.widthContainer {
|
.widthContainer {
|
||||||
|
@ -30,17 +30,18 @@ import {
|
|||||||
useToaster
|
useToaster
|
||||||
} from '@harnessio/uicore'
|
} from '@harnessio/uicore'
|
||||||
import { Icon } from '@harnessio/icons'
|
import { Icon } from '@harnessio/icons'
|
||||||
import { Color } from '@harnessio/design-system'
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
import { MutateMethod, useMutate } from 'restful-react'
|
import { MutateMethod, useMutate } from 'restful-react'
|
||||||
import { Case, Else, Match, Render, Truthy } from 'react-jsx-match'
|
import { Case, Else, Match, Render, Truthy } from 'react-jsx-match'
|
||||||
import { Menu, PopoverPosition, Icon as BIcon } from '@blueprintjs/core'
|
import { Menu, PopoverPosition, Icon as BIcon } from '@blueprintjs/core'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import ReactTimeago from 'react-timeago'
|
import { defaultTo } from 'lodash-es'
|
||||||
import type {
|
import type {
|
||||||
CreateBranchPathParams,
|
CreateBranchPathParams,
|
||||||
DeletePullReqSourceBranchQueryParams,
|
DeletePullReqSourceBranchQueryParams,
|
||||||
OpenapiCreateBranchRequest,
|
OpenapiCreateBranchRequest,
|
||||||
OpenapiStatePullReqRequest,
|
OpenapiStatePullReqRequest,
|
||||||
|
RebaseBranchRequestBody,
|
||||||
TypesListCommitResponse,
|
TypesListCommitResponse,
|
||||||
TypesPullReq,
|
TypesPullReq,
|
||||||
TypesRuleViolations
|
TypesRuleViolations
|
||||||
@ -58,9 +59,9 @@ import {
|
|||||||
permissionProps
|
permissionProps
|
||||||
} from 'utils/Utils'
|
} from 'utils/Utils'
|
||||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||||
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
|
||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
||||||
|
import { TimePopoverWithLocal } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
||||||
import { BranchActionsButton } from '../PullRequestOverviewPanel/sections/BranchActionsSection'
|
import { BranchActionsButton } from '../PullRequestOverviewPanel/sections/BranchActionsSection'
|
||||||
import InlineMergeBox from './InlineMergeBox'
|
import InlineMergeBox from './InlineMergeBox'
|
||||||
import css from './PullRequestActionsBox.module.scss'
|
import css from './PullRequestActionsBox.module.scss'
|
||||||
@ -83,6 +84,9 @@ export interface PullRequestActionsBoxProps extends Pick<GitInfoProps, 'repoMeta
|
|||||||
setShowDeleteBranchButton: React.Dispatch<React.SetStateAction<boolean>>
|
setShowDeleteBranchButton: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
setShowRestoreBranchButton: React.Dispatch<React.SetStateAction<boolean>>
|
setShowRestoreBranchButton: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
isSourceBranchDeleted: boolean
|
isSourceBranchDeleted: boolean
|
||||||
|
mergeOption: PRMergeOption
|
||||||
|
setMergeOption: (val: PRMergeOption) => void
|
||||||
|
rebasePossible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
||||||
@ -102,10 +106,13 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
showDeleteBranchButton,
|
showDeleteBranchButton,
|
||||||
setShowRestoreBranchButton,
|
setShowRestoreBranchButton,
|
||||||
setShowDeleteBranchButton,
|
setShowDeleteBranchButton,
|
||||||
isSourceBranchDeleted
|
isSourceBranchDeleted,
|
||||||
|
mergeOption,
|
||||||
|
setMergeOption,
|
||||||
|
rebasePossible
|
||||||
}) => {
|
}) => {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { showError } = useToaster()
|
const { showSuccess, showError } = useToaster()
|
||||||
const inlineMergeRef = useRef<inlineMergeFormRefType>(null)
|
const inlineMergeRef = useRef<inlineMergeFormRefType>(null)
|
||||||
const { hooks, standalone } = useAppContext()
|
const { hooks, standalone } = useAppContext()
|
||||||
const space = useGetSpaceParam()
|
const space = useGetSpaceParam()
|
||||||
@ -122,6 +129,19 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
verb: 'POST',
|
verb: 'POST',
|
||||||
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullReqMetadata.number}/state`
|
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullReqMetadata.number}/state`
|
||||||
})
|
})
|
||||||
|
const { mutate: rebase } = useMutate<RebaseBranchRequestBody>({
|
||||||
|
verb: 'POST',
|
||||||
|
path: `/api/v1/repos/${repoMetadata.path}/+/rebase`
|
||||||
|
})
|
||||||
|
|
||||||
|
const rebaseRequestPayload = {
|
||||||
|
base_branch: pullReqMetadata.target_branch,
|
||||||
|
bypass_rules: true,
|
||||||
|
dry_run_rules: false,
|
||||||
|
head_branch: pullReqMetadata.source_branch,
|
||||||
|
head_commit_sha: pullReqMetadata.source_sha
|
||||||
|
}
|
||||||
|
|
||||||
const mergeable = useMemo(() => pullReqMetadata.merge_check_status === MergeCheckStatus.MERGEABLE, [pullReqMetadata])
|
const mergeable = useMemo(() => pullReqMetadata.merge_check_status === MergeCheckStatus.MERGEABLE, [pullReqMetadata])
|
||||||
const isClosed = pullReqMetadata.state === PullRequestState.CLOSED
|
const isClosed = pullReqMetadata.state === PullRequestState.CLOSED
|
||||||
const isOpen = pullReqMetadata.state === PullRequestState.OPEN
|
const isOpen = pullReqMetadata.state === PullRequestState.OPEN
|
||||||
@ -193,11 +213,12 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
} // eslint-disable-next-line react-hooks/exhaustive-deps
|
} // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [onPRStateChanged, isMerged, isClosed, pullReqMetadata?.source_sha])
|
}, [onPRStateChanged, isMerged, isClosed, pullReqMetadata?.source_sha])
|
||||||
|
|
||||||
const mergeOptions = useMemo(() => getMergeOptions(getString, mergeable).slice(0, 3), [mergeable])
|
const mergeOptions = useMemo(() => getMergeOptions(getString, mergeable).slice(0, 4), [mergeable])
|
||||||
const [allowedStrats, setAllowedStrats] = useState<string[]>([
|
const [allowedStrats, setAllowedStrats] = useState<string[]>([
|
||||||
mergeOptions[0].method,
|
mergeOptions[0].method,
|
||||||
mergeOptions[1].method,
|
mergeOptions[1].method,
|
||||||
mergeOptions[2].method
|
mergeOptions[2].method,
|
||||||
|
mergeOptions[3].method
|
||||||
])
|
])
|
||||||
const draftOptions: PRDraftOption[] = [
|
const draftOptions: PRDraftOption[] = [
|
||||||
{
|
{
|
||||||
@ -212,11 +233,6 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
const [showInlineMergeContainer, setShowInlineMergeContainer] = useState(false)
|
const [showInlineMergeContainer, setShowInlineMergeContainer] = useState(false)
|
||||||
const [mergeOption, setMergeOption] = useUserPreference<PRMergeOption>(
|
|
||||||
UserPreference.PULL_REQUEST_MERGE_STRATEGY,
|
|
||||||
mergeOptions[0],
|
|
||||||
option => option.method !== 'close'
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (allowedStrats) {
|
if (allowedStrats) {
|
||||||
@ -283,6 +299,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
className={cx(css.main, {
|
className={cx(css.main, {
|
||||||
[css.primary]: !PRStateLoading,
|
[css.primary]: !PRStateLoading,
|
||||||
[css.error]: mergeable === false && !unchecked && !isClosed && !isDraft,
|
[css.error]: mergeable === false && !unchecked && !isClosed && !isDraft,
|
||||||
|
[css.error]: mergeOption.method === MergeStrategy.FAST_FORWARD && rebasePossible,
|
||||||
[css.unchecked]: unchecked,
|
[css.unchecked]: unchecked,
|
||||||
[css.closed]: isClosed,
|
[css.closed]: isClosed,
|
||||||
[css.draft]: isDraft,
|
[css.draft]: isDraft,
|
||||||
@ -302,6 +319,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
[css.draft]: isDraft,
|
[css.draft]: isDraft,
|
||||||
[css.closed]: isClosed,
|
[css.closed]: isClosed,
|
||||||
[css.unmergeable]: mergeable === false && isOpen,
|
[css.unmergeable]: mergeable === false && isOpen,
|
||||||
|
[css.unmergeable]: mergeOption.method === MergeStrategy.FAST_FORWARD && rebasePossible && isOpen,
|
||||||
[css.ruleViolate]: ruleViolation && !isClosed
|
[css.ruleViolate]: ruleViolation && !isClosed
|
||||||
})}>
|
})}>
|
||||||
{getString(
|
{getString(
|
||||||
@ -315,6 +333,8 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
? 'branchProtection.prFailedText'
|
? 'branchProtection.prFailedText'
|
||||||
: ruleViolation
|
: ruleViolation
|
||||||
? 'branchProtection.prFailedText'
|
? 'branchProtection.prFailedText'
|
||||||
|
: mergeOption.method === MergeStrategy.FAST_FORWARD && rebasePossible
|
||||||
|
? 'branchProtection.prFailedText'
|
||||||
: 'pr.branchHasNoConflicts'
|
: 'pr.branchHasNoConflicts'
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
@ -473,7 +493,9 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isMerged}
|
disabled={
|
||||||
|
isMerged || (mergeOption.method === MergeStrategy.FAST_FORWARD && rebasePossible)
|
||||||
|
}
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
text={getString('confirmStrat', { strat: mergeOption.title })}
|
text={getString('confirmStrat', { strat: mergeOption.title })}
|
||||||
/>
|
/>
|
||||||
@ -506,7 +528,25 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
})
|
})
|
||||||
.catch(exception => showError(getErrorMessage(exception)))
|
.catch(exception => showError(getErrorMessage(exception)))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
...(rebasePossible
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
hasIcon: true,
|
||||||
|
iconName: 'code-pull',
|
||||||
|
text: getString('rebase'),
|
||||||
|
onClick: () =>
|
||||||
|
rebase(rebaseRequestPayload)
|
||||||
|
.then(() => {
|
||||||
|
showSuccess(getString('updatedBranchMessageRebase'))
|
||||||
|
setTimeout(() => {
|
||||||
|
refetchActivities()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
.catch(err => showError(getErrorMessage(err)))
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
]}
|
]}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
interactionKind: 'click',
|
interactionKind: 'click',
|
||||||
@ -593,7 +633,15 @@ const MergeInfo: React.FC<{
|
|||||||
</strong>
|
</strong>
|
||||||
</Container>
|
</Container>
|
||||||
),
|
),
|
||||||
time: <ReactTimeago className={css.dateText} date={pullRequestMetadata.merged as number} />
|
time: (
|
||||||
|
<TimePopoverWithLocal
|
||||||
|
className={css.dateText}
|
||||||
|
time={defaultTo(pullRequestMetadata.merged as number, 0)}
|
||||||
|
inline={false}
|
||||||
|
font={{ variation: FontVariation.SMALL }}
|
||||||
|
color={Color.GREY_400}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -30,15 +30,17 @@ import type {
|
|||||||
TypesBranch
|
TypesBranch
|
||||||
} from 'services/code'
|
} from 'services/code'
|
||||||
import {
|
import {
|
||||||
|
PRMergeOption,
|
||||||
PanelSectionOutletPosition,
|
PanelSectionOutletPosition,
|
||||||
extractSpecificViolations,
|
extractSpecificViolations,
|
||||||
getMergeOptions
|
getMergeOptions
|
||||||
} from 'pages/PullRequest/PullRequestUtils'
|
} from 'pages/PullRequest/PullRequestUtils'
|
||||||
import { MergeCheckStatus, extractInfoFromRuleViolationArr } from 'utils/Utils'
|
import { MergeCheckStatus, extractInfoFromRuleViolationArr } from 'utils/Utils'
|
||||||
import { PullRequestState, dryMerge } from 'utils/GitUtils'
|
import { MergeStrategy, PullRequestState, dryMerge } from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
|
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
|
||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
|
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
||||||
import { PullRequestActionsBox } from '../PullRequestActionsBox/PullRequestActionsBox'
|
import { PullRequestActionsBox } from '../PullRequestActionsBox/PullRequestActionsBox'
|
||||||
import PullRequestPanelSections from './PullRequestPanelSections'
|
import PullRequestPanelSections from './PullRequestPanelSections'
|
||||||
import ChecksSection from './sections/ChecksSection'
|
import ChecksSection from './sections/ChecksSection'
|
||||||
@ -241,6 +243,12 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
|
|||||||
[pullReqMetadata]
|
[pullReqMetadata]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [mergeOption, setMergeOption] = useUserPreference<PRMergeOption>(
|
||||||
|
UserPreference.PULL_REQUEST_MERGE_STRATEGY,
|
||||||
|
mergeOptions[0],
|
||||||
|
option => option.method !== 'close'
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container margin={{ bottom: 'medium' }} className={css.mainContainer}>
|
<Container margin={{ bottom: 'medium' }} className={css.mainContainer}>
|
||||||
<Layout.Vertical>
|
<Layout.Vertical>
|
||||||
@ -264,6 +272,9 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
|
|||||||
setShowDeleteBranchButton={setShowDeleteBranchButton}
|
setShowDeleteBranchButton={setShowDeleteBranchButton}
|
||||||
setShowRestoreBranchButton={setShowRestoreBranchButton}
|
setShowRestoreBranchButton={setShowRestoreBranchButton}
|
||||||
isSourceBranchDeleted={isSourceBranchDeleted}
|
isSourceBranchDeleted={isSourceBranchDeleted}
|
||||||
|
mergeOption={mergeOption}
|
||||||
|
setMergeOption={setMergeOption}
|
||||||
|
rebasePossible={rebasePossible}
|
||||||
/>
|
/>
|
||||||
{!isClosed ? (
|
{!isClosed ? (
|
||||||
<PullRequestPanelSections
|
<PullRequestPanelSections
|
||||||
@ -312,7 +323,8 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => {
|
|||||||
),
|
),
|
||||||
[PanelSectionOutletPosition.REBASE_SOURCE_BRANCH]: rebasePossible &&
|
[PanelSectionOutletPosition.REBASE_SOURCE_BRANCH]: rebasePossible &&
|
||||||
!mergeLoading &&
|
!mergeLoading &&
|
||||||
!conflictingFiles?.length && (
|
!conflictingFiles?.length &&
|
||||||
|
mergeOption.method === MergeStrategy.FAST_FORWARD && (
|
||||||
<RebaseSourceSection
|
<RebaseSourceSection
|
||||||
pullReqMetadata={pullReqMetadata}
|
pullReqMetadata={pullReqMetadata}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
|
@ -87,6 +87,7 @@ export const BranchActionsButton = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
style={{ whiteSpace: 'nowrap' }}
|
||||||
text={showDeleteBranchButton ? getString('deleteBranch') : getString('restoreBranch')}
|
text={showDeleteBranchButton ? getString('deleteBranch') : getString('restoreBranch')}
|
||||||
variation={ButtonVariation.SECONDARY}
|
variation={ButtonVariation.SECONDARY}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -33,7 +33,7 @@ import { GitRefLink } from 'components/GitRefLink/GitRefLink'
|
|||||||
import { getErrorMessage, permissionProps } from 'utils/Utils'
|
import { getErrorMessage, permissionProps } from 'utils/Utils'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import Fail from '../../../../../icons/code-fail-grey.svg?url'
|
import FailRed from '../../../../../icons/code-fail.svg?url'
|
||||||
import css from '../PullRequestOverviewPanel.module.scss'
|
import css from '../PullRequestOverviewPanel.module.scss'
|
||||||
|
|
||||||
interface RebaseSourceSectionProps {
|
interface RebaseSourceSectionProps {
|
||||||
@ -80,9 +80,9 @@ const RebaseSourceSection = (props: RebaseSourceSectionProps) => {
|
|||||||
<Container className={cx(css.sectionContainer, css.borderRadius)}>
|
<Container className={cx(css.sectionContainer, css.borderRadius)}>
|
||||||
<Layout.Horizontal flex={{ justifyContent: 'space-between' }}>
|
<Layout.Horizontal flex={{ justifyContent: 'space-between' }}>
|
||||||
<Layout.Horizontal flex={{ alignItems: 'center' }}>
|
<Layout.Horizontal flex={{ alignItems: 'center' }}>
|
||||||
<img alt={getString('failed')} width={26} height={26} color={Color.GREY_500} src={Fail} />
|
<img alt={getString('failed')} width={26} height={26} src={FailRed} />
|
||||||
<Layout.Vertical padding={{ left: 'medium' }}>
|
<Layout.Vertical padding={{ left: 'medium' }}>
|
||||||
<Text padding={{ bottom: 'xsmall' }} className={css.sectionTitle} color={Color.GREY_600}>
|
<Text padding={{ bottom: 'xsmall' }} className={css.sectionTitle} color={Color.RED_500}>
|
||||||
{getString('rebaseSource.title')}
|
{getString('rebaseSource.title')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={css.sectionSubheader} color={Color.GREY_450} font={{ variation: FontVariation.BODY }}>
|
<Text className={css.sectionSubheader} color={Color.GREY_450} font={{ variation: FontVariation.BODY }}>
|
||||||
|
@ -65,7 +65,7 @@ export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, c
|
|||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Avatar name={pullReqMetadata.merger?.display_name} size="small" hoverCard={false} />
|
<Avatar name={pullReqMetadata.merger?.display_name} size="small" hoverCard={false} />
|
||||||
<Text flex tag="div">
|
<Text flex tag="div" style={{ whiteSpace: 'nowrap' }}>
|
||||||
<StringSubstitute
|
<StringSubstitute
|
||||||
str={
|
str={
|
||||||
(payload?.payload as MergePayload)?.merge_method === MergeStrategy.REBASE
|
(payload?.payload as MergePayload)?.merge_method === MergeStrategy.REBASE
|
||||||
@ -74,8 +74,16 @@ export const SystemComment: React.FC<SystemCommentProps> = ({ pullReqMetadata, c
|
|||||||
}
|
}
|
||||||
vars={{
|
vars={{
|
||||||
user: <strong className={css.rightTextPadding}>{pullReqMetadata.merger?.display_name}</strong>,
|
user: <strong className={css.rightTextPadding}>{pullReqMetadata.merger?.display_name}</strong>,
|
||||||
source: <strong className={css.textPadding}>{pullReqMetadata.source_branch}</strong>,
|
source: (
|
||||||
target: <strong className={css.textPadding}>{pullReqMetadata.target_branch}</strong>,
|
<Text lineClamp={1}>
|
||||||
|
<strong className={css.textPadding}>{pullReqMetadata.source_branch}</strong>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
target: (
|
||||||
|
<Text lineClamp={1}>
|
||||||
|
<strong className={css.textPadding}>{pullReqMetadata.target_branch}</strong>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
bypassed: (payload?.payload as MergePayload)?.rules_bypassed,
|
bypassed: (payload?.payload as MergePayload)?.rules_bypassed,
|
||||||
mergeSha: (
|
mergeSha: (
|
||||||
<Container className={css.commitContainer} padding={{ left: 'small', right: 'xsmall' }}>
|
<Container className={css.commitContainer} padding={{ left: 'small', right: 'xsmall' }}>
|
||||||
|
@ -113,6 +113,14 @@ export const getMergeOptions = (getString: UseStringsReturn['getString'], mergea
|
|||||||
label: getString('pr.mergeOptions.rebaseAndMerge'),
|
label: getString('pr.mergeOptions.rebaseAndMerge'),
|
||||||
value: MergeStrategy.REBASE
|
value: MergeStrategy.REBASE
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: MergeStrategy.FAST_FORWARD,
|
||||||
|
title: getString('pr.mergeOptions.fastForwardMerge'),
|
||||||
|
desc: getString('pr.mergeOptions.fastForwardMergeDesc'),
|
||||||
|
disabled: mergeable === false,
|
||||||
|
label: getString('pr.mergeOptions.fastForwardMerge'),
|
||||||
|
value: MergeStrategy.FAST_FORWARD
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: 'close',
|
method: 'close',
|
||||||
title: getString('pr.mergeOptions.close'),
|
title: getString('pr.mergeOptions.close'),
|
||||||
|
@ -209,7 +209,7 @@ export function PullRequestsContentHeader({
|
|||||||
popoverClassName={css.branchDropdown}
|
popoverClassName={css.branchDropdown}
|
||||||
icon="nav-user-profile"
|
icon="nav-user-profile"
|
||||||
iconProps={{ size: 16 }}
|
iconProps={{ size: 16 }}
|
||||||
placeholder="Select Authors"
|
placeholder={getString('selectAuthor')}
|
||||||
addClearBtn={true}
|
addClearBtn={true}
|
||||||
resetOnClose
|
resetOnClose
|
||||||
resetOnSelect
|
resetOnSelect
|
||||||
|
@ -116,7 +116,7 @@ export type EnumMembershipRole = 'contributor' | 'executor' | 'reader' | 'space_
|
|||||||
|
|
||||||
export type EnumMergeCheckStatus = string
|
export type EnumMergeCheckStatus = string
|
||||||
|
|
||||||
export type EnumMergeMethod = 'merge' | 'rebase' | 'squash'
|
export type EnumMergeMethod = 'fast-forward' | 'merge' | 'rebase' | 'squash'
|
||||||
|
|
||||||
export type EnumParentResourceType = 'space' | 'repo'
|
export type EnumParentResourceType = 'space' | 'repo'
|
||||||
|
|
||||||
@ -1156,9 +1156,12 @@ export type TypesGitspaceInstance = {
|
|||||||
access_key?: string | null
|
access_key?: string | null
|
||||||
access_key_ref?: string | null
|
access_key_ref?: string | null
|
||||||
access_type?: EnumGitspaceAccessType
|
access_type?: EnumGitspaceAccessType
|
||||||
|
active_time_ended?: number | null
|
||||||
|
active_time_started?: number | null
|
||||||
created?: number
|
created?: number
|
||||||
identifier?: string
|
identifier?: string
|
||||||
last_used?: number
|
last_heartbeat?: number | null
|
||||||
|
last_used?: number | null
|
||||||
machine_user?: string | null
|
machine_user?: string | null
|
||||||
resource_usage?: string | null
|
resource_usage?: string | null
|
||||||
space_path?: string
|
space_path?: string
|
||||||
@ -1475,6 +1478,9 @@ export interface TypesPullReqStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TypesRebaseResponse {
|
export interface TypesRebaseResponse {
|
||||||
|
already_ancestor?: boolean
|
||||||
|
conflict_files?: string[]
|
||||||
|
dry_run?: boolean
|
||||||
dry_run_rules?: boolean
|
dry_run_rules?: boolean
|
||||||
new_head_branch_sha?: ShaSHA
|
new_head_branch_sha?: ShaSHA
|
||||||
rule_violations?: TypesRuleViolations[]
|
rule_violations?: TypesRuleViolations[]
|
||||||
@ -6240,6 +6246,7 @@ export interface RebaseBranchPathParams {
|
|||||||
export interface RebaseBranchRequestBody {
|
export interface RebaseBranchRequestBody {
|
||||||
base_branch?: string
|
base_branch?: string
|
||||||
bypass_rules?: boolean
|
bypass_rules?: boolean
|
||||||
|
dry_run?: boolean
|
||||||
dry_run_rules?: boolean
|
dry_run_rules?: boolean
|
||||||
head_branch?: string
|
head_branch?: string
|
||||||
head_commit_sha?: ShaSHA
|
head_commit_sha?: ShaSHA
|
||||||
|
@ -6225,6 +6225,8 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
bypass_rules:
|
bypass_rules:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
dry_run:
|
||||||
|
type: boolean
|
||||||
dry_run_rules:
|
dry_run_rules:
|
||||||
type: boolean
|
type: boolean
|
||||||
head_branch:
|
head_branch:
|
||||||
@ -10540,6 +10542,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
EnumMergeMethod:
|
EnumMergeMethod:
|
||||||
enum:
|
enum:
|
||||||
|
- fast-forward
|
||||||
- merge
|
- merge
|
||||||
- rebase
|
- rebase
|
||||||
- squash
|
- squash
|
||||||
@ -12408,11 +12411,21 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
access_type:
|
access_type:
|
||||||
$ref: '#/components/schemas/EnumGitspaceAccessType'
|
$ref: '#/components/schemas/EnumGitspaceAccessType'
|
||||||
|
active_time_ended:
|
||||||
|
nullable: true
|
||||||
|
type: integer
|
||||||
|
active_time_started:
|
||||||
|
nullable: true
|
||||||
|
type: integer
|
||||||
created:
|
created:
|
||||||
type: integer
|
type: integer
|
||||||
identifier:
|
identifier:
|
||||||
type: string
|
type: string
|
||||||
|
last_heartbeat:
|
||||||
|
nullable: true
|
||||||
|
type: integer
|
||||||
last_used:
|
last_used:
|
||||||
|
nullable: true
|
||||||
type: integer
|
type: integer
|
||||||
machine_user:
|
machine_user:
|
||||||
nullable: true
|
nullable: true
|
||||||
@ -13013,6 +13026,14 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
TypesRebaseResponse:
|
TypesRebaseResponse:
|
||||||
properties:
|
properties:
|
||||||
|
already_ancestor:
|
||||||
|
type: boolean
|
||||||
|
conflict_files:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
dry_run:
|
||||||
|
type: boolean
|
||||||
dry_run_rules:
|
dry_run_rules:
|
||||||
type: boolean
|
type: boolean
|
||||||
new_head_branch_sha:
|
new_head_branch_sha:
|
||||||
|
@ -233,7 +233,8 @@ export const PullRequestFilterOption = {
|
|||||||
export enum MergeStrategy {
|
export enum MergeStrategy {
|
||||||
MERGE = 'merge',
|
MERGE = 'merge',
|
||||||
SQUASH = 'squash',
|
SQUASH = 'squash',
|
||||||
REBASE = 'rebase'
|
REBASE = 'rebase',
|
||||||
|
FAST_FORWARD = 'fast-forward'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeIcon = {
|
export const CodeIcon = {
|
||||||
|
@ -385,6 +385,7 @@ export type RulesFormPayload = {
|
|||||||
mergeCommit?: boolean
|
mergeCommit?: boolean
|
||||||
squashMerge?: boolean
|
squashMerge?: boolean
|
||||||
rebaseMerge?: boolean
|
rebaseMerge?: boolean
|
||||||
|
fastForwardMerge?: boolean
|
||||||
autoDelete?: boolean
|
autoDelete?: boolean
|
||||||
blockBranchCreation?: boolean
|
blockBranchCreation?: boolean
|
||||||
blockBranchDeletion?: boolean
|
blockBranchDeletion?: boolean
|
||||||
|
BIN
web/src/videos/fastForward.mp4
Normal file
BIN
web/src/videos/fastForward.mp4
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user