Skip to content

Commit 45e92d5

Browse files
authored
Merge pull request #12 from mattip/use-python3
Use python3
2 parents 1f342cc + ae7184b commit 45e92d5

19 files changed

Lines changed: 634 additions & 66 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ matrix:
1414
- python: "3.5"
1515
env: DJANGO_VERSION=2.1
1616
install:
17-
- pip install flake8
17+
- pip install flake8 mock
1818
- pip install -q Django==$DJANGO_VERSION
1919
- python setup.py install
2020
before_script:

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ same name.
161161

162162
* The results associated to an executable and a revision which has a non blank
163163
tag field will be listed as a baseline option in the Timeline view.
164-
* Additionaly, the Comparison view will show the results of the latest revision
164+
* Additionally, the Comparison view will show the results of the latest revision
165165
of projects being tracked as an executable as well.
166166

167167
### Defaults
@@ -224,6 +224,18 @@ COMP_EXECUTABLES = [
224224
('myexe', 'L'),
225225
]
226226
```
227+
* `COMPARISON_COMMIT_TAGS: Defines a list of tags to display on the comparison page. This comes
228+
handy when there are a lot of tags. It defaults to ``None`` which means display all the available
229+
tags.
230+
231+
### VCS Provider Specific Settings
232+
233+
#### Github
234+
235+
* ``GITHUB_OAUTH_TOKEN`` - Github oAuth token to use for authenticating against
236+
the Github API. If not provided, it will default to unauthenticated API requests
237+
which have low rate limits so an exception may be thrown when retrieving info
238+
from the Github API due to the rate limit being reached.
227239

228240
## Getting help
229241

codespeed/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ProjectAdmin(admin.ModelAdmin):
3737

3838
@admin.register(Branch)
3939
class BranchAdmin(admin.ModelAdmin):
40-
list_display = ('name', 'project')
40+
list_display = ('name', 'project', 'display_on_comparison_page')
4141
list_filter = ('project',)
4242

4343

codespeed/auth.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import types
23
from functools import wraps
34
from django.contrib.auth import authenticate, login
45
from django.http import HttpResponse, HttpResponseForbidden
@@ -9,6 +10,20 @@
910
logger = logging.getLogger(__name__)
1011

1112

13+
def is_authenticated(request):
14+
# NOTE: We do type check so we also support newer versions of Django when
15+
# is_authenticated and some other methods have been properties
16+
if isinstance(request.user.is_authenticated, (types.FunctionType,
17+
types.MethodType)):
18+
return request.user.is_authenticated()
19+
elif isinstance(request.user.is_authenticated, bool):
20+
return request.user.is_authenticated
21+
else:
22+
logger.info('Got unexpected type for request.user.is_authenticated '
23+
'variable')
24+
return False
25+
26+
1227
def basic_auth_required(realm='default'):
1328
def _helper(func):
1429
@wraps(func)
@@ -18,7 +33,7 @@ def _decorator(request, *args, **kwargs):
1833
if settings.ALLOW_ANONYMOUS_POST:
1934
logger.debug('allowing anonymous post')
2035
allowed = True
21-
elif hasattr(request, 'user') and request.user.is_authenticated():
36+
elif hasattr(request, 'user') and is_authenticated(request=request):
2237
allowed = True
2338
elif 'HTTP_AUTHORIZATION' in request.META:
2439
logger.debug('checking for http authorization header')

codespeed/commits/git.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
import datetime
22
import logging
33
import os
4-
from string import strip
5-
from subprocess import Popen, PIPE
64

5+
from subprocess import Popen, PIPE
76
from django.conf import settings
8-
97
from .exceptions import CommitLogError
108

119
logger = logging.getLogger(__name__)
1210

1311

12+
def execute_command(cmd, cwd):
13+
p = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=cwd)
14+
stdout, stderr = p.communicate()
15+
stdout = stdout.decode('utf8') if stdout is not None else stdout
16+
stderr = stderr.decode('utf8') if stderr is not None else stderr
17+
return (p, stdout, stderr)
18+
19+
1420
def updaterepo(project, update=True):
1521
if os.path.exists(project.working_copy):
1622
if not update:
1723
return
1824

19-
p = Popen(['git', 'pull'], stdout=PIPE, stderr=PIPE,
20-
cwd=project.working_copy)
25+
p, _, stderr = execute_command(['git', 'pull'], cwd=project.working_copy)
2126

22-
stdout, stderr = p.communicate()
2327
if p.returncode != 0:
2428
raise CommitLogError("git pull returned %s: %s" % (p.returncode,
2529
stderr))
2630
else:
2731
return [{'error': False}]
2832
else:
2933
cmd = ['git', 'clone', project.repo_path, project.repo_name]
30-
p = Popen(cmd, stdout=PIPE, stderr=PIPE,
31-
cwd=settings.REPOSITORY_BASE_PATH)
34+
p, stdout, stderr = execute_command(cmd, settings.REPOSITORY_BASE_PATH)
3235
logger.debug('Cloning Git repo {0} for project {1}'.format(
3336
project.repo_path, project))
34-
stdout, stderr = p.communicate()
3537

3638
if p.returncode != 0:
3739
raise CommitLogError("%s returned %s: %s" % (
@@ -59,32 +61,24 @@ def getlogs(endrev, startrev):
5961
cmd.append(endrev.commitid)
6062

6163
working_copy = endrev.branch.project.working_copy
62-
p = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=working_copy)
63-
64-
stdout, stderr = p.communicate()
64+
p, stdout, stderr = execute_command(cmd, working_copy)
6565

6666
if p.returncode != 0:
6767
raise CommitLogError("%s returned %s: %s" % (
6868
" ".join(cmd), p.returncode, stderr))
6969
logs = []
70-
for log in filter(None, stdout.split(b'\x1e')):
70+
for log in filter(None, stdout.split('\x1e')):
7171
(short_commit_id, commit_id, date_t, author_name, author_email,
72-
subject, body) = map(strip, log.split(b'\x00', 7))
73-
74-
tag = ""
72+
subject, body) = map(lambda s: s.strip(), log.split('\x00', 7))
7573

7674
cmd = ["git", "tag", "--points-at", commit_id]
77-
proc = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=working_copy)
7875

7976
try:
80-
stdout, stderr = proc.communicate()
81-
except ValueError:
82-
stdout = b''
83-
stderr = b''
84-
85-
if proc.returncode == 0:
86-
tag = stdout.strip()
77+
p, stdout, stderr = execute_command(cmd, working_copy)
78+
except Exception:
79+
logger.debug('Failed to get tag', exc_info=True)
8780

81+
tag = stdout.strip() if p.returncode == 0 else ""
8882
date = datetime.datetime.fromtimestamp(
8983
int(date_t)).strftime("%Y-%m-%d %H:%M:%S")
9084

codespeed/commits/github.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111
try:
1212
# Python 3
1313
from urllib.request import urlopen
14+
from urllib.request import Request
1415
except ImportError:
1516
# Python 2
16-
from urllib import urlopen
17+
from urllib2 import urlopen
18+
from urllib2 import Request
1719
import re
1820
import json
1921

2022
import isodate
2123
from django.core.cache import cache
24+
from django.conf import settings
2225

2326
from .exceptions import CommitLogError
2427

@@ -42,8 +45,17 @@ def fetch_json(url):
4245
json_obj = cache.get(url)
4346

4447
if json_obj is None:
48+
github_oauth_token = getattr(settings, 'GITHUB_OAUTH_TOKEN', None)
49+
50+
if github_oauth_token:
51+
headers = {'Authorization': 'token %s' % (github_oauth_token)}
52+
else:
53+
headers = {}
54+
55+
request = Request(url=url, headers=headers)
56+
4557
try:
46-
json_obj = json.load(urlopen(url))
58+
json_obj = json.load(urlopen(request))
4759
except IOError as e:
4860
logger.exception("Unable to load %s: %s",
4961
url, e, exc_info=True)
@@ -91,11 +103,13 @@ def retrieve_revision(commit_id, username, project, revision=None):
91103
if revision:
92104
# Overwrite any existing data we might have for this revision since
93105
# we never want our records to be out of sync with the actual VCS:
94-
95-
# We need to convert the timezone-aware date to a naive (i.e.
96-
# timezone-less) date in UTC to avoid killing MySQL:
97-
revision.date = date.astimezone(
98-
isodate.tzinfo.Utc()).replace(tzinfo=None)
106+
if not getattr(settings, 'USE_TZ_AWARE_DATES', False):
107+
# We need to convert the timezone-aware date to a naive (i.e.
108+
# timezone-less) date in UTC to avoid killing MySQL:
109+
logger.debug('USE_TZ_AWARE_DATES setting is set to False, '
110+
'converting datetime object to a naive one')
111+
revision.date = date.astimezone(
112+
isodate.tzinfo.Utc()).replace(tzinfo=None)
99113
revision.author = commit_json['author']['name']
100114
revision.message = commit_json['message']
101115
revision.full_clean()

codespeed/commits/tests/__init__.py

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from datetime import datetime
2+
from django.test import TestCase, override_settings
3+
from mock import Mock, patch
4+
5+
from codespeed.commits.git import getlogs
6+
from codespeed.models import Project, Revision, Branch, Environment
7+
8+
9+
@override_settings(ALLOW_ANONYMOUS_POST=True)
10+
class GitTest(TestCase):
11+
def setUp(self):
12+
self.env = Environment.objects.create(name='env')
13+
self.project = Project.objects.create(name='project',
14+
repo_path='path',
15+
repo_type=Project.GIT)
16+
self.branch = Branch.objects.create(name='default',
17+
project_id=self.project.id)
18+
self.revision = Revision.objects.create(
19+
**{
20+
'commitid': 'id1',
21+
'date': datetime.now(),
22+
'project_id': self.project.id,
23+
'branch_id': self.branch.id,
24+
}
25+
)
26+
27+
@patch("codespeed.commits.git.Popen")
28+
def test_git_output_parsing(self, popen):
29+
# given
30+
outputs = {
31+
"log": b"id\x00long_id\x001583489681\x00author\x00email\x00msg\x00\x1e",
32+
"tag": b'tag',
33+
}
34+
35+
def side_effect(cmd, *args, **kwargs):
36+
ret = Mock()
37+
ret.returncode = 0
38+
git_command = cmd[1] if len(cmd) > 0 else None
39+
output = outputs.get(git_command, b'')
40+
ret.communicate.return_value = (output, b'')
41+
return ret
42+
43+
popen.side_effect = side_effect
44+
45+
# when
46+
# revision doesn't matter here, git commands are mocked
47+
logs = getlogs(self.revision, self.revision)
48+
49+
# then
50+
expected = {
51+
'date': '2020-03-06 04:14:41',
52+
'message': 'msg',
53+
'commitid': 'long_id',
54+
'author': 'author',
55+
'author_email': 'email',
56+
'body': '',
57+
'short_commit_id': 'id',
58+
'tag': 'tag',
59+
}
60+
self.assertEquals([expected], logs)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.1.15 on 2020-02-24 11:26
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('codespeed', '0003_project_default_branch'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='branch',
15+
name='display_on_comparison_page',
16+
field=models.BooleanField(default=True, verbose_name='True to display this branch on the comparison page'),
17+
),
18+
]

codespeed/models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
from django.urls import reverse
1010
from django.conf import settings
1111
from django.db import models
12-
from django.utils.encoding import python_2_unicode_compatible
12+
try:
13+
from django.utils.encoding import python_2_unicode_compatible
14+
except ImportError:
15+
def python_2_unicode_compatible(arg):
16+
return arg
1317

1418
from .commits.github import GITHUB_URL_RE
1519

@@ -107,6 +111,9 @@ class Branch(models.Model):
107111
name = models.CharField(max_length=32)
108112
project = models.ForeignKey(
109113
Project, on_delete=models.CASCADE, related_name="branches")
114+
display_on_comparison_page = models.BooleanField(
115+
"True to display this branch on the comparison page",
116+
default=True)
110117

111118
def __str__(self):
112119
return self.project.name + ":" + self.name

0 commit comments

Comments
 (0)