From 117d4e5c693f222c779a75bd29baca11de684780 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 15:10:59 -0500
Subject: [PATCH 01/18] Extract generic parts of Gitlab::Diff::InlineDiffMarker

---
 lib/gitlab/diff/inline_diff_marker.rb       | 114 ++------------------
 lib/gitlab/string_range_marker.rb           | 102 ++++++++++++++++++
 spec/lib/gitlab/string_range_marker_spec.rb |  36 +++++++
 3 files changed, 146 insertions(+), 106 deletions(-)
 create mode 100644 lib/gitlab/string_range_marker.rb
 create mode 100644 spec/lib/gitlab/string_range_marker_spec.rb

diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 736933b1c4b..96f5ca31e5c 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -1,38 +1,18 @@
 module Gitlab
   module Diff
-    class InlineDiffMarker
+    class InlineDiffMarker < Gitlab::StringRangeMarker
       MARKDOWN_SYMBOLS = {
         addition: "+",
         deletion: "-"
       }.freeze
 
-      attr_accessor :raw_line, :rich_line
-
-      def initialize(raw_line, rich_line = raw_line)
-        @raw_line = raw_line
-        @rich_line = ERB::Util.html_escape(rich_line)
-      end
-
       def mark(line_inline_diffs, mode: nil, markdown: false)
-        return rich_line unless line_inline_diffs
-
-        marker_ranges = []
-        line_inline_diffs.each do |inline_diff_range|
-          # Map the inline-diff range based on the raw line to character positions in the rich line
-          inline_diff_positions = position_mapping[inline_diff_range].flatten
-          # Turn the array of character positions into ranges
-          marker_ranges.concat(collapse_ranges(inline_diff_positions))
-        end
-
-        offset = 0
-
-        # Mark each range
-        marker_ranges.each_with_index do |range, index|
+        super(line_inline_diffs) do |text, left:, right:|
           before_content =
             if markdown
               "{#{MARKDOWN_SYMBOLS[mode]}"
             else
-              "<span class='#{html_class_names(marker_ranges, mode, index)}'>"
+              "<span class='#{html_class_names(left, right, mode)}'>"
             end
           after_content =
             if markdown
@@ -40,98 +20,20 @@ module Gitlab
             else
               "</span>"
             end
-          offset = insert_around_range(rich_line, range, before_content, after_content, offset)
-        end
 
-        rich_line.html_safe
+          "#{before_content}#{text}#{after_content}"
+        end
       end
 
       private
 
-      def html_class_names(marker_ranges, mode, index)
+      def html_class_names(left, right, mode)
         class_names = ["idiff"]
-        class_names << "left"  if index == 0
-        class_names << "right" if index == marker_ranges.length - 1
+        class_names << "left"  if left
+        class_names << "right" if right
         class_names << mode if mode
         class_names.join(" ")
       end
-
-      # Mapping of character positions in the raw line, to the rich (highlighted) line
-      def position_mapping
-        @position_mapping ||= begin
-          mapping = []
-          rich_pos = 0
-          (0..raw_line.length).each do |raw_pos|
-            rich_char = rich_line[rich_pos]
-
-            # The raw and rich lines are the same except for HTML tags,
-            # so skip over any `<...>` segment
-            while rich_char == '<'
-              until rich_char == '>'
-                rich_pos += 1
-                rich_char = rich_line[rich_pos]
-              end
-
-              rich_pos += 1
-              rich_char = rich_line[rich_pos]
-            end
-
-            # multi-char HTML entities in the rich line correspond to a single character in the raw line
-            if rich_char == '&'
-              multichar_mapping = [rich_pos]
-              until rich_char == ';'
-                rich_pos += 1
-                multichar_mapping << rich_pos
-                rich_char = rich_line[rich_pos]
-              end
-
-              mapping[raw_pos] = multichar_mapping
-            else
-              mapping[raw_pos] = rich_pos
-            end
-
-            rich_pos += 1
-          end
-
-          mapping
-        end
-      end
-
-      # Takes an array of integers, and returns an array of ranges covering the same integers
-      def collapse_ranges(positions)
-        return [] if positions.empty?
-        ranges = []
-
-        start = prev = positions[0]
-        range = start..prev
-        positions[1..-1].each do |pos|
-          if pos == prev + 1
-            range = start..pos
-            prev = pos
-          else
-            ranges << range
-            start = prev = pos
-            range = start..prev
-          end
-        end
-        ranges << range
-
-        ranges
-      end
-
-      # Inserts tags around the characters identified by the given range
-      def insert_around_range(text, range, before, after, offset = 0)
-        # Just to be sure
-        return offset if offset + range.end + 1 > text.length
-
-        text.insert(offset + range.begin, before)
-        offset += before.length
-
-        text.insert(offset + range.end + 1, after)
-        offset += after.length
-
-        offset
-      end
     end
   end
 end
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
new file mode 100644
index 00000000000..94fba0a221a
--- /dev/null
+++ b/lib/gitlab/string_range_marker.rb
@@ -0,0 +1,102 @@
+module Gitlab
+  class StringRangeMarker
+    attr_accessor :raw_line, :rich_line
+
+    def initialize(raw_line, rich_line = raw_line)
+      @raw_line = raw_line
+      @rich_line = ERB::Util.html_escape(rich_line)
+    end
+
+    def mark(marker_ranges)
+      return rich_line unless marker_ranges
+
+      rich_marker_ranges = []
+      marker_ranges.each do |range|
+        # Map the inline-diff range based on the raw line to character positions in the rich line
+        rich_positions = position_mapping[range].flatten
+        # Turn the array of character positions into ranges
+        rich_marker_ranges.concat(collapse_ranges(rich_positions))
+      end
+
+      offset = 0
+      # Mark each range
+      rich_marker_ranges.each_with_index do |range, i|
+        offset_range = (range.begin + offset)..(range.end + offset)
+        original_text = rich_line[offset_range]
+
+        text = yield(original_text, left: i == 0, right: i == rich_marker_ranges.length - 1)
+
+        rich_line[offset_range] = text
+
+        offset += text.length - original_text.length
+      end
+
+      rich_line.html_safe
+    end
+
+    private
+
+    # Mapping of character positions in the raw line, to the rich (highlighted) line
+    def position_mapping
+      @position_mapping ||= begin
+        mapping = []
+        rich_pos = 0
+        (0..raw_line.length).each do |raw_pos|
+          rich_char = rich_line[rich_pos]
+
+          # The raw and rich lines are the same except for HTML tags,
+          # so skip over any `<...>` segment
+          while rich_char == '<'
+            until rich_char == '>'
+              rich_pos += 1
+              rich_char = rich_line[rich_pos]
+            end
+
+            rich_pos += 1
+            rich_char = rich_line[rich_pos]
+          end
+
+          # multi-char HTML entities in the rich line correspond to a single character in the raw line
+          if rich_char == '&'
+            multichar_mapping = [rich_pos]
+            until rich_char == ';'
+              rich_pos += 1
+              multichar_mapping << rich_pos
+              rich_char = rich_line[rich_pos]
+            end
+
+            mapping[raw_pos] = multichar_mapping
+          else
+            mapping[raw_pos] = rich_pos
+          end
+
+          rich_pos += 1
+        end
+
+        mapping
+      end
+    end
+
+    # Takes an array of integers, and returns an array of ranges covering the same integers
+    def collapse_ranges(positions)
+      return [] if positions.empty?
+      ranges = []
+
+      start = prev = positions[0]
+      range = start..prev
+      positions[1..-1].each do |pos|
+        if pos == prev + 1
+          range = start..pos
+          prev = pos
+        else
+          ranges << range
+          start = prev = pos
+          range = start..prev
+        end
+      end
+      ranges << range
+
+      ranges
+    end
+  end
+end
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
new file mode 100644
index 00000000000..45b4d4af206
--- /dev/null
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::StringRangeMarker, lib: true do
+  describe '#mark' do
+    context "when the rich text is html safe" do
+      let(:raw)  { "abc <def>" }
+      let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
+      let(:inline_diffs) { [2..5] }
+      let(:subject) do
+        described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:|
+          "LEFT#{text}RIGHT"
+        end
+      end
+
+      it 'marks the inline diffs' do
+        expect(subject).to eq(%{<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT&lt;dRIGHTef&gt;</span>})
+        expect(subject).to be_html_safe
+      end
+    end
+
+    context "when the rich text is not html safe" do
+      let(:raw)  { "abc <def>" }
+      let(:inline_diffs) { [2..5] }
+      let(:subject) do
+        described_class.new(raw).mark(inline_diffs) do |text, left:, right:|
+          "LEFT#{text}RIGHT"
+        end
+      end
+
+      it 'marks the inline diffs' do
+        expect(subject).to eq(%{abLEFTc &lt;dRIGHTef&gt;})
+        expect(subject).to be_html_safe
+      end
+    end
+  end
+end
-- 
GitLab


From 1875fa71ca0eba9aba0bdde3bf004e310705b6e3 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 15:17:20 -0500
Subject: [PATCH 02/18] Add Gitlab::StringRegexMarker

---
 lib/gitlab/string_regex_marker.rb           | 13 +++++++++++++
 spec/lib/gitlab/string_range_marker_spec.rb |  4 ++--
 spec/lib/gitlab/string_regex_marker_spec.rb | 18 ++++++++++++++++++
 3 files changed, 33 insertions(+), 2 deletions(-)
 create mode 100644 lib/gitlab/string_regex_marker.rb
 create mode 100644 spec/lib/gitlab/string_regex_marker_spec.rb

diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb
new file mode 100644
index 00000000000..7ebf1c0428c
--- /dev/null
+++ b/lib/gitlab/string_regex_marker.rb
@@ -0,0 +1,13 @@
+module Gitlab
+  class StringRegexMarker < StringRangeMarker
+    def mark(regex, group: 0, &block)
+      regex_match = raw_line.match(regex)
+      return rich_line unless regex_match
+
+      begin_index, end_index = regex_match.offset(group)
+      name_range = begin_index..(end_index - 1)
+
+      super([name_range], &block)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
index 45b4d4af206..7c77772b3f6 100644
--- a/spec/lib/gitlab/string_range_marker_spec.rb
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::StringRangeMarker, lib: true do
       let(:raw)  { "abc <def>" }
       let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
       let(:inline_diffs) { [2..5] }
-      let(:subject) do
+      subject do
         described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:|
           "LEFT#{text}RIGHT"
         end
@@ -21,7 +21,7 @@ describe Gitlab::StringRangeMarker, lib: true do
     context "when the rich text is not html safe" do
       let(:raw)  { "abc <def>" }
       let(:inline_diffs) { [2..5] }
-      let(:subject) do
+      subject do
         described_class.new(raw).mark(inline_diffs) do |text, left:, right:|
           "LEFT#{text}RIGHT"
         end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
new file mode 100644
index 00000000000..2f5cf6c6e3b
--- /dev/null
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::StringRegexMarker, lib: true do
+  describe '#mark' do
+    let(:raw)  { %{"name": "AFNetworking"} }
+    let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
+    subject do
+      described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
+        %{<a href="#">#{text}</a>}
+      end
+    end
+
+    it 'marks the inline diffs' do
+      expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
+      expect(subject).to be_html_safe
+    end
+  end
+end
-- 
GitLab


From 05d6abbdaf41840375705dd1647c8f64eb59f465 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 16:13:06 -0500
Subject: [PATCH 03/18] Autolink package names in Gemfile

---
 lib/gitlab/dependency_linker.rb               | 20 ++++
 lib/gitlab/dependency_linker/base_linker.rb   | 96 +++++++++++++++++++
 .../dependency_linker/gemfile_linker.rb       | 25 +++++
 lib/gitlab/highlight.rb                       | 37 +++++--
 .../dependency_linker/gemfile_linker_spec.rb  | 59 ++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     | 13 +++
 spec/lib/gitlab/highlight_spec.rb             | 11 +++
 7 files changed, 251 insertions(+), 10 deletions(-)
 create mode 100644 lib/gitlab/dependency_linker.rb
 create mode 100644 lib/gitlab/dependency_linker/base_linker.rb
 create mode 100644 lib/gitlab/dependency_linker/gemfile_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
 create mode 100644 spec/lib/gitlab/dependency_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
new file mode 100644
index 00000000000..656023afb8e
--- /dev/null
+++ b/lib/gitlab/dependency_linker.rb
@@ -0,0 +1,20 @@
+module Gitlab
+  module DependencyLinker
+    LINKERS = [
+      GemfileLinker,
+    ]
+
+    def self.link(blob_name, plain_text, highlighted_text)
+      linker = linker(blob_name)
+      return highlighted_text unless linker
+
+      linker.link(plain_text, highlighted_text)
+    end
+
+    private
+
+    def self.linker(blob_name)
+      LINKERS.find { |linker| linker.support?(blob_name) }
+    end
+  end
+end
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
new file mode 100644
index 00000000000..defa5681c17
--- /dev/null
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -0,0 +1,96 @@
+module Gitlab
+  module DependencyLinker
+    class BaseLinker
+      def self.link(plain_text, highlighted_text)
+        new(plain_text, highlighted_text).link
+      end
+
+      attr_accessor :plain_text, :highlighted_text
+
+      def initialize(plain_text, highlighted_text)
+        @plain_text = plain_text
+        @highlighted_text = highlighted_text
+      end
+
+      def link
+        link_dependencies
+
+        highlighted_lines.join.html_safe
+      end
+
+      private
+
+      def package_url(name)
+        raise NotImplementedError
+      end
+
+      def link_dependencies
+        raise NotImplementedError
+      end
+
+      def package_link(name, url = package_url(name))
+        return name unless url
+
+        %{<a href="#{ERB::Util.html_escape_once(url)}">#{ERB::Util.html_escape_once(name)}</a>}
+      end
+
+      # Links package names in a method call or assignment string argument.
+      #
+      # Example:
+      #   link_method_call("gem")
+      #   # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"`
+      #
+      #   link_method_call("gem", "specific_package")
+      #   # Will link `specific_package` in `gem "specific_package"`
+      #
+      #   link_method_call("github", /[^\/]+\/[^\/]+/)
+      #   # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
+      #
+      #   link_method_call(%w[add_dependency add_development_dependency])
+      #   # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"`
+      #
+      #   link_method_call("name")
+      #   # Will link `package` in `self.name = "package"`
+      def link_method_call(method_names, value = nil, &url_proc)
+        value =
+          case value
+          when String
+            Regexp.escape(value)
+          when nil
+            %{[^'"]+}
+          else
+            value
+          end
+
+        method_names = Array(method_names).map { |name| Regexp.escape(name) }
+        link_regex(/#{Regexp.union(method_names)}\s*[(=]?\s*['"](?<name>#{value})['"]/, &url_proc)
+      end
+
+      # Links package names based on regex.
+      #
+      # Example:
+      #   link_regex(/(github:|:github =>)\s*['"](?<name>[^'"]+)['"]/)
+      #   # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"`
+      def link_regex(regex)
+        highlighted_lines.map!.with_index do |rich_line, i|
+          marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe)
+
+          marked_line = marker.mark(regex, group: :name) do |text, left:, right:|
+            url = block_given? ? yield(text) : package_url(text)
+            package_link(text, url)
+          end
+
+          marked_line
+        end
+      end
+
+      def plain_lines
+        @plain_lines ||= plain_text.lines
+      end
+
+      def highlighted_lines
+        @highlighted_lines ||= highlighted_text.lines
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb
new file mode 100644
index 00000000000..1e0d08f2291
--- /dev/null
+++ b/lib/gitlab/dependency_linker/gemfile_linker.rb
@@ -0,0 +1,25 @@
+module Gitlab
+  module DependencyLinker
+    class GemfileLinker < BaseLinker
+      def self.support?(blob_name)
+        blob_name == 'Gemfile' || blob_name == 'gems.rb'
+      end
+
+      private
+
+      def link_dependencies
+        # Link `gem "package_name"` to https://rubygems.org/gems/package_name
+        link_method_call("gem")
+
+        # Link `github: "user/repo"` to https://github.com/user/repo
+        link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/) do |name|
+          "https://github.com/#{name}"
+        end
+      end
+
+      def package_url(name)
+        "https://rubygems.org/gems/#{name}"
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index d787d5db4a0..83bc230df3e 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -13,6 +13,8 @@ module Gitlab
       highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe)
     end
 
+    attr_reader :blob_name
+
     def initialize(blob_name, blob_content, repository: nil)
       @formatter = Rouge::Formatters::HTMLGitlab
       @repository = repository
@@ -21,16 +23,9 @@ module Gitlab
     end
 
     def highlight(text, continue: true, plain: false)
-      if plain
-        hl_lexer = Rouge::Lexers::PlainText
-        continue = false
-      else
-        hl_lexer = self.lexer
-      end
-
-      @formatter.format(hl_lexer.lex(text, continue: continue), tag: hl_lexer.tag).html_safe
-    rescue
-      @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+      highlighted_text = highlight_text(text, continue: continue, plain: plain)
+      highlighted_text = link_dependencies(text, highlighted_text) if blob_name
+      highlighted_text
     end
 
     def lexer
@@ -50,5 +45,27 @@ module Gitlab
 
       Rouge::Lexer.find_fancy(language_name)
     end
+
+    def highlight_text(text, continue: true, plain: false)
+      if plain
+        highlight_plain(text)
+      else
+        highlight_rich(text, continue: continue)
+      end
+    end
+
+    def highlight_plain(text)
+      @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+    end
+
+    def highlight_rich(text, continue: true)
+      @formatter.format(lexer.lex(text, continue: continue), tag: lexer.tag).html_safe
+    rescue
+      highlight_plain(text)
+    end
+
+    def link_dependencies(text, highlighted_text)
+      Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
+    end
   end
 end
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
new file mode 100644
index 00000000000..8f9d995fef6
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -0,0 +1,59 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GemfileLinker, lib: true do
+  describe '.support?' do
+    it 'supports Gemfile' do
+      expect(described_class.support?('Gemfile')).to be_truthy
+    end
+
+    it 'supports gems.rb' do
+      expect(described_class.support?('gems.rb')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('Gemfile.lock')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "Gemfile" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        source 'https://rubygems.org'
+
+        gem "rails", '4.2.6', github: "rails/rails"
+        gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+
+        # Responders respond_to and respond_with
+        gem 'responders', '~> 2.0', :github => 'rails/responders'
+
+        # Specify a sprockets version due to increased performance
+        # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
+        gem 'sprockets', '~> 3.6.0'
+
+        # Default values for AR models
+        gem 'default_value_for', '~> 3.0.0'
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("rails", "https://rubygems.org/gems/rails"))
+      expect(subject).to include(link("rails-deprecated_sanitizer", "https://rubygems.org/gems/rails-deprecated_sanitizer"))
+      expect(subject).to include(link("responders", "https://rubygems.org/gems/responders"))
+      expect(subject).to include(link("sprockets", "https://rubygems.org/gems/sprockets"))
+      expect(subject).to include(link("default_value_for", "https://rubygems.org/gems/default_value_for"))
+    end
+
+    it "links GitHub repos" do
+      expect(subject).to include(link("rails/rails", "https://github.com/rails/rails"))
+      expect(subject).to include(link("rails/responders", "https://github.com/rails/responders"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
new file mode 100644
index 00000000000..03d5b61d70c
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -0,0 +1,13 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker, lib: true do
+  describe '.link' do
+    it 'links using GemfileLinker' do
+      blob_name = 'Gemfile'
+
+      expect(described_class::GemfileLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index e49799ad105..e57b3053871 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -57,4 +57,15 @@ describe Gitlab::Highlight, lib: true do
       end
     end
   end
+
+  describe '#highlight' do
+    subject { described_class.highlight(file_name, file_content, nowrap: false) }
+
+    it 'links dependencies via DependencyLinker' do
+      expect(Gitlab::DependencyLinker).to receive(:link).
+        with('file.name', 'Contents', anything).and_call_original
+
+      described_class.highlight('file.name', 'Contents')
+    end
+  end
 end
-- 
GitLab


From 9ec8d1b3eb5a2be8135764f5d4cce8e8597ee6e2 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 17:40:33 -0500
Subject: [PATCH 04/18] Autolink package names in gemspec

---
 lib/gitlab/dependency_linker.rb               |  1 +
 lib/gitlab/dependency_linker/base_linker.rb   |  4 ++
 .../dependency_linker/gemspec_linker.rb       | 16 +++++
 .../dependency_linker/gemspec_linker_spec.rb  | 63 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++
 5 files changed, 92 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/gemspec_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 656023afb8e..4e6e2f9df96 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -2,6 +2,7 @@ module Gitlab
   module DependencyLinker
     LINKERS = [
       GemfileLinker,
+      GemspecLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index defa5681c17..027eb88e259 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -28,6 +28,10 @@ module Gitlab
         raise NotImplementedError
       end
 
+      def license_url(name)
+        "http://spdx.org/licenses/#{name}.html" if name =~ /[A-Za-z0-9.-]+/
+      end
+
       def package_link(name, url = package_url(name))
         return name unless url
 
diff --git a/lib/gitlab/dependency_linker/gemspec_linker.rb b/lib/gitlab/dependency_linker/gemspec_linker.rb
new file mode 100644
index 00000000000..cf8acc5b004
--- /dev/null
+++ b/lib/gitlab/dependency_linker/gemspec_linker.rb
@@ -0,0 +1,16 @@
+module Gitlab
+  module DependencyLinker
+    class GemspecLinker < GemfileLinker
+      def self.support?(blob_name)
+        blob_name.end_with?('.gemspec')
+      end
+
+      private
+
+      def link_dependencies
+        link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency])
+        link_method_call("license", &method(:license_url))
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
new file mode 100644
index 00000000000..a60fda533d3
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -0,0 +1,63 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GemspecLinker, lib: true do
+  describe '.support?' do
+    it 'supports *.gemspec' do
+      expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('.gemspec.example')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "gitlab_git.gemspec" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        Gem::Specification.new do |s|
+          s.name        = 'gitlab_git'
+          s.version     = `cat VERSION`
+          s.date        = Time.now.strftime("%Y-%m-%d")
+          s.summary     = "Gitlab::Git library"
+          s.description = "GitLab wrapper around git objects"
+          s.authors     = ["Dmitriy Zaporozhets"]
+          s.email       = 'dmitriy.zaporozhets@gmail.com'
+          s.license     = 'MIT'
+          s.files       = `git ls-files lib/`.split("\n") << 'VERSION'
+          s.homepage    =
+            'https://gitlab.com/gitlab-org/gitlab_git'
+
+          s.add_dependency("github-linguist", "~> 4.7.0")
+          s.add_dependency("activesupport", "~> 4.0")
+          s.add_dependency("rugged", "~> 0.24.0")
+          s.add_runtime_dependency("charlock_holmes", "~> 0.7.3")
+          s.add_development_dependency("listen", "~> 3.0.6")
+        end
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links the gem name" do
+      expect(subject).to include(link("gitlab_git", "https://rubygems.org/gems/gitlab_git"))
+    end
+
+    it "links the license" do
+      expect(subject).to include(link("MIT", "http://spdx.org/licenses/MIT.html"))
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("github-linguist", "https://rubygems.org/gems/github-linguist"))
+      expect(subject).to include(link("activesupport", "https://rubygems.org/gems/activesupport"))
+      expect(subject).to include(link("rugged", "https://rubygems.org/gems/rugged"))
+      expect(subject).to include(link("charlock_holmes", "https://rubygems.org/gems/charlock_holmes"))
+      expect(subject).to include(link("listen", "https://rubygems.org/gems/listen"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 03d5b61d70c..7ba987026a4 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -9,5 +9,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using GemspecLinker' do
+      blob_name = 'gitlab_git.gemspec'
+
+      expect(described_class::GemspecLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From b9f21795bc69a9a4882c6a0668fbbb3957feec84 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 17:41:25 -0500
Subject: [PATCH 05/18] Autolink package names in package.json

---
 lib/gitlab/dependency_linker.rb               |  1 +
 lib/gitlab/dependency_linker/base_linker.rb   | 44 ++++++++++++
 lib/gitlab/dependency_linker/json_linker.rb   | 17 +++++
 .../dependency_linker/package_json_linker.rb  | 35 ++++++++++
 .../package_json_linker_spec.rb               | 70 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++
 6 files changed, 175 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/json_linker.rb
 create mode 100644 lib/gitlab/dependency_linker/package_json_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 4e6e2f9df96..deb2950eea2 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -3,6 +3,7 @@ module Gitlab
     LINKERS = [
       GemfileLinker,
       GemspecLinker,
+      PackageJsonLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 027eb88e259..bb589041d4d 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -70,6 +70,50 @@ module Gitlab
         link_regex(/#{Regexp.union(method_names)}\s*[(=]?\s*['"](?<name>#{value})['"]/, &url_proc)
       end
 
+      # Links package names in a JSON key or values.
+      #
+      # Example:
+      #   link_json("name")
+      #   # Will link `package` in `"name": "package"`
+      #
+      #   link_json("name", "specific_package")
+      #   # Will link `specific_package` in `"name": "specific_package"`
+      #
+      #   link_json("name", /[^\/]+\/[^\/]+/)
+      #   # Will link `user/repo` in `"name": "user/repo"`, but not `"name": "package"`
+      #
+      #   link_json("specific_package", "1.0.1", package: :key)
+      #   # Will link `specific_package` in `"specific_package": "1.0.1"`
+      def link_json(key, value = nil, package: :value, &url_proc)
+        key =
+          case key
+          when String
+            Regexp.escape(key)
+          when nil
+            '[^"]+'
+          else
+            key
+          end
+
+        value =
+          case value
+          when String
+            Regexp.escape(value)
+          when nil
+            '[^"]+'
+          else
+            value
+          end
+
+        if package == :value
+          value = "(?<name>#{value})"
+        else
+          key = "(?<name>#{key})"
+        end
+
+        link_regex(/"#{key}":\s*"#{value}"/, &url_proc)
+      end
+
       # Links package names based on regex.
       #
       # Example:
diff --git a/lib/gitlab/dependency_linker/json_linker.rb b/lib/gitlab/dependency_linker/json_linker.rb
new file mode 100644
index 00000000000..4225cfe4126
--- /dev/null
+++ b/lib/gitlab/dependency_linker/json_linker.rb
@@ -0,0 +1,17 @@
+module Gitlab
+  module DependencyLinker
+    class JsonLinker < BaseLinker
+      def link
+        return highlighted_text unless json
+
+        super
+      end
+
+      private
+
+      def json
+        @json ||= JSON.parse(plain_text) rescue nil
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/dependency_linker/package_json_linker.rb b/lib/gitlab/dependency_linker/package_json_linker.rb
new file mode 100644
index 00000000000..7c5f3b97d88
--- /dev/null
+++ b/lib/gitlab/dependency_linker/package_json_linker.rb
@@ -0,0 +1,35 @@
+module Gitlab
+  module DependencyLinker
+    class PackageJsonLinker < JsonLinker
+      def self.support?(blob_name)
+        blob_name == 'package.json'
+      end
+
+      private
+
+      def link_dependencies
+        link_json("name", json["name"])
+        link_json("license", &method(:license_url))
+
+        link_dependencies_at_key("dependencies")
+        link_dependencies_at_key("devDependencies")
+      end
+
+      def link_dependencies_at_key(key)
+        dependencies = json[key]
+        return unless dependencies
+
+        dependencies.each do |name, version|
+          link_json(name, version, package: :key)
+          link_json(name, /[^\/"]+\/[^\/"]+/) do |name|
+            "https://github.com/#{name}"
+          end
+        end
+      end
+
+      def package_url(name)
+        "https://npmjs.com/package/#{name}"
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
new file mode 100644
index 00000000000..49d917ff80b
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -0,0 +1,70 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PackageJsonLinker, lib: true do
+  describe '.support?' do
+    it 'supports package.json' do
+      expect(described_class.support?('package.json')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('package.json.example')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "package.json" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        {
+          "name": "module-name",
+          "version": "10.3.1",
+          "dependencies": {
+            "primus": "*",
+            "async": "~0.8.0",
+            "express": "4.2.x",
+            "winston": "git://github.com/flatiron/winston#master",
+            "bigpipe": "bigpipe/pagelet",
+            "plates": "https://github.com/flatiron/plates/tarball/master"
+          },
+          "devDependencies": {
+            "vows": "^0.7.0",
+            "assume": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
+            "pre-commit": "*"
+          },
+          "license": "MIT"
+        }
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links the module name" do
+      expect(subject).to include(link("module-name", "https://npmjs.com/package/module-name"))
+    end
+
+    it "links the license" do
+      expect(subject).to include(link("MIT", "http://spdx.org/licenses/MIT.html"))
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("primus", "https://npmjs.com/package/primus"))
+      expect(subject).to include(link("async", "https://npmjs.com/package/async"))
+      expect(subject).to include(link("express", "https://npmjs.com/package/express"))
+      expect(subject).to include(link("winston", "https://npmjs.com/package/winston"))
+      expect(subject).to include(link("bigpipe", "https://npmjs.com/package/bigpipe"))
+      expect(subject).to include(link("plates", "https://npmjs.com/package/plates"))
+      expect(subject).to include(link("vows", "https://npmjs.com/package/vows"))
+      expect(subject).to include(link("assume", "https://npmjs.com/package/assume"))
+      expect(subject).to include(link("pre-commit", "https://npmjs.com/package/pre-commit"))
+    end
+
+    it "links GitHub repos" do
+      expect(subject).to include(link("bigpipe/pagelet", "https://github.com/bigpipe/pagelet"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 7ba987026a4..53960b0a662 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -17,5 +17,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using PackageJsonLinker' do
+      blob_name = 'package.json'
+
+      expect(described_class::PackageJsonLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 502ef3cad4e84e5ef345b8433fc718007bbca450 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 17:45:31 -0500
Subject: [PATCH 06/18] Autolink package names in composer.json

---
 lib/gitlab/dependency_linker.rb               |  1 +
 .../dependency_linker/composer_json_linker.rb | 23 +++++++
 .../composer_json_linker_spec.rb              | 67 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++
 4 files changed, 99 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/composer_json_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index deb2950eea2..75a0e508515 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -4,6 +4,7 @@ module Gitlab
       GemfileLinker,
       GemspecLinker,
       PackageJsonLinker,
+      ComposerJsonLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb
new file mode 100644
index 00000000000..4c8a80a29ca
--- /dev/null
+++ b/lib/gitlab/dependency_linker/composer_json_linker.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module DependencyLinker
+    class ComposerJsonLinker < PackageJsonLinker
+      def self.support?(blob_name)
+        blob_name == 'composer.json'
+      end
+
+      private
+
+      def link_dependencies
+        link_json("name", json["name"])
+        link_json("license", &method(:license_url))
+
+        link_dependencies_at_key("require")
+        link_dependencies_at_key("require-dev")
+      end
+
+      def package_url(name)
+        "https://packagist.org/packages/#{name}" if name =~ /\A[^\/]+\/[^\/]+\z/
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
new file mode 100644
index 00000000000..92bdf0fb370
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -0,0 +1,67 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::ComposerJsonLinker, lib: true do
+  describe '.support?' do
+    it 'supports composer.json' do
+      expect(described_class.support?('composer.json')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('composer.json.example')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "composer.json" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        {
+            "name": "laravel/laravel",
+            "description": "The Laravel Framework.",
+            "keywords": ["framework", "laravel"],
+            "license": "MIT",
+            "type": "project",
+            "require": {
+                "php": ">=5.5.9",
+                "laravel/framework": "5.2.*"
+            },
+            "require-dev": {
+                "fzaninotto/faker": "~1.4",
+                "mockery/mockery": "0.9.*",
+                "phpunit/phpunit": "~4.0",
+                "symfony/css-selector": "2.8.*|3.0.*",
+                "symfony/dom-crawler": "2.8.*|3.0.*"
+            }
+        }
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links the module name" do
+      expect(subject).to include(link("laravel/laravel", "https://packagist.org/packages/laravel/laravel"))
+    end
+
+    it "links the license" do
+      expect(subject).to include(link("MIT", "http://spdx.org/licenses/MIT.html"))
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("laravel/framework", "https://packagist.org/packages/laravel/framework"))
+      expect(subject).to include(link("fzaninotto/faker", "https://packagist.org/packages/fzaninotto/faker"))
+      expect(subject).to include(link("mockery/mockery", "https://packagist.org/packages/mockery/mockery"))
+      expect(subject).to include(link("phpunit/phpunit", "https://packagist.org/packages/phpunit/phpunit"))
+      expect(subject).to include(link("symfony/css-selector", "https://packagist.org/packages/symfony/css-selector"))
+      expect(subject).to include(link("symfony/dom-crawler", "https://packagist.org/packages/symfony/dom-crawler"))
+    end
+
+    it "doesn't link core dependencies" do
+      expect(subject).not_to include(link("php", "https://packagist.org/packages/php"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 53960b0a662..7493e747e01 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -25,5 +25,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using ComposerJsonLinker' do
+      blob_name = 'composer.json'
+
+      expect(described_class::ComposerJsonLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 40f0c0d24ab5b1a375526b2b066d0fc231f76c1c Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 17:50:47 -0500
Subject: [PATCH 07/18] Autolink package names in Podfile

---
 lib/gitlab/dependency_linker.rb               |  1 +
 .../dependency_linker/podfile_linker.rb       | 20 +++++++++++
 .../dependency_linker/podfile_linker_spec.rb  | 36 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++++
 4 files changed, 65 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/podfile_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 75a0e508515..e6e6184d428 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -5,6 +5,7 @@ module Gitlab
       GemspecLinker,
       PackageJsonLinker,
       ComposerJsonLinker,
+      PodfileLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/podfile_linker.rb b/lib/gitlab/dependency_linker/podfile_linker.rb
new file mode 100644
index 00000000000..6716d5bdfd4
--- /dev/null
+++ b/lib/gitlab/dependency_linker/podfile_linker.rb
@@ -0,0 +1,20 @@
+module Gitlab
+  module DependencyLinker
+    class PodfileLinker < BaseLinker
+      def self.support?(blob_name)
+        blob_name == 'Podfile'
+      end
+
+      private
+
+      def link_dependencies
+        link_method_call("pod")
+      end
+
+      def package_url(name)
+        package = name.split("/", 2).first
+        "https://cocoapods.org/pods/#{package}"
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
new file mode 100644
index 00000000000..049306f9bd9
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodfileLinker, lib: true do
+  describe '.support?' do
+    it 'supports Podfile' do
+      expect(described_class.support?('Podfile')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('Podfile.lock')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "Podfile" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        target 'MyApp'
+        pod 'AFNetworking', '~> 1.0'
+        pod "RestKit/CoreData"
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("AFNetworking", "https://cocoapods.org/pods/AFNetworking"))
+      expect(subject).to include(link("RestKit/CoreData", "https://cocoapods.org/pods/RestKit"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 7493e747e01..104e3a32e6c 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -33,5 +33,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using PodfileLinker' do
+      blob_name = 'Podfile'
+
+      expect(described_class::PodfileLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 1d138bd0fd09530842bb10ca5084e3c47ca0f732 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 17:54:52 -0500
Subject: [PATCH 08/18] Autolink package names in podspec

---
 lib/gitlab/dependency_linker.rb               |  1 +
 .../dependency_linker/podspec_linker.rb       | 33 ++++++++++
 .../dependency_linker/podspec_linker_spec.rb  | 61 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++
 4 files changed, 103 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/podspec_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index e6e6184d428..4152c230e60 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -6,6 +6,7 @@ module Gitlab
       PackageJsonLinker,
       ComposerJsonLinker,
       PodfileLinker,
+      PodspecLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb
new file mode 100644
index 00000000000..fd4b17e39bb
--- /dev/null
+++ b/lib/gitlab/dependency_linker/podspec_linker.rb
@@ -0,0 +1,33 @@
+module Gitlab
+  module DependencyLinker
+    class PodspecLinker < PodfileLinker
+      def self.support?(blob_name)
+        blob_name.end_with?('.podspec')
+      end
+
+      private
+
+      def link_dependencies
+        link_method_call(%w[name dependency])
+
+        license_regex = %r{
+          license
+          \s*
+          =
+          \s*
+          (?:
+              # spec.license = 'MIT'
+              ['"](?<name>[^'"]+)['"]
+            |
+              # spec.license = { :type => 'MIT' }
+              \{\s*:type\s*=>\s*['"](?<name>[^'"]+)['"]
+            |
+              # spec.license = { type: 'MIT' }
+              \{\s*type:\s*['"](?<name>[^'"]+)['"]
+          )
+        }x
+        link_regex(license_regex, &method(:license_url))
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
new file mode 100644
index 00000000000..597a6393c18
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -0,0 +1,61 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodspecLinker, lib: true do
+  describe '.support?' do
+    it 'supports *.podspec' do
+      expect(described_class.support?('Reachability.podspec')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('.podspec.example')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "Reachability.podspec" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        Pod::Spec.new do |spec|
+          spec.name         = 'Reachability'
+          spec.version      = '3.1.0'
+          spec.license      = { :type => 'BSD' }
+          spec.license      = "MIT"
+          spec.license      = { type: 'Apache-1.0' }
+          spec.homepage     = 'https://github.com/tonymillion/Reachability'
+          spec.authors      = { 'Tony Million' => 'tonymillion@gmail.com' }
+          spec.summary      = 'ARC and GCD Compatible Reachability Class for iOS and OS X.'
+          spec.source       = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0' }
+          spec.source_files = 'Reachability.{h,m}'
+          spec.framework    = 'SystemConfiguration'
+
+          spec.dependency 'AFNetworking', '~> 1.0'
+          spec.dependency 'RestKit/CoreData', '~> 0.20.0'
+          spec.ios.dependency 'MBProgressHUD', '~> 0.5'
+        end
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links the gem name" do
+      expect(subject).to include(link("Reachability", "https://cocoapods.org/pods/Reachability"))
+    end
+
+    it "links the license" do
+      expect(subject).to include(link("BSD", "http://spdx.org/licenses/BSD.html"))
+      expect(subject).to include(link("MIT", "http://spdx.org/licenses/MIT.html"))
+      expect(subject).to include(link("Apache-1.0", "http://spdx.org/licenses/Apache-1.0.html"))
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("AFNetworking", "https://cocoapods.org/pods/AFNetworking"))
+      expect(subject).to include(link("RestKit/CoreData", "https://cocoapods.org/pods/RestKit"))
+      expect(subject).to include(link("MBProgressHUD", "https://cocoapods.org/pods/MBProgressHUD"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 104e3a32e6c..b6ec756adeb 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -41,5 +41,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using PodspecLinker' do
+      blob_name = 'Reachability.podspec'
+
+      expect(described_class::PodspecLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 0e0d6c70b8b33e171dc2ec528a4b7c7fbec7a5e2 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 18:04:47 -0500
Subject: [PATCH 09/18] Autolink package names in podspec.json

---
 lib/gitlab/dependency_linker.rb               |  1 +
 .../dependency_linker/podspec_json_linker.rb  | 39 ++++++++
 .../podspec_json_linker_spec.rb               | 88 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 ++
 4 files changed, 136 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/podspec_json_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 4152c230e60..0868d6be552 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -7,6 +7,7 @@ module Gitlab
       ComposerJsonLinker,
       PodfileLinker,
       PodspecLinker,
+      PodspecJsonLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/podspec_json_linker.rb b/lib/gitlab/dependency_linker/podspec_json_linker.rb
new file mode 100644
index 00000000000..825c548c171
--- /dev/null
+++ b/lib/gitlab/dependency_linker/podspec_json_linker.rb
@@ -0,0 +1,39 @@
+module Gitlab
+  module DependencyLinker
+    class PodspecJsonLinker < JsonLinker
+      def self.support?(blob_name)
+        blob_name.end_with?('.podspec.json')
+      end
+
+      private
+
+      def link_dependencies
+        link_json("name", json["name"])
+        link_json("license", &method(:license_url))
+
+        link_dependencies_at_key("dependencies")
+
+        subspecs = json["subspecs"]
+        if subspecs
+          subspecs.each do |subspec|
+            link_dependencies_at_key("dependencies", subspec)
+          end
+        end
+      end
+
+      def link_dependencies_at_key(key, root = json)
+        dependencies = root[key]
+        return unless dependencies
+
+        dependencies.each do |name, _|
+          link_regex(/"(?<name>#{Regexp.escape(name)})":\s*\[/)
+        end
+      end
+
+      def package_url(name)
+        package = name.split("/", 2).first
+        "https://cocoapods.org/pods/#{package}"
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
new file mode 100644
index 00000000000..a508def683f
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -0,0 +1,88 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodspecJsonLinker, lib: true do
+  describe '.support?' do
+    it 'supports *.podspec.json' do
+      expect(described_class.support?('Reachability.podspec.json')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('.podspec.json.example')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "AFNetworking.podspec.json" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        {
+          "name": "AFNetworking",
+          "version": "2.0.0",
+          "license": "MIT",
+          "summary": "A delightful iOS and OS X networking framework.",
+          "homepage": "https://github.com/AFNetworking/AFNetworking",
+          "authors": {
+            "Mattt Thompson": "m@mattt.me"
+          },
+          "source": {
+            "git": "https://github.com/AFNetworking/AFNetworking.git",
+            "tag": "2.0.0",
+            "submodules": true
+          },
+          "requires_arc": true,
+          "platforms": {
+            "ios": "6.0",
+            "osx": "10.8"
+          },
+          "public_header_files": "AFNetworking/*.h",
+          "subspecs": [
+            {
+              "name": "NSURLConnection",
+              "dependencies": {
+                "AFNetworking/Serialization": [
+
+                ],
+                "AFNetworking/Reachability": [
+
+                ],
+                "AFNetworking/Security": [
+
+                ]
+              },
+              "source_files": [
+                "AFNetworking/AFURLConnectionOperation.{h,m}",
+                "AFNetworking/AFHTTPRequestOperation.{h,m}",
+                "AFNetworking/AFHTTPRequestOperationManager.{h,m}"
+              ]
+            }
+          ]
+        }
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links the gem name" do
+      expect(subject).to include(link("AFNetworking", "https://cocoapods.org/pods/AFNetworking"))
+    end
+
+    it "links the license" do
+      expect(subject).to include(link("MIT", "http://spdx.org/licenses/MIT.html"))
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("AFNetworking/Serialization", "https://cocoapods.org/pods/AFNetworking"))
+      expect(subject).to include(link("AFNetworking/Reachability", "https://cocoapods.org/pods/AFNetworking"))
+      expect(subject).to include(link("AFNetworking/Security", "https://cocoapods.org/pods/AFNetworking"))
+    end
+
+    it "doesn't link subspec names" do
+      expect(subject).not_to include(link("NSURLConnection", "https://cocoapods.org/pods/NSURLConnection"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index b6ec756adeb..9fc539b604a 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -49,5 +49,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using PodspecJsonLinker' do
+      blob_name = 'AFNetworking.podspec.json'
+
+      expect(described_class::PodspecJsonLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 2defe9ec45f5caa777086e13ef6bc2348dd328ae Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 18:24:08 -0500
Subject: [PATCH 10/18] Autolink package names in Cartfile

---
 lib/gitlab/dependency_linker.rb               |  1 +
 lib/gitlab/dependency_linker/base_linker.rb   |  2 +-
 .../dependency_linker/cartfile_linker.rb      | 19 ++++++
 .../dependency_linker/cartfile_linker_spec.rb | 62 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++
 5 files changed, 91 insertions(+), 1 deletion(-)
 create mode 100644 lib/gitlab/dependency_linker/cartfile_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 0868d6be552..f45c22a682b 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -8,6 +8,7 @@ module Gitlab
       PodfileLinker,
       PodspecLinker,
       PodspecJsonLinker,
+      CartfileLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index bb589041d4d..d5449eafc40 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -47,7 +47,7 @@ module Gitlab
       #   link_method_call("gem", "specific_package")
       #   # Will link `specific_package` in `gem "specific_package"`
       #
-      #   link_method_call("github", /[^\/]+\/[^\/]+/)
+      #   link_method_call("github", /[^\/"]+\/[^\/"]+/)
       #   # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
       #
       #   link_method_call(%w[add_dependency add_development_dependency])
diff --git a/lib/gitlab/dependency_linker/cartfile_linker.rb b/lib/gitlab/dependency_linker/cartfile_linker.rb
new file mode 100644
index 00000000000..9f7d10b5974
--- /dev/null
+++ b/lib/gitlab/dependency_linker/cartfile_linker.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module DependencyLinker
+    class CartfileLinker < BaseLinker
+      def self.support?(blob_name)
+        blob_name.start_with?('Cartfile')
+      end
+
+      private
+
+      def link_dependencies
+        link_method_call("github", /[^\/"]+\/[^\/"]+/)
+      end
+
+      def package_url(name)
+        "https://github.com/#{name}"
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
new file mode 100644
index 00000000000..d562f81fab8
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -0,0 +1,62 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::CartfileLinker, lib: true do
+  describe '.support?' do
+    it 'supports Cartfile' do
+      expect(described_class.support?('Cartfile')).to be_truthy
+    end
+
+    it 'supports Cartfile.private' do
+      expect(described_class.support?('Cartfile.private')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('test.Cartfile')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "Cartfile" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        # Require version 2.3.1 or later
+        github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1
+
+        # Require version 1.x
+        github "Mantle/Mantle" ~> 1.0    # (1.0 or later, but less than 2.0)
+
+        # Require exactly version 0.4.1
+        github "jspahrsummers/libextobjc" == 0.4.1
+
+        # Use the latest version
+        github "jspahrsummers/xcconfigs"
+
+        # Use the branch
+        github "jspahrsummers/xcconfigs" "branch"
+
+        # Use a project from GitHub Enterprise
+        github "https://enterprise.local/ghe/desktop/git-error-translations"
+
+        # Use a project from any arbitrary server, on the "development" branch
+        git "https://enterprise.local/desktop/git-error-translations2.git" "development"
+
+        # Use a local project
+        git "file:///directory/to/project" "branch"
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("ReactiveCocoa/ReactiveCocoa", "https://github.com/ReactiveCocoa/ReactiveCocoa"))
+      expect(subject).to include(link("Mantle/Mantle", "https://github.com/Mantle/Mantle"))
+      expect(subject).to include(link("jspahrsummers/libextobjc", "https://github.com/jspahrsummers/libextobjc"))
+      expect(subject).to include(link("jspahrsummers/xcconfigs", "https://github.com/jspahrsummers/xcconfigs"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 9fc539b604a..f27f633a62c 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -57,5 +57,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using CartfileLinker' do
+      blob_name = 'Cartfile'
+
+      expect(described_class::CartfileLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From bcbefb9d2936287cd3c1ea7f4a537ea529455fe2 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 18:30:30 -0500
Subject: [PATCH 11/18] Autolink package names in Godeps.json

---
 lib/gitlab/dependency_linker.rb               |  1 +
 .../dependency_linker/godeps_json_linker.rb   | 23 +++++++
 .../godeps_json_linker_spec.rb                | 67 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 +++
 4 files changed, 99 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/godeps_json_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index f45c22a682b..37923dcc017 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -9,6 +9,7 @@ module Gitlab
       PodspecLinker,
       PodspecJsonLinker,
       CartfileLinker,
+      GodepsJsonLinker,
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/godeps_json_linker.rb b/lib/gitlab/dependency_linker/godeps_json_linker.rb
new file mode 100644
index 00000000000..227f7439fa0
--- /dev/null
+++ b/lib/gitlab/dependency_linker/godeps_json_linker.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module DependencyLinker
+    class GodepsJsonLinker < BaseLinker
+      def self.support?(blob_name)
+        blob_name == 'Godeps.json'
+      end
+
+      private
+
+      def link_dependencies
+        link_json("ImportPath")
+      end
+
+      def package_url(name)
+        if name =~ /\A(?<repo>git(lab|hub)\.com\/[^\/]+\/[^\/]+)\/(?<path>.+)\z/
+          "http://#{$~[:repo]}/tree/master/#{$~[:path]}"
+        else
+          "http://#{name}"
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
new file mode 100644
index 00000000000..a9ae721f05e
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -0,0 +1,67 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GodepsJsonLinker, lib: true do
+  describe '.support?' do
+    it 'supports Godeps.json' do
+      expect(described_class.support?('Godeps.json')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('Godeps.json.example')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "Godeps.json" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        {
+        	"ImportPath": "gitlab.com/gitlab-org/gitlab-pages",
+        	"GoVersion": "go1.5",
+        	"Packages": [
+        		"./..."
+        	],
+        	"Deps": [
+        		{
+        			"ImportPath": "github.com/kardianos/osext",
+        			"Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
+        		},
+        		{
+        			"ImportPath": "github.com/stretchr/testify/assert",
+        			"Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+        		},
+        		{
+        			"ImportPath": "github.com/stretchr/testify/require",
+        			"Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+        		},
+        		{
+        			"ImportPath": "golang.org/x/crypto/ssh/terminal",
+        			"Rev": "1351f936d976c60a0a48d728281922cf63eafb8d"
+        		},
+        		{
+        			"ImportPath": "golang.org/x/net/http2",
+        			"Rev": "b4e17d61b15679caf2335da776c614169a1b4643"
+        		}
+        	]
+        }
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links the package name" do
+      expect(subject).to include(link("gitlab.com/gitlab-org/gitlab-pages", "http://gitlab.com/gitlab-org/gitlab-pages"))
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("github.com/kardianos/osext", "http://github.com/kardianos/osext"))
+      expect(subject).to include(link("github.com/stretchr/testify/assert", "http://github.com/stretchr/testify/tree/master/assert"))
+      expect(subject).to include(link("github.com/stretchr/testify/require", "http://github.com/stretchr/testify/tree/master/require"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index f27f633a62c..15a8bf6a426 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -65,5 +65,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using GodepsJsonLinker' do
+      blob_name = 'Godeps.json'
+
+      expect(described_class::GodepsJsonLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 4b1b42800f71cde2224db1c00ef8fec53c6ff99b Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 18:58:55 -0500
Subject: [PATCH 12/18] Autolink package names in requirements.txt

---
 lib/gitlab/dependency_linker.rb               |  1 +
 .../requirements_txt_linker.rb                | 19 +++++
 .../requirements_txt_linker_spec.rb           | 83 +++++++++++++++++++
 spec/lib/gitlab/dependency_linker_spec.rb     |  8 ++
 4 files changed, 111 insertions(+)
 create mode 100644 lib/gitlab/dependency_linker/requirements_txt_linker.rb
 create mode 100644 spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb

diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 37923dcc017..57fc3c6e0e5 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -10,6 +10,7 @@ module Gitlab
       PodspecJsonLinker,
       CartfileLinker,
       GodepsJsonLinker,
+      RequirementsTxtLinker
     ]
 
     def self.link(blob_name, plain_text, highlighted_text)
diff --git a/lib/gitlab/dependency_linker/requirements_txt_linker.rb b/lib/gitlab/dependency_linker/requirements_txt_linker.rb
new file mode 100644
index 00000000000..92fcb8177ea
--- /dev/null
+++ b/lib/gitlab/dependency_linker/requirements_txt_linker.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module DependencyLinker
+    class RequirementsTxtLinker < BaseLinker
+      def self.support?(blob_name)
+        blob_name.end_with?('requirements.txt')
+      end
+
+      private
+
+      def link_dependencies
+        link_regex(/(?<name>^(?![a-z+]+:)[^#.-][^ ><=;\[]+)/)
+      end
+
+      def package_url(name)
+        "https://pypi.python.org/pypi/#{name}"
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
new file mode 100644
index 00000000000..a334bb38db1
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -0,0 +1,83 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
+  describe '.support?' do
+    it 'supports requirements.txt' do
+      expect(described_class.support?('requirements.txt')).to be_truthy
+    end
+
+    it 'supports doc-requirements.txt' do
+      expect(described_class.support?('doc-requirements.txt')).to be_truthy
+    end
+
+    it 'does not support other files' do
+      expect(described_class.support?('requirements')).to be_falsey
+    end
+  end
+
+  describe '#link' do
+    let(:file_name) { "requirements.txt" }
+
+    let(:file_content) do
+      <<-CONTENT.strip_heredoc
+        #
+        ####### example-requirements.txt #######
+        #
+        ###### Requirements without Version Specifiers ######
+        nose
+        nose-cov
+        beautifulsoup4
+        #
+        ###### Requirements with Version Specifiers ######
+        #   See https://www.python.org/dev/peps/pep-0440/#version-specifiers
+        docopt == 0.6.1             # Version Matching. Must be version 0.6.1
+        keyring >= 4.1.1            # Minimum version 4.1.1
+        coverage != 3.5             # Version Exclusion. Anything except version 3.5
+        Mopidy-Dirble ~= 1.1        # Compatible release. Same as >= 1.1, == 1.*
+        #
+        ###### Refer to other requirements files ######
+        -r other-requirements.txt
+        #
+        #
+        ###### A particular file ######
+        ./downloads/numpy-1.9.2-cp34-none-win32.whl
+        http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl
+        #
+        ###### Additional Requirements without Version Specifiers ######
+        #   Same as 1st section, just here to show that you can put things in any order.
+        rejected
+        green
+        #
+
+        Jinja2>=2.3
+        Pygments>=1.2
+        Sphinx>=1.3
+        docutils>=0.7
+        markupsafe
+      CONTENT
+    end
+
+    subject { Gitlab::Highlight.highlight(file_name, file_content, nowrap: false) }
+
+    def link(name, url)
+      %{<a href="#{url}" rel="nofollow noreferrer" target="_blank">#{name}</a>}
+    end
+
+    it "links dependencies" do
+      expect(subject).to include(link("nose", "https://pypi.python.org/pypi/nose"))
+      expect(subject).to include(link("nose-cov", "https://pypi.python.org/pypi/nose-cov"))
+      expect(subject).to include(link("beautifulsoup4", "https://pypi.python.org/pypi/beautifulsoup4"))
+      expect(subject).to include(link("docopt", "https://pypi.python.org/pypi/docopt"))
+      expect(subject).to include(link("keyring", "https://pypi.python.org/pypi/keyring"))
+      expect(subject).to include(link("coverage", "https://pypi.python.org/pypi/coverage"))
+      expect(subject).to include(link("Mopidy-Dirble", "https://pypi.python.org/pypi/Mopidy-Dirble"))
+      expect(subject).to include(link("rejected", "https://pypi.python.org/pypi/rejected"))
+      expect(subject).to include(link("green", "https://pypi.python.org/pypi/green"))
+      expect(subject).to include(link("Jinja2", "https://pypi.python.org/pypi/Jinja2"))
+      expect(subject).to include(link("Pygments", "https://pypi.python.org/pypi/Pygments"))
+      expect(subject).to include(link("Sphinx", "https://pypi.python.org/pypi/Sphinx"))
+      expect(subject).to include(link("docutils", "https://pypi.python.org/pypi/docutils"))
+      expect(subject).to include(link("markupsafe", "https://pypi.python.org/pypi/markupsafe"))
+    end
+  end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 15a8bf6a426..3d1cfbcfbf7 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -73,5 +73,13 @@ describe Gitlab::DependencyLinker, lib: true do
 
       described_class.link(blob_name, nil, nil)
     end
+
+    it 'links using RequirementsTxtLinker' do
+      blob_name = 'requirements.txt'
+
+      expect(described_class::RequirementsTxtLinker).to receive(:link)
+
+      described_class.link(blob_name, nil, nil)
+    end
   end
 end
-- 
GitLab


From 26992add96ad3f5b0d5f8b8d1bf415fabfa3bba3 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 15:12:45 -0500
Subject: [PATCH 13/18] Make AutolinkFilter smarter about trailing punctuation

---
 lib/banzai/filter/autolink_filter.rb           | 2 +-
 spec/lib/banzai/filter/autolink_filter_spec.rb | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index b8d2673c1a6..a8ca2757612 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -26,7 +26,7 @@ module Banzai
       # in the generated link.
       #
       # Rubular: http://rubular.com/r/cxjPyZc7Sb
-      LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<!,|\.)}
+      LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<![,."':?!&)<>])}
 
       # Text matching LINK_PATTERN inside these elements will not be linked
       IGNORE_PARENTS = %w(a code kbd pre script style).to_set
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index a6d2ea11fcc..7fdcd1148aa 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -130,6 +130,15 @@ describe Banzai::Filter::AutolinkFilter, lib: true do
 
       doc = filter("See #{link}...")
       expect(doc.at_css('a').text).to eq link
+
+      doc = filter("See #{link}\"")
+      expect(doc.at_css('a').text).to eq link
+
+      doc = filter("See #{link}'")
+      expect(doc.at_css('a').text).to eq link
+
+      doc = filter("See #{link})")
+      expect(doc.at_css('a').text).to eq link
     end
 
     it 'does not include trailing HTML entities' do
-- 
GitLab


From f5cf7cdb4d79b11a51e19fea2d73f9cb84d1bda2 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 15:38:46 -0500
Subject: [PATCH 14/18] Autolink URLs and emails in blobs and diffs

---
 app/assets/stylesheets/highlight/dark.scss    |  5 ++
 app/assets/stylesheets/highlight/monokai.scss |  5 ++
 .../stylesheets/highlight/solarized_dark.scss |  5 ++
 .../highlight/solarized_light.scss            |  5 ++
 app/assets/stylesheets/highlight/white.scss   |  5 ++
 lib/banzai/filter/autolink_filter.rb          | 10 ++-
 lib/banzai/pipeline/autolink_pipeline.rb      | 12 ++++
 lib/gitlab/highlight.rb                       | 20 +++++-
 spec/lib/gitlab/highlight_spec.rb             | 65 ++++++++++++++++++-
 9 files changed, 123 insertions(+), 9 deletions(-)
 create mode 100644 lib/banzai/pipeline/autolink_pipeline.rb

diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 09951fe3d3e..6e3829d994f 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -185,6 +185,11 @@ $dark-il: #de935f;
     color: $dark-highlight-color !important;
   }
 
+  // Links to URLs, emails, or dependencies
+  .line a {
+    color: $dark-na;
+  }
+
   .hll { background-color: $dark-hll-bg; }
   .c { color: $dark-c; } /* Comment */
   .err { color: $dark-err; } /* Error */
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index b6a6d298adf..68eb0c7720f 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -185,6 +185,11 @@ $monokai-gi: #a6e22e;
     color: $black !important;
   }
 
+  // Links to URLs, emails, or dependencies
+  .line a {
+    color: $monokai-k;
+  }
+
   .hll { background-color: $monokai-hll; }
   .c { color: $monokai-c; } /* Comment */
   .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 4f7a50dcb4f..2cc968c32f2 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -188,6 +188,11 @@ $solarized-dark-il: #2aa198;
     background-color: $solarized-dark-highlight !important;
   }
 
+  // Links to URLs, emails, or dependencies
+  .line a {
+    color: $solarized-dark-kd;
+  }
+
   /* Solarized Dark
 
   For use with Jekyll and Pygments
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 6463fe96c1b..b61b85a2cd1 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -196,6 +196,11 @@ $solarized-light-il: #2aa198;
     background-color: $solarized-light-highlight !important;
   }
 
+  // Links to URLs, emails, or dependencies
+  .line a {
+    color: $solarized-light-kd;
+  }
+
   /* Solarized Light
 
   For use with Jekyll and Pygments
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index ab2018bfbca..1daa10aef24 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -203,6 +203,11 @@ $white-gc-bg: #eaf2f5;
     background-color: $white-highlight !important;
   }
 
+  // Links to URLs, emails, or dependencies
+  .line a {
+    color: $white-nb;
+  }
+
   .hll { background-color: $white-hll-bg; }
   .c { color: $white-c; font-style: italic; }
   .err { color: $white-err; background-color: $white-err-bg; }
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index a8ca2757612..15f7da5d934 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -54,15 +54,13 @@ module Banzai
       #
       # `@doc` will be re-parsed with the HTML String from Rinku.
       def rinku_parse
-        # Convert the options from a Hash to a String that Rinku expects
-        options = tag_options(link_options)
-
         # NOTE: We don't parse email links because it will erroneously match
         # external Commit and CommitRange references.
         #
         # The final argument tells Rinku to link short URLs that don't include a
         # period (e.g., http://localhost:3000/)
-        rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a, 1)
+        mode = context[:autolink_emails] ? :all : :urls
+        rinku = Rinku.auto_link(html, mode, tag_options(link_options), IGNORE_PARENTS.to_a, 1)
 
         return if rinku == html
 
@@ -111,9 +109,9 @@ module Banzai
         # order to be output literally rather than escaped.
         match.gsub!(/((?:&[\w#]+;)+)\z/, '')
         dropped = ($1 || '').html_safe
+        match = ERB::Util.html_escape_once(match)
 
-        options = link_options.merge(href: match)
-        content_tag(:a, match, options) + dropped
+        %{<a href="#{match}" #{tag_options(link_options)}>#{match}</a>#{dropped}}.html_safe
       end
 
       def autolink_filter(text)
diff --git a/lib/banzai/pipeline/autolink_pipeline.rb b/lib/banzai/pipeline/autolink_pipeline.rb
new file mode 100644
index 00000000000..53f2da5c7b5
--- /dev/null
+++ b/lib/banzai/pipeline/autolink_pipeline.rb
@@ -0,0 +1,12 @@
+module Banzai
+  module Pipeline
+    class AutolinkPipeline < BasePipeline
+      def self.filters
+        @filters ||= FilterArray[
+          Filter::AutolinkFilter,
+          Filter::ExternalLinkFilter
+        ]
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 83bc230df3e..57ae127254b 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -25,7 +25,7 @@ module Gitlab
     def highlight(text, continue: true, plain: false)
       highlighted_text = highlight_text(text, continue: continue, plain: plain)
       highlighted_text = link_dependencies(text, highlighted_text) if blob_name
-      highlighted_text
+      autolink_strings(highlighted_text)
     end
 
     def lexer
@@ -67,5 +67,23 @@ module Gitlab
     def link_dependencies(text, highlighted_text)
       Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
     end
+
+    def autolink_strings(highlighted_text)
+      doc = Nokogiri::HTML::DocumentFragment.parse(highlighted_text)
+
+      # Files without highlighting have all text in `span.line`.
+      # Files with highlighting have strings and comments in `span`s with a
+      # `class` starting with `c` or `s`.
+      doc.xpath('.//span[@class="line" or starts-with(@class, "c") or starts-with(@class, "s")]/text()').each do |node|
+        content = node.to_html
+        html = Banzai.render(content, pipeline: :autolink, autolink_emails: true)
+
+        next if html == content
+
+        node.replace(html)
+      end
+
+      doc.to_html.html_safe
+    end
   end
 end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index e57b3053871..4f6ae157d6e 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Highlight, lib: true do
 
   describe '.highlight_lines' do
     let(:lines) do
-      Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
+      described_class.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
     end
 
     it 'highlights all the lines properly' do
@@ -59,7 +59,68 @@ describe Gitlab::Highlight, lib: true do
   end
 
   describe '#highlight' do
-    subject { described_class.highlight(file_name, file_content, nowrap: false) }
+    subject { described_class.highlight(file_name, file_content) }
+
+    context "plain text file" do
+      let(:file_name) { "example.txt" }
+      let(:file_content) do
+        <<-CONTENT.strip_heredoc
+          URL: http://www.google.com
+          Email: hello@example.com
+        CONTENT
+      end
+
+      it "links URLs" do
+        expect(subject).to include(%{<a href="http://www.google.com" rel="nofollow noreferrer" target="_blank">http://www.google.com</a>})
+      end
+
+      it "links emails" do
+        expect(subject).to include(%{<a href="mailto:hello@example.com">hello@example.com</a>})
+      end
+    end
+
+    context "file with highlighting" do
+      let(:file_name) { "example.rb" }
+      let(:file_content) do
+        <<-CONTENT.strip_heredoc
+          # URL in comment: http://www.google.com
+          # Email in comment: hello@example.com
+
+          "URL in string: http://www.google.com"
+          "Email in string: hello@example.com"
+
+          # <http://www.google.com>
+          # <url>http://www.google.com</url>
+        CONTENT
+      end
+
+      context "in a comment" do
+        it "links URLs" do
+          expect(subject).to include(%{URL in comment: <a href="http://www.google.com" rel="nofollow noreferrer" target="_blank">http://www.google.com</a>})
+        end
+
+        it "links emails" do
+          expect(subject).to include(%{Email in comment: <a href="mailto:hello@example.com">hello@example.com</a>})
+        end
+      end
+
+      context "in a string" do
+        it "links URLs" do
+          expect(subject).to include(%{URL in string: <a href="http://www.google.com" rel="nofollow noreferrer" target="_blank">http://www.google.com</a>})
+        end
+
+        it "links emails" do
+          expect(subject).to include(%{Email in string: <a href="mailto:hello@example.com">hello@example.com</a>})
+        end
+      end
+
+      context 'in HTML/XML tags' do
+        it "links URLs" do
+          expect(subject).to include(%{&lt;<a href="http://www.google.com" rel="nofollow noreferrer" target="_blank">http://www.google.com</a>&gt;})
+          expect(subject).to include(%{&lt;url&gt;<a href="http://www.google.com" rel="nofollow noreferrer" target="_blank">http://www.google.com</a>&lt;/url&gt;})
+        end
+      end
+    end
 
     it 'links dependencies via DependencyLinker' do
       expect(Gitlab::DependencyLinker).to receive(:link).
-- 
GitLab


From 820cfa14a5216b5bb27bb34ba2dec77867901aee Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 10 Jul 2016 15:39:20 -0500
Subject: [PATCH 15/18] Update specs

---
 .../lib/gitlab/diff/inline_diff_marker_spec.rb | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 198ff977f24..418521ae650 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -1,26 +1,26 @@
 require 'spec_helper'
 
 describe Gitlab::Diff::InlineDiffMarker, lib: true do
-  describe '#inline_diffs' do
+  describe '#mark' do
     context "when the rich text is html safe" do
-      let(:raw)  { "abc 'def'" }
-      let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
+      let(:raw)  { "abc <def>" }
+      let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
       let(:inline_diffs) { [2..5] }
-      let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+      let(:subject) { described_class.new(raw, rich).mark(inline_diffs) }
 
       it 'marks the inline diffs' do
-        expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
+        expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&lt;d</span>ef&gt;</span>})
         expect(subject).to be_html_safe
       end
     end
 
-    context "when the text text is not html safe" do
-      let(:raw)  { "abc 'def'" }
+    context "when the text is not html safe" do
+      let(:raw)  { "abc <def>" }
       let(:inline_diffs) { [2..5] }
-      let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+      let(:subject) { described_class.new(raw).mark(inline_diffs) }
 
       it 'marks the inline diffs' do
-        expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
+        expect(subject).to eq(%{ab<span class='idiff left right'>c &lt;d</span>ef&gt;})
         expect(subject).to be_html_safe
       end
     end
-- 
GitLab


From bc6e3daa592a4a1b7de19bc55be2a4bea8008cf0 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Tue, 9 May 2017 15:59:21 -0500
Subject: [PATCH 16/18] Add autolink and link_deps flags to ease benchmarking

---
 lib/gitlab/highlight.rb | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 57ae127254b..558a26810b7 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -1,8 +1,8 @@
 module Gitlab
   class Highlight
-    def self.highlight(blob_name, blob_content, repository: nil, plain: false)
+    def self.highlight(blob_name, blob_content, repository: nil, plain: false, link_deps: true, autolink: true)
       new(blob_name, blob_content, repository: repository).
-        highlight(blob_content, continue: false, plain: plain)
+        highlight(blob_content, continue: false, plain: plain, link_deps: link_deps, autolink: autolink)
     end
 
     def self.highlight_lines(repository, ref, file_name)
@@ -22,10 +22,11 @@ module Gitlab
       @blob_content = blob_content
     end
 
-    def highlight(text, continue: true, plain: false)
+    def highlight(text, continue: true, plain: false, link_deps: true, autolink: true)
       highlighted_text = highlight_text(text, continue: continue, plain: plain)
-      highlighted_text = link_dependencies(text, highlighted_text) if blob_name
-      autolink_strings(highlighted_text)
+      highlighted_text = link_dependencies(text, highlighted_text) if blob_name && link_deps
+      highlighted_text = autolink_strings(highlighted_text) if autolink
+      highlighted_text
     end
 
     def lexer
-- 
GitLab


From 180bb5fa08f0524855e919ddc3c3fbf38e88334c Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Tue, 9 May 2017 16:00:17 -0500
Subject: [PATCH 17/18] Autolink all of the highlighted text at once

---
 lib/gitlab/highlight.rb | 17 ++---------------
 1 file changed, 2 insertions(+), 15 deletions(-)

diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 558a26810b7..9013655ff3a 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -70,21 +70,8 @@ module Gitlab
     end
 
     def autolink_strings(highlighted_text)
-      doc = Nokogiri::HTML::DocumentFragment.parse(highlighted_text)
-
-      # Files without highlighting have all text in `span.line`.
-      # Files with highlighting have strings and comments in `span`s with a
-      # `class` starting with `c` or `s`.
-      doc.xpath('.//span[@class="line" or starts-with(@class, "c") or starts-with(@class, "s")]/text()').each do |node|
-        content = node.to_html
-        html = Banzai.render(content, pipeline: :autolink, autolink_emails: true)
-
-        next if html == content
-
-        node.replace(html)
-      end
-
-      doc.to_html.html_safe
+      # TODO: Don't run pre-processing pipeline, because this may break the highlighting
+      Banzai.render(highlighted_text, pipeline: :autolink, autolink_emails: true).html_safe
     end
   end
 end
-- 
GitLab


From 968d68258afcc61dcab3882f7a32431e38204189 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Tue, 9 May 2017 16:02:55 -0500
Subject: [PATCH 18/18] =?UTF-8?q?Autolink=20the=20raw=20text=20and=20?=
 =?UTF-8?q?=E2=80=9Ctransfer=E2=80=9D=20the=20found=20links=20to=20the=20h?=
 =?UTF-8?q?ighlighted=20text?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/gitlab/highlight.rb | 39 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 9013655ff3a..0d5c7a92540 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -25,7 +25,7 @@ module Gitlab
     def highlight(text, continue: true, plain: false, link_deps: true, autolink: true)
       highlighted_text = highlight_text(text, continue: continue, plain: plain)
       highlighted_text = link_dependencies(text, highlighted_text) if blob_name && link_deps
-      highlighted_text = autolink_strings(highlighted_text) if autolink
+      highlighted_text = autolink_strings(text, highlighted_text) if autolink
       highlighted_text
     end
 
@@ -69,9 +69,42 @@ module Gitlab
       Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
     end
 
-    def autolink_strings(highlighted_text)
+    def autolink_strings(text, highlighted_text)
+      raw_lines = text.lines
+
       # TODO: Don't run pre-processing pipeline, because this may break the highlighting
-      Banzai.render(highlighted_text, pipeline: :autolink, autolink_emails: true).html_safe
+      linked_text = Banzai.render(
+        ERB::Util.html_escape(text),
+        pipeline: :autolink,
+        autolink_emails: true
+      ).html_safe
+
+      linked_lines = linked_text.lines
+
+      highlighted_lines = highlighted_text.lines
+
+      highlighted_lines.map!.with_index do |rich_line, i|
+        matches = []
+        linked_lines[i].scan(/(?<start><a[^>]+>)(?<content>[^<]+)(?<end><\/a>)/) { matches << Regexp.last_match }
+        next rich_line if matches.empty?
+
+        raw_line = raw_lines[i]
+        marked_line = rich_line.html_safe
+
+        matches.each do |match|
+          marker = StringRegexMarker.new(raw_line, marked_line)
+
+          regex = /#{Regexp.escape(match[:content])}/
+
+          marked_line = marker.mark(regex) do |text, left:, right:|
+            "#{match[:start]}#{text}#{match[:end]}"
+          end
+        end
+
+        marked_line
+      end
+
+      highlighted_lines.join.html_safe
     end
   end
 end
-- 
GitLab