mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-16 03:40:37 +08:00
Show the branch a patch is opened against
Resolve the merge target from `target` and the project payload, and name it in the byline next to the patch ID. Co-Authored-By: Brandon Oxendine <brandon.oxendine@gmail.com>
This commit is contained in:
co-authored by
Brandon Oxendine
parent
1a160e55c1
commit
8e78634a23
+18
-3
@@ -1,9 +1,24 @@
|
||||
import type { Author, BaseUrl } from "@http-client";
|
||||
import type { Author, BaseUrl, Repo } from "@http-client";
|
||||
|
||||
import md5 from "md5";
|
||||
import bs58 from "bs58";
|
||||
import twemojiModule from "twemoji";
|
||||
|
||||
export const REFS_HEADS = "refs/heads/";
|
||||
|
||||
// Reads the repository's default branch. Heartwood no longer requires the
|
||||
// project payload, so this is the single place to change once the API stops
|
||||
// carrying the default branch there.
|
||||
export function defaultBranch(repo: Repo): string {
|
||||
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 async function toClipboard(text: string): Promise<void> {
|
||||
await navigator.clipboard.writeText(text);
|
||||
}
|
||||
@@ -328,8 +343,8 @@ export function getBranchesFromRefs(
|
||||
): Record<string, string> {
|
||||
const branches: Record<string, string> = {};
|
||||
for (const [name, oid] of Object.entries(refs)) {
|
||||
if (name.startsWith("refs/heads/")) {
|
||||
branches[name.slice("refs/heads/".length)] = oid;
|
||||
if (name.startsWith(REFS_HEADS)) {
|
||||
branches[name.slice(REFS_HEADS.length)] = oid;
|
||||
}
|
||||
}
|
||||
return branches;
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
Comment,
|
||||
Review,
|
||||
Merge,
|
||||
Patch,
|
||||
Repo,
|
||||
Revision,
|
||||
Diff,
|
||||
} from "@http-client";
|
||||
|
||||
import * as utils from "@app/lib/utils";
|
||||
|
||||
interface Thread {
|
||||
root: Comment;
|
||||
replies: Comment[];
|
||||
@@ -41,15 +44,32 @@
|
||||
string,
|
||||
{ latest: boolean; review: Review }
|
||||
>;
|
||||
|
||||
// The branch a patch is opened against, unqualified. The `delegates` target
|
||||
// leaves the repository default implied.
|
||||
function resolveTargetBranch(patch: Patch, repo: Repo): string {
|
||||
return patch.target === "delegates"
|
||||
? utils.defaultBranch(repo)
|
||||
: utils.unqualifyBranch(patch.target.branch);
|
||||
}
|
||||
|
||||
// Nothing validates a patch's target when it is opened, so it can name a
|
||||
// branch that is not a canonical reference. The source view cannot resolve
|
||||
// such a branch, so it is shown without a link rather than one that errors.
|
||||
function isBrowsableBranch(branch: string, repo: Repo): boolean {
|
||||
return (
|
||||
branch === utils.defaultBranch(repo) ||
|
||||
repo.refs?.refs[`${utils.REFS_HEADS}${branch}`] !== undefined
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { BaseUrl, Patch } from "@http-client";
|
||||
import type { BaseUrl } from "@http-client";
|
||||
import type { PatchView } from "./router";
|
||||
import type { Route } from "@app/lib/router";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
import * as utils from "@app/lib/utils";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
|
||||
@@ -232,6 +252,9 @@
|
||||
})),
|
||||
].sort((a, b) => a.timestamp - b.timestamp),
|
||||
]);
|
||||
$: targetBranch = resolveTargetBranch(patch, repo);
|
||||
$: targetBranchCaption = `This patch merges into ${targetBranch}`;
|
||||
$: targetBranchBrowsable = isBrowsableBranch(targetBranch, repo);
|
||||
$: firstRevision = timelineTuple[0][0];
|
||||
$: latestRevision = patch.revisions[patch.revisions.length - 1];
|
||||
</script>
|
||||
@@ -286,6 +309,19 @@
|
||||
color: var(--color-text-tertiary);
|
||||
font: var(--txt-body-m-regular);
|
||||
}
|
||||
.code-chip {
|
||||
display: inline-flex;
|
||||
}
|
||||
.code-chip > :global(*) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font: var(--txt-code-regular);
|
||||
color: var(--color-text-tertiary);
|
||||
background-color: var(--color-surface-alpha-subtle);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
.revision-description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -379,7 +415,33 @@
|
||||
nodeId={patch.author.id}
|
||||
alias={patch.author.alias} />
|
||||
opened
|
||||
<Id id={patch.id} />
|
||||
<span class="code-chip">
|
||||
<Id id={patch.id} />
|
||||
</span>
|
||||
<span class="global-flex-item" title={targetBranchCaption}>
|
||||
<Icon name="arrow-right" />
|
||||
</span>
|
||||
<span class="code-chip">
|
||||
{#if targetBranchBrowsable}
|
||||
<Link
|
||||
styleHoverState
|
||||
title={targetBranchCaption}
|
||||
route={{
|
||||
resource: "repo.source",
|
||||
repo: repoId,
|
||||
node: baseUrl,
|
||||
revision: targetBranch,
|
||||
}}>
|
||||
<Icon name="branch" />
|
||||
<span class="txt-overflow">{targetBranch}</span>
|
||||
</Link>
|
||||
{:else}
|
||||
<span title={targetBranchCaption}>
|
||||
<Icon name="branch" />
|
||||
<span class="txt-overflow">{targetBranch}</span>
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span title={utils.absoluteTimestamp(patch.revisions[0].timestamp)}>
|
||||
{utils.formatTimestamp(patch.revisions[0].timestamp)}
|
||||
</span>
|
||||
|
||||
@@ -32,6 +32,7 @@ import { ResponseError, ResponseParseError } from "@http-client/lib/fetcher";
|
||||
import { cached } from "@app/lib/cache";
|
||||
import { handleError, unreachableError } from "@app/views/repos/error";
|
||||
import {
|
||||
REFS_HEADS,
|
||||
getBranchesFromRefs,
|
||||
getTagsFromRefs,
|
||||
unreachable,
|
||||
@@ -43,7 +44,7 @@ export const ISSUES_PER_PAGE = 10;
|
||||
export const RELEASES_PER_PAGE = 30;
|
||||
|
||||
export function peerHasBranches(peer: PeerRefs): boolean {
|
||||
return Object.keys(peer.refs).some(name => name.startsWith("refs/heads/"));
|
||||
return Object.keys(peer.refs).some(name => name.startsWith(REFS_HEADS));
|
||||
}
|
||||
|
||||
function canonicalOids(
|
||||
@@ -70,7 +71,7 @@ export function remoteToPeerRefs(remote: Remote): PeerRefs {
|
||||
const refs: Record<string, string> = {};
|
||||
|
||||
for (const [name, oid] of Object.entries(remote.heads)) {
|
||||
refs[`refs/heads/${name}`] = oid;
|
||||
refs[`${REFS_HEADS}${name}`] = oid;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -979,8 +980,8 @@ function canonicalBranchMap(repo: Repo): Record<string, string> {
|
||||
}
|
||||
|
||||
for (const [refName, oid] of canonicalOids(repo.refs)) {
|
||||
const shortName = refName.startsWith("refs/heads/")
|
||||
? refName.slice("refs/heads/".length)
|
||||
const shortName = refName.startsWith(REFS_HEADS)
|
||||
? refName.slice(REFS_HEADS.length)
|
||||
: refName.startsWith("refs/tags/")
|
||||
? refName.slice("refs/tags/".length)
|
||||
: refName;
|
||||
|
||||
Reference in New Issue
Block a user