feat(repository): add complete support for git diff/compare/history@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1663803924923,
+ "_generatedAtUnix": 1663839168820,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -21,6 +21,12 @@
"pathBundle": "./public/.islands/RepositoryCreateForm.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryCreateForm.bundle.js.map"
},
+ "RepositoryFilesDiffsList": {
+ "hash": "9b4c9043730e3c41c7d7f4f86b90745331cd91b2",
+ "pathSource": "./app/islands/RepositoryFilesDiffsList.tsx",
+ "pathBundle": "./public/.islands/RepositoryFilesDiffsList.bundle.js",
+ "pathSourceMap": "./public/.islands/RepositoryFilesDiffsList.bundle.js.map"
+ },
"RepositoryInitialSetup": {
"hash": "ade4c6a8e8d2b5abef10d1e4281cf6888d128b5c",
"pathSource": "./app/islands/RepositoryInitialSetup.tsx",
@@ -28,7 +34,7 @@
"pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
},
"RepositoryTreeView": {
- "hash": "f1affa1ed5c3dc2c543bf07bd799d582bb1292de",
+ "hash": "d044fd8b6334961b63150969b638b2f74addade9",
"pathSource": "./app/islands/RepositoryTreeView.tsx",
"pathBundle": "./public/.islands/RepositoryTreeView.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
@@ -60,9 +66,13 @@
"pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
},
"RepositoryCommitsLogView": {
- "hash": "2cae832cc936ac1d69fda4ad0ef1a470f1424933",
+ "hash": "9c2b20bc4bc30e627457e6d48b353ff0d9816b09",
"pathSource": "./app/views/repository/RepositoryCommitsLogView.tsx"
},
+ "RepositoryCompareView": {
+ "hash": "2e9f1d15bbd826f1a6d89bb9d19ae6172062071c",
+ "pathSource": "./app/views/repository/RepositoryCompareView.tsx"
+ },
"RepositoryCreateView": {
"hash": "f141b710674ecd55db0fa429ab73901e30001a39",
"pathSource": "./app/views/repository/RepositoryCreateView.tsx"
@@ -0,0 +1,50 @@
+// 1st-party
+import { ReqHandler } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { User } from "@prisma/client";
+import { AppRoute, AppRoutesParams } from "app/routes";
+// app services
+import { makeOrganizationService } from "../../services/organization";
+import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
+// app views
+import RepositoryCompareView, {
+ RepositoryCompareViewProps,
+} from "../../views/repository/RepositoryCompareView";
+
+const getRepositoryCompareView: ReqHandler = async (request, reply) => {
+ const { curr_user_uid } = request.session.data;
+ const { orgSlug, repoSlug, refA, refB } =
+ request.params as AppRoutesParams[AppRoute.REPOSITORY_COMPARE]["params"];
+
+ const orgService = makeOrganizationService({ request });
+ const repoService = makeRepositoryService({ request });
+ const userService = makeUsersService({ request });
+
+ const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
+ const repo = await repoService.getRepository(orgSlug, repoSlug);
+
+ if (parentOrg == null || repo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ let currentUser: null | User = null;
+
+ if (curr_user_uid != null) {
+ currentUser = await userService.getUserById(curr_user_uid);
+ }
+
+ const filesDiffs = await repoService.getRepositoryRefDiff(repo, refA, refB);
+
+ const reqHandler = reply.makeRequestHandler(request, reply);
+ reqHandler<RepositoryCompareViewProps>(RepositoryCompareView.name, {
+ currentUser,
+ filesDiffs,
+ parentOrg,
+ repo,
+ refA,
+ refB,
+ });
+};
+
+export default getRepositoryCompareView;
@@ -1,5 +1,6 @@
import { default as getRepositoryBrowserView } from "./getRepositoryBrowserView";
import { default as getRepositoryCommitsLogView } from "./getRepositoryCommitsLogView";
+import { default as getRepositoryCompareView } from "./getRepositoryCompareView";
import { default as getRepositoryCreateView } from "./getRepositoryCreateView";
import { default as getRepositoryDetailsView } from "./getRepositoryDetailsView";
import { default as getRepositoryExploreView } from "./getRepositoryExploreView";
@@ -8,6 +9,7 @@ import { default as postRepositoryCreateAction } from "./postRepositoryCreateAct
export const RepositoryController = {
getRepositoryBrowserView,
getRepositoryCommitsLogView,
+ getRepositoryCompareView,
getRepositoryCreateView,
getRepositoryDetailsView,
getRepositoryExploreView,
@@ -0,0 +1,83 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// app
+import type {
+ RepositoryFileDiff,
+ RepositoryFileDiffChunk,
+ WithThemeSchemeProp,
+} from "../types";
+import { Code, Card, Grid, getThemedCodeCss } from "../components";
+
+export interface RepositoryFilesDiffsList {
+ filesDiffs: RepositoryFileDiff[];
+ orgSlug: string;
+ repoSlug: string;
+ commitHash: string;
+}
+
+const getChunkContent = (chunk: RepositoryFileDiffChunk): string => {
+ let sb = [] as string[];
+ sb.push(chunk.content);
+ chunk.changes.forEach((change) => sb.push(change.content));
+ return `${sb.join("\n")}\n`;
+};
+
+const RepositoryFilesDiffsList: ReactIsland<
+ RepositoryFilesDiffsList & WithThemeSchemeProp
+> = ({ commitHash, filesDiffs, orgSlug, repoSlug, themeScheme }) => (
+ <>
+ {getThemedCodeCss(themeScheme)}
+ <div style={{ marginTop: 24 }}>
+ {filesDiffs.map(({ chunks, ...diff }) => (
+ <Card
+ key={[diff.from, diff.to].join(":")}
+ style={{ marginTop: 16 }}
+ themeScheme={themeScheme}
+ >
+ <Grid.Col fluid nowrap>
+ <Grid.Row fluid nowrap>
+ <strong>{diff.from}</strong>
+ <span style={{ marginLeft: 16 }}>{" -> "}</span>
+ <strong style={{ marginLeft: 16 }}>{diff.to}</strong>
+ </Grid.Row>
+ <Grid.Row
+ fluid
+ nowrap
+ alignItems={"center"}
+ style={{ marginTop: 8 }}
+ >
+ <div>
+ <strong>additions:</strong> <span>{diff.additions}</span>
+ </div>
+ <div style={{ marginLeft: 16 }}>
+ <strong>deletions:</strong> <span>{diff.deletions}</span>
+ </div>
+ <div style={{ marginLeft: 16 }}>
+ <a
+ href={`/${orgSlug}/${repoSlug}/${commitHash}/tree/${diff.to}`}
+ >
+ View file (current ref)
+ </a>
+ </div>
+ </Grid.Row>
+ </Grid.Col>
+ <div>
+ {chunks.map((chunk, idx) => (
+ <Code
+ key={[idx, chunk.content].join(":")}
+ code={getChunkContent(chunk)}
+ language={"diff"}
+ themeScheme={themeScheme}
+ />
+ ))}
+ </div>
+ </Card>
+ ))}
+ </div>
+ </>
+);
+
+RepositoryFilesDiffsList.displayName = "RepositoryFilesDiffsList";
+export default RepositoryFilesDiffsList;
@@ -60,12 +60,16 @@ const RepositoryTreeView: ReactIsland<RepositoryTreeViewProps> = ({
{" ∙ "}
<span>{lastCommit.subject}</span>
{" - "}
- <span>
- {lastCommit.abbreviated_commit}
- {lastCommit.abbreviated_parent.trim() != ""
- ? ` ∙ parent ${lastCommit.abbreviated_parent}`
- : ""}
- </span>
+ <a
+ href={`/${orgSlug}/${repoSlug}/compare/${lastCommit.abbreviated_parent}..${lastCommit.abbreviated_commit}`}
+ >
+ <span>
+ {lastCommit.abbreviated_commit}
+ {lastCommit.abbreviated_parent.trim() != ""
+ ? ` (parent ${lastCommit.abbreviated_parent})`
+ : ""}
+ </span>
+ </a>
{" ∙ "}
<span>{new Date(lastCommit.author.date).toUTCString()}</span>
<a href={`/${orgSlug}/${repoSlug}/commits`}>History</a>
@@ -29,6 +29,7 @@ export enum AppRoute {
ORGANIZATION_DETAILS = "organization.details",
REPOSITORY_EXPLORE = "repository.explore",
REPOSITORY_COMMITS_LOG = "repository.commits_log",
+ REPOSITORY_COMPARE = "repository.compare",
REPOSITORY_CREATE = "repository.create",
REPOSITORY_CREATE_ACTION = "repository.create.action",
REPOSITORY_DETAILS = "repository.details",
@@ -74,6 +75,14 @@ export interface AppRoutesParams extends IRouteParams {
repoSlug: string;
};
};
+ [AppRoute.REPOSITORY_COMPARE]: {
+ params: {
+ orgSlug: string;
+ repoSlug: string;
+ refA: string;
+ refB: string;
+ };
+ };
[AppRoute.REPOSITORY_CREATE]: undefined;
[AppRoute.REPOSITORY_CREATE_ACTION]: {
body: {
@@ -188,6 +197,27 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
},
},
},
+ [AppRoute.REPOSITORY_COMPARE]: {
+ params: {
+ type: "object",
+ required: ["orgSlug", "repoSlug", "refA", "refB"],
+ additionalProperties: false,
+ properties: {
+ orgSlug: {
+ type: "string",
+ },
+ repoSlug: {
+ type: "string",
+ },
+ refA: {
+ type: "string",
+ },
+ refB: {
+ type: "string",
+ },
+ },
+ },
+ },
[AppRoute.REPOSITORY_CREATE]: undefined,
[AppRoute.REPOSITORY_CREATE_ACTION]: {
body: {
@@ -386,10 +416,16 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.REPOSITORY_COMMITS_LOG}
method={"GET"}
path={"/:orgSlug/:repoSlug/commits"}
- preHandler={loggedOrLoginRedirect}
schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMMITS_LOG]}
handler={RepositoryController.getRepositoryCommitsLogView}
/>
+ <Router.Route
+ name={AppRoute.REPOSITORY_COMPARE}
+ method={"GET"}
+ path={"/:orgSlug/:repoSlug/compare/:refA..:refB"}
+ schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMPARE]}
+ handler={RepositoryController.getRepositoryCompareView}
+ />
<Router.Route
name={AppRoute.REPOSITORY_CREATE}
method={"GET"}
@@ -58,7 +58,7 @@ const makeGetRepositoryRefDiff: ServiceMethodFactory<
});
});
- return parseDiff(gitDiffRefsResult);
+ return parseDiff(gitDiffRefsResult, { findRenames: true });
} catch (_) {
return [];
}
@@ -38,7 +38,7 @@ const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
{history.map((log) => (
<li key={log.tree}>
<a
- href={`/${parentOrg.slug}/${repo.slug}/commits/${log.commit}`}
+ href={`/${parentOrg.slug}/${repo.slug}/compare/${log.abbreviated_parent}..${log.abbreviated_commit}`}
>
<strong>{log.author.name}</strong>
{" ∙ "}
@@ -0,0 +1,60 @@
+// 1st-party
+import type { ReactView } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// generated via script[prisma:generate]
+import type { Organization, Repository, User } from "@prisma/client";
+// app
+import type { CommonProps, RepositoryFileDiff } from "../../types";
+import { Grid, Layout, PageWrapper } from "../../components";
+// app islands
+import RepositoryFilesDiffsList from "../../islands/RepositoryFilesDiffsList";
+
+export interface RepositoryCompareViewProps extends CommonProps {
+ currentUser: null | User;
+ filesDiffs: RepositoryFileDiff[];
+ parentOrg: Organization;
+ repo: Repository;
+ refA: string;
+ refB: string;
+}
+
+const RepositoryCompareView: ReactView<RepositoryCompareViewProps> = ({
+ commonProps,
+ filesDiffs,
+ parentOrg,
+ repo,
+ refA,
+ refB,
+}) => {
+ return (
+ <Layout {...commonProps}>
+ <PageWrapper>
+ <h1>
+ <a href={`/${parentOrg.slug}`}>
+ {parentOrg.displayName || parentOrg.slug}
+ </a>
+ {" / "}
+ <a href={`/${parentOrg.slug}/${repo.slug}`}>
+ {repo.displayName || repo.slug}
+ </a>
+ {" / "}
+ {refA}..{refB}
+ </h1>
+ {/*getThemedCodeCss(commonProps.themeScheme)*/}
+ <Grid.Col fluid data-islandid={`${RepositoryFilesDiffsList.name}$$0`}>
+ <RepositoryFilesDiffsList
+ filesDiffs={filesDiffs}
+ themeScheme={commonProps.themeScheme}
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commitHash={refB}
+ />
+ </Grid.Col>
+ </PageWrapper>
+ </Layout>
+ );
+};
+
+RepositoryCompareView.displayName = "RepositoryCompareView";
+export default RepositoryCompareView;
@@ -2,6 +2,9 @@ import type { RepositoryFileDiff } from "../app/types";
declare module "diffparser";
-export declare const parse: (diff: string) => RepositoryFileDiff[];
+export declare const parse: (
+ diff: string,
+ opts?: { findRenames: boolean }
+) => RepositoryFileDiff[];
export default parse;