Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gitlab-ce
Commits
bb929c21
Commit
bb929c21
authored
Oct 05, 2014
by
Marin Jankovski
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #7933 from mr-vinn/cross-project-markdown
Implement cross-project Markdown references
parents
43be3fcb
8dce0cd2
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
356 additions
and
94 deletions
+356
-94
CHANGELOG
CHANGELOG
+1
-0
app/models/concerns/mentionable.rb
app/models/concerns/mentionable.rb
+4
-2
app/models/note.rb
app/models/note.rb
+69
-4
doc/markdown/markdown.md
doc/markdown/markdown.md
+6
-0
lib/gitlab/closing_issue_extractor.rb
lib/gitlab/closing_issue_extractor.rb
+1
-1
lib/gitlab/markdown.rb
lib/gitlab/markdown.rb
+65
-33
lib/gitlab/reference_extractor.rb
lib/gitlab/reference_extractor.rb
+32
-20
spec/helpers/gitlab_markdown_helper_spec.rb
spec/helpers/gitlab_markdown_helper_spec.rb
+102
-0
spec/lib/gitlab/reference_extractor_spec.rb
spec/lib/gitlab/reference_extractor_spec.rb
+28
-24
spec/models/commit_spec.rb
spec/models/commit_spec.rb
+13
-1
spec/models/note_spec.rb
spec/models/note_spec.rb
+2
-2
spec/support/mentionable_shared_examples.rb
spec/support/mentionable_shared_examples.rb
+33
-7
No files found.
CHANGELOG
View file @
bb929c21
...
@@ -17,6 +17,7 @@ v 7.4.0
...
@@ -17,6 +17,7 @@ v 7.4.0
- Font Awesome 4.2 integration (Sullivan Senechal)
- Font Awesome 4.2 integration (Sullivan Senechal)
- Add Pushover service integration (Sullivan Senechal)
- Add Pushover service integration (Sullivan Senechal)
- Add select field type for services options (Sullivan Senechal)
- Add select field type for services options (Sullivan Senechal)
- Add cross-project references to the Markdown parser (Vinnie Okada)
v 7.3.2
v 7.3.2
- Fix creating new file via web editor
- Fix creating new file via web editor
...
...
app/models/concerns/mentionable.rb
View file @
bb929c21
...
@@ -67,8 +67,10 @@ module Mentionable
...
@@ -67,8 +67,10 @@ module Mentionable
def
references
(
p
=
project
,
text
=
mentionable_text
)
def
references
(
p
=
project
,
text
=
mentionable_text
)
return
[]
if
text
.
blank?
return
[]
if
text
.
blank?
ext
=
Gitlab
::
ReferenceExtractor
.
new
ext
=
Gitlab
::
ReferenceExtractor
.
new
ext
.
analyze
(
text
)
ext
.
analyze
(
text
,
p
)
(
ext
.
issues_for
(
p
)
+
ext
.
merge_requests_for
(
p
)
+
ext
.
commits_for
(
p
)).
uniq
-
[
local_reference
]
(
ext
.
issues_for
+
ext
.
merge_requests_for
+
ext
.
commits_for
).
uniq
-
[
local_reference
]
end
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
...
...
app/models/note.rb
View file @
bb929c21
...
@@ -70,13 +70,17 @@ class Note < ActiveRecord::Base
...
@@ -70,13 +70,17 @@ class Note < ActiveRecord::Base
)
)
end
end
# +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
# +noteable+ was referenced from +mentioner+, by including GFM in either
# Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
# +mentioner+'s description or an associated Note.
# Create a system Note associated with +noteable+ with a GFM back-reference
# to +mentioner+.
def
create_cross_reference_note
(
noteable
,
mentioner
,
author
,
project
)
def
create_cross_reference_note
(
noteable
,
mentioner
,
author
,
project
)
gfm_reference
=
mentioner_gfm_ref
(
noteable
,
mentioner
,
project
)
note_options
=
{
note_options
=
{
project:
project
,
project:
project
,
author:
author
,
author:
author
,
note:
"_mentioned in
#{
mentioner
.
gfm_reference
}
_"
,
note:
"_mentioned in
#{
gfm_reference
}
_"
,
system:
true
system:
true
}
}
...
@@ -163,12 +167,73 @@ class Note < ActiveRecord::Base
...
@@ -163,12 +167,73 @@ class Note < ActiveRecord::Base
# Determine whether or not a cross-reference note already exists.
# Determine whether or not a cross-reference note already exists.
def
cross_reference_exists?
(
noteable
,
mentioner
)
def
cross_reference_exists?
(
noteable
,
mentioner
)
where
(
noteable_id:
noteable
.
id
,
system:
true
,
note:
"_mentioned in
#{
mentioner
.
gfm_reference
}
_"
).
any?
gfm_reference
=
mentioner_gfm_ref
(
noteable
,
mentioner
)
where
([
'noteable_id = ? and system = ? and note like ?'
,
noteable
.
id
,
true
,
"_mentioned in
#{
gfm_reference
}
_"
]).
any?
end
end
def
search
(
query
)
def
search
(
query
)
where
(
"note like :query"
,
query:
"%
#{
query
}
%"
)
where
(
"note like :query"
,
query:
"%
#{
query
}
%"
)
end
end
private
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
def
mentioner_gfm_ref
(
noteable
,
mentioner
,
project
=
nil
)
if
mentioner
.
is_a?
(
Commit
)
if
project
.
nil?
return
mentioner
.
gfm_reference
.
sub
(
'commit '
,
'commit %'
)
else
mentioning_project
=
project
end
else
mentioning_project
=
mentioner
.
project
end
noteable_project_id
=
noteable_project_id
(
noteable
,
mentioning_project
)
full_gfm_reference
(
mentioning_project
,
noteable_project_id
,
mentioner
)
end
# Return the ID of the project that +noteable+ belongs to, or nil if
# +noteable+ is a commit and is not part of the project that owns
# +mentioner+.
def
noteable_project_id
(
noteable
,
mentioning_project
)
if
noteable
.
is_a?
(
Commit
)
if
mentioning_project
.
repository
.
commit
(
noteable
.
id
)
# The noteable commit belongs to the mentioner's project
mentioning_project
.
id
else
nil
end
else
noteable
.
project
.
id
end
end
# Return the +mentioner+ GFM reference. If the mentioner and noteable
# projects are not the same, add the mentioning project's path to the
# returned value.
def
full_gfm_reference
(
mentioning_project
,
noteable_project_id
,
mentioner
)
if
mentioning_project
.
id
==
noteable_project_id
mentioner
.
gfm_reference
else
if
mentioner
.
is_a?
(
Commit
)
mentioner
.
gfm_reference
.
sub
(
/(commit )/
,
"
\\
1
#{
mentioning_project
.
path_with_namespace
}
@"
)
else
mentioner
.
gfm_reference
.
sub
(
/(issue |merge request )/
,
"
\\
1
#{
mentioning_project
.
path_with_namespace
}
"
)
end
end
end
end
end
def
commit_author
def
commit_author
...
...
doc/markdown/markdown.md
View file @
bb929c21
...
@@ -177,6 +177,12 @@ GFM will recognize the following:
...
@@ -177,6 +177,12 @@ GFM will recognize the following:
- 1234567 : for commits
- 1234567 : for commits
- \[file\](path/to/file) : for file references
- \[file\](path/to/file) : for file references
GFM also recognizes references to commits, issues, and merge requests in other projects:
- namespace/project#123 : for issues
- namespace/project!123 : for merge requests
- namespace/project@1234567 : for commits
# Standard Markdown
# Standard Markdown
## Headers
## Headers
...
...
lib/gitlab/closing_issue_extractor.rb
View file @
bb929c21
...
@@ -6,7 +6,7 @@ module Gitlab
...
@@ -6,7 +6,7 @@ module Gitlab
md
=
ISSUE_CLOSING_REGEX
.
match
(
message
)
md
=
ISSUE_CLOSING_REGEX
.
match
(
message
)
if
md
if
md
extractor
=
Gitlab
::
ReferenceExtractor
.
new
extractor
=
Gitlab
::
ReferenceExtractor
.
new
extractor
.
analyze
(
md
[
0
])
extractor
.
analyze
(
md
[
0
]
,
project
)
extractor
.
issues_for
(
project
)
extractor
.
issues_for
(
project
)
else
else
[]
[]
...
...
lib/gitlab/markdown.rb
View file @
bb929c21
...
@@ -108,15 +108,18 @@ module Gitlab
...
@@ -108,15 +108,18 @@ module Gitlab
text
text
end
end
NAME_STR
=
'[a-zA-Z][a-zA-Z0-9_\-\.]*'
PROJ_STR
=
"(?<project>
#{
NAME_STR
}
/
#{
NAME_STR
}
)"
REFERENCE_PATTERN
=
%r{
REFERENCE_PATTERN
=
%r{
(?<prefix>
\W
)? # Prefix
(?<prefix>
\W
)? # Prefix
( # Reference
( # Reference
@(?<user>
[a-zA-Z][a-zA-Z0-9_
\-\.
]*)
# User name
@(?<user>
#{
NAME_STR
}
)
# User name
|(?<issue>([A-Z
\-
]+-)
\d
+) # JIRA Issue ID
|(?<issue>([A-Z
\-
]+-)
\d
+) # JIRA Issue ID
|
\#
(?<issue>([a-zA-Z
\-
]+-)?
\d
+)
# Issue ID
|
#{
PROJ_STR
}
?
\#
(?<issue>([a-zA-Z
\-
]+-)?
\d
+)
# Issue ID
|
!(?<merge_request>
\d
+)
# MR ID
|
#{
PROJ_STR
}
?!(?<merge_request>
\d
+)
# MR ID
|
\$
(?<snippet>
\d
+) # Snippet ID
|
\$
(?<snippet>
\d
+) # Snippet ID
|(
?<commit>[
\h
]{6,40})
# Commit ID
|(
#{
PROJ_STR
}
@)?(?<commit>[
\h
]{6,40})
# Commit ID
|(?<skip>gfm-extraction-[
\h
]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
|(?<skip>gfm-extraction-[
\h
]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
)
(?<suffix>
\W
)? # Suffix
(?<suffix>
\W
)? # Suffix
...
@@ -127,38 +130,59 @@ module Gitlab
...
@@ -127,38 +130,59 @@ module Gitlab
def
parse_references
(
text
,
project
=
@project
)
def
parse_references
(
text
,
project
=
@project
)
# parse reference links
# parse reference links
text
.
gsub!
(
REFERENCE_PATTERN
)
do
|
match
|
text
.
gsub!
(
REFERENCE_PATTERN
)
do
|
match
|
prefix
=
$~
[
:prefix
]
suffix
=
$~
[
:suffix
]
type
=
TYPES
.
select
{
|
t
|
!
$~
[
t
].
nil?
}.
first
type
=
TYPES
.
select
{
|
t
|
!
$~
[
t
].
nil?
}.
first
if
type
actual_project
=
project
identifier
=
$~
[
type
]
project_prefix
=
nil
project_path
=
$LAST_MATCH_INFO
[
:project
]
# Avoid HTML entities
if
project_path
if
prefix
&&
suffix
&&
prefix
[
0
]
==
'&'
&&
suffix
[
-
1
]
==
';'
actual_project
=
::
Project
.
find_with_namespace
(
project_path
)
match
project_prefix
=
project_path
elsif
ref_link
=
reference_link
(
type
,
identifier
,
project
)
"
#{
prefix
}#{
ref_link
}#{
suffix
}
"
else
match
end
else
match
end
end
parse_result
(
$LAST_MATCH_INFO
,
type
,
actual_project
,
project_prefix
)
||
match
end
end
# Called from #parse_references. Attempts to build a gitlab reference
# link. Returns nil if +type+ is nil, if the match string is an HTML
# entity, if the reference is invalid, or if the matched text includes an
# invalid project path.
def
parse_result
(
match_info
,
type
,
project
,
project_prefix
)
prefix
=
match_info
[
:prefix
]
suffix
=
match_info
[
:suffix
]
return
nil
if
html_entity?
(
prefix
,
suffix
)
||
type
.
nil?
return
nil
if
project
.
nil?
&&
!
project_prefix
.
nil?
identifier
=
match_info
[
type
]
ref_link
=
reference_link
(
type
,
identifier
,
project
,
project_prefix
)
if
ref_link
"
#{
prefix
}#{
ref_link
}#{
suffix
}
"
else
nil
end
end
end
end
# Return true if the +prefix+ and +suffix+ indicate that the matched string
# is an HTML entity like &
def
html_entity?
(
prefix
,
suffix
)
prefix
&&
suffix
&&
prefix
[
0
]
==
'&'
&&
suffix
[
-
1
]
==
';'
end
# Private: Dispatches to a dedicated processing method based on reference
# Private: Dispatches to a dedicated processing method based on reference
#
#
# reference - Object reference ("@1234", "!567", etc.)
# reference - Object reference ("@1234", "!567", etc.)
# identifier - Object identifier (Issue ID, SHA hash, etc.)
# identifier - Object identifier (Issue ID, SHA hash, etc.)
#
#
# Returns string rendered by the processing method
# Returns string rendered by the processing method
def
reference_link
(
type
,
identifier
,
project
=
@project
)
def
reference_link
(
type
,
identifier
,
project
=
@project
,
prefix_text
=
nil
)
send
(
"reference_
#{
type
}
"
,
identifier
,
project
)
send
(
"reference_
#{
type
}
"
,
identifier
,
project
,
prefix_text
)
end
end
def
reference_user
(
identifier
,
project
=
@project
)
def
reference_user
(
identifier
,
project
=
@project
,
_
=
nil
)
options
=
html_options
.
merge
(
options
=
html_options
.
merge
(
class:
"gfm gfm-team_member
#{
html_options
[
:class
]
}
"
class:
"gfm gfm-team_member
#{
html_options
[
:class
]
}
"
)
)
...
@@ -170,39 +194,41 @@ module Gitlab
...
@@ -170,39 +194,41 @@ module Gitlab
end
end
end
end
def
reference_issue
(
identifier
,
project
=
@project
)
def
reference_issue
(
identifier
,
project
=
@project
,
prefix_text
=
nil
)
if
project
.
used_default_issues_tracker?
||
!
external_issues_tracker_enabled?
if
project
.
used_default_issues_tracker?
||
!
external_issues_tracker_enabled?
if
project
.
issue_exists?
identifier
if
project
.
issue_exists?
identifier
url
=
url_for_issue
(
identifier
,
project
)
url
=
url_for_issue
(
identifier
,
project
)
title
=
title_for_issue
(
identifier
)
title
=
title_for_issue
(
identifier
,
project
)
options
=
html_options
.
merge
(
options
=
html_options
.
merge
(
title:
"Issue:
#{
title
}
"
,
title:
"Issue:
#{
title
}
"
,
class:
"gfm gfm-issue
#{
html_options
[
:class
]
}
"
class:
"gfm gfm-issue
#{
html_options
[
:class
]
}
"
)
)
link_to
(
"#
#{
identifier
}
"
,
url
,
options
)
link_to
(
"
#
{
prefix_text
}
#
#{
identifier
}
"
,
url
,
options
)
end
end
else
else
config
=
Gitlab
.
config
config
=
Gitlab
.
config
external_issue_tracker
=
config
.
issues_tracker
[
project
.
issues_tracker
]
external_issue_tracker
=
config
.
issues_tracker
[
project
.
issues_tracker
]
if
external_issue_tracker
.
present?
if
external_issue_tracker
.
present?
reference_external_issue
(
identifier
,
external_issue_tracker
,
project
)
reference_external_issue
(
identifier
,
external_issue_tracker
,
project
,
prefix_text
)
end
end
end
end
end
end
def
reference_merge_request
(
identifier
,
project
=
@project
)
def
reference_merge_request
(
identifier
,
project
=
@project
,
prefix_text
=
nil
)
if
merge_request
=
project
.
merge_requests
.
find_by
(
iid:
identifier
)
if
merge_request
=
project
.
merge_requests
.
find_by
(
iid:
identifier
)
options
=
html_options
.
merge
(
options
=
html_options
.
merge
(
title:
"Merge Request:
#{
merge_request
.
title
}
"
,
title:
"Merge Request:
#{
merge_request
.
title
}
"
,
class:
"gfm gfm-merge_request
#{
html_options
[
:class
]
}
"
class:
"gfm gfm-merge_request
#{
html_options
[
:class
]
}
"
)
)
url
=
project_merge_request_url
(
project
,
merge_request
)
url
=
project_merge_request_url
(
project
,
merge_request
)
link_to
(
"!
#{
identifier
}
"
,
url
,
options
)
link_to
(
"
#{
prefix_text
}
!
#{
identifier
}
"
,
url
,
options
)
end
end
end
end
def
reference_snippet
(
identifier
,
project
=
@project
)
def
reference_snippet
(
identifier
,
project
=
@project
,
_
=
nil
)
if
snippet
=
project
.
snippets
.
find_by
(
id:
identifier
)
if
snippet
=
project
.
snippets
.
find_by
(
id:
identifier
)
options
=
html_options
.
merge
(
options
=
html_options
.
merge
(
title:
"Snippet:
#{
snippet
.
title
}
"
,
title:
"Snippet:
#{
snippet
.
title
}
"
,
...
@@ -213,17 +239,23 @@ module Gitlab
...
@@ -213,17 +239,23 @@ module Gitlab
end
end
end
end
def
reference_commit
(
identifier
,
project
=
@project
)
def
reference_commit
(
identifier
,
project
=
@project
,
prefix_text
=
nil
)
if
project
.
valid_repo?
&&
commit
=
project
.
repository
.
commit
(
identifier
)
if
project
.
valid_repo?
&&
commit
=
project
.
repository
.
commit
(
identifier
)
options
=
html_options
.
merge
(
options
=
html_options
.
merge
(
title:
commit
.
link_title
,
title:
commit
.
link_title
,
class:
"gfm gfm-commit
#{
html_options
[
:class
]
}
"
class:
"gfm gfm-commit
#{
html_options
[
:class
]
}
"
)
)
link_to
(
identifier
,
project_commit_url
(
project
,
commit
),
options
)
prefix_text
=
"
#{
prefix_text
}
@"
if
prefix_text
link_to
(
"
#{
prefix_text
}#{
identifier
}
"
,
project_commit_url
(
project
,
commit
),
options
)
end
end
end
end
def
reference_external_issue
(
identifier
,
issue_tracker
,
project
=
@project
)
def
reference_external_issue
(
identifier
,
issue_tracker
,
project
=
@project
,
prefix_text
=
nil
)
url
=
url_for_issue
(
identifier
,
project
)
url
=
url_for_issue
(
identifier
,
project
)
title
=
issue_tracker
[
'title'
]
title
=
issue_tracker
[
'title'
]
...
@@ -231,7 +263,7 @@ module Gitlab
...
@@ -231,7 +263,7 @@ module Gitlab
title:
"Issue in
#{
title
}
"
,
title:
"Issue in
#{
title
}
"
,
class:
"gfm gfm-issue
#{
html_options
[
:class
]
}
"
class:
"gfm gfm-issue
#{
html_options
[
:class
]
}
"
)
)
link_to
(
"#
#{
identifier
}
"
,
url
,
options
)
link_to
(
"
#
{
prefix_text
}
#
#{
identifier
}
"
,
url
,
options
)
end
end
end
end
end
end
lib/gitlab/reference_extractor.rb
View file @
bb929c21
...
@@ -9,51 +9,63 @@ module Gitlab
...
@@ -9,51 +9,63 @@ module Gitlab
@users
,
@issues
,
@merge_requests
,
@snippets
,
@commits
=
[],
[],
[],
[],
[]
@users
,
@issues
,
@merge_requests
,
@snippets
,
@commits
=
[],
[],
[],
[],
[]
end
end
def
analyze
(
string
)
def
analyze
(
string
,
project
)
parse_references
(
string
.
dup
)
parse_references
(
string
.
dup
,
project
)
end
end
# Given a valid project, resolve the extracted identifiers of the requested type to
# Given a valid project, resolve the extracted identifiers of the requested type to
# model objects.
# model objects.
def
users_for
(
project
)
def
users_for
(
project
)
users
.
map
do
|
identifier
|
users
.
map
do
|
entry
|
project
.
users
.
where
(
username:
identifier
).
first
project
.
users
.
where
(
username:
entry
[
:id
]
).
first
end
.
reject
(
&
:nil?
)
end
.
reject
(
&
:nil?
)
end
end
def
issues_for
(
project
)
def
issues_for
(
project
=
nil
)
issues
.
map
do
|
identifier
|
issues
.
map
do
|
entry
|
project
.
issues
.
where
(
iid:
identifier
).
first
if
should_lookup?
(
project
,
entry
[
:project
])
entry
[
:project
].
issues
.
where
(
iid:
entry
[
:id
]).
first
end
end
.
reject
(
&
:nil?
)
end
.
reject
(
&
:nil?
)
end
end
def
merge_requests_for
(
project
)
def
merge_requests_for
(
project
=
nil
)
merge_requests
.
map
do
|
identifier
|
merge_requests
.
map
do
|
entry
|
project
.
merge_requests
.
where
(
iid:
identifier
).
first
if
should_lookup?
(
project
,
entry
[
:project
])
entry
[
:project
].
merge_requests
.
where
(
iid:
entry
[
:id
]).
first
end
end
.
reject
(
&
:nil?
)
end
.
reject
(
&
:nil?
)
end
end
def
snippets_for
(
project
)
def
snippets_for
(
project
)
snippets
.
map
do
|
identifier
|
snippets
.
map
do
|
entry
|
project
.
snippets
.
where
(
id:
identifier
).
first
project
.
snippets
.
where
(
id:
entry
[
:id
]
).
first
end
.
reject
(
&
:nil?
)
end
.
reject
(
&
:nil?
)
end
end
def
commits_for
(
project
)
def
commits_for
(
project
=
nil
)
repo
=
project
.
repository
commits
.
map
do
|
entry
|
return
[]
if
repo
.
nil?
repo
=
entry
[
:project
].
repository
if
entry
[
:project
]
if
should_lookup?
(
project
,
entry
[
:project
])
commits
.
map
do
|
identifier
|
repo
.
commit
(
entry
[
:id
])
if
repo
repo
.
commit
(
identifier
)
end
end
.
reject
(
&
:nil?
)
end
.
reject
(
&
:nil?
)
end
end
private
private
def
reference_link
(
type
,
identifier
,
project
)
def
reference_link
(
type
,
identifier
,
project
,
_
)
# Append identifier to the appropriate collection.
# Append identifier to the appropriate collection.
send
(
"
#{
type
}
s"
)
<<
identifier
send
(
"
#{
type
}
s"
)
<<
{
project:
project
,
id:
identifier
}
end
def
should_lookup?
(
project
,
entry_project
)
if
entry_project
.
nil?
false
else
project
.
nil?
||
project
.
id
==
entry_project
.
id
end
end
end
end
end
end
end
spec/helpers/gitlab_markdown_helper_spec.rb
View file @
bb929c21
...
@@ -181,6 +181,76 @@ describe GitlabMarkdownHelper do
...
@@ -181,6 +181,76 @@ describe GitlabMarkdownHelper do
end
end
end
end
# Shared examples for referencing an object in a different project
#
# Expects the following attributes to be available in the example group:
#
# - object - The object itself
# - reference - The object reference string (e.g., #1234, $1234, !1234)
# - other_project - The project that owns the target object
#
# Currently limited to Snippets, Issues and MergeRequests
shared_examples
'cross-project referenced object'
do
let
(
:project_path
)
{
@other_project
.
path_with_namespace
}
let
(
:full_reference
)
{
"
#{
project_path
}#{
reference
}
"
}
let
(
:actual
)
{
"Reference to
#{
full_reference
}
"
}
let
(
:expected
)
do
if
object
.
is_a?
(
Commit
)
project_commit_path
(
@other_project
,
object
)
else
polymorphic_path
([
@other_project
,
object
])
end
end
it
'should link using a valid id'
do
gfm
(
actual
).
should
match
(
/
#{
expected
}
.*
#{
Regexp
.
escape
(
full_reference
)
}
/
)
end
it
'should link with adjacent text'
do
# Wrap the reference in parenthesis
gfm
(
actual
.
gsub
(
full_reference
,
"(
#{
full_reference
}
)"
)).
should
(
match
(
expected
)
)
# Append some text to the end of the reference
gfm
(
actual
.
gsub
(
full_reference
,
"
#{
full_reference
}
, right?"
)).
should
(
match
(
expected
)
)
end
it
'should keep whitespace intact'
do
actual
=
"Referenced
#{
full_reference
}
already."
expected
=
/Referenced <a.+>[^\s]+<\/a> already/
gfm
(
actual
).
should
match
(
expected
)
end
it
'should not link with an invalid id'
do
# Modify the reference string so it's still parsed, but is invalid
if
object
.
is_a?
(
Commit
)
reference
.
gsub!
(
/^(.).+$/
,
'\1'
+
'12345abcd'
)
else
reference
.
gsub!
(
/^(.)(\d+)$/
,
'\1'
+
(
'\2'
*
2
))
end
gfm
(
actual
).
should
==
actual
end
it
'should include a title attribute'
do
if
object
.
is_a?
(
Commit
)
title
=
object
.
link_title
else
title
=
"
#{
object
.
class
.
to_s
.
titlecase
}
:
#{
object
.
title
}
"
end
gfm
(
actual
).
should
match
(
/title="
#{
title
}
"/
)
end
it
'should include standard gfm classes'
do
css
=
object
.
class
.
to_s
.
underscore
gfm
(
actual
).
should
match
(
/class="\s?gfm gfm-
#{
css
}
\s?"/
)
end
end
describe
"referencing an issue"
do
describe
"referencing an issue"
do
let
(
:object
)
{
issue
}
let
(
:object
)
{
issue
}
let
(
:reference
)
{
"#
#{
issue
.
iid
}
"
}
let
(
:reference
)
{
"#
#{
issue
.
iid
}
"
}
...
@@ -188,6 +258,38 @@ describe GitlabMarkdownHelper do
...
@@ -188,6 +258,38 @@ describe GitlabMarkdownHelper do
include_examples
'referenced object'
include_examples
'referenced object'
end
end
context
'cross-repo references'
do
before
(
:all
)
do
@other_project
=
create
(
:project
,
:public
)
@commit2
=
@other_project
.
repository
.
commit
@issue2
=
create
(
:issue
,
project:
@other_project
)
@merge_request2
=
create
(
:merge_request
,
source_project:
@other_project
,
target_project:
@other_project
)
end
describe
'referencing an issue in another project'
do
let
(
:object
)
{
@issue2
}
let
(
:reference
)
{
"#
#{
@issue2
.
iid
}
"
}
include_examples
'cross-project referenced object'
end
describe
'referencing an merge request in another project'
do
let
(
:object
)
{
@merge_request2
}
let
(
:reference
)
{
"!
#{
@merge_request2
.
iid
}
"
}
include_examples
'cross-project referenced object'
end
describe
'referencing a commit in another project'
do
let
(
:object
)
{
@commit2
}
let
(
:reference
)
{
"@
#{
@commit2
.
id
}
"
}
include_examples
'cross-project referenced object'
end
end
describe
"referencing a Jira issue"
do
describe
"referencing a Jira issue"
do
let
(
:actual
)
{
"Reference to JIRA-
#{
issue
.
iid
}
"
}
let
(
:actual
)
{
"Reference to JIRA-
#{
issue
.
iid
}
"
}
let
(
:expected
)
{
"http://jira.example/browse/JIRA-
#{
issue
.
iid
}
"
}
let
(
:expected
)
{
"http://jira.example/browse/JIRA-
#{
issue
.
iid
}
"
}
...
...
spec/lib/gitlab/reference_extractor_spec.rb
View file @
bb929c21
...
@@ -2,45 +2,48 @@ require 'spec_helper'
...
@@ -2,45 +2,48 @@ require 'spec_helper'
describe
Gitlab
::
ReferenceExtractor
do
describe
Gitlab
::
ReferenceExtractor
do
it
'extracts username references'
do
it
'extracts username references'
do
subject
.
analyze
"this contains a @user reference"
subject
.
analyze
(
'this contains a @user reference'
,
nil
)
subject
.
users
.
should
==
[
"user"
]
subject
.
users
.
should
==
[
{
project:
nil
,
id:
'user'
}
]
end
end
it
'extracts issue references'
do
it
'extracts issue references'
do
subject
.
analyze
"this one talks about issue #1234"
subject
.
analyze
(
'this one talks about issue #1234'
,
nil
)
subject
.
issues
.
should
==
[
"1234"
]
subject
.
issues
.
should
==
[
{
project:
nil
,
id:
'1234'
}
]
end
end
it
'extracts JIRA issue references'
do
it
'extracts JIRA issue references'
do
Gitlab
.
config
.
gitlab
.
stub
(
:issues_tracker
).
and_return
(
"jira"
)
Gitlab
.
config
.
gitlab
.
stub
(
:issues_tracker
).
and_return
(
'jira'
)
subject
.
analyze
"this one talks about issue JIRA-1234"
subject
.
analyze
(
'this one talks about issue JIRA-1234'
,
nil
)
subject
.
issues
.
should
==
[
"JIRA-1234"
]
subject
.
issues
.
should
==
[
{
project:
nil
,
id:
'JIRA-1234'
}
]
end
end
it
'extracts merge request references'
do
it
'extracts merge request references'
do
subject
.
analyze
"and here's !43, a merge request"
subject
.
analyze
(
"and here's !43, a merge request"
,
nil
)
subject
.
merge_requests
.
should
==
[
"43"
]
subject
.
merge_requests
.
should
==
[
{
project:
nil
,
id:
'43'
}
]
end
end
it
'extracts snippet ids'
do
it
'extracts snippet ids'
do
subject
.
analyze
"snippets like $12 get extracted as well"
subject
.
analyze
(
'snippets like $12 get extracted as well'
,
nil
)
subject
.
snippets
.
should
==
[
"12"
]
subject
.
snippets
.
should
==
[
{
project:
nil
,
id:
'12'
}
]
end
end
it
'extracts commit shas'
do
it
'extracts commit shas'
do
subject
.
analyze
"commit shas 98cf0ae3 are pulled out as Strings"
subject
.
analyze
(
'commit shas 98cf0ae3 are pulled out as Strings'
,
nil
)
subject
.
commits
.
should
==
[
"98cf0ae3"
]
subject
.
commits
.
should
==
[
{
project:
nil
,
id:
'98cf0ae3'
}
]
end
end
it
'extracts multiple references and preserves their order'
do
it
'extracts multiple references and preserves their order'
do
subject
.
analyze
"@me and @you both care about this"
subject
.
analyze
(
'@me and @you both care about this'
,
nil
)
subject
.
users
.
should
==
[
"me"
,
"you"
]
subject
.
users
.
should
==
[
{
project:
nil
,
id:
'me'
},
{
project:
nil
,
id:
'you'
}
]
end
end
it
'leaves the original note unmodified'
do
it
'leaves the original note unmodified'
do
text
=
"issue #123 is just the worst, @user"
text
=
'issue #123 is just the worst, @user'
subject
.
analyze
text
subject
.
analyze
(
text
,
nil
)
text
.
should
==
"issue #123 is just the worst, @user"
text
.
should
==
'issue #123 is just the worst, @user'
end
end
it
'handles all possible kinds of references'
do
it
'handles all possible kinds of references'
do
...
@@ -59,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
...
@@ -59,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
project
.
team
<<
[
@u_foo
,
:reporter
]
project
.
team
<<
[
@u_foo
,
:reporter
]
project
.
team
<<
[
@u_bar
,
:guest
]
project
.
team
<<
[
@u_bar
,
:guest
]
subject
.
analyze
"@foo, @baduser, @bar, and @offteam"
subject
.
analyze
(
'@foo, @baduser, @bar, and @offteam'
,
project
)
subject
.
users_for
(
project
).
should
==
[
@u_foo
,
@u_bar
]
subject
.
users_for
(
project
).
should
==
[
@u_foo
,
@u_bar
]
end
end
...
@@ -67,7 +70,7 @@ describe Gitlab::ReferenceExtractor do
...
@@ -67,7 +70,7 @@ describe Gitlab::ReferenceExtractor do
@i0
=
create
(
:issue
,
project:
project
)
@i0
=
create
(
:issue
,
project:
project
)
@i1
=
create
(
:issue
,
project:
project
)
@i1
=
create
(
:issue
,
project:
project
)
subject
.
analyze
"#
#{
@i0
.
iid
}
, #
#{
@i1
.
iid
}
, and #999."
subject
.
analyze
(
"#
#{
@i0
.
iid
}
, #
#{
@i1
.
iid
}
, and #999."
,
project
)
subject
.
issues_for
(
project
).
should
==
[
@i0
,
@i1
]
subject
.
issues_for
(
project
).
should
==
[
@i0
,
@i1
]
end
end
...
@@ -75,7 +78,7 @@ describe Gitlab::ReferenceExtractor do
...
@@ -75,7 +78,7 @@ describe Gitlab::ReferenceExtractor do
@m0
=
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'aaa'
)
@m0
=
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'aaa'
)
@m1
=
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'bbb'
)
@m1
=
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'bbb'
)
subject
.
analyze
"!999, !
#{
@m1
.
iid
}
, and !
#{
@m0
.
iid
}
."
subject
.
analyze
(
"!999, !
#{
@m1
.
iid
}
, and !
#{
@m0
.
iid
}
."
,
project
)
subject
.
merge_requests_for
(
project
).
should
==
[
@m1
,
@m0
]
subject
.
merge_requests_for
(
project
).
should
==
[
@m1
,
@m0
]
end
end
...
@@ -84,14 +87,15 @@ describe Gitlab::ReferenceExtractor do
...
@@ -84,14 +87,15 @@ describe Gitlab::ReferenceExtractor do
@s1
=
create
(
:project_snippet
,
project:
project
)
@s1
=
create
(
:project_snippet
,
project:
project
)
@s2
=
create
(
:project_snippet
)
@s2
=
create
(
:project_snippet
)
subject
.
analyze
"$
#{
@s0
.
id
}
, $999, $
#{
@s2
.
id
}
, $
#{
@s1
.
id
}
"
subject
.
analyze
(
"$
#{
@s0
.
id
}
, $999, $
#{
@s2
.
id
}
, $
#{
@s1
.
id
}
"
,
project
)
subject
.
snippets_for
(
project
).
should
==
[
@s0
,
@s1
]
subject
.
snippets_for
(
project
).
should
==
[
@s0
,
@s1
]
end
end
it
'accesses valid commits'
do
it
'accesses valid commits'
do
commit
=
project
.
repository
.
commit
(
"master"
)
commit
=
project
.
repository
.
commit
(
'master'
)
subject
.
analyze
"this references commits
#{
commit
.
sha
[
0
..
6
]
}
and 012345"
subject
.
analyze
(
"this references commits
#{
commit
.
sha
[
0
..
6
]
}
and 012345"
,
project
)
extracted
=
subject
.
commits_for
(
project
)
extracted
=
subject
.
commits_for
(
project
)
extracted
.
should
have
(
1
).
item
extracted
.
should
have
(
1
).
item
extracted
[
0
].
sha
.
should
==
commit
.
sha
extracted
[
0
].
sha
.
should
==
commit
.
sha
...
...
spec/models/commit_spec.rb
View file @
bb929c21
...
@@ -53,11 +53,23 @@ eos
...
@@ -53,11 +53,23 @@ eos
describe
'#closes_issues'
do
describe
'#closes_issues'
do
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:other_project
)
{
create
:project
,
:public
}
let
(
:other_issue
)
{
create
:issue
,
project:
other_project
}
it
'detects issues that this commit is marked as closing'
do
it
'detects issues that this commit is marked as closing'
do
commit
.
stub
(
issue_closing_regex:
/^([Cc]loses|[Ff]ixes) #\d+/
,
safe_message:
"Fixes #
#{
issue
.
iid
}
"
)
stub_const
(
'Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX'
,
/Fixes #\d+/
)
commit
.
stub
(
safe_message:
"Fixes #
#{
issue
.
iid
}
"
)
commit
.
closes_issues
(
project
).
should
==
[
issue
]
commit
.
closes_issues
(
project
).
should
==
[
issue
]
end
end
it
'does not detect issues from other projects'
do
ext_ref
=
"
#{
other_project
.
path_with_namespace
}
#
#{
other_issue
.
iid
}
"
stub_const
(
'Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX'
,
/^([Cc]loses|[Ff]ixes)/
)
commit
.
stub
(
safe_message:
"Fixes
#{
ext_ref
}
"
)
commit
.
closes_issues
(
project
).
should
be_empty
end
end
end
it_behaves_like
'a mentionable'
do
it_behaves_like
'a mentionable'
do
...
...
spec/models/note_spec.rb
View file @
bb929c21
...
@@ -264,8 +264,8 @@ describe Note do
...
@@ -264,8 +264,8 @@ describe Note do
let
(
:project
)
{
create
:project
}
let
(
:project
)
{
create
:project
}
let
(
:author
)
{
create
:user
}
let
(
:author
)
{
create
:user
}
let
(
:issue
)
{
create
:issue
}
let
(
:issue
)
{
create
:issue
}
let
(
:commit0
)
{
double
'commit0'
,
gfm_reference:
'commit 123456'
}
let
(
:commit0
)
{
project
.
repository
.
commit
}
let
(
:commit1
)
{
double
'commit1'
,
gfm_reference:
'commit 654321'
}
let
(
:commit1
)
{
project
.
repository
.
commit
(
'HEAD~2'
)
}
before
do
before
do
Note
.
create_cross_reference_note
(
issue
,
commit0
,
author
,
project
)
Note
.
create_cross_reference_note
(
issue
,
commit0
,
author
,
project
)
...
...
spec/support/mentionable_shared_examples.rb
View file @
bb929c21
...
@@ -14,13 +14,23 @@ def common_mentionable_setup
...
@@ -14,13 +14,23 @@ def common_mentionable_setup
let
(
:mentioned_mr
)
{
create
:merge_request
,
:simple
,
source_project:
mproject
}
let
(
:mentioned_mr
)
{
create
:merge_request
,
:simple
,
source_project:
mproject
}
let
(
:mentioned_commit
)
{
double
(
'commit'
,
sha:
'1234567890abcdef'
).
as_null_object
}
let
(
:mentioned_commit
)
{
double
(
'commit'
,
sha:
'1234567890abcdef'
).
as_null_object
}
let
(
:ext_proj
)
{
create
:project
,
:public
}
let
(
:ext_issue
)
{
create
:issue
,
project:
ext_proj
}
let
(
:other_ext_issue
)
{
create
:issue
,
project:
ext_proj
}
let
(
:ext_mr
)
{
create
:merge_request
,
:simple
,
source_project:
ext_proj
}
let
(
:ext_commit
)
{
ext_proj
.
repository
.
commit
}
# Override to add known commits to the repository stub.
# Override to add known commits to the repository stub.
let
(
:extra_commits
)
{
[]
}
let
(
:extra_commits
)
{
[]
}
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+.
# to this string and place it in their +mentionable_text+.
let
(
:ref_string
)
do
let
(
:ref_string
)
do
"mentions #
#{
mentioned_issue
.
iid
}
twice #
#{
mentioned_issue
.
iid
}
, !
#{
mentioned_mr
.
iid
}
, "
+
"mentions #
#{
mentioned_issue
.
iid
}
twice #
#{
mentioned_issue
.
iid
}
, "
+
"!
#{
mentioned_mr
.
iid
}
, "
+
"
#{
ext_proj
.
path_with_namespace
}
#
#{
ext_issue
.
iid
}
, "
+
"
#{
ext_proj
.
path_with_namespace
}
!
#{
ext_mr
.
iid
}
, "
+
"
#{
ext_proj
.
path_with_namespace
}
@
#{
ext_commit
.
id
[
0
..
5
]
}
, "
+
"
#{
mentioned_commit
.
sha
[
0
..
5
]
}
and itself as
#{
backref_text
}
"
"
#{
mentioned_commit
.
sha
[
0
..
5
]
}
and itself as
#{
backref_text
}
"
end
end
...
@@ -45,14 +55,20 @@ shared_examples 'a mentionable' do
...
@@ -45,14 +55,20 @@ shared_examples 'a mentionable' do
# De-duplicate and omit itself
# De-duplicate and omit itself
refs
=
subject
.
references
(
mproject
)
refs
=
subject
.
references
(
mproject
)
refs
.
should
have
(
3
).
items
refs
.
should
have
(
6
).
items
refs
.
should
include
(
mentioned_issue
)
refs
.
should
include
(
mentioned_issue
)
refs
.
should
include
(
mentioned_mr
)
refs
.
should
include
(
mentioned_mr
)
refs
.
should
include
(
mentioned_commit
)
refs
.
should
include
(
mentioned_commit
)
refs
.
should
include
(
ext_issue
)
refs
.
should
include
(
ext_mr
)
refs
.
should
include
(
ext_commit
)
end
end
it
'creates cross-reference notes'
do
it
'creates cross-reference notes'
do
[
mentioned_issue
,
mentioned_mr
,
mentioned_commit
].
each
do
|
referenced
|
mentioned_objects
=
[
mentioned_issue
,
mentioned_mr
,
mentioned_commit
,
ext_issue
,
ext_mr
,
ext_commit
]
mentioned_objects
.
each
do
|
referenced
|
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
referenced
,
subject
.
local_reference
,
mauthor
,
mproject
)
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
referenced
,
subject
.
local_reference
,
mauthor
,
mproject
)
end
end
...
@@ -73,15 +89,25 @@ shared_examples 'an editable mentionable' do
...
@@ -73,15 +89,25 @@ shared_examples 'an editable mentionable' do
it_behaves_like
'a mentionable'
it_behaves_like
'a mentionable'
it
'creates new cross-reference notes when the mentionable text is edited'
do
it
'creates new cross-reference notes when the mentionable text is edited'
do
new_text
=
"this text still mentions #
#{
mentioned_issue
.
iid
}
and
#{
mentioned_commit
.
sha
[
0
..
5
]
}
, "
+
new_text
=
"still mentions #
#{
mentioned_issue
.
iid
}
, "
+
"but now it mentions #
#{
other_issue
.
iid
}
, too."
"
#{
mentioned_commit
.
sha
[
0
..
5
]
}
, "
+
"
#{
ext_issue
.
iid
}
, "
+
"new refs: #
#{
other_issue
.
iid
}
, "
+
"
#{
ext_proj
.
path_with_namespace
}
#
#{
other_ext_issue
.
iid
}
"
[
mentioned_issue
,
mentioned_commit
].
each
do
|
oldref
|
[
mentioned_issue
,
mentioned_commit
,
ext_issue
].
each
do
|
oldref
|
Note
.
should_not_receive
(
:create_cross_reference_note
).
with
(
oldref
,
subject
.
local_reference
,
Note
.
should_not_receive
(
:create_cross_reference_note
).
with
(
oldref
,
subject
.
local_reference
,
mauthor
,
mproject
)
mauthor
,
mproject
)
end
end
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
other_issue
,
subject
.
local_reference
,
mauthor
,
mproject
)
[
other_issue
,
other_ext_issue
].
each
do
|
newref
|
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
newref
,
subject
.
local_reference
,
mauthor
,
mproject
)
end
subject
.
save
subject
.
save
set_mentionable_text
.
call
(
new_text
)
set_mentionable_text
.
call
(
new_text
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment