diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index bd4e624430..772fc387b9 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -95,6 +95,11 @@ declare module 'vscode' { */ description?: string | MarkdownString; + /** + * An optional badge that provides additional context about the chat session. + */ + badge?: string | MarkdownString; + /** * An optional status indicating the current state of the session. */ diff --git a/src/common/timelineEvent.ts b/src/common/timelineEvent.ts index 8ef0c07ee0..7fa1cf656e 100644 --- a/src/common/timelineEvent.ts +++ b/src/common/timelineEvent.ts @@ -22,6 +22,7 @@ export enum EventType { CrossReferenced, Closed, Reopened, + BaseRefChanged, CopilotStarted, CopilotFinished, CopilotFinishedError, @@ -156,6 +157,15 @@ export interface ReopenedEvent { createdAt: string; } +export interface BaseRefChangedEvent { + id: string; + event: EventType.BaseRefChanged; + actor: IActor; + createdAt: string; + currentRefName: string; + previousRefName: string; +} + export interface SessionPullInfo { id: number; host: string; @@ -192,4 +202,4 @@ export interface CopilotFinishedErrorEvent { sessionLink: SessionLinkInfo; } -export type TimelineEvent = CommitEvent | ReviewEvent | CommentEvent | NewCommitsSinceReviewEvent | MergedEvent | AssignEvent | UnassignEvent | HeadRefDeleteEvent | CrossReferencedEvent | ClosedEvent | ReopenedEvent | CopilotStartedEvent | CopilotFinishedEvent | CopilotFinishedErrorEvent; +export type TimelineEvent = CommitEvent | ReviewEvent | CommentEvent | NewCommitsSinceReviewEvent | MergedEvent | AssignEvent | UnassignEvent | HeadRefDeleteEvent | CrossReferencedEvent | ClosedEvent | ReopenedEvent | BaseRefChangedEvent | CopilotStartedEvent | CopilotFinishedEvent | CopilotFinishedErrorEvent; diff --git a/src/github/createPRViewProvider.ts b/src/github/createPRViewProvider.ts index 40763443bb..60b1bc7ace 100644 --- a/src/github/createPRViewProvider.ts +++ b/src/github/createPRViewProvider.ts @@ -15,12 +15,12 @@ import { IAccount, ILabel, IMilestone, IProject, isITeam, ITeam, MergeMethod, Re import { BaseBranchMetadata, PullRequestGitHelper } from './pullRequestGitHelper'; import { PullRequestModel } from './pullRequestModel'; import { getDefaultMergeMethod } from './pullRequestOverview'; -import { getAssigneesQuickPickItems, getLabelOptions, getMilestoneFromQuickPick, getProjectFromQuickPick, reviewersQuickPick } from './quickPicks'; +import { branchPicks, getAssigneesQuickPickItems, getLabelOptions, getMilestoneFromQuickPick, getProjectFromQuickPick, reviewersQuickPick } from './quickPicks'; import { getIssueNumberLabelFromParsed, ISSUE_EXPRESSION, ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput, variableSubstitution } from './utils'; import { DisplayLabel, PreReviewState } from './views'; import { RemoteInfo } from '../../common/types'; import { ChooseBaseRemoteAndBranchResult, ChooseCompareRemoteAndBranchResult, ChooseRemoteAndBranchArgs, CreateParamsNew, CreatePullRequestNew, TitleAndDescriptionArgs } from '../../common/views'; -import type { Branch, Ref } from '../api/api'; +import type { Branch } from '../api/api'; import { GitHubServerType } from '../common/authentication'; import { emojify, ensureEmojis } from '../common/emoji'; import { commands, contexts } from '../common/executeCommands'; @@ -812,40 +812,6 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv }); } - private async branchPicks(githubRepository: GitHubRepository, changeRepoMessage: string, isBase: boolean): Promise<(vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[]> { - let branches: (string | Ref)[]; - if (isBase) { - // For the base, we only want to show branches from GitHub. - branches = await githubRepository.listBranches(githubRepository.remote.owner, githubRepository.remote.repositoryName); - } else { - // For the compare, we only want to show local branches. - branches = (await this._folderRepositoryManager.repository.getBranches({ remote: false })).filter(branch => branch.name); - } - // TODO: @alexr00 - Add sorting so that the most likely to be used branch (ex main or release if base) is at the top of the list. - const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = branches.map(branch => { - const branchName = typeof branch === 'string' ? branch : branch.name!; - const pick: (vscode.QuickPickItem & { remote: RemoteInfo, branch: string }) = { - iconPath: new vscode.ThemeIcon('git-branch'), - label: branchName, - remote: { - owner: githubRepository.remote.owner, - repositoryName: githubRepository.remote.repositoryName - }, - branch: branchName - }; - return pick; - }); - branchPicks.unshift({ - kind: vscode.QuickPickItemKind.Separator, - label: `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}` - }); - branchPicks.unshift({ - iconPath: new vscode.ThemeIcon('repo'), - label: changeRepoMessage - }); - return branchPicks; - } - private async processRemoteAndBranchResult(githubRepository: GitHubRepository, result: { remote: RemoteInfo, branch: string }, isBase: boolean) { const [defaultBranch, viewerPermission] = await Promise.all([githubRepository.getDefaultBranch(), githubRepository.getViewerPermission()]); @@ -922,7 +888,7 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv quickPick.placeholder = githubRepository ? branchPlaceholder : remotePlaceholder; quickPick.show(); quickPick.busy = true; - quickPick.items = githubRepository ? await this.branchPicks(githubRepository, chooseDifferentRemote, isBase) : await this.remotePicks(isBase); + quickPick.items = githubRepository ? await branchPicks(githubRepository, this._folderRepositoryManager, chooseDifferentRemote, isBase) : await this.remotePicks(isBase); const activeItem = message.args.currentBranch ? quickPick.items.find(item => item.branch === message.args.currentBranch) : undefined; quickPick.activeItems = activeItem ? [activeItem] : []; quickPick.busy = false; @@ -941,7 +907,7 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv const selectedRemote = selectedPick as vscode.QuickPickItem & { remote: RemoteInfo }; quickPick.busy = true; githubRepository = this._folderRepositoryManager.findRepo(repo => repo.remote.owner === selectedRemote.remote.owner && repo.remote.repositoryName === selectedRemote.remote.repositoryName)!; - quickPick.items = await this.branchPicks(githubRepository, chooseDifferentRemote, isBase); + quickPick.items = await branchPicks(githubRepository, this._folderRepositoryManager, chooseDifferentRemote, isBase); quickPick.placeholder = branchPlaceholder; quickPick.busy = false; } else if (selectedPick.branch && selectedPick.remote) { diff --git a/src/github/graphql.ts b/src/github/graphql.ts index 68f41b1ded..00838018f7 100644 --- a/src/github/graphql.ts +++ b/src/github/graphql.ts @@ -68,6 +68,15 @@ export interface ReopenedEvent { createdAt: string; } +export interface BaseRefChangedEvent { + __typename: string; + id: string; + actor: Actor; + createdAt: string; + currentRefName: string; + previousRefName: string; +} + export interface AbbreviatedIssueComment { author: Account; body: string; @@ -265,7 +274,7 @@ export interface TimelineEventsResponse { repository: { pullRequest: { timelineItems: { - nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent | null)[]; + nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent | BaseRefChangedEvent | null)[]; }; }; } | null; @@ -1051,7 +1060,7 @@ export interface MergePullRequestResponse { mergePullRequest: { pullRequest: PullRequest & { timelineItems: { - nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent)[] + nodes: (MergedEvent | Review | IssueComment | Commit | AssignedEvent | HeadRefDeletedEvent | BaseRefChangedEvent)[] } }; } diff --git a/src/github/issueModel.ts b/src/github/issueModel.ts index d64ab70a19..23750c43f4 100644 --- a/src/github/issueModel.ts +++ b/src/github/issueModel.ts @@ -41,6 +41,7 @@ export interface IssueChangeEvent { draft?: true; reviewers?: true; + base?: true; } export class IssueModel extends Disposable { diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index 4cb54d86ed..3a9c7a1021 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -41,6 +41,7 @@ import { SubmitReviewResponse, TimelineEventsResponse, UnresolveReviewThreadResponse, + UpdateIssueResponse, } from './graphql'; import { AccountType, @@ -1199,6 +1200,46 @@ export class PullRequestModel extends IssueModel implements IPullRe return true; } + /** + * Update the base branch of the pull request. + * @param newBaseBranch The new base branch name + */ + async updateBaseBranch(newBaseBranch: string): Promise { + Logger.debug(`Updating base branch to ${newBaseBranch} - enter`, PullRequestModel.ID); + try { + const { mutate, schema } = await this.githubRepository.ensure(); + + const { data } = await mutate({ + mutation: schema.UpdatePullRequest, + variables: { + input: { + pullRequestId: this.graphNodeId, + baseRefName: newBaseBranch, + }, + }, + }); + + if (data?.updateIssue?.issue) { + // Update the local base branch reference by creating a new GitHubRef instance + const cloneUrl = this.base.repositoryCloneUrl.toString() || ''; + this.base = new GitHubRef( + newBaseBranch, + `${this.base.owner}:${newBaseBranch}`, + this.base.sha, + cloneUrl, + this.base.owner, + this.base.name, + this.base.isInOrganization + ); + this._onDidChange.fire({ base: true }); + } + Logger.debug(`Updating base branch to ${newBaseBranch} - done`, PullRequestModel.ID); + } catch (e) { + Logger.error(`Updating base branch to ${newBaseBranch} failed: ${e}`, PullRequestModel.ID); + throw e; + } + } + /** * Get existing requests to review. */ diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 50aed1e41f..ce9df400b2 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -23,9 +23,9 @@ import { import { IssueOverviewPanel } from './issueOverview'; import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel'; import { PullRequestReviewCommon, ReviewContext } from './pullRequestReviewCommon'; -import { pickEmail, reviewersQuickPick } from './quickPicks'; +import { branchPicks, pickEmail, reviewersQuickPick } from './quickPicks'; import { parseReviewers } from './utils'; -import { CancelCodingAgentReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType } from './views'; +import { CancelCodingAgentReply, ChangeBaseReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReviewType } from './views'; import { IComment } from '../common/comment'; import { COPILOT_SWE_AGENT, copilotEventToStatus, CopilotPRStatus, mostRecentCopilotEvent } from '../common/copilot'; import { commands, contexts } from '../common/executeCommands'; @@ -425,6 +425,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel): Promise { + const quickPick = vscode.window.createQuickPick(); + + try { + quickPick.busy = true; + quickPick.canSelectMany = false; + quickPick.placeholder = vscode.l10n.t('Select a new base branch'); + quickPick.show(); + + quickPick.items = await branchPicks(this._item.githubRepository, this._folderRepositoryManager, undefined, true); + + quickPick.busy = false; + const acceptPromise = asPromise(quickPick.onDidAccept).then(() => { + return quickPick.selectedItems[0]?.branch; + }); + const hidePromise = asPromise(quickPick.onDidHide); + const selectedBranch = await Promise.race([acceptPromise, hidePromise]); + quickPick.busy = true; + quickPick.enabled = false; + + if (selectedBranch) { + try { + await this._item.updateBaseBranch(selectedBranch); + const events = await this._getTimeline(); + const reply: ChangeBaseReply = { + base: selectedBranch, + events + }; + await this._replyMessage(message, reply); + } catch (e) { + Logger.error(formatError(e), PullRequestOverviewPanel.ID); + vscode.window.showErrorMessage(vscode.l10n.t('Changing base branch failed. {0}', formatError(e))); + this._throwError(message, `${formatError(e)}`); + } + } + } catch (e) { + Logger.error(formatError(e), PullRequestOverviewPanel.ID); + vscode.window.showErrorMessage(formatError(e)); + } finally { + quickPick.hide(); + quickPick.dispose(); + } + } + override dispose() { super.dispose(); disposeAll(this._prListeners); diff --git a/src/github/queriesShared.gql b/src/github/queriesShared.gql index 473841f0a1..17165ab3c5 100644 --- a/src/github/queriesShared.gql +++ b/src/github/queriesShared.gql @@ -210,6 +210,17 @@ fragment ReopenedEvent on ReopenedEvent { createdAt } +fragment BaseRefChangedEvent on BaseRefChangedEvent { + id + actor { + ...Node + ...Actor + } + createdAt + currentRefName + previousRefName +} + fragment Review on PullRequestReview { id databaseId @@ -312,6 +323,7 @@ query TimelineEvents($owner: String!, $name: String!, $number: Int!, $last: Int ...CrossReferencedEvent ...ClosedEvent ...ReopenedEvent + ...BaseRefChangedEvent } } } @@ -1230,6 +1242,7 @@ mutation MergePullRequest($input: MergePullRequestInput!, $last: Int = 150) { ...CrossReferencedEvent ...ClosedEvent ...ReopenedEvent + ...BaseRefChangedEvent } } } diff --git a/src/github/quickPicks.ts b/src/github/quickPicks.ts index 78e723db45..f38de7852f 100644 --- a/src/github/quickPicks.ts +++ b/src/github/quickPicks.ts @@ -11,6 +11,8 @@ import { GitHubRepository, TeamReviewerRefreshKind } from './githubRepository'; import { AccountType, IAccount, ILabel, IMilestone, IProject, isISuggestedReviewer, isITeam, ISuggestedReviewer, ITeam, reviewerId, ReviewState } from './interface'; import { IssueModel } from './issueModel'; import { DisplayLabel } from './views'; +import { RemoteInfo } from '../../common/types'; +import { Ref } from '../api/api'; import { COPILOT_ACCOUNTS } from '../common/comment'; import { COPILOT_REVIEWER, COPILOT_REVIEWER_ID, COPILOT_SWE_AGENT } from '../common/copilot'; import { emojify, ensureEmojis } from '../common/emoji'; @@ -479,4 +481,40 @@ export async function pickEmail(githubRepository: GitHubRepository, current: str const result = await vscode.window.showQuickPick(getEmails(), { canPickMany: false, title: vscode.l10n.t('Choose an email') }); return result ? result.label : undefined; +} + +export async function branchPicks(githubRepository: GitHubRepository, folderRepoManager: FolderRepositoryManager, changeRepoMessage: string | undefined, isBase: boolean): Promise<(vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[]> { + let branches: (string | Ref)[]; + if (isBase) { + // For the base, we only want to show branches from GitHub. + branches = await githubRepository.listBranches(githubRepository.remote.owner, githubRepository.remote.repositoryName); + } else { + // For the compare, we only want to show local branches. + branches = (await folderRepoManager.repository.getBranches({ remote: false })).filter(branch => branch.name); + } + // TODO: @alexr00 - Add sorting so that the most likely to be used branch (ex main or release if base) is at the top of the list. + const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = branches.map(branch => { + const branchName = typeof branch === 'string' ? branch : branch.name!; + const pick: (vscode.QuickPickItem & { remote: RemoteInfo, branch: string }) = { + iconPath: new vscode.ThemeIcon('git-branch'), + label: branchName, + remote: { + owner: githubRepository.remote.owner, + repositoryName: githubRepository.remote.repositoryName + }, + branch: branchName + }; + return pick; + }); + branchPicks.unshift({ + kind: vscode.QuickPickItemKind.Separator, + label: `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}` + }); + if (changeRepoMessage) { + branchPicks.unshift({ + iconPath: new vscode.ThemeIcon('repo'), + label: changeRepoMessage + }); + } + return branchPicks; } \ No newline at end of file diff --git a/src/github/utils.ts b/src/github/utils.ts index 559bbc0266..14c2a9c8e4 100644 --- a/src/github/utils.ts +++ b/src/github/utils.ts @@ -490,6 +490,8 @@ export function convertGraphQLEventType(text: string) { return Common.EventType.Closed; case 'ReopenedEvent': return Common.EventType.Reopened; + case 'BaseRefChangedEvent': + return Common.EventType.BaseRefChanged; default: return Common.EventType.Other; } @@ -1120,6 +1122,7 @@ export async function parseCombinedTimelineEvents( | GraphQL.AssignedEvent | GraphQL.HeadRefDeletedEvent | GraphQL.CrossReferencedEvent + | GraphQL.BaseRefChangedEvent | null )[], restEvents: Common.TimelineEvent[], @@ -1294,6 +1297,18 @@ export async function parseCombinedTimelineEvents( createdAt: reopenedEv.createdAt, }); break; + case Common.EventType.BaseRefChanged: + const baseRefChangedEv = event as GraphQL.BaseRefChangedEvent; + + addTimelineEvent({ + id: baseRefChangedEv.id, + event: type, + actor: parseAccount(baseRefChangedEv.actor, githubRepository), + createdAt: baseRefChangedEv.createdAt, + currentRefName: baseRefChangedEv.currentRefName, + previousRefName: baseRefChangedEv.previousRefName, + }); + break; default: break; } diff --git a/src/github/views.ts b/src/github/views.ts index a2866cb004..98df3e516d 100644 --- a/src/github/views.ts +++ b/src/github/views.ts @@ -173,4 +173,9 @@ export interface OverviewContext { export interface CodingAgentContext extends SessionLinkInfo { 'preventDefaultContextMenuItems': true; [key: string]: boolean | string | number | undefined; +} + +export interface ChangeBaseReply { + base: string; + events: TimelineEvent[]; } \ No newline at end of file diff --git a/src/test/github/pullRequestModel.test.ts b/src/test/github/pullRequestModel.test.ts index 5497cde4ab..f7fa80af71 100644 --- a/src/test/github/pullRequestModel.test.ts +++ b/src/test/github/pullRequestModel.test.ts @@ -148,4 +148,14 @@ describe('PullRequestModel', function () { assert.strictEqual(onDidChangeReviewThreads.getCall(0).args[0]['removed'].length, 0); }); }); + + describe('updateBaseBranch', function () { + it('should have updateBaseBranch method', function () { + const pr = new PullRequestBuilder().build(); + const model = new PullRequestModel(credentials, telemetry, repo, remote, convertRESTPullRequestToRawPullRequest(pr, repo)); + + // Verify the method exists + assert.strictEqual(typeof model.updateBaseBranch, 'function'); + }); + }); }); diff --git a/webviews/common/context.tsx b/webviews/common/context.tsx index d5aa38643a..c9d9a32496 100644 --- a/webviews/common/context.tsx +++ b/webviews/common/context.tsx @@ -10,7 +10,7 @@ import { CloseResult, OpenCommitChangesArgs } from '../../common/views'; import { IComment } from '../../src/common/comment'; import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../../src/common/timelineEvent'; import { IProjectItem, MergeMethod, ReadyForReview } from '../../src/github/interface'; -import { CancelCodingAgentReply, ChangeAssigneesReply, DeleteReviewResult, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, ReadyForReviewReply, SubmitReviewReply } from '../../src/github/views'; +import { CancelCodingAgentReply, ChangeAssigneesReply, ChangeBaseReply, DeleteReviewResult, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, ReadyForReviewReply, SubmitReviewReply } from '../../src/github/views'; export class PRContext { constructor( @@ -92,6 +92,12 @@ export class PRContext { public readyForReviewAndMerge = (args: { mergeMethod: MergeMethod }): Promise => this.postMessage({ command: 'pr.readyForReviewAndMerge', args }); public addReviewers = () => this.postMessage({ command: 'pr.change-reviewers' }); + public changeBaseBranch = async () => { + const result: ChangeBaseReply = await this.postMessage({ command: 'pr.change-base-branch' }); + if (result?.base) { + this.updatePR({ base: result.base, events: result.events }); + } + }; public changeProjects = (): Promise => this.postMessage({ command: 'pr.change-projects' }); public removeProject = (project: IProjectItem) => this.postMessage({ command: 'pr.remove-project', args: project }); public addMilestone = () => this.postMessage({ command: 'pr.add-milestone' }); diff --git a/webviews/components/header.tsx b/webviews/components/header.tsx index 9418515e19..80acf14b23 100644 --- a/webviews/components/header.tsx +++ b/webviews/components/header.tsx @@ -52,7 +52,7 @@ export function Header({ owner={owner} repo={repo} /> - +
{!isIssue ? (<> {getActionText(state)} into{' '} - {base} from {head} + {canEdit && state === GithubItemStateEnum.Open ? ( + + ) : ( + {base} + )} + {' '}from {head} ) : null}
diff --git a/webviews/components/timeline.tsx b/webviews/components/timeline.tsx index b3b1cd24a0..3480bca712 100644 --- a/webviews/components/timeline.tsx +++ b/webviews/components/timeline.tsx @@ -13,6 +13,7 @@ import { AuthorLink, Avatar } from './user'; import { IComment } from '../../src/common/comment'; import { AssignEvent, + BaseRefChangedEvent, ClosedEvent, CommentEvent, CommitEvent, @@ -89,6 +90,8 @@ export const Timeline = ({ events, isIssue }: { events: TimelineEvent[], isIssue return ; case EventType.Reopened: return ; + case EventType.BaseRefChanged: + return ; case EventType.NewCommitsSinceReview: return ; case EventType.CopilotStarted: @@ -516,6 +519,24 @@ const ReopenedEventView = ({ event, isIssue }: { event: ReopenedEvent, isIssue: ); }; +const BaseRefChangedEventView = ({ event }: { event: BaseRefChangedEvent }) => { + const { actor, createdAt, currentRefName, previousRefName } = event; + return ( +
+
+
+ +
+ +
+ changed the base branch from {previousRefName} to {currentRefName} +
+
+ +
+ ); +}; + const CopilotStartedEventView = (event: CopilotStartedEvent) => { const { createdAt, onBehalfOf, sessionLink } = event; const { openSessionLog } = useContext(PullRequestContext); diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css index 4716e0152e..951d135afb 100644 --- a/webviews/editorWebview/index.css +++ b/webviews/editorWebview/index.css @@ -541,6 +541,35 @@ body button .icon { border-radius: 4px; } +.branch-tag-button { + background-color: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + border: 1px solid transparent; + border-radius: 4px; + cursor: pointer; + padding: 0; + margin-top: 3px; + font-family: inherit; + font-size: inherit; +} + +.branch-tag-button:hover:enabled { + background-color: var(--vscode-button-secondaryHoverBackground); +} + +.branch-tag-button:active { + background-color: var(--vscode-button-secondaryHoverBackground); +} + +.branch-tag-button .branch-tag { + background: transparent; + margin-top: 0; +} + +.merge-branches .icon-button { + margin-top: 4px; +} + .subtitle .created-at { margin-left: auto; white-space: nowrap;