Пример #1
0
    def testMoveDetection(self):
        """Testing move detection"""
        # movetest1 has two blocks of code that would appear to be moves:
        # a function, and an empty comment block. Only the function should
        # be seen as a move, whereas the empty comment block is less useful
        # (since it's content-less) and shouldn't be seen as once.
        old = self._get_file('orig_src', 'movetest1.c')
        new = self._get_file('new_src', 'movetest1.c')
        differ = diffutils.Differ(old.splitlines(), new.splitlines())

        r_moves = []
        i_moves = []

        for opcodes in diffutils.opcodes_with_metadata(differ):
            tag = opcodes[0]
            meta = opcodes[-1]

            if tag == 'delete':
                if 'moved' in meta:
                    r_moves.append(meta['moved'])
            elif tag == 'insert':
                if 'moved' in meta:
                    i_moves.append(meta['moved'])

        self.assertEqual(len(r_moves), 1)
        self.assertEqual(len(i_moves), 1)

        moves = [
            (15, 28),
            (16, 29),
            (17, 30),
            (18, 31),
            (19, 32)
        ]

        for i, j in moves:
            self.assertTrue(j in i_moves[0])
            self.assertTrue(i in r_moves[0])
            self.assertEqual(i_moves[0][j], i)
            self.assertEqual(r_moves[0][i], j)
Пример #2
0
def get_chunks(request_file_object):
  def diff_line(vlinenum, oldlinenum, newlinenum, oldline, newline,
              oldmarkup, newmarkup):
    """
    A generator that yields chunks within a range of lines in the specified
    filediff/interfilediff.

    This is primarily intended for use with templates. It takes a
    RequestContext for looking up the user and for caching file lists,
    in order to improve performance and reduce lookup times for files that have
    already been fetched.

    Each returned chunk is a dictionary with the following fields:

      ============= ========================================================
      Variable      Description
      ============= ========================================================
      ``change``    The change type ("equal", "replace", "insert", "delete")
      ``numlines``  The number of lines in the chunk.
      ``lines``     The list of lines in the chunk.
      ``meta``      A dictionary containing metadata on the chunk
      ============= ========================================================

    Each line in the list of lines is an array with the following data:

      ======== =============================================================
      Index    Description
      ======== =============================================================
      0        Virtual line number (union of the original and patched files)
      1        Real line number in the original file
      2        HTML markup of the original file
      3        Changed regions of the original line (for "replace" chunks)
      4        Real line number in the patched file
      5        HTML markup of the patched file
      6        Changed regions of the patched line (for "replace" chunks)
      7        True if line consists of only whitespace changes
      8        Comments associated with the current line
      ======== =============================================================
    """
    # This function accesses the variable meta, defined in an outer context.
    if oldline and newline and oldline != newline:
      oldregion, newregion = get_line_changed_regions(oldline, newline)
    else:
      oldregion = newregion = []

    result = [vlinenum,
              oldlinenum or '', mark_safe(oldmarkup or ''), oldregion,
              newlinenum or '', mark_safe(newmarkup or ''), newregion,
              (oldlinenum, newlinenum) in meta['whitespace_lines']]

    try:
      # Get the top-level comment for this line
      # Need to handle multiple comments on the same line here
      comments = []
      for c in file_comments:
        if c[1] == newlinenum:
          comments.append(RawComment(c[0], c[1], c[2], c[3]))
      # Done so the web template can detect that there aren't any comments
      if len(comments) == 0:
        comments = None
    except ObjectDoesNotExist:
      comments = None
    result.append(comments)

    if oldlinenum and oldlinenum in meta.get('moved', {}):
      destination = meta["moved"][oldlinenum]
      result.append(destination)
    elif newlinenum and newlinenum in meta.get('moved', {}):
      destination = meta["moved"][newlinenum]
      result.append(destination)

    return result

  def new_chunk(lines, start, end, collapsable=False,
              tag='equal', meta=None):
    if not meta:
        meta = {}

    left_headers = list(get_interesting_headers(differ, lines,
                                                start, end - 1, False))
    right_headers = list(get_interesting_headers(differ, lines,
                                                 start, end - 1, True))

    meta['left_headers'] = left_headers
    meta['right_headers'] = right_headers

    if left_headers:
        last_header[0] = left_headers[-1][1]

    if right_headers:
        last_header[1] = right_headers[-1][1]

    if (collapsable and end < len(lines) and
        (last_header[0] or last_header[1])):
        meta['headers'] = [
            (last_header[0] or "").strip(),
            (last_header[1] or "").strip(),
        ]

    return {
        'lines': lines[start:end], #if tag != 'equal' else ["asdf"],
        'numlines': end - start,
        'change': tag,
        'collapsable': collapsable,
        'meta': meta
    }

  def get_interesting_headers(differ, lines, start, end, is_modified_file):
      """Returns all headers for a region of a diff.

      This scans for all headers that fall within the specified range
      of the specified lines on both the original and modified files.
      """
      possible_functions = differ.get_interesting_lines('header',
                                                        is_modified_file)

      if not possible_functions:
          raise StopIteration

      try:
          if is_modified_file:
              last_index = last_header_index[1]
              i1 = lines[start][4]
              i2 = lines[end - 1][4]
          else:
              last_index = last_header_index[0]
              i1 = lines[start][1]
              i2 = lines[end - 1][1]
      except IndexError:
          raise StopIteration

      for i in xrange(last_index, len(possible_functions)):
          linenum, line = possible_functions[i]
          linenum += 1

          if linenum > i2:
              break
          elif linenum >= i1:
              last_index = i
              yield (linenum, line)

      if is_modified_file:
          last_header_index[1] = last_index
      else:
          last_header_index[0] = last_index

  def apply_pygments(data, filename):
    # XXX Guessing is preferable but really slow, especially on XML
    #     files.
    #if filename.endswith(".xml"):

    try:
      lexer = get_lexer_for_filename(filename, stripnl=False, encoding='utf-8')
    except pygments.util.ClassNotFound:
      lexer = CppLexer()

    #try:
    #  # This is only available in 0.7 and higher
    #  lexer.add_filter('codetagify')
    #except AttributeError:
    #  pass

    return pygments.highlight(data, lexer, NoWrapperHtmlFormatter()).splitlines()

  """
  Config variables
  """
  contextLines = 3
  enableSyntaxHighlighting = False
  collapse_threshold = 2 * contextLines + 3

  old = new = None
  if request_file_object.modified or request_file_object.deleted:
    old = request_file_object.get_old_file().get_data()
  if request_file_object.modified or request_file_object.new:
    new = request_file_object.get_new_file().get_data()

  linenum = 1
  last_header = (None, None)

  a = NEWLINES_RE.split(old or '')
  b = NEWLINES_RE.split(new or '')

  a_num_lines = len(a)
  b_num_lines = len(b)

  markup_a = markup_b = None

  if enableSyntaxHighlighting:
    markup_a = apply_pygments(old or '', request_file_object.get_name())
    markup_b = apply_pygments(new or '', request_file_object.get_name())

  if not markup_a:
    markup_a = NEWLINES_RE.split(escape(old))
  if not markup_b:
    markup_b = NEWLINES_RE.split(escape(new))

  differ = MyersDiffer(a, b, False)

  override_tag = not (request_file_object.modified)

  file_comments_temp = DiffComment.objects.filter(file_id=request_file_object). \
                                      order_by('line_number'). \
                                      values_list('user_id', 'line_number', 'comment', 'id')
  file_comments = []
  for comment in file_comments_temp:
    #file_comments.append((User.objects.get(pk=comment[0]), comment[1], comment[2]))
    file_comments.append((User.objects.get(pk=comment[0]).username, comment[1], comment[2], comment[3]))
  RawComment = namedtuple('RawComment', 'user_id line_number comment id')

  for tag, i1, i2, j1, j2, meta in opcodes_with_metadata(differ):
      oldlines = markup_a[i1:i2]
      newlines = markup_b[j1:j2]
      numlines = max(len(oldlines), len(newlines))

      lines = map(diff_line,
          xrange(linenum, linenum + numlines),
          xrange(i1 + 1, i2 + 1), xrange(j1 + 1, j2 + 1),
          a[i1:i2], b[j1:j2], oldlines, newlines)

      if tag == 'equal' and numlines > collapse_threshold:
        last_range_start = numlines - contextLines

        if linenum == 1:
          yield new_chunk(lines, 0, last_range_start, True)
          yield new_chunk(lines, last_range_start, numlines)
        else:
          yield new_chunk(lines, 0, contextLines)

          if i2 == a_num_lines and j2 == b_num_lines:
            yield new_chunk(lines, contextLines, numlines, True)
          else:
            yield new_chunk(lines, contextLines,
                            last_range_start, True)
            yield new_chunk(lines, last_range_start, numlines)
      else:
          if override_tag:
            yield new_chunk(lines, 0, numlines, False, 'equal', meta)
          else:
            yield new_chunk(lines, 0, numlines, False, tag, meta)

      linenum += numlines