commit_range_reference_filter.rb 3.1 KB
Newer Older
R
Robert Speicher 已提交
1 2
module Gitlab
  module Markdown
3
    # HTML filter that replaces commit range references with links.
R
Robert Speicher 已提交
4 5
    #
    # This filter supports cross-project references.
6
    class CommitRangeReferenceFilter < ReferenceFilter
R
Robert Speicher 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
      include CrossProjectReference

      # Public: Find commit range references in text
      #
      #   CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
      #     "<a href=...>#{commit_range}</a>"
      #   end
      #
      # text - String text to search.
      #
      # Yields the String match, the String commit range, and an optional String
      # of the external project reference.
      #
      # Returns a String replaced with the return of the block.
      def self.references_in(text)
        text.gsub(COMMIT_RANGE_PATTERN) do |match|
          yield match, $~[:commit_range], $~[:project]
        end
      end

      # Pattern used to extract commit range references from text
      #
      # The beginning and ending SHA1 sums can be between 6 and 40 hex
      # characters, and the range selection can be double- or triple-dot.
      #
      # This pattern supports cross-project references.
      COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>\h{6,40}\.{2,3}\h{6,40})/

      def call
36 37
        replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content|
          commit_range_link_filter(content)
R
Robert Speicher 已提交
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
        end
      end

      # Replace commit range references in text with links to compare the commit
      # ranges.
      #
      # text - String text to replace references in.
      #
      # Returns a String with commit range references replaced with links. All
      # links have `gfm` and `gfm-commit_range` class names attached for
      # styling.
      def commit_range_link_filter(text)
        self.class.references_in(text) do |match, commit_range, project_ref|
          project = self.project_from_ref(project_ref)

          from_id, to_id = split_commit_range(commit_range)

          if valid_range?(project, from_id, to_id)
            url = url_for_commit_range(project, from_id, to_id)

            title = "Commits #{from_id} through #{to_id}"
59
            klass = reference_class(:commit_range)
R
Robert Speicher 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

            project_ref += '@' if project_ref

            %(<a href="#{url}"
                 title="#{title}"
                 class="#{klass}">#{project_ref}#{commit_range}</a>)
          else
            match
          end
        end
      end

      def split_commit_range(range)
        from_id, to_id = range.split(/\.{2,3}/, 2)
        from_id << "^" if range !~ /\.{3}/

        [from_id, to_id]
      end

      def valid_range?(project, from_id, to_id)
        project.valid_repo? &&
          project.repository.commit(from_id) &&
          project.repository.commit(to_id)
      end

      def url_for_commit_range(project, from_id, to_id)
        h = Rails.application.routes.url_helpers
        h.namespace_project_compare_url(project.namespace, project,
                                        from: from_id, to: to_id,
                                        only_path: context[:only_path])
      end
    end
  end
end