OLD | NEW |
1 # Copyright 2008 Google Inc. | 1 # Copyright 2008 Google Inc. |
2 # | 2 # |
3 # Licensed under the Apache License, Version 2.0 (the "License"); | 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
4 # you may not use this file except in compliance with the License. | 4 # you may not use this file except in compliance with the License. |
5 # You may obtain a copy of the License at | 5 # You may obtain a copy of the License at |
6 # | 6 # |
7 # http://www.apache.org/licenses/LICENSE-2.0 | 7 # http://www.apache.org/licenses/LICENSE-2.0 |
8 # | 8 # |
9 # Unless required by applicable law or agreed to in writing, software | 9 # Unless required by applicable law or agreed to in writing, software |
10 # distributed under the License is distributed on an "AS IS" BASIS, | 10 # distributed under the License is distributed on an "AS IS" BASIS, |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
71 from oauth2client.appengine import _safe_html | 71 from oauth2client.appengine import _safe_html |
72 from oauth2client.appengine import CredentialsNDBModel | 72 from oauth2client.appengine import CredentialsNDBModel |
73 from oauth2client.appengine import StorageByKeyName | 73 from oauth2client.appengine import StorageByKeyName |
74 from oauth2client.appengine import xsrf_secret_key | 74 from oauth2client.appengine import xsrf_secret_key |
75 from oauth2client.client import AccessTokenRefreshError | 75 from oauth2client.client import AccessTokenRefreshError |
76 from oauth2client.client import OAuth2WebServerFlow | 76 from oauth2client.client import OAuth2WebServerFlow |
77 from oauth2client import xsrfutil | 77 from oauth2client import xsrfutil |
78 | 78 |
79 from codereview import auth_utils | 79 from codereview import auth_utils |
80 from codereview import buildbucket | 80 from codereview import buildbucket |
| 81 from codereview import dependency_utils |
81 from codereview import engine | 82 from codereview import engine |
82 from codereview import library | 83 from codereview import library |
83 from codereview import models | 84 from codereview import models |
84 from codereview import models_chromium | 85 from codereview import models_chromium |
85 from codereview import net | 86 from codereview import net |
86 from codereview import notify_xmpp | 87 from codereview import notify_xmpp |
87 from codereview import patching | 88 from codereview import patching |
88 from codereview import utils | 89 from codereview import utils |
89 from codereview import common | 90 from codereview import common |
90 from codereview.exceptions import FetchError | 91 from codereview.exceptions import FetchError |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
174 class UploadForm(forms.Form): | 175 class UploadForm(forms.Form): |
175 | 176 |
176 subject = forms.CharField(max_length=MAX_SUBJECT) | 177 subject = forms.CharField(max_length=MAX_SUBJECT) |
177 description = forms.CharField(max_length=MAX_DESCRIPTION, required=False) | 178 description = forms.CharField(max_length=MAX_DESCRIPTION, required=False) |
178 project = forms.CharField(required=False) | 179 project = forms.CharField(required=False) |
179 content_upload = forms.BooleanField(required=False) | 180 content_upload = forms.BooleanField(required=False) |
180 separate_patches = forms.BooleanField(required=False) | 181 separate_patches = forms.BooleanField(required=False) |
181 base = forms.CharField(max_length=MAX_URL, required=False) | 182 base = forms.CharField(max_length=MAX_URL, required=False) |
182 target_ref = forms.CharField(max_length=MAX_URL, required=False) | 183 target_ref = forms.CharField(max_length=MAX_URL, required=False) |
183 cq_dry_run = forms.BooleanField(required=False) | 184 cq_dry_run = forms.BooleanField(required=False) |
| 185 depends_on_patchset = forms.CharField(required=False) |
184 data = forms.FileField(required=False) | 186 data = forms.FileField(required=False) |
185 issue = forms.IntegerField(required=False) | 187 issue = forms.IntegerField(required=False) |
186 reviewers = forms.CharField(max_length=MAX_REVIEWERS, required=False) | 188 reviewers = forms.CharField(max_length=MAX_REVIEWERS, required=False) |
187 cc = forms.CharField(max_length=MAX_CC, required=False) | 189 cc = forms.CharField(max_length=MAX_CC, required=False) |
188 private = forms.BooleanField(required=False, initial=False) | 190 private = forms.BooleanField(required=False, initial=False) |
189 send_mail = forms.BooleanField(required=False) | 191 send_mail = forms.BooleanField(required=False) |
190 base_hashes = forms.CharField(required=False) | 192 base_hashes = forms.CharField(required=False) |
191 commit = forms.BooleanField(required=False) | 193 commit = forms.BooleanField(required=False) |
192 repo_guid = forms.CharField(required=False, max_length=MAX_URL) | 194 repo_guid = forms.CharField(required=False, max_length=MAX_URL) |
193 | 195 |
(...skipping 823 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1017 return HttpResponseBadRequest( | 1019 return HttpResponseBadRequest( |
1018 'Only the issue owner can edit patchset titles') | 1020 'Only the issue owner can edit patchset titles') |
1019 | 1021 |
1020 patchset = request.patchset | 1022 patchset = request.patchset |
1021 patchset.message = request.POST.get('patchset_title') | 1023 patchset.message = request.POST.get('patchset_title') |
1022 patchset.put() | 1024 patchset.put() |
1023 | 1025 |
1024 return HttpResponse('OK', content_type='text/plain') | 1026 return HttpResponse('OK', content_type='text/plain') |
1025 | 1027 |
1026 | 1028 |
| 1029 @deco.access_control_allow_origin_star |
| 1030 @deco.require_methods('POST') |
| 1031 @deco.patchset_required |
| 1032 @deco.json_response |
| 1033 def get_depends_on_patchset(request): |
| 1034 """/<issue>/get_depends_on_patchset- The patchset this patchset depends on.""" |
| 1035 response = {} |
| 1036 if request.patchset.depends_on_patchset: |
| 1037 # Verify that the depended upon issue is not closed. |
| 1038 tokens = dependency_utils.get_dependency_tokens( |
| 1039 request.patchset.depends_on_patchset) |
| 1040 depends_on_issue = models.Issue.get_by_id(int(tokens[0])) |
| 1041 if depends_on_issue and not depends_on_issue.closed: |
| 1042 response = { |
| 1043 'issue': tokens[0], |
| 1044 'patchset': tokens[1], |
| 1045 } |
| 1046 return response |
| 1047 |
| 1048 |
1027 @deco.admin_required | 1049 @deco.admin_required |
1028 @deco.user_key_required | 1050 @deco.user_key_required |
1029 @deco.xsrf_required | 1051 @deco.xsrf_required |
1030 def block_user(request): | 1052 def block_user(request): |
1031 """/user/<user>/block - Blocks a specific user.""" | 1053 """/user/<user>/block - Blocks a specific user.""" |
1032 account = models.Account.get_account_for_user(request.user_to_show) | 1054 account = models.Account.get_account_for_user(request.user_to_show) |
1033 if request.method == 'POST': | 1055 if request.method == 'POST': |
1034 form = BlockForm(request.POST) | 1056 form = BlockForm(request.POST) |
1035 if form.is_valid(): | 1057 if form.is_valid(): |
1036 account.blocked = form.cleaned_data['blocked'] | 1058 account.blocked = form.cleaned_data['blocked'] |
(...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1430 issue.put() | 1452 issue.put() |
1431 | 1453 |
1432 if cq_dry_run: | 1454 if cq_dry_run: |
1433 commit_checked_msg = 'The CQ bit was checked by %s to run a CQ dry run' % ( | 1455 commit_checked_msg = 'The CQ bit was checked by %s to run a CQ dry run' % ( |
1434 cq_dry_run_triggered_by) | 1456 cq_dry_run_triggered_by) |
1435 make_message(request, issue, commit_checked_msg, send_mail=False, | 1457 make_message(request, issue, commit_checked_msg, send_mail=False, |
1436 auto_generated=True).put() | 1458 auto_generated=True).put() |
1437 | 1459 |
1438 first_ps_id, _ = models.PatchSet.allocate_ids(1, parent=issue.key) | 1460 first_ps_id, _ = models.PatchSet.allocate_ids(1, parent=issue.key) |
1439 ps_key = ndb.Key(models.PatchSet, first_ps_id, parent=issue.key) | 1461 ps_key = ndb.Key(models.PatchSet, first_ps_id, parent=issue.key) |
| 1462 depends_on_patchset = ( |
| 1463 dependency_utils.mark_as_dependent_and_get_dependency_str( |
| 1464 form.cleaned_data.get('depends_on_patchset'), issue.key.id(), |
| 1465 ps_key.id())) |
1440 patchset = models.PatchSet( | 1466 patchset = models.PatchSet( |
1441 issue_key=issue.key, data=data, url=url, key=ps_key) | 1467 issue_key=issue.key, data=data, url=url, key=ps_key, |
| 1468 depends_on_patchset=depends_on_patchset) |
1442 patchset.put() | 1469 patchset.put() |
1443 | 1470 |
1444 if not separate_patches: | 1471 if not separate_patches: |
1445 try: | 1472 try: |
1446 patches = engine.ParsePatchSet(patchset) | 1473 patches = engine.ParsePatchSet(patchset) |
1447 except Exception: | 1474 except Exception: |
1448 # catch all exceptions happening in engine.ParsePatchSet, | 1475 # catch all exceptions happening in engine.ParsePatchSet, |
1449 # engine_utils.SplitPatch. With malformed diffs a variety of exceptions | 1476 # engine_utils.SplitPatch. With malformed diffs a variety of exceptions |
1450 # could happen there. | 1477 # could happen there. |
1451 logging.exception('Exception during patch parsing') | 1478 logging.exception('Exception during patch parsing') |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1525 account = models.Account.get_account_for_user(request.user) | 1552 account = models.Account.get_account_for_user(request.user) |
1526 if account.blocked: | 1553 if account.blocked: |
1527 return None | 1554 return None |
1528 if not issue.edit_allowed: | 1555 if not issue.edit_allowed: |
1529 # This check is done at each call site but check again as a safety measure. | 1556 # This check is done at each call site but check again as a safety measure. |
1530 return None | 1557 return None |
1531 data, url, separate_patches = data_url | 1558 data, url, separate_patches = data_url |
1532 message = form.cleaned_data[message_key] | 1559 message = form.cleaned_data[message_key] |
1533 first_id, _ = models.PatchSet.allocate_ids(1, parent=issue.key) | 1560 first_id, _ = models.PatchSet.allocate_ids(1, parent=issue.key) |
1534 ps_key = ndb.Key(models.PatchSet, first_id, parent=issue.key) | 1561 ps_key = ndb.Key(models.PatchSet, first_id, parent=issue.key) |
| 1562 |
| 1563 depends_on_patchset = ( |
| 1564 dependency_utils.mark_as_dependent_and_get_dependency_str( |
| 1565 form.cleaned_data.get('depends_on_patchset'), issue.key.id(), |
| 1566 ps_key.id())) |
1535 patchset = models.PatchSet( | 1567 patchset = models.PatchSet( |
1536 issue_key=issue.key, message=message, data=data, url=url, key=ps_key) | 1568 issue_key=issue.key, message=message, data=data, url=url, key=ps_key, |
| 1569 depends_on_patchset=depends_on_patchset) |
1537 patchset.put() | 1570 patchset.put() |
1538 | 1571 |
1539 if not separate_patches: | 1572 if not separate_patches: |
1540 try: | 1573 try: |
1541 patches = engine.ParsePatchSet(patchset) | 1574 patches = engine.ParsePatchSet(patchset) |
1542 except Exception: | 1575 except Exception: |
1543 logging.exception('Exception during patchset parsing') | 1576 logging.exception('Exception during patchset parsing') |
1544 patches = [] | 1577 patches = [] |
1545 if not patches: | 1578 if not patches: |
1546 patchset.key.delete() | 1579 patchset.key.delete() |
(...skipping 503 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2050 logging.info("Updating %d patches", len(patches)) | 2083 logging.info("Updating %d patches", len(patches)) |
2051 ndb.put_multi(patches) | 2084 ndb.put_multi(patches) |
2052 | 2085 |
2053 | 2086 |
2054 @deco.require_methods('POST') | 2087 @deco.require_methods('POST') |
2055 @deco.issue_editor_required | 2088 @deco.issue_editor_required |
2056 @deco.xsrf_required | 2089 @deco.xsrf_required |
2057 def delete(request): | 2090 def delete(request): |
2058 """/<issue>/delete - Delete an issue. There is no way back.""" | 2091 """/<issue>/delete - Delete an issue. There is no way back.""" |
2059 issue = request.issue | 2092 issue = request.issue |
| 2093 # Update the dependents to remove dependency on this issue. |
| 2094 dependency_utils.remove_dependencies_from_all_patchsets(issue) |
| 2095 # Remove this issue as a dependent. |
| 2096 dependency_utils.remove_all_patchsets_as_dependents(issue) |
| 2097 |
2060 tbd = [issue] | 2098 tbd = [issue] |
2061 for cls in [models.PatchSet, models.Patch, models.Comment, | 2099 for cls in [models.PatchSet, models.Patch, models.Comment, |
2062 models.Message, models.Content, models.TryJobResult]: | 2100 models.Message, models.Content, models.TryJobResult]: |
2063 tbd += cls.query(ancestor=issue.key) | 2101 tbd += cls.query(ancestor=issue.key) |
2064 ndb.delete_multi(entity.key for entity in tbd) | 2102 ndb.delete_multi(entity.key for entity in tbd) |
2065 return HttpResponseRedirect(reverse(mine)) | 2103 return HttpResponseRedirect(reverse(mine)) |
2066 | 2104 |
2067 | 2105 |
2068 @deco.require_methods('POST') | 2106 @deco.require_methods('POST') |
2069 @deco.patchset_editor_required | 2107 @deco.patchset_editor_required |
2070 @deco.xsrf_required | 2108 @deco.xsrf_required |
2071 def delete_patchset(request): | 2109 def delete_patchset(request): |
2072 """/<issue>/patch/<patchset>/delete - Delete a patchset. | 2110 """/<issue>/patch/<patchset>/delete - Delete a patchset. |
2073 | 2111 |
2074 There is no way back. | 2112 There is no way back. |
2075 """ | 2113 """ |
2076 # Log patchset deletion. | 2114 # Log patchset deletion. |
2077 patchset_num = 0 | 2115 patchset_num = 0 |
2078 for patchset in list(request.issue.patchsets): | 2116 for patchset in list(request.issue.patchsets): |
2079 patchset_num += 1 | 2117 patchset_num += 1 |
2080 if patchset.key.id() == request.patchset.key.id(): | 2118 if patchset.key.id() == request.patchset.key.id(): |
2081 break | 2119 break |
2082 delete_msg = 'Patchset #%s (id:%s) has been deleted' % ( | 2120 delete_msg = 'Patchset #%s (id:%s) has been deleted' % ( |
2083 patchset_num, request.patchset.key.id()) | 2121 patchset_num, request.patchset.key.id()) |
2084 make_message(request, request.issue, delete_msg, send_mail=False, | 2122 make_message(request, request.issue, delete_msg, send_mail=False, |
2085 auto_generated=True).put() | 2123 auto_generated=True).put() |
| 2124 # Update all dependents of this patchset. |
| 2125 dependency_utils.remove_dependencies(request.patchset) |
| 2126 # Remove this patchset as a dependent. |
| 2127 dependency_utils.remove_as_dependent(request.patchset) |
2086 # Delete the patchset. | 2128 # Delete the patchset. |
2087 request.patchset.nuke() | 2129 request.patchset.nuke() |
2088 return HttpResponseRedirect(reverse(show, args=[request.issue.key.id()])) | 2130 return HttpResponseRedirect(reverse(show, args=[request.issue.key.id()])) |
2089 | 2131 |
2090 | 2132 |
2091 @deco.require_methods('POST') | 2133 @deco.require_methods('POST') |
2092 @deco.issue_editor_required | 2134 @deco.issue_editor_required |
2093 @deco.xsrf_required | 2135 @deco.xsrf_required |
2094 def close(request): | 2136 def close(request): |
2095 """/<issue>/close - Close an issue.""" | 2137 """/<issue>/close - Close an issue.""" |
(...skipping 267 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2363 values = { | 2405 values = { |
2364 'patchset': patchset.key.id(), | 2406 'patchset': patchset.key.id(), |
2365 'issue': issue.key.id(), | 2407 'issue': issue.key.id(), |
2366 'owner': library.get_nickname(issue.owner, True, request), | 2408 'owner': library.get_nickname(issue.owner, True, request), |
2367 'owner_email': issue.owner.email(), | 2409 'owner_email': issue.owner.email(), |
2368 'message': patchset.message, | 2410 'message': patchset.message, |
2369 'url': patchset.url, | 2411 'url': patchset.url, |
2370 'created': str(patchset.created), | 2412 'created': str(patchset.created), |
2371 'modified': str(patchset.modified), | 2413 'modified': str(patchset.modified), |
2372 'num_comments': patchset.num_comments, | 2414 'num_comments': patchset.num_comments, |
| 2415 'depends_on_patchset': patchset.depends_on_patchset, |
| 2416 'dependent_patchsets': patchset.dependent_patchsets, |
2373 'files': {}, | 2417 'files': {}, |
2374 } | 2418 } |
2375 if (try_jobs): | 2419 if (try_jobs): |
2376 values['try_job_results'] = [ | 2420 values['try_job_results'] = [ |
2377 t.to_dict() for t in patchset.try_job_results] | 2421 t.to_dict() for t in patchset.try_job_results] |
2378 | 2422 |
2379 all_no_base_file_keys_future = models.Content.query( | 2423 all_no_base_file_keys_future = models.Content.query( |
2380 models.Content.file_too_large == True, | 2424 models.Content.file_too_large == True, |
2381 ancestor=patchset.key).fetch_async(10000, keys_only=True, batch_size=1000) | 2425 ancestor=patchset.key).fetch_async(10000, keys_only=True, batch_size=1000) |
2382 patches_future = models.Patch.query(ancestor=patchset.key).fetch_async( | 2426 patches_future = models.Patch.query(ancestor=patchset.key).fetch_async( |
(...skipping 3005 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5388 return HttpResponseNotFound() | 5432 return HttpResponseNotFound() |
5389 tops = [] | 5433 tops = [] |
5390 shame = [] | 5434 shame = [] |
5391 for i in data: | 5435 for i in data: |
5392 if i.score == models.AccountStatsBase.NULL_SCORE: | 5436 if i.score == models.AccountStatsBase.NULL_SCORE: |
5393 shame.append(i) | 5437 shame.append(i) |
5394 else: | 5438 else: |
5395 tops.append(i) | 5439 tops.append(i) |
5396 return respond( | 5440 return respond( |
5397 request, 'leaderboard.html', {'tops': tops, 'shame': shame, 'when': when}) | 5441 request, 'leaderboard.html', {'tops': tops, 'shame': shame, 'when': when}) |
OLD | NEW |