GitFOSS
feat(repository): add a "RepositoryShowObjectView" + logic
+ 415
- 73
@@ -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 file
app/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 file
app/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>

...
@@ -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>
   </>
 );
 

app/islands/RepositoryTreeView.tsx
@@ -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>
   );

app/services/gitServer/onPushEvent.ts
@@ -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`);
     }

new file
app/services/repository/getRepositoryObject.ts
@@ -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;

app/services/repository/index.ts
@@ -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,

app/services/repository/types.ts
@@ -13,6 +13,7 @@ import type {
   RepositoryFileDiff,
   RepositoryHead,
   RepositoryLog,
+  RepositoryObject,
 } from "../../types";
 
 export interface CreateRepositoryDTO {

feat(repository): add a "RepositoryShowObjectView" + logic
+ 415
- 73
@@ -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 file
app/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 file
app/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,

@@ -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 file
app/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>
   </>
 );
 

app/islands/RepositoryTreeView.tsx
@@ -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>
   );

app/services/gitServer/onPushEvent.ts
@@ -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`);
     }

new file
app/services/repository/getRepositoryObject.ts
@@ -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;

app/services/repository/index.ts
@@ -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,

app/services/repository/types.ts
@@ -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;

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 file
app/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;