Fix several issues with draft diffs
Updated 10 months ago
| Christian Hammond | Reviewers | ||
| trunk | reviewboard | ||
| 323 | |||
| None | Review Board SVN | ||
We had several issues with draft diffs. 1) They were automatically placed in the diffset history of a review request, making them publicly accessible. This meant they were not destroyed when destroying a draft. I'm unsure as to how long this has been broken. 2) The queries for grabbing a draft diff didn't take into account the user accessing the review request. 3) The diff viewer didn't really let you know you were looking at a draft diff, and didn't work when #1 was fixed. We now keep diffs out of a review request's diffset history when creating them, and we assign them an appropriate revision number. We only check for the draft diffs if the requesting user is logged in and owns the review request. We also modify the diff viewer a bit to tell the user when they're looking at a draft diff. This fixes bug 323, and should also fix bugs regarding revision numbers being skipped and default reviewers not being set (since that check queried if any diffsets existed for the review request, which they did due to #1 above).
All my manual tests showed that the correct things were being seen for a logged in user accessing a draft diff, and that other users and non-logged in users couldn't see it. Verified that interdiffs work with the draft diffs and that discarding a draft deletes the diffset. All unit tests pass (with a tweak to fix broken behavior).
| /trunk/reviewboard/reviews/views.py | |||
|---|---|---|---|
| Revision 1203 | New Change | ||
| ... | 201 lines hidden [Expand] | ||
| 202 | _("%s's review requests") % username) |
202 | _("%s's review requests") % username) |
| 203 | 203 | ||
| 204 | return datagrid.render_to_response(template_name) |
204 | return datagrid.render_to_response(template_name) |
| 205 | 205 | ||
| 206 | 206 | ||
| 207 | def _query_for_diff(review_request, revision, query_extra=None): |
207 | def _query_for_diff(review_request, user, revision, query_extra=None): |
| 208 | """ |
208 | """ |
| 209 | Queries for a diff based on several parameters. |
209 | Queries for a diff based on several parameters. |
| 210 | 210 | ||
| 211 | If the draft does not exist, this throws an Http404 exception. |
211 | If the draft does not exist, this throws an Http404 exception. |
| 212 | """ |
212 | """ |
| 213 | 213 | ||
| 214 | # Normalize the revision, since it might come in as a string. |
||
| 215 | if revision != None: |
||
| 216 | revision = int(revision) |
||
| 217 | |||
| 218 | if user.is_authenticated() and review_request.submitter == user: |
||
| 214 | # This will try to grab the diff associated with a draft if the review |
219 | # This will try to grab the diff associated with a draft if the review |
| 215 | # request has an associated draft and is either the revision being |
220 | # request has an associated draft and is either the revision being |
| 216 | # requested or no revision is being requested. |
221 | # requested or no revision is being requested. |
| 217 | draft = get_object_or_none(review_request.reviewrequestdraft_set) |
222 | draft = get_object_or_none(review_request.reviewrequestdraft_set) |
| 218 | if draft and draft.diffset and \ |
223 | if draft and draft.diffset and \ |
| ... | 23 lines hidden [Expand] | ||
| 242 | A wrapper around diffviewer.views.view_diff that handles querying for |
247 | A wrapper around diffviewer.views.view_diff that handles querying for |
| 243 | diffs owned by a review request,taking into account interdiffs and |
248 | diffs owned by a review request,taking into account interdiffs and |
| 244 | providing the user's current review of the diff if it exists. |
249 | providing the user's current review of the diff if it exists. |
| 245 | """ |
250 | """ |
| 246 | review_request = get_object_or_404(ReviewRequest, pk=review_request_id) |
251 | review_request = get_object_or_404(ReviewRequest, pk=review_request_id) |
| 247 | diffset = _query_for_diff(review_request, revision) |
252 | diffset = _query_for_diff(review_request, request.user, revision) |
| 248 | 253 | ||
| 254 | interdiffset = None |
||
| 249 | interdiffset_id = None |
255 | interdiffset_id = None |
| 250 | review = None |
256 | review = None |
| 257 | draft = None |
||
| 251 | 258 | ||
| 252 | if interdiff_revision and interdiff_revision != revision: |
259 | if interdiff_revision and interdiff_revision != revision: |
| 253 | # An interdiff revision was specified. Try to find a matching |
260 | # An interdiff revision was specified. Try to find a matching |
| 254 | # diffset. |
261 | # diffset. |
| 255 | interdiffset = _query_for_diff(review_request, interdiff_revision) |
262 | interdiffset = _query_for_diff(review_request, request.user, |
| 263 | interdiff_revision) |
||
| 256 | interdiffset_id = interdiffset.id |
264 | interdiffset_id = interdiffset.id |
| 257 | 265 | ||
| 258 | if request.user.is_authenticated(): |
266 | if request.user.is_authenticated(): |
| 259 | # Try to find an existing pending review of this diff from the |
267 | # Try to find an existing pending review of this diff from the |
| 260 | # current user. |
268 | # current user. |
| 261 | review = get_object_or_none(Review, |
269 | review = get_object_or_none(Review, |
| 262 | user=request.user, |
270 | user=request.user, |
| 263 | review_request=review_request, |
271 | review_request=review_request, |
| 264 | public=False, |
272 | public=False, |
| 265 | base_reply_to__isnull=True) |
273 | base_reply_to__isnull=True) |
| 274 | draft = get_object_or_none(review_request.reviewrequestdraft_set, |
||
| 275 | review_request__submitter=request.user) |
||
| 266 | 276 | ||
| 267 | draft = get_object_or_none(review_request.reviewrequestdraft_set) |
||
| 268 | repository = review_request.repository |
277 | repository = review_request.repository |
| 269 | 278 | ||
| 279 | has_draft_diff = draft and draft.diffset |
||
| 280 | is_draft_diff = has_draft_diff and draft.diffset == diffset |
||
| 281 | is_draft_interdiff = has_draft_diff and interdiffset and \ |
||
| 282 | draft.diffset == interdiffset |
||
| 283 | |||
| 270 | return view_diff(request, diffset.id, interdiffset_id, { |
284 | return view_diff(request, diffset.id, interdiffset_id, { |
| 271 | 'review': review, |
285 | 'review': review, |
| 272 | 'review_request': review_request, |
286 | 'review_request': review_request, |
| 273 | 'review_request_details': draft or review_request, |
287 | 'review_request_details': draft or review_request, |
| 274 | 'draft': draft, |
288 | 'draft': draft, |
| 289 | 'is_draft_diff': is_draft_diff, |
||
| 290 | 'is_draft_interdiff': is_draft_interdiff, |
||
| 275 | 'upload_diff_form': UploadDiffForm(repository), |
291 | 'upload_diff_form': UploadDiffForm(repository), |
| 276 | 'upload_screenshot_form': UploadScreenshotForm(), |
292 | 'upload_screenshot_form': UploadScreenshotForm(), |
| 277 | 'scmtool': repository.get_scmtool(), |
293 | 'scmtool': repository.get_scmtool(), |
| 278 | }, template_name) |
294 | }, template_name) |
| 279 | 295 | ||
| 280 | 296 | ||
| 281 | @check_login_required |
297 | @check_login_required |
| 282 | def raw_diff(request, review_request_id, revision=None): |
298 | def raw_diff(request, review_request_id, revision=None): |
| 283 | """ |
299 | """ |
| 284 | Displays a raw diff of all the filediffs in a diffset for the |
300 | Displays a raw diff of all the filediffs in a diffset for the |
| 285 | given review request. |
301 | given review request. |
| 286 | """ |
302 | """ |
| 287 | review_request = get_object_or_404(ReviewRequest, pk=review_request_id) |
303 | review_request = get_object_or_404(ReviewRequest, pk=review_request_id) |
| 288 | diffset = _query_for_diff(review_request, revision) |
304 | diffset = _query_for_diff(review_request, request.user, revision) |
| 289 | 305 | ||
| 290 | data = ''.join([filediff.diff for filediff in diffset.files.all()]) |
306 | data = ''.join([filediff.diff for filediff in diffset.files.all()]) |
| 291 | 307 | ||
| 292 | resp = HttpResponse(data, mimetype='text/x-patch') |
308 | resp = HttpResponse(data, mimetype='text/x-patch') |
| 293 | resp['Content-Disposition'] = 'inline; filename=%s' % diffset.name |
309 | resp['Content-Disposition'] = 'inline; filename=%s' % diffset.name |
| ... | 20 lines hidden [Expand] | ||
| 314 | query_extra = Q(reviewrequestdraft=draft) |
330 | query_extra = Q(reviewrequestdraft=draft) |
| 315 | except ReviewRequestDraft.DoesNotExist: |
331 | except ReviewRequestDraft.DoesNotExist: |
| 316 | query_extra = None |
332 | query_extra = None |
| 317 | 333 | ||
| 318 | if interdiff_revision is not None: |
334 | if interdiff_revision is not None: |
| 319 | interdiffset = _query_for_diff(review_request, interdiff_revision, |
335 | interdiffset = _query_for_diff(review_request, request.user, |
| 320 | query_extra) |
336 | interdiff_revision, query_extra) |
| 321 | interdiffset_id = interdiffset.id |
337 | interdiffset_id = interdiffset.id |
| 322 | # Only the interdiff should have an extra query for the draft. |
338 | # Only the interdiff should have an extra query for the draft. |
| 323 | # It's going to be the most recent diff (generally). We should be |
339 | # It's going to be the most recent diff (generally). We should be |
| 324 | # smarter and check. |
340 | # smarter and check. |
| 325 | query_extra = None |
341 | query_extra = None |
| 326 | else: |
342 | else: |
| 327 | interdiffset_id = None |
343 | interdiffset_id = None |
| 328 | 344 | ||
| 329 | diffset = _query_for_diff(review_request, revision, query_extra) |
345 | diffset = _query_for_diff(review_request, request.user, revision, |
| 346 | query_extra) |
||
| 330 | 347 | ||
| 331 | return view_diff_fragment(request, diffset.id, filediff_id, |
348 | return view_diff_fragment(request, diffset.id, filediff_id, |
| 332 | interdiffset_id, chunkindex, collapseall, |
349 | interdiffset_id, chunkindex, collapseall, |
| 333 | template_name) |
350 | template_name) |
| 334 | 351 | ||
| ... | 251 lines hidden [Expand] | ||
| /trunk/reviewboard/templates/diffviewer/view_diff.html | |||
|---|---|---|---|
| Revision 1203 | New Change | ||
| ... | 58 lines hidden [Expand] | ||
| 59 | <a name="index_header"></a> |
59 | <a name="index_header"></a> |
| 60 | {% with review_request.diffset_history.diffset_set.latest.revision as latest_revision %} |
60 | {% with review_request.diffset_history.diffset_set.latest.revision as latest_revision %} |
| 61 | {% with diffset.revision as revision %} |
61 | {% with diffset.revision as revision %} |
| 62 | <div class="main"> |
62 | <div class="main"> |
| 63 | {% if interdiffset %} |
63 | {% if interdiffset %} |
| 64 | {# We're showing an interdiff, so indicate the revision range. #} |
||
| 64 | <h1>{% blocktrans with interdiffset.revision as interdiff_revision %}Changes between revision {{revision}} and {{interdiff_revision}}{% endblocktrans %}</h1> |
65 | <h1>{% blocktrans with interdiffset.revision as interdiff_revision %}Changes between revision {{revision}} and {{interdiff_revision}}{% endblocktrans %}</h1> |
| 65 | {% else %} |
66 | {% else %} |
| 66 | {% ifequal diffset.revision latest_revision %} |
67 | {% ifequal diffset.revision latest_revision %} |
| 67 | <h1>{% blocktrans %}Diff revision {{revision}} (Latest){% endblocktrans %}</h1> |
68 | <h1>{% blocktrans %}Diff revision {{revision}} (Latest){% endblocktrans %}</h1> |
| 68 | {% else %} |
69 | {% else %} |
| 70 | {# This is not the most recent diff. See if we're showing a draft. #} |
||
| 71 | {% if is_draft_diff %} |
||
| 72 | <h1>{% trans "Draft diff" %}</h1> |
||
| 73 | <p> |
||
| 74 | {% blocktrans %} |
||
| 75 | This diff is part of your current draft. Other users will not see this |
||
| 76 | diff until you publish your draft. |
||
| 77 | {% endblocktrans %} |
||
| 78 | {% else %} |
||
| 79 | {# This is not a draft. Tell the user this is not the most recent. #} |
||
| 69 | <h1>{% blocktrans %}Diff revision {{revision}}{% endblocktrans %}</h1> |
80 | <h1>{% blocktrans %}Diff revision {{revision}}{% endblocktrans %}</h1> |
| 70 | <p> |
81 | <p> |
| 71 | {% blocktrans with diffset.revision as revision and review_request.get_absolute_url as review_request_url%} |
82 | {% blocktrans with diffset.revision as revision and review_request.get_absolute_url as review_request_url%} |
| 72 | This is not the most recent revision of the diff. The |
83 | This is not the most recent revision of the diff. The |
| 73 | <a href="{{review_request_url}}diff/">latest diff</a> is revision |
84 | <a href="{{review_request_url}}diff/">latest diff</a> is revision |
| 74 | {{latest_revision}}. |
85 | {{latest_revision}}. |
| 75 | <a href="{{review_request_url}}diff/{{revision}}-{{latest_revision}}/">See what's changed.</a> |
86 | <a href="{{review_request_url}}diff/{{revision}}-{{latest_revision}}/">See what's changed.</a> |
| 76 | {% endblocktrans %} |
87 | {% endblocktrans %} |
| 88 | {% endif %} |
||
| 77 | </p> |
89 | </p> |
| 78 | {% endifequal %} |
90 | {% endifequal %} |
| 79 | {% endif %} |
91 | {% endif %} |
| 92 | |||
| 93 | {# Notify the user if they have unpublished comments in other diffs. #} |
||
| 80 | {% if review|has_comments_in_diffsets_excluding:diffset_pair %} |
94 | {% if review|has_comments_in_diffsets_excluding:diffset_pair %} |
| 81 | {% box "important" %} |
95 | {% box "important" %} |
| 82 | <h1>{% trans "You have unpublished comments on other revisions" %}</h1> |
96 | <h1>{% trans "You have unpublished comments on other revisions" %}</h1> |
| 83 | <p> |
97 | <p> |
| 84 | {% trans "Your review consists of comments on the following revisions:" %} |
98 | {% trans "Your review consists of comments on the following revisions:" %} |
| ... | 16 lines hidden [Expand] | ||
| 101 | </ul> |
115 | </ul> |
| 102 | {% endbox %} |
116 | {% endbox %} |
| 103 | {% endif %} |
117 | {% endif %} |
| 104 | 118 | ||
| 105 | {% ifnotequal review_request.diffset_history.diffset_set.count 1 %} |
119 | {% ifnotequal review_request.diffset_history.diffset_set.count 1 %} |
| 120 | {# Show a revision selector for jumping between revisions. #} |
||
| 106 | <table class="revision-selector"> |
121 | <table class="revision-selector"> |
| 107 | <tr> |
122 | <tr> |
| 108 | <th><label for="jump_to_revision">Jump to revision:<label></th> |
123 | <th><label for="jump_to_revision">Jump to revision:<label></th> |
| 109 | <td> |
124 | <td> |
| 110 | <div id="jump_to_revision" class="revisions"> |
125 | <div id="jump_to_revision" class="revisions"> |
| 111 | {% for item in review_request.diffset_history|revision_link_list:diffset_pair %} |
126 | {% for item in review_request.diffset_history|revision_link_list:diffset_pair %} |
| 112 | {% if item.is_current %} |
127 | {% if item.is_current %} |
| 113 | <span class="current">{{item.revision}}</span> |
128 | <span class="current">{{item.revision}}</span> |
| 114 | {% else %} |
129 | {% else %} |
| 115 | <a href="{{review_request.get_absolute_url}}diff/{{item.revision}}/#index_header">{{item.revision}}</a> |
130 | <a href="{{review_request.get_absolute_url}}diff/{{item.revision}}/#index_header">{{item.revision}}</a> |
| 116 | {% endif %} |
131 | {% endif %} |
| 117 | {% endfor %} |
132 | {% endfor %} |
| 133 | {% if draft and draft.diffset %} |
||
| 134 | {# There's a draft diff, so display it in the list. #} |
||
| 135 | {% if is_draft_diff %} |
||
| 136 | <span class="current">{{draft.diffset.revision}}</span> |
||
| 137 | {% else %} |
||
| 138 | <a href="{{review_request.get_absolute_url}}diff/{{draft.diffset.revision}}/#index_header">{{draft.diffset.revision}}</a> |
||
| 139 | {% endif %} |
||
| 140 | {% endif %} |
||
| 118 | </div> |
141 | </div> |
| 119 | </td> |
142 | </td> |
| 120 | </tr> |
143 | </tr> |
| 121 | <tr> |
144 | <tr> |
| 122 | <th><label for="show_interdiff">Changes between r{{revision}} and:</label></th> |
145 | <th><label for="show_interdiff">Changes between r{{revision}} and:</label></th> |
| ... | 4 lines hidden [Expand] | ||
| 127 | <span class="current">{{item.revision}}</span> |
150 | <span class="current">{{item.revision}}</span> |
| 128 | {% else %} |
151 | {% else %} |
| 129 | <a href="{{review_request.get_absolute_url}}diff/{{item.path}}/#index_header">{{item.revision}}</a> |
152 | <a href="{{review_request.get_absolute_url}}diff/{{item.path}}/#index_header">{{item.revision}}</a> |
| 130 | {% endif %} |
153 | {% endif %} |
| 131 | {% endfor %} |
154 | {% endfor %} |
| 155 | {% if draft and draft.diffset %} |
||
| 156 | {# There's a draft diff, so display it in the list. #} |
||
| 157 | {% if is_draft_diff or is_draft_interdiff %} |
||
| 158 | <span class="current">{{draft.diffset.revision}}</span> |
||
| 159 | {% else %} |
||
| 160 | <a href="{{review_request.get_absolute_url}}diff/{{diffset_pair.0}}-{{diffset_pair.1}}/#index_header">{{draft.diffset.revision}}</a> |
||
| 161 | {% endif %} |
||
| 162 | {% endif %} |
||
| 132 | </div> |
163 | </div> |
| 133 | </td> |
164 | </td> |
| 134 | </tr> |
165 | </tr> |
| 135 | </table> |
166 | </table> |
| 136 | {% endifnotequal %} |
167 | {% endifnotequal %} |
| ... | 40 lines hidden [Expand] | ||
| /trunk/reviewboard/webapi/json.py | |||
|---|---|---|---|
| Revision 1208 | New Change | ||
| ... | 961 lines hidden [Expand] | ||
| 962 | 962 | ||
| 963 | if not form.is_valid(): |
963 | if not form.is_valid(): |
| 964 | return WebAPIResponseFormError(request, form) |
964 | return WebAPIResponseFormError(request, form) |
| 965 | 965 | ||
| 966 | try: |
966 | try: |
| 967 | diffset = form.create(request.FILES['path'], |
967 | diffset = form.create(request.FILES['path']) |
| 968 | review_request.diffset_history) |
968 | |
| 969 | # Set the initial revision to be one newer than the most recent |
||
| 970 | # public revision, so we can reference it in the diff viewer. |
||
| 971 | # |
||
| 972 | # It would be nice to later consolidate this with the logic in |
||
| 973 | # DiffSet.save. |
||
| 974 | public_diffsets = review_request.diffset_history.diffset_set |
||
| 975 | |||
| 976 | if public_diffsets.count() > 0: |
||
| 977 | diffset.revision = public_diffsets.latest().revision + 1 |
||
| 978 | diffset.save() |
||
| 979 | else: |
||
| 980 | diffset.revision = 1 |
||
| 969 | except scmtools.FileNotFoundError, e: |
981 | except scmtools.FileNotFoundError, e: |
| 970 | return WebAPIResponseError(request, REPO_FILE_NOT_FOUND, { |
982 | return WebAPIResponseError(request, REPO_FILE_NOT_FOUND, { |
| 971 | 'file': e.path, |
983 | 'file': e.path, |
| 972 | 'revision': e.revision |
984 | 'revision': e.revision |
| 973 | }) |
985 | }) |
| ... | 222 lines hidden [Expand] | ||
| /trunk/reviewboard/webapi/tests.py | |||
|---|---|---|---|
| Revision 1203 | New Change | ||
| ... | 574 lines hidden [Expand] | ||
| 575 | x, y, w, h = 2, 2, 10, 10 |
575 | x, y, w, h = 2, 2, 10, 10 |
| 576 | 576 | ||
| 577 | screenshot = self.testNewScreenshot() |
577 | screenshot = self.testNewScreenshot() |
| 578 | review_request = screenshot.review_request.get() |
578 | review_request = screenshot.review_request.get() |
| 579 | diffset = self.testNewDiff(review_request) |
579 | diffset = self.testNewDiff(review_request) |
| 580 | rsp = self.apiPost("reviewrequests/%s/draft/save" % review_request.id) |
||
| 581 | self.assertEqual(rsp['stat'], 'ok') |
||
| 580 | 582 | ||
| 581 | self.postNewDiffComment(review_request, diff_comment_text) |
583 | self.postNewDiffComment(review_request, diff_comment_text) |
| 582 | self.postNewScreenshotComment(review_request, screenshot, |
584 | self.postNewScreenshotComment(review_request, screenshot, |
| 583 | screenshot_comment_text, x, y, w, h) |
585 | screenshot_comment_text, x, y, w, h) |
| 584 | 586 | ||
| ... | 323 lines hidden [Expand] | ||
| 908 | 910 | ||
| 909 | # Create a review request for this test. |
911 | # Create a review request for this test. |
| 910 | review_request = self.testNewReviewRequest() |
912 | review_request = self.testNewReviewRequest() |
| 911 | 913 | ||
| 912 | # Upload the first diff and publish the draft. |
914 | # Upload the first diff and publish the draft. |
| 913 | diffset = self.testNewDiff(review_request) |
915 | diffset_id = self.testNewDiff(review_request).id |
| 914 | rsp = self.apiPost("reviewrequests/%s/draft/save" % review_request.id) |
916 | rsp = self.apiPost("reviewrequests/%s/draft/save" % review_request.id) |
| 915 | self.assertEqual(rsp['stat'], 'ok') |
917 | self.assertEqual(rsp['stat'], 'ok') |
| 916 | 918 | ||
| 917 | # Upload the second diff and publish the draft. |
919 | # Upload the second diff and publish the draft. |
| 918 | interdiffset = self.testNewDiff(review_request) |
920 | interdiffset_id = self.testNewDiff(review_request).id |
| 919 | rsp = self.apiPost("reviewrequests/%s/draft/save" % review_request.id) |
921 | rsp = self.apiPost("reviewrequests/%s/draft/save" % review_request.id) |
| 920 | self.assertEqual(rsp['stat'], 'ok') |
922 | self.assertEqual(rsp['stat'], 'ok') |
| 921 | 923 | ||
| 924 | # Reload the diffsets, now that they've been modified. |
||
| 925 | diffset = DiffSet.objects.get(pk=diffset_id) |
||
| 926 | interdiffset = DiffSet.objects.get(pk=interdiffset_id) |
||
| 927 | |||
| 922 | # Get the interdiffs |
928 | # Get the interdiffs |
| 923 | filediff = diffset.files.all()[0] |
929 | filediff = diffset.files.all()[0] |
| 924 | interfilediff = interdiffset.files.all()[0] |
930 | interfilediff = interdiffset.files.all()[0] |
| 925 | 931 | ||
| 926 | rsp = self.apiPost( |
932 | rsp = self.apiPost( |
| ... | 149 lines hidden [Expand] | ||
Other reviews