Review Board

beta

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).

Diff revision 4 (Latest)

  1. /trunk/reviewboard/reviews/views.py: 15 changes [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ]
  2. /trunk/reviewboard/templates/diffviewer/view_diff.html: 9 changes [ 1 2 3 4 5 6 7 8 9 ]
  3. /trunk/reviewboard/webapi/json.py: 2 changes [ 1 2 ]
  4. /trunk/reviewboard/webapi/tests.py: 4 changes [ 1 2 3 4 ]
/trunk/reviewboard/reviews/views.py
Revision 1203 New Change
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 \
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
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
/trunk/reviewboard/templates/diffviewer/view_diff.html
Revision 1203 New Change
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:" %}
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>
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 %}
/trunk/reviewboard/webapi/json.py
Revision 1208 New Change
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
        })
/trunk/reviewboard/webapi/tests.py
Revision 1203 New Change
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
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(
  1. /trunk/reviewboard/reviews/views.py: 15 changes [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ]
  2. /trunk/reviewboard/templates/diffviewer/view_diff.html: 9 changes [ 1 2 3 4 5 6 7 8 9 ]
  3. /trunk/reviewboard/webapi/json.py: 2 changes [ 1 2 ]
  4. /trunk/reviewboard/webapi/tests.py: 4 changes [ 1 2 3 4 ]