Klaus Demo klaus / ee9d780
Display invalid repositories list in repos view Instead of returning an error 500 when a repository is invalid, keep track of invalid repositories and display them in the repository list with an error message. Nephe authored 4 months ago Jonas Haag committed 4 months ago
8 changed file(s) with 77 addition(s) and 21 deletion(s). Raw diff Collapse all Expand all
11 import flask
22 import httpauth
33 import dulwich.web
4 from dulwich.errors import NotGitRepository
45 from klaus import views, utils
5 from klaus.repo import FancyRepo
6 from klaus.repo import FancyRepo, InvalidRepo
67
78
89 KLAUS_VERSION = utils.guess_git_revision() or '1.4.0'
1617
1718 def __init__(self, repo_paths, site_name, use_smarthttp, ctags_policy='none'):
1819 """(See `make_app` for parameter descriptions.)"""
19 repo_objs = [FancyRepo(path) for path in repo_paths]
20 self.repos = dict((repo.name, repo) for repo in repo_objs)
2120 self.site_name = site_name
2221 self.use_smarthttp = use_smarthttp
2322 self.ctags_policy = ctags_policy
23
24 valid_repos, invalid_repos = self.load_repos(repo_paths)
25 self.valid_repos = {repo.name: repo for repo in valid_repos}
26 self.invalid_repos = {repo.name: repo for repo in invalid_repos}
2427
2528 flask.Flask.__init__(self, __name__)
2629
7881 else:
7982 raise ValueError("Unknown ctags policy %r" % self.ctags_policy)
8083
84 def load_repos(self, repo_paths):
85 valid_repos = []
86 invalid_repos = []
87 for path in repo_paths:
88 try:
89 valid_repos.append(FancyRepo(path))
90 except NotGitRepository:
91 invalid_repos.append(InvalidRepo(path))
92 return valid_repos, invalid_repos
8193
8294
8395 def make_app(repo_paths, site_name, use_smarthttp=False, htdigest_file=None,
127139 if use_smarthttp:
128140 # `path -> Repo` mapping for Dulwich's web support
129141 dulwich_backend = dulwich.server.DictBackend(
130 dict(('/'+name, repo) for name, repo in app.repos.items())
142 {'/'+name: repo for name, repo in app.valid_repos.items()}
131143 )
132144 # Dulwich takes care of all Git related requests/URLs
133145 # and passes through everything else to klaus
88 from dulwich.errors import NotTreeError
99 import dulwich, dulwich.patch
1010
11 from klaus.utils import force_unicode, parent_directory, encode_for_git, decode_from_git
11 from klaus.utils import force_unicode, parent_directory, repo_human_name, \
12 encode_for_git, decode_from_git
1213 from klaus.diff import render_diff
1314
1415
2526
2627 class FancyRepo(dulwich.repo.Repo):
2728 """A wrapper around Dulwich's Repo that adds some helper methods."""
28 # TODO: factor out stuff into dulwich
2929 @property
3030 def name(self):
31 """Get repository name from path.
32
33 1. /x/y.git -> /x/y and /x/y/.git/ -> /x/y//
34 2. /x/y/ -> /x/y
35 3. /x/y -> y
36 """
37 path = self.path.rstrip(os.sep).split(os.sep)[-1]
38 if path.endswith('.git'):
39 path = path[:-4]
40 return path
41
31 return repo_human_name(self.path)
32
33 # TODO: factor out stuff into dulwich
4234 def get_last_updated_at(self):
4335 """Get datetime of last commit to this repository."""
4436 # Cache result to speed up repo_list.html template.
328320 if self.__last_updated_at is NOT_SET:
329321 self.__last_updated_at = self.__repo.get_last_updated_at()
330322 return self.__last_updated_at
323
324
325 class InvalidRepo:
326 """Represent an invalid repository and store pertinent data."""
327 def __init__(self, path):
328 self.path = path
329
330 @property
331 def name(self):
332 return repo_human_name(self.path)
123123 .repolist li a:hover { text-decoration: none; }
124124 .repolist li a:hover .name { text-decoration: underline; }
125125
126 .invalid { color: red; }
127 .invalid .reason {
128 color: #737373;
129 font-size: 60%;
130 margin-left: 1px;
131 }
126132
127133 /* Base styles for history and commit views */
128134 .commit {
3535 {% endfor %}
3636 </ul>
3737
38 {% if invalid_repos %}
39 <ul class="repolist invalid">
40 {% for repo in invalid_repos %}
41 <li>
42 <div class=name>{{ repo.name }}</div>
43 <div class=reason>
44 Invalid git repository
45 </div>
46 </li>
47 {% endfor %}
48 </ul>
49 {% endif%}
50
3851 {% endblock %}
268268 return "%s@%s" % (repo_name, rev)
269269 else:
270270 return "%s-%s" % (repo_name, rev)
271
272
273 def repo_human_name(path):
274 """Get repository name from path.
275
276 1. /x/y.git -> /x/y and /x/y/.git/ -> /x/y//
277 2. /x/y/ -> /x/y
278 3. /x/y -> y
279 """
280 name = path.rstrip(os.sep).split(os.sep)[-1]
281 if name.endswith('.git'):
282 name = name[:-4]
283 return name
3535 sort_key = lambda repo: repo.name
3636 else:
3737 sort_key = lambda repo: (-(repo.fast_get_last_updated_at() or -1), repo.name)
38 repos = sorted([repo.freeze() for repo in current_app.repos.values()],
38 repos = sorted([repo.freeze() for repo in current_app.valid_repos.values()],
3939 key=sort_key)
40 return render_template('repo_list.html', repos=repos, base_href=None)
40 invalid_repos = sorted(current_app.invalid_repos.values(), key=lambda repo: repo.name)
41 return render_template('repo_list.html', repos=repos, invalid_repos=invalid_repos,
42 base_href=None)
4143
4244
4345
5153 rev += "/" + path.rstrip("/")
5254
5355 try:
54 repo = current_app.repos[repo]
56 repo = current_app.valid_repos[repo]
5557 except KeyError:
5658 raise NotFound("No such repository %r" % repo)
5759
4242 response = requests.get(UNAUTH_TEST_REPO_URL + "tree/HEAD/folder").text
4343 assert "blob/HEAD/test.txt" not in response
4444 assert "blob/HEAD/folder/test.txt" in response
45
46 def test_display_invalid_repos():
47 with serve():
48 response = requests.get(UNAUTH_TEST_SERVER).text
49 assert '<ul class="repolist invalid">' in response
50 assert '<div class=name>invalid_repo</div>' in response
2222 TEST_REPO_DONT_RENDER = os.path.abspath("tests/repos/build/dont-render")
2323 TEST_REPO_DONT_RENDER_URL = UNAUTH_TEST_SERVER + "dont-render/"
2424
25 ALL_TEST_REPOS = [TEST_REPO, TEST_REPO_NO_NEWLINE, TEST_REPO_DONT_RENDER]
25 TEST_INVALID_REPO = os.path.abspath("tests/repos/build/invalid_repo")
26
27 ALL_TEST_REPOS = [TEST_REPO, TEST_REPO_NO_NEWLINE, TEST_REPO_DONT_RENDER, TEST_INVALID_REPO]
2628
2729
2830 @contextlib.contextmanager