[gh] Add an action to remove a branch after merge (IJPL-74161)

#IJPL-74161 Fixed

GitOrigin-RevId: c9f7cb782593d07534e770736b96255bc6ebc45a
This commit is contained in:
Chris Lemaire
2025-10-21 18:51:10 +00:00
committed by intellij-monorepo-bot
parent f61fd6c1cc
commit 7bb09ab6b1
20 changed files with 276 additions and 61 deletions
@@ -1566,6 +1566,7 @@ f:com.intellij.collaboration.ui.codereview.diff.viewer.DiffViewerUtilKt
- sf:bindSelectedIn(javax.swing.JCheckBox,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):V
- sf:bindSelectedItemIn(com.intellij.ui.dsl.builder.Cell,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):com.intellij.ui.dsl.builder.Cell
- sf:bindSelectedItemIn(javax.swing.ComboBoxModel,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):V
- sf:bindText(javax.swing.JLabel,java.lang.String,kotlinx.coroutines.flow.Flow):V
- sf:bindTextHtmlIn(javax.swing.JEditorPane,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.Flow):V
- sf:bindTextIn(com.intellij.openapi.editor.Document,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.MutableStateFlow):V
- sf:bindTextIn(com.intellij.openapi.editor.Document,kotlinx.coroutines.CoroutineScope,kotlinx.coroutines.flow.StateFlow,kotlin.jvm.functions.Function1):V
@@ -240,6 +240,12 @@ fun JEditorPane.bindTextHtmlIn(scope: CoroutineScope, textFlow: Flow<@Nls String
}
}
fun JLabel.bindText(debugName: String, textFlow: Flow<@Nls String>) {
launchOnShow(debugName) {
bindTextIn(this, textFlow)
}
}
fun JLabel.bindTextIn(scope: CoroutineScope, textFlow: Flow<@Nls String>) {
scope.launch(start = CoroutineStart.UNDISPATCHED) {
textFlow.collect {
@@ -16,9 +16,14 @@ fragment pullRequestInfo on PullRequest {
...repository
}
headRef {
id
}
headRefName
headRefOid
headRepository {
...repository
}
viewerCanDeleteHeadRef
}
@@ -0,0 +1,5 @@
mutation ($refId: ID!) {
deleteRef(input: {refId: $refId}) {
clientMutationId
}
}
@@ -125,6 +125,10 @@
</extensions>
<actions>
<action id="GitHub.Timeline.DeleteMergedBranch"
class="org.jetbrains.plugins.github.pullrequest.action.GHPRDeleteMergedHeadAction"
icon="AllIcons.General.Delete"/>
<action id="Github.Sync.Fork"
class="org.jetbrains.plugins.github.GithubSyncForkAction"
icon="AllIcons.Vcs.Vendors.Github">
@@ -323,6 +323,16 @@ pull.request.timeline.comment.suggested.changes.tooltip.different.branch=Unable
pull.request.comment.suggested.changes.applying=Applying patch
pull.request.comment.suggested.changes.resolving=Resolving thread
pull.request.comment.suggested.changes.committing=Applying and committing changes
pullRequest.timeline.merged.title=Pull Request Successfully Merged
pullRequest.timeline.merged.body.restore=Restore\u2026
pullRequest.timeline.merged.body.delete=delete
pullRequest.timeline.merged.body.withoutDelete={0} has been deleted. {1}
pullRequest.timeline.merged.body.withDelete=You can now {0} {1}.
action.GitHub.Timeline.DeleteMergedBranch.text=Delete Branch
action.GitHub.Timeline.DeleteMergedBranch.description=Delete the merged branch on GitHub
#branch widget
pull.request.on.branch=\#{0,number,#} on {1}
pull.request.on.branch.description=Pull request \#{0,number,#} exists for current branch {1}
@@ -21,6 +21,7 @@ object GHGQLQueries {
const val pullRequestCommits = "graphql/query/pullRequestCommits.graphql"
const val pullRequestMergeabilityData = "graphql/query/findPullRequestMergeability.graphql"
const val pullRequestFiles = "graphql/query/pullRequestFiles.graphql"
const val deleteRef = "graphql/query/deleteRef.graphql"
const val markFileAsViewed = "graphql/query/markFileAsViewed.graphql"
const val unmarkFileAsViewed = "graphql/query/unmarkFileAsViewed.graphql"
const val createReview = "graphql/query/createReview.graphql"
@@ -195,6 +195,15 @@ object GHGQLRequests {
}
}
object Ref {
fun delete(server: GithubServerPath, refId: String): GQLQuery<Unit> =
GQLQuery.Parsed(
server.toGraphQLUrl(), GHGQLQueries.deleteRef,
mapOf("refId" to refId),
Unit::class.java
)
}
object PullRequest {
fun findOneId(repository: GHRepositoryCoordinates, number: Long): GQLQuery<GHPRIdentifier?> =
GQLQuery.OptionalTraversedParsed(
@@ -9,47 +9,56 @@ import org.jetbrains.plugins.github.api.data.*
import java.util.*
@GraphQLFragment("/graphql/fragment/pullRequestInfo.graphql")
class GHPullRequest(id: String,
url: String,
number: Long,
title: String,
state: GHPullRequestState,
isDraft: Boolean,
author: GHActor?,
createdAt: Date,
updatedAt: Date,
@JsonProperty("assignees") assignees: GraphQLNodesDTO<GHUser>,
@JsonProperty("labels") labels: GraphQLNodesDTO<GHLabel>?,
@JsonProperty("reviewRequests") reviewRequests: GraphQLNodesDTO<GHPullRequestReviewRequest>?,
@JsonProperty("reviewThreads") reviewThreads: GraphQLNodesDTO<ReviewThreadDetails>,
@JsonProperty("reviews") reviews: GraphQLNodesDTO<GHPullRequestReview>?,
val reviewDecision: GHPullRequestReviewDecision?,
mergeable: GHPullRequestMergeableState,
viewerCanUpdate: Boolean,
viewerCanReact: Boolean,
viewerDidAuthor: Boolean,
@NlsSafe val body: String,
val baseRefName: String,
val baseRefOid: String,
val baseRepository: GHRepository?,
val headRefName: String,
val headRefOid: String,
val headRepository: GHRepository?,
override val reactions: GHReactable.ReactionConnection)
class GHPullRequest(
id: String,
url: String,
number: Long,
title: String,
state: GHPullRequestState,
isDraft: Boolean,
author: GHActor?,
createdAt: Date,
updatedAt: Date,
@JsonProperty("assignees") assignees: GraphQLNodesDTO<GHUser>,
@JsonProperty("labels") labels: GraphQLNodesDTO<GHLabel>?,
@JsonProperty("reviewRequests") reviewRequests: GraphQLNodesDTO<GHPullRequestReviewRequest>?,
@JsonProperty("reviewThreads") reviewThreads: GraphQLNodesDTO<ReviewThreadDetails>,
@JsonProperty("reviews") reviews: GraphQLNodesDTO<GHPullRequestReview>?,
val reviewDecision: GHPullRequestReviewDecision?,
mergeable: GHPullRequestMergeableState,
viewerCanUpdate: Boolean,
viewerCanReact: Boolean,
viewerDidAuthor: Boolean,
val viewerCanDeleteHeadRef: Boolean,
val body: @NlsSafe String,
val baseRefName: String,
val baseRefOid: String,
val baseRepository: GHRepository?,
private val headRef: HeadRef?,
val headRefName: String,
val headRefOid: String,
val headRepository: GHRepository?,
override val reactions: GHReactable.ReactionConnection,
)
: GHPullRequestShort(id, url, number, title, state, isDraft, author, createdAt, updatedAt,
assignees, labels, reviewRequests, reviewThreads,
reviews, mergeable, viewerCanUpdate, viewerCanReact, viewerDidAuthor, reactions) {
val headRefId: String? get() = headRef?.id
data class HeadRef(val id: String?)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GHPullRequest) return false
if (!super.equals(other)) return false
if (viewerCanDeleteHeadRef != other.viewerCanDeleteHeadRef) return false
if (reviewDecision != other.reviewDecision) return false
if (body != other.body) return false
if (baseRefName != other.baseRefName) return false
if (baseRefOid != other.baseRefOid) return false
if (baseRepository != other.baseRepository) return false
if (headRef != other.headRef) return false
if (headRefName != other.headRefName) return false
if (headRefOid != other.headRefOid) return false
if (headRepository != other.headRepository) return false
@@ -60,11 +69,13 @@ class GHPullRequest(id: String,
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + viewerCanDeleteHeadRef.hashCode()
result = 31 * result + (reviewDecision?.hashCode() ?: 0)
result = 31 * result + body.hashCode()
result = 31 * result + baseRefName.hashCode()
result = 31 * result + baseRefOid.hashCode()
result = 31 * result + (baseRepository?.hashCode() ?: 0)
result = 31 * result + headRef.hashCode()
result = 31 * result + headRefName.hashCode()
result = 31 * result + headRefOid.hashCode()
result = 31 * result + (headRepository?.hashCode() ?: 0)
@@ -0,0 +1,23 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.github.pullrequest.action
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import org.jetbrains.plugins.github.pullrequest.ui.timeline.GHPRTimelineViewModel
internal class GHPRDeleteMergedHeadAction : DumbAwareAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = false
val vm = e.getData(GHPRTimelineViewModel.DATA_KEY) ?: return
e.presentation.isEnabledAndVisible = vm.detailsVm.canDeleteMergedBranch.value
}
override fun actionPerformed(e: AnActionEvent) {
val vm = e.getData(GHPRTimelineViewModel.DATA_KEY) ?: return
vm.detailsVm.deleteMergedBranch()
}
}
@@ -42,6 +42,8 @@ interface GHPRDetailsDataProvider {
suspend fun squashMerge(commitMessage: Pair<String, String>, currentHeadRef: String)
suspend fun deleteMergedBranch(refId: String)
suspend fun signalDetailsNeedReload()
suspend fun signalMergeabilityNeedsReload()
@@ -113,6 +113,15 @@ internal class GHPRDetailsDataProviderImpl(parentCs: CoroutineScope,
notifyNeedsReload()
}
override suspend fun deleteMergedBranch(refId: String) {
try {
detailsService.deleteMergedBranch(pullRequestId, refId)
}
finally {
notifyNeedsReload()
}
}
private suspend fun notifyNeedsReload(state: Boolean = true) {
withContext(NonCancellable) {
signalDetailsNeedReload()
@@ -35,4 +35,6 @@ internal interface GHPRDetailsService {
suspend fun rebaseMerge(pullRequestId: GHPRIdentifier, currentHeadRef: String)
suspend fun squashMerge(pullRequestId: GHPRIdentifier, commitMessage: Pair<String, String>, currentHeadRef: String)
suspend fun deleteMergedBranch(pullRequestId: GHPRIdentifier, refId: String)
}
@@ -180,6 +180,16 @@ internal class GHPRDetailsServiceImpl(
}
}
override suspend fun deleteMergedBranch(pullRequestId: GHPRIdentifier, refId: String) {
runCatching {
requestExecutor.executeSuspend(
GHGQLRequests.Ref.delete(repository.serverPath, refId)
)
}.processErrorAndGet { e ->
LOG.info("Error occurred while deleting branch for PR ${pullRequestId.number}", e)
}
}
companion object {
private val LOG = logger<GHPRDetailsService>()
}
@@ -4,16 +4,23 @@ package org.jetbrains.plugins.github.pullrequest.ui.details.model
import com.intellij.openapi.util.NlsSafe
import org.jetbrains.plugins.github.api.data.GHActor
import org.jetbrains.plugins.github.api.data.GHReaction
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestState
import org.jetbrains.plugins.github.pullrequest.data.GHPRIdentifier
import java.util.*
data class GHPRDetailsFull(val id: GHPRIdentifier,
val url: String,
val author: GHActor,
val createdAt: Date,
val titleHtml: @NlsSafe String,
val description: String?,
val descriptionHtml: @NlsSafe String?,
val canEditDescription: Boolean,
val canReactDescription: Boolean,
val reactions: List<GHReaction>)
data class GHPRDetailsFull(
val id: GHPRIdentifier,
val url: String,
val author: GHActor,
val createdAt: Date,
val state: GHPullRequestState,
val titleHtml: @NlsSafe String,
val description: String?,
val descriptionHtml: @NlsSafe String?,
val headRefId: String?,
val headRefName: String?,
val canEditDescription: Boolean,
val canReactDescription: Boolean,
val canDeleteHeadRef: Boolean,
val reactions: List<GHReaction>,
)
@@ -2,20 +2,25 @@
package org.jetbrains.plugins.github.pullrequest.ui.timeline
import com.intellij.collaboration.async.childScope
import com.intellij.collaboration.async.mapState
import com.intellij.collaboration.async.stateInNow
import com.intellij.collaboration.ui.codereview.comment.CodeReviewSubmittableTextViewModelBase
import com.intellij.collaboration.ui.codereview.comment.CodeReviewTextEditingViewModel
import com.intellij.collaboration.ui.icon.IconsProvider
import com.intellij.collaboration.util.ComputedResult
import com.intellij.collaboration.util.ResultUtil.runCatchingUser
import com.intellij.collaboration.util.getOrNull
import com.intellij.collaboration.util.map
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.jetbrains.plugins.github.api.data.GHReactionContent
import org.jetbrains.plugins.github.api.data.GHUser
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequest
import org.jetbrains.plugins.github.api.data.pullrequest.GHPullRequestState
import org.jetbrains.plugins.github.pullrequest.comment.convertToHtml
import org.jetbrains.plugins.github.pullrequest.data.GHPRDataContext
import org.jetbrains.plugins.github.pullrequest.data.GHReactionsService
@@ -26,10 +31,16 @@ import org.jetbrains.plugins.github.pullrequest.ui.details.model.GHPRDetailsFull
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionViewModelImpl
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionsViewModel
class GHPRDetailsTimelineViewModel internal constructor(private val project: Project,
parentCs: CoroutineScope,
private val dataContext: GHPRDataContext,
private val dataProvider: GHPRDataProvider) {
class GHPRDetailsTimelineViewModel internal constructor(
private val project: Project,
parentCs: CoroutineScope,
private val dataContext: GHPRDataContext,
private val dataProvider: GHPRDataProvider,
) {
companion object {
private val LOG = logger<GHPRDetailsTimelineViewModel>()
}
private val cs = parentCs.childScope(this::class)
private val currentUser: GHUser = dataContext.securityService.currentUser
@@ -47,16 +58,31 @@ class GHPRDetailsTimelineViewModel internal constructor(private val project: Pro
val reactionsVm: GHReactionsViewModel =
GHReactionViewModelImpl(cs, dataProvider.id.id, loadedReactionsState, currentUser, reactionsService, reactionIconsProvider)
val isMerged: StateFlow<Boolean> =
details.mapState { it.getOrNull()?.state == GHPullRequestState.MERGED }
val canDeleteMergedBranch: StateFlow<Boolean> =
details.mapState {
val details = it.getOrNull() ?: return@mapState false
details.canDeleteHeadRef && details.state == GHPullRequestState.MERGED
}
val headRefName: StateFlow<String?> =
details.mapState { it.getOrNull()?.headRefName }
private fun createDetails(data: GHPullRequest): GHPRDetailsFull = GHPRDetailsFull(
dataProvider.id,
data.url,
data.author ?: dataContext.securityService.ghostUser,
data.createdAt,
data.state,
data.title.convertToHtml(project),
data.body,
data.body.convertToHtml(project),
data.headRefId,
data.headRefName,
data.viewerCanUpdate,
data.viewerCanReact,
data.viewerCanDeleteHeadRef && data.headRefId != null,
data.reactions.nodes
)
@@ -73,14 +99,29 @@ class GHPRDetailsTimelineViewModel internal constructor(private val project: Pro
requestFocus()
}
}
fun deleteMergedBranch() {
if (!canDeleteMergedBranch.value) return
val details = details.value.getOrNull() ?: return
val refId = details.headRefId ?: return
cs.launch {
runCatchingUser {
dataProvider.detailsData.deleteMergedBranch(refId)
}.onFailure { e ->
LOG.warn(e)
}
}
}
}
class GHPREditDescriptionViewModel internal constructor(project: Project,
parentCs: CoroutineScope,
private val detailsData: GHPRDetailsDataProvider,
initialText: String,
private val onDone: () -> Unit)
: CodeReviewSubmittableTextViewModelBase(project, parentCs, initialText), CodeReviewTextEditingViewModel {
class GHPREditDescriptionViewModel internal constructor(
project: Project,
parentCs: CoroutineScope,
private val detailsData: GHPRDetailsDataProvider,
initialText: String,
private val onDone: () -> Unit,
) : CodeReviewSubmittableTextViewModelBase(project, parentCs, initialText), CodeReviewTextEditingViewModel {
override fun save() {
submit {
detailsData.updateDetails(description = it)
@@ -13,11 +13,10 @@ import com.intellij.collaboration.ui.codereview.comment.submitActionIn
import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPanelFactory
import com.intellij.collaboration.ui.codereview.list.error.ErrorStatusPresenter
import com.intellij.collaboration.ui.codereview.timeline.comment.CommentTextFieldFactory
import com.intellij.collaboration.ui.util.bindContent
import com.intellij.collaboration.ui.util.bindTextHtmlIn
import com.intellij.collaboration.ui.util.bindTextIn
import com.intellij.collaboration.ui.util.bindVisibilityIn
import com.intellij.collaboration.ui.util.*
import com.intellij.collaboration.util.getOrNull
import com.intellij.icons.AllIcons
import com.intellij.ide.BrowserUtil
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
@@ -31,6 +30,7 @@ import com.intellij.ui.PopupHandler
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.components.panels.ListLayout
import com.intellij.ui.components.panels.Wrapper
import com.intellij.util.IconUtil
import com.intellij.util.asDisposable
import com.intellij.util.ui.JBFont
import com.intellij.util.ui.JBUI
@@ -38,6 +38,7 @@ import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.update.UiNotifyConnector
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import org.jetbrains.plugins.github.GithubIcons
import org.jetbrains.plugins.github.ai.GHPRAISummaryExtension
import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException
import org.jetbrains.plugins.github.i18n.GithubBundle
@@ -45,13 +46,16 @@ import org.jetbrains.plugins.github.pullrequest.ui.GHPRConnectedProjectViewModel
import org.jetbrains.plugins.github.pullrequest.ui.details.model.GHPRDetailsFull
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionsComponentFactory
import org.jetbrains.plugins.github.pullrequest.ui.emoji.GHReactionsPickerComponentFactory
import org.jetbrains.plugins.github.pullrequest.ui.timeline.GHPRTimelineEventComponentFactoryImpl.Companion.branchHTML
import org.jetbrains.plugins.github.ui.component.GHHtmlErrorPanel
import org.jetbrains.plugins.github.ui.util.addGithubHyperlinkListener
import java.awt.Font
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.event.ChangeEvent
import javax.swing.event.ChangeListener
import javax.swing.event.HyperlinkEvent
internal class GHPRFileEditorComponentFactory(
private val cs: CoroutineScope,
@@ -69,7 +73,15 @@ internal class GHPRFileEditorComponentFactory(
val description = createDescription(loadedDetails)
val itemComponentFactory = createItemComponentFactory()
val timeline = ComponentListPanelFactory.createVertical(cs, timelineVm.timelineItems, componentFactory = itemComponentFactory)
val timelineEvents = ComponentListPanelFactory.createVertical(cs, timelineVm.timelineItems, componentFactory = itemComponentFactory)
val mergedTimelineItem = Wrapper().apply {
bindContent(
debugName = "mergeActionChild",
dataFlow = combine(timelineVm.detailsVm.isMerged, timelineVm.loadingError, ::Pair).distinctUntilChanged()
) { (isMerged, loadingError) ->
if (isMerged && loadingError == null) createMergedTimelineItem(timelineVm.detailsVm) else null
}
}
val progressAndErrorPanel = JPanel(ListLayout.vertical(0, ListLayout.Alignment.CENTER)).apply {
isOpaque = false
@@ -112,9 +124,10 @@ internal class GHPRFileEditorComponentFactory(
})
add(description)
add(timeline)
add(timelineEvents)
add(progressAndErrorPanel)
add(mergedTimelineItem)
timelineVm.commentVm?.also {
val commentTextField = createCommentField(it).apply {
@@ -138,8 +151,7 @@ internal class GHPRFileEditorComponentFactory(
}
})
}
UiNotifyConnector.doWhenFirstShown(scrollPane)
{
UiNotifyConnector.doWhenFirstShown(scrollPane) {
timelineVm.requestMore()
}
@@ -226,6 +238,57 @@ internal class GHPRFileEditorComponentFactory(
return CodeReviewCommentTextFieldFactory.createIn(cs, vm, actions, icon)
}
private fun createMergedTimelineItem(detailsVm: GHPRDetailsTimelineViewModel): JComponent {
return CodeReviewChatItemUIUtil.build(
type = CodeReviewChatItemUIUtil.ComponentType.FULL,
iconProvider = { iconSize -> IconUtil.resizeSquared(GithubIcons.PullRequestMerged, iconSize) },
content = HorizontalListPanel(gap = 4).apply {
add(SimpleHtmlPane(addBrowserListener = false).apply {
addHyperlinkListener { e ->
if (e.eventType != HyperlinkEvent.EventType.ACTIVATED) return@addHyperlinkListener
if (e.description == DELETE_ACTION_NAME) {
detailsVm.deleteMergedBranch()
}
else if (e.description == RESTORE_ACTION_NAME) {
val url = detailsVm.details.value.getOrNull()?.url ?: return@addHyperlinkListener
BrowserUtil.browse(url)
}
}
bindTextHtml(
"mergedTimelineItem.body.text",
combine(detailsVm.canDeleteMergedBranch, detailsVm.headRefName) { canDeleteMergedBranch, headRefName ->
val branchHtml = headRefName?.let(::branchHTML) ?: "branch"
if (canDeleteMergedBranch) {
val deleteBranchHtml = GithubBundle.message("pullRequest.timeline.merged.body.delete")
val link = HtmlChunk.link(DELETE_ACTION_NAME, HtmlChunk.raw(deleteBranchHtml))
GithubBundle.message("pullRequest.timeline.merged.body.withDelete", link, branchHtml)
}
else {
val restoreText = GithubBundle.message("pullRequest.timeline.merged.body.restore")
val link = HtmlChunk.link(RESTORE_ACTION_NAME, HtmlChunk.raw(restoreText))
GithubBundle.message("pullRequest.timeline.merged.body.withoutDelete", branchHtml, link)
}
}
)
})
add(JLabel().apply {
bindIcon("mergedTimelineItem.body.icon", detailsVm.canDeleteMergedBranch.map { canDeleteMergedBranch ->
if (!canDeleteMergedBranch) AllIcons.Ide.External_link_arrow else null
})
})
}
) {
withHeader(JLabel(GithubBundle.message("pullRequest.timeline.merged.title")).apply {
font = font.deriveFont(Font.BOLD)
})
}
}
private fun createItemComponentFactory() = GHPRTimelineItemComponentFactory(project, timelineVm)
private val noDescriptionHtmlText by lazy {
@@ -235,4 +298,9 @@ internal class GHPRFileEditorComponentFactory(
.wrapWith("i")
.toString()
}
companion object {
private const val RESTORE_ACTION_NAME = "restore"
private const val DELETE_ACTION_NAME = "delete"
}
}
@@ -248,7 +248,7 @@ internal class GHPRTimelineEventComponentFactoryImpl(
}
companion object {
private fun branchHTML(name: @Nls String): HtmlChunk {
internal fun branchHTML(name: @Nls String): HtmlChunk {
val foreground = CurrentBranchComponent.TEXT_COLOR
val background = CurrentBranchComponent.getBranchPresentationBackground(UIUtil.getListBackground())
@@ -91,7 +91,7 @@ internal class GHPRTimelineViewModelImpl(
override val ghostUser: GHUser = securityService.ghostUser
override val currentUser: GHUser = securityService.currentUser
override val detailsVm = GHPRDetailsTimelineViewModel(project, parentCs, dataContext, dataProvider)
override val detailsVm = GHPRDetailsTimelineViewModel(project, cs, dataContext, dataProvider)
private val timelineLoader = dataProvider.acquireTimelineLoader(cs)
override val loadingErrorHandler: GHLoadingErrorHandler =
@@ -99,7 +99,7 @@ internal class GHPRTimelineViewModelImpl(
override val commentVm: GHPRNewCommentViewModel? =
if (securityService.currentUserHasPermissionLevel(GHRepositoryPermissionLevel.READ)) {
GHPRNewCommentViewModel(project, parentCs, commentsData)
GHPRNewCommentViewModel(project, cs, commentsData)
}
else null
@@ -32,7 +32,6 @@ import org.jetbrains.plugins.github.api.data.GHReactionContent
import org.junit.Ignore
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.*
@@ -526,6 +525,8 @@ private object TestCases {
GHGQLRequests.PullRequest.markFileAsViewed(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT),
GHGQLRequests.PullRequest.unmarkFileAsViewed(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT),
GHGQLRequests.Ref.delete(DUMMY_SERVER_PATH, DUMMY_TEXT),
GHGQLRequests.PullRequest.Timeline.items(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT, DUMMY_NUMBER, DUMMY_PAGINATION),
GHGQLRequests.PullRequest.Timeline.items(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_TEXT, DUMMY_NUMBER, null),
GHGQLRequests.PullRequest.Review.create(DUMMY_SERVER_PATH, DUMMY_TEXT, DUMMY_PR_EVENT, DUMMY_TEXT, DUMMY_TEXT, listOf()),
@@ -586,8 +587,8 @@ class GHGQLDeserializationAssumptionsTest {
)
@Test
fun `deserializing a missing value instead of a list is not fine`() {
assertThrows<Exception> { mapper.readValue("""{}""", ListHolder::class.java) }
fun `deserializing a missing value instead of a list is fine`() {
assertDoesNotThrow { mapper.readValue("""{}""", ListHolder::class.java) }
}
@Test