mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-31 17:20:44 +08:00
Show the branch a patch is opened against
Add it to the patch list byline and the patch page metadata, shown only when it differs from the default branch. The merge activity item now names the real target instead of always naming the default branch. Co-Authored-By: Brandon Oxendine <brandon.oxendine@gmail.com>
This commit is contained in:
co-authored by
Brandon Oxendine
parent
3688218060
commit
e35baf750f
@@ -257,6 +257,11 @@
|
||||
.merge-badge :global(*) {
|
||||
color: inherit;
|
||||
}
|
||||
.merge-target {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.copy-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -572,7 +577,11 @@
|
||||
<div class="summary-line">
|
||||
<span class="txt-body-m-medium">merged patch</span>
|
||||
{#if targetBranch}
|
||||
into <b>{targetBranch}</b>
|
||||
into
|
||||
<span class="merge-target">
|
||||
<Icon name="branch" />
|
||||
{targetBranch}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
import type { Stats } from "@bindings/diff/Stats";
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
import type { RepoRefs } from "@bindings/repo/RepoRefs";
|
||||
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
@@ -19,12 +20,14 @@
|
||||
summaryTitle,
|
||||
} from "@app/lib/reviewSummary";
|
||||
import * as roles from "@app/lib/roles";
|
||||
import { push } from "@app/lib/router";
|
||||
import { push, routeToPath } from "@app/lib/router";
|
||||
import {
|
||||
authorForNodeId,
|
||||
defaultBranch,
|
||||
formatOid,
|
||||
pluralize,
|
||||
publicKeyFromDid,
|
||||
unqualifyBranch,
|
||||
verdictIcon,
|
||||
} from "@app/lib/utils";
|
||||
|
||||
@@ -149,6 +152,58 @@
|
||||
await loadPatch();
|
||||
}
|
||||
}
|
||||
|
||||
const targetBranch = $derived(
|
||||
patch.targetBranch === undefined
|
||||
? undefined
|
||||
: unqualifyBranch(patch.targetBranch),
|
||||
);
|
||||
const targetBranchCaption = $derived(
|
||||
`This patch merges into ${targetBranch}`,
|
||||
);
|
||||
|
||||
// A patch's target is not validated when it is opened, so it can name a
|
||||
// branch the source view cannot resolve. The default branch is canonical by
|
||||
// definition; any other target needs the canonical ref list, which is
|
||||
// expensive enough to load after render. An unresolved target renders
|
||||
// unlinked — a missing link beats one that errors.
|
||||
const targetIsDefaultBranch = $derived(
|
||||
targetBranch !== undefined && targetBranch === defaultBranch(repo),
|
||||
);
|
||||
|
||||
let canonicalBranches: Record<string, string> | undefined = $state();
|
||||
let canonicalBranchesRid: string | undefined;
|
||||
|
||||
$effect(() => {
|
||||
if (targetBranch === undefined || targetIsDefaultBranch) {
|
||||
return;
|
||||
}
|
||||
const requested = repo.rid;
|
||||
if (canonicalBranchesRid === requested) {
|
||||
return;
|
||||
}
|
||||
canonicalBranchesRid = requested;
|
||||
canonicalBranches = undefined;
|
||||
void invoke<RepoRefs>("list_repo_refs", { rid: requested })
|
||||
.then(refs => {
|
||||
if (canonicalBranchesRid === requested) {
|
||||
canonicalBranches = refs.canonical.branches;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (canonicalBranchesRid === requested) {
|
||||
canonicalBranchesRid = undefined;
|
||||
}
|
||||
console.error("Failed to load repo refs", error);
|
||||
});
|
||||
});
|
||||
|
||||
const targetBranchBrowsable = $derived(
|
||||
targetIsDefaultBranch ||
|
||||
(targetBranch !== undefined &&
|
||||
canonicalBranches !== undefined &&
|
||||
targetBranch in canonicalBranches),
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -238,6 +293,7 @@
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
.patch-id-chip,
|
||||
.target-branch-chip,
|
||||
.author-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -249,6 +305,24 @@
|
||||
color: var(--color-text-tertiary);
|
||||
font: var(--txt-body-m-regular);
|
||||
}
|
||||
.target-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-surface-canvas);
|
||||
color: var(--color-text-tertiary);
|
||||
font: var(--txt-body-m-regular);
|
||||
}
|
||||
.target-group .patch-id-chip,
|
||||
.target-group .target-branch-chip {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
}
|
||||
.patch-id-chip {
|
||||
gap: 0.375rem;
|
||||
cursor: pointer;
|
||||
@@ -258,6 +332,19 @@
|
||||
background-color: var(--color-surface-subtle);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.target-arrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.target-branch-chip {
|
||||
gap: 0.375rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.target-branch-chip:hover,
|
||||
a.target-branch-chip:focus-visible {
|
||||
background-color: var(--color-surface-subtle);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.patch-id-value {
|
||||
font: var(--txt-code-regular);
|
||||
}
|
||||
@@ -284,19 +371,44 @@
|
||||
<div class="author-chip" title="Patch author">
|
||||
<NodeId {...authorForNodeId(patch.author)} />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="patch-id-chip"
|
||||
title={patchIdCopied ? "Copied to clipboard" : "Copy patch ID"}
|
||||
onclick={copyPatchId}>
|
||||
{#if patchIdCopied}
|
||||
<Icon name="checkmark" />
|
||||
{:else}
|
||||
<span class="pid-icon-default"><Icon name="hash" /></span>
|
||||
<span class="pid-icon-hover"><Icon name="copy" /></span>
|
||||
<div class="target-group">
|
||||
<button
|
||||
type="button"
|
||||
class="patch-id-chip"
|
||||
title={patchIdCopied ? "Copied to clipboard" : "Copy patch ID"}
|
||||
onclick={copyPatchId}>
|
||||
{#if patchIdCopied}
|
||||
<Icon name="checkmark" />
|
||||
{:else}
|
||||
<span class="pid-icon-default"><Icon name="hash" /></span>
|
||||
<span class="pid-icon-hover"><Icon name="copy" /></span>
|
||||
{/if}
|
||||
<span class="patch-id-value">{formatOid(patch.id)}</span>
|
||||
</button>
|
||||
{#if targetBranch}
|
||||
<span class="target-arrow" title={targetBranchCaption}>
|
||||
<Icon name="arrow-right" />
|
||||
</span>
|
||||
{#if targetBranchBrowsable}
|
||||
<a
|
||||
class="target-branch-chip"
|
||||
title={targetBranchCaption}
|
||||
href={routeToPath({
|
||||
resource: "repo.home",
|
||||
rid: repo.rid,
|
||||
revision: targetBranch,
|
||||
})}>
|
||||
<Icon name="branch" />
|
||||
<span>{targetBranch}</span>
|
||||
</a>
|
||||
{:else}
|
||||
<span class="target-branch-chip" title={targetBranchCaption}>
|
||||
<Icon name="branch" />
|
||||
<span>{targetBranch}</span>
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
<span class="patch-id-value">{formatOid(patch.id)}</span>
|
||||
</button>
|
||||
</div>
|
||||
{#if stats}
|
||||
{#if onShowChanges}
|
||||
<button
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import type { Thread } from "@bindings/cob/thread/Thread";
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
import type { Commit } from "@bindings/repo/Commit";
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
import partial from "lodash/partial";
|
||||
@@ -38,6 +37,7 @@
|
||||
didFromPublicKey,
|
||||
pluralize,
|
||||
publicKeyFromDid,
|
||||
unqualifyBranch,
|
||||
} from "@app/lib/utils";
|
||||
|
||||
import { announce } from "@app/components/AnnounceSwitch.svelte";
|
||||
@@ -88,9 +88,10 @@
|
||||
|
||||
interface Props {
|
||||
rid: string;
|
||||
repo: RepoInfo;
|
||||
repoDelegates: Author[];
|
||||
patchId: string;
|
||||
// The patch's resolved merge target, fully qualified.
|
||||
patchTargetBranch?: string;
|
||||
revision: Revision;
|
||||
config: Config;
|
||||
loadPatch: () => Promise<void>;
|
||||
@@ -119,9 +120,9 @@
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
rid,
|
||||
repo,
|
||||
repoDelegates,
|
||||
patchId,
|
||||
patchTargetBranch,
|
||||
revision,
|
||||
config,
|
||||
loadPatch,
|
||||
@@ -183,7 +184,9 @@
|
||||
: undefined;
|
||||
});
|
||||
const targetBranch = $derived(
|
||||
repo.payloads["xyz.radicle.project"]?.data.defaultBranch,
|
||||
patchTargetBranch === undefined
|
||||
? undefined
|
||||
: unqualifyBranch(patchTargetBranch),
|
||||
);
|
||||
let revisionToggles: Record<string, boolean> = $state({});
|
||||
let commitGroupToggles: Record<string, boolean> = $state({});
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Issue } from "@bindings/cob/issue/Issue";
|
||||
import type { Patch } from "@bindings/cob/patch/Patch";
|
||||
import type { Review } from "@bindings/cob/patch/Review";
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
import bs58 from "bs58";
|
||||
@@ -16,6 +17,20 @@ export const unreachable = (value: never): never => {
|
||||
throw new Error(`Unreachable code: ${value}`);
|
||||
};
|
||||
|
||||
export const REFS_HEADS = "refs/heads/";
|
||||
|
||||
// The single place to change once the API stops carrying the default branch
|
||||
// in the project payload, which Heartwood no longer requires.
|
||||
export function defaultBranch(repo: RepoInfo): string | undefined {
|
||||
return repo.payloads["xyz.radicle.project"]?.data.defaultBranch;
|
||||
}
|
||||
|
||||
export function unqualifyBranch(refname: string): string {
|
||||
return refname.startsWith(REFS_HEADS)
|
||||
? refname.slice(REFS_HEADS.length)
|
||||
: refname;
|
||||
}
|
||||
|
||||
export function formatRepositoryId(id: string): string {
|
||||
const parsedId = parseRepositoryId(id);
|
||||
|
||||
|
||||
@@ -1514,9 +1514,9 @@
|
||||
|
||||
{#snippet revisionBody()}
|
||||
<RevisionComponent
|
||||
patchTargetBranch={patch.targetBranch}
|
||||
bind:this={revisionComponent}
|
||||
rid={repo.rid}
|
||||
{repo}
|
||||
repoDelegates={repo.delegates}
|
||||
patchId={patch.id}
|
||||
{loadPatch}
|
||||
@@ -1571,8 +1571,8 @@
|
||||
class:collapsed={descriptionCollapsed}>
|
||||
<div class="patch-description-body" bind:this={descriptionEl}>
|
||||
<RevisionComponent
|
||||
patchTargetBranch={patch.targetBranch}
|
||||
rid={repo.rid}
|
||||
{repo}
|
||||
repoDelegates={repo.delegates}
|
||||
patchId={patch.id}
|
||||
{loadPatch}
|
||||
|
||||
Reference in New Issue
Block a user