mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-30 20:09:50 +03:00
Merged origin/master into lane-style-updates
This commit is contained in:
commit
6999c94586
@ -6,16 +6,18 @@ import { map, startWith, switchMap } from 'rxjs/operators';
|
||||
import type { RemoteBranchService } from '$lib/stores/remoteBranches';
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import type { VirtualBranchService } from '$lib/vbranches/branchStoresCache';
|
||||
import type { Transaction } from '@sentry/sveltekit';
|
||||
import { capture } from '$lib/analytics/posthog';
|
||||
|
||||
export class BranchService {
|
||||
public branches$: Observable<CombinedBranch[]>;
|
||||
|
||||
constructor(
|
||||
vbranchService: VirtualBranchService,
|
||||
private vbranchService: VirtualBranchService,
|
||||
remoteBranchService: RemoteBranchService,
|
||||
githubService: GitHubService
|
||||
private githubService: GitHubService
|
||||
) {
|
||||
const vbranchesWithEmpty$ = vbranchService.branches$.pipe(startWith([]));
|
||||
const vbranchesWithEmpty$ = vbranchService.activeBranches$.pipe(startWith([]));
|
||||
const branchesWithEmpty$ = remoteBranchService.branches$.pipe(startWith([]));
|
||||
const prWithEmpty$ = githubService.prs$.pipe(startWith([]));
|
||||
|
||||
@ -34,6 +36,50 @@ export class BranchService {
|
||||
map((branches) => branches.filter((b) => !b.vbranch || b.vbranch.active))
|
||||
);
|
||||
}
|
||||
|
||||
async createPr(
|
||||
branch: Branch,
|
||||
baseBranch: string,
|
||||
sentryTxn: Transaction
|
||||
): Promise<PullRequest | undefined> {
|
||||
// Using this mutable variable while investigating why branch variable
|
||||
// does not seem to update reliably.
|
||||
// TODO: This needs to be fixed and removed.
|
||||
let newBranch: Branch | undefined;
|
||||
|
||||
// Push if local commits
|
||||
if (branch.commits.some((c) => !c.isRemote)) {
|
||||
const pushBranchSpan = sentryTxn.startChild({ op: 'branch_push' });
|
||||
newBranch = await this.vbranchService.pushBranch(branch.id, branch.requiresForce);
|
||||
pushBranchSpan.finish();
|
||||
} else {
|
||||
newBranch = branch;
|
||||
}
|
||||
|
||||
if (!newBranch) {
|
||||
const err = 'branch push failed';
|
||||
capture(err, { upstream: branch.upstreamName });
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!newBranch.upstreamName) {
|
||||
throw 'Cannot create PR without remote branch name';
|
||||
}
|
||||
|
||||
const createPrSpan = sentryTxn.startChild({ op: 'pr_api_create' });
|
||||
|
||||
try {
|
||||
return await this.githubService.createPullRequest(
|
||||
baseBranch,
|
||||
newBranch.name,
|
||||
newBranch.notes,
|
||||
newBranch.id,
|
||||
newBranch.upstreamName
|
||||
);
|
||||
} finally {
|
||||
createPrSpan.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeBranchesAndPrs(
|
||||
|
@ -30,7 +30,10 @@ export class VirtualBranchService {
|
||||
private reload$ = new BehaviorSubject<void>(undefined);
|
||||
private fresh$ = new Subject<void>();
|
||||
|
||||
constructor(projectId: string, gbBranchActive$: Observable<boolean>) {
|
||||
constructor(
|
||||
private projectId: string,
|
||||
gbBranchActive$: Observable<boolean>
|
||||
) {
|
||||
this.branches$ = this.reload$.pipe(
|
||||
switchMap(() => gbBranchActive$),
|
||||
switchMap((gbBranchActive) =>
|
||||
@ -98,6 +101,24 @@ export class VirtualBranchService {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async pushBranch(branchId: string, withForce: boolean): Promise<Branch | undefined> {
|
||||
try {
|
||||
await invoke<void>('push_virtual_branch', {
|
||||
projectId: this.projectId,
|
||||
branchId,
|
||||
withForce
|
||||
});
|
||||
await this.reload();
|
||||
return await this.getById(branchId);
|
||||
} catch (err: any) {
|
||||
if (err.code === 'errors.git.authentication') {
|
||||
toasts.error('Failed to authenticate. Did you setup GitButler ssh keys?');
|
||||
} else {
|
||||
toasts.error(`Failed to push branch: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function subscribeToVirtualBranches(projectId: string, callback: (branches: Branch[]) => void) {
|
||||
|
@ -5,6 +5,6 @@
|
||||
|
||||
$: projectId = $page.params.projectId;
|
||||
$: if (projectId) {
|
||||
goto(`/${projectId}/base`, { replaceState: true });
|
||||
goto(`/${projectId}/board`, { replaceState: true });
|
||||
}
|
||||
</script>
|
||||
|
@ -15,6 +15,7 @@
|
||||
$: base$ = baseBranchService.base$;
|
||||
$: user$ = data.user$;
|
||||
$: githubService = data.githubService;
|
||||
$: branchService = data.branchService;
|
||||
|
||||
$: project$ = data.project$;
|
||||
$: branches = vbranchService.branches$;
|
||||
@ -58,6 +59,7 @@
|
||||
<div class="scroll-contents" bind:this={contents}>
|
||||
<Board
|
||||
{branchController}
|
||||
{branchService}
|
||||
project={$project$}
|
||||
{cloud}
|
||||
base={$base$}
|
||||
|
@ -1,15 +1,18 @@
|
||||
<script lang="ts" async="true">
|
||||
import BranchLane from '../components/BranchLane.svelte';
|
||||
import NewBranchDropZone from './NewBranchDropZone.svelte';
|
||||
import type { User, getCloudApiClient } from '$lib/backend/cloud';
|
||||
import type { BaseBranch, Branch } from '$lib/vbranches/types';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { User, getCloudApiClient } from '$lib/backend/cloud';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import type { BranchService } from '$lib/branches/service';
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import { cloneWithRotation } from '$lib/utils/draggable';
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
import Icon from '$lib/icons/Icon.svelte';
|
||||
|
||||
import { cloneWithRotation } from '$lib/utils/draggable';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
|
||||
import NewBranchDropZone from './NewBranchDropZone.svelte';
|
||||
import BranchLane from '../components/BranchLane.svelte';
|
||||
import ImgThemed from '$lib/components/ImgThemed.svelte';
|
||||
import Icon from '$lib/icons/Icon.svelte';
|
||||
|
||||
export let project: Project;
|
||||
export let projectPath: string;
|
||||
@ -21,6 +24,7 @@
|
||||
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
export let branchService: BranchService;
|
||||
export let githubService: GitHubService;
|
||||
|
||||
export let user: User | undefined;
|
||||
@ -111,6 +115,7 @@
|
||||
{base}
|
||||
{cloud}
|
||||
{branchController}
|
||||
{branchService}
|
||||
branchCount={branches.filter((c) => c.active).length}
|
||||
{projectPath}
|
||||
{user}
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
import DropzoneOverlay from './DropzoneOverlay.svelte';
|
||||
import ScrollableContainer from '$lib/components/ScrollableContainer.svelte';
|
||||
import type { BranchService } from '$lib/branches/service';
|
||||
|
||||
export let branch: Branch;
|
||||
export let readonly = false;
|
||||
@ -37,6 +38,7 @@
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
export let branchService: BranchService;
|
||||
export let branchCount = 1;
|
||||
export let user: User | undefined;
|
||||
export let selectedFiles: Writable<File[]>;
|
||||
@ -256,6 +258,7 @@
|
||||
{project}
|
||||
{githubService}
|
||||
{branchController}
|
||||
{branchService}
|
||||
{branchCount}
|
||||
{readonly}
|
||||
/>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
import type { BranchService } from '$lib/branches/service';
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { BaseBranch, Branch } from '$lib/vbranches/types';
|
||||
@ -10,6 +11,7 @@
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let githubService: GitHubService;
|
||||
export let branchController: BranchController;
|
||||
export let branchService: BranchService;
|
||||
export let readonly: boolean;
|
||||
export let branchCount: number;
|
||||
</script>
|
||||
@ -20,6 +22,7 @@
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{branchService}
|
||||
{branchCount}
|
||||
{githubService}
|
||||
{readonly}
|
||||
@ -30,6 +33,7 @@
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{branchService}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="local"
|
||||
@ -39,6 +43,7 @@
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{branchService}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="remote"
|
||||
@ -48,6 +53,7 @@
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{branchService}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="integrated"
|
||||
|
@ -8,6 +8,7 @@
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
import type { BranchService } from '$lib/branches/service';
|
||||
|
||||
export let branch: Branch;
|
||||
export let readonly = false;
|
||||
@ -15,6 +16,7 @@
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
export let branchService: BranchService;
|
||||
export let branchCount = 1;
|
||||
export let user: User | undefined;
|
||||
export let projectPath: string;
|
||||
@ -43,6 +45,7 @@
|
||||
{base}
|
||||
{cloud}
|
||||
{branchController}
|
||||
{branchService}
|
||||
{selectedOwnership}
|
||||
bind:commitBoxOpen
|
||||
{branchCount}
|
||||
|
@ -6,6 +6,7 @@
|
||||
import CommitListHeader from './CommitListHeader.svelte';
|
||||
import CommitListFooter from './CommitListFooter.svelte';
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
import type { BranchService } from '$lib/branches/service';
|
||||
|
||||
export let branch: Branch;
|
||||
export let base: BaseBranch | undefined | null;
|
||||
@ -13,6 +14,7 @@
|
||||
export let branchController: BranchController;
|
||||
export let type: CommitStatus;
|
||||
export let githubService: GitHubService;
|
||||
export let branchService: BranchService;
|
||||
export let readonly: boolean;
|
||||
export let branchCount: number = 0;
|
||||
|
||||
@ -58,6 +60,7 @@
|
||||
</div>{/if}
|
||||
<CommitListFooter
|
||||
{branchController}
|
||||
{branchService}
|
||||
{branch}
|
||||
{githubService}
|
||||
{type}
|
||||
|
@ -7,12 +7,13 @@
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { startTransaction } from '@sentry/sveltekit';
|
||||
import { capture } from '$lib/analytics/posthog';
|
||||
import type { BranchService } from '$lib/branches/service';
|
||||
|
||||
export let branch: Branch;
|
||||
export let type: CommitStatus;
|
||||
export let readonly: boolean;
|
||||
export let branchController: BranchController;
|
||||
export let branchService: BranchService;
|
||||
export let githubService: GitHubService;
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let projectId: string;
|
||||
@ -31,9 +32,6 @@
|
||||
}
|
||||
|
||||
async function createPr(): Promise<PullRequest | undefined> {
|
||||
isPushing = true;
|
||||
const txn = startTransaction({ name: 'pull_request_create' });
|
||||
|
||||
if (!githubService.isEnabled()) {
|
||||
toast.error('Cannot create PR without GitHub credentials');
|
||||
return;
|
||||
@ -44,43 +42,19 @@
|
||||
return;
|
||||
}
|
||||
|
||||
let newBranch: Branch | undefined;
|
||||
// Push if local commits
|
||||
if (branch.commits.some((c) => !c.isRemote)) {
|
||||
const pushBranchSpan = txn.startChild({ op: 'branch_push' });
|
||||
newBranch = await branchController.pushBranch(branch.id, branch.requiresForce);
|
||||
pushBranchSpan.finish();
|
||||
} else {
|
||||
newBranch = branch;
|
||||
// Sentry transaction for measuring pr creation latency
|
||||
const sentryTxn = startTransaction({ name: 'pull_request_create' });
|
||||
|
||||
isPushing = true;
|
||||
try {
|
||||
return await branchService.createPr(branch, base.shortName, sentryTxn);
|
||||
} catch (err) {
|
||||
toast.error(err as string);
|
||||
console.error(err);
|
||||
} finally {
|
||||
sentryTxn.finish();
|
||||
isPushing = false;
|
||||
}
|
||||
|
||||
if (newBranch?.upstreamName != branch.upstreamName) {
|
||||
capture('branch push mismatch', { new: newBranch?.upstreamName, old: branch.upstreamName });
|
||||
}
|
||||
|
||||
if (!newBranch) {
|
||||
console.error('Branch push failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newBranch.upstreamName) {
|
||||
toast.error('Cannot create PR without remote branch name');
|
||||
return;
|
||||
}
|
||||
|
||||
const createPrSpan = txn.startChild({ op: 'pr_api_create' });
|
||||
const resp = await githubService.createPullRequest(
|
||||
base.shortName,
|
||||
newBranch.name,
|
||||
newBranch.notes,
|
||||
newBranch.id,
|
||||
newBranch.upstreamName
|
||||
);
|
||||
|
||||
createPrSpan.finish();
|
||||
txn.finish();
|
||||
isPushing = false;
|
||||
return resp;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,132 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { page } from '$app/stores';
|
||||
import BranchLane from '../../components/BranchLane.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import type { Branch } from '$lib/vbranches/types';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let applyConflictedModal: Modal;
|
||||
let deleteBranchModal: Modal;
|
||||
|
||||
$: projectId = data.projectId;
|
||||
$: user$ = data.user$;
|
||||
$: cloud = data.cloud;
|
||||
$: project$ = data.project$;
|
||||
|
||||
$: branchController = data.branchController;
|
||||
$: vbranchService = data.vbranchService;
|
||||
$: baseBranchService = data.baseBranchService;
|
||||
$: baseBranch$ = baseBranchService.base$;
|
||||
|
||||
$: branches$ = vbranchService.branches$;
|
||||
$: error$ = vbranchService.branchesError$;
|
||||
$: branch = $branches$?.find((b) => b.id == $page.params.branchId);
|
||||
$: githubService = data.githubService;
|
||||
|
||||
function applyBranch(branch: Branch) {
|
||||
if (!branch.isMergeable) {
|
||||
applyConflictedModal.show(branch);
|
||||
} else {
|
||||
branchController.applyBranch(branch.id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col p-3">
|
||||
{#if $error$}
|
||||
<p>Error...</p>
|
||||
{:else if !$branches$}
|
||||
<p>Loading...</p>
|
||||
{:else if branch}
|
||||
<div class="flex items-center justify-between pb-2">
|
||||
<Button
|
||||
kind="outlined"
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
branch && applyBranch(branch);
|
||||
goto(`/${projectId}/board`);
|
||||
}}
|
||||
>
|
||||
<span class="purple"> Apply </span>
|
||||
</Button>
|
||||
<div class="flex items-center">
|
||||
{#await branch.isMergeable then isMergeable}
|
||||
{#if !isMergeable}
|
||||
<Tooltip
|
||||
timeoutMilliseconds={100}
|
||||
label="Applying this branch will add merge conflict markers that you will have to resolve"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-default select-none rounded bg-yellow-300 px-2 py-0.5 dark:bg-yellow-800"
|
||||
>
|
||||
Conflicts with Applied Branches
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{/await}
|
||||
<IconButton
|
||||
icon="cross"
|
||||
title="delete branch"
|
||||
on:click={() => deleteBranchModal.show(branch)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-full">
|
||||
<BranchLane
|
||||
{branch}
|
||||
{branchController}
|
||||
base={$baseBranch$}
|
||||
{cloud}
|
||||
project={$project$}
|
||||
readonly={true}
|
||||
user={$user$}
|
||||
projectPath={$project$.path}
|
||||
{githubService}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Branch no longer exists</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Modal width="small" title="Merge conflicts" bind:this={applyConflictedModal}>
|
||||
<p>Applying this branch will introduce merge conflicts.</p>
|
||||
<svelte:fragment slot="controls" let:item let:close>
|
||||
<Button kind="outlined" color="neutral" on:click={close}>Cancel</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
branchController.applyBranch(item.id);
|
||||
close();
|
||||
goto(`/${projectId}/board`);
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
<Modal width="small" title="Delete branch" bind:this={deleteBranchModal} let:item>
|
||||
<div>
|
||||
Deleting <code>{item.name}</code> cannot be undone.
|
||||
</div>
|
||||
<svelte:fragment slot="controls" let:close let:item>
|
||||
<Button kind="outlined" color="neutral" on:click={close}>Cancel</Button>
|
||||
<Button
|
||||
color="error"
|
||||
on:click={() => {
|
||||
branchController.deleteBranch(item.id);
|
||||
close();
|
||||
goto(`/${projectId}/board`);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
Loading…
Reference in New Issue
Block a user