Changeset 74142 in webkit


Ignore:
Timestamp:
Dec 15, 2010 2:51:46 PM (13 years ago)
Author:
ojan@chromium.org
Message:

2010-12-14 Ojan Vafai <ojan@chromium.org>

Reviewed by Adam Barth.

add ability to view for file context to the review tool
https://bugs.webkit.org/show_bug.cgi?id=51057

At the beginning/end of each file diff and between each
hunk add links to expand the context. For now it grabs the
tip of tree version of the file and tries to apply the diff
to that file. If it can't apply, then it gives up as we
wouldn't want to show the wrong lines of context.

In the future, we can consider adding the upload svn revision
to the diff itself, then we could fallback to the file at that
revision if tip of tree doesn't apply.

  • PrettyPatch/PrettyPatch.rb:
  • code-review.js:
Location:
trunk/BugsSite
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/BugsSite/ChangeLog

    r74130 r74142  
     12010-12-14  Ojan Vafai  <ojan@chromium.org>
     2
     3        Reviewed by Adam Barth.
     4
     5        add ability to view for file context to the review tool
     6        https://bugs.webkit.org/show_bug.cgi?id=51057
     7
     8        At the beginning/end of each file diff and between each
     9        hunk add links to expand the context. For now it grabs the
     10        tip of tree version of the file and tries to apply the diff
     11        to that file. If it can't apply, then it gives up as we
     12        wouldn't want to show the wrong lines of context.
     13
     14        In the future, we can consider adding the upload svn revision
     15        to the diff itself, then we could fallback to the file at that
     16        revision if tip of tree doesn't apply.
     17
     18        * PrettyPatch/PrettyPatch.rb:
     19        * code-review.js:
     20
    1212010-12-08  Ojan Vafai  <ojan@chromium.org>
    222
  • trunk/BugsSite/PrettyPatch/PrettyPatch.rb

    r74130 r74142  
    113113}
    114114
     115:link {
     116    color: #039;
     117}
     118
    115119.FileDiff {
    116120    background-color: #f8f8f8;
     
    142146}
    143147
    144 .lineNumber {
    145     background-color: #eed;
     148.lineNumber, .expansionLineNumber {
    146149    border-bottom: 1px solid #998;
    147150    border-right: 1px solid #ddd;
     
    152155    vertical-align: bottom;
    153156    width: 3em;
     157}
     158
     159.lineNumber {
     160  background-color: #eed;
     161}
     162
     163.expansionLineNumber {
     164  background-color: #eee;
    154165}
    155166
     
    270281  border-bottom-color: #69F;
    271282  border-right-color: #69F;
     283}
     284
     285.ExpandArea {
     286  margin: 0;
     287}
     288
     289.ExpandText {
     290  margin-left: 0.67em;
     291}
     292
     293.ExpandLinkContainer a {
     294  border: 0;
     295}
     296
     297.ExpandLinkContainer a:after {
     298  content: " | ";
     299  color: black;
     300}
     301
     302.ExpandLinkContainer a:last-of-type:after {
     303  content: "";
    272304}
    273305
  • trunk/BugsSite/code-review.js

    r74130 r74142  
    2424
    2525(function() {
     26  /**
     27   * Create a new function with some of its arguements
     28   * pre-filled.
     29   * Taken from goog.partial in the Closure library.
     30   * @param {Function} fn A function to partially apply.
     31   * @param {...*} var_args Additional arguments that are partially
     32   *     applied to fn.
     33   * @return {!Function} A partially-applied form of the function.
     34   */
     35  function partial(fn, var_args) {
     36    var args = Array.prototype.slice.call(arguments, 1);
     37    return function() {
     38      // Prepend the bound arguments to the current arguments.
     39      var newArgs = Array.prototype.slice.call(arguments);
     40      newArgs.unshift.apply(newArgs, args);
     41      return fn.apply(this, newArgs);
     42    };
     43  };
     44
    2645  function determineAttachmentID() {
    2746    try {
     
    4261
    4362  var next_line_id = 0;
     63  var files = {};
     64  var original_file_contents = {};
     65  var patched_file_contents = {};
     66  var WEBKIT_BASE_DIR = "http://svn.webkit.org/repository/webkit/trunk/";
    4467
    4568  function idForLine(number) {
     
    121144    addCommentFor($('#' + id));
    122145  }
    123 
    124   var files = {}
    125146
    126147  function addPreviousComment(line, author, comment_text) {
     
    258279      var file_name = $(this).children('h1').text();
    259280      files[file_name] = this;
    260     });
     281      addExpandLinks(file_name);
     282    });
     283  }
     284
     285  function addExpandLinks(file_name) {
     286    if (file_name.indexOf('ChangeLog') != -1)
     287      return;
     288
     289    var file_diff = files[file_name];
     290    $('.context', file_diff).detach();
     291
     292    var expand_bar_index = 0;
     293
     294    // Don't show the links to expand upwards/downwards if the patch starts/ends without context
     295    // lines, i.e. starts/ends with add/remove lines.
     296    var first_line = file_diff.querySelector('.Line');
     297    if (!$(first_line).hasClass('add') && !$(first_line).hasClass('remove'))
     298      $('h1', file_diff).after(expandBarHtml(file_name, BELOW))
     299
     300    $('br').replaceWith(expandBarHtml(file_name));
     301
     302    var last_line = file_diff.querySelector('.Line:last-of-type');
     303    // Some patches for new files somehow end up with an empty context line at the end
     304    // with a from line number of 0. Don't show expand links in that case either.
     305    if (!$(last_line).hasClass('add') && !$(last_line).hasClass('remove') && fromLineNumber(last_line) != 0)
     306      $(file_diff).append(expandBarHtml(file_name, ABOVE));
     307  }
     308
     309  function expandBarHtml(file_name, opt_direction) {
     310    var html = '<div class="ExpandBar">' +
     311        '<pre class="ExpandArea Expand' + ABOVE + '"></pre>' +
     312        '<div class="ExpandLinkContainer"><span class="ExpandText">expand: </span>';
     313
     314    // FIXME: If there are <100 line to expand, don't show the expand-100 link.
     315    // If there are <20 lines to expand, don't show the expand-20 link.
     316    if (!opt_direction || opt_direction == ABOVE) {
     317      html += expandLinkHtml(ABOVE, 100) +
     318          expandLinkHtml(ABOVE, 20);
     319    }
     320
     321    html += expandLinkHtml(ALL);
     322
     323    if (!opt_direction || opt_direction == BELOW) {
     324      html += expandLinkHtml(BELOW, 20) +
     325        expandLinkHtml(BELOW, 100);
     326    }
     327
     328    html += '</div><pre class="ExpandArea Expand' + BELOW + '"></pre></div>';
     329    return html;
     330  }
     331
     332  function expandLinkHtml(direction, amount) {
     333    return "<a class='ExpandLink' href='javascript:' data-direction='" + direction + "' data-amount='" + amount + "'>" +
     334        (amount ? amount + " " : "") + direction + "</a>";
     335  }
     336
     337  $(window).bind('click', function (e) {
     338    var target = e.target;
     339    if (target.className != 'ExpandLink')
     340      return;
     341
     342    // Can't use $ here because something in the window's scope sets $ to something other than jQuery.
     343    var expand_bar = jQuery(target).parents('.ExpandBar');
     344    var file_name = expand_bar.parents('.FileDiff').children('h1')[0].textContent;
     345    var expand_function = partial(expand, expand_bar[0], file_name, target.getAttribute('data-direction'), Number(target.getAttribute('data-amount')));
     346    if (file_name in original_file_contents)
     347      expand_function();
     348    else
     349      getWebKitSourceFile(file_name, expand_function, expand_bar);
     350  });
     351
     352  function getWebKitSourceFile(file_name, onLoad, expand_bar) {
     353    function handleLoad(contents) {
     354      original_file_contents[file_name] = contents.split('\n');
     355      patched_file_contents[file_name] = applyDiff(original_file_contents[file_name], file_name);
     356      onLoad();
     357    };
     358
     359    $.ajax({
     360      url: WEBKIT_BASE_DIR + file_name,
     361      context: document.body,
     362      complete: function(xhr, data) {
     363              if (xhr.status == 0)
     364                  handleLoadError(expand_bar);
     365              else
     366                  handleLoad(xhr.responseText);
     367      }
     368    });
     369  }
     370
     371  function replaceExpandLinkContainers(expand_bar, text) {
     372    $('.ExpandLinkContainer', $(expand_bar).parents('.FileDiff')).replaceWith('<span class="ExpandText">' + text + '</span>');
     373  }
     374
     375  function handleLoadError(expand_bar) {
     376    // FIXME: In this case, try fetching the source file at the revision the patch was created at,
     377    // in case the file has bee deleted.
     378    // Might need to modify webkit-patch to include that data in the diff.
     379    replaceExpandLinkContainers(expand_bar, "Can't expand. Is this a new or deleted file?");
     380  }
     381
     382  var ABOVE = 'above';
     383  var BELOW = 'below';
     384  var ALL = 'all';
     385
     386  function expand(expand_bar, file_name, direction, amount) {
     387    if (file_name in original_file_contents && !patched_file_contents[file_name]) {
     388      // FIXME: In this case, try fetching the source file at the revision the patch was created at.
     389      // Might need to modify webkit-patch to include that data in the diff.
     390      replaceExpandLinkContainers(expand_bar, "Can't expand. Unable to apply patch to tip of tree.");
     391      return;
     392    }
     393
     394    var above_expansion = expand_bar.querySelector('.Expand' + ABOVE)
     395    var below_expansion = expand_bar.querySelector('.Expand' + BELOW)
     396
     397    var above_last_line = above_expansion.querySelector('.ExpansionLine:last-of-type');
     398    if (!above_last_line) {
     399      var diff_section = expand_bar.previousElementSibling;
     400      above_last_line = diff_section.querySelector('.Line:last-of-type');
     401    }
     402
     403    var above_last_line_num, above_last_from_line_num;
     404    if (above_last_line) {
     405      above_last_line_num = toLineNumber(above_last_line);
     406      above_last_from_line_num = fromLineNumber(above_last_line);
     407    } else
     408      above_last_from_line_num = above_last_line_num = 0;
     409
     410    var below_first_line = below_expansion.querySelector('.ExpansionLine');
     411    if (!below_first_line) {
     412      var diff_section = expand_bar.nextElementSibling;
     413      if (diff_section)
     414        below_first_line = diff_section.querySelector('.Line');
     415    }
     416
     417    var below_first_line_num, below_first_from_line_num;
     418    if (below_first_line) {
     419      below_first_line_num = toLineNumber(below_first_line) - 1;
     420      below_first_from_line_num = fromLineNumber(below_first_line) - 1;
     421    } else
     422      below_first_from_line_num = below_first_line_num = patched_file_contents[file_name].length - 1;
     423
     424    var start_line_num, start_from_line_num;
     425    var end_line_num;
     426
     427    if (direction == ABOVE) {
     428      start_from_line_num = above_last_from_line_num;
     429      start_line_num = above_last_line_num;
     430      end_line_num = Math.min(start_line_num + amount, below_first_line_num);
     431    } else if (direction == BELOW) {
     432      end_line_num = below_first_line_num;
     433      start_line_num = Math.max(end_line_num - amount, above_last_line_num)
     434      start_from_line_num = Math.max(below_first_from_line_num - amount, above_last_from_line_num)
     435    } else { // direction == ALL
     436      start_line_num = above_last_line_num;
     437      start_from_line_num = above_last_from_line_num;
     438      end_line_num = below_first_line_num;
     439    }
     440
     441    var expansion_area;
     442    // Filling in all the remaining lines. Overwrite the expand links.
     443    if (start_line_num == above_last_line_num && end_line_num == below_first_line_num) {
     444      expansion_area = expand_bar.querySelector('.ExpandLinkContainer');
     445      expansion_area.innerHTML = '';
     446    } else {
     447      expansion_area = direction == ABOVE ? above_expansion : below_expansion;
     448    }
     449
     450    insertLines(file_name, expansion_area, direction, start_line_num, end_line_num, start_from_line_num);
     451  }
     452
     453  function insertLines(file_name, expansion_area, direction, start_line_num, end_line_num, start_from_line_num) {
     454    var fragment = document.createDocumentFragment();
     455    for (var i = 0; i < end_line_num - start_line_num; i++) {
     456      var line = document.createElement('div');
     457      line.className = 'ExpansionLine';
     458      // FIXME: from line numbers are wrong
     459      line.innerHTML = '<span class="from expansionlineNumber">' + (start_from_line_num + i + 1) +
     460          '</span><span class="to expansionlineNumber">' + (start_line_num + i + 1) +
     461          '</span> <span class="text"></span>';
     462      line.querySelector('.text').textContent = patched_file_contents[file_name][start_line_num + i];
     463      fragment.appendChild(line);
     464    }
     465
     466    if (direction == BELOW)
     467      expansion_area.insertBefore(fragment, expansion_area.firstChild);
     468    else
     469      expansion_area.appendChild(fragment);
     470  }
     471
     472  function hunkStartingLine(patched_file, context, prev_line, hunk_num) {
     473    var PATCH_FUZZ = 2;
     474    var current_line = -1;
     475    var last_context_line = context[context.length - 1];
     476    if (patched_file[prev_line] == last_context_line)
     477      current_line = prev_line + 1;
     478    else {
     479      for (var i = prev_line - PATCH_FUZZ; i < prev_line + PATCH_FUZZ; i++) {
     480        if (patched_file[i] == last_context_line)
     481          current_line = i + 1;
     482      }
     483
     484      if (current_line == -1) {
     485        console.log('Hunk #' + hunk_num + ' FAILED.');
     486        return -1;
     487      }
     488    }
     489
     490    // For paranoia sake, confirm the rest of the context matches;
     491    for (var i = 0; i < context.length - 1; i++) {
     492      if (patched_file[current_line - context.length + i] != context[i]) {
     493        console.log('Hunk #' + hunk_num + ' FAILED. Did not match preceding context.');
     494        return -1;
     495      }
     496    }
     497
     498    return current_line;
     499  }
     500
     501  function fromLineNumber(line) {
     502    return Number(line.querySelector('.from').textContent);
     503  }
     504
     505  function toLineNumber(line) {
     506    return Number(line.querySelector('.to').textContent);
     507  }
     508
     509  function lineNumberForFirstNonContextLine(patched_file, line, prev_line, context, hunk_num) {
     510    if (context.length) {
     511      var prev_line_num = fromLineNumber(prev_line) - 1;
     512      return hunkStartingLine(patched_file, context, prev_line_num, hunk_num);
     513    }
     514
     515    if (toLineNumber(line) == 1 || fromLineNumber(line) == 1)
     516      return 0;
     517
     518    console.log('Failed to apply patch. Adds or removes lines before any context lines.');
     519    return -1;
     520  }
     521
     522  function applyDiff(original_file, file_name) {
     523    var diff_sections = files[file_name].getElementsByClassName('DiffSection');
     524    var patched_file = original_file.concat([]);
     525
     526    // Apply diffs in reverse order to avoid needing to keep track of changing line numbers.
     527    for (var i = diff_sections.length - 1; i >= 0; i--) {
     528      var section = diff_sections[i];
     529      var lines = section.getElementsByClassName('Line');
     530      var current_line = -1;
     531      var context = [];
     532      var hunk_num = i + 1;
     533
     534      for (var j = 0, lines_len = lines.length; j < lines_len; j++) {
     535        var line = lines[j];
     536        var line_contents = line.querySelector('.text').textContent;
     537        if ($(line).hasClass('add')) {
     538          if (current_line == -1) {
     539            current_line = lineNumberForFirstNonContextLine(patched_file, line, lines[j-1], context, hunk_num);
     540            if (current_line == -1)
     541              return null;
     542          }
     543
     544          patched_file.splice(current_line, 0, line_contents);
     545          current_line++;
     546        } else if ($(line).hasClass('remove')) {
     547          if (current_line == -1) {
     548            current_line = lineNumberForFirstNonContextLine(patched_file, line, lines[j-1], context, hunk_num);
     549            if (current_line == -1)
     550              return null;
     551          }
     552
     553          if (patched_file[current_line] != line_contents) {
     554            console.log('Hunk #' + hunk_num + ' FAILED.');
     555            return null;
     556          }
     557
     558          patched_file.splice(current_line, 1);
     559        } else if (current_line == -1) {
     560          context.push(line_contents);
     561        } else if (line_contents != patched_file[current_line]) {
     562          console.log('Hunk #' + hunk_num + ' FAILED. Context at end did not match');
     563          return null;
     564        } else {
     565          current_line++;
     566        }
     567      }
     568    }
     569
     570    return patched_file;
    261571  }
    262572
Note: See TracChangeset for help on using the changeset viewer.