OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 """Script to manage code reviews.""" | 3 """Script to manage code reviews.""" |
4 | 4 |
5 from __future__ import print_function | 5 from __future__ import print_function |
6 import argparse | 6 import argparse |
7 import json | 7 import json |
8 import logging | 8 import logging |
9 import os | 9 import os |
| 10 import random |
10 import re | 11 import re |
11 import shlex | 12 import shlex |
12 import subprocess | 13 import subprocess |
13 import sys | 14 import sys |
14 import time | 15 import time |
15 | 16 |
16 # pylint: disable=import-error | 17 # pylint: disable=import-error |
17 # pylint: disable=no-name-in-module | 18 # pylint: disable=no-name-in-module |
18 if sys.version_info[0] < 3: | 19 if sys.version_info[0] < 3: |
19 # Use urllib2 here since this code should be able to be used by a default | 20 # Use urllib2 here since this code should be able to be used by a default |
20 # Python set up. Otherwise usage of requests is preferred. | 21 # Python set up. Otherwise usage of requests is preferred. |
21 import urllib as urllib_parse | 22 import urllib as urllib_parse |
22 import urllib2 as urllib_error | 23 import urllib2 as urllib_error |
23 import urllib2 as urllib_request | 24 import urllib2 as urllib_request |
24 else: | 25 else: |
25 import urllib.error as urllib_error | 26 import urllib.error as urllib_error |
26 import urllib.parse as urllib_parse | 27 import urllib.parse as urllib_parse |
27 import urllib.request as urllib_request | 28 import urllib.request as urllib_request |
28 | 29 |
29 # Change PYTHONPATH to include utils. | 30 # Change PYTHONPATH to include utils. |
30 sys.path.insert(0, u'.') | 31 sys.path.insert(0, u'.') |
31 | 32 |
32 import utils.upload | 33 import utils.upload # pylint: disable=wrong-import-position |
33 | 34 |
34 | 35 |
35 class CLIHelper(object): | 36 class CLIHelper(object): |
36 """Class that defines CLI helper functions.""" | 37 """Command line interface (CLI) helper.""" |
37 | 38 |
38 def RunCommand(self, command): | 39 def RunCommand(self, command): |
39 """Runs a command. | 40 """Runs a command. |
40 | 41 |
41 Args: | 42 Args: |
42 command (str): command to run. | 43 command (str): command to run. |
43 | 44 |
44 Returns: | 45 Returns: |
45 tuple[int,file,file]: exit code, stdout and stderr file-like objects. | 46 tuple[int, bytes, bytes]: exit code, stdout and stderr data. |
46 """ | 47 """ |
47 arguments = shlex.split(command) | 48 arguments = shlex.split(command) |
48 process = subprocess.Popen( | 49 |
49 arguments, stderr=subprocess.PIPE, stdout=subprocess.PIPE) | 50 try: |
50 if not process: | 51 process = subprocess.Popen( |
51 logging.error(u'Running: "{0:s}" failed.'.format(command)) | 52 arguments, stderr=subprocess.PIPE, stdout=subprocess.PIPE) |
| 53 except OSError as exception: |
| 54 logging.error(u'Running: "{0:s}" failed with error: {1:s}'.format( |
| 55 command, exception)) |
52 return 1, None, None | 56 return 1, None, None |
53 | 57 |
54 output, error = process.communicate() | 58 output, error = process.communicate() |
55 if process.returncode != 0: | 59 if process.returncode != 0: |
56 logging.error(u'Running: "{0:s}" failed with error: {1:s}.'.format( | 60 logging.error(u'Running: "{0:s}" failed with error: {1!s}.'.format( |
57 command, error)) | 61 command, error)) |
58 | 62 |
59 return process.returncode, output, error | 63 return process.returncode, output, error |
60 | 64 |
61 | 65 |
62 class CodeReviewHelper(CLIHelper): | 66 class CodeReviewHelper(CLIHelper): |
63 """Class that defines codereview helper functions.""" | 67 """Codereview upload.py command helper.""" |
64 | 68 |
65 _REVIEWERS = frozenset([ | 69 _REVIEWERS_PER_PROJECT = { |
| 70 u'dfdatetime': frozenset([ |
| 71 u'joachim.metz@gmail.com', |
| 72 u'onager@deerpie.com']), |
| 73 u'dfkinds': frozenset([ |
| 74 u'joachim.metz@gmail.com', |
| 75 u'onager@deerpie.com']), |
| 76 u'dfvfs': frozenset([ |
| 77 u'joachim.metz@gmail.com', |
| 78 u'onager@deerpie.com']), |
| 79 u'dfwinreg': frozenset([ |
| 80 u'joachim.metz@gmail.com', |
| 81 u'onager@deerpie.com']), |
| 82 u'dftimewolf': frozenset([ |
| 83 u'jberggren@gmail.com', |
| 84 u'someguyiknow@google.com', |
| 85 u'tomchop@gmail.com']), |
| 86 u'plaso': frozenset([ |
| 87 u'aaronp@gmail.com', |
| 88 u'jberggren@gmail.com', |
| 89 u'joachim.metz@gmail.com', |
| 90 u'onager@deerpie.com', |
| 91 u'romaing@google.com'])} |
| 92 |
| 93 _REVIEWERS_DEFAULT = frozenset([ |
66 u'jberggren@gmail.com', | 94 u'jberggren@gmail.com', |
67 u'joachim.metz@gmail.com', | 95 u'joachim.metz@gmail.com', |
68 u'onager@deerpie.com', | 96 u'onager@deerpie.com']) |
69 u'romaing@google.com']) | |
70 | 97 |
71 _REVIEWERS_CC = frozenset([ | 98 _REVIEWERS_CC = frozenset([ |
72 u'kiddi@kiddaland.net', | 99 u'kiddi@kiddaland.net', |
73 u'log2timeline-dev@googlegroups.com']) | 100 u'log2timeline-dev@googlegroups.com']) |
74 | 101 |
75 def __init__(self, email_address, no_browser=False): | 102 def __init__(self, email_address, no_browser=False): |
76 """Initializes a codereview helper object. | 103 """Initializes a codereview helper. |
77 | 104 |
78 Args: | 105 Args: |
79 email_address (str): email address. | 106 email_address (str): email address. |
80 no_browser (Optional[bool]): True if the functionality to use the | 107 no_browser (Optional[bool]): True if the functionality to use the |
81 webbrowser to get the OAuth token should be disabled. | 108 webbrowser to get the OAuth token should be disabled. |
82 """ | 109 """ |
83 super(CodeReviewHelper, self).__init__() | 110 super(CodeReviewHelper, self).__init__() |
84 self._access_token = None | 111 self._access_token = None |
85 self._email_address = email_address | 112 self._email_address = email_address |
86 self._no_browser = no_browser | 113 self._no_browser = no_browser |
87 self._upload_py_path = os.path.join(u'utils', u'upload.py') | 114 self._upload_py_path = os.path.join(u'utils', u'upload.py') |
88 self._xsrf_token = None | 115 self._xsrf_token = None |
89 | 116 |
| 117 def _GetReviewer(self, project_name): |
| 118 """Determines the reviewer. |
| 119 |
| 120 Args: |
| 121 project_name (str): name of the project. |
| 122 |
| 123 Returns: |
| 124 str: email address of the reviewer that is used on codereview. |
| 125 """ |
| 126 reviewers = list(self._REVIEWERS_PER_PROJECT.get( |
| 127 project_name, self._REVIEWERS_DEFAULT)) |
| 128 |
| 129 try: |
| 130 reviewers.remove(self._email_address) |
| 131 except ValueError: |
| 132 pass |
| 133 |
| 134 random.shuffle(reviewers) |
| 135 |
| 136 return reviewers[0] |
| 137 |
| 138 def _GetReviewersOnCC(self, project_name, reviewer): |
| 139 """Determines the reviewers on CC. |
| 140 |
| 141 Args: |
| 142 project_name (str): name of the project. |
| 143 reviewer (str): email address of the reviewer that is used on codereview. |
| 144 |
| 145 Returns: |
| 146 str: comma seperated email addresses. |
| 147 """ |
| 148 reviewers_cc = set(self._REVIEWERS_PER_PROJECT.get( |
| 149 project_name, self._REVIEWERS_DEFAULT)) |
| 150 reviewers_cc.update(self._REVIEWERS_CC) |
| 151 |
| 152 reviewers_cc.remove(reviewer) |
| 153 |
| 154 try: |
| 155 reviewers_cc.remove(self._email_address) |
| 156 except KeyError: |
| 157 pass |
| 158 |
| 159 return u','.join(reviewers_cc) |
| 160 |
90 def AddMergeMessage(self, issue_number, message): | 161 def AddMergeMessage(self, issue_number, message): |
91 """Adds a merge message to the code review issue. | 162 """Adds a merge message to the code review issue. |
92 | 163 |
93 Where the merge is a commit to the main project git repository. | 164 Where the merge is a commit to the main project git repository. |
94 | 165 |
95 Args: | 166 Args: |
96 issue_number (int|str): codereview issue number. | 167 issue_number (int|str): codereview issue number. |
97 message (str): message to add to the code review issue. | 168 message (str): message to add to the code review issue. |
98 | 169 |
99 Returns: | 170 Returns: |
(...skipping 21 matching lines...) Expand all Loading... |
121 request.add_header( | 192 request.add_header( |
122 u'Authorization', u'OAuth {0:s}'.format(codereview_access_token)) | 193 u'Authorization', u'OAuth {0:s}'.format(codereview_access_token)) |
123 | 194 |
124 # This will change the request into a POST. | 195 # This will change the request into a POST. |
125 request.add_data(post_data) | 196 request.add_data(post_data) |
126 | 197 |
127 try: | 198 try: |
128 url_object = urllib_request.urlopen(request) | 199 url_object = urllib_request.urlopen(request) |
129 except urllib_error.HTTPError as exception: | 200 except urllib_error.HTTPError as exception: |
130 logging.error( | 201 logging.error( |
131 u'Failed publish to codereview issue: {0!s} with error: {1:s}'.format( | 202 u'Failed publish to codereview issue: {0!s} with error: {1!s}'.format( |
132 issue_number, exception)) | 203 issue_number, exception)) |
133 return False | 204 return False |
134 | 205 |
135 if url_object.code not in (200, 201): | 206 if url_object.code not in (200, 201): |
136 logging.error(( | 207 logging.error(( |
137 u'Failed publish to codereview issue: {0!s} with status code: ' | 208 u'Failed publish to codereview issue: {0!s} with status code: ' |
138 u'{1:d}').format(issue_number, url_object.code)) | 209 u'{1:d}').format(issue_number, url_object.code)) |
139 return False | 210 return False |
140 | 211 |
141 return True | 212 return True |
(...skipping 24 matching lines...) Expand all Loading... |
166 request.add_header( | 237 request.add_header( |
167 u'Authorization', u'OAuth {0:s}'.format(codereview_access_token)) | 238 u'Authorization', u'OAuth {0:s}'.format(codereview_access_token)) |
168 | 239 |
169 # This will change the request into a POST. | 240 # This will change the request into a POST. |
170 request.add_data(post_data) | 241 request.add_data(post_data) |
171 | 242 |
172 try: | 243 try: |
173 url_object = urllib_request.urlopen(request) | 244 url_object = urllib_request.urlopen(request) |
174 except urllib_error.HTTPError as exception: | 245 except urllib_error.HTTPError as exception: |
175 logging.error( | 246 logging.error( |
176 u'Failed closing codereview issue: {0!s} with error: {1:s}'.format( | 247 u'Failed closing codereview issue: {0!s} with error: {1!s}'.format( |
177 issue_number, exception)) | 248 issue_number, exception)) |
178 return False | 249 return False |
179 | 250 |
180 if url_object.code != 200: | 251 if url_object.code != 200: |
181 logging.error(( | 252 logging.error(( |
182 u'Failed closing codereview issue: {0!s} with status code: ' | 253 u'Failed closing codereview issue: {0!s} with status code: ' |
183 u'{1:d}').format(issue_number, url_object.code)) | 254 u'{1:d}').format(issue_number, url_object.code)) |
184 return False | 255 return False |
185 | 256 |
186 return True | 257 return True |
187 | 258 |
188 def CreateIssue(self, diffbase, description): | 259 def CreateIssue(self, project_name, diffbase, description): |
189 """Creates a new codereview issue. | 260 """Creates a new codereview issue. |
190 | 261 |
191 Args: | 262 Args: |
| 263 project_name (str): name of the project. |
192 diffbase (str): diffbase. | 264 diffbase (str): diffbase. |
193 description (str): description. | 265 description (str): description. |
194 | 266 |
195 Returns: | 267 Returns: |
196 int: codereview issue number or None. | 268 int: codereview issue number or None. |
197 """ | 269 """ |
198 reviewers = list(self._REVIEWERS) | 270 reviewer = self._GetReviewer(project_name) |
199 reviewers_cc = list(self._REVIEWERS_CC) | 271 reviewers_cc = self._GetReviewersOnCC(project_name, reviewer) |
200 | |
201 try: | |
202 # Remove self from reviewers list. | |
203 reviewers.remove(self._email_address) | |
204 except ValueError: | |
205 pass | |
206 | |
207 try: | |
208 # Remove self from reviewers CC list. | |
209 reviewers_cc.remove(self._email_address) | |
210 except ValueError: | |
211 pass | |
212 | |
213 reviewers = u','.join(reviewers) | |
214 reviewers_cc = u','.join(reviewers_cc) | |
215 | 272 |
216 command = u'{0:s} {1:s} --oauth2'.format( | 273 command = u'{0:s} {1:s} --oauth2'.format( |
217 sys.executable, self._upload_py_path) | 274 sys.executable, self._upload_py_path) |
218 | 275 |
219 if self._no_browser: | 276 if self._no_browser: |
220 command = u'{0:s} --no_oauth2_webbrowser'.format(command) | 277 command = u'{0:s} --no_oauth2_webbrowser'.format(command) |
221 | 278 |
222 command = ( | 279 command = ( |
223 u'{0:s} --send_mail -r {1:s} --cc {2:s} -t "{3:s}" -y -- ' | 280 u'{0:s} --send_mail -r {1:s} --cc {2:s} -t "{3:s}" -y -- ' |
224 u'{4:s}').format( | 281 u'{4:s}').format( |
225 command, reviewers, reviewers_cc, description, diffbase) | 282 command, reviewer, reviewers_cc, description, diffbase) |
226 | 283 |
227 if self._no_browser: | 284 if self._no_browser: |
228 print( | 285 print( |
229 u'Upload server: codereview.appspot.com (change with -s/--server)\n' | 286 u'Upload server: codereview.appspot.com (change with -s/--server)\n' |
230 u'Go to the following link in your browser:\n' | 287 u'Go to the following link in your browser:\n' |
231 u'\n' | 288 u'\n' |
232 u' https://codereview.appspot.com/get-access-token\n' | 289 u' https://codereview.appspot.com/get-access-token\n' |
233 u'\n' | 290 u'\n' |
234 u'and copy the access token.\n' | 291 u'and copy the access token.\n' |
235 u'\n') | 292 u'\n') |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
284 | 341 |
285 # Add header: Authorization: OAuth <codereview access token> | 342 # Add header: Authorization: OAuth <codereview access token> |
286 request.add_header( | 343 request.add_header( |
287 u'Authorization', u'OAuth {0:s}'.format(codereview_access_token)) | 344 u'Authorization', u'OAuth {0:s}'.format(codereview_access_token)) |
288 request.add_header(u'X-Requesting-XSRF-Token', u'1') | 345 request.add_header(u'X-Requesting-XSRF-Token', u'1') |
289 | 346 |
290 try: | 347 try: |
291 url_object = urllib_request.urlopen(request) | 348 url_object = urllib_request.urlopen(request) |
292 except urllib_error.HTTPError as exception: | 349 except urllib_error.HTTPError as exception: |
293 logging.error( | 350 logging.error( |
294 u'Failed retrieving codereview XSRF token with error: {0:s}'.format( | 351 u'Failed retrieving codereview XSRF token with error: {0!s}'.format( |
295 exception)) | 352 exception)) |
296 return | 353 return |
297 | 354 |
298 if url_object.code != 200: | 355 if url_object.code != 200: |
299 logging.error(( | 356 logging.error(( |
300 u'Failed retrieving codereview XSRF token with status code: ' | 357 u'Failed retrieving codereview XSRF token with status code: ' |
301 u'{0:d}').format(url_object.code)) | 358 u'{0:d}').format(url_object.code)) |
302 return | 359 return |
303 | 360 |
304 self._xsrf_token = url_object.read() | 361 self._xsrf_token = url_object.read() |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
337 """ | 394 """ |
338 codereview_url = b'https://codereview.appspot.com/api/{0!s}'.format( | 395 codereview_url = b'https://codereview.appspot.com/api/{0!s}'.format( |
339 issue_number) | 396 issue_number) |
340 | 397 |
341 request = urllib_request.Request(codereview_url) | 398 request = urllib_request.Request(codereview_url) |
342 | 399 |
343 try: | 400 try: |
344 url_object = urllib_request.urlopen(request) | 401 url_object = urllib_request.urlopen(request) |
345 except urllib_error.HTTPError as exception: | 402 except urllib_error.HTTPError as exception: |
346 logging.error( | 403 logging.error( |
347 u'Failed querying codereview issue: {0!s} with error: {1:s}'.format( | 404 u'Failed querying codereview issue: {0!s} with error: {1!s}'.format( |
348 issue_number, exception)) | 405 issue_number, exception)) |
349 return | 406 return |
350 | 407 |
351 if url_object.code != 200: | 408 if url_object.code != 200: |
352 logging.error(( | 409 logging.error(( |
353 u'Failed querying codereview issue: {0!s} with status code: ' | 410 u'Failed querying codereview issue: {0!s} with status code: ' |
354 u'{1:d}').format(issue_number, url_object.code)) | 411 u'{1:d}').format(issue_number, url_object.code)) |
355 return | 412 return |
356 | 413 |
357 response_data = url_object.read() | 414 response_data = url_object.read() |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 | 448 |
392 sys.stdout.flush() | 449 sys.stdout.flush() |
393 | 450 |
394 exit_code, output, _ = self.RunCommand(command) | 451 exit_code, output, _ = self.RunCommand(command) |
395 print(output) | 452 print(output) |
396 | 453 |
397 return exit_code == 0 | 454 return exit_code == 0 |
398 | 455 |
399 | 456 |
400 class GitHelper(CLIHelper): | 457 class GitHelper(CLIHelper): |
401 """Class that defines git helper functions.""" | 458 """Git command helper.""" |
402 | 459 |
403 def __init__(self, git_repo_url): | 460 def __init__(self, git_repo_url): |
404 """Initializes a git helper object. | 461 """Initializes a git helper. |
405 | 462 |
406 Args: | 463 Args: |
407 git_repo_url (str): git repo URL. | 464 git_repo_url (str): git repo URL. |
408 """ | 465 """ |
409 super(GitHelper, self).__init__() | 466 super(GitHelper, self).__init__() |
410 self._git_repo_url = git_repo_url | 467 self._git_repo_url = git_repo_url |
411 self._remotes = [] | 468 self._remotes = [] |
412 | 469 |
413 def _GetRemotes(self): | 470 def _GetRemotes(self): |
414 """Retrieves the git repository remotes. | 471 """Retrieves the git repository remotes. |
415 | 472 |
416 Returns: | 473 Returns: |
417 list[str]: git repository remotes or None. | 474 list[str]: git repository remotes or None. |
418 """ | 475 """ |
419 if not self._remotes: | 476 if not self._remotes: |
420 exit_code, output, _ = self.RunCommand(u'git remote -v') | 477 exit_code, output, _ = self.RunCommand(u'git remote -v') |
421 if exit_code == 0: | 478 if exit_code == 0: |
422 self._remotes = output.split(b'\n') | 479 self._remotes = list(filter(None, output.split(b'\n'))) |
423 | 480 |
424 return self._remotes | 481 return self._remotes |
425 | 482 |
426 def AddPath(self, path): | 483 def AddPath(self, path): |
427 """Adds a specific path to be managed by git. | 484 """Adds a specific path to be managed by git. |
428 | 485 |
429 Args: | 486 Args: |
430 path (str): path. | 487 path (str): path. |
431 | 488 |
432 Returns: | 489 Returns: |
(...skipping 23 matching lines...) Expand all Loading... |
456 return True | 513 return True |
457 return False | 514 return False |
458 | 515 |
459 def CheckHasProjectOrigin(self): | 516 def CheckHasProjectOrigin(self): |
460 """Checks if the git repo has the project remote origin defined. | 517 """Checks if the git repo has the project remote origin defined. |
461 | 518 |
462 Returns: | 519 Returns: |
463 bool: True if the git repo has the project origin defined. | 520 bool: True if the git repo has the project origin defined. |
464 """ | 521 """ |
465 origin_git_repo_url = self.GetRemoteOrigin() | 522 origin_git_repo_url = self.GetRemoteOrigin() |
466 return origin_git_repo_url == self._git_repo_url | 523 |
| 524 is_match = origin_git_repo_url == self._git_repo_url |
| 525 if not is_match: |
| 526 is_match = origin_git_repo_url == self._git_repo_url[:-4] |
| 527 |
| 528 return is_match |
467 | 529 |
468 def CheckHasProjectUpstream(self): | 530 def CheckHasProjectUpstream(self): |
469 """Checks if the git repo has the project remote upstream defined. | 531 """Checks if the git repo has the project remote upstream defined. |
470 | 532 |
471 Returns: | 533 Returns: |
472 bool: True if the git repo has the project remote upstream defined. | 534 bool: True if the git repo has the project remote upstream defined. |
473 """ | 535 """ |
474 # Check for remote entries starting with upstream. | 536 # Check for remote entries starting with upstream. |
475 for remote in self._GetRemotes(): | 537 for remote in self._GetRemotes(): |
476 if remote.startswith(b'upstream\t{0:s}'.format(self._git_repo_url)): | 538 if remote.startswith(b'upstream\t{0:s}'.format(self._git_repo_url)): |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
595 | 657 |
596 Returns: | 658 Returns: |
597 list[str]: names of the changed Python files. | 659 list[str]: names of the changed Python files. |
598 """ | 660 """ |
599 upload_path = os.path.join(u'utils', u'upload.py') | 661 upload_path = os.path.join(u'utils', u'upload.py') |
600 python_files = [] | 662 python_files = [] |
601 for changed_file in self.GetChangedFiles(diffbase=diffbase): | 663 for changed_file in self.GetChangedFiles(diffbase=diffbase): |
602 if (not changed_file.endswith(u'.py') or | 664 if (not changed_file.endswith(u'.py') or |
603 changed_file.endswith(u'_pb2.py') or | 665 changed_file.endswith(u'_pb2.py') or |
604 not os.path.exists(changed_file) or | 666 not os.path.exists(changed_file) or |
| 667 changed_file.startswith(u'data') or |
605 changed_file.startswith(u'docs') or | 668 changed_file.startswith(u'docs') or |
606 changed_file.startswith(u'test_data') or | 669 changed_file.startswith(u'test_data') or |
607 changed_file in (u'setup.py', upload_path)): | 670 changed_file in (u'setup.py', upload_path)): |
608 continue | 671 continue |
609 | 672 |
610 python_files.append(changed_file) | 673 python_files.append(changed_file) |
611 | 674 |
612 return python_files | 675 return python_files |
613 | 676 |
614 def GetEmailAddress(self): | 677 def GetEmailAddress(self): |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
736 | 799 |
737 return exit_code == 0 | 800 return exit_code == 0 |
738 | 801 |
739 def SwitchToMasterBranch(self): | 802 def SwitchToMasterBranch(self): |
740 """Switches git to the master branch. | 803 """Switches git to the master branch. |
741 | 804 |
742 Returns: | 805 Returns: |
743 bool: True if the git repository has switched to the master branch. | 806 bool: True if the git repository has switched to the master branch. |
744 """ | 807 """ |
745 exit_code, _, _ = self.RunCommand(u'git checkout master') | 808 exit_code, _, _ = self.RunCommand(u'git checkout master') |
746 return exit_code != 0 | 809 return exit_code == 0 |
747 | 810 |
748 | 811 |
749 class GitHubHelper(object): | 812 class GitHubHelper(object): |
750 """Class that defines github helper functions.""" | 813 """Github helper.""" |
751 | 814 |
752 def __init__(self, organization, project): | 815 def __init__(self, organization, project): |
753 """Initializes a github helper object. | 816 """Initializes a github helper. |
754 | 817 |
755 Args: | 818 Args: |
756 organization (str): github organization name. | 819 organization (str): github organization name. |
757 project (str): github project name. | 820 project (str): github project name. |
758 """ | 821 """ |
759 super(GitHubHelper, self).__init__() | 822 super(GitHubHelper, self).__init__() |
760 self._organization = organization | 823 self._organization = organization |
761 self._project = project | 824 self._project = project |
762 | 825 |
763 def CreatePullRequest( | 826 def CreatePullRequest( |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
795 | 858 |
796 request = urllib_request.Request(github_url) | 859 request = urllib_request.Request(github_url) |
797 | 860 |
798 # This will change the request into a POST. | 861 # This will change the request into a POST. |
799 request.add_data(post_data) | 862 request.add_data(post_data) |
800 | 863 |
801 try: | 864 try: |
802 url_object = urllib_request.urlopen(request) | 865 url_object = urllib_request.urlopen(request) |
803 except urllib_error.HTTPError as exception: | 866 except urllib_error.HTTPError as exception: |
804 logging.error( | 867 logging.error( |
805 u'Failed creating pull request: {0!s} with error: {1:s}'.format( | 868 u'Failed creating pull request: {0!s} with error: {1!s}'.format( |
806 codereview_issue_number, exception)) | 869 codereview_issue_number, exception)) |
807 return False | 870 return False |
808 | 871 |
809 if url_object.code not in (200, 201): | 872 if url_object.code not in (200, 201): |
810 logging.error( | 873 logging.error( |
811 u'Failed creating pull request: {0!s} with status code: {1:d}'.format( | 874 u'Failed creating pull request: {0!s} with status code: {1:d}'.format( |
812 codereview_issue_number, url_object.code)) | 875 codereview_issue_number, url_object.code)) |
813 return False | 876 return False |
814 | 877 |
815 return True | 878 return True |
(...skipping 19 matching lines...) Expand all Loading... |
835 dict[str,object]: JSON response or None. | 898 dict[str,object]: JSON response or None. |
836 """ | 899 """ |
837 github_url = b'https://api.github.com/users/{0:s}'.format(username) | 900 github_url = b'https://api.github.com/users/{0:s}'.format(username) |
838 | 901 |
839 request = urllib_request.Request(github_url) | 902 request = urllib_request.Request(github_url) |
840 | 903 |
841 try: | 904 try: |
842 url_object = urllib_request.urlopen(request) | 905 url_object = urllib_request.urlopen(request) |
843 except urllib_error.HTTPError as exception: | 906 except urllib_error.HTTPError as exception: |
844 logging.error( | 907 logging.error( |
845 u'Failed querying github user: {0:s} with error: {1:s}'.format( | 908 u'Failed querying github user: {0:s} with error: {1!s}'.format( |
846 username, exception)) | 909 username, exception)) |
847 return | 910 return |
848 | 911 |
849 if url_object.code != 200: | 912 if url_object.code != 200: |
850 logging.error( | 913 logging.error( |
851 u'Failed querying github user: {0:d} with status code: {1:d}'.format( | 914 u'Failed querying github user: {0:d} with status code: {1:d}'.format( |
852 username, url_object.code)) | 915 username, url_object.code)) |
853 return | 916 return |
854 | 917 |
855 response_data = url_object.read() | 918 response_data = url_object.read() |
(...skipping 14 matching lines...) Expand all Loading... |
870 u'# Name (email address)', | 933 u'# Name (email address)', |
871 u'#', | 934 u'#', |
872 u'# For organizations:', | 935 u'# For organizations:', |
873 u'# Organization (fnmatch pattern)', | 936 u'# Organization (fnmatch pattern)', |
874 u'#', | 937 u'#', |
875 u'# See python fnmatch module documentation for more information.', | 938 u'# See python fnmatch module documentation for more information.', |
876 u'', | 939 u'', |
877 u'Google Inc. (*@google.com)'] | 940 u'Google Inc. (*@google.com)'] |
878 | 941 |
879 SUPPORTED_PROJECTS = frozenset([ | 942 SUPPORTED_PROJECTS = frozenset([ |
880 u'dfdatetime', u'dfvfs', u'dfwinreg', u'l2tdevtools', u'l2tdocs', | 943 u'artifacts', u'dfdatetime', u'dfkinds', u'dfvfs', u'dfwinreg', |
881 u'plaso']) | 944 u'dftimewolf', u'eccemotus', u'l2tdevtools', u'l2tdocs', u'plaso']) |
882 | 945 |
883 def __init__(self, script_path): | 946 def __init__(self, script_path): |
884 """Initializes a project helper object. | 947 """Initializes a project helper. |
885 | 948 |
886 Args: | 949 Args: |
887 script_path (str): path to the script. | 950 script_path (str): path to the script. |
888 | 951 |
889 Raises: | 952 Raises: |
890 ValueError: if the project name is not supported. | 953 ValueError: if the project name is not supported. |
891 """ | 954 """ |
892 super(ProjectHelper, self).__init__() | 955 super(ProjectHelper, self).__init__() |
893 self.project_name = self._GetProjectName(script_path) | 956 self.project_name = self._GetProjectName(script_path) |
894 | 957 |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
932 """ | 995 """ |
933 if not os.path.exists(path): | 996 if not os.path.exists(path): |
934 logging.error(u'Missing file: {0:s}'.format(path)) | 997 logging.error(u'Missing file: {0:s}'.format(path)) |
935 return | 998 return |
936 | 999 |
937 try: | 1000 try: |
938 with open(path, u'rb') as file_object: | 1001 with open(path, u'rb') as file_object: |
939 file_contents = file_object.read() | 1002 file_contents = file_object.read() |
940 | 1003 |
941 except IOError as exception: | 1004 except IOError as exception: |
942 logging.error(u'Unable to read file with error: {0:s}'.format(exception)) | 1005 logging.error(u'Unable to read file with error: {0!s}'.format(exception)) |
943 return | 1006 return |
944 | 1007 |
945 try: | 1008 try: |
946 file_contents = file_contents.decode(u'utf-8') | 1009 file_contents = file_contents.decode(u'utf-8') |
947 except UnicodeDecodeError as exception: | 1010 except UnicodeDecodeError as exception: |
948 logging.error( | 1011 logging.error( |
949 u'Unable to read file with error: {0:s}'.format(exception)) | 1012 u'Unable to read file with error: {0!s}'.format(exception)) |
950 return | 1013 return |
951 | 1014 |
952 return file_contents | 1015 return file_contents |
953 | 1016 |
954 def GetVersion(self): | 1017 def GetVersion(self): |
955 """Retrieves the project version from the version file. | 1018 """Retrieves the project version from the version file. |
956 | 1019 |
957 Returns: | 1020 Returns: |
958 str: project version or None. | 1021 str: project version or None. |
959 """ | 1022 """ |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
992 self.project_name, project_version), | 1055 self.project_name, project_version), |
993 u'', | 1056 u'', |
994 u' * Auto-generated', | 1057 u' * Auto-generated', |
995 u'', | 1058 u'', |
996 u' -- {0:s} {1:s}'.format(dpkg_maintainter, dpkg_date)]) | 1059 u' -- {0:s} {1:s}'.format(dpkg_maintainter, dpkg_date)]) |
997 | 1060 |
998 try: | 1061 try: |
999 dpkg_changelog_content = dpkg_changelog_content.encode(u'utf-8') | 1062 dpkg_changelog_content = dpkg_changelog_content.encode(u'utf-8') |
1000 except UnicodeEncodeError as exception: | 1063 except UnicodeEncodeError as exception: |
1001 logging.error( | 1064 logging.error( |
1002 u'Unable to write dpkg changelog file with error: {0:s}'.format( | 1065 u'Unable to write dpkg changelog file with error: {0!s}'.format( |
1003 exception)) | 1066 exception)) |
1004 return False | 1067 return False |
1005 | 1068 |
1006 try: | 1069 try: |
1007 with open(dpkg_changelog_path, u'wb') as file_object: | 1070 with open(dpkg_changelog_path, u'wb') as file_object: |
1008 file_object.write(dpkg_changelog_content) | 1071 file_object.write(dpkg_changelog_content) |
1009 except IOError as exception: | 1072 except IOError as exception: |
1010 logging.error( | 1073 logging.error( |
1011 u'Unable to write dpkg changelog file with error: {0:s}'.format( | 1074 u'Unable to write dpkg changelog file with error: {0!s}'.format( |
1012 exception)) | 1075 exception)) |
1013 return False | 1076 return False |
1014 | 1077 |
1015 return True | 1078 return True |
1016 | 1079 |
1017 def UpdateAuthorsFile(self): | 1080 def UpdateAuthorsFile(self): |
1018 """Updates the AUTHORS file. | 1081 """Updates the AUTHORS file. |
1019 | 1082 |
1020 Returns: | 1083 Returns: |
1021 bool: True if the AUTHORS file update was successful. | 1084 bool: True if the AUTHORS file update was successful. |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1077 line.startswith(u'__version__ = ')): | 1140 line.startswith(u'__version__ = ')): |
1078 version_string = u'__version__ = \'{0:s}\''.format(date_version) | 1141 version_string = u'__version__ = \'{0:s}\''.format(date_version) |
1079 lines[line_index] = version_string | 1142 lines[line_index] = version_string |
1080 | 1143 |
1081 version_file_contents = u'\n'.join(lines) | 1144 version_file_contents = u'\n'.join(lines) |
1082 | 1145 |
1083 try: | 1146 try: |
1084 version_file_contents = version_file_contents.encode(u'utf-8') | 1147 version_file_contents = version_file_contents.encode(u'utf-8') |
1085 except UnicodeEncodeError as exception: | 1148 except UnicodeEncodeError as exception: |
1086 logging.error( | 1149 logging.error( |
1087 u'Unable to write version file with error: {0:s}'.format(exception)) | 1150 u'Unable to write version file with error: {0!s}'.format(exception)) |
1088 return False | 1151 return False |
1089 | 1152 |
1090 try: | 1153 try: |
1091 with open(self.version_file_path, u'wb') as file_object: | 1154 with open(self.version_file_path, u'wb') as file_object: |
1092 file_object.write(version_file_contents) | 1155 file_object.write(version_file_contents) |
1093 | 1156 |
1094 except IOError as exception: | 1157 except IOError as exception: |
1095 logging.error( | 1158 logging.error( |
1096 u'Unable to write version file with error: {0:s}'.format(exception)) | 1159 u'Unable to write version file with error: {0!s}'.format(exception)) |
1097 return False | 1160 return False |
1098 | 1161 |
1099 return True | 1162 return True |
1100 | 1163 |
1101 | 1164 |
1102 class PylintHelper(CLIHelper): | 1165 class PylintHelper(CLIHelper): |
1103 """Class that defines pylint helper functions.""" | 1166 """Class that defines pylint helper functions.""" |
1104 | 1167 |
1105 _MINIMUM_VERSION_TUPLE = (1, 5, 0) | 1168 _MINIMUM_VERSION_TUPLE = (1, 5, 0) |
1106 | 1169 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1149 | 1212 |
1150 version_tuple = tuple([int(digit) for digit in version.split(b'.')]) | 1213 version_tuple = tuple([int(digit) for digit in version.split(b'.')]) |
1151 | 1214 |
1152 return version_tuple >= self._MINIMUM_VERSION_TUPLE | 1215 return version_tuple >= self._MINIMUM_VERSION_TUPLE |
1153 | 1216 |
1154 | 1217 |
1155 class ReadTheDocsHelper(object): | 1218 class ReadTheDocsHelper(object): |
1156 """Class that defines readthedocs helper functions.""" | 1219 """Class that defines readthedocs helper functions.""" |
1157 | 1220 |
1158 def __init__(self, project): | 1221 def __init__(self, project): |
1159 """Initializes a readthedocs helper object. | 1222 """Initializes a readthedocs helper. |
1160 | 1223 |
1161 Args: | 1224 Args: |
1162 project (str): github project name. | 1225 project (str): github project name. |
1163 """ | 1226 """ |
1164 super(ReadTheDocsHelper, self).__init__() | 1227 super(ReadTheDocsHelper, self).__init__() |
1165 self._project = project | 1228 self._project = project |
1166 | 1229 |
1167 def TriggerBuild(self): | 1230 def TriggerBuild(self): |
1168 """Triggers readthedocs to build the docs of the project. | 1231 """Triggers readthedocs to build the docs of the project. |
1169 | 1232 |
1170 Returns: | 1233 Returns: |
1171 bool: True if the build was triggered. | 1234 bool: True if the build was triggered. |
1172 """ | 1235 """ |
1173 readthedocs_url = u'https://readthedocs.org/build/{0:s}'.format( | 1236 readthedocs_url = u'https://readthedocs.org/build/{0:s}'.format( |
1174 self._project) | 1237 self._project) |
1175 | 1238 |
1176 request = urllib_request.Request(readthedocs_url) | 1239 request = urllib_request.Request(readthedocs_url) |
1177 | 1240 |
1178 # This will change the request into a POST. | 1241 # This will change the request into a POST. |
1179 request.add_data(b'') | 1242 request.add_data(b'') |
1180 | 1243 |
1181 try: | 1244 try: |
1182 url_object = urllib_request.urlopen(request) | 1245 url_object = urllib_request.urlopen(request) |
1183 except urllib_error.HTTPError as exception: | 1246 except urllib_error.HTTPError as exception: |
1184 logging.error( | 1247 logging.error( |
1185 u'Failed triggering build with error: {0:s}'.format( | 1248 u'Failed triggering build with error: {0!s}'.format( |
1186 exception)) | 1249 exception)) |
1187 return False | 1250 return False |
1188 | 1251 |
1189 if url_object.code != 200: | 1252 if url_object.code != 200: |
1190 logging.error( | 1253 logging.error( |
1191 u'Failed triggering build with status code: {1:d}'.format( | 1254 u'Failed triggering build with status code: {1:d}'.format( |
1192 url_object.code)) | 1255 url_object.code)) |
1193 return False | 1256 return False |
1194 | 1257 |
1195 return True | 1258 return True |
1196 | 1259 |
1197 | 1260 |
1198 class SphinxAPIDocHelper(CLIHelper): | 1261 class SphinxAPIDocHelper(CLIHelper): |
1199 """Class that defines sphinx-apidoc helper functions.""" | 1262 """Class that defines sphinx-apidoc helper functions.""" |
1200 | 1263 |
1201 _MINIMUM_VERSION_TUPLE = (1, 2, 0) | 1264 _MINIMUM_VERSION_TUPLE = (1, 2, 0) |
1202 | 1265 |
1203 def __init__(self, project): | 1266 def __init__(self, project): |
1204 """Initializes a sphinx-apidoc helper object. | 1267 """Initializes a sphinx-apidoc helper. |
1205 | 1268 |
1206 Args: | 1269 Args: |
1207 project (str): github project name. | 1270 project (str): github project name. |
1208 """ | 1271 """ |
1209 super(SphinxAPIDocHelper, self).__init__() | 1272 super(SphinxAPIDocHelper, self).__init__() |
1210 self._project = project | 1273 self._project = project |
1211 | 1274 |
1212 def CheckUpToDateVersion(self): | 1275 def CheckUpToDateVersion(self): |
1213 """Checks if the sphinx-apidoc version is up to date. | 1276 """Checks if the sphinx-apidoc version is up to date. |
1214 | 1277 |
(...skipping 25 matching lines...) Expand all Loading... |
1240 | 1303 |
1241 return exit_code == 0 | 1304 return exit_code == 0 |
1242 | 1305 |
1243 | 1306 |
1244 class NetRCFile(object): | 1307 class NetRCFile(object): |
1245 """Class that defines a .netrc file.""" | 1308 """Class that defines a .netrc file.""" |
1246 | 1309 |
1247 _NETRC_SEPARATOR_RE = re.compile(r'[^ \t\n]+') | 1310 _NETRC_SEPARATOR_RE = re.compile(r'[^ \t\n]+') |
1248 | 1311 |
1249 def __init__(self): | 1312 def __init__(self): |
1250 """Initializes a .netrc file object.""" | 1313 """Initializes a .netrc file.""" |
1251 super(NetRCFile, self).__init__() | 1314 super(NetRCFile, self).__init__() |
1252 self._contents = None | 1315 self._contents = None |
1253 self._values = None | 1316 self._values = None |
1254 | 1317 |
1255 home_path = os.path.expanduser(u'~') | 1318 home_path = os.path.expanduser(u'~') |
1256 self._path = os.path.join(home_path, u'.netrc') | 1319 self._path = os.path.join(home_path, u'.netrc') |
1257 if not os.path.exists(self._path): | 1320 if not os.path.exists(self._path): |
1258 return | 1321 return |
1259 | 1322 |
1260 with open(self._path, 'r') as file_object: | 1323 with open(self._path, 'r') as file_object: |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1314 | 1377 |
1315 class ReviewFile(object): | 1378 class ReviewFile(object): |
1316 """Class that defines a review file. | 1379 """Class that defines a review file. |
1317 | 1380 |
1318 A review file is use to track code review relevant information like the | 1381 A review file is use to track code review relevant information like the |
1319 codereview issue number. It is stored in the .review subdirectory and | 1382 codereview issue number. It is stored in the .review subdirectory and |
1320 named after the feature branch e.g. ".review/feature". | 1383 named after the feature branch e.g. ".review/feature". |
1321 """ | 1384 """ |
1322 | 1385 |
1323 def __init__(self, branch_name): | 1386 def __init__(self, branch_name): |
1324 """Initializes a review file object. | 1387 """Initializes a review file. |
1325 | 1388 |
1326 Args: | 1389 Args: |
1327 branch_name (str): name of the feature branch of the review. | 1390 branch_name (str): name of the feature branch of the review. |
1328 """ | 1391 """ |
1329 super(ReviewFile, self).__init__() | 1392 super(ReviewFile, self).__init__() |
1330 self._contents = None | 1393 self._contents = None |
1331 self._path = os.path.join(u'.review', branch_name) | 1394 self._path = os.path.join(u'.review', branch_name) |
1332 | 1395 |
1333 if os.path.exists(self._path): | 1396 if os.path.exists(self._path): |
1334 with open(self._path, 'r') as file_object: | 1397 with open(self._path, 'r') as file_object: |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1382 | 1445 |
1383 class ReviewHelper(object): | 1446 class ReviewHelper(object): |
1384 """Class that defines review helper functions.""" | 1447 """Class that defines review helper functions.""" |
1385 | 1448 |
1386 _PROJECT_NAME_PREFIX_REGEX = re.compile( | 1449 _PROJECT_NAME_PREFIX_REGEX = re.compile( |
1387 r'\[({0:s})\] '.format(u'|'.join(ProjectHelper.SUPPORTED_PROJECTS))) | 1450 r'\[({0:s})\] '.format(u'|'.join(ProjectHelper.SUPPORTED_PROJECTS))) |
1388 | 1451 |
1389 def __init__( | 1452 def __init__( |
1390 self, command, github_origin, feature_branch, diffbase, all_files=False, | 1453 self, command, github_origin, feature_branch, diffbase, all_files=False, |
1391 no_browser=False, no_confirm=False): | 1454 no_browser=False, no_confirm=False): |
1392 """Initializes a review helper object. | 1455 """Initializes a review helper. |
1393 | 1456 |
1394 Args: | 1457 Args: |
1395 command (str): user provided command, for example "create", "lint". | 1458 command (str): user provided command, for example "create", "lint". |
1396 github_origin (str): github origin. | 1459 github_origin (str): github origin. |
1397 feature_branch (str): feature branch. | 1460 feature_branch (str): feature branch. |
1398 diffbase (str): diffbase. | 1461 diffbase (str): diffbase. |
1399 all_files (Optional[bool]): True if the command should apply to all | 1462 all_files (Optional[bool]): True if the command should apply to all |
1400 files. Currently this only affects the lint command. | 1463 files. Currently this only affects the lint command. |
1401 no_browser (Optional[bool]): True if the functionality to use the | 1464 no_browser (Optional[bool]): True if the functionality to use the |
1402 webbrowser to get the OAuth token should be disabled. | 1465 webbrowser to get the OAuth token should be disabled. |
(...skipping 14 matching lines...) Expand all Loading... |
1417 self._fork_feature_branch = None | 1480 self._fork_feature_branch = None |
1418 self._fork_username = None | 1481 self._fork_username = None |
1419 self._merge_author = None | 1482 self._merge_author = None |
1420 self._merge_description = None | 1483 self._merge_description = None |
1421 self._no_browser = no_browser | 1484 self._no_browser = no_browser |
1422 self._no_confirm = no_confirm | 1485 self._no_confirm = no_confirm |
1423 self._project_helper = None | 1486 self._project_helper = None |
1424 self._project_name = None | 1487 self._project_name = None |
1425 self._sphinxapidoc_helper = None | 1488 self._sphinxapidoc_helper = None |
1426 | 1489 |
| 1490 if self._github_origin: |
| 1491 self._fork_username, _, self._fork_feature_branch = ( |
| 1492 self._github_origin.partition(u':')) |
| 1493 |
1427 def CheckLocalGitState(self): | 1494 def CheckLocalGitState(self): |
1428 """Checks the state of the local git repository. | 1495 """Checks the state of the local git repository. |
1429 | 1496 |
1430 Returns: | 1497 Returns: |
1431 bool: True if the state of the local git repository is sane. | 1498 bool: True if the state of the local git repository is sane. |
1432 """ | 1499 """ |
1433 if self._command in (u'close', u'create', u'lint', u'update'): | 1500 if self._command in ( |
| 1501 u'close', u'create', u'lint', u'lint-test', u'lint_test', u'update'): |
1434 if not self._git_helper.CheckHasProjectUpstream(): | 1502 if not self._git_helper.CheckHasProjectUpstream(): |
1435 print(u'{0:s} aborted - missing project upstream.'.format( | 1503 print(u'{0:s} aborted - missing project upstream.'.format( |
1436 self._command.title())) | 1504 self._command.title())) |
1437 print(u'Run: git remote add upstream {0:s}'.format(self._git_repo_url)) | 1505 print(u'Run: git remote add upstream {0:s}'.format(self._git_repo_url)) |
1438 return False | 1506 return False |
1439 | 1507 |
1440 elif self._command == u'merge': | 1508 elif self._command == u'merge': |
1441 if not self._git_helper.CheckHasProjectOrigin(): | 1509 if not self._git_helper.CheckHasProjectOrigin(): |
1442 print(u'{0:s} aborted - missing project origin.'.format( | 1510 print(u'{0:s} aborted - missing project origin.'.format( |
1443 self._command.title())) | 1511 self._command.title())) |
1444 return False | 1512 return False |
1445 | 1513 |
1446 if self._command not in ( | 1514 if self._command not in ( |
1447 u'lint', u'test', u'update-version', u'update_version'): | 1515 u'lint', u'lint-test', u'lint_test', u'test', u'update-version', |
| 1516 u'update_version'): |
1448 if self._git_helper.CheckHasUncommittedChanges(): | 1517 if self._git_helper.CheckHasUncommittedChanges(): |
1449 print(u'{0:s} aborted - detected uncommitted changes.'.format( | 1518 print(u'{0:s} aborted - detected uncommitted changes.'.format( |
1450 self._command.title())) | 1519 self._command.title())) |
1451 print(u'Run: git commit') | 1520 print(u'Run: git commit') |
1452 return False | 1521 return False |
1453 | 1522 |
1454 self._active_branch = self._git_helper.GetActiveBranch() | 1523 self._active_branch = self._git_helper.GetActiveBranch() |
1455 if self._command in (u'create', u'update'): | 1524 if self._command in (u'create', u'update'): |
1456 if self._active_branch == u'master': | 1525 if self._active_branch == u'master': |
1457 print(u'{0:s} aborted - active branch is master.'.format( | 1526 print(u'{0:s} aborted - active branch is master.'.format( |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1494 force_push = True | 1563 force_push = True |
1495 else: | 1564 else: |
1496 force_push = False | 1565 force_push = False |
1497 | 1566 |
1498 if not self._git_helper.PushToOrigin( | 1567 if not self._git_helper.PushToOrigin( |
1499 self._active_branch, force=force_push): | 1568 self._active_branch, force=force_push): |
1500 print(u'{0:s} aborted - unable to push updates to origin/{1:s}.'.format( | 1569 print(u'{0:s} aborted - unable to push updates to origin/{1:s}.'.format( |
1501 self._command.title(), self._active_branch)) | 1570 self._command.title(), self._active_branch)) |
1502 return False | 1571 return False |
1503 | 1572 |
1504 elif self._command == u'lint': | 1573 elif self._command in (u'lint', u'lint-test', u'lint_test'): |
1505 self._git_helper.CheckSynchronizedWithUpstream() | 1574 self._git_helper.CheckSynchronizedWithUpstream() |
1506 | 1575 |
1507 elif self._command == u'merge': | 1576 elif self._command == u'merge': |
1508 if not self._git_helper.SynchronizeWithOrigin(): | 1577 if not self._git_helper.SynchronizeWithOrigin(): |
1509 print(( | 1578 print(( |
1510 u'{0:s} aborted - unable to synchronize with ' | 1579 u'{0:s} aborted - unable to synchronize with ' |
1511 u'origin/master.').format(self._command.title())) | 1580 u'origin/master.').format(self._command.title())) |
1512 return False | 1581 return False |
1513 | 1582 |
1514 return True | 1583 return True |
1515 | 1584 |
1516 def Close(self): | 1585 def Close(self): |
1517 """Closes a review. | 1586 """Closes a review. |
1518 | 1587 |
1519 Returns: | 1588 Returns: |
1520 bool: True if the close was successful. | 1589 bool: True if the close was successful. |
1521 """ | 1590 """ |
1522 review_file = ReviewFile(self._feature_branch) | |
1523 if not review_file.Exists(): | |
1524 print(u'Review file missing for branch: {0:s}'.format( | |
1525 self._feature_branch)) | |
1526 return False | |
1527 | |
1528 if not self._git_helper.CheckHasBranch(self._feature_branch): | 1591 if not self._git_helper.CheckHasBranch(self._feature_branch): |
1529 print(u'No such feature branch: {0:s}'.format(self._feature_branch)) | 1592 print(u'No such feature branch: {0:s}'.format(self._feature_branch)) |
1530 else: | 1593 else: |
1531 self._git_helper.RemoveFeatureBranch(self._feature_branch) | 1594 self._git_helper.RemoveFeatureBranch(self._feature_branch) |
1532 | 1595 |
1533 codereview_issue_number = review_file.GetCodeReviewIssueNumber() | 1596 review_file = ReviewFile(self._feature_branch) |
| 1597 if not review_file.Exists(): |
| 1598 print(u'Review file missing for branch: {0:s}'.format( |
| 1599 self._feature_branch)) |
1534 | 1600 |
1535 review_file.Remove() | 1601 else: |
| 1602 codereview_issue_number = review_file.GetCodeReviewIssueNumber() |
1536 | 1603 |
1537 if codereview_issue_number: | 1604 review_file.Remove() |
1538 if not self._codereview_helper.CloseIssue(codereview_issue_number): | 1605 |
1539 print(u'Unable to close code review: {0!s}'.format( | 1606 if codereview_issue_number: |
1540 codereview_issue_number)) | 1607 if not self._codereview_helper.CloseIssue(codereview_issue_number): |
1541 print(( | 1608 print(u'Unable to close code review: {0!s}'.format( |
1542 u'Close it manually on: https://codereview.appspot.com/' | 1609 codereview_issue_number)) |
1543 u'{0!s}').format(codereview_issue_number)) | 1610 print(( |
| 1611 u'Close it manually on: https://codereview.appspot.com/' |
| 1612 u'{0!s}').format(codereview_issue_number)) |
1544 | 1613 |
1545 return True | 1614 return True |
1546 | 1615 |
1547 def Create(self): | 1616 def Create(self): |
1548 """Creates a review. | 1617 """Creates a review. |
1549 | 1618 |
1550 Returns: | 1619 Returns: |
1551 bool: True if the create was successful. | 1620 bool: True if the create was successful. |
1552 """ | 1621 """ |
1553 review_file = ReviewFile(self._active_branch) | 1622 review_file = ReviewFile(self._active_branch) |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1590 description = last_commit_message | 1659 description = last_commit_message |
1591 else: | 1660 else: |
1592 description = user_input | 1661 description = user_input |
1593 | 1662 |
1594 # Prefix the description with the project name for code review to make it | 1663 # Prefix the description with the project name for code review to make it |
1595 # easier to distinguish between projects. | 1664 # easier to distinguish between projects. |
1596 code_review_description = u'[{0:s}] {1:s}'.format( | 1665 code_review_description = u'[{0:s}] {1:s}'.format( |
1597 self._project_name, description) | 1666 self._project_name, description) |
1598 | 1667 |
1599 codereview_issue_number = self._codereview_helper.CreateIssue( | 1668 codereview_issue_number = self._codereview_helper.CreateIssue( |
1600 self._diffbase, code_review_description) | 1669 self._project_name, self._diffbase, code_review_description) |
1601 if not codereview_issue_number: | 1670 if not codereview_issue_number: |
1602 print(u'{0:s} aborted - unable to create codereview issue.'.format( | 1671 print(u'{0:s} aborted - unable to create codereview issue.'.format( |
1603 self._command.title())) | 1672 self._command.title())) |
1604 return False | 1673 return False |
1605 | 1674 |
1606 if not os.path.isdir(u'.review'): | 1675 if not os.path.isdir(u'.review'): |
1607 os.mkdir(u'.review') | 1676 os.mkdir(u'.review') |
1608 | 1677 |
1609 review_file.Create(codereview_issue_number) | 1678 review_file.Create(codereview_issue_number) |
1610 | 1679 |
1611 create_github_origin = u'{0:s}:{1:s}'.format( | 1680 create_github_origin = u'{0:s}:{1:s}'.format( |
1612 git_origin, self._active_branch) | 1681 git_origin, self._active_branch) |
1613 if not self._github_helper.CreatePullRequest( | 1682 if not self._github_helper.CreatePullRequest( |
1614 github_access_token, codereview_issue_number, create_github_origin, | 1683 github_access_token, codereview_issue_number, create_github_origin, |
1615 description): | 1684 description): |
1616 print(u'Unable to create pull request.') | 1685 print(u'Unable to create pull request.') |
1617 | 1686 |
1618 return True | 1687 return True |
1619 | 1688 |
1620 def InitializeHelpers(self): | 1689 def InitializeHelpers(self): |
1621 """Initializes the helper objects. | 1690 """Initializes the helper. |
1622 | 1691 |
1623 Returns: | 1692 Returns: |
1624 bool: True if the helper initialization was successful. | 1693 bool: True if the helper initialization was successful. |
1625 """ | 1694 """ |
1626 script_path = os.path.abspath(__file__) | 1695 script_path = os.path.abspath(__file__) |
1627 | 1696 |
1628 self._project_helper = ProjectHelper(script_path) | 1697 self._project_helper = ProjectHelper(script_path) |
1629 | 1698 |
1630 self._project_name = self._project_helper.project_name | 1699 self._project_name = self._project_helper.project_name |
1631 if not self._project_name: | 1700 if not self._project_name: |
(...skipping 29 matching lines...) Expand all Loading... |
1661 | 1730 |
1662 def Lint(self): | 1731 def Lint(self): |
1663 """Lints a review. | 1732 """Lints a review. |
1664 | 1733 |
1665 Returns: | 1734 Returns: |
1666 bool: True if linting was successful. | 1735 bool: True if linting was successful. |
1667 """ | 1736 """ |
1668 if self._project_name == u'l2tdocs': | 1737 if self._project_name == u'l2tdocs': |
1669 return True | 1738 return True |
1670 | 1739 |
1671 if self._command not in (u'create', u'merge', u'lint', u'update'): | 1740 if self._command not in ( |
| 1741 u'create', u'merge', u'lint', u'lint-test', u'lint_test', u'update'): |
1672 return True | 1742 return True |
1673 | 1743 |
1674 pylint_helper = PylintHelper() | 1744 pylint_helper = PylintHelper() |
1675 if not pylint_helper.CheckUpToDateVersion(): | 1745 if not pylint_helper.CheckUpToDateVersion(): |
1676 print(u'{0:s} aborted - pylint verion 1.5.0 or later required.'.format( | 1746 print(u'{0:s} aborted - pylint verion 1.5.0 or later required.'.format( |
1677 self._command.title())) | 1747 self._command.title())) |
1678 return False | 1748 return False |
1679 | 1749 |
1680 if self._command == u'merge': | |
1681 fork_git_repo_url = self._github_helper.GetForkGitRepoUrl( | |
1682 self._fork_username) | |
1683 | |
1684 if not self._git_helper.PullFromFork( | |
1685 fork_git_repo_url, self._fork_feature_branch): | |
1686 print(u'{0:s} aborted - unable to pull changes from fork.'.format( | |
1687 self._command.title())) | |
1688 return False | |
1689 | |
1690 if self._all_files: | 1750 if self._all_files: |
1691 diffbase = None | 1751 diffbase = None |
1692 elif self._command == u'merge': | 1752 elif self._command == u'merge': |
1693 diffbase = u'origin/master' | 1753 diffbase = u'origin/master' |
1694 else: | 1754 else: |
1695 diffbase = self._diffbase | 1755 diffbase = self._diffbase |
1696 | 1756 |
1697 changed_python_files = self._git_helper.GetChangedPythonFiles( | 1757 changed_python_files = self._git_helper.GetChangedPythonFiles( |
1698 diffbase=diffbase) | 1758 diffbase=diffbase) |
1699 | 1759 |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1805 u'', self._merge_description) | 1865 u'', self._merge_description) |
1806 | 1866 |
1807 merge_email_address = codereview_information.get(u'owner_email', None) | 1867 merge_email_address = codereview_information.get(u'owner_email', None) |
1808 if not merge_email_address: | 1868 if not merge_email_address: |
1809 print(( | 1869 print(( |
1810 u'{0:s} aborted - unable to determine email address of owner of ' | 1870 u'{0:s} aborted - unable to determine email address of owner of ' |
1811 u'code review: {1!s}.').format( | 1871 u'code review: {1!s}.').format( |
1812 self._command.title(), codereview_issue_number)) | 1872 self._command.title(), codereview_issue_number)) |
1813 return False | 1873 return False |
1814 | 1874 |
1815 self._fork_username, _, self._fork_feature_branch = ( | |
1816 self._github_origin.partition(u':')) | |
1817 | |
1818 github_user_information = self._github_helper.QueryUser( | 1875 github_user_information = self._github_helper.QueryUser( |
1819 self._fork_username) | 1876 self._fork_username) |
1820 if not github_user_information: | 1877 if not github_user_information: |
1821 print(( | 1878 print(( |
1822 u'{0:s} aborted - unable to retrieve github user: {1:s} ' | 1879 u'{0:s} aborted - unable to retrieve github user: {1:s} ' |
1823 u'information.').format( | 1880 u'information.').format( |
1824 self._command.title(), self._fork_username)) | 1881 self._command.title(), self._fork_username)) |
1825 return False | 1882 return False |
1826 | 1883 |
1827 merge_fullname = github_user_information.get(u'name', None) | 1884 merge_fullname = github_user_information.get(u'name', None) |
1828 if not merge_fullname: | 1885 if not merge_fullname: |
1829 merge_fullname = codereview_information.get(u'owner', None) | 1886 merge_fullname = codereview_information.get(u'owner', None) |
1830 if not merge_fullname: | 1887 if not merge_fullname: |
1831 merge_fullname = github_user_information.get(u'company', None) | 1888 merge_fullname = github_user_information.get(u'company', None) |
1832 if not merge_fullname: | 1889 if not merge_fullname: |
1833 print(( | 1890 print(( |
1834 u'{0:s} aborted - unable to determine full name.').format( | 1891 u'{0:s} aborted - unable to determine full name.').format( |
1835 self._command.title())) | 1892 self._command.title())) |
1836 return False | 1893 return False |
1837 | 1894 |
1838 self._merge_author = u'{0:s} <{1:s}>'.format( | 1895 self._merge_author = u'{0:s} <{1:s}>'.format( |
1839 merge_fullname, merge_email_address) | 1896 merge_fullname, merge_email_address) |
1840 | 1897 |
1841 return True | 1898 return True |
1842 | 1899 |
| 1900 def PullChangesFromFork(self): |
| 1901 """Pulls changes from a feature branch on a fork. |
| 1902 |
| 1903 Returns: |
| 1904 bool: True if the pull was successful. |
| 1905 """ |
| 1906 fork_git_repo_url = self._github_helper.GetForkGitRepoUrl( |
| 1907 self._fork_username) |
| 1908 |
| 1909 if not self._git_helper.PullFromFork( |
| 1910 fork_git_repo_url, self._fork_feature_branch): |
| 1911 print(u'{0:s} aborted - unable to pull changes from fork.'.format( |
| 1912 self._command.title())) |
| 1913 return False |
| 1914 |
| 1915 return True |
| 1916 |
1843 def Test(self): | 1917 def Test(self): |
1844 """Tests a review. | 1918 """Tests a review. |
1845 | 1919 |
1846 Returns: | 1920 Returns: |
1847 bool: True if the tests were successful. | 1921 bool: True if the tests were successful. |
1848 """ | 1922 """ |
1849 if self._project_name == u'l2tdocs': | 1923 if self._project_name == u'l2tdocs': |
1850 return True | 1924 return True |
1851 | 1925 |
1852 if self._command not in (u'create', u'merge', u'test', u'update'): | 1926 if self._command not in ( |
| 1927 u'create', u'lint-test', u'lint_test', u'merge', u'test', u'update'): |
1853 return True | 1928 return True |
1854 | 1929 |
1855 # TODO: determine why this alters the behavior of argparse. | 1930 # TODO: determine why this alters the behavior of argparse. |
1856 # Currently affects this script being used in plaso. | 1931 # Currently affects this script being used in plaso. |
1857 command = u'{0:s} run_tests.py --fail-unless-has-test-file'.format( | 1932 command = u'{0:s} run_tests.py'.format(sys.executable) |
1858 sys.executable) | |
1859 exit_code = subprocess.call(command, shell=True) | 1933 exit_code = subprocess.call(command, shell=True) |
1860 if exit_code != 0: | 1934 if exit_code != 0: |
1861 print(u'{0:s} aborted - unable to pass tests.'.format( | 1935 print(u'{0:s} aborted - unable to pass tests.'.format( |
1862 self._command.title())) | 1936 self._command.title())) |
1863 | 1937 |
1864 if self._command == u'merge': | 1938 if self._command == u'merge': |
1865 self._git_helper.DropUncommittedChanges() | 1939 self._git_helper.DropUncommittedChanges() |
1866 return False | 1940 return False |
1867 | 1941 |
1868 return True | 1942 return True |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2001 u'codereview_issue_number', action=u'store', | 2075 u'codereview_issue_number', action=u'store', |
2002 metavar=u'CODEREVIEW_ISSUE_NUMBER', default=None, | 2076 metavar=u'CODEREVIEW_ISSUE_NUMBER', default=None, |
2003 help=u'the codereview issue number to be merged.') | 2077 help=u'the codereview issue number to be merged.') |
2004 | 2078 |
2005 # TODO: add this to help output. | 2079 # TODO: add this to help output. |
2006 merge_command_parser.add_argument( | 2080 merge_command_parser.add_argument( |
2007 u'github_origin', action=u'store', | 2081 u'github_origin', action=u'store', |
2008 metavar=u'GITHUB_ORIGIN', default=None, | 2082 metavar=u'GITHUB_ORIGIN', default=None, |
2009 help=u'the github origin to merged e.g. username:feature.') | 2083 help=u'the github origin to merged e.g. username:feature.') |
2010 | 2084 |
| 2085 merge_edit_command_parser = commands_parser.add_parser(u'merge-edit') |
| 2086 |
| 2087 # TODO: add this to help output. |
| 2088 merge_edit_command_parser.add_argument( |
| 2089 u'github_origin', action=u'store', |
| 2090 metavar=u'GITHUB_ORIGIN', default=None, |
| 2091 help=u'the github origin to merged e.g. username:feature.') |
| 2092 |
| 2093 merge_edit_command_parser = commands_parser.add_parser(u'merge_edit') |
| 2094 |
| 2095 # TODO: add this to help output. |
| 2096 merge_edit_command_parser.add_argument( |
| 2097 u'github_origin', action=u'store', |
| 2098 metavar=u'GITHUB_ORIGIN', default=None, |
| 2099 help=u'the github origin to merged e.g. username:feature.') |
| 2100 |
2011 commands_parser.add_parser(u'lint') | 2101 commands_parser.add_parser(u'lint') |
2012 | 2102 |
| 2103 commands_parser.add_parser(u'lint-test') |
| 2104 commands_parser.add_parser(u'lint_test') |
| 2105 |
2013 open_command_parser = commands_parser.add_parser(u'open') | 2106 open_command_parser = commands_parser.add_parser(u'open') |
2014 | 2107 |
2015 # TODO: add this to help output. | 2108 # TODO: add this to help output. |
2016 open_command_parser.add_argument( | 2109 open_command_parser.add_argument( |
2017 u'codereview_issue_number', action=u'store', | 2110 u'codereview_issue_number', action=u'store', |
2018 metavar=u'CODEREVIEW_ISSUE_NUMBER', default=None, | 2111 metavar=u'CODEREVIEW_ISSUE_NUMBER', default=None, |
2019 help=u'the codereview issue number to be opened.') | 2112 help=u'the codereview issue number to be opened.') |
2020 | 2113 |
2021 # TODO: add this to help output. | 2114 # TODO: add this to help output. |
2022 open_command_parser.add_argument( | 2115 open_command_parser.add_argument( |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2055 if u':' in feature_branch: | 2148 if u':' in feature_branch: |
2056 _, _, feature_branch = feature_branch.rpartition(u':') | 2149 _, _, feature_branch = feature_branch.rpartition(u':') |
2057 | 2150 |
2058 if options.command in (u'merge', u'open'): | 2151 if options.command in (u'merge', u'open'): |
2059 codereview_issue_number = getattr( | 2152 codereview_issue_number = getattr( |
2060 options, u'codereview_issue_number', None) | 2153 options, u'codereview_issue_number', None) |
2061 if not codereview_issue_number: | 2154 if not codereview_issue_number: |
2062 print(u'Codereview issue number value is missing.') | 2155 print(u'Codereview issue number value is missing.') |
2063 print_help_on_error = True | 2156 print_help_on_error = True |
2064 | 2157 |
2065 if options.command == u'merge': | 2158 if options.command in (u'merge', u'merge-edit', u'merge_edit'): |
2066 github_origin = getattr(options, u'github_origin', None) | 2159 github_origin = getattr(options, u'github_origin', None) |
2067 if not github_origin: | 2160 if not github_origin: |
2068 print(u'Github origin value is missing.') | 2161 print(u'Github origin value is missing.') |
2069 print_help_on_error = True | 2162 print_help_on_error = True |
2070 | 2163 |
2071 if options.offline and options.command not in (u'lint', u'test'): | 2164 if options.offline and options.command not in ( |
| 2165 u'lint', u'lint-test', u'lint_test', u'test'): |
2072 print(u'Cannot run: {0:s} in offline mode.'.format(options.command)) | 2166 print(u'Cannot run: {0:s} in offline mode.'.format(options.command)) |
2073 print_help_on_error = True | 2167 print_help_on_error = True |
2074 | 2168 |
2075 if print_help_on_error: | 2169 if print_help_on_error: |
2076 print(u'') | 2170 print(u'') |
2077 argument_parser.print_help() | 2171 argument_parser.print_help() |
2078 print(u'') | 2172 print(u'') |
2079 return False | 2173 return False |
2080 | 2174 |
2081 home_path = os.path.expanduser(u'~') | 2175 home_path = os.path.expanduser(u'~') |
(...skipping 14 matching lines...) Expand all Loading... |
2096 if not review_helper.CheckLocalGitState(): | 2190 if not review_helper.CheckLocalGitState(): |
2097 return False | 2191 return False |
2098 | 2192 |
2099 if not options.offline and not review_helper.CheckRemoteGitState(): | 2193 if not options.offline and not review_helper.CheckRemoteGitState(): |
2100 return False | 2194 return False |
2101 | 2195 |
2102 if options.command == u'merge': | 2196 if options.command == u'merge': |
2103 if not review_helper.PrepareMerge(codereview_issue_number): | 2197 if not review_helper.PrepareMerge(codereview_issue_number): |
2104 return False | 2198 return False |
2105 | 2199 |
| 2200 if options.command in (u'merge', u'merge-edit', u'merge_edit'): |
| 2201 if not review_helper.PullChangesFromFork(): |
| 2202 return False |
| 2203 |
2106 if not review_helper.Lint(): | 2204 if not review_helper.Lint(): |
2107 return False | 2205 return False |
2108 | 2206 |
2109 if not review_helper.Test(): | 2207 if not review_helper.Test(): |
2110 return False | 2208 return False |
2111 | 2209 |
2112 result = False | 2210 result = False |
2113 if options.command == u'create': | 2211 if options.command == u'create': |
2114 result = review_helper.Create() | 2212 result = review_helper.Create() |
2115 | 2213 |
2116 elif options.command == u'close': | 2214 elif options.command == u'close': |
2117 result = review_helper.Close() | 2215 result = review_helper.Close() |
2118 | 2216 |
2119 elif options.command in (u'lint', u'test'): | 2217 elif options.command in (u'lint', u'lint-test', u'lint_test', u'test'): |
2120 result = True | 2218 result = True |
2121 | 2219 |
2122 elif options.command == u'merge': | 2220 elif options.command == u'merge': |
2123 result = review_helper.Merge(codereview_issue_number) | 2221 result = review_helper.Merge(codereview_issue_number) |
2124 | 2222 |
2125 elif options.command == u'open': | 2223 elif options.command == u'open': |
2126 result = review_helper.Open(codereview_issue_number) | 2224 result = review_helper.Open(codereview_issue_number) |
2127 | 2225 |
2128 elif options.command == u'update': | 2226 elif options.command == u'update': |
2129 result = review_helper.Update() | 2227 result = review_helper.Update() |
2130 | 2228 |
2131 elif options.command in (u'update-authors', u'update_authors'): | 2229 elif options.command in (u'update-authors', u'update_authors'): |
2132 result = review_helper.UpdateAuthors() | 2230 result = review_helper.UpdateAuthors() |
2133 | 2231 |
2134 elif options.command in (u'update-version', u'update_version'): | 2232 elif options.command in (u'update-version', u'update_version'): |
2135 result = review_helper.UpdateVersion() | 2233 result = review_helper.UpdateVersion() |
2136 | 2234 |
2137 return result | 2235 return result |
2138 | 2236 |
2139 | 2237 |
2140 if __name__ == u'__main__': | 2238 if __name__ == u'__main__': |
2141 if not Main(): | 2239 if not Main(): |
2142 sys.exit(1) | 2240 sys.exit(1) |
2143 else: | 2241 else: |
2144 sys.exit(0) | 2242 sys.exit(0) |
OLD | NEW |