diff --git a/app/src/lib/components/Avatar.svelte b/app/src/lib/components/Avatar.svelte
new file mode 100644
index 000000000..9943e469b
--- /dev/null
+++ b/app/src/lib/components/Avatar.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
diff --git a/app/src/lib/components/BaseBranch.svelte b/app/src/lib/components/BaseBranch.svelte
index eb6e3fb70..21cdfb883 100644
--- a/app/src/lib/components/BaseBranch.svelte
+++ b/app/src/lib/components/BaseBranch.svelte
@@ -48,7 +48,12 @@
{#each base.upstreamCommits as commit}
-
+
{/each}
@@ -62,7 +67,7 @@
Local
{#each base.recentCommits as commit}
-
+
{/each}
diff --git a/app/src/lib/components/BranchCard.svelte b/app/src/lib/components/BranchCard.svelte
index d041f9b79..df9b456e7 100644
--- a/app/src/lib/components/BranchCard.svelte
+++ b/app/src/lib/components/BranchCard.svelte
@@ -1,8 +1,9 @@
-
-{#if $unknownCommits && $unknownCommits.length > 0}
-
-{/if}
-
-
-
diff --git a/app/src/lib/components/BranchFooter.svelte b/app/src/lib/components/BranchFooter.svelte
new file mode 100644
index 000000000..a1cabae7e
--- /dev/null
+++ b/app/src/lib/components/BranchFooter.svelte
@@ -0,0 +1,65 @@
+
+
+{#if !isUnapplied}
+
+ {#if !isPushed}
+ {#if $prompt}
+
+ {/if}
+
{
+ try {
+ if (e.detail.action == BranchAction.Push) {
+ isLoading = true;
+ await branchController.pushBranch($branch.id, $branch.requiresForce);
+ isLoading = false;
+ } else if (e.detail.action == BranchAction.Rebase) {
+ isLoading = true;
+ await branchController.mergeUpstream($branch.id);
+ isLoading = false;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }}
+ />
+ {:else}
+ Branch origin/second-branch is up to date with the remote.
+ {/if}
+
+{/if}
+
+
diff --git a/app/src/lib/components/BranchHeader.svelte b/app/src/lib/components/BranchHeader.svelte
index c01a74dcb..e07603c72 100644
--- a/app/src/lib/components/BranchHeader.svelte
+++ b/app/src/lib/components/BranchHeader.svelte
@@ -2,16 +2,21 @@
import ActiveBranchStatus from './ActiveBranchStatus.svelte';
import BranchLabel from './BranchLabel.svelte';
import BranchLanePopupMenu from './BranchLanePopupMenu.svelte';
+ import PullRequestButton from './PullRequestButton.svelte';
import Tag from './Tag.svelte';
import { Project } from '$lib/backend/projects';
+ import { BranchService } from '$lib/branches/service';
import { clickOutside } from '$lib/clickOutside';
import Button from '$lib/components/Button.svelte';
import Icon from '$lib/components/Icon.svelte';
+ import { GitHubService } from '$lib/github/service';
import { showError } from '$lib/notifications/toasts';
import { normalizeBranchName } from '$lib/utils/branch';
import { getContext, getContextStore } from '$lib/utils/context';
import { BranchController } from '$lib/vbranches/branchController';
- import { Branch } from '$lib/vbranches/types';
+ import { BaseBranch, Branch } from '$lib/vbranches/types';
+ import toast from 'svelte-french-toast';
+ import type { PullRequest } from '$lib/github/types';
import type { Persisted } from '$lib/persisted/persisted';
import { goto } from '$app/navigation';
@@ -20,16 +25,21 @@
export let isLaneCollapsed: Persisted;
const branchController = getContext(BranchController);
+ const githubService = getContext(GitHubService);
const branchStore = getContextStore(Branch);
const project = getContext(Project);
+ const branchService = getContext(BranchService);
+ const baseBranch = getContextStore(BaseBranch);
$: branch = $branchStore;
+ $: hasPullRequest = branch.upstreamName && githubService.hasPr(branch.upstreamName);
let meatballButton: HTMLDivElement;
let visible = false;
let isApplying = false;
let isDeleting = false;
let branchName = branch?.upstreamName || normalizeBranchName($branchStore.name);
+ let isLoading: boolean;
function handleBranchNameChange(title: string) {
if (title == '') return;
@@ -49,6 +59,34 @@
$: hasIntegratedCommits = branch.commits?.some((b) => b.isIntegrated);
let headerInfoHeight = 0;
+
+ interface CreatePrOpts {
+ draft: boolean;
+ }
+
+ const defaultPrOpts: CreatePrOpts = {
+ draft: true
+ };
+
+ async function createPr(createPrOpts: CreatePrOpts): Promise {
+ const opts = { ...defaultPrOpts, ...createPrOpts };
+ if (!githubService.isEnabled) {
+ toast.error('Cannot create PR without GitHub credentials');
+ return;
+ }
+
+ if (!$baseBranch?.shortName) {
+ toast.error('Cannot create PR without base branch');
+ return;
+ }
+
+ isLoading = true;
+ try {
+ return await branchService.createPr(branch, $baseBranch.shortName, opts.draft);
+ } finally {
+ isLoading = false;
+ }
+ }
{#if $isLaneCollapsed}
@@ -215,6 +253,12 @@
{:else}