checkmate5 4.0.67__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checkmate/__init__.py +21 -0
- checkmate/__main__.py +25 -0
- checkmate/contrib/__init__.py +21 -0
- checkmate/contrib/plugins/__init__.py +0 -0
- checkmate/contrib/plugins/all/gptanalyzer/__init__.py +0 -0
- checkmate/contrib/plugins/all/gptanalyzer/analyzer.py +99 -0
- checkmate/contrib/plugins/all/gptanalyzer/issues_data.py +6 -0
- checkmate/contrib/plugins/all/gptanalyzer/setup.py +13 -0
- checkmate/contrib/plugins/cve/__init__.py +0 -0
- checkmate/contrib/plugins/cve/text4shell/__init__.py +0 -0
- checkmate/contrib/plugins/cve/text4shell/analyzer.py +64 -0
- checkmate/contrib/plugins/cve/text4shell/issues_data.py +8 -0
- checkmate/contrib/plugins/cve/text4shell/setup.py +13 -0
- checkmate/contrib/plugins/git/__init__.py +0 -0
- checkmate/contrib/plugins/git/commands/__init__.py +6 -0
- checkmate/contrib/plugins/git/commands/analyze.py +364 -0
- checkmate/contrib/plugins/git/commands/base.py +16 -0
- checkmate/contrib/plugins/git/commands/diff.py +199 -0
- checkmate/contrib/plugins/git/commands/init.py +59 -0
- checkmate/contrib/plugins/git/commands/update_stats.py +41 -0
- checkmate/contrib/plugins/git/hooks/__init__.py +0 -0
- checkmate/contrib/plugins/git/hooks/project.py +19 -0
- checkmate/contrib/plugins/git/lib/__init__.py +1 -0
- checkmate/contrib/plugins/git/lib/repository.py +557 -0
- checkmate/contrib/plugins/git/lib/repository_pygit2.py +531 -0
- checkmate/contrib/plugins/git/models.py +178 -0
- checkmate/contrib/plugins/git/setup.py +27 -0
- checkmate/contrib/plugins/golang/__init__.py +0 -0
- checkmate/contrib/plugins/golang/gostaticcheck/__init__.py +0 -0
- checkmate/contrib/plugins/golang/gostaticcheck/analyzer.py +94 -0
- checkmate/contrib/plugins/golang/gostaticcheck/issues_data.py +1246 -0
- checkmate/contrib/plugins/golang/gostaticcheck/setup.py +13 -0
- checkmate/contrib/plugins/iac/__init__.py +0 -0
- checkmate/contrib/plugins/iac/kubescape/__init__.py +0 -0
- checkmate/contrib/plugins/iac/kubescape/analyzer.py +115 -0
- checkmate/contrib/plugins/iac/kubescape/issues_data.py +636 -0
- checkmate/contrib/plugins/iac/kubescape/setup.py +14 -0
- checkmate/contrib/plugins/iac/tfsec/__init__.py +0 -0
- checkmate/contrib/plugins/iac/tfsec/analyzer.py +92 -0
- checkmate/contrib/plugins/iac/tfsec/issues_data.py +1917 -0
- checkmate/contrib/plugins/iac/tfsec/setup.py +13 -0
- checkmate/contrib/plugins/java/__init__.py +0 -0
- checkmate/contrib/plugins/java/semgrepjava/__init__.py +0 -0
- checkmate/contrib/plugins/java/semgrepjava/analyzer.py +96 -0
- checkmate/contrib/plugins/java/semgrepjava/issues_data.py +5 -0
- checkmate/contrib/plugins/java/semgrepjava/setup.py +13 -0
- checkmate/contrib/plugins/javascript/__init__.py +0 -0
- checkmate/contrib/plugins/javascript/semgrepeslint/__init__.py +0 -0
- checkmate/contrib/plugins/javascript/semgrepeslint/analyzer.py +95 -0
- checkmate/contrib/plugins/javascript/semgrepeslint/issues_data.py +6 -0
- checkmate/contrib/plugins/javascript/semgrepeslint/setup.py +13 -0
- checkmate/contrib/plugins/perl/__init__.py +0 -0
- checkmate/contrib/plugins/perl/graudit/__init__.py +0 -0
- checkmate/contrib/plugins/perl/graudit/analyzer.py +70 -0
- checkmate/contrib/plugins/perl/graudit/issues_data.py +8 -0
- checkmate/contrib/plugins/perl/graudit/setup.py +13 -0
- checkmate/contrib/plugins/python/__init__.py +0 -0
- checkmate/contrib/plugins/python/bandit/__init__.py +0 -0
- checkmate/contrib/plugins/python/bandit/analyzer.py +74 -0
- checkmate/contrib/plugins/python/bandit/issues_data.py +426 -0
- checkmate/contrib/plugins/python/bandit/setup.py +13 -0
- checkmate/contrib/plugins/ruby/__init__.py +0 -0
- checkmate/contrib/plugins/ruby/brakeman/__init__.py +0 -0
- checkmate/contrib/plugins/ruby/brakeman/analyzer.py +96 -0
- checkmate/contrib/plugins/ruby/brakeman/issues_data.py +518 -0
- checkmate/contrib/plugins/ruby/brakeman/setup.py +13 -0
- checkmate/helpers/__init__.py +0 -0
- checkmate/helpers/facts.py +26 -0
- checkmate/helpers/hashing.py +68 -0
- checkmate/helpers/issue.py +101 -0
- checkmate/helpers/settings.py +14 -0
- checkmate/lib/__init__.py +1 -0
- checkmate/lib/analysis/__init__.py +3 -0
- checkmate/lib/analysis/base.py +103 -0
- checkmate/lib/code/__init__.py +3 -0
- checkmate/lib/code/environment.py +809 -0
- checkmate/lib/models.py +515 -0
- checkmate/lib/stats/__init__.py +1 -0
- checkmate/lib/stats/helpers.py +19 -0
- checkmate/lib/stats/mapreduce.py +29 -0
- checkmate/management/__init__.py +1 -0
- checkmate/management/commands/__init__.py +18 -0
- checkmate/management/commands/alembic.py +32 -0
- checkmate/management/commands/analyze.py +42 -0
- checkmate/management/commands/analyzers.py +1 -0
- checkmate/management/commands/base.py +66 -0
- checkmate/management/commands/compare.py +0 -0
- checkmate/management/commands/export.py +0 -0
- checkmate/management/commands/info.py +0 -0
- checkmate/management/commands/init.py +103 -0
- checkmate/management/commands/issues.py +478 -0
- checkmate/management/commands/props/__init__.py +1 -0
- checkmate/management/commands/props/delete.py +29 -0
- checkmate/management/commands/props/get.py +30 -0
- checkmate/management/commands/props/set.py +29 -0
- checkmate/management/commands/reset.py +53 -0
- checkmate/management/commands/shell.py +19 -0
- checkmate/management/commands/snapshots.py +22 -0
- checkmate/management/commands/stats.py +21 -0
- checkmate/management/commands/summary.py +19 -0
- checkmate/management/commands/sync.py +63 -0
- checkmate/management/commands/trend.py +1 -0
- checkmate/management/commands/watch.py +27 -0
- checkmate/management/decorators.py +1 -0
- checkmate/management/helpers.py +140 -0
- checkmate/scripts/__init__.py +18 -0
- checkmate/scripts/manage.py +121 -0
- checkmate/settings/__init__.py +2 -0
- checkmate/settings/base.py +127 -0
- checkmate/settings/defaults.py +133 -0
- checkmate5-4.0.67.dist-info/LICENSE.txt +4095 -0
- checkmate5-4.0.67.dist-info/METADATA +15 -0
- checkmate5-4.0.67.dist-info/RECORD +116 -0
- checkmate5-4.0.67.dist-info/WHEEL +5 -0
- checkmate5-4.0.67.dist-info/entry_points.txt +2 -0
- checkmate5-4.0.67.dist-info/top_level.txt +1 -0
checkmate/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2014 Andreas Dewes
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
# subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
checkmate/__main__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2014 Andreas Dewes
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
# subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
if __name__ == '__main__':
|
|
25
|
+
print((sys.argv))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2014 Andreas Dewes
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
# subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from checkmate.lib.analysis.base import BaseAnalyzer
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import tempfile
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GptAnalyzer(BaseAnalyzer):
|
|
18
|
+
|
|
19
|
+
def __init__(self, *args, **kwargs):
|
|
20
|
+
super(GptAnalyzer, self).__init__(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
def summarize(self, items):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def analyze(self, file_revision):
|
|
26
|
+
issues = []
|
|
27
|
+
tmpdir = "/tmp/"+file_revision.project.pk
|
|
28
|
+
|
|
29
|
+
if not os.path.exists(os.path.dirname(tmpdir+"/"+file_revision.path)):
|
|
30
|
+
try:
|
|
31
|
+
os.makedirs(os.path.dirname(tmpdir+"/"+file_revision.path))
|
|
32
|
+
except OSError as exc: # Guard against race condition
|
|
33
|
+
if exc.errno != errno.EEXIST:
|
|
34
|
+
raise
|
|
35
|
+
|
|
36
|
+
#result = subprocess.check_output(["rsync -r . "+tmpdir+" --exclude .git"],shell=True).strip()
|
|
37
|
+
|
|
38
|
+
f = open(tmpdir+"/"+file_revision.path, "wb")
|
|
39
|
+
|
|
40
|
+
result = {}
|
|
41
|
+
try:
|
|
42
|
+
with f:
|
|
43
|
+
try:
|
|
44
|
+
f.write(file_revision.get_file_content())
|
|
45
|
+
except UnicodeDecodeError:
|
|
46
|
+
pass
|
|
47
|
+
os.chdir(tmpdir)
|
|
48
|
+
os.environ["PATH"] = "/root/.go/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/:/usr/local/go/bin/"
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
result = subprocess.check_output(["/root/bin/ptpt",
|
|
52
|
+
"run",
|
|
53
|
+
"scr",
|
|
54
|
+
f.name],
|
|
55
|
+
stderr=subprocess.DEVNULL).strip()
|
|
56
|
+
except subprocess.CalledProcessError as e:
|
|
57
|
+
if e.returncode == 2:
|
|
58
|
+
result = e.output
|
|
59
|
+
elif e.returncode == 1:
|
|
60
|
+
result = e.output
|
|
61
|
+
pass
|
|
62
|
+
else:
|
|
63
|
+
result = []
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
json_result = json.loads(result)
|
|
67
|
+
except ValueError:
|
|
68
|
+
json_result = []
|
|
69
|
+
pass
|
|
70
|
+
try:
|
|
71
|
+
for issue in json_result:
|
|
72
|
+
value = issue['line']
|
|
73
|
+
|
|
74
|
+
location = (((value,None),
|
|
75
|
+
(value,None)),)
|
|
76
|
+
|
|
77
|
+
string = issue["finding"]
|
|
78
|
+
string = string.replace("'","")
|
|
79
|
+
string = string.replace("`","")
|
|
80
|
+
string = string.replace("\"","")
|
|
81
|
+
string = string.strip()
|
|
82
|
+
string = re.sub('[^A-Za-z0-9 ]+', '', string)
|
|
83
|
+
|
|
84
|
+
issues.append({
|
|
85
|
+
'code': "I001",
|
|
86
|
+
'location': location,
|
|
87
|
+
'data': string,
|
|
88
|
+
'file': file_revision.path,
|
|
89
|
+
'line': value,
|
|
90
|
+
'fingerprint': self.get_fingerprint_from_code(file_revision, location, extra_data=string)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
except:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
finally:
|
|
97
|
+
pass
|
|
98
|
+
return {'issues': issues}
|
|
99
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from checkmate.lib.analysis.base import BaseAnalyzer
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import tempfile
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Text4shellAnalyzer(BaseAnalyzer):
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
super(Text4shellAnalyzer, self).__init__(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
def summarize(self, items):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def analyze(self, file_revision):
|
|
24
|
+
issues = []
|
|
25
|
+
tmpdir = "/tmp/"+file_revision.project.pk
|
|
26
|
+
f = open(tmpdir+"/"+file_revision.path, "wb")
|
|
27
|
+
try:
|
|
28
|
+
with f:
|
|
29
|
+
f.write(file_revision.get_file_content())
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.check_output(["python3","/root/text4shell-ce/scan_commons_text_versions.py",
|
|
32
|
+
f.name,
|
|
33
|
+
"-quiet"]
|
|
34
|
+
)
|
|
35
|
+
except subprocess.CalledProcessError as e:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
json_result = json.loads(result)
|
|
40
|
+
except ValueError:
|
|
41
|
+
json_result = {}
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
line = "1"
|
|
46
|
+
line = int(line)
|
|
47
|
+
location = (((line, line),
|
|
48
|
+
(line, None)),)
|
|
49
|
+
|
|
50
|
+
issues.append({
|
|
51
|
+
'code': "I001",
|
|
52
|
+
'location': location,
|
|
53
|
+
'data': json_result["I001"],
|
|
54
|
+
'file': file_revision.path,
|
|
55
|
+
'line': line,
|
|
56
|
+
'fingerprint': self.get_fingerprint_from_code(file_revision, location, extra_data=json_result["I001"])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
except KeyError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
finally:
|
|
63
|
+
os.unlink(f.name)
|
|
64
|
+
return {'issues': issues}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .analyzer import Text4shellAnalyzer
|
|
2
|
+
from .issues_data import issues_data
|
|
3
|
+
|
|
4
|
+
analyzers = {
|
|
5
|
+
'text4shell':
|
|
6
|
+
{
|
|
7
|
+
'name': 'text4shell',
|
|
8
|
+
'title': 'text4shell',
|
|
9
|
+
'class': Text4shellAnalyzer,
|
|
10
|
+
'language': 'cve',
|
|
11
|
+
'issues_data': issues_data,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from checkmate.management.commands.analyze import Command as AnalyzeCommand
|
|
5
|
+
from .base import Command as BaseCommand
|
|
6
|
+
from ..lib.repository import group_snapshots_by_date, get_first_date_for_group
|
|
7
|
+
from ..models import GitBranch
|
|
8
|
+
from checkmate.contrib.plugins.git.models import GitSnapshot
|
|
9
|
+
from checkmate.lib.models import Diff, Snapshot
|
|
10
|
+
from checkmate.helpers.hashing import Hasher
|
|
11
|
+
from checkmate.helpers.settings import update
|
|
12
|
+
from checkmate.lib.models import (Snapshot,
|
|
13
|
+
Project,
|
|
14
|
+
BaseDocument,
|
|
15
|
+
FileRevision,
|
|
16
|
+
Diff,
|
|
17
|
+
IssueOccurrence)
|
|
18
|
+
|
|
19
|
+
import time
|
|
20
|
+
import datetime
|
|
21
|
+
import traceback
|
|
22
|
+
import logging
|
|
23
|
+
|
|
24
|
+
from checkmate.lib.code import CodeEnvironment
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Command(BaseCommand, AnalyzeCommand):
|
|
30
|
+
|
|
31
|
+
options = AnalyzeCommand.options + [
|
|
32
|
+
{
|
|
33
|
+
'name': '--n',
|
|
34
|
+
'action': 'store',
|
|
35
|
+
'dest': 'n',
|
|
36
|
+
'type': int,
|
|
37
|
+
'default': 1,
|
|
38
|
+
'help': 'The number of snapshots to analyze.'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
'name': '--offset',
|
|
42
|
+
'action': 'store',
|
|
43
|
+
'dest': 'offset',
|
|
44
|
+
'type': int,
|
|
45
|
+
'default': 0,
|
|
46
|
+
'help': 'The offset from the latest snapshot.'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
'name': '--shas',
|
|
50
|
+
'action': 'store',
|
|
51
|
+
'dest': 'shas',
|
|
52
|
+
'type': str,
|
|
53
|
+
'default': '',
|
|
54
|
+
'help': 'A comma-separated list of commit to analyze, identified by their SHAs.'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
'name': '--branch',
|
|
58
|
+
'action': 'store',
|
|
59
|
+
'dest': 'branch',
|
|
60
|
+
'type': str,
|
|
61
|
+
'default': 'master',
|
|
62
|
+
'help': 'The branch to analyze.'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
'name': '--from',
|
|
66
|
+
'action': 'store',
|
|
67
|
+
'dest': 'from',
|
|
68
|
+
'type': str,
|
|
69
|
+
'default': '',
|
|
70
|
+
'help': 'The date for which to perform the analysis.'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
'name': '--type',
|
|
74
|
+
'action': 'store',
|
|
75
|
+
'dest': 'type',
|
|
76
|
+
'type': str,
|
|
77
|
+
'default': 'latest',
|
|
78
|
+
'help': 'The type of analysis (latest, monthly, weekly, daily).'
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
def analyze_grouped_snapshots(self, branch, group, grouped_snapshots):
|
|
83
|
+
for key, snapshots in list(grouped_snapshots.items()):
|
|
84
|
+
last_snp, first_snp = snapshots[0], snapshots[-1]
|
|
85
|
+
self.analyze_and_generate_diffs(branch,
|
|
86
|
+
[first_snp, last_snp],
|
|
87
|
+
[{'snapshot_a': first_snp.sha,
|
|
88
|
+
'snapshot_b': last_snp.sha}])
|
|
89
|
+
|
|
90
|
+
def analyze_and_generate_diffs(self, branch, snapshots, diff_list):
|
|
91
|
+
|
|
92
|
+
analyzed_snapshots = {}
|
|
93
|
+
new_analyzed_snapshots = {}
|
|
94
|
+
|
|
95
|
+
if 'project_settings' in self.opts:
|
|
96
|
+
project_settings = self.opts['project_settings']
|
|
97
|
+
else:
|
|
98
|
+
project_settings = self.project.settings
|
|
99
|
+
|
|
100
|
+
# we look for a .checkmate.yml file in the main branch of the project
|
|
101
|
+
# if we find it, we either replace the project settings, update them
|
|
102
|
+
# or do nothing, depending on the desired configuration.
|
|
103
|
+
try:
|
|
104
|
+
git_settings = self.project.git.get_settings()
|
|
105
|
+
if git_settings is not None:
|
|
106
|
+
if git_settings.get('overwrite_project_settings', False):
|
|
107
|
+
project_settings = git_settings
|
|
108
|
+
elif not project_settings.get('ignore_git_settings', False):
|
|
109
|
+
project_settings.update(git_settings)
|
|
110
|
+
except ValueError:
|
|
111
|
+
logger.warning("Error when loading checkmate settings from git...")
|
|
112
|
+
git_settings = {}
|
|
113
|
+
|
|
114
|
+
code_environment = CodeEnvironment(self.project,
|
|
115
|
+
global_settings=self.settings,
|
|
116
|
+
project_settings=project_settings)
|
|
117
|
+
|
|
118
|
+
code_environment.env['branch'] = branch
|
|
119
|
+
code_environment.env['project'] = self.project
|
|
120
|
+
|
|
121
|
+
for git_snapshot in snapshots:
|
|
122
|
+
try:
|
|
123
|
+
query = {'sha': git_snapshot.sha,
|
|
124
|
+
'project': self.project}
|
|
125
|
+
git_snapshot = self.backend.get(
|
|
126
|
+
GitSnapshot, query, include=('snapshot',))
|
|
127
|
+
snapshot = git_snapshot.snapshot
|
|
128
|
+
# we check if the snapshot is analyzed and if the analysis configuration matches
|
|
129
|
+
if snapshot.analyzed and snapshot.configuration == self.project.configuration:
|
|
130
|
+
analyze_snapshot = False
|
|
131
|
+
else:
|
|
132
|
+
analyze_snapshot = True
|
|
133
|
+
snapshot.configuration = self.project.configuration
|
|
134
|
+
except GitSnapshot.DoesNotExist:
|
|
135
|
+
analyze_snapshot = True
|
|
136
|
+
snapshot = Snapshot()
|
|
137
|
+
snapshot.configuration = self.project.configuration
|
|
138
|
+
except GitSnapshot.MultipleDocumentsReturned:
|
|
139
|
+
logger.error("Multiple snapshots returned, deleting...")
|
|
140
|
+
with self.backend.transaction():
|
|
141
|
+
self.backend.filter(GitSnapshot, query).delete()
|
|
142
|
+
analyze_snapshot = True
|
|
143
|
+
if analyze_snapshot:
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
file_revisions = self.project.git.get_file_revisions(
|
|
147
|
+
git_snapshot.sha)
|
|
148
|
+
except:
|
|
149
|
+
logger.error("Cannot fetch file revisions for snapshot %s in project %s" % (
|
|
150
|
+
git_snapshot.sha, self.project.pk))
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
file_revisions = self.project.git.get_file_revisions(
|
|
156
|
+
git_snapshot.sha)
|
|
157
|
+
except:
|
|
158
|
+
logger.error("Cannot fetch file revisions for snapshot %s in project %s" % (
|
|
159
|
+
git_snapshot.sha, self.project.pk))
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
code_environment.file_revisions = file_revisions
|
|
165
|
+
git_snapshot.snapshot = code_environment.analyze(
|
|
166
|
+
file_revisions, save_if_empty=True, snapshot=snapshot)
|
|
167
|
+
|
|
168
|
+
new_analyzed_snapshots[git_snapshot.sha] = git_snapshot
|
|
169
|
+
|
|
170
|
+
with self.backend.transaction():
|
|
171
|
+
git_snapshot.snapshot.hash = git_snapshot.sha
|
|
172
|
+
self.backend.save(git_snapshot.snapshot)
|
|
173
|
+
git_snapshot.project = self.project
|
|
174
|
+
self.backend.save(git_snapshot)
|
|
175
|
+
|
|
176
|
+
analyzed_snapshots[git_snapshot.sha] = git_snapshot
|
|
177
|
+
|
|
178
|
+
snapshot_pairs = []
|
|
179
|
+
for diff_params in diff_list:
|
|
180
|
+
try:
|
|
181
|
+
snapshot_pairs.append(
|
|
182
|
+
[analyzed_snapshots[diff_params['snapshot_a']], analyzed_snapshots[diff_params['snapshot_b']]])
|
|
183
|
+
except KeyError:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
diffs = []
|
|
187
|
+
|
|
188
|
+
for git_snapshot_a, git_snapshot_b in snapshot_pairs:
|
|
189
|
+
diff = None
|
|
190
|
+
try:
|
|
191
|
+
query = {'snapshot_a': git_snapshot_a.snapshot,
|
|
192
|
+
'snapshot_b': git_snapshot_b.snapshot}
|
|
193
|
+
diff = self.backend.get(Diff, query)
|
|
194
|
+
except Diff.DoesNotExist:
|
|
195
|
+
logger.debug("Generating a diff between snapshots %s and %s" % (git_snapshot_a.sha,
|
|
196
|
+
git_snapshot_b.sha))
|
|
197
|
+
# if the configuration of the diff does not match the project configuration, we regenerate it
|
|
198
|
+
if diff is None or diff.configuration != self.project.configuration:
|
|
199
|
+
if diff is not None:
|
|
200
|
+
diff.configuration = self.project.configuration
|
|
201
|
+
diff, diff_file_revisions, diff_issue_occurrences = code_environment.diff_snapshots(
|
|
202
|
+
git_snapshot_a.snapshot,
|
|
203
|
+
git_snapshot_b.snapshot,
|
|
204
|
+
save=True,
|
|
205
|
+
diff=diff)
|
|
206
|
+
|
|
207
|
+
diffs.append(diff)
|
|
208
|
+
|
|
209
|
+
return analyzed_snapshots, diffs
|
|
210
|
+
|
|
211
|
+
def update_branch(self, branch_name, head_snapshot=None):
|
|
212
|
+
|
|
213
|
+
logger.debug("Updating info for branch %s" % branch_name)
|
|
214
|
+
|
|
215
|
+
hasher = Hasher()
|
|
216
|
+
hasher.add(branch_name)
|
|
217
|
+
|
|
218
|
+
branch = GitBranch({'project': self.project,
|
|
219
|
+
'name': branch_name,
|
|
220
|
+
'hash': hasher.digest.hexdigest(),
|
|
221
|
+
'snapshots': []})
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
branch = self.backend.get(GitBranch,
|
|
225
|
+
{'project.pk': self.project.pk, 'name': branch_name})
|
|
226
|
+
except GitBranch.DoesNotExist:
|
|
227
|
+
logger.debug("Creating branch....")
|
|
228
|
+
# we save the new branch instead...
|
|
229
|
+
with self.backend.transaction():
|
|
230
|
+
self.backend.save(branch)
|
|
231
|
+
|
|
232
|
+
if head_snapshot:
|
|
233
|
+
if branch.get('head_snapshot'):
|
|
234
|
+
if head_snapshot.committer_date_ts > branch.head_snapshot.eager.committer_date_ts:
|
|
235
|
+
branch.last_analyzed_snapshot = branch.head_snapshot
|
|
236
|
+
branch.head_snapshot = head_snapshot
|
|
237
|
+
else:
|
|
238
|
+
branch.head_snapshot = head_snapshot
|
|
239
|
+
with self.backend.transaction():
|
|
240
|
+
self.backend.update(
|
|
241
|
+
branch, ['last_analyzed_snapshot', 'head_snapshot'])
|
|
242
|
+
|
|
243
|
+
def run(self):
|
|
244
|
+
# to do: replace this with actual configuration information (in the future)
|
|
245
|
+
hasher = Hasher()
|
|
246
|
+
hasher.add("bars")
|
|
247
|
+
self.project.configuration = hasher.digest.hexdigest()
|
|
248
|
+
with self.backend.transaction():
|
|
249
|
+
self.backend.update(self.project, ['configuration'])
|
|
250
|
+
if not 'branch' in self.opts:
|
|
251
|
+
branch_name = self.project.git.get_default_branch()
|
|
252
|
+
else:
|
|
253
|
+
branch_name = self.opts.get('branch')
|
|
254
|
+
|
|
255
|
+
if self.opts['type'] in ('monthly', 'weekly', 'daily'):
|
|
256
|
+
|
|
257
|
+
if not branch_name:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
"Branch cannot be None for %s analysis!" % self.opts['type'])
|
|
260
|
+
|
|
261
|
+
logger.info("Analyzing %d %s commits in branch %s (offset: %d)" %
|
|
262
|
+
(self.opts['n'], self.opts['type'], branch_name, self.opts['offset']))
|
|
263
|
+
|
|
264
|
+
if self.opts['from']:
|
|
265
|
+
if isinstance(self.opts['from'], str):
|
|
266
|
+
try:
|
|
267
|
+
from_date = datetime.datetime.strptime(
|
|
268
|
+
self.opts['from'], "%Y-%m-%d")
|
|
269
|
+
except ValueError:
|
|
270
|
+
raise self.CommandException(
|
|
271
|
+
"Invalid date format or value for `from` parameter (use YYYY-mm-dd)")
|
|
272
|
+
else:
|
|
273
|
+
try:
|
|
274
|
+
from_date = self.project.git.get_snapshots(branch=branch_name, limit=1)[0]\
|
|
275
|
+
.committer_date + datetime.timedelta(days=1)
|
|
276
|
+
except IndexError:
|
|
277
|
+
logger.info("No snapshots found in branch %s" %
|
|
278
|
+
branch_name)
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
dt = get_first_date_for_group(from_date, self.opts['type'], self.opts['n'] +
|
|
282
|
+
self.opts['offset'])
|
|
283
|
+
|
|
284
|
+
snapshots = self.project.git.get_snapshots(branch=branch_name,
|
|
285
|
+
since=dt.ctime())[:-self.opts['offset']
|
|
286
|
+
if self.opts['offset'] != 0 else None]
|
|
287
|
+
|
|
288
|
+
grouped_snapshots = group_snapshots_by_date(
|
|
289
|
+
snapshots, self.opts['type'])
|
|
290
|
+
|
|
291
|
+
snapshots_to_analyze = []
|
|
292
|
+
diffs_to_generate = []
|
|
293
|
+
for key, snapshots in list(grouped_snapshots.items()):
|
|
294
|
+
last_snp, first_snp = snapshots[0], snapshots[-1]
|
|
295
|
+
snapshots_to_analyze.append(first_snp)
|
|
296
|
+
snapshots_to_analyze.append(last_snp)
|
|
297
|
+
diffs_to_generate.append(
|
|
298
|
+
{'snapshot_a': first_snp.sha, 'snapshot_b': last_snp.sha})
|
|
299
|
+
|
|
300
|
+
else:
|
|
301
|
+
if self.opts['shas']:
|
|
302
|
+
logger.debug("Analyzing %d snapshots" %
|
|
303
|
+
len(self.opts['shas'].split(",")))
|
|
304
|
+
if isinstance(self.opts['shas'], str):
|
|
305
|
+
shas = self.opts['shas'].split(",")
|
|
306
|
+
else:
|
|
307
|
+
shas = self.opts['shas']
|
|
308
|
+
snapshots_to_analyze = self.project.git.get_snapshots(
|
|
309
|
+
shas=shas)
|
|
310
|
+
else:
|
|
311
|
+
if branch_name is None:
|
|
312
|
+
logger.error("No branch specified!")
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
# we perform a sequential analysis
|
|
316
|
+
branch = None
|
|
317
|
+
try:
|
|
318
|
+
branch = self.backend.get(GitBranch,
|
|
319
|
+
{'project.pk': self.project.pk,
|
|
320
|
+
'name': branch_name})
|
|
321
|
+
except GitBranch.DoesNotExist:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
logger.info("Analyzing the %d most recent commits in branch %s (offset: %d)" %
|
|
325
|
+
(self.opts['n'], branch_name, self.opts['offset']))
|
|
326
|
+
|
|
327
|
+
if not self.opts['n']:
|
|
328
|
+
snapshots_to_analyze = self.project.git.get_snapshots(
|
|
329
|
+
branch=branch_name)
|
|
330
|
+
else:
|
|
331
|
+
snapshots_to_analyze = self.project.git.get_snapshots(branch=branch_name,
|
|
332
|
+
limit=self.opts['n'], offset=self.opts['offset'])
|
|
333
|
+
|
|
334
|
+
def timestamp(dt):
|
|
335
|
+
return (dt-datetime.datetime(1970, 1, 1)).total_seconds()
|
|
336
|
+
|
|
337
|
+
diffs_to_generate = [{'snapshot_a': snapshots_to_analyze[i]['sha'],
|
|
338
|
+
'snapshot_b':snapshots_to_analyze[i+1]['sha']}
|
|
339
|
+
for i in range(len(snapshots_to_analyze)-1)]
|
|
340
|
+
|
|
341
|
+
if branch and branch.get('last_analyzed_snapshot', None):
|
|
342
|
+
last_analyzed_snapshot = branch.last_analyzed_snapshot.eager
|
|
343
|
+
if last_analyzed_snapshot and snapshots_to_analyze and last_analyzed_snapshot['sha'] != snapshots_to_analyze[-1]['sha']:
|
|
344
|
+
snapshots_to_analyze.append(last_analyzed_snapshot)
|
|
345
|
+
last_diff = {
|
|
346
|
+
'snapshot_a': last_analyzed_snapshot['sha'],
|
|
347
|
+
'snapshot_b': snapshots_to_analyze[-1]['sha']
|
|
348
|
+
}
|
|
349
|
+
diffs_to_generate.append(last_diff)
|
|
350
|
+
|
|
351
|
+
if 'diffs' in self.opts and self.opts['diffs'] is not None:
|
|
352
|
+
diffs_to_generate = self.opts['diffs']
|
|
353
|
+
try:
|
|
354
|
+
analyzed_snapshots, diffs = self.analyze_and_generate_diffs(branch_name,
|
|
355
|
+
snapshots_to_analyze, diffs_to_generate)
|
|
356
|
+
head_snapshot = None
|
|
357
|
+
if analyzed_snapshots:
|
|
358
|
+
head_snapshot = sorted(
|
|
359
|
+
list(analyzed_snapshots.values()), key=lambda snp: snp['committer_date_ts'])[-1]
|
|
360
|
+
if branch_name:
|
|
361
|
+
self.update_branch(
|
|
362
|
+
branch_name, head_snapshot=head_snapshot)
|
|
363
|
+
except KeyboardInterrupt:
|
|
364
|
+
raise
|