Refactor commit and diff types, updates outdated diff types

- Adds support for modified moved files see https://github.com/radicle-dev/radicle-git/pull/141

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez
2023-09-19 07:49:36 +02:00
committed by Rūdolfs Ošiņš
parent afab097d09
commit ca6762fc95
5 changed files with 161 additions and 136 deletions
-4
View File
@@ -11,8 +11,6 @@ import type {
Commit,
CommitHeader,
Diff,
DiffAddedDeletedModifiedChangeset,
DiffCopiedMovedChangeset,
HunkLine,
} from "./lib/project/commit.js";
import type { Issue, IssueState } from "./lib/project/issue.js";
@@ -44,8 +42,6 @@ export type {
Commit,
CommitHeader,
Diff,
DiffAddedDeletedModifiedChangeset,
DiffCopiedMovedChangeset,
DiffResponse,
HunkLine,
Issue,
+106 -102
View File
@@ -1,171 +1,175 @@
import type { ZodSchema } from "zod";
import { array, literal, number, object, string, union } from "zod";
import type { z } from "zod";
import { array, literal, number, object, optional, string, union } from "zod";
interface GitPerson {
name: string;
email: string;
}
export { commitHeaderSchema, diffSchema, commitSchema, commitsSchema };
export type {
Commits,
HunkLine,
Commit,
DiffFile,
Diff,
DiffCopiedChangeset,
DiffMovedChangeset,
CommitHeader,
DiffAddedChangeset,
DiffDeletedChangeset,
DiffModifiedChangeset,
DiffContent,
};
const gitPersonSchema = object({
name: string(),
email: string(),
}) satisfies ZodSchema<GitPerson>;
});
export interface CommitHeader {
id: string;
author: GitPerson;
summary: string;
description: string;
parents: string[];
committer: GitPerson & { time: number };
}
type CommitHeader = z.infer<typeof commitHeaderSchema>;
export const commitHeaderSchema = object({
const commitHeaderSchema = object({
id: string(),
author: gitPersonSchema,
summary: string(),
description: string(),
parents: array(string()),
committer: gitPersonSchema.merge(object({ time: number() })),
}) satisfies ZodSchema<CommitHeader>;
});
interface AdditionHunkLine {
line: string;
lineNo: number;
type: "addition";
}
type AdditionHunkLine = z.infer<typeof additionHunkLineSchema>;
const additionHunkLineSchema = object({
line: string(),
lineNo: number(),
type: literal("addition"),
}) satisfies ZodSchema<AdditionHunkLine>;
});
interface DeletionHunkLine {
line: string;
lineNo: number;
type: "deletion";
}
type DeletionHunkLine = z.infer<typeof deletionHunkLineSchema>;
const deletionHunkLineSchema = object({
line: string(),
lineNo: number(),
type: literal("deletion"),
}) satisfies ZodSchema<DeletionHunkLine>;
});
interface ContextHunkLine {
line: string;
lineNoNew: number;
lineNoOld: number;
type: "context";
}
type DiffFile = z.infer<typeof diffFileSchema>;
const diffFileSchema = object({
oid: string(),
mode: union([
literal("blob"),
literal("blobExecutable"),
literal("tree"),
literal("link"),
literal("commit"),
]),
});
type ContextHunkLine = z.infer<typeof contextHunkLineSchema>;
const contextHunkLineSchema = object({
line: string(),
lineNoNew: number(),
lineNoOld: number(),
type: literal("context"),
}) satisfies ZodSchema<ContextHunkLine>;
});
export type HunkLine = AdditionHunkLine | DeletionHunkLine | ContextHunkLine;
type HunkLine = AdditionHunkLine | DeletionHunkLine | ContextHunkLine;
const hunkLineSchema = union([
additionHunkLineSchema,
deletionHunkLineSchema,
contextHunkLineSchema,
]) satisfies ZodSchema<HunkLine>;
interface ChangesetHunk {
header: string;
lines: HunkLine[];
}
]);
const changesetHunkSchema = object({
header: string(),
lines: array(hunkLineSchema),
}) satisfies ZodSchema<ChangesetHunk>;
});
export interface DiffAddedDeletedModifiedChangeset {
path: string;
diff: {
type: "plain" | "binary" | "empty";
hunks: ChangesetHunk[];
eof: "noneMissing" | "oldMissing" | "newMissing" | "bothMissing";
};
}
type DiffContent = z.infer<typeof diffContentSchema>;
const diffAddedDeletedModifiedChangesetSchema = object({
const diffContentSchema = object({
type: union([literal("plain"), literal("binary"), literal("empty")]),
hunks: array(changesetHunkSchema),
eof: union([
literal("noneMissing"),
literal("oldMissing"),
literal("newMissing"),
literal("bothMissing"),
]),
});
type DiffAddedChangeset = z.infer<typeof diffAddedChangesetSchema>;
const diffAddedChangesetSchema = object({
path: string(),
diff: object({
type: union([literal("plain"), literal("binary"), literal("empty")]),
hunks: array(changesetHunkSchema),
eof: union([
literal("noneMissing"),
literal("oldMissing"),
literal("newMissing"),
literal("bothMissing"),
]),
}),
}) satisfies ZodSchema<DiffAddedDeletedModifiedChangeset>;
diff: diffContentSchema,
new: diffFileSchema,
});
export interface DiffCopiedMovedChangeset {
newPath: string;
oldPath: string;
}
type DiffDeletedChangeset = z.infer<typeof diffDeletedChangesetSchema>;
const diffCopiedMovedChangesetSchema = object({
const diffDeletedChangesetSchema = object({
path: string(),
diff: diffContentSchema,
old: diffFileSchema,
});
type DiffModifiedChangeset = z.infer<typeof diffModifiedChangesetSchema>;
const diffModifiedChangesetSchema = object({
path: string(),
diff: diffContentSchema,
new: diffFileSchema,
old: diffFileSchema,
});
type DiffCopiedChangeset = z.infer<typeof diffCopiedChangesetSchema>;
const diffCopiedChangesetSchema = object({
newPath: string(),
oldPath: string(),
}) satisfies ZodSchema<DiffCopiedMovedChangeset>;
});
export interface Diff {
added: DiffAddedDeletedModifiedChangeset[];
deleted: DiffAddedDeletedModifiedChangeset[];
moved: DiffCopiedMovedChangeset[];
copied: DiffCopiedMovedChangeset[];
modified: DiffAddedDeletedModifiedChangeset[];
stats: {
filesChanged: number;
insertions: number;
deletions: number;
};
}
type DiffMovedChangeset = z.infer<typeof diffMovedChangesetSchema>;
export const diffSchema = object({
added: array(diffAddedDeletedModifiedChangesetSchema),
deleted: array(diffAddedDeletedModifiedChangesetSchema),
moved: array(diffCopiedMovedChangesetSchema),
copied: array(diffCopiedMovedChangesetSchema),
modified: array(diffAddedDeletedModifiedChangesetSchema),
const diffMovedChangesetSchema = diffCopiedChangesetSchema.merge(
object({
old: optional(diffFileSchema),
new: optional(diffFileSchema),
diff: optional(diffContentSchema),
}),
);
type Diff = z.infer<typeof diffSchema>;
const diffSchema = object({
added: array(diffAddedChangesetSchema),
deleted: array(diffDeletedChangesetSchema),
modified: array(diffModifiedChangesetSchema),
moved: array(diffMovedChangesetSchema),
copied: array(diffCopiedChangesetSchema),
stats: object({
filesChanged: number(),
insertions: number(),
deletions: number(),
}),
}) satisfies ZodSchema<Diff>;
});
export interface Commit {
commit: CommitHeader;
diff: Diff;
branches: string[];
}
type Commit = z.infer<typeof commitSchema>;
export const commitSchema = object({
const commitSchema = object({
commit: commitHeaderSchema,
diff: diffSchema,
branches: array(string()),
}) satisfies ZodSchema<Commit>;
});
export interface Commits {
commits: Commit[];
stats: { commits: number; branches: number; contributors: number };
}
type Commits = z.infer<typeof commitsSchema>;
export const commitsSchema = object({
const commitsSchema = object({
commits: array(commitSchema),
stats: object({
commits: number(),
branches: number(),
contributors: number(),
}),
}) satisfies ZodSchema<Commits>;
});
+33 -5
View File
@@ -72,25 +72,53 @@
<FileDiff
{projectId}
{baseUrl}
{file}
{revision}
filePath={file.path}
fileDiff={file.diff}
headerBadgeCaption="added" />
{/each}
{#each diff.deleted as file}
<FileDiff
{projectId}
{baseUrl}
{file}
{revision}
filePath={file.path}
fileDiff={file.diff}
headerBadgeCaption="deleted" />
{/each}
{#each diff.modified as file}
<FileDiff {projectId} {baseUrl} {file} {revision} />
<FileDiff
{projectId}
{baseUrl}
{revision}
filePath={file.path}
fileDiff={file.diff} />
{/each}
{#each diff.moved as file}
<FileLocationChange {projectId} {baseUrl} {file} {revision} mode="moved" />
{#if file.diff}
<FileDiff
{projectId}
{baseUrl}
{revision}
filePath={file.newPath}
fileDiff={file.diff} />
{:else}
<FileLocationChange
{projectId}
{baseUrl}
{revision}
newPath={file.newPath}
oldPath={file.oldPath}
mode="moved" />
{/if}
{/each}
{#each diff.copied as file}
<FileLocationChange {projectId} {baseUrl} {file} {revision} mode="copied" />
<FileLocationChange
{projectId}
{baseUrl}
{revision}
newPath={file.newPath}
oldPath={file.oldPath}
mode="copied" />
{/each}
</div>
+16 -20
View File
@@ -1,16 +1,14 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import type {
BaseUrl,
DiffAddedDeletedModifiedChangeset,
HunkLine,
} from "@httpd-client";
import type { DiffContent } from "@httpd-client/lib/project/commit";
import type { BaseUrl, HunkLine } from "@httpd-client";
import Badge from "@app/components/Badge.svelte";
import Icon from "@app/components/Icon.svelte";
import Link from "@app/components/Link.svelte";
export let file: DiffAddedDeletedModifiedChangeset;
export let filePath: string;
export let fileDiff: DiffContent;
export let revision: string;
export let headerBadgeCaption: "added" | "deleted" | undefined = undefined;
export let baseUrl: BaseUrl;
@@ -28,11 +26,9 @@
if (selection) {
document
.getElementById(
[
file.path,
"H" + selection.startHunk,
"L" + selection.startLine,
].join("-"),
[filePath, "H" + selection.startHunk, "L" + selection.startLine].join(
"-",
),
)
?.scrollIntoView();
}
@@ -57,7 +53,7 @@
function updateSelection() {
const fragment = window.location.hash.substring(1);
const match = fragment.match(/(.+):H(\d+)L(\d+)(H(\d+)L(\d+))?/);
if (match && match[1] === file.path) {
if (match && match[1] === filePath) {
selection = {
startHunk: parseInt(match[2]),
startLine: parseInt(match[3]),
@@ -139,7 +135,7 @@
lineIdx: number,
event: MouseEvent,
): string {
const path = file.path;
const path = filePath;
// single line selection
if (!event.shiftKey) {
return path + ":H" + hunkIdx + "L" + lineIdx;
@@ -317,7 +313,7 @@
}
</style>
<div id={file.path} class="wrapper">
<div id={filePath} class="wrapper">
<header class="header">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -329,7 +325,7 @@
{/if}
</div>
<div class="actions">
<p class="txt-regular">{file.path}</p>
<p class="txt-regular">{filePath}</p>
{#if headerBadgeCaption === "added"}
<Badge variant="positive">added</Badge>
{:else if headerBadgeCaption === "deleted"}
@@ -342,7 +338,7 @@
resource: "project.source",
project: projectId,
node: baseUrl,
path: file.path,
path: filePath,
revision,
}}>
<Icon name="browse" />
@@ -351,10 +347,10 @@
</header>
{#if !collapsed}
<main>
{#if file.diff.type === "plain"}
{#if file.diff.hunks.length > 0}
{#if fileDiff.type === "plain"}
{#if fileDiff.hunks.length > 0}
<table class="diff" data-file-diff-select>
{#each file.diff.hunks as hunk, hunkIdx}
{#each fileDiff.hunks as hunk, hunkIdx}
<tr
class="diff-line hunk-header"
class:selected={hunkHeaderSelected(selection, hunkIdx)}>
@@ -370,7 +366,7 @@
class={`diff-line type-${line.type}`}
class:selected={isLineSelected(selection, hunkIdx, lineIdx)}>
<td
id={[file.path, "H" + hunkIdx, "L" + lineIdx].join("-")}
id={[filePath, "H" + hunkIdx, "L" + lineIdx].join("-")}
class="diff-line-number left"
on:click={e => selectLine(hunkIdx, lineIdx, e)}>
<div class="selection-indicator" />
@@ -1,11 +1,12 @@
<script lang="ts">
import type { BaseUrl, DiffCopiedMovedChangeset } from "@httpd-client";
import type { BaseUrl } from "@httpd-client";
import Badge from "@app/components/Badge.svelte";
import Icon from "@app/components/Icon.svelte";
import Link from "@app/components/Link.svelte";
export let file: DiffCopiedMovedChangeset;
export let newPath: string;
export let oldPath: string;
export let revision: string;
export let mode: "moved" | "copied";
export let baseUrl: BaseUrl;
@@ -40,10 +41,10 @@
}
</style>
<div id={file.newPath} class="wrapper">
<div id={newPath} class="wrapper">
<header class="header">
<div class="actions">
<p class="txt-regular">{file.oldPath} → {file.newPath}</p>
<p class="txt-regular">{oldPath} → {newPath}</p>
{#if mode === "moved"}
<Badge variant="foreground">moved</Badge>
{:else if mode === "copied"}
@@ -56,7 +57,7 @@
resource: "project.source",
project: projectId,
node: baseUrl,
path: file.newPath,
path: newPath,
revision,
}}>
<Icon name="browse" />