mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 20:54:50 +03:00
Add stack footer
This commit is contained in:
parent
008d44ca43
commit
baa2a0bf7c
@ -2,6 +2,9 @@
|
||||
import ContextMenu from '$lib/components/contextmenu/ContextMenu.svelte';
|
||||
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
|
||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||
import { getForgePrService } from '$lib/forge/interface/forgePrService';
|
||||
import { updatePrDescriptionTables } from '$lib/forge/shared/prFooter';
|
||||
import { User } from '$lib/stores/user';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { VirtualBranch } from '$lib/vbranches/types';
|
||||
import { getContext, getContextStore } from '@gitbutler/shared/context';
|
||||
@ -9,6 +12,7 @@
|
||||
import Modal from '@gitbutler/ui/Modal.svelte';
|
||||
import Toggle from '@gitbutler/ui/Toggle.svelte';
|
||||
import Tooltip from '@gitbutler/ui/Tooltip.svelte';
|
||||
import { isDefined } from '@gitbutler/ui/utils/typeguards';
|
||||
|
||||
interface Props {
|
||||
prUrl?: string;
|
||||
@ -26,6 +30,8 @@
|
||||
|
||||
const branchStore = getContextStore(VirtualBranch);
|
||||
const branchController = getContext(BranchController);
|
||||
const prService = getForgePrService();
|
||||
const user = getContextStore(User);
|
||||
|
||||
let deleteBranchModal: Modal;
|
||||
let allowRebasing = $state<boolean>();
|
||||
@ -37,6 +43,8 @@
|
||||
allowRebasing = branch.allowRebasing;
|
||||
});
|
||||
|
||||
const allPrIds = $derived(branch.series.map((series) => series.prNumber).filter(isDefined));
|
||||
|
||||
async function toggleAllowRebasing() {
|
||||
branchController.updateBranchAllowRebasing(branch.id, !allowRebasing);
|
||||
}
|
||||
@ -113,6 +121,22 @@
|
||||
}}
|
||||
/>
|
||||
</ContextMenuSection>
|
||||
{#if $user && $user.role?.includes('admin')}
|
||||
<!-- TODO: Remove after iterating on the pull request footer. -->
|
||||
<ContextMenuSection label="admin only">
|
||||
<ContextMenuItem
|
||||
label="Update PR footers"
|
||||
disabled={allPrIds.length === 0}
|
||||
onclick={() => {
|
||||
if ($prService && branch) {
|
||||
const allPrIds = branch.series.map((series) => series.prNumber).filter(isDefined);
|
||||
updatePrDescriptionTables($prService, allPrIds);
|
||||
}
|
||||
contextMenuEl?.close();
|
||||
}}
|
||||
/>
|
||||
</ContextMenuSection>
|
||||
{/if}
|
||||
</ContextMenu>
|
||||
|
||||
<Modal
|
||||
|
@ -99,6 +99,23 @@
|
||||
$cloudBranch.state === 'not-found'
|
||||
);
|
||||
|
||||
/**
|
||||
* We are starting to store pull request id's locally so if we find one that does not have
|
||||
* one locally stored then we set it once.
|
||||
*
|
||||
* TODO: Remove this after transition is complete.
|
||||
*/
|
||||
$effect(() => {
|
||||
if (
|
||||
$forge?.name === 'github' &&
|
||||
!currentSeries.prNumber &&
|
||||
listedPr?.number &&
|
||||
listedPr.number !== currentSeries.prNumber
|
||||
) {
|
||||
branchController.updateBranchPrNumber(branch.id, currentSeries.name, listedPr.number);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleReloadPR() {
|
||||
await Promise.allSettled([prMonitor?.refresh(), checksMonitor?.update()]);
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
const { label, children }: { label?: string; children: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<div class="context-menu-section">
|
||||
<slot />
|
||||
{#if label}
|
||||
<div class="label text-12">{label}</div>
|
||||
{/if}
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
@ -17,4 +23,10 @@
|
||||
border-top: 1px solid var(--clr-border-2);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 6px 8px;
|
||||
color: var(--clr-scale-ntrl-50);
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
@ -96,4 +96,15 @@ export class GitHubPrService implements ForgePrService {
|
||||
prMonitor(prNumber: number): GitHubPrMonitor {
|
||||
return new GitHubPrMonitor(this, prNumber);
|
||||
}
|
||||
|
||||
async update(prNumber: number, details: { description?: string; state?: 'open' | 'closed' }) {
|
||||
const { description, state } = details;
|
||||
await this.octokit.pulls.update({
|
||||
owner: this.repo.owner,
|
||||
repo: this.repo.name,
|
||||
pull_number: prNumber,
|
||||
body: description,
|
||||
state: state
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import type { Writable } from 'svelte/store';
|
||||
|
||||
export const [getForgePrService, createForgePrServiceStore] = buildContextStore<
|
||||
ForgePrService | undefined
|
||||
>('gitBranchService');
|
||||
>('forgePrService');
|
||||
|
||||
export interface ForgePrService {
|
||||
loading: Writable<boolean>;
|
||||
@ -20,4 +20,8 @@ export interface ForgePrService {
|
||||
merge(method: MergeMethod, prNumber: number): Promise<void>;
|
||||
reopen(prNumber: number): Promise<void>;
|
||||
prMonitor(prNumber: number): ForgePrMonitor;
|
||||
update(
|
||||
prNumber: number,
|
||||
details: { description?: string; state?: 'open' | 'closed' }
|
||||
): Promise<void>;
|
||||
}
|
||||
|
50
apps/desktop/src/lib/forge/shared/prFooter.ts
Normal file
50
apps/desktop/src/lib/forge/shared/prFooter.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import type { ForgePrService } from '../interface/forgePrService';
|
||||
|
||||
export const FOOTER_BOUNDARY_TOP = '<!-- GitButler Footer Boundary Top -->';
|
||||
export const FOOTER_BOUNDARY_BOTTOM = '<!-- GitButler Footer Boundary Bottom -->';
|
||||
|
||||
/**
|
||||
* Updates a pull request description with a table pointing to other pull
|
||||
* requests in the same stack.
|
||||
*/
|
||||
export async function updatePrDescriptionTables(prService: ForgePrService, prNumbers: number[]) {
|
||||
if (prService && prNumbers.length > 1) {
|
||||
const prs = await Promise.all(prNumbers.map(async (id) => await prService.get(id)));
|
||||
const updates = prs.map((pr) => ({
|
||||
prNumber: pr.number,
|
||||
description: updateBody(pr.body, pr.number, prNumbers)
|
||||
}));
|
||||
await Promise.all(
|
||||
updates.map(async ({ prNumber, description }) => {
|
||||
await prService.update(prNumber, { description });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces or inserts a new footer into an existing body of text.
|
||||
*/
|
||||
function updateBody(body: string | undefined, prNumber: number, allPrNumbers: number[]) {
|
||||
const head = (body?.split(FOOTER_BOUNDARY_TOP).at(0) || '').trim();
|
||||
const tail = (body?.split(FOOTER_BOUNDARY_BOTTOM).at(1) || '').trim();
|
||||
const footer = generateFooter(prNumber, allPrNumbers);
|
||||
const description = head + '\n\n' + footer + '\n\n' + tail;
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a footer for use in pull request descriptions when part of a stack.
|
||||
*/
|
||||
export function generateFooter(forPrNumber: number, allPrNumbers: number[]) {
|
||||
const stackIndex = allPrNumbers.findIndex((number) => number === forPrNumber);
|
||||
let footer = '';
|
||||
footer += FOOTER_BOUNDARY_TOP + '\n';
|
||||
footer += 'This is part of a stack made with GitButler:\n';
|
||||
allPrNumbers.forEach((prNumber, i) => {
|
||||
const current = i === stackIndex;
|
||||
footer += `- #${prNumber} ${current ? '👈 ' : ''}\n`;
|
||||
});
|
||||
footer += FOOTER_BOUNDARY_BOTTOM;
|
||||
return footer;
|
||||
}
|
@ -20,6 +20,8 @@
|
||||
import { mapErrorToToast } from '$lib/forge/github/errorMap';
|
||||
import { getForge } from '$lib/forge/interface/forge';
|
||||
import { getForgePrService } from '$lib/forge/interface/forgePrService';
|
||||
import { type DetailedPullRequest, type PullRequest } from '$lib/forge/interface/types';
|
||||
import { updatePrDescriptionTables as updatePrStackInfo } from '$lib/forge/shared/prFooter';
|
||||
import { showError, showToast } from '$lib/notifications/toasts';
|
||||
import { isFailure } from '$lib/result';
|
||||
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
||||
@ -38,8 +40,8 @@
|
||||
import Textarea from '@gitbutler/ui/Textarea.svelte';
|
||||
import Textbox from '@gitbutler/ui/Textbox.svelte';
|
||||
import ToggleButton from '@gitbutler/ui/ToggleButton.svelte';
|
||||
import { isDefined } from '@gitbutler/ui/utils/typeguards';
|
||||
import { tick } from 'svelte';
|
||||
import type { DetailedPullRequest, PullRequest } from '$lib/forge/interface/types';
|
||||
|
||||
interface BaseProps {
|
||||
type: 'display' | 'preview' | 'preview-series';
|
||||
@ -163,6 +165,9 @@
|
||||
error('Pull request service not available');
|
||||
return;
|
||||
}
|
||||
if (props.type !== 'preview-series') {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
try {
|
||||
@ -201,6 +206,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// All ids that existed prior to creating a new one (including archived).
|
||||
const priorIds = branch.series.map((series) => series.prNumber).filter(isDefined);
|
||||
|
||||
const pr = await $prService.createPr({
|
||||
title: params.title,
|
||||
body: params.body,
|
||||
@ -208,12 +216,17 @@
|
||||
baseBranchName,
|
||||
upstreamName: upstreamBranchName
|
||||
});
|
||||
if (props.type === 'preview-series') {
|
||||
await branchController.updateSeriesPrNumber(
|
||||
props.stackId,
|
||||
props.currentSeries.name,
|
||||
pr.number
|
||||
);
|
||||
|
||||
// Store the new pull request number with the branch data.
|
||||
await branchController.updateBranchPrNumber(
|
||||
props.stackId,
|
||||
props.currentSeries.name,
|
||||
pr.number
|
||||
);
|
||||
|
||||
// If we now have two or more pull requests we add a stack table to the description.
|
||||
if (priorIds.length > 0) {
|
||||
updatePrStackInfo($prService, priorIds.concat([pr.number]));
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { createForgeChecksMonitorStore } from '$lib/forge/interface/forgeChecksMonitor';
|
||||
import { getForgeListingService } from '$lib/forge/interface/forgeListingService';
|
||||
import { createForgePrMonitorStore } from '$lib/forge/interface/forgePrMonitor';
|
||||
import { createForgePrServiceStore } from '$lib/forge/interface/forgePrService';
|
||||
import { getForgePrService } from '$lib/forge/interface/forgePrService';
|
||||
import type { PatchSeries } from '$lib/vbranches/types';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
@ -17,8 +17,7 @@
|
||||
|
||||
// Setup PR Store and Monitor on a per-series basis
|
||||
const forge = getForge();
|
||||
const prService = createForgePrServiceStore(undefined);
|
||||
$effect(() => prService.set($forge?.prService()));
|
||||
const prService = getForgePrService();
|
||||
|
||||
// Pretty cumbersome way of getting the PR number, would be great if we can
|
||||
// make it more concise somehow.
|
||||
|
@ -165,7 +165,7 @@ export class BranchController {
|
||||
* @param headName The branch name to update.
|
||||
* @param prNumber New pull request number to be set for the branch.
|
||||
*/
|
||||
async updateSeriesPrNumber(stackId: string, headName: string, prNumber: number | undefined) {
|
||||
async updateBranchPrNumber(stackId: string, headName: string, prNumber: number | undefined) {
|
||||
try {
|
||||
await invoke<void>('update_series_pr_number', {
|
||||
projectId: this.projectId,
|
||||
|
@ -19,6 +19,7 @@
|
||||
import { octokitFromAccessToken } from '$lib/forge/github/octokit';
|
||||
import { createForgeStore } from '$lib/forge/interface/forge';
|
||||
import { createForgeListingServiceStore } from '$lib/forge/interface/forgeListingService';
|
||||
import { createForgePrServiceStore } from '$lib/forge/interface/forgePrService';
|
||||
import History from '$lib/history/History.svelte';
|
||||
import { HistoryService } from '$lib/history/history';
|
||||
import { SyncedSnapshotService } from '$lib/history/syncedSnapshotService';
|
||||
@ -104,6 +105,7 @@
|
||||
|
||||
const listServiceStore = createForgeListingServiceStore(undefined);
|
||||
const forgeStore = createForgeStore(undefined);
|
||||
const prService = createForgePrServiceStore(undefined);
|
||||
|
||||
$effect.pre(() => {
|
||||
const combinedBranchListingService = new CombinedBranchListingService(
|
||||
@ -143,6 +145,7 @@
|
||||
const ghListService = forge?.listService();
|
||||
listServiceStore.set(ghListService);
|
||||
forgeStore.set(forge);
|
||||
prService.set(forge ? forge.prService() : undefined);
|
||||
});
|
||||
|
||||
// Once on load and every time the project id changes
|
||||
|
Loading…
Reference in New Issue
Block a user