From 2c55fd0019ea9ac33e310151a61892fada42d5a2 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Date: Mon, 13 Feb 2017 13:26:33 +0200
Subject: [PATCH] Add GFM support to nested groups

Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
---
 app/models/group.rb                                 |  2 +-
 app/models/user.rb                                  |  2 +-
 app/services/projects/participants_service.rb       |  2 +-
 lib/banzai/filter/abstract_reference_filter.rb      |  2 +-
 lib/banzai/filter/user_reference_filter.rb          |  6 +++---
 lib/gitlab/regex.rb                                 |  4 ++++
 .../lib/banzai/filter/user_reference_filter_spec.rb | 13 +++++++++++++
 spec/lib/gitlab/regex_spec.rb                       | 12 ++++++++++++
 8 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/app/models/group.rb b/app/models/group.rb
index cc6624ff4aa..240a17f1dc1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -81,7 +81,7 @@ class Group < Namespace
   end
 
   def to_reference(_from_project = nil, full: nil)
-    "#{self.class.reference_prefix}#{name}"
+    "#{self.class.reference_prefix}#{full_path}"
   end
 
   def web_url
diff --git a/app/models/user.rb b/app/models/user.rb
index 1649bf04eaa..ad997ce2b13 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -335,7 +335,7 @@ class User < ActiveRecord::Base
     def reference_pattern
       %r{
         #{Regexp.escape(reference_prefix)}
-        (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
+        (?<user>#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR})
       }x
     end
   end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 96c363c8d1a..e6193fcacee 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -36,7 +36,7 @@ module Projects
     def groups
       current_user.authorized_groups.sort_by(&:path).map do |group|
         count = group.users.count
-        { username: group.path, name: group.name, count: count, avatar_url: group.avatar_url }
+        { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url }
       end
     end
 
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index a3d495a5da0..955d857c679 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -285,7 +285,7 @@ module Banzai
       end
 
       def current_project_namespace_path
-        @current_project_namespace_path ||= project.namespace.path
+        @current_project_namespace_path ||= project.namespace.full_path
       end
 
       private
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 1aa9355b256..c973897f420 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -75,8 +75,8 @@ module Banzai
       # corresponding Namespace objects.
       def namespaces
         @namespaces ||=
-          Namespace.where(path: usernames).each_with_object({}) do |row, hash|
-            hash[row.path] = row
+          Namespace.where_full_path_in(usernames).each_with_object({}) do |row, hash|
+            hash[row.full_path] = row
           end
       end
 
@@ -122,7 +122,7 @@ module Banzai
 
       def link_to_namespace(namespace, link_content: nil)
         if namespace.is_a?(Group)
-          link_to_group(namespace.path, namespace, link_content: link_content)
+          link_to_group(namespace.full_path, namespace, link_content: link_content)
         else
           link_to_user(namespace.path, namespace, link_content: link_content)
         end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index a3fa7c1331a..c77fe2d8bdc 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -13,6 +13,10 @@ module Gitlab
     NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze
     PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze
 
+    # Same as NAMESPACE_REGEX_STR but allows `/` in the path.
+    # So `group/subgroup` will match this regex but not NAMESPACE_REGEX_STR
+    NAMESPACE_REF_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.\/]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze
+
     def namespace_regex
       @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
     end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 3e1ac9fb2b2..d5d128c1907 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -112,6 +112,19 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
     end
   end
 
+  context 'mentioning a nested group' do
+    it_behaves_like 'a reference containing an element node'
+
+    let(:group)     { create(:group, :nested) }
+    let(:reference) { group.to_reference }
+
+    it 'links to the nested group' do
+      doc = reference_filter("Hey #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+    end
+  end
+
   it 'links with adjacent text' do
     doc = reference_filter("Mention me (#{reference}.)")
     expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 1dbc2f6eb13..089ec4e2737 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -50,4 +50,16 @@ describe Gitlab::Regex, lib: true do
     it { is_expected.not_to match('9foo') }
     it { is_expected.not_to match('foo-') }
   end
+
+  describe 'NAMESPACE_REF_REGEX_STR' do
+    subject { %r{\A#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR}\z} }
+
+    it { is_expected.to match('gitlab.org') }
+    it { is_expected.to match('gitlab.org/gitlab-git') }
+    it { is_expected.not_to match('gitlab.org.') }
+    it { is_expected.not_to match('gitlab.org/') }
+    it { is_expected.not_to match('/gitlab.org') }
+    it { is_expected.not_to match('gitlab.git') }
+    it { is_expected.not_to match('gitlab git') }
+  end
 end
-- 
GitLab