diff --git a/pygitweb/README.md b/pygitweb/README.md
index 0e35498..ea88be4 100644
--- a/pygitweb/README.md
+++ b/pygitweb/README.md
@@ -55,7 +55,9 @@ See [ROUTES.md](ROUTES.md) for more detail.
 - `GET /{project}?a=history&h=...&f=...` — history of a file or path
 - `GET /{project}?a=tags` — list all tags
 - `GET /{project}?a=tag&h=...` — single tag view (tag ref or hash)
+- `GET /{project}?a=commit` — commit information
+- `GET /{project}?a=commitdiff_plain` — unified diff for commit
 
 **Project-scoped actions (stub only)**
 
-These actions are accepted but return a minimal placeholder page. Add handlers in `main.py` to implement: `blame`, `blame_incremental`, `blame_data`, `blobdiff`, `blobdiff_plain`, `commit`, `commitdiff`, `commitdiff_plain`, `heads`, `patch`, `patches`, `remotes`, `rss`, `atom`, `search`, `search_help`, `snapshot`, `object`.
+These actions are accepted but return a minimal placeholder page. Add handlers in `main.py` to implement: `blame`, `blame_incremental`, `blame_data`, `blobdiff`, `blobdiff_plain`, `commitdiff`, `heads`, `patch`, `patches`, `remotes`, `rss`, `atom`, `search`, `search_help`, `snapshot`, `object`.
diff --git a/pygitweb/main.py b/pygitweb/main.py
index ceb6598..6e83e6e 100644
--- a/pygitweb/main.py
+++ b/pygitweb/main.py
@@ -461,6 +461,10 @@ def dispatch(
         return git_tags(proj)
     if action == "tag":
         return git_tag(proj, hash_param)
+    if action == "commit":
+        return git_commit(proj, hash_param)
+    if action == "commitdiff_plain":
+        return git_commitdiff_plain(proj, hash_param)
     # Stub others with minimal response
     pre = PREAMBLE.render(title=f"{esc_html(action)} - {esc_html(proj)}", theme="dark", site_name=config.SITE_NAME)
     return HTMLResponse(
@@ -482,11 +486,11 @@ def git_summary(project: str) -> HTMLResponse:
     head_short = head[:7] if head else ""
 
     table = env.get_template("table.html").render(
-        cols=[],
+        cols=["Field", "Value"],
         rows=[
             ["Description", esc_html(descr)],
             ["Owner", esc_html(owner)],
-            [esc_html("HEAD"), f"<a href='/{project}/commit/{head or ''}'>{head_short or 'N/A'}</a>"],
+            [esc_html("HEAD"), f"<a href='/{project}?a=commit&h={quote(head or '', safe='')}'>{head_short or 'N/A'}</a>"],
             [esc_html("tree"), f"<a href='/{project}?a=tree&h={head or ''}'>browse</a>"],
         ]
     )
@@ -826,6 +830,83 @@ def git_tag(project: str, h: str | None) -> HTMLResponse:
     )
 
 
+# Well-known empty tree OID (Git standard)
+_EMPTY_TREE_OID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+
+
+def git_commit(project: str, h: str | None) -> HTMLResponse:
+    """Single commit view. Port of git_commit (git show)."""
+    if not h:
+        raise HTTPException(status_code=400, detail="Commit hash (h) required")
+    if not is_valid_ref_format(h):
+        raise HTTPException(status_code=400, detail="Invalid ref or hash")
+    try:
+        from pygitweb.git_helpers import open_repo
+        repo = open_repo(project)
+        obj = repo.revparse_single(h)
+    except (KeyError, pygit2.GitError, OSError):
+        raise HTTPException(status_code=404, detail="Commit not found")
+    if not isinstance(obj, pygit2.Commit):
+        raise HTTPException(status_code=404, detail="Not a commit object")
+    commit = obj
+    oid = str(commit.id)
+    oid_short = oid[:7]
+    data = parse_commit(project, oid)
+    author = data.get("author", "")
+    author_email = data.get("author_email", "")
+    author_date = _format_date(data.get("author_epoch"), data.get("author_tz"))
+    tree_oid = data.get("tree", "")
+    message = (data.get("body") or "").strip()
+    commit_link = f"/{project}?a=commit&h={quote(oid, safe='')}"
+    tree_link = f"/{project}?a=tree&h={quote(oid, safe='')}"
+    patch_link = f"/{project}?a=commitdiff_plain&h={quote(oid, safe='')}"
+    table_rows = [
+        ["commit", f'<a href="{commit_link}">{esc_html(oid)}</a>'],
+        ["Author", f"{esc_html(author)} &lt;{esc_html(author_email)}&gt;"],
+        ["Date", esc_html(author_date)],
+        ["tree", f'<a href="{tree_link}">{esc_html(tree_oid[:7])}</a>'],
+    ]
+    if message:
+        table_rows.append(["", f"<pre class='commit-message'>{esc_html(message)}</pre>"])
+    table_rows.append(["", f'<a href="{patch_link}">patch</a>'])
+    table = env.get_template("table.html").render(cols=["Field", "Value"], rows=table_rows)
+    title = f"Commit {esc_html(oid_short)} - {esc_html(project)}"
+    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    return HTMLResponse(
+        f"{pre}<h1>Commit {esc_html(oid_short)}</h1><p>Project: <a href='/{project}'>{esc_html(project)}</a></p>{table}{POSTAMBLE}"
+    )
+
+
+def git_commitdiff_plain(project: str, h: str | None) -> PlainTextResponse:
+    """Unified diff for a commit. Uses PyGit2 Diff; returns text/plain unified diff."""
+    if not h:
+        raise HTTPException(status_code=400, detail="Commit hash (h) required")
+    if not is_valid_ref_format(h):
+        raise HTTPException(status_code=400, detail="Invalid ref or hash")
+    try:
+        from pygitweb.git_helpers import open_repo
+        repo = open_repo(project)
+        commit = repo.revparse_single(h)
+    except (KeyError, pygit2.GitError, OSError):
+        raise HTTPException(status_code=404, detail="Commit not found")
+    if not isinstance(commit, pygit2.Commit):
+        raise HTTPException(status_code=404, detail="Not a commit object")
+    if commit.parents:
+        diff = repo.diff(commit.parents[0], commit)
+    else:
+        empty_tree = repo.revparse_single(_EMPTY_TREE_OID)
+        diff = repo.diff(empty_tree, commit.tree)
+    parts: list[str] = []
+    for patch in diff:
+        if patch.text:
+            parts.append(patch.text)
+    body = "".join(parts) if parts else ""
+    return PlainTextResponse(
+        body,
+        media_type="text/x-diff; charset=utf-8",
+    )
+
+
 if __name__ == "__main__":
     import uvicorn
     uvicorn.run(app, host="0.0.0.0", port=8000)
diff --git a/pygitweb/templates/table.html b/pygitweb/templates/table.html
index 6f451ec..45544b7 100644
--- a/pygitweb/templates/table.html
+++ b/pygitweb/templates/table.html
@@ -1,11 +1,13 @@
-<table>
+<table class="table table-vcenter card-table">
+<thead>
 <tr>{% for col in cols %}
 <th>{{col}}</th>
 {% endfor %}
 </tr>
+</thead>
 <tbody>{% for row in rows %}
-<tr>{% for cell in row %}   
-<td>{{cell}}</td>
+<tr>{% for cell in row %}
+<td>{{ cell | safe }}</td>
 {% endfor %}
 </tr>
 {% endfor %}
