diff --git a/pygitweb/git_helpers.py b/pygitweb/git_helpers.py
index 66ab298..e24663f 100644
--- a/pygitweb/git_helpers.py
+++ b/pygitweb/git_helpers.py
@@ -1,10 +1,9 @@
 """
 Git helpers via pygit2: open repo, rev_parse, config, refs, ls_tree, cat_file, etc.
-Ported from gitweb/gitweb.perl (git_cmd, git_get_head_hash, git_get_hash, git_get_type,
-git_parse_project_config, config_to_bool, config_to_int, config_to_multi, git_get_project_config,
-git_get_hash_by_path, git_get_path_by_hash, git_get_file_or_project_config,
-git_get_project_description, git_get_project_category, git_get_references, git_get_heads_list,
-git_get_tags_list, git_get_remotes_list, parse_commit, parse_tag, etc.).
+Ported from gitweb/gitweb.perl (git_cmd, git_get_type, git_parse_project_config, config_to_bool,
+config_to_int, git_get_project_config, git_get_file_or_project_config, git_get_project_description,
+git_get_references, git_get_heads_list, git_get_tags_list, git_get_remotes_info, parse_commit,
+parse_tag, etc.).
 """
 
 from __future__ import annotations
@@ -33,36 +32,6 @@ def open_repo(project: str):
 	return pygit2.Repository(path)
 
 
-def git_get_head_hash(project: str) -> str | None:
-	"""HEAD commit OID. Port of git_get_head_hash (pygit2: repo.head.target)."""
-	try:
-		repo = open_repo(project)
-		return str(repo.head.target) if repo.head else None
-	except (pygit2.GitError, OSError):
-		return None
-
-
-def git_get_full_hash(project: str, ref: str) -> str | None:
-	"""Full OID for ref. Port of git_get_full_hash (pygit2 rev_parse)."""
-	return git_get_hash(project, ref)
-
-
-def git_get_short_hash(project: str, ref: str, length: int = 7) -> str | None:
-	"""Short OID. Port of git_get_short_hash."""
-	full = git_get_hash(project, ref)
-	return full[:length] if full else None
-
-
-def git_get_hash(project: str, ref: str) -> str | None:
-	"""Resolve ref to full OID. Port of git_get_hash (pygit2 revparse_single)."""
-	try:
-		repo = open_repo(project)
-		obj = repo.revparse_single(ref)
-		return str(obj.id) if obj else None
-	except (KeyError, pygit2.GitError, OSError):
-		return None
-
-
 def git_get_type(project: str, ref: str) -> str | None:
 	"""Object type: commit, tree, blob, tag. Port of git_get_type (pygit2 obj.type)."""
 	try:
@@ -123,13 +92,6 @@ def config_to_int(val: str | None) -> int | str:
 	return val
 
 
-def config_to_multi(val: Any) -> list[Any]:
-	"""Config value to list. Port of config_to_multi."""
-	if isinstance(val, list):
-		return val
-	return [val] if val is not None else []
-
-
 # Per-repo cache for gitweb config (git_parse_project_config result)
 _config_cache: dict[str, tuple[str, dict[str, Any]]] = {}
 
@@ -164,22 +126,6 @@ def git_get_project_config(
 	return raw
 
 
-def git_get_hash_by_path(project: str, base: str, path: str, obj_type: str | None = None) -> str | None:
-	"""OID of path at base (tree-ish). Port of git_get_hash_by_path (pygit2 tree path lookup)."""
-	try:
-		repo = open_repo(project)
-		tree = repo.revparse_single(base).peel(pygit2.Tree)
-		path = path.rstrip("/")
-		entry = tree / path
-		if not entry:
-			return None
-		if obj_type and entry.type_str != obj_type:
-			return None
-		return str(entry.id)
-	except (KeyError, pygit2.GitError, OSError):
-		return None
-
-
 def get_tree_at_ref_path(project: str, ref: str | None, path: str | None) -> tuple[pygit2.Tree, str] | None:
 	"""
 	Resolve the tree at ref (commit or tree) and optional path.
@@ -229,31 +175,6 @@ def get_blob_at_ref_path(project: str, ref: str | None, path: str | None) -> tup
 		return None
 
 
-def git_get_path_by_hash(project: str, base: str, oid_str: str) -> str | None:
-	"""Path of object with given OID in base tree. Port of git_get_path_by_hash."""
-	try:
-		repo = open_repo(project)
-		tree = repo.revparse_single(base).peel(pygit2.Tree)
-
-		def find_in_tree(t: pygit2.Tree, prefix: str) -> str | None:
-			for e in t:
-				name = e.name or ""
-				p = f"{prefix}{name}" if prefix else name
-				if str(e.id) == oid_str:
-					return p
-				if e.type_str == "tree":
-					subtree = repo[e.id]
-					if isinstance(subtree, pygit2.Tree):
-						found = find_in_tree(subtree, p + "/")
-						if found:
-							return found
-			return None
-
-		return find_in_tree(tree, "")
-	except (KeyError, pygit2.GitError, OSError):
-		return None
-
-
 def git_get_file_or_project_config(project: str, name: str) -> str | None:
 	"""Value from $GIT_DIR/name file or gitweb.name config. Port of git_get_file_or_project_config."""
 	path = os.path.join(_repo_path(project), name)
@@ -272,11 +193,6 @@ def git_get_project_description(project: str) -> str | None:
 	return git_get_file_or_project_config(project, "description")
 
 
-def git_get_project_category(project: str) -> str | None:
-	"""Category file. Port of git_get_project_category."""
-	return git_get_file_or_project_config(project, "category")
-
-
 def git_get_references(project: str, ref_prefix: str = "refs/heads") -> list[tuple[str, str]]:
 	"""List (ref_name, oid) for prefix. Port of git_get_references (pygit2 references)."""
 	try:
@@ -326,15 +242,6 @@ def git_get_tags_list(project: str) -> list[tuple[str, str, str]]:
 		return []
 
 
-def git_get_remotes_list(project: str) -> list[str]:
-	"""Remote names. Port of git_get_remotes_list (pygit2 remotes)."""
-	try:
-		repo = open_repo(project)
-		return list(repo.remotes.names())
-	except (pygit2.GitError, OSError):
-		return []
-
-
 def git_get_remotes_info(project: str) -> list[dict[str, Any]]:
 	"""Remote info: name, url, push_url. Uses pygit2 Remote.url and Remote.push_url."""
 	try:
diff --git a/pygitweb/projects.py b/pygitweb/projects.py
index f263211..d934f34 100644
--- a/pygitweb/projects.py
+++ b/pygitweb/projects.py
@@ -172,13 +172,16 @@ def git_get_project_owner(
 
 def git_get_last_activity(project: str, projectroot: str | None = None) -> int | None:
 	"""Last commit timestamp for project. Port of git_get_last_activity."""
-	from pygitweb.git_helpers import git_get_head_hash, parse_commit
-
-	oid = git_get_head_hash(project)
-	if not oid:
+	try:
+		repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
+		if not repo.head:
+			return None
+		commit = repo[repo.head.target]
+		if isinstance(commit, pygit2.Commit):
+			return commit.committer.time
+	except (KeyError, pygit2.GitError, OSError):
 		return None
-	co = parse_commit(project, oid)
-	return co.get("committer_epoch")
+	return None
 
 
 def search_projects_list(
diff --git a/pygitweb/settings.py b/pygitweb/settings.py
index ce73793..e5b8f50 100644
--- a/pygitweb/settings.py
+++ b/pygitweb/settings.py
@@ -5,6 +5,7 @@ Forms are generated from type() and docstrings; project config shows only LOCAL/
 
 from __future__ import annotations
 
+import os
 from contextlib import suppress
 from typing import Any
 from urllib.parse import quote
@@ -14,7 +15,6 @@ from fastapi import APIRouter, HTTPException, Request
 from fastapi.responses import HTMLResponse, RedirectResponse
 
 from pygitweb.config import settings
-from pygitweb.git_helpers import open_repo
 from pygitweb.projects import git_get_projects_list
 from pygitweb.templates_env import POSTAMBLE, PREAMBLE, env, jinja_escape
 from pygitweb.validation import is_valid_project
@@ -251,14 +251,14 @@ def _get_project_config_entries(project: str) -> list[tuple[str, str]]:
 	Return list of (name, value) for repo config, only from LOCAL (5) or WORKTREE (6).
 	For each name we keep the last value (highest priority when iterating).
 	"""
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	cfg = repo.config
 	# Iterate; for level in (5, 6) keep last value per name
 	by_name: dict[str, str] = {}
 	for entry in cfg:
 		if entry.level not in (GIT_CONFIG_LEVEL_LOCAL, GIT_CONFIG_LEVEL_WORKTREE):
 			continue
-		by_name[entry.name] = entry.value
+		by_name[entry.name] = entry.value or ""
 	return [(k, v) for k, v in sorted(by_name.items())]
 
 
@@ -422,7 +422,7 @@ async def settings_project_submit(request: Request, name: str):
 		v = str(form.get(f"config_value_{i}") or "").strip() or ""
 		if k:
 			pairs.append((k, v))
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	cfg = repo.config
 	for config_key, config_value in pairs:
 		try:
diff --git a/pygitweb/tasks.py b/pygitweb/tasks.py
index 7962eee..0f15cbb 100644
--- a/pygitweb/tasks.py
+++ b/pygitweb/tasks.py
@@ -5,6 +5,7 @@ Data is stored in the repo ODB/RefDB via pygittools.tasks (Board, Task, Comment)
 
 from __future__ import annotations
 
+import os
 import time
 from typing import Any
 
@@ -24,7 +25,6 @@ from pygittools.tasks import (
 	get_task_by_oid,
 )
 from pygitweb.config import settings
-from pygitweb.git_helpers import git_get_references, open_repo
 from pygitweb.projects import git_get_projects_list
 from pygitweb.validation import is_valid_project
 
@@ -81,7 +81,7 @@ def create_board_for_project(
 	description: str = "",
 ) -> str:
 	"""Create a board in the project repo. Returns the board ref. Raises HTTPException on error."""
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = _board_ref(name)
 	if ref in repo.references:
 		raise HTTPException(status_code=409, detail="Board already exists")
@@ -104,8 +104,12 @@ def boards_list(
 	"""List all boards for the project."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
-	refs = git_get_references(project, BOARD_REF_PREFIX.rstrip("/"))
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
+	refs = [
+		(ref_name, str(repo.references[ref_name].resolve().target))
+		for ref_name in repo.references
+		if ref_name.startswith(BOARD_REF_PREFIX)
+	]
 	boards = []
 	for ref_name, _ in refs:
 		try:
@@ -133,7 +137,7 @@ def boards_create(
 	_validate_project(project)
 	if not name or "/" in name or ".." in name:
 		raise HTTPException(status_code=400, detail="Invalid board name")
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = _board_ref(name)
 	if ref in repo.references:
 		raise HTTPException(status_code=409, detail="Board already exists")
@@ -152,7 +156,7 @@ def boards_delete(
 	"""Delete a board (removes ref; tag object remains in ODB)."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = _board_ref(name)
 	if ref not in repo.references:
 		raise HTTPException(status_code=404, detail="Board not found")
@@ -193,7 +197,7 @@ def get_board_tasks_grouped(
 	Returns a list of { "status": str, "label": str, "tasks": [ _task_to_json, ... ] }
 	in BOARD_STATUS_ORDER. Skips validation/auth (caller must validate project).
 	"""
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = _board_ref(board_name)
 	b = get_board(repo, ref)
 	task_oids = getattr(b, "tasks", []) or []
@@ -229,7 +233,7 @@ def task_list(
 	"""List all tasks on a board."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = _board_ref(board)
 	b = get_board(repo, ref)
 	if not b:
@@ -262,7 +266,7 @@ def task_create(
 	"""Create a new task on a board."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	board_ref = _board_ref(board)
 	b = get_board(repo, board_ref)
 	if not b:
@@ -313,7 +317,7 @@ async def task_update(
 		body = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
 	except Exception:
 		body = {}
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = task_ref if task_ref.startswith("refs/") else _task_ref(task_ref)
 	t = get_task(repo, ref)
 	if not t:
@@ -359,7 +363,7 @@ def task_delete(
 	"""Delete a task (remove ref and remove from board.tasks)."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	ref = task_ref if task_ref.startswith("refs/") else _task_ref(task_ref)
 	if ref not in repo.references:
 		raise HTTPException(status_code=404, detail="Task not found")
@@ -401,7 +405,7 @@ def comment_list(
 	"""List all comments for a task."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	task_ref = task if task.startswith("refs/") else _task_ref(task)
 	t = get_task(repo, task_ref)
 	if not t:
@@ -430,7 +434,7 @@ def comment_create(
 	"""Create a new comment on a task."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	task_ref = task if task.startswith("refs/") else _task_ref(task)
 	t = get_task(repo, task_ref)
 	if not t:
@@ -461,7 +465,7 @@ async def comment_update(
 		body = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
 	except Exception:
 		body = {}
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	oid = pygit2.Oid(hex=comment_oid)
 	c = get_comment(repo, oid)
 	if not c:
@@ -498,7 +502,7 @@ def comment_delete(
 	"""Delete a comment (object remains in ODB; caller may remove from task.comments)."""
 	_require_auth_disabled()
 	_validate_project(project)
-	repo = open_repo(project)
+	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	oid = pygit2.Oid(hex=comment_oid)
 	try:
 		c = get_comment(repo, oid)
diff --git a/pygitweb/timeline_cache.py b/pygitweb/timeline_cache.py
index 2547a6a..56dfbdb 100644
--- a/pygitweb/timeline_cache.py
+++ b/pygitweb/timeline_cache.py
@@ -4,13 +4,13 @@ Per-project timeline cache for recent repository activity.
 
 from __future__ import annotations
 
+import os
 import threading
 from typing import Literal, TypedDict
 
 import pygit2
 
 from pygitweb.config import settings
-from pygitweb.git_helpers import open_repo
 from pygitweb.projects import git_get_projects_list
 
 TIMELINE_CACHE_SIZE: int = 10
@@ -121,7 +121,7 @@ def _collect_ref_events(repo: pygit2.Repository) -> list[TimelineEvent]:
 
 def build_project_timeline_cache(project: str, max_events: int = TIMELINE_CACHE_SIZE) -> list[TimelineEvent]:
 	try:
-		repo: pygit2.Repository = open_repo(project)
+		repo: pygit2.Repository = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
 	except (pygit2.GitError, OSError, ValueError):
 		return []
 
