Klaus Demo ~jonashaag/klaus / d96a5a7
Add repo namespaces Currently only available through manual make_app() call Jonas Haag 16 days ago
16 changed file(s) with 211 addition(s) and 92 deletion(s). Raw diff Collapse all Expand all
2222 self.ctags_policy = ctags_policy
2323
2424 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}
25 self.valid_repos = {repo.namespaced_name: repo for repo in valid_repos}
26 self.invalid_repos = {repo.namespaced_name: repo for repo in invalid_repos}
2727
2828 flask.Flask.__init__(self, __name__)
2929
7171 ('download', '/<repo>/tarball/<path:rev>/'),
7272 ]:
7373 self.add_url_rule(rule, view_func=getattr(views, endpoint))
74 if "<repo>" in rule:
75 self.add_url_rule(
76 "/~<namespace>" + rule, view_func=getattr(views, endpoint)
77 )
7478 # fmt: on
7579
7680 def should_use_ctags(self, git_repo, git_commit):
8690 def load_repos(self, repo_paths):
8791 valid_repos = []
8892 invalid_repos = []
89 for path in repo_paths:
90 try:
91 valid_repos.append(FancyRepo(path))
92 except NotGitRepository:
93 invalid_repos.append(InvalidRepo(path))
93 for namespace, paths in repo_paths.items():
94 for path in paths:
95 try:
96 valid_repos.append(FancyRepo(path, namespace))
97 except NotGitRepository:
98 invalid_repos.append(InvalidRepo(path, namespace))
9499 return valid_repos, invalid_repos
95100
96101
107112 """
108113 Returns a WSGI app with all the features (smarthttp, authentication)
109114 already patched in.
110
111 :param repo_paths: List of paths of repositories to serve.
115 :param repo_paths: Repositories to serve. This can either be a list of paths
116 or dictionary of the following form:
117 {
118 "namespace1": [list of paths of repositories],
119 "namespace2": [list of paths of repositories],
120 ...
121 None: [list of paths of repositories without namespace]
122 }
112123 :param site_name: Name of the Web site (e.g. "John Doe's Git Repositories")
113124 :param use_smarthttp: Enable Git Smart HTTP mode, which makes it possible to
114125 pull from the served repositories. If `htdigest_file` is set as well,
140151 raise ValueError(
141152 "'htdigest_file' set without 'use_smarthttp' or 'require_browser_auth'"
142153 )
154 if not isinstance(repo_paths, dict):
155 # If repos is given as a flat list, put all repos under the "no namespace" namespace
156 repo_paths = {None: repo_paths}
143157
144158 app = Klaus(
145159 repo_paths,
152166 if use_smarthttp:
153167 # `path -> Repo` mapping for Dulwich's web support
154168 dulwich_backend = dulwich.server.DictBackend(
155 {"/" + name: repo for name, repo in app.valid_repos.items()}
169 {
170 "/" + namespaced_name: repo
171 for namespaced_name, repo in app.valid_repos.items()
172 }
156173 )
157174 # Dulwich takes care of all Git related requests/URLs
158175 # and passes through everything else to klaus
176193 # Git will never call /<repo-name>/git-receive-pack if authentication
177194 # failed for /info/refs, but since it's used to upload stuff to the server
178195 # we must secure it anyway for security reasons.
179 PATTERN = r"^/[^/]+/(info/refs\?service=git-receive-pack|git-receive-pack)$"
196 PATTERN = (
197 r"^/(~[^/]+/)?[^/]+/(info/refs\?service=git-receive-pack|git-receive-pack)$"
198 )
180199 if unauthenticated_push:
181200 # DANGER ZONE: Don't require authentication for push'ing
182201 app.wsgi_app = dulwich_wrapped_app
3232 class FancyRepo(dulwich.repo.Repo):
3333 """A wrapper around Dulwich's Repo that adds some helper methods."""
3434
35 def __init__(self, path, namespace):
36 super().__init__(path)
37 self.namespace = namespace
38
3539 @property
3640 def name(self):
3741 return repo_human_name(self.path)
42
43 @property
44 def namespaced_name(self):
45 if self.namespace:
46 return f"~{self.namespace}/{self.name}"
47 else:
48 return self.name
3849
3950 # TODO: factor out stuff into dulwich
4051 def get_last_updated_at(self):
342353 class InvalidRepo:
343354 """Represent an invalid repository and store pertinent data."""
344355
345 def __init__(self, path):
356 def __init__(self, path, namespace):
346357 self.path = path
358 self.namespace = namespace
347359
348360 @property
349361 def name(self):
350362 return repo_human_name(self.path)
363
364 @property
365 def namespaced_name(self):
366 if self.namespace:
367 return f"~{self.namespace}/{self.name}"
368 else:
369 return self.name
55
66 {% block breadcrumbs %}
77 <span>
8 <a href="{{ url_for('index', repo=repo.name) }}">{{ repo.name }}</a>
8 <a href="{{ url_for('index', repo=repo.name, namespace=namespace) }}">{{ repo.namespaced_name }}</a>
99 <span class=slash>/</span>
10 <a href="{{ url_for('history', repo=repo.name, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
10 <a href="{{ url_for('history', repo=repo.name, namespace=namespace, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
1111 </span>
1212
1313 {% if subpaths %}
1616 {% if loop.last %}
1717 <a href="">{{ name|force_unicode }}</a>
1818 {% else %}
19 <a href="{{ url_for('history', repo=repo.name, rev=rev, path=subpath) }}">{{ name|force_unicode }}</a>
19 <a href="{{ url_for('history', repo=repo.name, namespace=namespace, rev=rev, path=subpath) }}">{{ name|force_unicode }}</a>
2020 <span class=slash>/</span>
2121 {% endif %}
2222 {% endfor %}
3030 <div>
3131 <ul class=branches>
3232 {% for branch in branches %}
33 <li><a href="{{ url_for(view, repo=repo.name, rev=branch, path=path) }}">{{ branch }}</a></li>
33 <li><a href="{{ url_for(view, repo=repo.name, namespace=namespace, rev=branch, path=path) }}">{{ branch }}</a></li>
3434 {% endfor %}
3535 </ul>
3636 {% if tags %}
3737 <ul class=tags>
3838 {% for tag in tags %}
39 <li><a href="{{ url_for(view, repo=repo.name, rev=tag, path=path) }}">{{ tag }}</a></li>
39 <li><a href="{{ url_for(view, repo=repo.name, namespace=namespace, rev=tag, path=path) }}">{{ tag }}</a></li>
4040 {% endfor %}
4141 </ul>
4242 {% endif %}
1111 <h2>
1212 {{ filename|force_unicode }}
1313 <span>
14 @<a href="{{ url_for('commit', repo=repo.name, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
14 @<a href="{{ url_for('commit', repo=repo.name, namespace=namespace, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
1515 </span>
1616 </h2>
1717 {% if not can_render %}
2626 {%- if commit == None %}
2727 &nbsp;
2828 {%- else %}
29 <a href="{{ url_for('commit', repo=repo.name, rev=commit) }}">{{ commit | shorten_sha1 }}</a>
29 <a href="{{ url_for('commit', repo=repo.name, namespace=namespace, rev=commit) }}">{{ commit | shorten_sha1 }}</a>
3030 {%- endif -%}
3131 {%- endfor -%}
3232 </pre>
44 {% if n is none %}
55 <span class=n>...</span>
66 {% else %}
7 <a href="{{ url_for('history', repo=repo.name, rev=rev, path=path)}}?page={{n}}" class=n>{{ n }}</a>
7 <a href="{{ url_for('history', repo=repo.name, namespace=namespace, rev=rev, path=path)}}?page={{n}}" class=n>{{ n }}</a>
88 {% endif %}
99 {% endfor %}
1010 {% endif %}
1111 {% if more_commits %}
12 <a href="{{ url_for('history', repo=repo.name, rev=rev, path=path)}}?page={{page+1}}">»»</a>
12 <a href="{{ url_for('history', repo=repo.name, namespace=namespace, rev=rev, path=path)}}?page={{page+1}}">»»</a>
1313 {% elif page %}
1414 <span>»»</span>
1515 {% endif%}
3131 Commit History
3232 {% endif %}
3333 <span>
34 @<a href="{{ url_for('index', repo=repo.name, rev=rev) }}">{{ rev }}</a>
34 @<a href="{{ url_for('index', repo=repo.name, namespace=namespace, rev=rev) }}">{{ rev }}</a>
3535 </span>
3636 {% if USE_SMARTHTTP %}
37 <code>git clone {{ url_for('index', repo=repo.name, _external=True) }}</code>
37 <code>git clone {{ url_for('index', repo=repo.name, namespace=namespace, _external=True) }}</code>
3838 {% endif %}
3939 {% if repo.cloneurl %}
4040 <code>git clone {{ repo.cloneurl }}</code>
4646 <ul>
4747 {% for commit in history %}
4848 <li>
49 <a class=commit href="{{ url_for('commit', repo=repo.name, rev=commit.id|force_unicode) }}">
49 <a class=commit href="{{ url_for('commit', repo=repo.name, namespace=namespace, rev=commit.id|force_unicode) }}">
5050 <span class=line1>
5151 <span>{{ commit.message|force_unicode|shorten_message }}</span>
5252 </span>
2828 <li>
2929 <a
3030 {% if last_updated_at %}
31 href="{{ url_for('index', repo=repo.name) }}"
31 href="{{ url_for('index', namespace=repo.namespace, repo=repo.name) }}"
3232 {% endif %}
3333 >
34 <div class=name>{{ repo.name }}</div>
34 <div class=name>{{ repo.namespaced_name }}</div>
3535 {% if description %}
3636 <div class=description>{{ description }}</div>
3737 {% endif %}
5353 <ul class="repolist invalid">
5454 {% for repo in invalid_repos %}
5555 <li>
56 <div class=name>{{ repo.name }}</div>
56 <div class=name>{{ repo.namespaced_name }}</div>
5757 <div class=reason>
5858 Invalid git repository
5959 </div>
88 <h2>
99 {{ path|force_unicode }}
1010 <span>
11 @<a href="{{ url_for('commit', repo=repo.name, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
11 @<a href="{{ url_for('commit', repo=repo.name, namespace=namespace, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
1212 </span>
1313 </h2>
1414 <p>The path at {{ path|force_unicode }} contains a submodule, revision {{ submodule_rev }}. {% if submodule_url %} It can be checked out from <a href="{{ submodule_url }}">{{ submodule_url }}</a>. {% endif %}
00 <div class=tree>
1 <h2>Tree @<a href="{{ url_for('commit', repo=repo.name, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
2 <span>(<a href="{{ url_for('download', repo=repo.name, rev=rev) }}">Download .tar.gz</a>)</span>
1 <h2>Tree @<a href="{{ url_for('commit', namespace=namespace, repo=repo.name, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
2 <span>(<a href="{{ url_for('download', namespace=namespace, repo=repo.name, rev=rev) }}">Download .tar.gz</a>)</span>
33 </h2>
44 <ul>
55 {% for name, fullpath in root_tree.dirs %}
6 <li><a href="{{ url_for('history', repo=repo.name, rev=rev, path=fullpath) }}" class=dir>{{ name }}</a></li>
6 <li><a href="{{ url_for('history', namespace=namespace, repo=repo.name, rev=rev, path=fullpath) }}" class=dir>{{ name }}</a></li>
77 {% endfor %}
88 {% for name, fullpath in root_tree.submodules %}
9 <li><a href="{{ url_for('submodule', repo=repo.name, rev=rev, path=fullpath) }}" class=submodule>{{ name }}</a></li>
9 <li><a href="{{ url_for('submodule', namespace=namespace, repo=repo.name, rev=rev, path=fullpath) }}" class=submodule>{{ name }}</a></li>
1010 {% endfor %}
1111 {% for name, fullpath in root_tree.files %}
12 <li><a href="{{ url_for('blob', repo=repo.name, rev=rev, path=fullpath) }}">{{ name }}</a></li>
12 <li><a href="{{ url_for('blob', namespace=namespace, repo=repo.name, rev=rev, path=fullpath) }}">{{ name }}</a></li>
1313 {% endfor %}
1414 </ul>
1515 </div>
77
88 {% include 'tree.inc.html' %}
99
10 {% set raw_url = url_for('raw', repo=repo.name, rev=rev, path=path) %}
10 {% set raw_url = url_for('raw', repo=repo.name, namespace=namespace, rev=rev, path=path) %}
1111 {% macro not_shown(reason) %}
1212 <div>
1313 ({{ reason }} not shown &mdash; <a href="{{ raw_url }}">Download file</a>)
1818 <h2>
1919 {{ filename|force_unicode }}
2020 <span>
21 @<a href="{{ url_for('commit', repo=repo.name, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
21 @<a href="{{ url_for('commit', repo=repo.name, namespace=namespace, rev=rev) }}">{{ rev|shorten_sha1 }}</a>
2222 &mdash;
2323 {% if is_markup %}
2424 {% if render_markup %}
2929 &middot;
3030 {% endif %}
3131 <a href="{{ raw_url }}">raw</a>
32 &middot; <a href="{{ url_for('history', repo=repo.name, rev=rev, path=path) }}">history</a>
32 &middot; <a href="{{ url_for('history', repo=repo.name, namespace=namespace, rev=rev, path=path) }}">history</a>
3333 {% if not is_binary and not too_large %}
34 &middot; <a href="{{ url_for('blame', repo=repo.name, rev=rev, path=path) }}">blame</a>
34 &middot; <a href="{{ url_for('blame', repo=repo.name, namespace=namespace, rev=rev, path=path) }}">blame</a>
3535 {% endif %}
3636 </span>
3737 </h2>
3232 and <span class=deletions>{{ summary.ndeletions }} deletion(s)</span>.
3333 </span>
3434 <span>
35 <a href="{{ url_for('patch', repo=repo.name, rev=commit.id|force_unicode) }}">Raw diff</a>
35 <a href="{{ url_for('patch', repo=repo.name, namespace=namespace, rev=commit.id|force_unicode) }}">Raw diff</a>
3636 </span>
3737 <span>
3838 <a href=# onclick="toggler.collapseAll('.file'); return false">Collapse all</a>
6262 {% if file.new_filename == '/dev/null' %}
6363 <del>{{ file.old_filename|force_unicode }}</del>
6464 {% else %}
65 <a href="{{ url_for('blob', repo=repo.name, rev=rev, path=file.new_filename) }}">
65 <a href="{{ url_for('blob', repo=repo.name, namespace=namespace, rev=rev, path=file.new_filename) }}">
6666 {{ file.new_filename|force_unicode }}
6767 </a>
6868 {% endif %}
5454 search_query = request.args.get("q") or ""
5555
5656 if search_query:
57 repos = [r for r in repos if search_query.lower() in r.name.lower()]
57 repos = [r for r in repos if search_query.lower() in r.namespaced_name.lower()]
5858 invalid_repos = [
59 r for r in invalid_repos if search_query.lower() in r.name.lower()
59 r
60 for r in invalid_repos
61 if search_query.lower() in r.namespaced_name.lower()
6062 ]
6163
6264 if order_by == "name":
63 sort_key = lambda repo: repo.name
65 sort_key = lambda repo: repo.namespaced_name
6466 else:
6567 sort_key = lambda repo: (
6668 -(repo.fast_get_last_updated_at() or -1),
67 repo.name,
69 repo.namespaced_name,
6870 )
6971
7072 repos = sorted(repos, key=sort_key)
71 invalid_repos = sorted(invalid_repos, key=lambda repo: repo.name)
73 invalid_repos = sorted(invalid_repos, key=lambda repo: repo.namespaced_name)
7274
7375 return render_template(
7476 "repo_list.html",
8587 return current_app.send_static_file("robots.txt")
8688
8789
88 def _get_repo_and_rev(repo, rev=None, path=None):
90 def _get_repo_and_rev(repo, namespace=None, rev=None, path=None):
8991 if path and rev:
9092 rev += "/" + path.rstrip("/")
9193
94 if namespace:
95 repo_key = f"~{namespace}/{repo}"
96 else:
97 repo_key = repo
9298 try:
93 repo = current_app.valid_repos[repo]
99 repo = current_app.valid_repos[repo_key]
94100 except KeyError:
95101 raise NotFound("No such repository %r" % repo)
96102
144150 self.view_name = view_name
145151 self.context = {}
146152
147 def dispatch_request(self, repo, rev=None, path=""):
153 def dispatch_request(self, repo, namespace=None, rev=None, path=""):
148154 """Dispatch repository, revision (if any) and path (if any). To retain
149155 compatibility with :func:`url_for`, view routing uses two arguments:
150156 rev and path, although a single path is sufficient (from Git's point of
157163
158164 [1] https://github.com/jonashaag/klaus/issues/36#issuecomment-23990266
159165 """
160 self.make_template_context(repo, rev, path.strip("/"))
166 self.make_template_context(repo, namespace, rev, path.strip("/"))
161167 return self.get_response()
162168
163169 def get_response(self):
164170 return render_template(self.template_name, **self.context)
165171
166 def make_template_context(self, repo, rev, path):
167 repo, rev, path, commit = _get_repo_and_rev(repo, rev, path)
172 def make_template_context(self, repo, namespace, rev, path):
173 repo, rev, path, commit = _get_repo_and_rev(repo, namespace, rev, path)
168174
169175 try:
170176 blob_or_tree = repo.get_blob_or_tree(commit, path)
174180 self.context = {
175181 "view": self.view_name,
176182 "repo": repo,
183 "namespace": namespace,
177184 "rev": rev,
178185 "commit": commit,
179186 "branches": repo.get_branch_names(exclude=rev),
284291 super(IndexView, self).make_template_context(*args)
285292
286293 self.context["base_href"] = url_for(
287 "blob", repo=self.context["repo"].name, rev=self.context["rev"], path=""
294 "blob",
295 repo=self.context["repo"].namespaced_name,
296 rev=self.context["rev"],
297 path="",
288298 )
289299
290300 self.context["page"] = 0
342352
343353 template_name = "submodule.html"
344354
345 def make_template_context(self, repo, rev, path):
346 repo, rev, path, commit = _get_repo_and_rev(repo, rev, path)
355 def make_template_context(self, repo, namespace, rev, path):
356 repo, rev, path, commit = _get_repo_and_rev(repo, namespace, rev, path)
347357
348358 try:
349359 submodule_rev = tree_lookup_path(
392402 raise ImportError("Ctags enabled but python-ctags not installed")
393403 ctags_base_url = url_for(
394404 self.view_name,
395 repo=self.context["repo"].name,
405 repo=self.context["repo"].namespaced_name,
396406 rev=self.context["rev"],
397407 path="",
398408 )
412422 force_unicode(self.context["blob_or_tree"].data),
413423 self.context["filename"],
414424 render_markup,
415 **ctags_args
425 **ctags_args,
416426 )
417427
418428 def make_template_context(self, *args):
1111 with serve():
1212 for file in ["binary", "image.jpg", "toolarge"]:
1313 response = requests.get(
14 TEST_REPO_DONT_RENDER_URL + "blob/HEAD/" + file
14 UNAUTH_TEST_REPO_DONT_RENDER_URL + "blob/HEAD/" + file
1515 ).text
1616 assert "blame" not in response
1717
2121 with serve():
2222 for file in ["binary", "image.jpg", "toolarge"]:
2323 response = requests.get(
24 TEST_REPO_DONT_RENDER_URL + "blame/HEAD/" + file
24 UNAUTH_TEST_REPO_DONT_RENDER_URL + "blame/HEAD/" + file
2525 ).text
2626 assert "Can't show blame" in response
44 except ImportError:
55 pass
66
7 import subprocess
8
79 import pytest
10 import requests
11
812
913 from klaus.contrib import app_args
1014 from .utils import *
11 from .test_make_app import can_reach_unauth, can_push_auth
1215
1316
1417 def check_env(env, expected_args, expected_kwargs):
4043 monkeypatch.setattr(os, "environ", os.environ.copy())
4144 check_env(
4245 {
43 "KLAUS_REPOS": TEST_REPO,
46 "KLAUS_REPOS": TEST_REPO_NO_NAMESPACE,
4447 "KLAUS_SITE_NAME": TEST_SITE_NAME,
4548 "KLAUS_HTDIGEST_FILE": HTDIGEST_FILE,
4649 "KLAUS_USE_SMARTHTTP": "yes",
4952 "KLAUS_UNAUTHENTICATED_PUSH": "0",
5053 "KLAUS_CTAGS_POLICY": "ALL",
5154 },
52 ([TEST_REPO], TEST_SITE_NAME),
55 ([TEST_REPO_NO_NAMESPACE], TEST_SITE_NAME),
5356 dict(
5457 htdigest_file=HTDIGEST_FILE,
5558 use_smarthttp=True,
6770 with pytest.raises(ValueError):
6871 check_env(
6972 {
70 "KLAUS_REPOS": TEST_REPO,
73 "KLAUS_REPOS": TEST_REPO_NO_NAMESPACE,
7174 "KLAUS_SITE_NAME": TEST_SITE_NAME,
7275 "KLAUS_HTDIGEST_FILE": HTDIGEST_FILE,
7376 "KLAUS_USE_SMARTHTTP": "unsupported",
8083 def test_wsgi(monkeypatch):
8184 """Test start of wsgi app"""
8285 monkeypatch.setattr(os, "environ", os.environ.copy())
83 os.environ["KLAUS_REPOS"] = TEST_REPO
86 os.environ["KLAUS_REPOS"] = TEST_REPO_NO_NAMESPACE
8487 os.environ["KLAUS_SITE_NAME"] = TEST_SITE_NAME
8588 from klaus.contrib import wsgi
8689
99102 def test_wsgi_autoreload(monkeypatch):
100103 """Test start of wsgi autoreload app"""
101104 monkeypatch.setattr(os, "environ", os.environ.copy())
102 os.environ["KLAUS_REPOS_ROOT"] = TEST_REPO_ROOT
105 os.environ["KLAUS_REPOS_ROOT"] = TEST_REPO_NO_NAMESPACE_ROOT
103106 os.environ["KLAUS_SITE_NAME"] = TEST_SITE_NAME
104107 from klaus.contrib import wsgi_autoreload, wsgi_autoreloading
105108
113116 reload(wsgi_autoreloading)
114117 with serve_app(wsgi_autoreload.application):
115118 assert can_push_auth()
119
120
121 def can_reach_unauth():
122 return _check_http200(_GET_unauth, TEST_REPO_NO_NAMESPACE_BASE_URL)
123
124
125 def can_push_auth():
126 return _can_push(_GET_auth, AUTH_TEST_REPO_NO_NAMESPACE_URL)
127
128
129 def _can_push(http_get, url):
130 return any(
131 [
132 _check_http200(
133 http_get,
134 TEST_REPO_NO_NAMESPACE_BASE_URL + "info/refs?service=git-receive-pack",
135 ),
136 _check_http200(
137 http_get, TEST_REPO_NO_NAMESPACE_BASE_URL + "git-receive-pack"
138 ),
139 subprocess.call(["git", "push", url, "master"], cwd=TEST_REPO_NO_NAMESPACE)
140 == 0,
141 ]
142 )
143
144
145 def _GET_unauth(url=""):
146 return requests.get(
147 UNAUTH_TEST_SERVER + url,
148 auth=requests.auth.HTTPDigestAuth("invalid", "password"),
149 )
150
151
152 def _GET_auth(url=""):
153 return requests.get(
154 AUTH_TEST_SERVER + url,
155 auth=requests.auth.HTTPDigestAuth("testuser", "testpassword"),
156 )
157
158
159 def _check_http200(http_get, url):
160 return http_get(url).status_code == 200
1010 import requests.auth
1111
1212 from .utils import *
13
14
15 def test_make_app_using_list():
16 app = klaus.make_app(REPOS, TEST_SITE_NAME)
17 with serve_app(app):
18 response = requests.get(UNAUTH_TEST_SERVER).text
19 assert TEST_REPO_NO_NEWLINE_BASE_URL in response
1320
1421
1522 def test_htdigest_file_without_smarthttp_or_require_browser_auth():
4956 else:
5057 checks = ["can_%s_unauth" % check, "can_%s_auth" % check]
5158 for check in checks:
52 assert globals()[check]() == permitted
59 assert globals()[check]() == permitted, check
5360
5461 return test
5562
115122
116123 # Reach
117124 def can_reach_unauth():
118 return _check_http200(_GET_unauth, "test_repo")
125 return _check_http200(_GET_unauth, TEST_REPO_BASE_URL)
119126
120127
121128 def can_reach_auth():
122 return _check_http200(_GET_auth, "test_repo")
129 return _check_http200(_GET_auth, TEST_REPO_BASE_URL)
123130
124131
125132 # Clone
136143 try:
137144 return any(
138145 [
139 "git clone" in http_get(TEST_REPO_URL).text,
146 "git clone" in http_get(TEST_REPO_BASE_URL).text,
140147 _check_http200(
141 http_get, TEST_REPO_URL + "info/refs?service=git-upload-pack"
148 http_get, TEST_REPO_BASE_URL + "info/refs?service=git-upload-pack"
142149 ),
143150 subprocess.call(["git", "clone", url, tmp]) == 0,
144151 ]
160167 return any(
161168 [
162169 _check_http200(
163 http_get, TEST_REPO_URL + "info/refs?service=git-receive-pack"
170 http_get, TEST_REPO_BASE_URL + "info/refs?service=git-receive-pack"
164171 ),
165 _check_http200(http_get, TEST_REPO_URL + "git-receive-pack"),
172 _check_http200(http_get, TEST_REPO_BASE_URL + "git-receive-pack"),
166173 subprocess.call(["git", "push", url, "master"], cwd=TEST_REPO) == 0,
167174 ]
168175 )
190197 def _ctags_enabled(ref, filename):
191198 response = requests.get(UNAUTH_TEST_REPO_URL + "blob/%s/%s" % (ref, filename))
192199 assert response.status_code == 200, response.text
193 href = '<a href="/%sblob/%s/%s#L-1">' % (TEST_REPO_URL, ref, filename)
200 href = '<a href="/%sblob/%s/%s#L-1">' % (TEST_REPO_BASE_URL, ref, filename)
194201 return href in response.text
195202
196203
209216
210217
211218 def _check_http200(http_get, url):
212 try:
213 return http_get(url).status_code == 200
214 except:
215 return False
219 return http_get(url).status_code == 200
2424 assert TEST_INVALID_REPO_NAME in response
2525
2626
27 def test_repo_list_search_namespace():
28 with serve():
29 response = requests.get(UNAUTH_TEST_SERVER + "?q=" + NAMESPACE).text
30 assert TEST_REPO_BASE_URL in response
31 assert not TEST_REPO_DONT_RENDER_BASE_URL in response
32 assert not TEST_REPO_NO_NEWLINE_BASE_URL in response
33 assert not TEST_INVALID_REPO_NAME in response
34
35
2736 def test_download():
2837 with serve():
2938 response = requests.get(UNAUTH_TEST_REPO_URL + "tarball/master", stream=True)
3544
3645 def test_no_newline_at_end_of_file():
3746 with serve():
38 response = requests.get(TEST_REPO_NO_NEWLINE_URL + "commit/HEAD/").text
47 response = requests.get(UNAUTH_TEST_REPO_NO_NEWLINE_URL + "commit/HEAD/").text
3948 assert response.count("No newline at end of file") == 1
4049
4150
4251 def test_dont_render_binary():
4352 with serve():
44 response = requests.get(TEST_REPO_DONT_RENDER_URL + "blob/HEAD/binary").text
53 response = requests.get(
54 UNAUTH_TEST_REPO_DONT_RENDER_URL + "blob/HEAD/binary"
55 ).text
4556 assert "Binary data not shown" in response
4657
4758
4859 def test_render_image():
4960 with serve():
50 response = requests.get(TEST_REPO_DONT_RENDER_URL + "blob/HEAD/image.jpg").text
61 response = requests.get(
62 UNAUTH_TEST_REPO_DONT_RENDER_URL + "blob/HEAD/image.jpg"
63 ).text
5164 assert '<img src="/dont-render/raw/HEAD/image.jpg"' in response
5265
5366
5467 def test_dont_render_large_file():
5568 with serve():
56 response = requests.get(TEST_REPO_DONT_RENDER_URL + "blob/HEAD/toolarge").text
69 response = requests.get(
70 UNAUTH_TEST_REPO_DONT_RENDER_URL + "blob/HEAD/toolarge"
71 ).text
5772 assert "Large file not shown" in response
5873
5974
88 TEST_SITE_NAME = "Some site"
99 HTDIGEST_FILE = "tests/credentials.htdigest"
1010
11 UNAUTH_TEST_SERVER = "http://invalid:password@localhost:9876/"
12 AUTH_TEST_SERVER = "http://testuser:testpassword@localhost:9876/"
13
14 NAMESPACE = "namespace1"
15
1116 TEST_REPO = os.path.abspath("tests/repos/build/test_repo")
1217 TEST_REPO_ROOT = os.path.abspath("tests/repos/build")
13 TEST_REPO_URL = "test_repo/"
14 UNAUTH_TEST_SERVER = "http://invalid:password@localhost:9876/"
15 UNAUTH_TEST_REPO_URL = UNAUTH_TEST_SERVER + TEST_REPO_URL
16 AUTH_TEST_SERVER = "http://testuser:testpassword@localhost:9876/"
17 AUTH_TEST_REPO_URL = AUTH_TEST_SERVER + TEST_REPO_URL
18 TEST_REPO_BASE_URL = f"~{NAMESPACE}/test_repo/"
19 UNAUTH_TEST_REPO_URL = UNAUTH_TEST_SERVER + TEST_REPO_BASE_URL
20 AUTH_TEST_REPO_URL = AUTH_TEST_SERVER + TEST_REPO_BASE_URL
21
22 TEST_REPO_NO_NAMESPACE = TEST_REPO
23 TEST_REPO_NO_NAMESPACE_ROOT = TEST_REPO_ROOT
24 TEST_REPO_NO_NAMESPACE_BASE_URL = "test_repo/"
25 AUTH_TEST_REPO_NO_NAMESPACE_URL = AUTH_TEST_SERVER + TEST_REPO_NO_NAMESPACE_BASE_URL
1826
1927 TEST_REPO_NO_NEWLINE = os.path.abspath("tests/repos/build/no-newline-at-end-of-file")
20 TEST_REPO_NO_NEWLINE_URL = UNAUTH_TEST_SERVER + "no-newline-at-end-of-file/"
28 TEST_REPO_NO_NEWLINE_BASE_URL = "no-newline-at-end-of-file/"
29 UNAUTH_TEST_REPO_NO_NEWLINE_URL = UNAUTH_TEST_SERVER + TEST_REPO_NO_NEWLINE_BASE_URL
2130
2231 TEST_REPO_DONT_RENDER = os.path.abspath("tests/repos/build/dont-render")
23 TEST_REPO_DONT_RENDER_URL = UNAUTH_TEST_SERVER + "dont-render/"
32 TEST_REPO_DONT_RENDER_BASE_URL = "dont-render/"
33 UNAUTH_TEST_REPO_DONT_RENDER_URL = UNAUTH_TEST_SERVER + TEST_REPO_DONT_RENDER_BASE_URL
2434
2535 TEST_INVALID_REPO = os.path.abspath("tests/repos/build/invalid_repo")
36 TEST_INVALID_REPO_NAME = "invalid_repo"
2637
27 ALL_TEST_REPOS = [
28 TEST_REPO,
29 TEST_REPO_NO_NEWLINE,
30 TEST_REPO_DONT_RENDER,
31 TEST_INVALID_REPO,
32 ]
38 REPOS = [TEST_REPO_NO_NEWLINE, TEST_REPO_DONT_RENDER, TEST_INVALID_REPO]
39 ALL_TEST_REPOS = {NAMESPACE: [TEST_REPO], None: REPOS}
3340
3441
3542 @contextlib.contextmanager