1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""Discover PyGitWeb projects (git repositories under PROJECTROOT)."""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import unquote
from pygit2 import GitError, Repository, discover_repository
from pygittools.tui.branches import current_branch_name
from pygittools.tui.pygitweb_layout import PygitwebLayout
@dataclass(frozen=True, slots=True)
class ProjectEntry:
path: str
worktree: Path
branch: str
def list_projects(layout: PygitwebLayout) -> list[ProjectEntry]:
projects_list = layout.projects_list
if projects_list.is_dir():
raw = _find_projects_in_dir(layout)
elif projects_list.is_file():
raw = _projects_from_file(layout)
else:
raw = []
entries: list[ProjectEntry] = []
for project_path, worktree in raw:
entries.append(
ProjectEntry(
path=project_path,
worktree=worktree,
branch=_branch_for_worktree(worktree),
),
)
return sorted(entries, key=lambda entry: entry.path.casefold())
def _branch_for_worktree(worktree: Path) -> str:
try:
repo = Repository(str(worktree))
except GitError:
return "?"
return current_branch_name(repo)
def _find_projects_in_dir(layout: PygitwebLayout) -> list[tuple[str, Path]]:
root = layout.projects_list.resolve()
projectroot = layout.projectroot.resolve()
result: list[tuple[str, Path]] = []
for dirpath, dirnames, _ in os.walk(root, topdown=True):
rel = os.path.relpath(dirpath, root)
depth = 0 if rel == "." else rel.count(os.sep) + 1
if depth >= layout.project_maxdepth:
dirnames.clear()
continue
for name in list(dirnames):
path = Path(dirpath) / name
if not path.is_dir():
continue
try:
if not os.access(path, os.X_OK):
continue
except OSError:
continue
discovered = discover_repository(str(path))
if discovered is None:
continue
project_path = os.path.relpath(path, projectroot).replace("\\", "/")
if not layout.list_all and not _export_ok(path, layout.export_ok):
continue
worktree = path.resolve()
result.append((project_path, worktree))
dirnames.remove(name)
return result
def _projects_from_file(layout: PygitwebLayout) -> list[tuple[str, Path]]:
projectroot = layout.projectroot.resolve()
result: list[tuple[str, Path]] = []
try:
lines = layout.projects_list.read_text(encoding="utf-8").splitlines()
except OSError:
return result
for line in lines:
line = line.strip()
if not line:
continue
parts = line.split(None, 1)
project_path = unquote(parts[0]) if parts else ""
if not project_path:
continue
worktree = (projectroot / project_path).resolve()
if not worktree.is_dir():
continue
if discover_repository(str(worktree)) is None:
continue
if not layout.list_all and not _export_ok(worktree, layout.export_ok):
continue
result.append((project_path, worktree))
return result
def _export_ok(worktree: Path, export_ok: str) -> bool:
if export_ok and not (worktree / export_ok).is_file():
return False
return discover_repository(str(worktree)) is not None