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
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
551946a3
Commit
551946a3
authored
Aug 26, 2013
by
Dmitriy Zaporozhets
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4507 from smashwilson/linking-issues
Linking objects from GFM references
parents
2b36dee6
c8a115c0
Changes
32
Show whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
925 additions
and
50 deletions
+925
-50
CHANGELOG
CHANGELOG
+5
-1
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+5
-0
app/models/commit.rb
app/models/commit.rb
+26
-0
app/models/concerns/mentionable.rb
app/models/concerns/mentionable.rb
+69
-9
app/models/issue.rb
app/models/issue.rb
+7
-0
app/models/merge_request.rb
app/models/merge_request.rb
+15
-1
app/models/note.rb
app/models/note.rb
+34
-2
app/models/repository.rb
app/models/repository.rb
+1
-0
app/observers/activity_observer.rb
app/observers/activity_observer.rb
+2
-2
app/observers/base_observer.rb
app/observers/base_observer.rb
+4
-0
app/observers/issue_observer.rb
app/observers/issue_observer.rb
+5
-1
app/observers/merge_request_observer.rb
app/observers/merge_request_observer.rb
+6
-2
app/observers/note_observer.rb
app/observers/note_observer.rb
+12
-0
app/services/git_push_service.rb
app/services/git_push_service.rb
+75
-13
app/views/projects/merge_requests/show/_mr_box.html.haml
app/views/projects/merge_requests/show/_mr_box.html.haml
+7
-1
config/gitlab.yml.example
config/gitlab.yml.example
+5
-0
config/initializers/1_settings.rb
config/initializers/1_settings.rb
+1
-0
db/migrate/20130528184641_add_system_to_notes.rb
db/migrate/20130528184641_add_system_to_notes.rb
+16
-0
db/schema.rb
db/schema.rb
+1
-0
lib/gitlab/reference_extractor.rb
lib/gitlab/reference_extractor.rb
+59
-0
spec/lib/gitlab/reference_extractor_spec.rb
spec/lib/gitlab/reference_extractor_spec.rb
+95
-0
spec/models/commit_spec.rb
spec/models/commit_spec.rb
+21
-1
spec/models/issue_spec.rb
spec/models/issue_spec.rb
+6
-0
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+30
-1
spec/models/note_spec.rb
spec/models/note_spec.rb
+105
-3
spec/observers/activity_observer_spec.rb
spec/observers/activity_observer_spec.rb
+24
-0
spec/observers/issue_observer_spec.rb
spec/observers/issue_observer_spec.rb
+31
-3
spec/observers/merge_request_observer_spec.rb
spec/observers/merge_request_observer_spec.rb
+28
-8
spec/observers/note_observer_spec.rb
spec/observers/note_observer_spec.rb
+31
-1
spec/services/git_push_service_spec.rb
spec/services/git_push_service_spec.rb
+104
-1
spec/support/login_helpers.rb
spec/support/login_helpers.rb
+1
-0
spec/support/mentionable_shared_examples.rb
spec/support/mentionable_shared_examples.rb
+94
-0
No files found.
CHANGELOG
View file @
551946a3
v 6.1.0
- Link issues, merge requests, and commits when they reference each other with GFM
- Close issues automatically when pushing commits with a special message
v 6.0.0
v 6.0.0
- Feature: Replace teams with group membership
- Feature: Replace teams with group membership
We introduce group membership in 6.0 as a replacement for teams.
We introduce group membership in 6.0 as a replacement for teams.
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
551946a3
...
@@ -3,6 +3,7 @@ require 'gitlab/satellite/satellite'
...
@@ -3,6 +3,7 @@ require 'gitlab/satellite/satellite'
class
Projects::MergeRequestsController
<
Projects
::
ApplicationController
class
Projects::MergeRequestsController
<
Projects
::
ApplicationController
before_filter
:module_enabled
before_filter
:module_enabled
before_filter
:merge_request
,
only:
[
:edit
,
:update
,
:show
,
:commits
,
:diffs
,
:automerge
,
:automerge_check
,
:ci_status
]
before_filter
:merge_request
,
only:
[
:edit
,
:update
,
:show
,
:commits
,
:diffs
,
:automerge
,
:automerge_check
,
:ci_status
]
before_filter
:closes_issues
,
only:
[
:edit
,
:update
,
:show
,
:commits
,
:diffs
]
before_filter
:validates_merge_request
,
only:
[
:show
,
:diffs
]
before_filter
:validates_merge_request
,
only:
[
:show
,
:diffs
]
before_filter
:define_show_vars
,
only:
[
:show
,
:diffs
]
before_filter
:define_show_vars
,
only:
[
:show
,
:diffs
]
...
@@ -135,6 +136,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -135,6 +136,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request
||=
@project
.
merge_requests
.
find_by_iid!
(
params
[
:id
])
@merge_request
||=
@project
.
merge_requests
.
find_by_iid!
(
params
[
:id
])
end
end
def
closes_issues
@closes_issues
||=
@merge_request
.
closes_issues
end
def
authorize_modify_merge_request!
def
authorize_modify_merge_request!
return
render_404
unless
can?
(
current_user
,
:modify_merge_request
,
@merge_request
)
return
render_404
unless
can?
(
current_user
,
:modify_merge_request
,
@merge_request
)
end
end
...
...
app/models/commit.rb
View file @
551946a3
...
@@ -2,6 +2,9 @@ class Commit
...
@@ -2,6 +2,9 @@ class Commit
include
ActiveModel
::
Conversion
include
ActiveModel
::
Conversion
include
StaticModel
include
StaticModel
extend
ActiveModel
::
Naming
extend
ActiveModel
::
Naming
include
Mentionable
attr_mentionable
:safe_message
# Safe amount of files with diffs in one commit to render
# Safe amount of files with diffs in one commit to render
# Used to prevent 500 error on huge commits by suppressing diff
# Used to prevent 500 error on huge commits by suppressing diff
...
@@ -65,6 +68,29 @@ class Commit
...
@@ -65,6 +68,29 @@ class Commit
end
end
end
end
# Regular expression that identifies commit message clauses that trigger issue closing.
def
issue_closing_regex
@issue_closing_regex
||=
Regexp
.
new
(
Gitlab
.
config
.
gitlab
.
issue_closing_pattern
)
end
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def
closes_issues
project
md
=
issue_closing_regex
.
match
(
safe_message
)
if
md
extractor
=
Gitlab
::
ReferenceExtractor
.
new
extractor
.
analyze
(
md
[
0
])
extractor
.
issues_for
(
project
)
else
[]
end
end
# Mentionable override.
def
gfm_reference
"commit
#{
sha
[
0
..
5
]
}
"
end
def
method_missing
(
m
,
*
args
,
&
block
)
def
method_missing
(
m
,
*
args
,
&
block
)
@raw
.
send
(
m
,
*
args
,
&
block
)
@raw
.
send
(
m
,
*
args
,
&
block
)
end
end
...
...
app/models/concerns/mentionable.rb
View file @
551946a3
# == Mentionable concern
# == Mentionable concern
#
#
# Contains common functionality shared between Issues and Notes
# Contains functionality related to objects that can mention Users, Issues, MergeRequests, or Commits by
# GFM references.
#
#
# Used by Issue, Note
# Used by Issue, Note
, MergeRequest, and Commit.
#
#
module
Mentionable
module
Mentionable
extend
ActiveSupport
::
Concern
extend
ActiveSupport
::
Concern
module
ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
def
attr_mentionable
*
attrs
mentionable_attrs
.
concat
(
attrs
.
map
(
&
:to_s
))
end
# Accessor for attributes marked mentionable.
def
mentionable_attrs
@mentionable_attrs
||=
[]
end
end
# Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must
# be overridden if this model object can be referenced directly by GFM notation.
def
gfm_reference
raise
NotImplementedError
.
new
(
"
#{
self
.
class
}
does not implement #gfm_reference"
)
end
# Construct a String that contains possible GFM references.
def
mentionable_text
self
.
class
.
mentionable_attrs
.
map
{
|
attr
|
send
(
attr
)
||
''
}.
join
end
# The GFM reference to this Mentionable, which shouldn't be included in its #references.
def
local_reference
self
end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def
has_mentioned?
target
Note
.
cross_reference_exists?
(
target
,
local_reference
)
end
def
mentioned_users
def
mentioned_users
users
=
[]
users
=
[]
return
users
if
mentionable_text
.
blank?
return
users
if
mentionable_text
.
blank?
...
@@ -24,14 +59,39 @@ module Mentionable
...
@@ -24,14 +59,39 @@ module Mentionable
users
.
uniq
users
.
uniq
end
end
def
mentionable_text
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
if
self
.
class
==
Issue
def
references
p
=
project
,
text
=
mentionable_text
description
return
[]
if
text
.
blank?
elsif
self
.
class
==
Note
ext
=
Gitlab
::
ReferenceExtractor
.
new
note
ext
.
analyze
(
text
)
else
(
ext
.
issues_for
(
p
)
+
ext
.
merge_requests_for
(
p
)
+
ext
.
commits_for
(
p
)).
uniq
-
[
local_reference
]
nil
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def
create_cross_references!
p
=
project
,
a
=
author
,
without
=
[]
refs
=
references
(
p
)
-
without
refs
.
each
do
|
ref
|
Note
.
create_cross_reference_note
(
ref
,
local_reference
,
a
,
p
)
end
end
end
end
# If the mentionable_text field is about to change, locate any *added* references and create cross references for
# them. Invoke from an observer's #before_save implementation.
def
notice_added_references
p
=
project
,
a
=
author
ch
=
changed_attributes
original
,
mentionable_changed
=
""
,
false
self
.
class
.
mentionable_attrs
.
each
do
|
attr
|
if
ch
[
attr
]
original
<<
ch
[
attr
]
mentionable_changed
=
true
end
end
# Only proceed if the saved changes actually include a chance to an attr_mentionable field.
return
unless
mentionable_changed
preexisting
=
references
(
p
,
original
)
create_cross_references!
(
p
,
a
,
preexisting
)
end
end
end
app/models/issue.rb
View file @
551946a3
...
@@ -32,6 +32,7 @@ class Issue < ActiveRecord::Base
...
@@ -32,6 +32,7 @@ class Issue < ActiveRecord::Base
attr_accessible
:title
,
:assignee_id
,
:position
,
:description
,
attr_accessible
:title
,
:assignee_id
,
:position
,
:description
,
:milestone_id
,
:label_list
,
:author_id_of_changes
,
:milestone_id
,
:label_list
,
:author_id_of_changes
,
:state_event
:state_event
attr_mentionable
:title
,
:description
acts_as_taggable_on
:labels
acts_as_taggable_on
:labels
...
@@ -56,4 +57,10 @@ class Issue < ActiveRecord::Base
...
@@ -56,4 +57,10 @@ class Issue < ActiveRecord::Base
# Both open and reopened issues should be listed as opened
# Both open and reopened issues should be listed as opened
scope
:opened
,
->
{
with_state
(
:opened
,
:reopened
)
}
scope
:opened
,
->
{
with_state
(
:opened
,
:reopened
)
}
# Mentionable overrides.
def
gfm_reference
"issue #
#{
iid
}
"
end
end
end
app/models/merge_request.rb
View file @
551946a3
...
@@ -31,7 +31,7 @@ class MergeRequest < ActiveRecord::Base
...
@@ -31,7 +31,7 @@ class MergeRequest < ActiveRecord::Base
belongs_to
:source_project
,
foreign_key: :source_project_id
,
class_name:
"Project"
belongs_to
:source_project
,
foreign_key: :source_project_id
,
class_name:
"Project"
attr_accessible
:title
,
:assignee_id
,
:source_project_id
,
:source_branch
,
:target_project_id
,
:target_branch
,
:milestone_id
,
:author_id_of_changes
,
:state_event
attr_accessible
:title
,
:assignee_id
,
:source_project_id
,
:source_branch
,
:target_project_id
,
:target_branch
,
:milestone_id
,
:author_id_of_changes
,
:state_event
attr_mentionable
:title
attr_accessor
:should_remove_source_branch
attr_accessor
:should_remove_source_branch
...
@@ -255,6 +255,20 @@ class MergeRequest < ActiveRecord::Base
...
@@ -255,6 +255,20 @@ class MergeRequest < ActiveRecord::Base
target_project
target_project
end
end
# Return the set of issues that will be closed if this merge request is accepted.
def
closes_issues
if
target_branch
==
project
.
default_branch
unmerged_commits
.
map
{
|
c
|
c
.
closes_issues
(
project
)
}.
flatten
.
uniq
.
sort_by
(
&
:id
)
else
[]
end
end
# Mentionable override.
def
gfm_reference
"merge request !
#{
iid
}
"
end
private
private
def
dump_commits
(
commits
)
def
dump_commits
(
commits
)
...
...
app/models/note.rb
View file @
551946a3
...
@@ -24,6 +24,7 @@ class Note < ActiveRecord::Base
...
@@ -24,6 +24,7 @@ class Note < ActiveRecord::Base
attr_accessible
:note
,
:noteable
,
:noteable_id
,
:noteable_type
,
:project_id
,
attr_accessible
:note
,
:noteable
,
:noteable_id
,
:noteable_type
,
:project_id
,
:attachment
,
:line_code
,
:commit_id
:attachment
,
:line_code
,
:commit_id
attr_mentionable
:note
belongs_to
:project
belongs_to
:project
belongs_to
:noteable
,
polymorphic:
true
belongs_to
:noteable
,
polymorphic:
true
...
@@ -54,15 +55,36 @@ class Note < ActiveRecord::Base
...
@@ -54,15 +55,36 @@ class Note < ActiveRecord::Base
serialize
:st_diff
serialize
:st_diff
before_create
:set_diff
,
if:
->
(
n
)
{
n
.
line_code
.
present?
}
before_create
:set_diff
,
if:
->
(
n
)
{
n
.
line_code
.
present?
}
def
self
.
create_status_change_note
(
noteable
,
project
,
author
,
status
)
def
self
.
create_status_change_note
(
noteable
,
project
,
author
,
status
,
source
)
body
=
"_Status changed to
#{
status
}#{
' by '
+
source
.
gfm_reference
if
source
}
_"
create
({
noteable:
noteable
,
project:
project
,
author:
author
,
note:
body
,
system:
true
},
without_protection:
true
)
end
# +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
# Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
def
self
.
create_cross_reference_note
(
noteable
,
mentioner
,
author
,
project
)
create
({
create
({
noteable:
noteable
,
noteable:
noteable
,
commit_id:
(
noteable
.
sha
if
noteable
.
respond_to?
:sha
),
project:
project
,
project:
project
,
author:
author
,
author:
author
,
note:
"_Status changed to
#{
status
}
_"
note:
"_mentioned in
#{
mentioner
.
gfm_reference
}
_"
,
system:
true
},
without_protection:
true
)
},
without_protection:
true
)
end
end
# Determine whether or not a cross-reference note already exists.
def
self
.
cross_reference_exists?
(
noteable
,
mentioner
)
where
(
noteable_id:
noteable
.
id
,
system:
true
,
note:
"_mentioned in
#{
mentioner
.
gfm_reference
}
_"
).
any?
end
def
commit_author
def
commit_author
@commit_author
||=
@commit_author
||=
project
.
users
.
find_by_email
(
noteable
.
author_email
)
||
project
.
users
.
find_by_email
(
noteable
.
author_email
)
||
...
@@ -191,6 +213,16 @@ class Note < ActiveRecord::Base
...
@@ -191,6 +213,16 @@ class Note < ActiveRecord::Base
for_issue?
||
(
for_merge_request?
&&
!
for_diff_line?
)
for_issue?
||
(
for_merge_request?
&&
!
for_diff_line?
)
end
end
# Mentionable override.
def
gfm_reference
noteable
.
gfm_reference
end
# Mentionable override.
def
local_reference
noteable
end
def
noteable_type_name
def
noteable_type_name
if
noteable_type
.
present?
if
noteable_type
.
present?
noteable_type
.
downcase
noteable_type
.
downcase
...
...
app/models/repository.rb
View file @
551946a3
...
@@ -18,6 +18,7 @@ class Repository
...
@@ -18,6 +18,7 @@ class Repository
end
end
def
commit
(
id
=
nil
)
def
commit
(
id
=
nil
)
return
nil
unless
raw_repository
commit
=
Gitlab
::
Git
::
Commit
.
find
(
raw_repository
,
id
)
commit
=
Gitlab
::
Git
::
Commit
.
find
(
raw_repository
,
id
)
commit
=
Commit
.
new
(
commit
)
if
commit
commit
=
Commit
.
new
(
commit
)
if
commit
commit
commit
...
...
app/observers/activity_observer.rb
View file @
551946a3
...
@@ -5,8 +5,8 @@ class ActivityObserver < BaseObserver
...
@@ -5,8 +5,8 @@ class ActivityObserver < BaseObserver
event_author_id
=
record
.
author_id
event_author_id
=
record
.
author_id
if
record
.
kind_of?
(
Note
)
if
record
.
kind_of?
(
Note
)
# Skip system
status notes like 'status changed to close'
# Skip system
notes, like status changes and cross-references.
return
true
if
record
.
note
.
include?
(
"_Status changed to "
)
return
true
if
record
.
system?
# Skip wall notes to prevent spamming of dashboard
# Skip wall notes to prevent spamming of dashboard
return
true
if
record
.
noteable_type
.
blank?
return
true
if
record
.
noteable_type
.
blank?
...
...
app/observers/base_observer.rb
View file @
551946a3
...
@@ -10,4 +10,8 @@ class BaseObserver < ActiveRecord::Observer
...
@@ -10,4 +10,8 @@ class BaseObserver < ActiveRecord::Observer
def
current_user
def
current_user
Thread
.
current
[
:current_user
]
Thread
.
current
[
:current_user
]
end
end
def
current_commit
Thread
.
current
[
:current_commit
]
end
end
end
app/observers/issue_observer.rb
View file @
551946a3
class
IssueObserver
<
BaseObserver
class
IssueObserver
<
BaseObserver
def
after_create
(
issue
)
def
after_create
(
issue
)
notification
.
new_issue
(
issue
,
current_user
)
notification
.
new_issue
(
issue
,
current_user
)
issue
.
create_cross_references!
(
issue
.
project
,
current_user
)
end
end
def
after_close
(
issue
,
transition
)
def
after_close
(
issue
,
transition
)
...
@@ -17,12 +19,14 @@ class IssueObserver < BaseObserver
...
@@ -17,12 +19,14 @@ class IssueObserver < BaseObserver
if
issue
.
is_being_reassigned?
if
issue
.
is_being_reassigned?
notification
.
reassigned_issue
(
issue
,
current_user
)
notification
.
reassigned_issue
(
issue
,
current_user
)
end
end
issue
.
notice_added_references
(
issue
.
project
,
current_user
)
end
end
protected
protected
# Create issue note with service comment like 'Status changed to closed'
# Create issue note with service comment like 'Status changed to closed'
def
create_note
(
issue
)
def
create_note
(
issue
)
Note
.
create_status_change_note
(
issue
,
issue
.
project
,
current_user
,
issue
.
state
)
Note
.
create_status_change_note
(
issue
,
issue
.
project
,
current_user
,
issue
.
state
,
current_commit
)
end
end
end
end
app/observers/merge_request_observer.rb
View file @
551946a3
...
@@ -7,11 +7,13 @@ class MergeRequestObserver < ActivityObserver
...
@@ -7,11 +7,13 @@ class MergeRequestObserver < ActivityObserver
end
end
notification
.
new_merge_request
(
merge_request
,
current_user
)
notification
.
new_merge_request
(
merge_request
,
current_user
)
merge_request
.
create_cross_references!
(
merge_request
.
project
,
current_user
)
end
end
def
after_close
(
merge_request
,
transition
)
def
after_close
(
merge_request
,
transition
)
create_event
(
merge_request
,
Event
::
CLOSED
)
create_event
(
merge_request
,
Event
::
CLOSED
)
Note
.
create_status_change_note
(
merge_request
,
merge_request
.
target_project
,
current_user
,
merge_request
.
state
)
Note
.
create_status_change_note
(
merge_request
,
merge_request
.
target_project
,
current_user
,
merge_request
.
state
,
nil
)
notification
.
close_mr
(
merge_request
,
current_user
)
notification
.
close_mr
(
merge_request
,
current_user
)
end
end
...
@@ -33,11 +35,13 @@ class MergeRequestObserver < ActivityObserver
...
@@ -33,11 +35,13 @@ class MergeRequestObserver < ActivityObserver
def
after_reopen
(
merge_request
,
transition
)
def
after_reopen
(
merge_request
,
transition
)
create_event
(
merge_request
,
Event
::
REOPENED
)
create_event
(
merge_request
,
Event
::
REOPENED
)
Note
.
create_status_change_note
(
merge_request
,
merge_request
.
target_project
,
current_user
,
merge_request
.
state
)
Note
.
create_status_change_note
(
merge_request
,
merge_request
.
target_project
,
current_user
,
merge_request
.
state
,
nil
)
end
end
def
after_update
(
merge_request
)
def
after_update
(
merge_request
)
notification
.
reassigned_merge_request
(
merge_request
,
current_user
)
if
merge_request
.
is_being_reassigned?
notification
.
reassigned_merge_request
(
merge_request
,
current_user
)
if
merge_request
.
is_being_reassigned?
merge_request
.
notice_added_references
(
merge_request
.
project
,
current_user
)
end
end
def
create_event
(
record
,
status
)
def
create_event
(
record
,
status
)
...
...
app/observers/note_observer.rb
View file @
551946a3
class
NoteObserver
<
BaseObserver
class
NoteObserver
<
BaseObserver
def
after_create
(
note
)
def
after_create
(
note
)
notification
.
new_note
(
note
)
notification
.
new_note
(
note
)
unless
note
.
system?
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
note
.
references
.
each
do
|
mentioned
|
Note
.
create_cross_reference_note
(
mentioned
,
note
.
noteable
,
note
.
author
,
note
.
project
)
end
end
end
def
after_update
(
note
)
note
.
notice_added_references
(
note
.
project
,
current_user
)
end
end
end
end
app/services/git_push_service.rb
View file @
551946a3
class
GitPushService
class
GitPushService
attr_accessor
:project
,
:user
,
:push_data
attr_accessor
:project
,
:user
,
:push_data
,
:push_commits
# This method will be called after each git update
# This method will be called after each git update
# and only if the provided user and project is present in GitLab.
# and only if the provided user and project is present in GitLab.
#
#
# All callbacks for post receive action should be placed here.
# All callbacks for post receive action should be placed here.
#
#
# Now this method do next:
# Next, this method:
# 1. Ensure project satellite exists
# 1. Creates the push event
# 2. Update merge requests
# 2. Ensures that the project satellite exists
# 3. Execute project web hooks
# 3. Updates merge requests
# 4. Execute project services
# 4. Recognizes cross-references from commit messages
# 5. Create Push Event
# 5. Executes the project's web hooks
# 6. Executes the project's services
#
#
def
execute
(
project
,
user
,
oldrev
,
newrev
,
ref
)
def
execute
(
project
,
user
,
oldrev
,
newrev
,
ref
)
@project
,
@user
=
project
,
user
@project
,
@user
=
project
,
user
# Collect data for this git push
# Collect data for this git push
@push_commits
=
project
.
repository
.
commits_between
(
oldrev
,
newrev
)
@push_data
=
post_receive_data
(
oldrev
,
newrev
,
ref
)
@push_data
=
post_receive_data
(
oldrev
,
newrev
,
ref
)
create_push_event
create_push_event
...
@@ -25,11 +27,27 @@ class GitPushService
...
@@ -25,11 +27,27 @@ class GitPushService
project
.
discover_default_branch
project
.
discover_default_branch
project
.
repository
.
expire_cache
project
.
repository
.
expire_cache
if
push_to_branch?
(
ref
,
oldrev
)
if
push_to_
existing_
branch?
(
ref
,
oldrev
)
project
.
update_merge_requests
(
oldrev
,
newrev
,
ref
,
@user
)
project
.
update_merge_requests
(
oldrev
,
newrev
,
ref
,
@user
)
process_commit_messages
(
ref
)
project
.
execute_hooks
(
@push_data
.
dup
)
project
.
execute_hooks
(
@push_data
.
dup
)
project
.
execute_services
(
@push_data
.
dup
)
project
.
execute_services
(
@push_data
.
dup
)
end
end
if
push_to_new_branch?
(
ref
,
oldrev
)
# Re-find the pushed commits.
if
is_default_branch?
(
ref
)
# Initial push to the default branch. Take the full history of that branch as "newly pushed".
@push_commits
=
project
.
repository
.
commits
(
newrev
)
else
# Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later.
@push_commits
=
project
.
repository
.
commits_between
(
project
.
default_branch
,
newrev
)
end
process_commit_messages
(
ref
)
end
end
end
# This method provide a sample data
# This method provide a sample data
...
@@ -45,7 +63,7 @@ class GitPushService
...
@@ -45,7 +63,7 @@ class GitPushService
protected
protected
def
create_push_event
def
create_push_event
Event
.
create
(
Event
.
create
!
(
project:
project
,
project:
project
,
action:
Event
::
PUSHED
,
action:
Event
::
PUSHED
,
data:
push_data
,
data:
push_data
,
...
@@ -53,6 +71,36 @@ class GitPushService
...
@@ -53,6 +71,36 @@ class GitPushService
)
)
end
end
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
def
process_commit_messages
ref
is_default_branch
=
is_default_branch?
(
ref
)
@push_commits
.
each
do
|
commit
|
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
issues_to_close
=
commit
.
closes_issues
(
project
)
author
=
commit_user
(
commit
)
if
!
issues_to_close
.
empty?
&&
is_default_branch
Thread
.
current
[
:current_user
]
=
author
Thread
.
current
[
:current_commit
]
=
commit
issues_to_close
.
each
{
|
i
|
i
.
close
&&
i
.
save
}
end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit
# being pushed to a different branch).
refs
=
commit
.
references
(
project
)
-
issues_to_close
refs
.
reject!
{
|
r
|
commit
.
has_mentioned?
(
r
)
}
refs
.
each
do
|
r
|
Note
.
create_cross_reference_note
(
r
,
commit
,
author
,
project
)
end
end
end
# Produce a hash of post-receive data
# Produce a hash of post-receive data
#
#
# data = {
# data = {
...
@@ -72,8 +120,6 @@ class GitPushService
...
@@ -72,8 +120,6 @@ class GitPushService
# }
# }
#
#
def
post_receive_data
(
oldrev
,
newrev
,
ref
)
def
post_receive_data
(
oldrev
,
newrev
,
ref
)
push_commits
=
project
.
repository
.
commits_between
(
oldrev
,
newrev
)
# Total commits count
# Total commits count
push_commits_count
=
push_commits
.
size
push_commits_count
=
push_commits
.
size
...
@@ -116,10 +162,26 @@ class GitPushService
...
@@ -116,10 +162,26 @@ class GitPushService
data
data
end
end
def
push_to_branch?
ref
,
oldrev
def
push_to_
existing_
branch?
ref
,
oldrev
ref_parts
=
ref
.
split
(
'/'
)
ref_parts
=
ref
.
split
(
'/'
)
# Return if this is not a push to a branch (e.g. new commits)
# Return if this is not a push to a branch (e.g. new commits)
!
(
ref_parts
[
1
]
!~
/heads/
||
oldrev
==
"00000000000000000000000000000000"
)
ref_parts
[
1
]
=~
/heads/
&&
oldrev
!=
"0000000000000000000000000000000000000000"
end
def
push_to_new_branch?
ref
,
oldrev
ref_parts
=
ref
.
split
(
'/'
)
ref_parts
[
1
]
=~
/heads/
&&
oldrev
==
"0000000000000000000000000000000000000000"
end
def
is_default_branch?
ref
ref
==
"refs/heads/
#{
project
.
default_branch
}
"
end
def
commit_user
commit
User
.
where
(
email:
commit
.
author_email
).
first
||
User
.
where
(
name:
commit
.
author_name
).
first
||
user
end
end
end
end
app/views/projects/merge_requests/show/_mr_box.html.haml
View file @
551946a3
...
@@ -33,4 +33,10 @@
...
@@ -33,4 +33,10 @@
%i
.icon-ok
%i
.icon-ok
Merged by
#{
link_to_member
(
@project
,
@merge_request
.
merge_event
.
author
)
}
Merged by
#{
link_to_member
(
@project
,
@merge_request
.
merge_event
.
author
)
}
#{
time_ago_in_words
(
@merge_request
.
merge_event
.
created_at
)
}
ago.
#{
time_ago_in_words
(
@merge_request
.
merge_event
.
created_at
)
}
ago.
-
if
!
@closes_issues
.
empty?
&&
@merge_request
.
opened?
.ui-box-bottom.alert-info
%span
%i
.icon-ok
Accepting this merge request will close
#{
@closes_issues
.
size
==
1
?
'issue'
:
'issues'
}
=
succeed
'.'
do
!=
gfm
(
@closes_issues
.
map
{
|
i
|
"#
#{
i
.
iid
}
"
}.
to_sentence
)
config/gitlab.yml.example
View file @
551946a3
...
@@ -46,6 +46,11 @@ production: &base
...
@@ -46,6 +46,11 @@ production: &base
## Users management
## Users management
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
## Automatic issue closing
# If a commit message matches this regular express, all issues referenced from the matched text will be closed
# if it's pushed to a project's default branch.
# issue_closing_pattern: "^([Cc]loses|[Ff]ixes) +#\d+"
## Default project features settings
## Default project features settings
default_projects_features:
default_projects_features:
issues: true
issues: true
...
...
config/initializers/1_settings.rb
View file @
551946a3
...
@@ -68,6 +68,7 @@ rescue ArgumentError # no user configured
...
@@ -68,6 +68,7 @@ rescue ArgumentError # no user configured
end
end
Settings
.
gitlab
[
'signup_enabled'
]
||=
false
Settings
.
gitlab
[
'signup_enabled'
]
||=
false
Settings
.
gitlab
[
'username_changing_enabled'
]
=
true
if
Settings
.
gitlab
[
'username_changing_enabled'
].
nil?
Settings
.
gitlab
[
'username_changing_enabled'
]
=
true
if
Settings
.
gitlab
[
'username_changing_enabled'
].
nil?
Settings
.
gitlab
[
'issue_closing_pattern'
]
=
'^([Cc]loses|[Ff]ixes) #(\d+)'
if
Settings
.
gitlab
[
'issue_closing_pattern'
].
nil?
Settings
.
gitlab
[
'default_projects_features'
]
||=
{}
Settings
.
gitlab
[
'default_projects_features'
]
||=
{}
Settings
.
gitlab
.
default_projects_features
[
'issues'
]
=
true
if
Settings
.
gitlab
.
default_projects_features
[
'issues'
].
nil?
Settings
.
gitlab
.
default_projects_features
[
'issues'
]
=
true
if
Settings
.
gitlab
.
default_projects_features
[
'issues'
].
nil?
Settings
.
gitlab
.
default_projects_features
[
'merge_requests'
]
=
true
if
Settings
.
gitlab
.
default_projects_features
[
'merge_requests'
].
nil?
Settings
.
gitlab
.
default_projects_features
[
'merge_requests'
]
=
true
if
Settings
.
gitlab
.
default_projects_features
[
'merge_requests'
].
nil?
...
...
db/migrate/20130528184641_add_system_to_notes.rb
0 → 100644
View file @
551946a3
class
AddSystemToNotes
<
ActiveRecord
::
Migration
class
Note
<
ActiveRecord
::
Base
end
def
up
add_column
:notes
,
:system
,
:boolean
,
default:
false
,
null:
false
Note
.
reset_column_information
Note
.
update_all
(
system:
false
)
Note
.
where
(
"note like '_status changed to%'"
).
update_all
(
system:
true
)
end
def
down
remove_column
:notes
,
:system
end
end
db/schema.rb
View file @
551946a3
...
@@ -152,6 +152,7 @@ ActiveRecord::Schema.define(:version => 20130821090531) do
...
@@ -152,6 +152,7 @@ ActiveRecord::Schema.define(:version => 20130821090531) do
t
.
string
"commit_id"
t
.
string
"commit_id"
t
.
integer
"noteable_id"
t
.
integer
"noteable_id"
t
.
text
"st_diff"
t
.
text
"st_diff"
t
.
boolean
"system"
,
:default
=>
false
,
:null
=>
false
end
end
add_index
"notes"
,
[
"author_id"
],
:name
=>
"index_notes_on_author_id"
add_index
"notes"
,
[
"author_id"
],
:name
=>
"index_notes_on_author_id"
...
...
lib/gitlab/reference_extractor.rb
0 → 100644
View file @
551946a3
module
Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class
ReferenceExtractor
attr_accessor
:users
,
:issues
,
:merge_requests
,
:snippets
,
:commits
include
Markdown
def
initialize
@users
,
@issues
,
@merge_requests
,
@snippets
,
@commits
=
[],
[],
[],
[],
[]
end
def
analyze
string
parse_references
(
string
.
dup
)
end
# Given a valid project, resolve the extracted identifiers of the requested type to
# model objects.
def
users_for
project
users
.
map
do
|
identifier
|
project
.
users
.
where
(
username:
identifier
).
first
end
.
reject
(
&
:nil?
)
end
def
issues_for
project
issues
.
map
do
|
identifier
|
project
.
issues
.
where
(
iid:
identifier
).
first
end
.
reject
(
&
:nil?
)
end
def
merge_requests_for
project
merge_requests
.
map
do
|
identifier
|
project
.
merge_requests
.
where
(
iid:
identifier
).
first
end
.
reject
(
&
:nil?
)
end
def
snippets_for
project
snippets
.
map
do
|
identifier
|
project
.
snippets
.
where
(
id:
identifier
).
first
end
.
reject
(
&
:nil?
)
end
def
commits_for
project
repo
=
project
.
repository
return
[]
if
repo
.
nil?
commits
.
map
do
|
identifier
|
repo
.
commit
(
identifier
)
end
.
reject
(
&
:nil?
)
end
private
def
reference_link
type
,
identifier
# Append identifier to the appropriate collection.
send
(
"
#{
type
}
s"
)
<<
identifier
end
end
end
spec/lib/gitlab/reference_extractor_spec.rb
0 → 100644
View file @
551946a3
require
'spec_helper'
describe
Gitlab
::
ReferenceExtractor
do
it
'extracts username references'
do
subject
.
analyze
"this contains a @user reference"
subject
.
users
.
should
==
[
"user"
]
end
it
'extracts issue references'
do
subject
.
analyze
"this one talks about issue #1234"
subject
.
issues
.
should
==
[
"1234"
]
end
it
'extracts merge request references'
do
subject
.
analyze
"and here's !43, a merge request"
subject
.
merge_requests
.
should
==
[
"43"
]
end
it
'extracts snippet ids'
do
subject
.
analyze
"snippets like $12 get extracted as well"
subject
.
snippets
.
should
==
[
"12"
]
end
it
'extracts commit shas'
do
subject
.
analyze
"commit shas 98cf0ae3 are pulled out as Strings"
subject
.
commits
.
should
==
[
"98cf0ae3"
]
end
it
'extracts multiple references and preserves their order'
do
subject
.
analyze
"@me and @you both care about this"
subject
.
users
.
should
==
[
"me"
,
"you"
]
end
it
'leaves the original note unmodified'
do
text
=
"issue #123 is just the worst, @user"
subject
.
analyze
text
text
.
should
==
"issue #123 is just the worst, @user"
end
it
'handles all possible kinds of references'
do
accessors
=
Gitlab
::
Markdown
::
TYPES
.
map
{
|
t
|
"
#{
t
}
s"
.
to_sym
}
subject
.
should
respond_to
(
*
accessors
)
end
context
'with a project'
do
let
(
:project
)
{
create
(
:project_with_code
)
}
it
'accesses valid user objects on the project team'
do
@u_foo
=
create
(
:user
,
username:
'foo'
)
@u_bar
=
create
(
:user
,
username:
'bar'
)
create
(
:user
,
username:
'offteam'
)
project
.
team
<<
[
@u_foo
,
:reporter
]
project
.
team
<<
[
@u_bar
,
:guest
]
subject
.
analyze
"@foo, @baduser, @bar, and @offteam"
subject
.
users_for
(
project
).
should
==
[
@u_foo
,
@u_bar
]
end
it
'accesses valid issue objects'
do
@i0
=
create
(
:issue
,
project:
project
)
@i1
=
create
(
:issue
,
project:
project
)
subject
.
analyze
"#
#{
@i0
.
iid
}
, #
#{
@i1
.
iid
}
, and #999."
subject
.
issues_for
(
project
).
should
==
[
@i0
,
@i1
]
end
it
'accesses valid merge requests'
do
@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'
)
subject
.
analyze
"!999, !
#{
@m1
.
iid
}
, and !
#{
@m0
.
iid
}
."
subject
.
merge_requests_for
(
project
).
should
==
[
@m1
,
@m0
]
end
it
'accesses valid snippets'
do
@s0
=
create
(
:project_snippet
,
project:
project
)
@s1
=
create
(
:project_snippet
,
project:
project
)
@s2
=
create
(
:project_snippet
)
subject
.
analyze
"$
#{
@s0
.
id
}
, $999, $
#{
@s2
.
id
}
, $
#{
@s1
.
id
}
"
subject
.
snippets_for
(
project
).
should
==
[
@s0
,
@s1
]
end
it
'accesses valid commits'
do
commit
=
project
.
repository
.
commit
(
"master"
)
subject
.
analyze
"this references commits
#{
commit
.
sha
[
0
..
6
]
}
and 012345"
extracted
=
subject
.
commits_for
(
project
)
extracted
.
should
have
(
1
).
item
extracted
[
0
].
sha
.
should
==
commit
.
sha
extracted
[
0
].
message
.
should
==
commit
.
message
end
end
end
spec/models/commit_spec.rb
View file @
551946a3
require
'spec_helper'
require
'spec_helper'
describe
Commit
do
describe
Commit
do
let
(
:commit
)
{
create
(
:project_with_code
).
repository
.
commit
}
let
(
:project
)
{
create
:project_with_code
}
let
(
:commit
)
{
project
.
repository
.
commit
}
describe
'#title'
do
describe
'#title'
do
it
"returns no_commit_message when safe_message is blank"
do
it
"returns no_commit_message when safe_message is blank"
do
...
@@ -46,4 +47,23 @@ describe Commit do
...
@@ -46,4 +47,23 @@ describe Commit do
it
{
should
respond_to
(
:id
)
}
it
{
should
respond_to
(
:id
)
}
it
{
should
respond_to
(
:to_patch
)
}
it
{
should
respond_to
(
:to_patch
)
}
end
end
describe
'#closes_issues'
do
let
(
:issue
)
{
create
:issue
,
project:
project
}
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
}
"
)
commit
.
closes_issues
(
project
).
should
==
[
issue
]
end
end
it_behaves_like
'a mentionable'
do
let
(
:subject
)
{
commit
}
let
(
:mauthor
)
{
create
:user
,
email:
commit
.
author_email
}
let
(
:backref_text
)
{
"commit
#{
subject
.
sha
[
0
..
5
]
}
"
}
let
(
:set_mentionable_text
)
{
->
(
txt
){
subject
.
stub
(
safe_message:
txt
)
}
}
# Include the subject in the repository stub.
let
(
:extra_commits
)
{
[
subject
]
}
end
end
end
spec/models/issue_spec.rb
View file @
551946a3
...
@@ -56,4 +56,10 @@ describe Issue do
...
@@ -56,4 +56,10 @@ describe Issue do
Issue
.
open_for
(
user
).
count
.
should
eq
2
Issue
.
open_for
(
user
).
count
.
should
eq
2
end
end
end
end
it_behaves_like
'an editable mentionable'
do
let
(
:subject
)
{
create
:issue
,
project:
mproject
}
let
(
:backref_text
)
{
"issue #
#{
subject
.
iid
}
"
}
let
(
:set_mentionable_text
)
{
->
(
txt
){
subject
.
description
=
txt
}
}
end
end
end
spec/models/merge_request_spec.rb
View file @
551946a3
...
@@ -43,7 +43,6 @@ describe MergeRequest do
...
@@ -43,7 +43,6 @@ describe MergeRequest do
it
{
should
include_module
(
Issuable
)
}
it
{
should
include_module
(
Issuable
)
}
end
end
describe
"#mr_and_commit_notes"
do
describe
"#mr_and_commit_notes"
do
let!
(
:merge_request
)
{
create
(
:merge_request
)
}
let!
(
:merge_request
)
{
create
(
:merge_request
)
}
...
@@ -104,4 +103,34 @@ describe MergeRequest do
...
@@ -104,4 +103,34 @@ describe MergeRequest do
end
end
end
end
describe
'detection of issues to be closed'
do
let
(
:issue0
)
{
create
:issue
,
project:
subject
.
project
}
let
(
:issue1
)
{
create
:issue
,
project:
subject
.
project
}
let
(
:commit0
)
{
mock
(
'commit0'
,
closes_issues:
[
issue0
])
}
let
(
:commit1
)
{
mock
(
'commit1'
,
closes_issues:
[
issue0
])
}
let
(
:commit2
)
{
mock
(
'commit2'
,
closes_issues:
[
issue1
])
}
before
do
subject
.
stub
(
unmerged_commits:
[
commit0
,
commit1
,
commit2
])
end
it
'accesses the set of issues that will be closed on acceptance'
do
subject
.
project
.
default_branch
=
subject
.
target_branch
subject
.
closes_issues
.
should
==
[
issue0
,
issue1
].
sort_by
(
&
:id
)
end
it
'only lists issues as to be closed if it targets the default branch'
do
subject
.
project
.
default_branch
=
'master'
subject
.
target_branch
=
'something-else'
subject
.
closes_issues
.
should
be_empty
end
end
it_behaves_like
'an editable mentionable'
do
let
(
:subject
)
{
create
:merge_request
,
source_project:
mproject
,
target_project:
mproject
}
let
(
:backref_text
)
{
"merge request !
#{
subject
.
iid
}
"
}
let
(
:set_mentionable_text
)
{
->
(
txt
){
subject
.
title
=
txt
}
}
end
end
end
spec/models/note_spec.rb
View file @
551946a3
...
@@ -72,7 +72,6 @@ describe Note do
...
@@ -72,7 +72,6 @@ describe Note do
end
end
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:commit
)
{
project
.
repository
.
commit
}
describe
"Commit notes"
do
describe
"Commit notes"
do
let!
(
:note
)
{
create
(
:note_on_commit
,
note:
"+1 from me"
)
}
let!
(
:note
)
{
create
(
:note_on_commit
,
note:
"+1 from me"
)
}
...
@@ -131,7 +130,7 @@ describe Note do
...
@@ -131,7 +130,7 @@ describe Note do
describe
"Merge request notes"
do
describe
"Merge request notes"
do
let!
(
:note
)
{
create
(
:note_on_merge_request
,
note:
"+1 from me"
)
}
let!
(
:note
)
{
create
(
:note_on_merge_request
,
note:
"+1 from me"
)
}
it
"should
not
be votable"
do
it
"should be votable"
do
note
.
should
be_votable
note
.
should
be_votable
end
end
end
end
...
@@ -150,7 +149,7 @@ describe Note do
...
@@ -150,7 +149,7 @@ describe Note do
let
(
:author
)
{
create
(
:user
)
}
let
(
:author
)
{
create
(
:user
)
}
let
(
:status
)
{
'new_status'
}
let
(
:status
)
{
'new_status'
}
subject
{
Note
.
create_status_change_note
(
thing
,
project
,
author
,
status
)
}
subject
{
Note
.
create_status_change_note
(
thing
,
project
,
author
,
status
,
nil
)
}
it
'creates and saves a Note'
do
it
'creates and saves a Note'
do
should
be_a
Note
should
be_a
Note
...
@@ -161,6 +160,102 @@ describe Note do
...
@@ -161,6 +160,102 @@ describe Note do
its
(
:project
)
{
should
==
thing
.
project
}
its
(
:project
)
{
should
==
thing
.
project
}
its
(
:author
)
{
should
==
author
}
its
(
:author
)
{
should
==
author
}
its
(
:note
)
{
should
=~
/Status changed to
#{
status
}
/
}
its
(
:note
)
{
should
=~
/Status changed to
#{
status
}
/
}
it
'appends a back-reference if a closing mentionable is supplied'
do
commit
=
double
(
'commit'
,
gfm_reference:
'commit 123456'
)
n
=
Note
.
create_status_change_note
(
thing
,
project
,
author
,
status
,
commit
)
n
.
note
.
should
=~
/Status changed to
#{
status
}
by commit 123456/
end
end
describe
'#create_cross_reference_note'
do
let
(
:project
)
{
create
(
:project_with_code
)
}
let
(
:author
)
{
create
(
:user
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:mergereq
)
{
create
(
:merge_request
,
target_project:
project
)
}
let
(
:commit
)
{
project
.
repository
.
commit
}
# Test all of {issue, merge request, commit} in both the referenced and referencing
# roles, to ensure that the correct information can be inferred from any argument.
context
'issue from a merge request'
do
subject
{
Note
.
create_cross_reference_note
(
issue
,
mergereq
,
author
,
project
)
}
it
{
should
be_valid
}
its
(
:noteable
)
{
should
==
issue
}
its
(
:project
)
{
should
==
issue
.
project
}
its
(
:author
)
{
should
==
author
}
its
(
:note
)
{
should
==
"_mentioned in merge request !
#{
mergereq
.
iid
}
_"
}
end
context
'issue from a commit'
do
subject
{
Note
.
create_cross_reference_note
(
issue
,
commit
,
author
,
project
)
}
it
{
should
be_valid
}
its
(
:noteable
)
{
should
==
issue
}
its
(
:note
)
{
should
==
"_mentioned in commit
#{
commit
.
sha
[
0
..
5
]
}
_"
}
end
context
'merge request from an issue'
do
subject
{
Note
.
create_cross_reference_note
(
mergereq
,
issue
,
author
,
project
)
}
it
{
should
be_valid
}
its
(
:noteable
)
{
should
==
mergereq
}
its
(
:project
)
{
should
==
mergereq
.
project
}
its
(
:note
)
{
should
==
"_mentioned in issue #
#{
issue
.
iid
}
_"
}
end
context
'commit from a merge request'
do
subject
{
Note
.
create_cross_reference_note
(
commit
,
mergereq
,
author
,
project
)
}
it
{
should
be_valid
}
its
(
:noteable
)
{
should
==
commit
}
its
(
:project
)
{
should
==
project
}
its
(
:note
)
{
should
==
"_mentioned in merge request !
#{
mergereq
.
iid
}
_"
}
end
end
describe
'#cross_reference_exists?'
do
let
(
:project
)
{
create
:project
}
let
(
:author
)
{
create
:user
}
let
(
:issue
)
{
create
:issue
}
let
(
:commit0
)
{
double
'commit0'
,
gfm_reference:
'commit 123456'
}
let
(
:commit1
)
{
double
'commit1'
,
gfm_reference:
'commit 654321'
}
before
do
Note
.
create_cross_reference_note
(
issue
,
commit0
,
author
,
project
)
end
it
'detects if a mentionable has already been mentioned'
do
Note
.
cross_reference_exists?
(
issue
,
commit0
).
should
be_true
end
it
'detects if a mentionable has not already been mentioned'
do
Note
.
cross_reference_exists?
(
issue
,
commit1
).
should
be_false
end
end
describe
'#system?'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:other
)
{
create
(
:issue
,
project:
project
)
}
let
(
:author
)
{
create
(
:user
)
}
it
'should recognize user-supplied notes as non-system'
do
@note
=
create
(
:note_on_issue
)
@note
.
should_not
be_system
end
it
'should identify status-change notes as system notes'
do
@note
=
Note
.
create_status_change_note
(
issue
,
project
,
author
,
'closed'
,
nil
)
@note
.
should
be_system
end
it
'should identify cross-reference notes as system notes'
do
@note
=
Note
.
create_cross_reference_note
(
issue
,
other
,
author
,
project
)
@note
.
should
be_system
end
end
end
describe
:authorization
do
describe
:authorization
do
...
@@ -208,4 +303,11 @@ describe Note do
...
@@ -208,4 +303,11 @@ describe Note do
it
{
@abilities
.
allowed?
(
@u3
,
:admin_note
,
@p1
).
should
be_false
}
it
{
@abilities
.
allowed?
(
@u3
,
:admin_note
,
@p1
).
should
be_false
}
end
end
end
end
it_behaves_like
'an editable mentionable'
do
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:subject
)
{
create
:note
,
noteable:
issue
,
project:
project
}
let
(
:backref_text
)
{
issue
.
gfm_reference
}
let
(
:set_mentionable_text
)
{
->
(
txt
)
{
subject
.
note
=
txt
}
}
end
end
end
spec/observers/activity_observer_spec.rb
View file @
551946a3
...
@@ -3,6 +3,8 @@ require 'spec_helper'
...
@@ -3,6 +3,8 @@ require 'spec_helper'
describe
ActivityObserver
do
describe
ActivityObserver
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
)
}
before
{
Thread
.
current
[
:current_user
]
=
create
(
:user
)
}
def
self
.
it_should_be_valid_event
def
self
.
it_should_be_valid_event
it
{
@event
.
should_not
be_nil
}
it
{
@event
.
should_not
be_nil
}
it
{
@event
.
project
.
should
==
project
}
it
{
@event
.
project
.
should
==
project
}
...
@@ -34,4 +36,26 @@ describe ActivityObserver do
...
@@ -34,4 +36,26 @@ describe ActivityObserver do
it
{
@event
.
action
.
should
==
Event
::
COMMENTED
}
it
{
@event
.
action
.
should
==
Event
::
COMMENTED
}
it
{
@event
.
target
.
should
==
@note
}
it
{
@event
.
target
.
should
==
@note
}
end
end
describe
"Ignore system notes"
do
let
(
:author
)
{
create
(
:user
)
}
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let!
(
:other
)
{
create
(
:issue
)
}
it
"should not create events for status change notes"
do
expect
do
Note
.
observers
.
enable
:activity_observer
do
Note
.
create_status_change_note
(
issue
,
project
,
author
,
'reopened'
,
nil
)
end
end
.
to_not
change
{
Event
.
count
}
end
it
"should not create events for cross-reference notes"
do
expect
do
Note
.
observers
.
enable
:activity_observer
do
Note
.
create_cross_reference_note
(
issue
,
other
,
author
,
issue
.
project
)
end
end
.
to_not
change
{
Event
.
count
}
end
end
end
end
spec/observers/issue_observer_spec.rb
View file @
551946a3
...
@@ -8,8 +8,9 @@ describe IssueObserver do
...
@@ -8,8 +8,9 @@ describe IssueObserver do
before
{
subject
.
stub
(
:current_user
).
and_return
(
some_user
)
}
before
{
subject
.
stub
(
:current_user
).
and_return
(
some_user
)
}
before
{
subject
.
stub
(
:current_commit
).
and_return
(
nil
)
}
before
{
subject
.
stub
(
notification:
mock
(
'NotificationService'
).
as_null_object
)
}
before
{
subject
.
stub
(
notification:
mock
(
'NotificationService'
).
as_null_object
)
}
before
{
mock_issue
.
project
.
stub_chain
(
:repository
,
:commit
).
and_return
(
nil
)
}
subject
{
IssueObserver
.
instance
}
subject
{
IssueObserver
.
instance
}
...
@@ -19,6 +20,15 @@ describe IssueObserver do
...
@@ -19,6 +20,15 @@ describe IssueObserver do
subject
.
after_create
(
mock_issue
)
subject
.
after_create
(
mock_issue
)
end
end
it
'should create cross-reference notes'
do
other_issue
=
create
(
:issue
)
mock_issue
.
stub
(
references:
[
other_issue
])
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
other_issue
,
mock_issue
,
some_user
,
mock_issue
.
project
)
subject
.
after_create
(
mock_issue
)
end
end
end
context
'#after_close'
do
context
'#after_close'
do
...
@@ -26,7 +36,7 @@ describe IssueObserver do
...
@@ -26,7 +36,7 @@ describe IssueObserver do
before
{
mock_issue
.
stub
(
state:
'closed'
)
}
before
{
mock_issue
.
stub
(
state:
'closed'
)
}
it
'note is created if the issue is being closed'
do
it
'note is created if the issue is being closed'
do
Note
.
should_receive
(
:create_status_change_note
).
with
(
mock_issue
,
mock_issue
.
project
,
some_user
,
'closed'
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
mock_issue
,
mock_issue
.
project
,
some_user
,
'closed'
,
nil
)
subject
.
after_close
(
mock_issue
,
nil
)
subject
.
after_close
(
mock_issue
,
nil
)
end
end
...
@@ -35,13 +45,23 @@ describe IssueObserver do
...
@@ -35,13 +45,23 @@ describe IssueObserver do
subject
.
notification
.
should_receive
(
:close_issue
).
with
(
mock_issue
,
some_user
)
subject
.
notification
.
should_receive
(
:close_issue
).
with
(
mock_issue
,
some_user
)
subject
.
after_close
(
mock_issue
,
nil
)
subject
.
after_close
(
mock_issue
,
nil
)
end
end
it
'appends a mention to the closing commit if one is present'
do
commit
=
double
(
'commit'
,
gfm_reference:
'commit 123456'
)
subject
.
stub
(
current_commit:
commit
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
mock_issue
,
mock_issue
.
project
,
some_user
,
'closed'
,
commit
)
subject
.
after_close
(
mock_issue
,
nil
)
end
end
end
context
'a status "reopened"'
do
context
'a status "reopened"'
do
before
{
mock_issue
.
stub
(
state:
'reopened'
)
}
before
{
mock_issue
.
stub
(
state:
'reopened'
)
}
it
'note is created if the issue is being reopened'
do
it
'note is created if the issue is being reopened'
do
Note
.
should_receive
(
:create_status_change_note
).
with
(
mock_issue
,
mock_issue
.
project
,
some_user
,
'reopened'
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
mock_issue
,
mock_issue
.
project
,
some_user
,
'reopened'
,
nil
)
subject
.
after_reopen
(
mock_issue
,
nil
)
subject
.
after_reopen
(
mock_issue
,
nil
)
end
end
end
end
...
@@ -67,5 +87,13 @@ describe IssueObserver do
...
@@ -67,5 +87,13 @@ describe IssueObserver do
subject
.
after_update
(
mock_issue
)
subject
.
after_update
(
mock_issue
)
end
end
end
end
context
'cross-references'
do
it
'notices added references'
do
mock_issue
.
should_receive
(
:notice_added_references
)
subject
.
after_update
(
mock_issue
)
end
end
end
end
end
end
spec/observers/merge_request_observer_spec.rb
View file @
551946a3
...
@@ -5,15 +5,20 @@ describe MergeRequestObserver do
...
@@ -5,15 +5,20 @@ describe MergeRequestObserver do
let
(
:assignee
)
{
create
:user
}
let
(
:assignee
)
{
create
:user
}
let
(
:author
)
{
create
:user
}
let
(
:author
)
{
create
:user
}
let
(
:mr_mock
)
{
double
(
:merge_request
,
id:
42
,
assignee:
assignee
,
author:
author
)
}
let
(
:mr_mock
)
{
double
(
:merge_request
,
id:
42
,
assignee:
assignee
,
author:
author
)
}
let
(
:assigned_mr
)
{
create
(
:merge_request
,
assignee:
assignee
,
author:
author
)
}
let
(
:assigned_mr
)
{
create
(
:merge_request
,
assignee:
assignee
,
author:
author
,
target_project:
create
(
:project
)
)
}
let
(
:unassigned_mr
)
{
create
(
:merge_request
,
author:
author
)
}
let
(
:unassigned_mr
)
{
create
(
:merge_request
,
author:
author
,
target_project:
create
(
:project
)
)
}
let
(
:closed_assigned_mr
)
{
create
(
:closed_merge_request
,
assignee:
assignee
,
author:
author
)
}
let
(
:closed_assigned_mr
)
{
create
(
:closed_merge_request
,
assignee:
assignee
,
author:
author
,
target_project:
create
(
:project
)
)
}
let
(
:closed_unassigned_mr
)
{
create
(
:closed_merge_request
,
author:
author
)
}
let
(
:closed_unassigned_mr
)
{
create
(
:closed_merge_request
,
author:
author
,
target_project:
create
(
:project
)
)
}
before
{
subject
.
stub
(
:current_user
).
and_return
(
some_user
)
}
before
{
subject
.
stub
(
:current_user
).
and_return
(
some_user
)
}
before
{
subject
.
stub
(
notification:
mock
(
'NotificationService'
).
as_null_object
)
}
before
{
subject
.
stub
(
notification:
mock
(
'NotificationService'
).
as_null_object
)
}
before
{
mr_mock
.
stub
(
:author_id
)
}
before
{
mr_mock
.
stub
(
:author_id
)
}
before
{
mr_mock
.
stub
(
:target_project
)
}
before
{
mr_mock
.
stub
(
:target_project
)
}
before
{
mr_mock
.
stub
(
:source_project
)
}
before
{
mr_mock
.
stub
(
:project
)
}
before
{
mr_mock
.
stub
(
:create_cross_references!
).
and_return
(
true
)
}
before
{
Repository
.
any_instance
.
stub
(
commit:
nil
)
}
before
(
:each
)
{
enable_observers
}
before
(
:each
)
{
enable_observers
}
after
(
:each
)
{
disable_observers
}
after
(
:each
)
{
disable_observers
}
...
@@ -24,11 +29,20 @@ describe MergeRequestObserver do
...
@@ -24,11 +29,20 @@ describe MergeRequestObserver do
subject
.
should_receive
(
:notification
)
subject
.
should_receive
(
:notification
)
subject
.
after_create
(
mr_mock
)
subject
.
after_create
(
mr_mock
)
end
end
it
'creates cross-reference notes'
do
project
=
create
:project
mr_mock
.
stub
(
title:
"this mr references !
#{
assigned_mr
.
id
}
"
,
project:
project
)
mr_mock
.
should_receive
(
:create_cross_references!
).
with
(
project
,
some_user
)
subject
.
after_create
(
mr_mock
)
end
end
end
context
'#after_update'
do
context
'#after_update'
do
before
(
:each
)
do
before
(
:each
)
do
mr_mock
.
stub
(
:is_being_reassigned?
).
and_return
(
false
)
mr_mock
.
stub
(
:is_being_reassigned?
).
and_return
(
false
)
mr_mock
.
stub
(
:notice_added_references
)
end
end
it
'is called when a merge request is changed'
do
it
'is called when a merge request is changed'
do
...
@@ -41,6 +55,12 @@ describe MergeRequestObserver do
...
@@ -41,6 +55,12 @@ describe MergeRequestObserver do
end
end
end
end
it
'checks for new references'
do
mr_mock
.
should_receive
(
:notice_added_references
)
subject
.
after_update
(
mr_mock
)
end
context
'a notification'
do
context
'a notification'
do
it
'is sent if the merge request is being reassigned'
do
it
'is sent if the merge request is being reassigned'
do
mr_mock
.
should_receive
(
:is_being_reassigned?
).
and_return
(
true
)
mr_mock
.
should_receive
(
:is_being_reassigned?
).
and_return
(
true
)
...
@@ -61,13 +81,13 @@ describe MergeRequestObserver do
...
@@ -61,13 +81,13 @@ describe MergeRequestObserver do
context
'#after_close'
do
context
'#after_close'
do
context
'a status "closed"'
do
context
'a status "closed"'
do
it
'note is created if the merge request is being closed'
do
it
'note is created if the merge request is being closed'
do
Note
.
should_receive
(
:create_status_change_note
).
with
(
assigned_mr
,
assigned_mr
.
target_project
,
some_user
,
'closed'
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
assigned_mr
,
assigned_mr
.
target_project
,
some_user
,
'closed'
,
nil
)
assigned_mr
.
close
assigned_mr
.
close
end
end
it
'notification is delivered only to author if the merge request is being closed'
do
it
'notification is delivered only to author if the merge request is being closed'
do
Note
.
should_receive
(
:create_status_change_note
).
with
(
unassigned_mr
,
unassigned_mr
.
target_project
,
some_user
,
'closed'
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
unassigned_mr
,
unassigned_mr
.
target_project
,
some_user
,
'closed'
,
nil
)
unassigned_mr
.
close
unassigned_mr
.
close
end
end
...
@@ -77,13 +97,13 @@ describe MergeRequestObserver do
...
@@ -77,13 +97,13 @@ describe MergeRequestObserver do
context
'#after_reopen'
do
context
'#after_reopen'
do
context
'a status "reopened"'
do
context
'a status "reopened"'
do
it
'note is created if the merge request is being reopened'
do
it
'note is created if the merge request is being reopened'
do
Note
.
should_receive
(
:create_status_change_note
).
with
(
closed_assigned_mr
,
closed_assigned_mr
.
target_project
,
some_user
,
'reopened'
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
closed_assigned_mr
,
closed_assigned_mr
.
target_project
,
some_user
,
'reopened'
,
nil
)
closed_assigned_mr
.
reopen
closed_assigned_mr
.
reopen
end
end
it
'notification is delivered only to author if the merge request is being reopened'
do
it
'notification is delivered only to author if the merge request is being reopened'
do
Note
.
should_receive
(
:create_status_change_note
).
with
(
closed_unassigned_mr
,
closed_unassigned_mr
.
target_project
,
some_user
,
'reopened'
)
Note
.
should_receive
(
:create_status_change_note
).
with
(
closed_unassigned_mr
,
closed_unassigned_mr
.
target_project
,
some_user
,
'reopened'
,
nil
)
closed_unassigned_mr
.
reopen
closed_unassigned_mr
.
reopen
end
end
...
...
spec/observers/note_observer_spec.rb
View file @
551946a3
...
@@ -5,9 +5,9 @@ describe NoteObserver do
...
@@ -5,9 +5,9 @@ describe NoteObserver do
before
{
subject
.
stub
(
notification:
mock
(
'NotificationService'
).
as_null_object
)
}
before
{
subject
.
stub
(
notification:
mock
(
'NotificationService'
).
as_null_object
)
}
let
(
:team_without_author
)
{
(
1
..
2
).
map
{
|
n
|
double
:user
,
id:
n
}
}
let
(
:team_without_author
)
{
(
1
..
2
).
map
{
|
n
|
double
:user
,
id:
n
}
}
let
(
:note
)
{
double
(
:note
).
as_null_object
}
describe
'#after_create'
do
describe
'#after_create'
do
let
(
:note
)
{
double
:note
}
it
'is called after a note is created'
do
it
'is called after a note is created'
do
subject
.
should_receive
:after_create
subject
.
should_receive
:after_create
...
@@ -22,5 +22,35 @@ describe NoteObserver do
...
@@ -22,5 +22,35 @@ describe NoteObserver do
subject
.
after_create
(
note
)
subject
.
after_create
(
note
)
end
end
it
'creates cross-reference notes as appropriate'
do
@p
=
create
(
:project
)
@referenced
=
create
(
:issue
,
project:
@p
)
@referencer
=
create
(
:issue
,
project:
@p
)
@author
=
create
(
:user
)
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
@referenced
,
@referencer
,
@author
,
@p
)
Note
.
observers
.
enable
:note_observer
do
create
(
:note
,
project:
@p
,
author:
@author
,
noteable:
@referencer
,
note:
"Duplicate of #
#{
@referenced
.
iid
}
"
)
end
end
it
"doesn't cross-reference system notes"
do
Note
.
should_receive
(
:create_cross_reference_note
).
once
Note
.
observers
.
enable
:note_observer
do
Note
.
create_cross_reference_note
(
create
(
:issue
),
create
(
:issue
))
end
end
end
describe
'#after_update'
do
it
'checks for new cross-references'
do
note
.
should_receive
(
:notice_added_references
)
subject
.
after_update
(
note
)
end
end
end
end
end
spec/services/git_push_service_spec.rb
View file @
551946a3
...
@@ -6,6 +6,7 @@ describe GitPushService do
...
@@ -6,6 +6,7 @@ describe GitPushService do
let
(
:service
)
{
GitPushService
.
new
}
let
(
:service
)
{
GitPushService
.
new
}
before
do
before
do
@blankrev
=
'0000000000000000000000000000000000000000'
@oldrev
=
'b98a310def241a6fd9c9a9a3e7934c48e498fe81'
@oldrev
=
'b98a310def241a6fd9c9a9a3e7934c48e498fe81'
@newrev
=
'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb'
@newrev
=
'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb'
@ref
=
'refs/heads/master'
@ref
=
'refs/heads/master'
...
@@ -98,7 +99,7 @@ describe GitPushService do
...
@@ -98,7 +99,7 @@ describe GitPushService do
it
"when pushing a branch for the first time"
do
it
"when pushing a branch for the first time"
do
@project_hook
.
should_not_receive
(
:execute
)
@project_hook
.
should_not_receive
(
:execute
)
service
.
execute
(
project
,
user
,
'00000000000000000000000000000000'
,
'newrev'
,
'refs/heads/master'
)
service
.
execute
(
project
,
user
,
@blankrev
,
'newrev'
,
'refs/heads/master'
)
end
end
it
"when pushing tags"
do
it
"when pushing tags"
do
...
@@ -107,5 +108,107 @@ describe GitPushService do
...
@@ -107,5 +108,107 @@ describe GitPushService do
end
end
end
end
end
end
describe
"cross-reference notes"
do
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:commit_author
)
{
create
:user
}
let
(
:commit
)
{
project
.
repository
.
commit
}
before
do
commit
.
stub
({
safe_message:
"this commit
\n
mentions #
#{
issue
.
id
}
"
,
references:
[
issue
],
author_name:
commit_author
.
name
,
author_email:
commit_author
.
email
})
project
.
repository
.
stub
(
commits_between:
[
commit
])
end
it
"creates a note if a pushed commit mentions an issue"
do
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
issue
,
commit
,
commit_author
,
project
)
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
@ref
)
end
it
"only creates a cross-reference note if one doesn't already exist"
do
Note
.
create_cross_reference_note
(
issue
,
commit
,
user
,
project
)
Note
.
should_not_receive
(
:create_cross_reference_note
).
with
(
issue
,
commit
,
commit_author
,
project
)
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
@ref
)
end
it
"defaults to the pushing user if the commit's author is not known"
do
commit
.
stub
(
author_name:
'unknown name'
,
author_email:
'unknown@email.com'
)
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
issue
,
commit
,
user
,
project
)
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
@ref
)
end
it
"finds references in the first push to a non-default branch"
do
project
.
repository
.
stub
(
:commits_between
).
with
(
@blankrev
,
@newrev
).
and_return
([])
project
.
repository
.
stub
(
:commits_between
).
with
(
"master"
,
@newrev
).
and_return
([
commit
])
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
issue
,
commit
,
commit_author
,
project
)
service
.
execute
(
project
,
user
,
@blankrev
,
@newrev
,
'refs/heads/other'
)
end
it
"finds references in the first push to a default branch"
do
project
.
repository
.
stub
(
:commits_between
).
with
(
@blankrev
,
@newrev
).
and_return
([])
project
.
repository
.
stub
(
:commits
).
with
(
@newrev
).
and_return
([
commit
])
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
issue
,
commit
,
commit_author
,
project
)
service
.
execute
(
project
,
user
,
@blankrev
,
@newrev
,
'refs/heads/master'
)
end
end
describe
"closing issues from pushed commits"
do
let
(
:issue
)
{
create
:issue
,
project:
project
}
let
(
:other_issue
)
{
create
:issue
,
project:
project
}
let
(
:commit_author
)
{
create
:user
}
let
(
:closing_commit
)
{
project
.
repository
.
commit
}
before
do
closing_commit
.
stub
({
issue_closing_regex:
/^([Cc]loses|[Ff]ixes) #\d+/
,
safe_message:
"this is some work.
\n\n
closes #
#{
issue
.
iid
}
"
,
author_name:
commit_author
.
name
,
author_email:
commit_author
.
email
})
project
.
repository
.
stub
(
commits_between:
[
closing_commit
])
end
it
"closes issues with commit messages"
do
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
@ref
)
Issue
.
find
(
issue
.
id
).
should
be_closed
end
it
"passes the closing commit as a thread-local"
do
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
@ref
)
Thread
.
current
[
:current_commit
].
should
==
closing_commit
end
it
"doesn't create cross-reference notes for a closing reference"
do
expect
{
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
@ref
)
}.
not_to
change
{
Note
.
where
(
project_id:
project
.
id
,
system:
true
).
count
}
end
it
"doesn't close issues when pushed to non-default branches"
do
project
.
stub
(
default_branch:
'durf'
)
# The push still shouldn't create cross-reference notes.
expect
{
service
.
execute
(
project
,
user
,
@oldrev
,
@newrev
,
'refs/heads/hurf'
)
}.
not_to
change
{
Note
.
where
(
project_id:
project
.
id
,
system:
true
).
count
}
Issue
.
find
(
issue
.
id
).
should
be_opened
end
end
end
end
spec/support/login_helpers.rb
View file @
551946a3
...
@@ -18,6 +18,7 @@ module LoginHelpers
...
@@ -18,6 +18,7 @@ module LoginHelpers
fill_in
"user_login"
,
with:
user
.
email
fill_in
"user_login"
,
with:
user
.
email
fill_in
"user_password"
,
with:
"123456"
fill_in
"user_password"
,
with:
"123456"
click_button
"Sign in"
click_button
"Sign in"
Thread
.
current
[
:current_user
]
=
user
end
end
def
logout
def
logout
...
...
spec/support/mentionable_shared_examples.rb
0 → 100644
View file @
551946a3
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
# - let(:subject) { "the mentionable implementation" }
# - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " }
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
def
common_mentionable_setup
# Avoid name collisions with let(:project) or let(:author) in the surrounding scope.
let
(
:mproject
)
{
create
:project
}
let
(
:mauthor
)
{
subject
.
author
}
let
(
:mentioned_issue
)
{
create
:issue
,
project:
mproject
}
let
(
:other_issue
)
{
create
:issue
,
project:
mproject
}
let
(
:mentioned_mr
)
{
create
:merge_request
,
target_project:
mproject
,
source_branch:
'different'
}
let
(
:mentioned_commit
)
{
mock
(
'commit'
,
sha:
'1234567890abcdef'
).
as_null_object
}
# Override to add known commits to the repository stub.
let
(
:extra_commits
)
{
[]
}
# 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+.
let
(
:ref_string
)
do
"mentions #
#{
mentioned_issue
.
iid
}
twice #
#{
mentioned_issue
.
iid
}
, !
#{
mentioned_mr
.
iid
}
, "
+
"
#{
mentioned_commit
.
sha
[
0
..
5
]
}
and itself as
#{
backref_text
}
"
end
before
do
# Wire the project's repository to return the mentioned commit, and +nil+ for any
# unrecognized commits.
commitmap
=
{
'123456'
=>
mentioned_commit
}
extra_commits
.
each
{
|
c
|
commitmap
[
c
.
sha
[
0
..
5
]]
=
c
}
repo
=
mock
(
'repository'
)
repo
.
stub
(
:commit
)
{
|
sha
|
commitmap
[
sha
]
}
mproject
.
stub
(
repository:
repo
)
set_mentionable_text
.
call
(
ref_string
)
end
end
shared_examples
'a mentionable'
do
common_mentionable_setup
it
'generates a descriptive back-reference'
do
subject
.
gfm_reference
.
should
==
backref_text
end
it
"extracts references from its reference property"
do
# De-duplicate and omit itself
refs
=
subject
.
references
(
mproject
)
refs
.
should
have
(
3
).
items
refs
.
should
include
(
mentioned_issue
)
refs
.
should
include
(
mentioned_mr
)
refs
.
should
include
(
mentioned_commit
)
end
it
'creates cross-reference notes'
do
[
mentioned_issue
,
mentioned_mr
,
mentioned_commit
].
each
do
|
referenced
|
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
referenced
,
subject
.
local_reference
,
mauthor
,
mproject
)
end
subject
.
create_cross_references!
(
mproject
,
mauthor
)
end
it
'detects existing cross-references'
do
Note
.
create_cross_reference_note
(
mentioned_issue
,
subject
.
local_reference
,
mauthor
,
mproject
)
subject
.
has_mentioned?
(
mentioned_issue
).
should
be_true
subject
.
has_mentioned?
(
mentioned_mr
).
should
be_false
end
end
shared_examples
'an editable mentionable'
do
common_mentionable_setup
it_behaves_like
'a mentionable'
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
]
}
, "
+
"but now it mentions #
#{
other_issue
.
iid
}
, too."
[
mentioned_issue
,
mentioned_commit
].
each
do
|
oldref
|
Note
.
should_not_receive
(
:create_cross_reference_note
).
with
(
oldref
,
subject
.
local_reference
,
mauthor
,
mproject
)
end
Note
.
should_receive
(
:create_cross_reference_note
).
with
(
other_issue
,
subject
.
local_reference
,
mauthor
,
mproject
)
subject
.
save
set_mentionable_text
.
call
(
new_text
)
subject
.
notice_added_references
(
mproject
,
mauthor
)
end
end
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