feat(repository): add a "RepositoryShowObjectView" + logic@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1664028999133,
+ "_generatedAtUnix": 1664142366214,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -15,6 +15,12 @@
"pathBundle": "./public/.islands/RepositoriesList.bundle.js",
"pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"
},
+ "RepositoryCommitSummaryLine": {
+ "hash": "e248dcfd52a6086ffa54f7db222229291c5a227a",
+ "pathSource": "./app/islands/RepositoryCommitSummaryLine.tsx",
+ "pathBundle": "./public/.islands/RepositoryCommitSummaryLine.bundle.js",
+ "pathSourceMap": "./public/.islands/RepositoryCommitSummaryLine.bundle.js.map"
+ },
"RepositoryCreateForm": {
"hash": "13bc29f8548d0df057f56a78bb6f2f6b31cb7d31",
"pathSource": "./app/islands/RepositoryCreateForm.tsx",
@@ -22,7 +28,7 @@
"pathSourceMap": "./public/.islands/RepositoryCreateForm.bundle.js.map"
},
"RepositoryFilesDiffsList": {
- "hash": "ab1579b7b0bfbe3f8af63352960a7b4c0e987971",
+ "hash": "003091766fc95fa365523379793b24e14d7ed61b",
"pathSource": "./app/islands/RepositoryFilesDiffsList.tsx",
"pathBundle": "./public/.islands/RepositoryFilesDiffsList.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryFilesDiffsList.bundle.js.map"
@@ -34,7 +40,7 @@
"pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
},
"RepositoryTreeView": {
- "hash": "ee5b76a50148c328f479d60cf5f4ac3178ebc72b",
+ "hash": "09d177e71de6bea5a60a919bfb6dcf51d4ea7cc3",
"pathSource": "./app/islands/RepositoryTreeView.tsx",
"pathBundle": "./public/.islands/RepositoryTreeView.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
@@ -62,11 +68,11 @@
"pathSource": "./app/views/organization/OrganizationDetailsView.tsx"
},
"RepositoryBrowserView": {
- "hash": "0d530206f5c3ae6a5122c0b29a1a0123934ce122",
+ "hash": "e013bb2954f5e3f0b1d370e7f99f87c0e6121653",
"pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
},
"RepositoryCommitsLogView": {
- "hash": "cfb132fe26e998535c0c022d4796f35fd7913812",
+ "hash": "edb26387e0cab7e58978c25599ab857b9ece0d6d",
"pathSource": "./app/views/repository/RepositoryCommitsLogView.tsx"
},
"RepositoryCompareView": {
@@ -78,13 +84,17 @@
"pathSource": "./app/views/repository/RepositoryCreateView.tsx"
},
"RepositoryDetailsView": {
- "hash": "b15fff7aad6bc1d2636ed58be26492552d7d9946",
+ "hash": "715c846b8fbaee1f7186e2dc64d0535d0b738880",
"pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
},
"RepositoryExploreView": {
"hash": "d3cd2213b77362ecf1dacaf408987d6bae744cd3",
"pathSource": "./app/views/repository/RepositoryExploreView.tsx"
},
+ "RepositoryShowObjectView": {
+ "hash": "19144b3c408ae1f0dfa04c72f6737ffaacb6e8cb",
+ "pathSource": "./app/views/repository/RepositoryShowObjectView.tsx"
+ },
"UserDashboardView": {
"hash": "616b3877b11bb101b48391f25ed83ad08f0c2df8",
"pathSource": "./app/views/user/UserDashboardView.tsx"
@@ -0,0 +1,68 @@
+// 1st-party
+import type { ReqHandler } from "@ethicdevs/react-monolith";
+import { ResourceVisibility } from "@prisma/client";
+// app
+import { AppRoute, AppRoutesParams } from "../../routes";
+// app services
+import { makeOrganizationService } from "../../services/organization";
+import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
+// app views
+import RepositoryShowObjectView, {
+ RepositoryShowObjectViewProps,
+} from "../../views/repository/RepositoryShowObjectView";
+
+const getRepositoryShowObjectView: ReqHandler = async (request, reply) => {
+ const { orgSlug, repoSlug, objectId } =
+ request.params as AppRoutesParams[AppRoute.REPOSITORY_SHOW_OBJECT]["params"];
+
+ const orgService = makeOrganizationService({ request });
+ const repoService = makeRepositoryService({ request });
+ const usersService = makeUsersService({ request });
+
+ const currentUser =
+ request.session.data.authenticated &&
+ request.session.data.curr_user_uid != null
+ ? await usersService.getUserById(request.session.data.curr_user_uid)
+ : null;
+
+ const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
+ const repo = await repoService.getRepository(orgSlug, repoSlug);
+
+ if (parentOrg == null || repo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ if (repo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, repo)) === false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
+ const gitObject = await repoService.getRepositoryObject(repo, objectId);
+ const gitObjectDiffs =
+ gitObject != null
+ ? await repoService.getRepositoryRefDiff(
+ repo,
+ gitObject.parent,
+ gitObject.commit
+ )
+ : null;
+
+ const reqHandler = reply.makeRequestHandler(request, reply);
+ return reqHandler<RepositoryShowObjectViewProps>(
+ RepositoryShowObjectView.name,
+ {
+ parentOrg,
+ repo,
+ gitObject,
+ gitObjectDiffs,
+ }
+ );
+};
+
+export default getRepositoryShowObjectView;
@@ -4,6 +4,7 @@ import { default as getRepositoryCompareView } from "./getRepositoryCompareView"
import { default as getRepositoryCreateView } from "./getRepositoryCreateView";
import { default as getRepositoryDetailsView } from "./getRepositoryDetailsView";
import { default as getRepositoryExploreView } from "./getRepositoryExploreView";
+import { default as getRepositoryShowObjectView } from "./getRepositoryShowObjectView";
import { default as postRepositoryCreateAction } from "./postRepositoryCreateAction";
export const RepositoryController = {
@@ -13,5 +14,6 @@ export const RepositoryController = {
getRepositoryCreateView,
getRepositoryDetailsView,
getRepositoryExploreView,
+ getRepositoryShowObjectView,
postRepositoryCreateAction,
};
@@ -0,0 +1,44 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// app
+import type { RepositoryObject } from "../types";
+import { Grid } from "../components";
+
+export interface RepositoryCommitSummaryLineProps {
+ orgSlug: string;
+ repoSlug: string;
+ commit: RepositoryObject;
+}
+
+const RepositoryCommitSummaryLine: ReactIsland<RepositoryCommitSummaryLineProps> =
+ ({ orgSlug, repoSlug, commit }) => {
+ return (
+ <Grid.Row gap={8} alignItems={"flex-start"}>
+ <strong>{commit.author.name}</strong>
+ {" ∙ "}
+ <span style={{ flex: 1 }}>
+ <a href={`/${orgSlug}/${repoSlug}/show/${commit.commit}`}>
+ {commit.subject}
+ </a>
+ </span>
+ {" ∙ "}
+ <span>
+ <a href={`/${orgSlug}/${repoSlug}/show/${commit.commit}`}>
+ {commit.abbreviated_commit}
+ </a>
+ {commit.abbreviated_parent.trim() != "" ? (
+ <a href={`/${orgSlug}/${repoSlug}/show/${commit.parent}`}>
+ {` (parent ${commit.abbreviated_parent})`}
+ </a>
+ ) : null}
+ </span>
+ {" ∙ "}
+ <span>{new Date(commit.author.date).toLocaleDateString()}</span>
+ </Grid.Row>
+ );
+ };
+
+RepositoryCommitSummaryLine.displayName = "RepositoryCommitSummaryLine";
+export default RepositoryCommitSummaryLine;
@@ -29,11 +29,11 @@ const RepositoryFilesDiffsList: ReactIsland<
> = ({ commitHash, filesDiffs, orgSlug, repoSlug, themeScheme }) => (
<>
{getThemedCodeCss(themeScheme)}
- <div style={{ marginTop: 24, width: "100%" }}>
- {filesDiffs.map(({ chunks, ...diff }) => (
+ <Grid.Col fluid>
+ {filesDiffs.map(({ chunks, ...diff }, idx) => (
<Card
key={[diff.from, diff.to].join(":")}
- style={{ marginTop: 16, width: "100%" }}
+ style={{ marginTop: idx > 0 ? 16 : 0, width: "100%" }}
themeScheme={themeScheme}
>
<Grid.Col fluid nowrap>
@@ -69,7 +69,7 @@ const RepositoryFilesDiffsList: ReactIsland<
</div>
</Grid.Row>
</Grid.Col>
- <div style={{ width: "100%" }}>
+ <Grid.Col fluid style={{ marginTop: 8 }}>
{chunks.map((chunk, idx) => (
<Code
key={[idx, chunk.content].join(":")}
@@ -78,10 +78,10 @@ const RepositoryFilesDiffsList: ReactIsland<
themeScheme={themeScheme}
/>
))}
- </div>
+ </Grid.Col>
</Card>
))}
- </div>
+ </Grid.Col>
</>
);
@@ -6,6 +6,7 @@ import styled from "styled-components";
// app
import type { RepositoryFile, RepositoryLog } from "../types";
import { Grid } from "../components";
+// import RepositoryCommitSummaryLine from "./RepositoryCommitSummaryLine";
export interface RepositoryTreeViewProps {
currentPath: string;
@@ -19,7 +20,7 @@ export interface RepositoryTreeViewProps {
const RepositoryTreeView: ReactIsland<RepositoryTreeViewProps> = ({
currentPath,
currentRef,
- lastCommit,
+ // lastCommit,
orgSlug,
repoFiles,
repoSlug,
@@ -61,30 +62,6 @@ const RepositoryTreeView: ReactIsland<RepositoryTreeViewProps> = ({
return (
<StyledRepositoryTreeViewContainer>
<Grid.Col fluid>
- <Grid.Row gap={8} alignItems={"flex-start"}>
- <strong>{lastCommit.author.name}</strong>
- {" ∙ "}
- <span style={{ flex: 1 }}>
- <a
- href={`/${orgSlug}/${repoSlug}/compare/${lastCommit.abbreviated_parent}..${lastCommit.abbreviated_commit}`}
- >
- {lastCommit.subject}
- </a>
- </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).toLocaleDateString()}</span>
- </Grid.Row>
<Grid.Row
gap={8}
alignItems={"center"}
@@ -27,13 +27,14 @@ export enum AppRoute {
USER_DASHBOARD = "user.dashboard",
USER_DETAILS = "user.details",
ORGANIZATION_DETAILS = "organization.details",
- REPOSITORY_EXPLORE = "repository.explore",
+ REPOSITORY_BROWSER = "repository.browser",
REPOSITORY_COMMITS_LOG = "repository.commits_log",
REPOSITORY_COMPARE = "repository.compare",
REPOSITORY_CREATE = "repository.create",
REPOSITORY_CREATE_ACTION = "repository.create.action",
REPOSITORY_DETAILS = "repository.details",
- REPOSITORY_BROWSER = "repository.browser",
+ REPOSITORY_EXPLORE = "repository.explore",
+ REPOSITORY_SHOW_OBJECT = "repository.show_object",
}
export interface AppRoutesParams extends IRouteParams {
@@ -113,6 +114,13 @@ export interface AppRoutesParams extends IRouteParams {
"*": string;
};
};
+ [AppRoute.REPOSITORY_SHOW_OBJECT]: {
+ params: {
+ orgSlug: string;
+ repoSlug: string;
+ objectId: string;
+ };
+ };
}
export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
@@ -319,6 +327,24 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
},
},
},
+ [AppRoute.REPOSITORY_SHOW_OBJECT]: {
+ params: {
+ type: "object",
+ required: ["orgSlug", "repoSlug", "objectId"],
+ additionalProperties: false,
+ properties: {
+ orgSlug: {
+ type: "string",
+ },
+ repoSlug: {
+ type: "string",
+ },
+ objectId: {
+ type: "string",
+ },
+ },
+ },
+ },
};
const RootAppRouter: AppRouter = () => {
@@ -415,7 +441,7 @@ const RootAppRouter: AppRouter = () => {
<Router.Route
name={AppRoute.REPOSITORY_COMMITS_LOG}
method={"GET"}
- path={"/:orgSlug/:repoSlug/commits"}
+ path={"/:orgSlug/:repoSlug/commits"} // TODO: add :ref before /commits + adaptations to links
schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMMITS_LOG]}
handler={RepositoryController.getRepositoryCommitsLogView}
/>
@@ -469,6 +495,13 @@ const RootAppRouter: AppRouter = () => {
schema={AppRoutesSchemas[AppRoute.REPOSITORY_BROWSER]}
handler={RepositoryController.getRepositoryBrowserView}
/>
+ <Router.Route
+ name={AppRoute.REPOSITORY_SHOW_OBJECT}
+ method={"GET"}
+ path={"/:orgSlug/:repoSlug/show/:objectId"}
+ schema={AppRoutesSchemas[AppRoute.REPOSITORY_SHOW_OBJECT]}
+ handler={RepositoryController.getRepositoryShowObjectView}
+ />
</Router.Group>
</Router.Root>
);
@@ -15,8 +15,8 @@ const makeOnPushEvent: ServiceMethodFactory<
return ({ data, message }) => {
message.write("\n");
if (data.packType === GitServer.PackType.RECEIVE) {
- // client has done something like "git push"
- // it is uploading, we are receiving
+ // client has done something like "git push" it is uploading
+ // server is receiving
console.log("receive-pack");
const [orgSlug, repoSlug] = data.repoSlug.split("/");
@@ -50,16 +50,14 @@ const makeOnPushEvent: ServiceMethodFactory<
if (data.payload != null) {
message.write("🖖 See the details of your push at:\n");
if (data.payload.refType === "head") {
- message.write(
- `🧲 ${baseUrl}/compare/HEAD%5E..${data.payload.commitId}\n`
- );
+ message.write(`🧲 ${baseUrl}/show/${data.payload.commitId}\n`);
} else if (data.payload.refType === "tag") {
message.write(`➡️ ${baseUrl}/tags/${data.payload.refName}\n`);
}
}
} else if (data.packType === GitServer.PackType.UPLOAD) {
- // client has done something like "git clone"
- // it is receiving, we are uploading
+ // client has done something like "git clone" it is receiving
+ // server is uploading
console.log("upload-pack");
message.write(`🖖 Welcome at GitFOSS ${data.username}!\n`);
}
@@ -0,0 +1,104 @@
+// std
+import { existsSync } from "node:fs";
+import { spawn } from "node:child_process";
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import type { Repository } from "@prisma/client";
+// app
+import { Env } from "../../env";
+// service
+import type { RepositoryObject } from "../../types";
+import type { RepositoryServiceDeps } from "./types";
+
+const GIT_ESCAPED_JSON_FIELDS_WITH_UNESCAPED_NEWLINES_REGEXP =
+ / \^@\^[a-z_]+\^@\^: \^@\^([^\^]+|)\^@\^,?/gim;
+
+const makeGetRepositoryObject: ServiceMethodFactory<
+ RepositoryServiceDeps,
+ [Repository, string],
+ Promise<RepositoryObject | null>
+> = ({ request }) => {
+ return async (repo, objectId) => {
+ const parentOrg = await request.prisma.organization.findUnique({
+ where: {
+ id: repo.organizationId,
+ },
+ });
+
+ if (parentOrg == null) {
+ throw new Error(
+ `Could not find the parent organization for project "${repo.slug}".`
+ );
+ }
+
+ const repoPath = `${Env.GIT_REPOSITORIES_ROOT}/${parentOrg.slug}/${repo.slug}.git`;
+
+ if (existsSync(repoPath) === false) {
+ throw new Error(`Could not find a valid git repository at: ${repoPath}`);
+ }
+
+ var format =
+ "{%n ^@^commit^@^: ^@^%H^@^,%n ^@^abbreviated_commit^@^: ^@^%h^@^,%n ^@^tree^@^: ^@^%T^@^,%n ^@^abbreviated_tree^@^: ^@^%t^@^,%n ^@^parent^@^: ^@^%P^@^,%n ^@^abbreviated_parent^@^: ^@^%p^@^,%n ^@^refs^@^: ^@^%D^@^,%n ^@^encoding^@^: ^@^%e^@^,%n ^@^subject^@^: ^@^%s^@^,%n ^@^sanitized_subject_line^@^: ^@^%f^@^,%n ^@^body^@^: ^@^%b^@^,%n ^@^commit_notes^@^: ^@^%N^@^,%n ^@^verification_flag^@^: ^@^%G?^@^,%n ^@^signer^@^: ^@^%GS^@^,%n ^@^signer_key^@^: ^@^%GK^@^,%n ^@^author^@^: {%n ^@^name^@^: ^@^%aN^@^,%n ^@^email^@^: ^@^%aE^@^,%n ^@^date^@^: ^@^%aD^@^%n },%n ^@^commiter^@^: {%n ^@^name^@^: ^@^%cN^@^,%n ^@^email^@^: ^@^%cE^@^,%n ^@^date^@^: ^@^%cD^@^%n }%n},";
+
+ const args = [
+ "show",
+ "--quiet",
+ `--pretty=format:${format}`,
+ objectId,
+ ].filter((x): x is string => x != null);
+
+ const gitShowObjectProcess = spawn("git", args, {
+ cwd: repoPath,
+ });
+
+ try {
+ const gitShowObjectResult = await new Promise<RepositoryObject>(
+ (resolve, reject) => {
+ let buffer = [] as string[];
+ gitShowObjectProcess.stdout.on("data", (data) => buffer.push(data));
+ gitShowObjectProcess.stderr.on("data", (data) => {
+ reject(new Error(Buffer.from(data).toString("utf-8")));
+ });
+ gitShowObjectProcess.stdout.on("close", () => {
+ let escapedJson = buffer.join("");
+
+ // Find the fields that may contain \n for escaping them
+ const fieldsToEscape = escapedJson.match(
+ GIT_ESCAPED_JSON_FIELDS_WITH_UNESCAPED_NEWLINES_REGEXP
+ );
+
+ if (fieldsToEscape != null && Array.isArray(fieldsToEscape)) {
+ fieldsToEscape.forEach((fieldTxt) => {
+ escapedJson = escapedJson.replace(
+ fieldTxt,
+ fieldTxt.split("\n").join("\\n") // Escape unescaped newlines
+ );
+ });
+ }
+
+ escapedJson = escapedJson
+ .replace(/"/gm, '\\"') // Escape double-quotes: `"` -> `\"`
+ .replace(/\^@\^/gm, '"'); // Custom escape char `^@^` back to real double quotes `"`
+
+ // remove `,` from the end of string.
+ escapedJson = escapedJson.substring(0, escapedJson.length - 1);
+
+ try {
+ resolve(JSON.parse(escapedJson));
+ } catch (err) {
+ reject(err);
+ }
+ });
+ }
+ );
+
+ return gitShowObjectResult as RepositoryObject;
+ } catch (err) {
+ console.error("Cannot get git object", objectId, err);
+ return null;
+ }
+ };
+};
+
+export default makeGetRepositoryObject;
@@ -15,6 +15,7 @@ import { default as makeGetRepositoryFiles } from "./getRepositoryFiles";
import { default as makeGetRepositoryHead } from "./getRepositoryHead";
import { default as makeGetRepositoryHTTPCloneUrl } from "./getRepositoryHTTPCloneUrl";
import { default as makeGetRepositorySSHCloneUrl } from "./getRepositorySSHCloneUrl";
+import { default as makeGetRepositoryObject } from "./getRepositoryObject";
import { default as makeGetRepositoryRefDiff } from "./getRepositoryRefDiff";
import { default as makeGetRepositoryTags } from "./getRepositoryTags";
import { default as makeIsFileInRepositoryPath } from "./isFileInRepositoryPath";
@@ -35,6 +36,7 @@ export const makeRepositoryService = makeService<
getRepositoryHead: makeGetRepositoryHead,
getRepositoryHTTPCloneUrl: makeGetRepositoryHTTPCloneUrl,
getRepositorySSHCloneUrl: makeGetRepositorySSHCloneUrl,
+ getRepositoryObject: makeGetRepositoryObject,
getRepositoryRefDiff: makeGetRepositoryRefDiff,
getRepositoryTags: makeGetRepositoryTags,
isFileInRepositoryPath: makeIsFileInRepositoryPath,
@@ -13,6 +13,7 @@ import type {
RepositoryFileDiff,
RepositoryHead,
RepositoryLog,
+ RepositoryObject,
} from "../../types";
export interface CreateRepositoryDTO {
William Nemenchafeat(repository): add a "RepositoryShowObjectView" + logic69a27d8 (parent c829a2a)9/25/2022, 9:48:08 PMfeat(repository): add a "RepositoryShowObjectView" + logic+ 415- 73app.manifest.json@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1664028999133,
+ "_generatedAtUnix": 1664142366214,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
...@@ -15,6 +15,12 @@
"pathBundle": "./public/.islands/RepositoriesList.bundle.js",
"pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"
},
+ "RepositoryCommitSummaryLine": {
+ "hash": "e248dcfd52a6086ffa54f7db222229291c5a227a",
+ "pathSource": "./app/islands/RepositoryCommitSummaryLine.tsx",
+ "pathBundle": "./public/.islands/RepositoryCommitSummaryLine.bundle.js",
+ "pathSourceMap": "./public/.islands/RepositoryCommitSummaryLine.bundle.js.map"
+ },
"RepositoryCreateForm": {
"hash": "13bc29f8548d0df057f56a78bb6f2f6b31cb7d31",
"pathSource": "./app/islands/RepositoryCreateForm.tsx",
...@@ -22,7 +28,7 @@
"pathSourceMap": "./public/.islands/RepositoryCreateForm.bundle.js.map"
},
"RepositoryFilesDiffsList": {
- "hash": "ab1579b7b0bfbe3f8af63352960a7b4c0e987971",
+ "hash": "003091766fc95fa365523379793b24e14d7ed61b",
"pathSource": "./app/islands/RepositoryFilesDiffsList.tsx",
"pathBundle": "./public/.islands/RepositoryFilesDiffsList.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryFilesDiffsList.bundle.js.map"
...@@ -34,7 +40,7 @@
"pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
},
"RepositoryTreeView": {
- "hash": "ee5b76a50148c328f479d60cf5f4ac3178ebc72b",
+ "hash": "09d177e71de6bea5a60a919bfb6dcf51d4ea7cc3",
"pathSource": "./app/islands/RepositoryTreeView.tsx",
"pathBundle": "./public/.islands/RepositoryTreeView.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
...@@ -62,11 +68,11 @@
"pathSource": "./app/views/organization/OrganizationDetailsView.tsx"
},
"RepositoryBrowserView": {
- "hash": "0d530206f5c3ae6a5122c0b29a1a0123934ce122",
+ "hash": "e013bb2954f5e3f0b1d370e7f99f87c0e6121653",
"pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
},
"RepositoryCommitsLogView": {
- "hash": "cfb132fe26e998535c0c022d4796f35fd7913812",
+ "hash": "edb26387e0cab7e58978c25599ab857b9ece0d6d",
"pathSource": "./app/views/repository/RepositoryCommitsLogView.tsx"
},
"RepositoryCompareView": {
...@@ -78,13 +84,17 @@
"pathSource": "./app/views/repository/RepositoryCreateView.tsx"
},
"RepositoryDetailsView": {
- "hash": "b15fff7aad6bc1d2636ed58be26492552d7d9946",
+ "hash": "715c846b8fbaee1f7186e2dc64d0535d0b738880",
"pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
},
"RepositoryExploreView": {
"hash": "d3cd2213b77362ecf1dacaf408987d6bae744cd3",
"pathSource": "./app/views/repository/RepositoryExploreView.tsx"
},
+ "RepositoryShowObjectView": {
+ "hash": "19144b3c408ae1f0dfa04c72f6737ffaacb6e8cb",
+ "pathSource": "./app/views/repository/RepositoryShowObjectView.tsx"
+ },
"UserDashboardView": {
"hash": "616b3877b11bb101b48391f25ed83ad08f0c2df8",
"pathSource": "./app/views/user/UserDashboardView.tsx"
new fileapp/controllers/repository/getRepositoryShowObjectView.ts@@ -0,0 +1,68 @@
+// 1st-party
+import type { ReqHandler } from "@ethicdevs/react-monolith";
+import { ResourceVisibility } from "@prisma/client";
+// app
+import { AppRoute, AppRoutesParams } from "../../routes";
+// app services
+import { makeOrganizationService } from "../../services/organization";
+import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
+// app views
+import RepositoryShowObjectView, {
+ RepositoryShowObjectViewProps,
+} from "../../views/repository/RepositoryShowObjectView";
+
+const getRepositoryShowObjectView: ReqHandler = async (request, reply) => {
+ const { orgSlug, repoSlug, objectId } =
+ request.params as AppRoutesParams[AppRoute.REPOSITORY_SHOW_OBJECT]["params"];
+
+ const orgService = makeOrganizationService({ request });
+ const repoService = makeRepositoryService({ request });
+ const usersService = makeUsersService({ request });
+
+ const currentUser =
+ request.session.data.authenticated &&
+ request.session.data.curr_user_uid != null
+ ? await usersService.getUserById(request.session.data.curr_user_uid)
+ : null;
+
+ const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
+ const repo = await repoService.getRepository(orgSlug, repoSlug);
+
+ if (parentOrg == null || repo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ if (repo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, repo)) === false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
+ const gitObject = await repoService.getRepositoryObject(repo, objectId);
+ const gitObjectDiffs =
+ gitObject != null
+ ? await repoService.getRepositoryRefDiff(
+ repo,
+ gitObject.parent,
+ gitObject.commit
+ )
+ : null;
+
+ const reqHandler = reply.makeRequestHandler(request, reply);
+ return reqHandler<RepositoryShowObjectViewProps>(
+ RepositoryShowObjectView.name,
+ {
+ parentOrg,
+ repo,
+ gitObject,
+ gitObjectDiffs,
+ }
+ );
+};
+
+export default getRepositoryShowObjectView;
app/controllers/repository/index.ts@@ -4,6 +4,7 @@ import { default as getRepositoryCompareView } from "./getRepositoryCompareView"
import { default as getRepositoryCreateView } from "./getRepositoryCreateView";
import { default as getRepositoryDetailsView } from "./getRepositoryDetailsView";
import { default as getRepositoryExploreView } from "./getRepositoryExploreView";
+import { default as getRepositoryShowObjectView } from "./getRepositoryShowObjectView";
import { default as postRepositoryCreateAction } from "./postRepositoryCreateAction";
export const RepositoryController = {
...@@ -13,5 +14,6 @@ export const RepositoryController = {
getRepositoryCreateView,
getRepositoryDetailsView,
getRepositoryExploreView,
+ getRepositoryShowObjectView,
postRepositoryCreateAction,
};
new fileapp/islands/RepositoryCommitSummaryLine.tsx@@ -0,0 +1,44 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// app
+import type { RepositoryObject } from "../types";
+import { Grid } from "../components";
+
+export interface RepositoryCommitSummaryLineProps {
+ orgSlug: string;
+ repoSlug: string;
+ commit: RepositoryObject;
+}
+
+const RepositoryCommitSummaryLine: ReactIsland<RepositoryCommitSummaryLineProps> =
+ ({ orgSlug, repoSlug, commit }) => {
+ return (
+ <Grid.Row gap={8} alignItems={"flex-start"}>
+ <strong>{commit.author.name}</strong>
+ {" ∙ "}
+ <span style={{ flex: 1 }}>
+ <a href={`/${orgSlug}/${repoSlug}/show/${commit.commit}`}>
+ {commit.subject}
+ </a>
+ </span>
+ {" ∙ "}
+ <span>
+ <a href={`/${orgSlug}/${repoSlug}/show/${commit.commit}`}>
+ {commit.abbreviated_commit}
+ </a>
+ {commit.abbreviated_parent.trim() != "" ? (
+ <a href={`/${orgSlug}/${repoSlug}/show/${commit.parent}`}>
+ {` (parent ${commit.abbreviated_parent})`}
+ </a>
+ ) : null}
+ </span>
+ {" ∙ "}
+ <span>{new Date(commit.author.date).toLocaleDateString()}</span>
+ </Grid.Row>
+ );
+ };
+
+RepositoryCommitSummaryLine.displayName = "RepositoryCommitSummaryLine";
+export default RepositoryCommitSummaryLine;
app/islands/RepositoryFilesDiffsList.tsx@@ -29,11 +29,11 @@ const RepositoryFilesDiffsList: ReactIsland<
> = ({ commitHash, filesDiffs, orgSlug, repoSlug, themeScheme }) => (
<>
{getThemedCodeCss(themeScheme)}
- <div style={{ marginTop: 24, width: "100%" }}>
- {filesDiffs.map(({ chunks, ...diff }) => (
+ <Grid.Col fluid>
+ {filesDiffs.map(({ chunks, ...diff }, idx) => (
<Card
key={[diff.from, diff.to].join(":")}
- style={{ marginTop: 16, width: "100%" }}
+ style={{ marginTop: idx > 0 ? 16 : 0, width: "100%" }}
themeScheme={themeScheme}
>
<Grid.Col fluid nowrap>
...@@ -65,6 +66,10 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
): Promise<RepositoryHead>;
getRepositoryHTTPCloneUrl(repository: Repository): Promise<string>;
getRepositorySSHCloneUrl(repository: Repository): Promise<string>;
+ getRepositoryObject(
+ repository: Repository,
+ objectId: String
+ ): Promise<RepositoryObject | null>;
getRepositoryRefDiff(
repository: Repository,
refA: string,
app/types.ts@@ -142,3 +142,5 @@ export interface RepositoryLog {
date: string;
};
}
+
+export type RepositoryObject = RepositoryLog;
app/views/repository/RepositoryBrowserView.tsx@@ -50,17 +50,19 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
{path}
</h1>
<Grid.Col fluid style={{ marginTop: 16 }}>
- <Grid.Row nowrap alignItems={"center"} style={{ width: "100%" }}>
- <div>
- <strong>lang:</strong> <span>{linguistInfos.language}</span>
- </div>
- <div style={{ marginLeft: 16 }}>
- <strong>mime:</strong> <span>{linguistInfos.mimeType}</span>
- </div>
- </Grid.Row>
+ <Card style={{ width: "100%" }} themeScheme={commonProps.themeScheme}>
+ <Grid.Row nowrap alignItems={"center"} style={{ width: "100%" }}>
+ <div>
+ <strong>lang:</strong> <span>{linguistInfos.language}</span>
+ </div>
+ <div style={{ marginLeft: 16 }}>
+ <strong>mime:</strong> <span>{linguistInfos.mimeType}</span>
+ </div>
+ </Grid.Row>
+ </Card>
{linguistInfos.type === "image" ? (
<Card
- style={{ width: "100%" }}
+ style={{ width: "100%", marginTop: 16 }}
themeScheme={commonProps.themeScheme}
>
<img
...@@ -69,14 +71,17 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
/>
</Card>
) : (
- <>
+ <Card
+ style={{ width: "100%", marginTop: 16 }}
+ themeScheme={commonProps.themeScheme}
+ >
{getThemedCodeCss(commonProps.themeScheme)}
<Code
code={fileContent.content}
language={linguistInfos.language}
themeScheme={commonProps.themeScheme}
/>
- </>
+ </Card>
)}
</Grid.Col>
</PageWrapper>
app/views/repository/RepositoryCommitsLogView.tsx@@ -40,7 +40,7 @@ const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
{history.map((log) => (
<a
key={log.tree}
- href={`/${parentOrg.slug}/${repo.slug}/compare/${log.abbreviated_parent}..${log.abbreviated_commit}`}
+ href={`/${parentOrg.slug}/${repo.slug}/show/${log.commit}`}
style={{ marginTop: 8 }}
>
<strong>{log.author.name}</strong>
app/views/repository/RepositoryDetailsView.tsx@@ -20,6 +20,7 @@ import {
PageWrapper,
} from "../../components";
// app islands
+import RepositoryCommitSummaryLine from "../../islands/RepositoryCommitSummaryLine";
import RepositoryInitialSetup from "../../islands/RepositoryInitialSetup";
import RepositoryTreeView from "../../islands/RepositoryTreeView";
...@@ -91,20 +92,33 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
/>
</Card>
) : (
- <Card
- data-islandid={`${RepositoryTreeView.name}$$0`}
- style={{ width: "100%" }}
- themeScheme={commonProps.themeScheme}
- >
- <RepositoryTreeView
- currentRef={currentRef}
- currentPath={path}
- lastCommit={lastCommit}
- orgSlug={parentOrg.slug}
- repoFiles={repoFiles}
- repoSlug={repo.slug}
- />
- </Card>
+ <Grid.Col fluid>
+ <Card
+ data-islandid={`${RepositoryCommitSummaryLine.name}$$0`}
+ style={{ width: "100%" }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryCommitSummaryLine
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commit={lastCommit}
+ />
+ </Card>
+ <Card
+ data-islandid={`${RepositoryTreeView.name}$$0`}
+ style={{ width: "100%", marginTop: 16 }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryTreeView
+ currentRef={currentRef}
+ currentPath={path}
+ lastCommit={lastCommit}
+ orgSlug={parentOrg.slug}
+ repoFiles={repoFiles}
+ repoSlug={repo.slug}
+ />
+ </Card>
+ </Grid.Col>
)}
{readmeFileContent != null && (
<div style={{ width: "100%" }}>
new fileapp/views/repository/RepositoryShowObjectView.tsx@@ -0,0 +1,78 @@
+// 1st-party
+import type { ReactView } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// generated via script[generate:prisma]
+import type { Organization, Repository } from "@prisma/client";
+// app
+import type {
+ CommonProps,
+ RepositoryFileDiff,
+ RepositoryObject,
+} from "../../types";
+import { Card, Layout, PageWrapper } from "../../components";
+// app islands
+import RepositoryCommitSummaryLine from "../../islands/RepositoryCommitSummaryLine";
+import RepositoryFilesDiffsList from "../../islands/RepositoryFilesDiffsList";
+
+export interface RepositoryShowObjectViewProps extends CommonProps {
+ gitObject: RepositoryObject;
+ gitObjectDiffs: RepositoryFileDiff[] | null;
+ parentOrg: Organization;
+ repo: Repository;
+}
+
+const RepositoryShowObjectView: ReactView<RepositoryShowObjectViewProps> = ({
+ commonProps,
+ gitObject,
+ gitObjectDiffs,
+ parentOrg,
+ repo,
+}) => {
+ 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>
+ {" / Show / "}
+ <span>{gitObject.abbreviated_commit}</span>
+ </h1>
+ <Card
+ data-islandid={`${RepositoryCommitSummaryLine.name}$$0`}
+ style={{ width: "100%", marginTop: 32 }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryCommitSummaryLine
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commit={gitObject}
+ />
+ </Card>
+ {gitObjectDiffs != null && (
+ <Card
+ data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
+ style={{ width: "100%", marginTop: 16 }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryFilesDiffsList
+ filesDiffs={gitObjectDiffs}
+ themeScheme={commonProps.themeScheme}
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commitHash={gitObject.commit}
+ />
+ </Card>
+ )}
+ </PageWrapper>
+ </Layout>
+ );
+};
+
+RepositoryShowObjectView.displayName = "RepositoryShowObjectView";
+export default RepositoryShowObjectView;
@@ -69,7 +69,7 @@ const RepositoryFilesDiffsList: ReactIsland<
</div>
</Grid.Row>
</Grid.Col>
- <div style={{ width: "100%" }}>
+ <Grid.Col fluid style={{ marginTop: 8 }}>
{chunks.map((chunk, idx) => (
<Code
key={[idx, chunk.content].join(":")}
@@ -78,10 +78,10 @@ const RepositoryFilesDiffsList: ReactIsland<
themeScheme={themeScheme}
/>
))}
- </div>
+ </Grid.Col>
</Card>
))}
- </div>
+ </Grid.Col>
</>
);
@@ -6,6 +6,7 @@ import styled from "styled-components";
// app
import type { RepositoryFile, RepositoryLog } from "../types";
import { Grid } from "../components";
+// import RepositoryCommitSummaryLine from "./RepositoryCommitSummaryLine";
export interface RepositoryTreeViewProps {
currentPath: string;
@@ -19,7 +20,7 @@ export interface RepositoryTreeViewProps {
const RepositoryTreeView: ReactIsland<RepositoryTreeViewProps> = ({
currentPath,
currentRef,
- lastCommit,
+ // lastCommit,
orgSlug,
repoFiles,
repoSlug,
@@ -61,30 +62,6 @@ const RepositoryTreeView: ReactIsland<RepositoryTreeViewProps> = ({
return (
<StyledRepositoryTreeViewContainer>
<Grid.Col fluid>
- <Grid.Row gap={8} alignItems={"flex-start"}>
- <strong>{lastCommit.author.name}</strong>
- {" ∙ "}
- <span style={{ flex: 1 }}>
- <a
- href={`/${orgSlug}/${repoSlug}/compare/${lastCommit.abbreviated_parent}..${lastCommit.abbreviated_commit}`}
- >
- {lastCommit.subject}
- </a>
- </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).toLocaleDateString()}</span>
- </Grid.Row>
<Grid.Row
gap={8}
alignItems={"center"}
@@ -27,13 +27,14 @@ export enum AppRoute {
USER_DASHBOARD = "user.dashboard",
USER_DETAILS = "user.details",
ORGANIZATION_DETAILS = "organization.details",
- REPOSITORY_EXPLORE = "repository.explore",
+ REPOSITORY_BROWSER = "repository.browser",
REPOSITORY_COMMITS_LOG = "repository.commits_log",
REPOSITORY_COMPARE = "repository.compare",
REPOSITORY_CREATE = "repository.create",
REPOSITORY_CREATE_ACTION = "repository.create.action",
REPOSITORY_DETAILS = "repository.details",
- REPOSITORY_BROWSER = "repository.browser",
+ REPOSITORY_EXPLORE = "repository.explore",
+ REPOSITORY_SHOW_OBJECT = "repository.show_object",
}
export interface AppRoutesParams extends IRouteParams {
@@ -113,6 +114,13 @@ export interface AppRoutesParams extends IRouteParams {
"*": string;
};
};
+ [AppRoute.REPOSITORY_SHOW_OBJECT]: {
+ params: {
+ orgSlug: string;
+ repoSlug: string;
+ objectId: string;
+ };
+ };
}
export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
@@ -319,6 +327,24 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
},
},
},
+ [AppRoute.REPOSITORY_SHOW_OBJECT]: {
+ params: {
+ type: "object",
+ required: ["orgSlug", "repoSlug", "objectId"],
+ additionalProperties: false,
+ properties: {
+ orgSlug: {
+ type: "string",
+ },
+ repoSlug: {
+ type: "string",
+ },
+ objectId: {
+ type: "string",
+ },
+ },
+ },
+ },
};
const RootAppRouter: AppRouter = () => {
@@ -415,7 +441,7 @@ const RootAppRouter: AppRouter = () => {
<Router.Route
name={AppRoute.REPOSITORY_COMMITS_LOG}
method={"GET"}
- path={"/:orgSlug/:repoSlug/commits"}
+ path={"/:orgSlug/:repoSlug/commits"} // TODO: add :ref before /commits + adaptations to links
schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMMITS_LOG]}
handler={RepositoryController.getRepositoryCommitsLogView}
/>
@@ -469,6 +495,13 @@ const RootAppRouter: AppRouter = () => {
schema={AppRoutesSchemas[AppRoute.REPOSITORY_BROWSER]}
handler={RepositoryController.getRepositoryBrowserView}
/>
+ <Router.Route
+ name={AppRoute.REPOSITORY_SHOW_OBJECT}
+ method={"GET"}
+ path={"/:orgSlug/:repoSlug/show/:objectId"}
+ schema={AppRoutesSchemas[AppRoute.REPOSITORY_SHOW_OBJECT]}
+ handler={RepositoryController.getRepositoryShowObjectView}
+ />
</Router.Group>
</Router.Root>
);
@@ -15,8 +15,8 @@ const makeOnPushEvent: ServiceMethodFactory<
return ({ data, message }) => {
message.write("\n");
if (data.packType === GitServer.PackType.RECEIVE) {
- // client has done something like "git push"
- // it is uploading, we are receiving
+ // client has done something like "git push" it is uploading
+ // server is receiving
console.log("receive-pack");
const [orgSlug, repoSlug] = data.repoSlug.split("/");
@@ -50,16 +50,14 @@ const makeOnPushEvent: ServiceMethodFactory<
if (data.payload != null) {
message.write("🖖 See the details of your push at:\n");
if (data.payload.refType === "head") {
- message.write(
- `🧲 ${baseUrl}/compare/HEAD%5E..${data.payload.commitId}\n`
- );
+ message.write(`🧲 ${baseUrl}/show/${data.payload.commitId}\n`);
} else if (data.payload.refType === "tag") {
message.write(`➡️ ${baseUrl}/tags/${data.payload.refName}\n`);
}
}
} else if (data.packType === GitServer.PackType.UPLOAD) {
- // client has done something like "git clone"
- // it is receiving, we are uploading
+ // client has done something like "git clone" it is receiving
+ // server is uploading
console.log("upload-pack");
message.write(`🖖 Welcome at GitFOSS ${data.username}!\n`);
}
@@ -0,0 +1,104 @@
+// std
+import { existsSync } from "node:fs";
+import { spawn } from "node:child_process";
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import type { Repository } from "@prisma/client";
+// app
+import { Env } from "../../env";
+// service
+import type { RepositoryObject } from "../../types";
+import type { RepositoryServiceDeps } from "./types";
+
+const GIT_ESCAPED_JSON_FIELDS_WITH_UNESCAPED_NEWLINES_REGEXP =
+ / \^@\^[a-z_]+\^@\^: \^@\^([^\^]+|)\^@\^,?/gim;
+
+const makeGetRepositoryObject: ServiceMethodFactory<
+ RepositoryServiceDeps,
+ [Repository, string],
+ Promise<RepositoryObject | null>
+> = ({ request }) => {
+ return async (repo, objectId) => {
+ const parentOrg = await request.prisma.organization.findUnique({
+ where: {
+ id: repo.organizationId,
+ },
+ });
+
+ if (parentOrg == null) {
+ throw new Error(
+ `Could not find the parent organization for project "${repo.slug}".`
+ );
+ }
+
+ const repoPath = `${Env.GIT_REPOSITORIES_ROOT}/${parentOrg.slug}/${repo.slug}.git`;
+
+ if (existsSync(repoPath) === false) {
+ throw new Error(`Could not find a valid git repository at: ${repoPath}`);
+ }
+
+ var format =
+ "{%n ^@^commit^@^: ^@^%H^@^,%n ^@^abbreviated_commit^@^: ^@^%h^@^,%n ^@^tree^@^: ^@^%T^@^,%n ^@^abbreviated_tree^@^: ^@^%t^@^,%n ^@^parent^@^: ^@^%P^@^,%n ^@^abbreviated_parent^@^: ^@^%p^@^,%n ^@^refs^@^: ^@^%D^@^,%n ^@^encoding^@^: ^@^%e^@^,%n ^@^subject^@^: ^@^%s^@^,%n ^@^sanitized_subject_line^@^: ^@^%f^@^,%n ^@^body^@^: ^@^%b^@^,%n ^@^commit_notes^@^: ^@^%N^@^,%n ^@^verification_flag^@^: ^@^%G?^@^,%n ^@^signer^@^: ^@^%GS^@^,%n ^@^signer_key^@^: ^@^%GK^@^,%n ^@^author^@^: {%n ^@^name^@^: ^@^%aN^@^,%n ^@^email^@^: ^@^%aE^@^,%n ^@^date^@^: ^@^%aD^@^%n },%n ^@^commiter^@^: {%n ^@^name^@^: ^@^%cN^@^,%n ^@^email^@^: ^@^%cE^@^,%n ^@^date^@^: ^@^%cD^@^%n }%n},";
+
+ const args = [
+ "show",
+ "--quiet",
+ `--pretty=format:${format}`,
+ objectId,
+ ].filter((x): x is string => x != null);
+
+ const gitShowObjectProcess = spawn("git", args, {
+ cwd: repoPath,
+ });
+
+ try {
+ const gitShowObjectResult = await new Promise<RepositoryObject>(
+ (resolve, reject) => {
+ let buffer = [] as string[];
+ gitShowObjectProcess.stdout.on("data", (data) => buffer.push(data));
+ gitShowObjectProcess.stderr.on("data", (data) => {
+ reject(new Error(Buffer.from(data).toString("utf-8")));
+ });
+ gitShowObjectProcess.stdout.on("close", () => {
+ let escapedJson = buffer.join("");
+
+ // Find the fields that may contain \n for escaping them
+ const fieldsToEscape = escapedJson.match(
+ GIT_ESCAPED_JSON_FIELDS_WITH_UNESCAPED_NEWLINES_REGEXP
+ );
+
+ if (fieldsToEscape != null && Array.isArray(fieldsToEscape)) {
+ fieldsToEscape.forEach((fieldTxt) => {
+ escapedJson = escapedJson.replace(
+ fieldTxt,
+ fieldTxt.split("\n").join("\\n") // Escape unescaped newlines
+ );
+ });
+ }
+
+ escapedJson = escapedJson
+ .replace(/"/gm, '\\"') // Escape double-quotes: `"` -> `\"`
+ .replace(/\^@\^/gm, '"'); // Custom escape char `^@^` back to real double quotes `"`
+
+ // remove `,` from the end of string.
+ escapedJson = escapedJson.substring(0, escapedJson.length - 1);
+
+ try {
+ resolve(JSON.parse(escapedJson));
+ } catch (err) {
+ reject(err);
+ }
+ });
+ }
+ );
+
+ return gitShowObjectResult as RepositoryObject;
+ } catch (err) {
+ console.error("Cannot get git object", objectId, err);
+ return null;
+ }
+ };
+};
+
+export default makeGetRepositoryObject;
@@ -15,6 +15,7 @@ import { default as makeGetRepositoryFiles } from "./getRepositoryFiles";
import { default as makeGetRepositoryHead } from "./getRepositoryHead";
import { default as makeGetRepositoryHTTPCloneUrl } from "./getRepositoryHTTPCloneUrl";
import { default as makeGetRepositorySSHCloneUrl } from "./getRepositorySSHCloneUrl";
+import { default as makeGetRepositoryObject } from "./getRepositoryObject";
import { default as makeGetRepositoryRefDiff } from "./getRepositoryRefDiff";
import { default as makeGetRepositoryTags } from "./getRepositoryTags";
import { default as makeIsFileInRepositoryPath } from "./isFileInRepositoryPath";
@@ -35,6 +36,7 @@ export const makeRepositoryService = makeService<
getRepositoryHead: makeGetRepositoryHead,
getRepositoryHTTPCloneUrl: makeGetRepositoryHTTPCloneUrl,
getRepositorySSHCloneUrl: makeGetRepositorySSHCloneUrl,
+ getRepositoryObject: makeGetRepositoryObject,
getRepositoryRefDiff: makeGetRepositoryRefDiff,
getRepositoryTags: makeGetRepositoryTags,
isFileInRepositoryPath: makeIsFileInRepositoryPath,
@@ -13,6 +13,7 @@ import type {
RepositoryFileDiff,
RepositoryHead,
RepositoryLog,
+ RepositoryObject,
} from "../../types";
export interface CreateRepositoryDTO {
@@ -65,6 +66,10 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
): Promise<RepositoryHead>;
getRepositoryHTTPCloneUrl(repository: Repository): Promise<string>;
getRepositorySSHCloneUrl(repository: Repository): Promise<string>;
+ getRepositoryObject(
+ repository: Repository,
+ objectId: String
+ ): Promise<RepositoryObject | null>;
getRepositoryRefDiff(
repository: Repository,
refA: string,
@@ -142,3 +142,5 @@ export interface RepositoryLog {
date: string;
};
}
+
+export type RepositoryObject = RepositoryLog;
@@ -50,17 +50,19 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
{path}
</h1>
<Grid.Col fluid style={{ marginTop: 16 }}>
- <Grid.Row nowrap alignItems={"center"} style={{ width: "100%" }}>
- <div>
- <strong>lang:</strong> <span>{linguistInfos.language}</span>
- </div>
- <div style={{ marginLeft: 16 }}>
- <strong>mime:</strong> <span>{linguistInfos.mimeType}</span>
- </div>
- </Grid.Row>
+ <Card style={{ width: "100%" }} themeScheme={commonProps.themeScheme}>
+ <Grid.Row nowrap alignItems={"center"} style={{ width: "100%" }}>
+ <div>
+ <strong>lang:</strong> <span>{linguistInfos.language}</span>
+ </div>
+ <div style={{ marginLeft: 16 }}>
+ <strong>mime:</strong> <span>{linguistInfos.mimeType}</span>
+ </div>
+ </Grid.Row>
+ </Card>
{linguistInfos.type === "image" ? (
<Card
- style={{ width: "100%" }}
+ style={{ width: "100%", marginTop: 16 }}
themeScheme={commonProps.themeScheme}
>
<img
@@ -69,14 +71,17 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
/>
</Card>
) : (
- <>
+ <Card
+ style={{ width: "100%", marginTop: 16 }}
+ themeScheme={commonProps.themeScheme}
+ >
{getThemedCodeCss(commonProps.themeScheme)}
<Code
code={fileContent.content}
language={linguistInfos.language}
themeScheme={commonProps.themeScheme}
/>
- </>
+ </Card>
)}
</Grid.Col>
</PageWrapper>
@@ -40,7 +40,7 @@ const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
{history.map((log) => (
<a
key={log.tree}
- href={`/${parentOrg.slug}/${repo.slug}/compare/${log.abbreviated_parent}..${log.abbreviated_commit}`}
+ href={`/${parentOrg.slug}/${repo.slug}/show/${log.commit}`}
style={{ marginTop: 8 }}
>
<strong>{log.author.name}</strong>
@@ -20,6 +20,7 @@ import {
PageWrapper,
} from "../../components";
// app islands
+import RepositoryCommitSummaryLine from "../../islands/RepositoryCommitSummaryLine";
import RepositoryInitialSetup from "../../islands/RepositoryInitialSetup";
import RepositoryTreeView from "../../islands/RepositoryTreeView";
@@ -91,20 +92,33 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
/>
</Card>
) : (
- <Card
- data-islandid={`${RepositoryTreeView.name}$$0`}
- style={{ width: "100%" }}
- themeScheme={commonProps.themeScheme}
- >
- <RepositoryTreeView
- currentRef={currentRef}
- currentPath={path}
- lastCommit={lastCommit}
- orgSlug={parentOrg.slug}
- repoFiles={repoFiles}
- repoSlug={repo.slug}
- />
- </Card>
+ <Grid.Col fluid>
+ <Card
+ data-islandid={`${RepositoryCommitSummaryLine.name}$$0`}
+ style={{ width: "100%" }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryCommitSummaryLine
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commit={lastCommit}
+ />
+ </Card>
+ <Card
+ data-islandid={`${RepositoryTreeView.name}$$0`}
+ style={{ width: "100%", marginTop: 16 }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryTreeView
+ currentRef={currentRef}
+ currentPath={path}
+ lastCommit={lastCommit}
+ orgSlug={parentOrg.slug}
+ repoFiles={repoFiles}
+ repoSlug={repo.slug}
+ />
+ </Card>
+ </Grid.Col>
)}
{readmeFileContent != null && (
<div style={{ width: "100%" }}>
@@ -0,0 +1,78 @@
+// 1st-party
+import type { ReactView } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// generated via script[generate:prisma]
+import type { Organization, Repository } from "@prisma/client";
+// app
+import type {
+ CommonProps,
+ RepositoryFileDiff,
+ RepositoryObject,
+} from "../../types";
+import { Card, Layout, PageWrapper } from "../../components";
+// app islands
+import RepositoryCommitSummaryLine from "../../islands/RepositoryCommitSummaryLine";
+import RepositoryFilesDiffsList from "../../islands/RepositoryFilesDiffsList";
+
+export interface RepositoryShowObjectViewProps extends CommonProps {
+ gitObject: RepositoryObject;
+ gitObjectDiffs: RepositoryFileDiff[] | null;
+ parentOrg: Organization;
+ repo: Repository;
+}
+
+const RepositoryShowObjectView: ReactView<RepositoryShowObjectViewProps> = ({
+ commonProps,
+ gitObject,
+ gitObjectDiffs,
+ parentOrg,
+ repo,
+}) => {
+ 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>
+ {" / Show / "}
+ <span>{gitObject.abbreviated_commit}</span>
+ </h1>
+ <Card
+ data-islandid={`${RepositoryCommitSummaryLine.name}$$0`}
+ style={{ width: "100%", marginTop: 32 }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryCommitSummaryLine
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commit={gitObject}
+ />
+ </Card>
+ {gitObjectDiffs != null && (
+ <Card
+ data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
+ style={{ width: "100%", marginTop: 16 }}
+ themeScheme={commonProps.themeScheme}
+ >
+ <RepositoryFilesDiffsList
+ filesDiffs={gitObjectDiffs}
+ themeScheme={commonProps.themeScheme}
+ orgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
+ commitHash={gitObject.commit}
+ />
+ </Card>
+ )}
+ </PageWrapper>
+ </Layout>
+ );
+};
+
+RepositoryShowObjectView.displayName = "RepositoryShowObjectView";
+export default RepositoryShowObjectView;