Update sidebar grouping of branches

This commit is contained in:
Caleb Owens 2024-07-25 14:07:21 +02:00
parent 443d8d6b71
commit c6badd9f3e
12 changed files with 402 additions and 218 deletions

View File

@ -1,12 +1,19 @@
<script lang="ts">
import { tooltip } from '@gitbutler/ui/utils/tooltip';
export let name: 'remote-branch' | 'virtual-branch' | 'pr' | 'pr-draft' | 'pr-closed' | undefined;
export let name:
| 'remote-branch'
| 'local-branch'
| 'virtual-branch'
| 'pr'
| 'pr-draft'
| 'pr-closed'
| undefined;
export let help: string | undefined;
function getIconColor(name: string | undefined) {
if (name === 'remote-branch') return 'neutral';
if (name === 'virtual-branch') return 'virtual';
if (name === 'virtual-branch' || name === 'local-branch') return 'virtual';
if (name === 'pr') return 'success';
if (name === 'pr-draft') return 'purple';
if (name === 'pr-closed') return 'neutral';
@ -24,7 +31,7 @@
/>
</svg>
{/if}
{#if name === 'remote-branch'}
{#if name === 'remote-branch' || name === 'local-branch'}
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.75 9.99973V4H4.25V16H5.75V13C5.75 11.7574 6.75736 10.75 8 10.75C10.0711 10.75 11.75 9.07107 11.75 7V4H10.25V7C10.25 8.24264 9.24264 9.25 8 9.25C7.1558 9.25 6.37675 9.52896 5.75 9.99973Z"

View File

@ -13,10 +13,13 @@
import type { Branch } from '$lib/vbranches/types';
import { goto } from '$app/navigation';
export let branch: Branch;
export let localBranch: Branch | undefined;
export let remoteBranch: Branch | undefined;
export let base: BaseBranch | undefined | null;
export let pr: PullRequest | undefined;
$: branch = remoteBranch || localBranch!;
const branchController = getContext(BranchController);
const project = getContext(Project);
@ -26,29 +29,36 @@
<div class="header__wrapper">
<div class="header card">
<div class="header__info">
<BranchLabel disabled bind:name={branch.name} />
<BranchLabel disabled name={branch.name} />
<div class="header__remote-branch">
<div
class="status-tag text-base-11 text-semibold remote"
use:tooltip={'At least some of your changes have been pushed'}
>
<Icon name="remote-branch-small" /> remote
</div>
<Button
size="tag"
icon="open-link"
style="ghost"
outline
shrinkable
on:click={(e) => {
const url = base?.branchUrl(branch.name);
if (url) openExternalUrl(url);
e.preventDefault();
e.stopPropagation();
}}
>
{branch.displayName}
</Button>
{#if remoteBranch}
<div
class="status-tag text-base-11 text-semibold remote"
use:tooltip={'At least some of your changes have been pushed'}
>
<Icon name="remote-branch-small" />
{localBranch ? 'local and remote' : 'remote'}
</div>
<Button
size="tag"
icon="open-link"
style="ghost"
outline
shrinkable
on:click={(e) => {
const url = base?.branchUrl(branch.name);
if (url) openExternalUrl(url);
e.preventDefault();
e.stopPropagation();
}}
>
{branch.displayName}
</Button>
{:else}
<div class="status-tag text-base-11 text-semibold remote">
<Icon name="remote-branch-small" /> local
</div>
{/if}
{#if pr?.htmlUrl}
<Button
size="tag"

View File

@ -1,6 +1,8 @@
import { CombinedBranch } from '$lib/branches/types';
import { buildContextStore } from '$lib/utils/context';
import { groupBy } from '$lib/utils/groupBy';
import { derived, readable, writable, type Readable } from 'svelte/store';
import type { NameNormalizationService } from '$lib/branches/nameNormalizationService';
import type { GitHostListingService } from '$lib/gitHost/interface/gitHostListingService';
import type { PullRequest } from '$lib/gitHost/interface/types';
import type { RemoteBranchService } from '$lib/stores/remoteBranches';
@ -18,7 +20,8 @@ export class BranchService {
constructor(
vbranchService: VirtualBranchService,
remoteBranchService: RemoteBranchService,
gitPrService: GitHostListingService | undefined
gitPrService: GitHostListingService | undefined,
nameNormalizationService: NameNormalizationService
) {
const vbranches = vbranchService.branches;
const branches = remoteBranchService.branches;
@ -26,44 +29,73 @@ export class BranchService {
this.branches = derived(
[vbranches, branches, prs],
([vbranches, remoteBranches, pullRequests]) => {
return mergeBranchesAndPrs(vbranches, pullRequests, remoteBranches || []);
}
([vbranches, remoteBranches, pullRequests], set) => {
// derived with a set does not allow you to return a promise
mergeBranchesAndPrs(
vbranches || [],
pullRequests || [],
remoteBranches || [],
nameNormalizationService
).then((combinedBranches) => {
set(combinedBranches);
});
},
[] as CombinedBranch[] // Use an empty array as the default, with sufficient typing
);
}
}
function mergeBranchesAndPrs(
_vbranches: VirtualBranch[] | undefined,
pullRequests: PullRequest[] | undefined,
remoteBranches: Branch[] | undefined
): CombinedBranch[] {
async function mergeBranchesAndPrs(
virtualBranches: VirtualBranch[],
pullRequests: PullRequest[],
branches: Branch[],
nameNormalizationService: NameNormalizationService
): Promise<CombinedBranch[]> {
const contributions: CombinedBranch[] = [];
// Then remote branches that have no virtual branch, combined with pull requests if present
if (remoteBranches) {
contributions.push(
...remoteBranches.map((rb) => {
const pr = pullRequests?.find((pr) => pr.sha === rb.sha);
return new CombinedBranch({ remoteBranch: rb, pr });
})
);
const groupedBranches = groupBy(branches, (branch) => branch.givenName);
for (const [_, branches] of Object.entries(groupedBranches)) {
// There should only ever be one local reference for a particular given name
const localBranch = branches.find((branch) => !branch.isRemote);
const remoteBranches = branches.filter((branch) => branch.isRemote);
// There must be a local branch if there are no remote branches
if (remoteBranches.length === 0) {
contributions.push(new CombinedBranch({ localBranch }));
continue;
}
remoteBranches.forEach((remoteBranch) => {
contributions.push(new CombinedBranch({ remoteBranch, localBranch }));
});
}
// And finally pull requests that lack any corresponding branch
if (pullRequests) {
contributions.push(
...pullRequests
.filter((pr) => !contributions.some((cb) => pr.sha === cb.upstreamSha))
.map((pr) => {
return new CombinedBranch({ pr });
})
contributions.forEach((contribution) => {
const pullRequest = pullRequests.find(
// This may be over-sensitive in rare cases, but is preferable to using the head sha
(pullRequest) => contribution.remoteBranch?.givenName === pullRequest.sourceBranch
);
}
if (pullRequest) {
contribution.pr = pullRequest;
}
});
const normalizedVirtualBranchNames = new Set(
await Promise.all(
virtualBranches.map(
async (virtualBranch) => await nameNormalizationService.normalize(virtualBranch.name)
)
)
);
// This should be everything considered a branch in one list
const filtered = contributions.sort((a, b) => {
return (a.modifiedAt || new Date(0)) < (b.modifiedAt || new Date(0)) ? 1 : -1;
});
const filtered = contributions
.filter((combinedBranch) => !normalizedVirtualBranchNames.has(combinedBranch.branch!.givenName))
.sort((a, b) => {
return (a.modifiedAt || new Date(0)) < (b.modifiedAt || new Date(0)) ? 1 : -1;
});
return filtered;
}

View File

@ -4,19 +4,23 @@ import type { Author, VirtualBranch, Branch } from '$lib/vbranches/types';
export class CombinedBranch {
pr?: PullRequest;
remoteBranch?: Branch;
localBranch?: Branch;
vbranch?: VirtualBranch;
constructor({
vbranch,
remoteBranch,
localBranch,
pr
}: {
vbranch?: VirtualBranch;
remoteBranch?: Branch;
localBranch?: Branch;
pr?: PullRequest;
}) {
this.vbranch = vbranch;
this.remoteBranch = remoteBranch;
this.localBranch = localBranch;
this.pr = pr;
}
@ -24,6 +28,7 @@ export class CombinedBranch {
return (
this.pr?.sha ||
this.remoteBranch?.sha ||
this.localBranch?.sha ||
this.vbranch?.upstream?.sha ||
this.vbranch?.head ||
'unknown'
@ -32,7 +37,11 @@ export class CombinedBranch {
get displayName(): string {
return (
this.pr?.sourceBranch || this.remoteBranch?.displayName || this.vbranch?.name || 'unknown'
this.pr?.sourceBranch ||
this.remoteBranch?.displayName ||
this.localBranch?.displayName ||
this.vbranch?.name ||
'unknown'
);
}
@ -41,9 +50,9 @@ export class CombinedBranch {
if (this.pr?.author) {
authors.push(this.pr.author);
}
if (this.remoteBranch) {
if (this.remoteBranch.lastCommitAuthor) {
authors.push({ name: this.remoteBranch.lastCommitAuthor });
if (this.branch) {
if (this.branch.lastCommitAuthor) {
authors.push({ name: this.branch.lastCommitAuthor });
}
}
if (this.vbranch) {
@ -59,7 +68,14 @@ export class CombinedBranch {
return this.authors[0];
}
get icon(): 'remote-branch' | 'virtual-branch' | 'pr' | 'pr-draft' | 'pr-closed' | undefined {
get icon():
| 'remote-branch'
| 'local-branch'
| 'virtual-branch'
| 'pr'
| 'pr-draft'
| 'pr-closed'
| undefined {
return this.currentState();
}
@ -73,9 +89,9 @@ export class CombinedBranch {
get modifiedAt(): Date | undefined {
if (this.vbranch) return this.vbranch.updatedAt;
if (this.remoteBranch) {
return this.remoteBranch.lastCommitTimestampMs
? new Date(this.remoteBranch.lastCommitTimestampMs)
if (this.branch) {
return this.branch.lastCommitTimestampMs
? new Date(this.branch.lastCommitTimestampMs)
: undefined;
}
if (this.pr) {
@ -90,6 +106,8 @@ export class CombinedBranch {
return 'Virtual branch';
case BranchState.RemoteBranch:
return 'Remote branch';
case BranchState.LocalBranch:
return 'Local branch';
case BranchState.PR:
return 'Pull Request';
case BranchState.PRClosed:
@ -109,9 +127,9 @@ export class CombinedBranch {
this.pr.author?.email && identifiers.push(this.pr.author.email);
this.pr.author?.name && identifiers.push(this.pr.author.name);
}
if (this.remoteBranch) {
identifiers.push(this.remoteBranch.displayName);
this.remoteBranch.lastCommitAuthor && identifiers.push(this.remoteBranch.lastCommitAuthor);
if (this.branch) {
identifiers.push(this.branch.displayName);
this.branch.lastCommitAuthor && identifiers.push(this.branch.lastCommitAuthor);
}
return identifiers.map((identifier) => identifier.toLowerCase());
@ -120,13 +138,21 @@ export class CombinedBranch {
currentState(): BranchState | undefined {
if (this.pr) return BranchState.PR;
if (this.remoteBranch) return BranchState.RemoteBranch;
if (this.localBranch) return BranchState.LocalBranch;
if (this.vbranch) return BranchState.VirtualBranch;
return undefined;
}
get branch() {
// Prefer the local branch over the remote branch
// We should always have at least one branch
return this.localBranch || this.remoteBranch;
}
}
enum BranchState {
RemoteBranch = 'remote-branch',
LocalBranch = 'local-branch',
VirtualBranch = 'virtual-branch',
PR = 'pr',
PRDraft = 'pr-draft',

View File

@ -0,0 +1,231 @@
<script lang="ts">
import BranchPreviewHeader from '../branch/BranchPreviewHeader.svelte';
import Resizer from '../shared/Resizer.svelte';
import ScrollableContainer from '../shared/ScrollableContainer.svelte';
import { Project } from '$lib/backend/projects';
import { BaseBranch } from '$lib/baseBranch/baseBranch';
import CommitCard from '$lib/commit/CommitCard.svelte';
import { transformAnyCommit } from '$lib/commitLines/transformers';
import FileCard from '$lib/file/FileCard.svelte';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { RemoteBranchService } from '$lib/stores/remoteBranches';
import { getContext, getContextStore, getContextStoreBySymbol } from '$lib/utils/context';
import { getMarkdownRenderer } from '$lib/utils/markdown';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { BranchData, type Branch } from '$lib/vbranches/types';
import LineGroup from '@gitbutler/ui/CommitLines/LineGroup.svelte';
import { LineManagerFactory } from '@gitbutler/ui/CommitLines/lineManager';
import lscache from 'lscache';
import { marked } from 'marked';
import { onMount, setContext } from 'svelte';
import { writable } from 'svelte/store';
import type { PullRequest } from '$lib/gitHost/interface/types';
export let localBranch: Branch | undefined;
export let remoteBranch: Branch | undefined;
export let pr: PullRequest | undefined;
const project = getContext(Project);
const remoteBranchService = getContext(RemoteBranchService);
const baseBranch = getContextStore(BaseBranch);
const fileIdSelection = new FileIdSelection(project.id, writable([]));
setContext(FileIdSelection, fileIdSelection);
$: selectedFile = fileIdSelection.selectedFile;
const defaultBranchWidthRem = 30;
const laneWidthKey = 'branchPreviewLaneWidth';
const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
const lineManagerFactory = getContext(LineManagerFactory);
let localBranchData: BranchData | undefined;
let remoteBranchData: BranchData | undefined;
$: console.log({ localBranch, localBranchData, remoteBranch, remoteBranchData });
// The remote branch service (which needs to be renamed) is responsible for
// fetching local and remote branches.
// We must manually set the branch data to undefined as the component
// doesn't get completely re-rendered on a page change.
$: if (localBranch) {
remoteBranchService
.getRemoteBranchData(localBranch.name)
.then((branchData) => (localBranchData = branchData));
} else {
localBranchData = undefined;
}
$: if (remoteBranch) {
remoteBranchService
.getRemoteBranchData(remoteBranch.name)
.then((branchData) => (remoteBranchData = branchData));
} else {
localBranchData = undefined;
}
$: remoteCommitShas = new Set(remoteBranchData?.commits.map((commit) => commit.id) || []);
// Find commits common in the local and remote
$: localAndRemoteCommits =
localBranchData?.commits.filter((commit) => remoteCommitShas.has(commit.id)) || [];
$: localAndRemoteCommitShas = new Set(localAndRemoteCommits.map((commit) => commit.id));
// Find the local and remote commits that are not shared
$: localCommits =
localBranchData?.commits.filter((commit) => !localAndRemoteCommitShas.has(commit.id)) || [];
$: remoteCommits =
remoteBranchData?.commits.filter((commit) => !localAndRemoteCommitShas.has(commit.id)) || [];
$: lineManager = lineManagerFactory.build(
{
remoteCommits: remoteCommits.map(transformAnyCommit),
localCommits: localCommits.map(transformAnyCommit),
localAndRemoteCommits: localAndRemoteCommits.map(transformAnyCommit),
integratedCommits: []
},
true
);
let rsViewport: HTMLDivElement;
let laneWidth: number;
onMount(() => {
laneWidth = lscache.get(laneWidthKey);
});
const renderer = getMarkdownRenderer();
</script>
{#if remoteBranch || localBranch}
<div class="base">
<div
class="base__left"
bind:this={rsViewport}
style:width={`${laneWidth || defaultBranchWidthRem}rem`}
>
<ScrollableContainer wide>
<div class="branch-preview">
<BranchPreviewHeader base={$baseBranch} {localBranch} {remoteBranch} {pr} />
{#if pr}
<div class="card">
<div class="card__header text-base-body-14 text-semibold">{pr.title}</div>
{#if pr.body}
<div class="markdown card__content text-base-body-13">
{@html marked.parse(pr.body, { renderer })}
</div>
{/if}
</div>
{/if}
<div>
{#if remoteCommits}
{#each remoteCommits as commit, index (commit.id)}
<CommitCard
first={index === 0}
last={index === remoteCommits.length - 1}
{commit}
commitUrl={$baseBranch?.commitUrl(commit.id)}
type="remote"
>
{#snippet lines(topHeightPx)}
<LineGroup lineGroup={lineManager.get(commit.id)} {topHeightPx} />
{/snippet}
</CommitCard>
{/each}
{/if}
{#if localCommits}
{#each localCommits as commit, index (commit.id)}
<CommitCard
first={index === 0}
last={index === localCommits.length - 1}
{commit}
commitUrl={$baseBranch?.commitUrl(commit.id)}
type="local"
>
{#snippet lines(topHeightPx)}
<LineGroup lineGroup={lineManager.get(commit.id)} {topHeightPx} />
{/snippet}
</CommitCard>
{/each}
{/if}
{#if localAndRemoteCommits}
{#each localAndRemoteCommits as commit, index (commit.id)}
<CommitCard
first={index === 0}
last={index === localAndRemoteCommits.length - 1}
{commit}
commitUrl={$baseBranch?.commitUrl(commit.id)}
type="localAndRemote"
>
{#snippet lines(topHeightPx)}
<LineGroup lineGroup={lineManager.get(commit.id)} {topHeightPx} />
{/snippet}
</CommitCard>
{/each}
{/if}
</div>
</div>
</ScrollableContainer>
<Resizer
viewport={rsViewport}
direction="right"
minWidth={320}
on:width={(e) => {
laneWidth = e.detail / (16 * $userSettings.zoom);
lscache.set(laneWidthKey, laneWidth, 7 * 1440); // 7 day ttl
}}
/>
</div>
<div class="base__right">
{#await $selectedFile then selected}
{#if selected}
<FileCard
conflicted={selected.conflicted}
file={selected}
isUnapplied={false}
readonly={true}
on:close={() => {
fileIdSelection.clear();
}}
/>
{/if}
{/await}
</div>
</div>
{:else}
<p>No local or remote branch found</p>
{/if}
<style lang="postcss">
.base {
display: flex;
width: 100%;
overflow-x: auto;
}
.base__left {
display: flex;
flex-grow: 0;
flex-shrink: 0;
overflow-x: hidden;
position: relative;
}
.base__right {
display: flex;
overflow-x: auto;
align-items: flex-start;
padding: 12px 12px 12px 6px;
width: 800px;
}
.branch-preview {
display: flex;
flex-direction: column;
gap: 8px;
margin: 12px 6px 12px 12px;
}
.card__content {
color: var(--clr-scale-ntrl-30);
}
</style>

View File

@ -1,141 +0,0 @@
<script lang="ts">
import BranchPreviewHeader from '../branch/BranchPreviewHeader.svelte';
import Resizer from '../shared/Resizer.svelte';
import ScrollableContainer from '../shared/ScrollableContainer.svelte';
import { Project } from '$lib/backend/projects';
import { BaseBranch } from '$lib/baseBranch/baseBranch';
import CommitCard from '$lib/commit/CommitCard.svelte';
import FileCard from '$lib/file/FileCard.svelte';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { RemoteBranchService } from '$lib/stores/remoteBranches';
import { getContext, getContextStore, getContextStoreBySymbol } from '$lib/utils/context';
import { getMarkdownRenderer } from '$lib/utils/markdown';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { type Branch } from '$lib/vbranches/types';
import lscache from 'lscache';
import { marked } from 'marked';
import { onMount, setContext } from 'svelte';
import { writable } from 'svelte/store';
import type { PullRequest } from '$lib/gitHost/interface/types';
export let branch: Branch;
export let pr: PullRequest | undefined;
const project = getContext(Project);
const remoteBranchService = getContext(RemoteBranchService);
const baseBranch = getContextStore(BaseBranch);
const fileIdSelection = new FileIdSelection(project.id, writable([]));
setContext(FileIdSelection, fileIdSelection);
$: selectedFile = fileIdSelection.selectedFile;
const defaultBranchWidthRem = 30;
const laneWidthKey = 'branchPreviewLaneWidth';
const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
let rsViewport: HTMLDivElement;
let laneWidth: number;
onMount(() => {
laneWidth = lscache.get(laneWidthKey);
});
const renderer = getMarkdownRenderer();
</script>
<div class="base">
<div
class="base__left"
bind:this={rsViewport}
style:width={`${laneWidth || defaultBranchWidthRem}rem`}
>
<ScrollableContainer wide>
<div class="branch-preview">
<BranchPreviewHeader base={$baseBranch} {branch} {pr} />
{#if pr}
<div class="card">
<div class="card__header text-base-body-14 text-semibold">{pr.title}</div>
{#if pr.body}
<div class="markdown card__content text-base-body-13">
{@html marked.parse(pr.body, { renderer })}
</div>
{/if}
</div>
{/if}
{#await remoteBranchService.getRemoteBranchData(branch.name) then branchData}
{#if branchData.commits && branchData.commits.length > 0}
<div>
{#each branchData.commits as commit, index (commit.id)}
<CommitCard
first={index === 0}
last={index === branchData.commits.length - 1}
{commit}
commitUrl={$baseBranch?.commitUrl(commit.id)}
type="localAndRemote"
/>
{/each}
</div>
{/if}
{/await}
</div>
</ScrollableContainer>
<Resizer
viewport={rsViewport}
direction="right"
minWidth={320}
on:width={(e) => {
laneWidth = e.detail / (16 * $userSettings.zoom);
lscache.set(laneWidthKey, laneWidth, 7 * 1440); // 7 day ttl
}}
/>
</div>
<div class="base__right">
{#await $selectedFile then selected}
{#if selected}
<FileCard
conflicted={selected.conflicted}
file={selected}
isUnapplied={false}
readonly={true}
on:close={() => {
fileIdSelection.clear();
}}
/>
{/if}
{/await}
</div>
</div>
<style lang="postcss">
.base {
display: flex;
width: 100%;
overflow-x: auto;
}
.base__left {
display: flex;
flex-grow: 0;
flex-shrink: 0;
overflow-x: hidden;
position: relative;
}
.base__right {
display: flex;
overflow-x: auto;
align-items: flex-start;
padding: 12px 12px 12px 6px;
width: 800px;
}
.branch-preview {
display: flex;
flex-direction: column;
gap: 8px;
margin: 12px 6px 12px 12px;
}
.card__content {
color: var(--clr-scale-ntrl-30);
}
</style>

View File

@ -10,7 +10,10 @@
function getBranchLink(b: CombinedBranch): string | undefined {
if (b.vbranch) return `/${projectId}/board/`;
if (b.remoteBranch) return `/${projectId}/remote/${branch?.remoteBranch?.displayName}`;
// Here we specifically want to prefer looking at the remote branch as
// the there may be multiple remotes that share the same local branch.
if (b.branch)
return `/${projectId}/branch/${branch?.remoteBranch?.name || branch?.localBranch?.name}`;
if (b.pr) return `/${projectId}/pull/${b.pr.number}`;
}

View File

@ -67,7 +67,7 @@
if (b.pr) return false;
}
if (params.includeRemote && b.remoteBranch) return true;
if (params.includeRemote && b.branch) return true;
return false;
});
}

View File

@ -315,14 +315,11 @@ export class Branch {
lastCommitTimestampMs?: number | undefined;
lastCommitAuthor?: string | undefined;
givenName!: string;
isRemote!: boolean;
get displayName(): string {
return this.name.replace('refs/remotes/', '').replace('refs/heads/', '');
}
get isRemote() {
return !!this.upstream;
}
}
export class BranchData {

View File

@ -4,6 +4,7 @@
import { BaseBranch, NoDefaultTarget } from '$lib/baseBranch/baseBranch';
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
import { BranchDragActionsFactory } from '$lib/branches/dragActions';
import { getNameNormalizationServiceContext } from '$lib/branches/nameNormalizationService';
import { BranchService, createBranchServiceStore } from '$lib/branches/service';
import { CommitDragActionsFactory } from '$lib/commits/dragActions';
import NoBaseBranch from '$lib/components/NoBaseBranch.svelte';
@ -33,6 +34,8 @@
const { data, children }: { data: LayoutData; children: Snippet } = $props();
const nameNormalizationService = getNameNormalizationServiceContext();
const {
vbranchService,
project,
@ -104,7 +107,14 @@
listServiceStore.set(ghListService);
githubRepoServiceStore.set(gitHost);
branchServiceStore.set(new BranchService(vbranchService, remoteBranchService, ghListService));
branchServiceStore.set(
new BranchService(
vbranchService,
remoteBranchService,
ghListService,
nameNormalizationService
)
);
});
// Once on load and every time the project id changes

View File

@ -4,15 +4,18 @@
// - And it does NOT have a cooresponding vbranch
// It may also display details about a cooresponding pr if they exist
import { getBranchServiceStore } from '$lib/branches/service';
import RemoteBranchPreview from '$lib/components/BranchPreview.svelte';
import FullviewLoading from '$lib/components/FullviewLoading.svelte';
import RemoteBranchPreview from '$lib/components/RemoteBranchPreview.svelte';
import { page } from '$app/stores';
const branchService = getBranchServiceStore();
const branches = $derived($branchService?.branches);
const error = $derived($branchService?.error);
// Search for remote branch first as there may be multiple combined branches
// which have the same local branch
const branch = $derived(
$branches?.find((cb) => cb.remoteBranch?.displayName === $page.params.name)
$branches?.find((cb) => cb.remoteBranch?.name === $page.params.name) ||
$branches?.find((cb) => cb.localBranch?.name === $page.params.name)
);
// $: branch = $branches?.find((b) => b.displayName === $page.params.name);
</script>
@ -21,8 +24,12 @@
<p>Error...</p>
{:else if !$branches}
<FullviewLoading />
{:else if branch?.remoteBranch}
<RemoteBranchPreview branch={branch.remoteBranch} pr={branch.pr} />
{:else if branch?.remoteBranch || branch?.localBranch}
<RemoteBranchPreview
localBranch={branch?.localBranch}
remoteBranch={branch?.remoteBranch}
pr={branch.pr}
/>
{:else}
<p>Branch doesn't seem to exist</p>
{/if}

View File

@ -30,6 +30,7 @@ pub struct RemoteBranch {
pub given_name: String,
pub last_commit_timestamp_ms: Option<u128>,
pub last_commit_author: Option<String>,
pub is_remote: bool,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
@ -139,6 +140,7 @@ pub(crate) fn branch_to_remote_branch(
.map(|t: u128| t * 1000)
.ok(),
last_commit_author: commit.author().name().map(std::string::ToString::to_string),
is_remote: branch.get().is_remote(),
})
}