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.
Files changed (116) hide show
  1. checkmate/__init__.py +21 -0
  2. checkmate/__main__.py +25 -0
  3. checkmate/contrib/__init__.py +21 -0
  4. checkmate/contrib/plugins/__init__.py +0 -0
  5. checkmate/contrib/plugins/all/gptanalyzer/__init__.py +0 -0
  6. checkmate/contrib/plugins/all/gptanalyzer/analyzer.py +99 -0
  7. checkmate/contrib/plugins/all/gptanalyzer/issues_data.py +6 -0
  8. checkmate/contrib/plugins/all/gptanalyzer/setup.py +13 -0
  9. checkmate/contrib/plugins/cve/__init__.py +0 -0
  10. checkmate/contrib/plugins/cve/text4shell/__init__.py +0 -0
  11. checkmate/contrib/plugins/cve/text4shell/analyzer.py +64 -0
  12. checkmate/contrib/plugins/cve/text4shell/issues_data.py +8 -0
  13. checkmate/contrib/plugins/cve/text4shell/setup.py +13 -0
  14. checkmate/contrib/plugins/git/__init__.py +0 -0
  15. checkmate/contrib/plugins/git/commands/__init__.py +6 -0
  16. checkmate/contrib/plugins/git/commands/analyze.py +364 -0
  17. checkmate/contrib/plugins/git/commands/base.py +16 -0
  18. checkmate/contrib/plugins/git/commands/diff.py +199 -0
  19. checkmate/contrib/plugins/git/commands/init.py +59 -0
  20. checkmate/contrib/plugins/git/commands/update_stats.py +41 -0
  21. checkmate/contrib/plugins/git/hooks/__init__.py +0 -0
  22. checkmate/contrib/plugins/git/hooks/project.py +19 -0
  23. checkmate/contrib/plugins/git/lib/__init__.py +1 -0
  24. checkmate/contrib/plugins/git/lib/repository.py +557 -0
  25. checkmate/contrib/plugins/git/lib/repository_pygit2.py +531 -0
  26. checkmate/contrib/plugins/git/models.py +178 -0
  27. checkmate/contrib/plugins/git/setup.py +27 -0
  28. checkmate/contrib/plugins/golang/__init__.py +0 -0
  29. checkmate/contrib/plugins/golang/gostaticcheck/__init__.py +0 -0
  30. checkmate/contrib/plugins/golang/gostaticcheck/analyzer.py +94 -0
  31. checkmate/contrib/plugins/golang/gostaticcheck/issues_data.py +1246 -0
  32. checkmate/contrib/plugins/golang/gostaticcheck/setup.py +13 -0
  33. checkmate/contrib/plugins/iac/__init__.py +0 -0
  34. checkmate/contrib/plugins/iac/kubescape/__init__.py +0 -0
  35. checkmate/contrib/plugins/iac/kubescape/analyzer.py +115 -0
  36. checkmate/contrib/plugins/iac/kubescape/issues_data.py +636 -0
  37. checkmate/contrib/plugins/iac/kubescape/setup.py +14 -0
  38. checkmate/contrib/plugins/iac/tfsec/__init__.py +0 -0
  39. checkmate/contrib/plugins/iac/tfsec/analyzer.py +92 -0
  40. checkmate/contrib/plugins/iac/tfsec/issues_data.py +1917 -0
  41. checkmate/contrib/plugins/iac/tfsec/setup.py +13 -0
  42. checkmate/contrib/plugins/java/__init__.py +0 -0
  43. checkmate/contrib/plugins/java/semgrepjava/__init__.py +0 -0
  44. checkmate/contrib/plugins/java/semgrepjava/analyzer.py +96 -0
  45. checkmate/contrib/plugins/java/semgrepjava/issues_data.py +5 -0
  46. checkmate/contrib/plugins/java/semgrepjava/setup.py +13 -0
  47. checkmate/contrib/plugins/javascript/__init__.py +0 -0
  48. checkmate/contrib/plugins/javascript/semgrepeslint/__init__.py +0 -0
  49. checkmate/contrib/plugins/javascript/semgrepeslint/analyzer.py +95 -0
  50. checkmate/contrib/plugins/javascript/semgrepeslint/issues_data.py +6 -0
  51. checkmate/contrib/plugins/javascript/semgrepeslint/setup.py +13 -0
  52. checkmate/contrib/plugins/perl/__init__.py +0 -0
  53. checkmate/contrib/plugins/perl/graudit/__init__.py +0 -0
  54. checkmate/contrib/plugins/perl/graudit/analyzer.py +70 -0
  55. checkmate/contrib/plugins/perl/graudit/issues_data.py +8 -0
  56. checkmate/contrib/plugins/perl/graudit/setup.py +13 -0
  57. checkmate/contrib/plugins/python/__init__.py +0 -0
  58. checkmate/contrib/plugins/python/bandit/__init__.py +0 -0
  59. checkmate/contrib/plugins/python/bandit/analyzer.py +74 -0
  60. checkmate/contrib/plugins/python/bandit/issues_data.py +426 -0
  61. checkmate/contrib/plugins/python/bandit/setup.py +13 -0
  62. checkmate/contrib/plugins/ruby/__init__.py +0 -0
  63. checkmate/contrib/plugins/ruby/brakeman/__init__.py +0 -0
  64. checkmate/contrib/plugins/ruby/brakeman/analyzer.py +96 -0
  65. checkmate/contrib/plugins/ruby/brakeman/issues_data.py +518 -0
  66. checkmate/contrib/plugins/ruby/brakeman/setup.py +13 -0
  67. checkmate/helpers/__init__.py +0 -0
  68. checkmate/helpers/facts.py +26 -0
  69. checkmate/helpers/hashing.py +68 -0
  70. checkmate/helpers/issue.py +101 -0
  71. checkmate/helpers/settings.py +14 -0
  72. checkmate/lib/__init__.py +1 -0
  73. checkmate/lib/analysis/__init__.py +3 -0
  74. checkmate/lib/analysis/base.py +103 -0
  75. checkmate/lib/code/__init__.py +3 -0
  76. checkmate/lib/code/environment.py +809 -0
  77. checkmate/lib/models.py +515 -0
  78. checkmate/lib/stats/__init__.py +1 -0
  79. checkmate/lib/stats/helpers.py +19 -0
  80. checkmate/lib/stats/mapreduce.py +29 -0
  81. checkmate/management/__init__.py +1 -0
  82. checkmate/management/commands/__init__.py +18 -0
  83. checkmate/management/commands/alembic.py +32 -0
  84. checkmate/management/commands/analyze.py +42 -0
  85. checkmate/management/commands/analyzers.py +1 -0
  86. checkmate/management/commands/base.py +66 -0
  87. checkmate/management/commands/compare.py +0 -0
  88. checkmate/management/commands/export.py +0 -0
  89. checkmate/management/commands/info.py +0 -0
  90. checkmate/management/commands/init.py +103 -0
  91. checkmate/management/commands/issues.py +478 -0
  92. checkmate/management/commands/props/__init__.py +1 -0
  93. checkmate/management/commands/props/delete.py +29 -0
  94. checkmate/management/commands/props/get.py +30 -0
  95. checkmate/management/commands/props/set.py +29 -0
  96. checkmate/management/commands/reset.py +53 -0
  97. checkmate/management/commands/shell.py +19 -0
  98. checkmate/management/commands/snapshots.py +22 -0
  99. checkmate/management/commands/stats.py +21 -0
  100. checkmate/management/commands/summary.py +19 -0
  101. checkmate/management/commands/sync.py +63 -0
  102. checkmate/management/commands/trend.py +1 -0
  103. checkmate/management/commands/watch.py +27 -0
  104. checkmate/management/decorators.py +1 -0
  105. checkmate/management/helpers.py +140 -0
  106. checkmate/scripts/__init__.py +18 -0
  107. checkmate/scripts/manage.py +121 -0
  108. checkmate/settings/__init__.py +2 -0
  109. checkmate/settings/base.py +127 -0
  110. checkmate/settings/defaults.py +133 -0
  111. checkmate5-4.0.67.dist-info/LICENSE.txt +4095 -0
  112. checkmate5-4.0.67.dist-info/METADATA +15 -0
  113. checkmate5-4.0.67.dist-info/RECORD +116 -0
  114. checkmate5-4.0.67.dist-info/WHEEL +5 -0
  115. checkmate5-4.0.67.dist-info/entry_points.txt +2 -0
  116. checkmate5-4.0.67.dist-info/top_level.txt +1 -0
@@ -0,0 +1,531 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ NOTICE:
5
+
6
+ This version of the repository API migrates its code to pygit2, which is
7
+ a bit tricky to install but which provides a clean API to the git libraries.
8
+
9
+ In the future, it will replace the old repository module that relies on git's
10
+ command line interface.
11
+ """
12
+
13
+
14
+ import os
15
+ import subprocess
16
+ import datetime
17
+ import re
18
+ import time
19
+ import logging
20
+ import traceback
21
+ import select
22
+ import fcntl
23
+ import shutil
24
+ import io
25
+ import tempfile
26
+ import pygit2
27
+ from collections import defaultdict
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class GitException(BaseException):
33
+
34
+ pass
35
+
36
+
37
+ def get_first_date_for_group(start_date, group_type, n):
38
+ """
39
+ :param start: start date
40
+ :n : how many groups we want to get
41
+ :group_type : daily, weekly, monthly
42
+ """
43
+ current_date = start_date
44
+ if group_type == 'monthly':
45
+ current_year = start_date.year
46
+ current_month = start_date.month
47
+ for i in range(n-1):
48
+ current_month -= 1
49
+ if current_month == 0:
50
+ current_month = 12
51
+ current_year -= 1
52
+ first_date = datetime.datetime(current_year, current_month, 1)
53
+ elif group_type == 'weekly':
54
+ first_date = start_date - \
55
+ datetime.timedelta(days=start_date.weekday()+(n-1)*7)
56
+ elif group_type == 'daily':
57
+ first_date = start_date-datetime.timedelta(days=n-1)
58
+ first_date = datetime.datetime(
59
+ first_date.year, first_date.month, first_date.day, 0, 0, 0)
60
+ return first_date
61
+
62
+
63
+ def group_snapshots_by_date(snapshots, period):
64
+
65
+ available_periods = {
66
+ 'daily': lambda dt: dt.strftime("%Y-%m-%d"),
67
+ 'weekly': lambda dt: dt.strftime("%Y-%W"),
68
+ 'monthly': lambda dt: dt.strftime("%Y-%m")
69
+ }
70
+ formatter = available_periods[period]
71
+
72
+ grouped_snapshots = defaultdict(list)
73
+
74
+ for snapshot in snapshots:
75
+ dt = datetime.datetime.fromtimestamp(snapshot.committer_date_ts).date()
76
+ key = formatter(dt)
77
+ grouped_snapshots[key].append(snapshot)
78
+
79
+ return grouped_snapshots
80
+
81
+
82
+ class Repository(object):
83
+
84
+ def __init__(self, path):
85
+ self._path = path
86
+ self.devnull = open(os.devnull, "w")
87
+ self.stderr = ''
88
+ self.stdout = ''
89
+ self.returncode = None
90
+
91
+ @property
92
+ def path(self):
93
+ return self._path
94
+
95
+ @path.setter
96
+ def set_path(self, path):
97
+ self._path = path
98
+
99
+ @property
100
+ def repo(self):
101
+ if not hasattr(self, '_repo'):
102
+ self._repo = pygit2.Repository(self._path)
103
+ return self._repo
104
+
105
+ def _call(self, args, kwargs, capture_stderr=True, timeout=None):
106
+
107
+ if not 'cwd' in kwargs:
108
+ kwargs['cwd'] = self.path
109
+
110
+ if timeout:
111
+
112
+ # We write command output to temporary files, so that we are able to read it
113
+ # even if we terminate the command abruptly.
114
+ with tempfile.TemporaryFile() as stdout, tempfile.TemporaryFile() as stderr:
115
+
116
+ if capture_stderr:
117
+ stderr = stdout
118
+
119
+ p = subprocess.Popen(
120
+ *args, stdout=stdout, stderr=stderr, preexec_fn=os.setsid, **kwargs)
121
+
122
+ def read_output():
123
+ stdout.flush()
124
+ stderr.flush()
125
+ stdout.seek(0)
126
+ self.stdout = stdout.read()
127
+ stderr.seek(0)
128
+ self.stderr = stderr.read()
129
+
130
+ start_time = time.time()
131
+
132
+ while time.time() - start_time < timeout:
133
+ if p.poll() != None:
134
+ break
135
+ time.sleep(0.001)
136
+
137
+ timeout_occured = False
138
+
139
+ if p.poll() == None:
140
+ timeout_occured = True
141
+ stdout.flush()
142
+ stderr.flush()
143
+ p.terminate()
144
+ time.sleep(0.1)
145
+ if p.poll() == None:
146
+ p.kill()
147
+
148
+ read_output()
149
+
150
+ if timeout_occured:
151
+ self.stderr += "\n[process timed out after %d seconds]" % int(
152
+ timeout)
153
+
154
+ self.returncode = p.returncode
155
+ return p.returncode, self.stdout
156
+ else:
157
+ if capture_stderr:
158
+ stderr = subprocess.STDOUT
159
+ else:
160
+ stderr = subprocess.PIPE
161
+ p = subprocess.Popen(
162
+ *args, stdout=subprocess.PIPE, stderr=stderr, **kwargs)
163
+ stdout, stderr = p.communicate()
164
+ return p.returncode, stdout
165
+
166
+ def call(self, *args, **kwargs):
167
+ if 'timeout' in kwargs:
168
+ timeout = kwargs['timeout']
169
+ del kwargs['timeout']
170
+ return self._call(args, kwargs, timeout=timeout)
171
+ else:
172
+ return self._call(args, kwargs)
173
+
174
+ def check_output(self, *args, **kwargs):
175
+ returncode, stdout = self._call(args, kwargs, capture_stderr=False)
176
+ if returncode != 0:
177
+ raise subprocess.CalledProcessError(returncode, args[0], stdout)
178
+ return stdout
179
+
180
+ def add_remote(self, name, url):
181
+ return_code, stdout = self.call(["git", "remote", "add", name, url])
182
+ return return_code
183
+
184
+ def remove_remote(self, name):
185
+ return_code, stdout = self.call(["git", "remote", "remove", name])
186
+ return return_code
187
+
188
+ def get_remotes(self):
189
+ remotes = []
190
+ for remote in self.repo.remotes:
191
+ remotes.append({
192
+ 'name': remote.name,
193
+ 'url': remote.url
194
+ })
195
+ return remotes
196
+
197
+ def update_remote_url(self, remote, url):
198
+ self.repo.remotes.set_url(remote, url)
199
+
200
+ def update_remote_name(self, remote, name):
201
+ self.repo.remotes.rename(remote, name)
202
+
203
+ def init(self):
204
+ return_code, stdout = self.call(["git", "init"])
205
+ return return_code
206
+
207
+ def pull(self, remote="origin", branch="master"):
208
+ return_code, stdout = self.call(["git", "pull", remote, branch])
209
+ return return_code
210
+
211
+ def _get_ssh_wrapper(self):
212
+ wrapper = os.path.abspath(__file__+"/..")+"/ssh"
213
+ return wrapper
214
+
215
+ def _get_ssh_config(self, identity_file):
216
+ return """Host *
217
+ StrictHostKeyChecking no
218
+ IdentityFile "%s"
219
+ IdentitiesOnly yes
220
+ """ % identity_file
221
+
222
+ def fetch(self, remote="origin", branch=None, ssh_identity_file=None, git_config=None, git_credentials=None):
223
+ if not re.match(r"^[\w\d]+$", remote):
224
+ raise ValueError("Invalid remote: %s" % remote)
225
+ try:
226
+ directory = tempfile.mkdtemp()
227
+ env = {'HOME': directory}
228
+ if ssh_identity_file:
229
+ # To Do: Security audit
230
+ logger.debug("Fetching with SSH key")
231
+
232
+ env.update({'CONFIG_FILE': directory+"/ssh_config",
233
+ 'GIT_SSH': self._get_ssh_wrapper()})
234
+
235
+ with open(directory+"/ssh_config", "w") as ssh_config_file:
236
+ ssh_config_file.write(
237
+ self._get_ssh_config(ssh_identity_file))
238
+
239
+ if git_config:
240
+ env.update({'GIT_CONFIG_NOSYSTEM': '1'})
241
+
242
+ with open(directory+"/.gitconfig", "w") as git_config_file:
243
+ git_config_file.write(git_config)
244
+
245
+ if git_credentials:
246
+
247
+ with open(directory+"/.git-credentials", "w") as git_credentials_file:
248
+ git_credentials_file.write(git_credentials)
249
+
250
+ extra_args = []
251
+ if branch is not None:
252
+ extra_args.append(branch)
253
+
254
+ return_code, stdout = self.call(
255
+ ["git", "fetch", remote]+extra_args, env=env, timeout=120)
256
+ finally:
257
+ shutil.rmtree(directory)
258
+
259
+ return return_code
260
+
261
+ def reset(self, branch):
262
+ return_code, stdout = self.call(["git", "reset", branch])
263
+
264
+ def get_branches(self, include_remote=True):
265
+ if include_remote:
266
+ extra_args = ['-a']
267
+ else:
268
+ extra_args = []
269
+ raw_output = self.check_output(
270
+ ["git", "branch", "--list"]+extra_args).decode("utf-8", 'ignore')
271
+ branches = [re.sub(r"^remotes\/", "", ss) for ss in [re.sub(r"[^\~\w\d\-\:\/\.\\]*", "", s.strip())
272
+ for s in raw_output.split("\n")] if ss]
273
+ return branches
274
+
275
+ def set_branch(self, branch):
276
+ return self.call(["git", "checkout", branch])[0]
277
+
278
+ def filter_commits_by_branch(self, commits, branch="master"):
279
+
280
+ since = min([commit['committer_date_ts'] for commit in commits])
281
+ until = max([commit['committer_date_ts'] for commit in commits])
282
+
283
+ branch_commits = self.get_commits(
284
+ branch=branch, since=since, until=until)
285
+ branch_shas = [commit['sha'] for commit in branch_commits]
286
+
287
+ filtered_commits = [
288
+ commit for commit in commits if commit['sha'] in branch_shas]
289
+
290
+ return filtered_commits
291
+
292
+ def summarize_commits(self, commits, include_limit=6):
293
+
294
+ sorted_commits = sorted(
295
+ commits, key=lambda commit: commit['committer_date'])
296
+ summary = {'count': len(commits)}
297
+
298
+ if len(commits) <= include_limit:
299
+ summary['commits'] = commits
300
+ summary['slices'] = [(None, None)]
301
+ else:
302
+ summary['commits'] = commits[:include_limit/2:] + \
303
+ commits[-include_limit/2:]
304
+ summary['slices'] = [(None, include_limit/2),
305
+ (-include_limit/2, None)]
306
+
307
+ summary['authors'] = {}
308
+
309
+ for commit in commits:
310
+ author_name = commit['author_name']
311
+ if author_name in summary['authors']:
312
+ author_summary = summary['authors'][author_name]
313
+ author_summary['count'] += 1
314
+ if not commit['author_email'] in author_summary['emails']:
315
+ author_summary['emails'].append(commit['author_email'])
316
+ else:
317
+ summary['authors'][author_name] = {'emails': [commit['author_email']],
318
+ 'count': 1,
319
+ 'name': author_name}
320
+
321
+ summary['authors'] = list(summary['authors'].values())
322
+
323
+ return summary
324
+
325
+ def get_parents(self, commit_sha):
326
+ base_args = ["git",
327
+ "rev-list",
328
+ "--parents",
329
+ "-n",
330
+ "1"]
331
+ return self.check_output(base_args+[commit_sha]).decode('utf-8', 'ignore').split()[1:]
332
+
333
+ def get_commits(self, branch=None, offset=0, limit=0, shas=None, params=None,
334
+ from_to=None, args=None, **kwargs):
335
+
336
+ split_sequence = '---a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2---'
337
+ try:
338
+ base_args = ["git",
339
+ "--no-pager",
340
+ "log",
341
+ "--date=raw",
342
+ "--pretty=format:%H:-:%ct:-:%cn:-:%ce:-:%at:-:%an:-:%ae:-:%P:-:%T:-:%n%B%n"+split_sequence+"%n"]
343
+ extra_args = []
344
+ if params:
345
+ extra_args.extend(params)
346
+ if args:
347
+ extra_args.extend(args)
348
+ for key, value in list(kwargs.items()):
349
+ if key in ('before', 'until') and isinstance(value, datetime.datetime):
350
+ value = (value+datetime.timedelta(days=1)).ctime()
351
+ elif key in ('after', 'since') and isinstance(value, datetime.datetime):
352
+ value = (value-datetime.timedelta(days=1)).ctime()
353
+ base_args.append("--%s" % key.replace('_', '-'))
354
+ if value:
355
+ base_args.append("%s" % value)
356
+ if shas:
357
+ raw_log_output = ''
358
+ for sha in shas:
359
+ sha_extra_args = extra_args+['-n', '1', sha]
360
+ raw_log_output += self.check_output(
361
+ base_args+sha_extra_args).decode('utf-8', 'ignore')
362
+ else:
363
+ if branch:
364
+ extra_args.extend([branch])
365
+ if from_to:
366
+ extra_args.extend([from_to[0]+".."+from_to[1]])
367
+ if offset != 0:
368
+ extra_args.extend(["--skip", "%d" % offset])
369
+ if limit != 0:
370
+ extra_args.extend(["--max-count", "%d" % limit])
371
+ raw_log_output = self.check_output(
372
+ base_args+extra_args).decode('utf-8', 'ignore')
373
+ except subprocess.CalledProcessError as e:
374
+ if not e.returncode in (141, 128):
375
+ raise
376
+ raw_log_output = e.output.decode("utf-8", "ignore")
377
+ headers_and_logs_str = map(lambda x: x.lstrip().split(
378
+ "\n", 1), raw_log_output.split(split_sequence))[:-1]
379
+ headers_and_logs = [(x[0].split(":-:"), x[1])
380
+ for x in headers_and_logs_str]
381
+
382
+ def get_initials(name):
383
+ parts = re.sub(r"[^\s\w\d]+", "", name).split()
384
+ if len(parts) <= 3:
385
+ return "".join([s[0].upper() for s in parts])
386
+ else:
387
+ return "".join([s[0].upper() for s in parts[:3]+parts[-1:]])
388
+
389
+ def decode_entry(x):
390
+ return {
391
+ 'sha': x[0][0],
392
+ 'committer_date': datetime.datetime.fromtimestamp(int(x[0][1])) if x[0][1] else None,
393
+ 'committer_date_ts': int(x[0][1]) if x[0][1] else None,
394
+ 'committer_name': x[0][2],
395
+ 'committer_email': x[0][3],
396
+ 'committer_initials': get_initials(x[0][2]),
397
+ 'author_initials': get_initials(x[0][5]),
398
+ 'author_date': datetime.datetime.fromtimestamp(int(x[0][4])) if x[0][4] else None,
399
+ 'author_date_ts': int(x[0][4]) if x[0][4] else None,
400
+ 'author_name': x[0][5],
401
+ 'author_email': x[0][6],
402
+ 'parents': x[0][7].split(),
403
+ 'tree_sha': x[0][8],
404
+ 'log': x[1],
405
+ }
406
+
407
+ commits = sorted(map(decode_entry, headers_and_logs),
408
+ key=lambda x: x['committer_date_ts'])
409
+
410
+ # Workaround to achieve precise datetime matching in the 'before' and 'since' fields.
411
+ for key in ('before', 'until'):
412
+ if key in kwargs and isinstance(kwargs[key], datetime.datetime):
413
+ commits = [
414
+ commit for commit in commits if commit['committer_date'] < kwargs[key]]
415
+ for key in ('since', 'after'):
416
+ if key in kwargs and isinstance(kwargs[key], datetime.datetime):
417
+ commits = [
418
+ commit for commit in commits if commit['committer_date'] > kwargs[key]]
419
+
420
+ return commits
421
+
422
+ def get_submodules(self):
423
+ submodules = map(lambda x: x.split(" ")[1],
424
+ self.check_output(["git", "submodule"]).split("\n")[:-1]).decode("utf-8", 'ignore')
425
+ return submodules
426
+
427
+ def get_modifications_by_author(self, commits):
428
+ modifications_by_author = defaultdict(lambda: defaultdict(lambda: {'lines_added': 0,
429
+ 'lines_removed': 0,
430
+ 'commits': 0}))
431
+ for commit in commits:
432
+ author_email = commit['author_email']
433
+ sha = commit['sha']
434
+ try:
435
+ modified_files = [(int(v[0]), int(v[1]), v[2])
436
+ for v in [s.split("\t")
437
+ for s in self.check_output(["git",
438
+ "diff",
439
+ r"--numstat",
440
+ "{sha}^..{sha}"
441
+ .format(sha=sha)
442
+ .decode("utf-8", 'ignore')])
443
+ .strip()
444
+ .split("\n")]
445
+ if len(v) == 3
446
+ and v[2] != ''
447
+ and v[0] != '-'
448
+ and v[1] != '-']
449
+ except subprocess.CalledProcessError:
450
+ continue
451
+ for (lines_added, lines_removed, path) in modified_files:
452
+ d = modifications_by_author[author_email][path]
453
+ d['lines_added'] += lines_added
454
+ d['lines_removed'] += lines_removed
455
+ d['commits'] += 1
456
+ return modifications_by_author
457
+
458
+ def get_contributors(self, branch=None):
459
+ args = ["-se"]
460
+ if branch:
461
+ args += [branch]
462
+ else:
463
+ args += ["--all"]
464
+ lines = self.check_output(
465
+ ["git", "shortlog"]+args).decode("utf-8", 'ignore').split("\n")
466
+ contributors = []
467
+ for line in lines:
468
+ match = re.match("^.*?(\d+)\s+(.*?)\s*\<(.*)\>\s*$", line)
469
+ if match:
470
+ (n_commits, name, email) = match.groups()
471
+ contributors.append(
472
+ {'name': name, 'email': email, 'n_commits': int(n_commits)})
473
+ return contributors
474
+
475
+ def get_number_of_commits(self, branch=None):
476
+ if branch:
477
+ command = ["git", "rev-list", branch, "--count"]
478
+ else:
479
+ command = ["git", "rev-list", "HEAD", "--count"]
480
+ n_commits = int(self.check_output(
481
+ command).strip().decode("utf-8", 'ignore'))
482
+ return n_commits
483
+
484
+ def get_files_in_commit(self, commit_sha, path=None):
485
+ if path == None:
486
+ opts = ["--full-tree", "-r", commit_sha]
487
+ else:
488
+ opts = ["%s:%s" % (commit_sha, path)]
489
+ files = [dict(list(zip(['mode', 'type', 'sha', 'path'], f.strip().split())))
490
+ for f in self.check_output(["git", "ls-tree"]+opts)
491
+ .decode("utf-8", 'ignore')
492
+ .split("\n") if f]
493
+ return files
494
+
495
+ def get_file_details(self, commit_sha, path):
496
+ (file_mode, file_type, file_sha, file_path) = self.check_output(["git",
497
+ "ls-tree",
498
+ commit_sha,
499
+ path])\
500
+ .decode("utf-8", 'ignore').split()
501
+ return {'mode': file_mode, 'type': file_type, 'sha': file_sha, 'path': file_path}
502
+
503
+ def get_diffs(self, commit_sha_a, commit_sha_b=None):
504
+ if not commit_sha_b:
505
+ files = self.get_files_in_commit(commit_sha_a)
506
+ return [['A', file['path']] for file in files]
507
+ else:
508
+ diffs = self.check_output(["git",
509
+ "diff",
510
+ "--name-status",
511
+ commit_sha_a,
512
+ commit_sha_b])\
513
+ .decode("utf-8", 'ignore').split("\n")
514
+ diffs = [x.split("\t") for x in diffs]
515
+ return [x for x in diffs if len(x) == 2]
516
+
517
+ def get_file_content(self, commit_sha, path):
518
+ try:
519
+ file_content = self.check_output(
520
+ ["git", "show", "%s:%s" % (commit_sha, path)])
521
+ except subprocess.CalledProcessError:
522
+ raise IOError
523
+ return file_content
524
+
525
+ def get_file_content_by_sha(self, sha):
526
+ try:
527
+ file_content = self.check_output(
528
+ ["git", "cat-file", "blob", "%s" % (sha,)])
529
+ except subprocess.CalledProcessError:
530
+ raise IOError
531
+ return file_content