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