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
3b39214f
Commit
3b39214f
authored
Apr 21, 2016
by
Alfredo Sumaran
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master' into issue_14904
parents
d4f6d398
1c7c114e
Changes
99
Hide whitespace changes
Inline
Side-by-side
Showing
99 changed files
with
1000 additions
and
3074 deletions
+1000
-3074
CHANGELOG
CHANGELOG
+4
-1
PROCESS.md
PROCESS.md
+19
-0
app/assets/javascripts/application.js.coffee
app/assets/javascripts/application.js.coffee
+0
-1
app/assets/javascripts/merge_request_tabs.js.coffee
app/assets/javascripts/merge_request_tabs.js.coffee
+6
-6
app/assets/javascripts/raven_config.js.coffee
app/assets/javascripts/raven_config.js.coffee
+0
-44
app/assets/javascripts/todos.js.coffee
app/assets/javascripts/todos.js.coffee
+42
-1
app/assets/stylesheets/framework/files.scss
app/assets/stylesheets/framework/files.scss
+2
-0
app/assets/stylesheets/pages/notes.scss
app/assets/stylesheets/pages/notes.scss
+3
-0
app/controllers/projects/commit_controller.rb
app/controllers/projects/commit_controller.rb
+10
-5
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+1
-0
app/controllers/projects/project_members_controller.rb
app/controllers/projects/project_members_controller.rb
+1
-1
app/finders/issuable_finder.rb
app/finders/issuable_finder.rb
+1
-2
app/helpers/ci_status_helper.rb
app/helpers/ci_status_helper.rb
+5
-10
app/helpers/gitlab_routing_helper.rb
app/helpers/gitlab_routing_helper.rb
+4
-0
app/helpers/page_layout_helper.rb
app/helpers/page_layout_helper.rb
+8
-0
app/helpers/projects_helper.rb
app/helpers/projects_helper.rb
+4
-0
app/helpers/tab_helper.rb
app/helpers/tab_helper.rb
+8
-0
app/models/ci/build.rb
app/models/ci/build.rb
+8
-14
app/models/ci/commit.rb
app/models/ci/commit.rb
+53
-89
app/models/commit.rb
app/models/commit.rb
+4
-3
app/models/commit_status.rb
app/models/commit_status.rb
+22
-34
app/models/concerns/issuable.rb
app/models/concerns/issuable.rb
+8
-1
app/models/concerns/statuseable.rb
app/models/concerns/statuseable.rb
+81
-0
app/models/merge_request.rb
app/models/merge_request.rb
+1
-1
app/models/project.rb
app/models/project.rb
+4
-4
app/services/ci/create_builds_service.rb
app/services/ci/create_builds_service.rb
+18
-9
app/services/ci/create_trigger_request_service.rb
app/services/ci/create_trigger_request_service.rb
+2
-2
app/services/ci/image_for_build_service.rb
app/services/ci/image_for_build_service.rb
+6
-5
app/services/create_commit_builds_service.rb
app/services/create_commit_builds_service.rb
+6
-4
app/views/admin/users/_form.html.haml
app/views/admin/users/_form.html.haml
+3
-3
app/views/dashboard/todos/index.html.haml
app/views/dashboard/todos/index.html.haml
+1
-0
app/views/doorkeeper/applications/index.html.haml
app/views/doorkeeper/applications/index.html.haml
+0
-1
app/views/layouts/_page.html.haml
app/views/layouts/_page.html.haml
+4
-0
app/views/layouts/application.html.haml
app/views/layouts/application.html.haml
+1
-1
app/views/layouts/nav/_dashboard.html.haml
app/views/layouts/nav/_dashboard.html.haml
+1
-2
app/views/layouts/nav/_profile.html.haml
app/views/layouts/nav/_profile.html.haml
+2
-12
app/views/layouts/nav/_project.html.haml
app/views/layouts/nav/_project.html.haml
+1
-1
app/views/layouts/profile.html.haml
app/views/layouts/profile.html.haml
+2
-1
app/views/profiles/accounts/show.html.haml
app/views/profiles/accounts/show.html.haml
+0
-1
app/views/profiles/audit_log.html.haml
app/views/profiles/audit_log.html.haml
+0
-1
app/views/profiles/emails/index.html.haml
app/views/profiles/emails/index.html.haml
+0
-1
app/views/profiles/keys/index.html.haml
app/views/profiles/keys/index.html.haml
+0
-1
app/views/profiles/notifications/show.html.haml
app/views/profiles/notifications/show.html.haml
+0
-1
app/views/profiles/passwords/edit.html.haml
app/views/profiles/passwords/edit.html.haml
+0
-1
app/views/profiles/preferences/show.html.haml
app/views/profiles/preferences/show.html.haml
+0
-1
app/views/projects/_builds_settings.html.haml
app/views/projects/_builds_settings.html.haml
+3
-0
app/views/projects/_last_commit.html.haml
app/views/projects/_last_commit.html.haml
+4
-5
app/views/projects/blob/diff.html.haml
app/views/projects/blob/diff.html.haml
+4
-4
app/views/projects/builds/index.html.haml
app/views/projects/builds/index.html.haml
+1
-1
app/views/projects/builds/show.html.haml
app/views/projects/builds/show.html.haml
+1
-1
app/views/projects/ci/builds/_build.html.haml
app/views/projects/ci/builds/_build.html.haml
+8
-5
app/views/projects/commit/_builds.html.haml
app/views/projects/commit/_builds.html.haml
+2
-67
app/views/projects/commit/_ci_commit.html.haml
app/views/projects/commit/_ci_commit.html.haml
+69
-0
app/views/projects/commit/_commit_box.html.haml
app/views/projects/commit/_commit_box.html.haml
+4
-4
app/views/projects/commit/show.html.haml
app/views/projects/commit/show.html.haml
+1
-1
app/views/projects/commits/_commit.html.haml
app/views/projects/commits/_commit.html.haml
+3
-4
app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
.../generic_commit_statuses/_generic_commit_status.html.haml
+7
-6
app/views/projects/issues/_related_branches.html.haml
app/views/projects/issues/_related_branches.html.haml
+1
-1
app/views/projects/merge_requests/invalid.html.haml
app/views/projects/merge_requests/invalid.html.haml
+1
-1
app/views/projects/merge_requests/show/_builds.html.haml
app/views/projects/merge_requests/show/_builds.html.haml
+2
-1
app/views/shared/projects/_project.html.haml
app/views/shared/projects/_project.html.haml
+3
-4
db/fixtures/development/14_builds.rb
db/fixtures/development/14_builds.rb
+1
-1
db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
...grate/20160226114608_add_trigram_indexes_for_searching.rb
+11
-0
db/migrate/20160412173416_add_fields_to_ci_commit.rb
db/migrate/20160412173416_add_fields_to_ci_commit.rb
+8
-0
db/migrate/20160412173417_update_ci_commit.rb
db/migrate/20160412173417_update_ci_commit.rb
+35
-0
db/migrate/20160412173418_add_ci_commit_indexes.rb
db/migrate/20160412173418_add_ci_commit_indexes.rb
+19
-0
db/schema.rb
db/schema.rb
+7
-0
features/steps/profile/active_tab.rb
features/steps/profile/active_tab.rb
+4
-0
features/steps/project/merge_requests.rb
features/steps/project/merge_requests.rb
+1
-1
features/steps/shared/builds.rb
features/steps/shared/builds.rb
+3
-3
features/steps/shared/project.rb
features/steps/shared/project.rb
+1
-1
lib/api/commit_statuses.rb
lib/api/commit_statuses.rb
+17
-4
lib/banzai/filter/label_reference_filter.rb
lib/banzai/filter/label_reference_filter.rb
+21
-15
lib/ci/status.rb
lib/ci/status.rb
+0
-19
lib/gitlab/gon_helper.rb
lib/gitlab/gon_helper.rb
+0
-1
lib/gitlab/sidekiq_middleware/memory_killer.rb
lib/gitlab/sidekiq_middleware/memory_killer.rb
+2
-2
spec/controllers/projects/project_members_controller_spec.rb
spec/controllers/projects/project_members_controller_spec.rb
+16
-0
spec/features/issues/filter_by_labels_spec.rb
spec/features/issues/filter_by_labels_spec.rb
+23
-28
spec/features/security/project/internal_access_spec.rb
spec/features/security/project/internal_access_spec.rb
+5
-5
spec/features/security/project/private_access_spec.rb
spec/features/security/project/private_access_spec.rb
+3
-3
spec/features/security/project/public_access_spec.rb
spec/features/security/project/public_access_spec.rb
+6
-6
spec/features/todos/todos_spec.rb
spec/features/todos/todos_spec.rb
+79
-0
spec/helpers/ci_status_helper_spec.rb
spec/helpers/ci_status_helper_spec.rb
+3
-3
spec/lib/banzai/filter/label_reference_filter_spec.rb
spec/lib/banzai/filter/label_reference_filter_spec.rb
+26
-16
spec/lib/gitlab/badge/build_spec.rb
spec/lib/gitlab/badge/build_spec.rb
+3
-3
spec/models/ci/commit_spec.rb
spec/models/ci/commit_spec.rb
+102
-111
spec/models/commit_spec.rb
spec/models/commit_spec.rb
+8
-0
spec/models/commit_status_spec.rb
spec/models/commit_status_spec.rb
+50
-14
spec/models/concerns/issuable_spec.rb
spec/models/concerns/issuable_spec.rb
+30
-0
spec/models/concerns/statuseable_spec.rb
spec/models/concerns/statuseable_spec.rb
+12
-3
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+2
-2
spec/models/project_spec.rb
spec/models/project_spec.rb
+15
-2
spec/requests/api/builds_spec.rb
spec/requests/api/builds_spec.rb
+1
-1
spec/requests/api/commit_status_spec.rb
spec/requests/api/commit_status_spec.rb
+10
-9
spec/requests/api/commits_spec.rb
spec/requests/api/commits_spec.rb
+3
-3
spec/requests/ci/api/builds_spec.rb
spec/requests/ci/api/builds_spec.rb
+10
-10
spec/services/ci/create_builds_service_spec.rb
spec/services/ci/create_builds_service_spec.rb
+2
-2
spec/services/ci/image_for_build_service_spec.rb
spec/services/ci/image_for_build_service_spec.rb
+1
-1
vendor/assets/javascripts/raven.js
vendor/assets/javascripts/raven.js
+0
-2435
No files found.
CHANGELOG
View file @
3b39214f
...
...
@@ -4,6 +4,7 @@ v 8.8.0 (unreleased)
v 8.7.0 (unreleased)
- The number of InfluxDB points stored per UDP packet can now be configured
- Fix error when cross-project label reference used with non-existent project
- Transactions for /internal/allowed now have an "action" tag set
- Method instrumentation now uses Module#prepend instead of aliasing methods
- Repository.clean_old_archives is now instrumented
...
...
@@ -50,6 +51,7 @@ v 8.7.0 (unreleased)
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
- Display project members page to all members
- Handle nil descriptions in Slack issue messages (Stan Hu)
- Add automated repository integrity checks
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
...
...
@@ -68,6 +70,7 @@ v 8.7.0 (unreleased)
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
- Make Ci::Commit to group only similar builds and make it stateful (ref, tag)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
...
...
@@ -87,10 +90,10 @@ v 8.7.0 (unreleased)
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
- Show JavaScript errors in sentry
- Diff design updates (colors, button styles, etc)
- Copying and pasting a diff no longer pastes the line numbers or +/-
- Add null check to formData when updating profile content to fix Firefox bug
- Disable spellcheck and autocorrect for username field in admin page
- Delete tags using Rugged for performance reasons (Robert Schilling)
- Add Slack notifications when Wiki is edited (Sebastian Klier)
- Diffs load at the correct point when linking from from number
...
...
PROCESS.md
View file @
3b39214f
...
...
@@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide].
## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release
manager.
During the feature freeze all merge requests that are meant to go into the next
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses
### Improperly formatted issue
...
...
app/assets/javascripts/application.js.coffee
View file @
3b39214f
...
...
@@ -55,7 +55,6 @@
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
#= require raven
window
.
slugify
=
(
text
)
->
text
.
replace
(
/[^-a-zA-Z0-9]+/g
,
'_'
).
toLowerCase
()
...
...
app/assets/javascripts/merge_request_tabs.js.coffee
View file @
3b39214f
...
...
@@ -87,8 +87,8 @@ class @MergeRequestTabs
if
window
.
location
.
hash
navBarHeight
=
$
(
'.navbar-gitlab'
).
outerHeight
()
$el
=
$
(
"
#{
container
}
#{
window
.
location
.
hash
}
"
)
$
.
scrollTo
(
"
#{
container
}
#{
window
.
location
.
hash
}
"
,
offset
:
-
navBarHeight
)
if
$el
.
length
$el
=
$
(
"
#{
container
}
#{
window
.
location
.
hash
}
:not(.match)
"
)
$
.
scrollTo
(
"
#{
container
}
#{
window
.
location
.
hash
}
:not(.match)
"
,
offset
:
-
navBarHeight
)
if
$el
.
length
# Activate a tab based on the current action
activateTab
:
(
action
)
->
...
...
@@ -176,12 +176,12 @@ class @MergeRequestTabs
if
locationHash
isnt
''
hashClassString
=
".
#{
locationHash
.
replace
(
'#'
,
''
)
}
"
$diffLine
=
$
(
locationHash
)
$diffLine
=
$
(
"
#{
locationHash
}
:not(.match)"
,
$
(
'#diffs'
)
)
if
$diffLine
.
is
':not(tr)
'
$diffLine
=
$
(
"td
#{
locationHash
}
, td
#{
hashClassString
}
"
)
if
not
$diffLine
.
is
'tr
'
$diffLine
=
$
(
'#diffs'
).
find
(
"td
#{
locationHash
}
, td
#{
hashClassString
}
"
)
else
$diffLine
=
$
(
'td'
,
$diffLine
)
$diffLine
=
$
diffLine
.
find
(
'td'
)
if
$diffLine
.
length
$diffLine
.
addClass
'hll'
...
...
app/assets/javascripts/raven_config.js.coffee
deleted
100644 → 0
View file @
d4f6d398
@
raven
=
init
:
->
if
gon
.
sentry_dsn
?
Raven
.
config
(
gon
.
sentry_dsn
,
{
includePaths
:
[
/gon.relative_url_root/
]
ignoreErrors
:
[
# Random plugins/extensions
'top.GLOBALS'
,
# See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html
'originalCreateNotification'
,
'canvas.contentDocument'
,
'MyApp_RemoveAllHighlights'
,
'http://tt.epicplay.com'
,
'Can
\'
t find variable: ZiteReader'
,
'jigsaw is not defined'
,
'ComboSearch is not defined'
,
'http://loading.retry.widdit.com/'
,
'atomicFindClose'
,
# ISP "optimizing" proxy - `Cache-Control: no-transform` seems to
# reduce this. (thanks @acdha)
# See http://stackoverflow.com/questions/4113268
'bmi_SafeAddOnload'
,
'EBCallBackMessageReceived'
,
# See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
'conduitPage'
],
ignoreUrls
:
[
# Chrome extensions
/extensions\//i
,
/^chrome:\/\//i
,
# Other plugins
/127\.0\.0\.1:4001\/isrunning/i
,
# Cacaoweb
/webappstoolbarba\.texthelp\.com\//i
,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i
]
}).
install
()
if
gon
.
current_user_id
Raven
.
setUserContext
({
id
:
gon
.
current_user_id
})
$
->
raven
.
init
()
app/assets/javascripts/todos.js.coffee
View file @
3b39214f
class
@
Todos
constructor
:
(
@
name
)
->
constructor
:
(
opts
=
{})
->
{
@
el
=
$
(
'.js-todos-options'
)
}
=
opts
@
perPage
=
@
el
.
data
(
'perPage'
)
@
clearListeners
()
@
initBtnListeners
()
...
...
@@ -26,6 +32,7 @@ class @Todos
dataType
:
'json'
data
:
'_method'
:
'delete'
success
:
(
data
)
=>
@
redirectIfNeeded
data
.
count
@
clearDone
$this
.
closest
(
'li'
)
@
updateBadges
data
...
...
@@ -57,6 +64,40 @@ class @Todos
$
(
'.todos-pending .badge, .todos-pending-count'
).
text
data
.
count
$
(
'.todos-done .badge'
).
text
data
.
done_count
getTotalPages
:
->
@
el
.
data
(
'totalPages'
)
getCurrentPage
:
->
@
el
.
data
(
'currentPage'
)
getTodosPerPage
:
->
@
el
.
data
(
'perPage'
)
redirectIfNeeded
:
(
total
)
->
currPages
=
@
getTotalPages
()
currPage
=
@
getCurrentPage
()
# Refresh if no remaining Todos
if
not
total
location
.
reload
()
return
# Do nothing if no pagination
return
if
not
currPages
newPages
=
Math
.
ceil
(
total
/
@
getTodosPerPage
())
url
=
location
.
href
# Includes query strings
# If new total of pages is different than we have now
if
newPages
isnt
currPages
# Redirect to previous page if there's one available
if
currPages
>
1
and
currPage
is
currPages
pageParams
=
page
:
currPages
-
1
url
=
gl
.
utils
.
mergeUrlParams
(
pageParams
,
url
)
Turbolinks
.
visit
(
url
)
goToTodoUrl
:
(
e
)
->
todoLink
=
$
(
this
).
data
(
'url'
)
return
unless
todoLink
...
...
app/assets/stylesheets/framework/files.scss
View file @
3b39214f
...
...
@@ -38,12 +38,14 @@
.filename
{
&
.old
{
display
:
inline-block
;
span
.idiff
{
background-color
:
#f8cbcb
;
}
}
&
.new
{
display
:
inline-block
;
span
.idiff
{
background-color
:
#a6f3a6
;
}
...
...
app/assets/stylesheets/pages/notes.scss
View file @
3b39214f
...
...
@@ -183,6 +183,9 @@ ul.notes {
}
}
.author_link
{
color
:
$gl-gray
;
}
}
.note-headline-light
,
...
...
app/controllers/projects/commit_controller.rb
View file @
3b39214f
...
...
@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end
def
cancel_builds
ci_
commit
.
builds
.
running_or_pending
.
each
(
&
:cancel
)
ci_builds
.
running_or_pending
.
each
(
&
:cancel
)
redirect_back_or_default
default:
builds_namespace_project_commit_path
(
project
.
namespace
,
project
,
commit
.
sha
)
end
def
retry_builds
ci_
commit
.
builds
.
latest
.
failed
.
each
do
|
build
|
ci_builds
.
latest
.
failed
.
each
do
|
build
|
if
build
.
retryable?
Ci
::
Build
.
retry
(
build
)
end
...
...
@@ -99,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit
||=
@project
.
commit
(
params
[
:id
])
end
def
ci_commit
@ci_commit
||=
project
.
ci_commit
(
commit
.
sha
)
def
ci_commits
@ci_commits
||=
project
.
ci_commits
.
where
(
sha:
commit
.
sha
)
end
def
ci_builds
@ci_builds
||=
Ci
::
Build
.
where
(
commit:
ci_commits
)
end
def
define_show_vars
...
...
@@ -113,7 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs
=
[
commit
.
parent
||
commit
,
commit
]
@notes_count
=
commit
.
notes
.
count
@statuses
=
ci_commit
.
statuses
if
ci_commit
@statuses
=
CommitStatus
.
where
(
commit:
ci_commits
)
@builds
=
Ci
::
Build
.
where
(
commit:
ci_commits
)
end
def
assign_change_commit_vars
(
mr_source_branch
)
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
3b39214f
...
...
@@ -321,6 +321,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def
define_widget_vars
@ci_commit
=
@merge_request
.
ci_commit
@ci_commits
=
[
@ci_commit
].
compact
closes_issues
end
...
...
app/controllers/projects/project_members_controller.rb
View file @
3b39214f
class
Projects::ProjectMembersController
<
Projects
::
ApplicationController
# Authorize
before_action
:authorize_admin_project_member!
,
except:
:leave
before_action
:authorize_admin_project_member!
,
except:
[
:leave
,
:index
]
def
index
@project_members
=
@project
.
project_members
...
...
app/finders/issuable_finder.rb
View file @
3b39214f
...
...
@@ -272,7 +272,6 @@ class IssuableFinder
items
=
items
.
without_label
else
items
=
items
.
with_label
(
label_names
)
if
projects
items
=
items
.
where
(
labels:
{
project_id:
projects
})
end
...
...
@@ -321,7 +320,7 @@ class IssuableFinder
end
def
label_names
params
[
:label_name
].
split
(
','
)
params
[
:label_name
].
is_a?
(
String
)
?
params
[
:label_name
].
split
(
','
)
:
params
[
:label_name
]
end
def
current_user_related?
...
...
app/helpers/ci_status_helper.rb
View file @
3b39214f
...
...
@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path
(
project
.
namespace
,
project
,
ci_commit
.
sha
)
end
def
ci_status_icon
(
ci_commit
)
ci_icon_for_status
(
ci_commit
.
status
)
end
def
ci_status_label
(
ci_commit
)
ci_label_for_status
(
ci_commit
.
status
)
end
def
ci_status_with_icon
(
status
,
target
=
nil
)
content
=
ci_icon_for_status
(
status
)
+
' '
.
html_safe
+
ci_label_for_status
(
status
)
klass
=
"ci-status ci-
#{
status
}
"
...
...
@@ -47,10 +39,13 @@ module CiStatusHelper
end
def
render_ci_status
(
ci_commit
,
tooltip_placement:
'auto left'
)
link_to
ci_status_icon
(
ci_commit
),
# TODO: split this method into
# - render_commit_status
# - render_pipeline_status
link_to
ci_icon_for_status
(
ci_commit
.
status
),
ci_status_path
(
ci_commit
),
class:
"ci-status-link ci-status-icon-
#{
ci_commit
.
status
.
dasherize
}
"
,
title:
"Build
#{
ci_
status_label
(
ci_commit
)
}
"
,
title:
"Build
#{
ci_
label_for_status
(
ci_commit
.
status
)
}
"
,
data:
{
toggle:
'tooltip'
,
placement:
tooltip_placement
}
end
...
...
app/helpers/gitlab_routing_helper.rb
View file @
3b39214f
...
...
@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path
(
project
.
namespace
,
project
,
@ref
||
project
.
repository
.
root_ref
)
end
def
project_pipelines_path
(
project
,
*
args
)
namespace_project_pipelines_path
(
project
.
namespace
,
project
,
*
args
)
end
def
project_builds_path
(
project
,
*
args
)
namespace_project_builds_path
(
project
.
namespace
,
project
,
*
args
)
end
...
...
app/helpers/page_layout_helper.rb
View file @
3b39214f
...
...
@@ -84,6 +84,14 @@ module PageLayoutHelper
end
end
def
nav
(
name
=
nil
)
if
name
@nav
=
name
else
@nav
end
end
def
fluid_layout
(
enabled
=
false
)
if
@fluid_layout
.
nil?
@fluid_layout
=
(
current_user
&&
current_user
.
layout
==
"fluid"
)
||
enabled
...
...
app/helpers/projects_helper.rb
View file @
3b39214f
...
...
@@ -144,6 +144,10 @@ module ProjectsHelper
nav_tabs
<<
:settings
end
if
can?
(
current_user
,
:read_project_member
,
project
)
nav_tabs
<<
:team
end
if
can?
(
current_user
,
:read_issue
,
project
)
nav_tabs
<<
:issues
end
...
...
app/helpers/tab_helper.rb
View file @
3b39214f
...
...
@@ -110,4 +110,12 @@ module TabHelper
'active'
end
end
def
profile_tab_class
if
controller
.
controller_path
=~
/\Aprofiles/
return
'active'
end
'active'
if
current_controller?
(
'oauth/applications'
)
end
end
app/models/ci/build.rb
View file @
3b39214f
...
...
@@ -37,8 +37,6 @@
module
Ci
class
Build
<
CommitStatus
LAZY_ATTRIBUTES
=
[
'trace'
]
belongs_to
:runner
,
class_name:
'Ci::Runner'
belongs_to
:trigger_request
,
class_name:
'Ci::TriggerRequest'
belongs_to
:erased_by
,
class_name:
'User'
...
...
@@ -50,25 +48,17 @@ module Ci
scope
:unstarted
,
->
()
{
where
(
runner_id:
nil
)
}
scope
:ignore_failures
,
->
()
{
where
(
allow_failure:
false
)
}
scope
:similar
,
->
(
build
)
{
where
(
ref:
build
.
ref
,
tag:
build
.
tag
,
trigger_request_id:
build
.
trigger_request_id
)
}
mount_uploader
:artifacts_file
,
ArtifactUploader
mount_uploader
:artifacts_metadata
,
ArtifactUploader
acts_as_taggable
# To prevent db load megabytes of data from trace
default_scope
->
{
select
(
Ci
::
Build
.
columns_without_lazy
)
}
before_destroy
{
project
}
class
<<
self
def
columns_without_lazy
(
column_names
-
LAZY_ATTRIBUTES
).
map
do
|
column_name
|
"
#{
table_name
}
.
#{
column_name
}
"
end
end
after_create
:execute_hooks
class
<<
self
def
last_month
where
(
'created_at > ?'
,
Date
.
today
-
1
.
month
)
end
...
...
@@ -126,12 +116,16 @@ module Ci
end
def
retried?
!
self
.
commit
.
latest_statuses_for_ref
(
self
.
ref
).
include?
(
self
)
!
self
.
commit
.
statuses
.
latest
.
include?
(
self
)
end
def
retry
Ci
::
Build
.
retry
(
self
)
end
def
depends_on_builds
# Get builds of the same type
latest_builds
=
self
.
commit
.
builds
.
similar
(
self
).
latest
latest_builds
=
self
.
commit
.
builds
.
latest
# Return builds from previous stages
latest_builds
.
where
(
'stage_idx < ?'
,
stage_idx
)
...
...
app/models/ci/commit.rb
View file @
3b39214f
...
...
@@ -19,21 +19,28 @@
module
Ci
class
Commit
<
ActiveRecord
::
Base
extend
Ci
::
Model
include
Statuseable
belongs_to
:project
,
class_name:
'::Project'
,
foreign_key: :gl_project_id
has_many
:statuses
,
class_name:
'CommitStatus'
has_many
:builds
,
class_name:
'Ci::Build'
has_many
:trigger_requests
,
dependent: :destroy
,
class_name:
'Ci::TriggerRequest'
delegate
:stages
,
to: :statuses
validates_presence_of
:sha
validates_presence_of
:status
validate
:valid_commit_sha
# Invalidate object and save if when touched
after_touch
:update_state
def
self
.
truncate_sha
(
sha
)
sha
[
0
...
8
]
end
def
to_param
sha
def
self
.
stages
CommitStatus
.
where
(
commit:
all
).
stages
end
def
project_id
...
...
@@ -68,15 +75,20 @@ module Ci
nil
end
def
stage
running_or_pending
=
statuses
.
latest
.
running_or_pending
.
ordered
running_or_pending
.
first
.
try
(
:stage
)
def
branch?
!
tag?
end
def
retryable?
builds
.
latest
.
any?
do
|
build
|
build
.
failed?
&&
build
.
retryable?
end
end
def
create_builds
(
ref
,
tag
,
user
,
trigger_request
=
nil
)
def
create_builds
(
user
,
trigger_request
=
nil
)
return
unless
config_processor
config_processor
.
stages
.
any?
do
|
stage
|
CreateBuildsService
.
new
.
execute
(
self
,
stage
,
ref
,
tag
,
user
,
trigger_request
,
'success'
).
present?
CreateBuildsService
.
new
(
self
).
execute
(
stage
,
user
,
'success'
,
trigger_request
).
present?
end
end
...
...
@@ -84,7 +96,7 @@ module Ci
return
unless
config_processor
# don't create other builds if this one is retried
latest_builds
=
builds
.
similar
(
build
).
latest
latest_builds
=
builds
.
latest
return
unless
latest_builds
.
exists?
(
build
.
id
)
# get list of stages after this build
...
...
@@ -92,88 +104,21 @@ module Ci
next_stages
.
delete
(
build
.
stage
)
# get status for all prior builds
prior_builds
=
latest_builds
.
reject
{
|
other_build
|
next_stages
.
include?
(
other_build
.
stage
)
}
status
=
Ci
::
Status
.
get_status
(
prior_builds
)
prior_builds
=
latest_builds
.
where
.
not
(
stage:
next_stages
)
prior_status
=
prior_builds
.
status
# create builds for next stages based
next_stages
.
any?
do
|
stage
|
CreateBuildsService
.
new
.
execute
(
self
,
stage
,
build
.
ref
,
build
.
tag
,
build
.
user
,
build
.
trigger_request
,
status
).
present?
CreateBuildsService
.
new
(
self
).
execute
(
stage
,
build
.
user
,
prior_status
,
build
.
trigger_request
).
present?
end
end
def
refs
statuses
.
order
(
:ref
).
pluck
(
:ref
).
uniq
end
def
latest_statuses
@latest_statuses
||=
statuses
.
latest
.
to_a
end
def
latest_statuses_for_ref
(
ref
)
latest_statuses
.
select
{
|
status
|
status
.
ref
==
ref
}
end
def
matrix_builds
(
build
=
nil
)
matrix_builds
=
builds
.
latest
.
ordered
matrix_builds
=
matrix_builds
.
similar
(
build
)
if
build
matrix_builds
.
to_a
end
def
retried
@retried
||=
(
statuses
.
order
(
id: :desc
)
-
statuses
.
latest
)
end
def
status
if
yaml_errors
.
present?
return
'failed'
end
@status
||=
Ci
::
Status
.
get_status
(
latest_statuses
)
end
def
pending?
status
==
'pending'
end
def
running?
status
==
'running'
end
def
success?
status
==
'success'
end
def
failed?
status
==
'failed'
end
def
canceled?
status
==
'canceled'
end
def
active?
running?
||
pending?
end
def
complete?
canceled?
||
success?
||
failed?
end
def
duration
duration_array
=
statuses
.
map
(
&
:duration
).
compact
duration_array
.
reduce
(:
+
).
to_i
end
def
started_at
@started_at
||=
statuses
.
order
(
'started_at ASC'
).
first
.
try
(
:started_at
)
end
def
finished_at
@finished_at
||=
statuses
.
order
(
'finished_at DESC'
).
first
.
try
(
:finished_at
)
end
def
coverage
coverage_array
=
latest_statuses
.
map
(
&
:coverage
).
compact
coverage_array
=
statuses
.
latest
.
map
(
&
:coverage
).
compact
if
coverage_array
.
size
>=
1
'%.2f'
%
(
coverage_array
.
reduce
(:
+
)
/
coverage_array
.
size
)
end
...
...
@@ -181,23 +126,29 @@ module Ci
def
config_processor
return
nil
unless
ci_yaml_file
@config_processor
||=
Ci
::
GitlabCiYamlProcessor
.
new
(
ci_yaml_file
,
project
.
path_with_namespace
)
rescue
Ci
::
GitlabCiYamlProcessor
::
ValidationError
,
Psych
::
SyntaxError
=>
e
save_yaml_error
(
e
.
message
)
nil
rescue
save_yaml_error
(
"Undefined error"
)
nil
return
@config_processor
if
defined?
(
@config_processor
)
@config_processor
||=
begin
Ci
::
GitlabCiYamlProcessor
.
new
(
ci_yaml_file
,
project
.
path_with_namespace
)
rescue
Ci
::
GitlabCiYamlProcessor
::
ValidationError
,
Psych
::
SyntaxError
=>
e
save_yaml_error
(
e
.
message
)
nil
rescue
save_yaml_error
(
"Undefined error"
)
nil
end
end
def
ci_yaml_file
return
@ci_yaml_file
if
defined?
(
@ci_yaml_file
)
@ci_yaml_file
||=
begin
blob
=
project
.
repository
.
blob_at
(
sha
,
'.gitlab-ci.yml'
)
blob
.
load_all_data!
(
project
.
repository
)
blob
.
data
rescue
nil
end
rescue
nil
end
def
skip_ci?
...
...
@@ -206,10 +157,23 @@ module Ci
private
def
update_state
statuses
.
reload
self
.
status
=
if
yaml_errors
.
blank?
statuses
.
latest
.
status
||
'skipped'
else
'failed'
end
self
.
started_at
=
statuses
.
started_at
self
.
finished_at
=
statuses
.
finished_at
self
.
duration
=
statuses
.
latest
.
duration
save
end
def
save_yaml_error
(
error
)
return
if
self
.
yaml_errors?
self
.
yaml_errors
=
error
sav
e
update_stat
e
end
end
end
app/models/commit.rb
View file @
3b39214f
...
...
@@ -207,12 +207,13 @@ class Commit
@raw
.
short_id
(
7
)
end
def
ci_commit
project
.
ci_commit
(
sha
)
def
ci_commit
s
@ci_commits
||=
project
.
ci_commits
.
where
(
sha:
sha
)
end
def
status
ci_commit
.
try
(
:status
)
||
:not_found
return
@status
if
defined?
(
@status
)
@status
||=
ci_commits
.
status
end
def
revert_branch_name
...
...
app/models/commit_status.rb
View file @
3b39214f
...
...
@@ -33,30 +33,23 @@
#
class
CommitStatus
<
ActiveRecord
::
Base
include
Statuseable
self
.
table_name
=
'ci_builds'
belongs_to
:project
,
class_name:
'::Project'
,
foreign_key: :gl_project_id
belongs_to
:commit
,
class_name:
'Ci::Commit'
belongs_to
:commit
,
class_name:
'Ci::Commit'
,
touch:
true
belongs_to
:user
validates
:commit
,
presence:
true
validates
:status
,
inclusion:
{
in:
%w(pending running failed success canceled)
}
validates_presence_of
:name
alias_attribute
:author
,
:user
scope
:running
,
->
{
where
(
status:
'running'
)
}
scope
:pending
,
->
{
where
(
status:
'pending'
)
}
scope
:success
,
->
{
where
(
status:
'success'
)
}
scope
:failed
,
->
{
where
(
status:
'failed'
)
}
scope
:running_or_pending
,
->
{
where
(
status:
[
:running
,
:pending
])
}
scope
:finished
,
->
{
where
(
status:
[
:success
,
:failed
,
:canceled
])
}
scope
:latest
,
->
{
where
(
id:
unscope
(
:select
).
select
(
'max(id)'
).
group
(
:name
,
:ref
))
}
scope
:latest
,
->
{
where
(
id:
unscope
(
:select
).
select
(
'max(id)'
).
group
(
:name
,
:commit_id
))
}
scope
:ordered
,
->
{
order
(
:ref
,
:stage_idx
,
:name
)
}
scope
:for_ref
,
->
(
ref
)
{
where
(
ref:
ref
)
}
AVAILABLE_STATUSES
=
[
'pending'
,
'running'
,
'success'
,
'failed'
,
'canceled'
]
scope
:ignored
,
->
{
where
(
allow_failure:
true
,
status:
[
:failed
,
:canceled
])
}
state_machine
:status
,
initial: :pending
do
event
:run
do
...
...
@@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base
after_transition
[
:pending
,
:running
]
=>
:success
do
|
commit_status
|
MergeRequests
::
MergeWhenBuildSucceedsService
.
new
(
commit_status
.
commit
.
project
,
nil
).
trigger
(
commit_status
)
end
state
:pending
,
value:
'pending'
state
:running
,
value:
'running'
state
:failed
,
value:
'failed'
state
:success
,
value:
'success'
state
:canceled
,
value:
'canceled'
end
delegate
:sha
,
:short_sha
,
to: :commit
,
prefix:
false
delegate
:sha
,
:short_sha
,
to: :commit
# TODO: this should be removed with all references
def
before_sha
Gitlab
::
Git
::
BLANK_SHA
commit
.
before_sha
||
Gitlab
::
Git
::
BLANK_SHA
end
def
started?
!
pending?
&&
!
canceled?
&&
started_at
def
self
.
stages
order_by
=
'max(stage_idx)'
group
(
'stage'
).
order
(
order_by
).
pluck
(
:stage
,
order_by
).
map
(
&
:first
).
compact
end
def
active?
running?
||
pending?
end
def
complete?
canceled?
||
success?
||
failed?
def
self
.
stages_status
all
.
stages
.
inject
({})
do
|
h
,
stage
|
h
[
stage
]
=
all
.
where
(
stage:
stage
).
status
h
end
end
def
ignored?
...
...
@@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base
end
def
duration
if
started_at
&&
finished_at
finished_at
-
started_at
elsif
started_at
Time
.
now
-
started_at
end
duration
=
if
started_at
&&
finished_at
finished_at
-
started_at
elsif
started_at
Time
.
now
-
started_at
end
duration
end
def
stuck?
...
...
app/models/concerns/issuable.rb
View file @
3b39214f
...
...
@@ -37,7 +37,6 @@ module Issuable
scope
:closed
,
->
{
with_state
(
:closed
)
}
scope
:order_milestone_due_desc
,
->
{
joins
(
:milestone
).
reorder
(
'milestones.due_date DESC, milestones.id DESC'
)
}
scope
:order_milestone_due_asc
,
->
{
joins
(
:milestone
).
reorder
(
'milestones.due_date ASC, milestones.id ASC'
)
}
scope
:with_label
,
->
(
title
)
{
joins
(
:labels
).
where
(
labels:
{
title:
title
})
}
scope
:without_label
,
->
{
joins
(
"LEFT OUTER JOIN label_links ON label_links.target_type = '
#{
name
}
' AND label_links.target_id =
#{
table_name
}
.id"
).
where
(
label_links:
{
id:
nil
})
}
scope
:join_project
,
->
{
joins
(
:project
)
}
...
...
@@ -122,6 +121,14 @@ module Issuable
joins
(
join_clause
).
group
(
issuable_table
[
:id
]).
reorder
(
"COUNT(notes.id) DESC"
)
end
def
with_label
(
title
)
if
title
.
is_a?
(
Array
)
&&
title
.
count
>
1
joins
(
:labels
).
where
(
labels:
{
title:
title
}).
group
(
'issues.id'
).
having
(
"count(distinct labels.title) =
#{
title
.
count
}
"
)
else
joins
(
:labels
).
where
(
labels:
{
title:
title
})
end
end
end
def
today?
...
...
app/models/concerns/statuseable.rb
0 → 100644
View file @
3b39214f
module
Statuseable
extend
ActiveSupport
::
Concern
AVAILABLE_STATUSES
=
%w(pending running success failed canceled skipped)
class_methods
do
def
status_sql
builds
=
all
.
select
(
'count(*)'
).
to_sql
success
=
all
.
success
.
select
(
'count(*)'
).
to_sql
ignored
=
all
.
ignored
.
select
(
'count(*)'
).
to_sql
if
all
.
respond_to?
(
:ignored
)
ignored
||=
'0'
pending
=
all
.
pending
.
select
(
'count(*)'
).
to_sql
running
=
all
.
running
.
select
(
'count(*)'
).
to_sql
canceled
=
all
.
canceled
.
select
(
'count(*)'
).
to_sql
skipped
=
all
.
skipped
.
select
(
'count(*)'
).
to_sql
deduce_status
=
"(CASE
WHEN (
#{
builds
}
)=0 THEN NULL
WHEN (
#{
builds
}
)=(
#{
success
}
)+(
#{
ignored
}
) THEN 'success'
WHEN (
#{
builds
}
)=(
#{
pending
}
) THEN 'pending'
WHEN (
#{
builds
}
)=(
#{
canceled
}
) THEN 'canceled'
WHEN (
#{
builds
}
)=(
#{
skipped
}
) THEN 'skipped'
WHEN (
#{
running
}
)+(
#{
pending
}
)>0 THEN 'running'
ELSE 'failed'
END)"
deduce_status
end
def
status
all
.
pluck
(
self
.
status_sql
).
first
end
def
duration
duration_array
=
all
.
map
(
&
:duration
).
compact
duration_array
.
reduce
(:
+
)
end
def
started_at
all
.
minimum
(
:started_at
)
end
def
finished_at
all
.
maximum
(
:finished_at
)
end
end
included
do
validates
:status
,
inclusion:
{
in:
AVAILABLE_STATUSES
}
state_machine
:status
,
initial: :pending
do
state
:pending
,
value:
'pending'
state
:running
,
value:
'running'
state
:failed
,
value:
'failed'
state
:success
,
value:
'success'
state
:canceled
,
value:
'canceled'
state
:skipped
,
value:
'skipped'
end
scope
:running
,
->
{
where
(
status:
'running'
)
}
scope
:pending
,
->
{
where
(
status:
'pending'
)
}
scope
:success
,
->
{
where
(
status:
'success'
)
}
scope
:failed
,
->
{
where
(
status:
'failed'
)
}
scope
:canceled
,
->
{
where
(
status:
'canceled'
)
}
scope
:skipped
,
->
{
where
(
status:
'skipped'
)
}
scope
:running_or_pending
,
->
{
where
(
status:
[
:running
,
:pending
])
}
scope
:finished
,
->
{
where
(
status:
[
:success
,
:failed
,
:canceled
])
}
end
def
started?
!
pending?
&&
!
canceled?
&&
started_at
end
def
active?
running?
||
pending?
end
def
complete?
canceled?
||
success?
||
failed?
end
end
app/models/merge_request.rb
View file @
3b39214f
...
...
@@ -586,7 +586,7 @@ class MergeRequest < ActiveRecord::Base
end
def
ci_commit
@ci_commit
||=
source_project
.
ci_commit
(
last_commit
.
id
)
if
last_commit
&&
source_project
@ci_commit
||=
source_project
.
ci_commit
(
last_commit
.
id
,
source_branch
)
if
last_commit
&&
source_project
end
def
diff_refs
...
...
app/models/project.rb
View file @
3b39214f
...
...
@@ -963,12 +963,12 @@ class Project < ActiveRecord::Base
!
namespace
.
share_with_group_lock
end
def
ci_commit
(
sha
)
ci_commits
.
find_by
(
sha:
sha
)
def
ci_commit
(
sha
,
ref
)
ci_commits
.
order
(
id: :desc
).
find_by
(
sha:
sha
,
ref:
ref
)
end
def
ensure_ci_commit
(
sha
)
ci_commit
(
sha
)
||
ci_commits
.
create
(
sha:
sha
)
def
ensure_ci_commit
(
sha
,
ref
)
ci_commit
(
sha
,
ref
)
||
ci_commits
.
create
(
sha:
sha
,
ref:
ref
)
end
def
enable_ci
...
...
app/services/ci/create_builds_service.rb
View file @
3b39214f
module
Ci
class
CreateBuildsService
def
execute
(
commit
,
stage
,
ref
,
tag
,
user
,
trigger_request
,
status
)
builds_attrs
=
commit
.
config_processor
.
builds_for_stage_and_ref
(
stage
,
ref
,
tag
,
trigger_request
)
def
initialize
(
commit
)
@commit
=
commit
end
def
execute
(
stage
,
user
,
status
,
trigger_request
=
nil
)
builds_attrs
=
config_processor
.
builds_for_stage_and_ref
(
stage
,
@commit
.
ref
,
@commit
.
tag
,
trigger_request
)
# check when to create next build
builds_attrs
=
builds_attrs
.
select
do
|
build_attrs
|
...
...
@@ -17,7 +21,8 @@ module Ci
builds_attrs
.
map
do
|
build_attrs
|
# don't create the same build twice
unless
commit
.
builds
.
find_by
(
ref:
ref
,
tag:
tag
,
trigger_request:
trigger_request
,
name:
build_attrs
[
:name
])
unless
@commit
.
builds
.
find_by
(
ref:
@commit
.
ref
,
tag:
@commit
.
tag
,
trigger_request:
trigger_request
,
name:
build_attrs
[
:name
])
build_attrs
.
slice!
(
:name
,
:commands
,
:tag_list
,
...
...
@@ -26,17 +31,21 @@ module Ci
:stage
,
:stage_idx
)
build_attrs
.
merge!
(
ref:
ref
,
tag:
tag
,
build_attrs
.
merge!
(
ref:
@commit
.
ref
,
tag:
@commit
.
tag
,
trigger_request:
trigger_request
,
user:
user
,
project:
commit
.
project
)
project:
@
commit
.
project
)
build
=
commit
.
builds
.
create!
(
build_attrs
)
build
.
execute_hooks
build
@commit
.
builds
.
create!
(
build_attrs
)
end
end
end
private
def
config_processor
@config_processor
||=
@commit
.
config_processor
end
end
end
app/services/ci/create_trigger_request_service.rb
View file @
3b39214f
...
...
@@ -7,14 +7,14 @@ module Ci
# check if ref is tag
tag
=
project
.
repository
.
find_tag
(
ref
).
present?
ci_commit
=
project
.
ensure_ci_commit
(
commit
.
sha
)
ci_commit
=
project
.
ci_commits
.
create
(
sha:
commit
.
sha
,
ref:
ref
,
tag:
tag
)
trigger_request
=
trigger
.
trigger_requests
.
create!
(
variables:
variables
,
commit:
ci_commit
,
)
if
ci_commit
.
create_builds
(
ref
,
tag
,
nil
,
trigger_request
)
if
ci_commit
.
create_builds
(
nil
,
trigger_request
)
trigger_request
end
end
...
...
app/services/ci/image_for_build_service.rb
View file @
3b39214f
...
...
@@ -3,8 +3,9 @@ module Ci
def
execute
(
project
,
opts
)
sha
=
opts
[
:sha
]
||
ref_sha
(
project
,
opts
[
:ref
])
commit
=
project
.
ci_commits
.
find_by
(
sha:
sha
)
image_name
=
image_for_commit
(
commit
)
ci_commits
=
project
.
ci_commits
.
where
(
sha:
sha
)
ci_commits
=
ci_commits
.
where
(
ref:
opts
[
:ref
])
if
opts
[
:ref
]
image_name
=
image_for_status
(
ci_commits
.
status
)
image_path
=
Rails
.
root
.
join
(
'public/ci'
,
image_name
)
OpenStruct
.
new
(
path:
image_path
,
name:
image_name
)
...
...
@@ -16,9 +17,9 @@ module Ci
project
.
commit
(
ref
).
try
(
:sha
)
if
ref
end
def
image_for_
commit
(
commit
)
return
'build-unknown.svg'
unless
commit
'build-'
+
commit
.
status
+
".svg"
def
image_for_
status
(
status
)
status
||=
'unknown'
'build-'
+
status
+
".svg"
end
end
end
app/services/create_commit_builds_service.rb
View file @
3b39214f
...
...
@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def
execute
(
project
,
user
,
params
)
return
false
unless
project
.
builds_enabled?
before_sha
=
params
[
:checkout_sha
]
||
params
[
:before
]
sha
=
params
[
:checkout_sha
]
||
params
[
:after
]
origin_ref
=
params
[
:ref
]
...
...
@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end
ref
=
Gitlab
::
Git
.
ref_name
(
origin_ref
)
tag
=
Gitlab
::
Git
.
tag_ref?
(
origin_ref
)
# Skip branch removal
if
sha
==
Gitlab
::
Git
::
BLANK_SHA
return
false
end
commit
=
project
.
ci_commit
(
sha
)
commit
=
project
.
ci_commit
(
sha
,
ref
)
unless
commit
commit
=
project
.
ci_commits
.
new
(
sha:
sha
)
commit
=
project
.
ci_commits
.
new
(
sha:
sha
,
ref:
ref
,
before_sha:
before_sha
,
tag:
tag
)
# Skip creating ci_commit when no gitlab-ci.yml is found
unless
commit
.
ci_yaml_file
...
...
@@ -32,10 +34,10 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip]
unless
commit
.
skip_ci?
# Create builds for commit
tag
=
Gitlab
::
Git
.
tag_ref?
(
origin_ref
)
commit
.
create_builds
(
ref
,
tag
,
user
)
commit
.
create_builds
(
user
)
end
commit
.
touch
commit
end
end
app/views/admin/users/_form.html.haml
View file @
3b39214f
...
...
@@ -7,17 +7,17 @@
.form-group
=
f
.
label
:name
,
class:
'control-label'
.col-sm-10
=
f
.
text_field
:name
,
required:
true
,
autocomplete:
"off"
,
class:
'form-control'
=
f
.
text_field
:name
,
required:
true
,
autocomplete:
'off'
,
class:
'form-control'
%span
.help-inline
* required
.form-group
=
f
.
label
:username
,
class:
'control-label'
.col-sm-10
=
f
.
text_field
:username
,
required:
true
,
autocomplete:
"off"
,
class:
'form-control'
=
f
.
text_field
:username
,
required:
true
,
autocomplete:
'off'
,
autocorrect:
'off'
,
autocapitalize:
'off'
,
spellcheck:
false
,
class:
'form-control'
%span
.help-inline
* required
.form-group
=
f
.
label
:email
,
class:
'control-label'
.col-sm-10
=
f
.
text_field
:email
,
required:
true
,
autocomplete:
"off"
,
class:
'form-control'
=
f
.
text_field
:email
,
required:
true
,
autocomplete:
'off'
,
class:
'form-control'
%span
.help-inline
* required
-
if
@user
.
new_record?
...
...
app/views/dashboard/todos/index.html.haml
View file @
3b39214f
...
...
@@ -45,6 +45,7 @@
.prepend-top-default
-
if
@todos
.
any?
.js-todos-options
{
data:
{
per_page:
@todos
.
limit_value
,
current_page:
@todos
.
current_page
,
total_pages:
@todos
.
total_pages
}
}
-
@todos
.
group_by
(
&
:project
).
each
do
|
group
|
.panel.panel-default.panel-small.js-todos-list
-
project
=
group
[
0
]
...
...
app/views/doorkeeper/applications/index.html.haml
View file @
3b39214f
-
page_title
"Applications"
-
header_title
page_title
,
applications_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
...
...
app/views/layouts/_page.html.haml
View file @
3b39214f
...
...
@@ -25,6 +25,10 @@
.content-wrapper
=
render
"layouts/flash"
=
yield
:flash_message
-
if
defined?
(
nav
)
&&
nav
.layout-nav
%div
{
class:
container_class
}
=
render
"layouts/nav/
#{
nav
}
"
%div
{
class:
(
container_class
unless
@no_container
)
}
.content
.clearfix
...
...
app/views/layouts/application.html.haml
View file @
3b39214f
...
...
@@ -6,6 +6,6 @@
=
yield
:scripts_body_top
=
render
"layouts/header/default"
,
title:
header_title
=
render
'layouts/page'
,
sidebar:
sidebar
=
render
'layouts/page'
,
sidebar:
sidebar
,
nav:
nav
=
yield
:scripts_body
app/views/layouts/nav/_dashboard.html.haml
View file @
3b39214f
...
...
@@ -48,8 +48,7 @@
%span
Help
%li
.separate-item
=
nav_link
(
controller: :profile
)
do
=
nav_link
(
html_options:
{
class:
profile_tab_class
})
do
=
link_to
profile_path
,
title:
'Profile Settings'
,
data:
{
placement:
'bottom'
}
do
=
icon
(
'user fw'
)
%span
...
...
app/views/layouts/nav/_profile.html.haml
View file @
3b39214f
%ul
.nav.nav-sidebar
=
nav_link
do
=
link_to
root_path
,
title:
'Go to dashboard'
,
class:
'back-link'
do
=
icon
(
'caret-square-o-left fw'
)
%span
Go to dashboard
%li
.separate-item
%ul
.nav-links
=
nav_link
(
path:
'profiles#show'
,
html_options:
{
class:
'home'
})
do
=
link_to
profile_path
,
title:
'Profile Settings'
do
=
icon
(
'user fw'
)
%span
Profile
Settings
Profile
=
nav_link
(
controller:
[
:accounts
,
:two_factor_auths
])
do
=
link_to
profile_account_path
,
title:
'Account'
do
=
icon
(
'gear fw'
)
...
...
@@ -27,7 +19,6 @@
=
icon
(
'envelope-o fw'
)
%span
Emails
%span
.count
=
number_with_delimiter
(
current_user
.
emails
.
count
+
1
)
-
unless
current_user
.
ldap_user?
=
nav_link
(
controller: :passwords
)
do
=
link_to
edit_profile_password_path
,
title:
'Password'
do
...
...
@@ -45,7 +36,6 @@
=
icon
(
'key fw'
)
%span
SSH Keys
%span
.count
=
number_with_delimiter
(
current_user
.
keys
.
count
)
=
nav_link
(
controller: :preferences
)
do
=
link_to
profile_preferences_path
,
title:
'Preferences'
do
-# TODO (rspeicher): Better icon?
...
...
app/views/layouts/nav/_project.html.haml
View file @
3b39214f
...
...
@@ -77,7 +77,7 @@
Merge Requests
%span
.count.merge_counter
=
number_with_delimiter
(
@project
.
merge_requests
.
opened
.
count
)
-
if
project_nav_tab?
:
settings
-
if
project_nav_tab?
:
team
=
nav_link
(
controller:
[
:project_members
,
:teams
])
do
=
link_to
namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
title:
'Members'
,
class:
'team-tab tab'
do
=
icon
(
'users fw'
)
...
...
app/views/layouts/profile.html.haml
View file @
3b39214f
-
page_title
"Profile Settings"
-
header_title
"Profile Settings"
,
profile_path
unless
header_title
-
sidebar
"profile"
-
sidebar
"dashboard"
-
nav
"profile"
=
render
template:
"layouts/application"
app/views/profiles/accounts/show.html.haml
View file @
3b39214f
-
page_title
"Account"
-
header_title
page_title
,
profile_account_path
-
if
current_user
.
ldap_user?
.alert.alert-info
...
...
app/views/profiles/audit_log.html.haml
View file @
3b39214f
-
page_title
"Audit Log"
-
header_title
page_title
,
audit_log_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
...
...
app/views/profiles/emails/index.html.haml
View file @
3b39214f
-
page_title
"Emails"
-
header_title
page_title
,
profile_emails_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
...
...
app/views/profiles/keys/index.html.haml
View file @
3b39214f
-
page_title
"SSH Keys"
-
header_title
page_title
,
profile_keys_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
...
...
app/views/profiles/notifications/show.html.haml
View file @
3b39214f
-
page_title
"Notifications"
-
header_title
page_title
,
profile_notifications_path
%div
-
if
@user
.
errors
.
any?
...
...
app/views/profiles/passwords/edit.html.haml
View file @
3b39214f
-
page_title
"Password"
-
header_title
page_title
,
edit_profile_password_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
...
...
app/views/profiles/preferences/show.html.haml
View file @
3b39214f
-
page_title
'Preferences'
-
header_title
page_title
,
profile_preferences_path
=
form_for
@user
,
url:
profile_preferences_path
,
remote:
true
,
method: :put
,
html:
{
class:
'row prepend-top-default js-preferences-form'
}
do
|
f
|
.col-lg-3.profile-settings-sidebar
...
...
app/views/projects/_builds_settings.html.haml
View file @
3b39214f
...
...
@@ -55,6 +55,9 @@
%li
gcovr (C/C++) -
%code
^TOTAL.*\s+(\d+\%)$
%li
tap --coverage-report=text-summary (Node.js) -
%code
^Statements\s*:\s*([^%]+)
.form-group
.col-sm-offset-2.col-sm-10
...
...
app/views/projects/_last_commit.html.haml
View file @
3b39214f
.project-last-commit
-
ci_commit
=
project
.
ci_commit
(
commit
.
sha
)
-
if
ci_commit
=
link_to
ci_status_path
(
ci_commit
),
class:
"ci-status ci-
#{
ci_commit
.
status
}
"
do
=
ci_status_icon
(
ci_commit
)
=
ci_status_label
(
ci_commit
)
-
if
commit
.
status
=
link_to
builds_namespace_project_commit_path
(
commit
.
project
.
namespace
,
commit
.
project
,
commit
),
class:
"ci-status ci-
#{
commit
.
status
}
"
do
=
ci_icon_for_status
(
commit
.
status
)
=
ci_label_for_status
(
commit
.
status
)
=
link_to
commit
.
short_id
,
namespace_project_commit_path
(
project
.
namespace
,
project
,
commit
),
class:
"commit_short_id"
=
link_to_gfm
commit
.
title
,
namespace_project_commit_path
(
project
.
namespace
,
project
,
commit
),
class:
"commit-row-message"
...
...
app/views/projects/blob/diff.html.haml
View file @
3b39214f
-
if
@lines
.
present?
-
if
@form
.
unfold?
&&
@form
.
since
!=
1
&&
!
@form
.
bottom?
%tr
.line_holder
{
id:
@form
.
since
}
%tr
.line_holder
=
render
"projects/diffs/match_line"
,
{
line:
@match_line
,
line_old:
@form
.
since
,
line_new:
@form
.
since
,
bottom:
false
,
new_file:
false
}
-
@lines
.
each_with_index
do
|
line
,
index
|
-
line_new
=
index
+
@form
.
since
-
line_old
=
line_new
-
@form
.
offset
%tr
.line_holder
%tr
.line_holder
{
id:
line_old
}
%td
.old_line.diff-line-num
{
data:
{
linenumber:
line_old
}
}
=
link_to
raw
(
line_old
),
"#"
=
link_to
raw
(
line_old
),
"#
#{
line_old
}
"
%td
.new_line.diff-line-num
{
data:
{
linenumber:
line_old
}
}
=
link_to
raw
(
line_new
)
,
"#"
=
link_to
raw
(
line_new
)
,
"#
#{
line_old
}
"
%td
.line_content.noteable_line
==
#{
' '
*
@form
.
indent
}#{
line
}
-
if
@form
.
unfold?
&&
@form
.
bottom?
&&
@form
.
to
<
@blob
.
loc
...
...
app/views/projects/builds/index.html.haml
View file @
3b39214f
...
...
@@ -58,6 +58,6 @@
%th
Coverage
%th
=
render
@builds
,
commit_sha:
true
,
stage:
true
,
allow_retry:
true
,
coverage:
@project
.
build_coverage_enabled?
=
render
@builds
,
commit_sha:
true
,
ref:
true
,
stage:
true
,
allow_retry:
true
,
coverage:
@project
.
build_coverage_enabled?
=
paginate
@builds
,
theme:
'gitlab'
app/views/projects/builds/show.html.haml
View file @
3b39214f
...
...
@@ -13,7 +13,7 @@
=
link_to
"merge request
#{
merge_request
.
to_reference
}
"
,
merge_request_path
(
merge_request
)
#up-build-trace
-
builds
=
@build
.
commit
.
matrix_builds
(
@build
)
-
builds
=
@build
.
commit
.
builds
.
latest
.
to_a
-
if
builds
.
size
>
1
%ul
.nav-links.no-top.no-bottom
-
builds
.
each
do
|
build
|
...
...
app/views/projects/ci/builds/_build.html.haml
View file @
3b39214f
...
...
@@ -19,11 +19,12 @@
%td
=
link_to
build
.
short_sha
,
namespace_project_commit_path
(
build
.
project
.
namespace
,
build
.
project
,
build
.
sha
),
class:
"monospace"
%td
-
if
build
.
ref
=
link_to
build
.
ref
,
namespace_project_commits_path
(
build
.
project
.
namespace
,
build
.
project
,
build
.
ref
)
-
else
.light
none
-
if
defined?
(
ref
)
&&
ref
%td
-
if
build
.
ref
=
link_to
build
.
ref
,
namespace_project_commits_path
(
build
.
project
.
namespace
,
build
.
project
,
build
.
ref
)
-
else
.light
none
-
if
defined?
(
runner
)
&&
runner
%td
...
...
@@ -48,6 +49,8 @@
%span
.label.label-info
triggered
-
if
build
.
try
(
:allow_failure
)
%span
.label.label-danger
allowed to fail
-
if
defined?
(
retried
)
&&
retried
%span
.label.label-warning
retried
%td
.duration
-
if
build
.
duration
...
...
app/views/projects/commit/_builds.html.haml
View file @
3b39214f
.gray-content-block.middle-block
.pull-right
-
if
can?
(
current_user
,
:update_build
,
@ci_commit
.
project
)
-
if
@ci_commit
.
builds
.
latest
.
failed
.
any?
(
&
:retryable?
)
=
link_to
"Retry failed"
,
retry_builds_namespace_project_commit_path
(
@ci_commit
.
project
.
namespace
,
@ci_commit
.
project
,
@ci_commit
.
sha
),
class:
'btn btn-grouped btn-primary'
,
method: :post
-
if
@ci_commit
.
builds
.
running_or_pending
.
any?
=
link_to
"Cancel running"
,
cancel_builds_namespace_project_commit_path
(
@ci_commit
.
project
.
namespace
,
@ci_commit
.
project
,
@ci_commit
.
sha
),
data:
{
confirm:
'Are you sure?'
},
class:
'btn btn-grouped btn-danger'
,
method: :post
.oneline
=
pluralize
@statuses
.
count
(
:id
),
"build"
-
if
defined?
(
link_to_commit
)
&&
link_to_commit
for commit
=
link_to
@ci_commit
.
short_sha
,
namespace_project_commit_path
(
@ci_commit
.
project
.
namespace
,
@ci_commit
.
project
,
@ci_commit
.
sha
),
class:
"monospace"
-
if
@ci_commit
.
duration
>
0
in
=
time_interval_in_words
@ci_commit
.
duration
-
if
@ci_commit
.
yaml_errors
.
present?
.bs-callout.bs-callout-danger
%h4
Found errors in your .gitlab-ci.yml:
%ul
-
@ci_commit
.
yaml_errors
.
split
(
","
).
each
do
|
error
|
%li
=
error
You can also test your .gitlab-ci.yml in the
#{
link_to
"Lint"
,
ci_lint_path
}
-
if
@ci_commit
.
project
.
builds_enabled?
&&
!
@ci_commit
.
ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
.table-holder
%table
.table.builds
%thead
%tr
%th
Status
%th
Build ID
%th
Ref
%th
Stage
%th
Name
%th
Duration
%th
Finished at
-
if
@ci_commit
.
project
.
build_coverage_enabled?
%th
Coverage
%th
-
@ci_commit
.
refs
.
each
do
|
ref
|
-
builds
=
@ci_commit
.
statuses
.
for_ref
(
ref
).
latest
.
ordered
=
render
builds
,
coverage:
@ci_commit
.
project
.
build_coverage_enabled?
,
stage:
true
,
allow_retry:
true
-
if
@ci_commit
.
retried
.
any?
.gray-content-block.second-block
Retried builds
.table-holder
%table
.table.builds
%thead
%tr
%th
Status
%th
Build ID
%th
Ref
%th
Stage
%th
Name
%th
Duration
%th
Finished at
-
if
@ci_commit
.
project
.
build_coverage_enabled?
%th
Coverage
%th
=
render
@ci_commit
.
retried
,
coverage:
@ci_commit
.
project
.
build_coverage_enabled?
,
stage:
true
-
@ci_commits
.
each
do
|
ci_commit
|
=
render
"ci_commit"
,
ci_commit:
ci_commit
app/views/projects/commit/_ci_commit.html.haml
0 → 100644
View file @
3b39214f
.gray-content-block.middle-block
.pull-right
-
if
can?
(
current_user
,
:update_build
,
@project
)
-
if
ci_commit
.
builds
.
latest
.
failed
.
any?
(
&
:retryable?
)
=
link_to
"Retry failed"
,
retry_builds_namespace_project_commit_path
(
@project
.
namespace
,
@project
,
ci_commit
.
sha
),
class:
'btn btn-grouped btn-primary'
,
method: :post
-
if
ci_commit
.
builds
.
running_or_pending
.
any?
=
link_to
"Cancel running"
,
cancel_builds_namespace_project_commit_path
(
@project
.
namespace
,
@project
,
ci_commit
.
sha
),
data:
{
confirm:
'Are you sure?'
},
class:
'btn btn-grouped btn-danger'
,
method: :post
.oneline
=
pluralize
ci_commit
.
statuses
.
count
(
:id
),
"build"
-
if
ci_commit
.
ref
for
%span
.label.label-info
=
ci_commit
.
ref
-
if
defined?
(
link_to_commit
)
&&
link_to_commit
for commit
=
link_to
ci_commit
.
short_sha
,
namespace_project_commit_path
(
@project
.
namespace
,
@project
,
ci_commit
.
sha
),
class:
"monospace"
-
if
ci_commit
.
duration
>
0
in
=
time_interval_in_words
ci_commit
.
duration
-
if
ci_commit
.
yaml_errors
.
present?
.bs-callout.bs-callout-danger
%h4
Found errors in your .gitlab-ci.yml:
%ul
-
ci_commit
.
yaml_errors
.
split
(
","
).
each
do
|
error
|
%li
=
error
You can also test your .gitlab-ci.yml in the
#{
link_to
"Lint"
,
ci_lint_path
}
-
if
@project
.
builds_enabled?
&&
!
ci_commit
.
ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
.table-holder
%table
.table.builds
%thead
%tr
%th
Status
%th
Build ID
%th
Stage
%th
Name
%th
Duration
%th
Finished at
-
if
@project
.
build_coverage_enabled?
%th
Coverage
%th
-
builds
=
ci_commit
.
statuses
.
latest
.
ordered
=
render
builds
,
coverage:
@project
.
build_coverage_enabled?
,
stage:
true
,
ref:
false
,
allow_retry:
true
-
if
ci_commit
.
retried
.
any?
.gray-content-block.second-block
Retried builds
.table-holder
%table
.table.builds
%thead
%tr
%th
Status
%th
Build ID
%th
Ref
%th
Stage
%th
Name
%th
Duration
%th
Finished at
-
if
@project
.
build_coverage_enabled?
%th
Coverage
%th
=
render
ci_commit
.
retried
,
coverage:
@project
.
build_coverage_enabled?
,
stage:
true
,
ref:
false
app/views/projects/commit/_commit_box.html.haml
View file @
3b39214f
...
...
@@ -43,12 +43,12 @@
-
@commit
.
parents
.
each
do
|
parent
|
=
link_to
parent
.
short_id
,
namespace_project_commit_path
(
@project
.
namespace
,
@project
,
parent
),
class:
"monospace"
-
if
@c
i_commit
-
if
@c
ommit
.
status
.pull-right
=
link_to
ci_status_path
(
@ci_commit
),
class:
"ci-status ci-
#{
@ci_
commit
.
status
}
"
do
=
ci_
status_icon
(
@ci_commit
)
=
link_to
builds_namespace_project_commit_path
(
@project
.
namespace
,
@project
,
@commit
.
id
),
class:
"ci-status ci-
#{
@
commit
.
status
}
"
do
=
ci_
icon_for_status
(
@commit
.
status
)
build:
=
ci_
status_label
(
@ci_commit
)
=
ci_
label_for_status
(
@commit
.
status
)
.commit-info-row.branches
%i
.fa.fa-spinner.fa-spin
...
...
app/views/projects/commit/show.html.haml
View file @
3b39214f
...
...
@@ -5,7 +5,7 @@
.prepend-top-default
=
render
"commit_box"
-
if
@c
i_commit
-
if
@c
ommit
.
status
=
render
"ci_menu"
-
else
%div
.block-connector
...
...
app/views/projects/commits/_commit.html.haml
View file @
3b39214f
...
...
@@ -4,9 +4,8 @@
-
notes
=
commit
.
notes
-
note_count
=
notes
.
user
.
count
-
ci_commit
=
project
.
ci_commit
(
commit
.
sha
)
-
cache_key
=
[
project
.
path_with_namespace
,
commit
.
id
,
current_application_settings
,
note_count
]
-
cache_key
.
push
(
c
i_commit
.
status
)
if
ci_commit
-
cache_key
.
push
(
c
ommit
.
status
)
if
commit
.
status
=
cache
(
cache_key
)
do
%li
.commit.js-toggle-container
{
id:
"commit-#{commit.short_id}"
}
...
...
@@ -17,8 +16,8 @@
%a
.text-expander.js-toggle-button
...
.pull-right
-
if
c
i_commit
=
render_ci_status
(
c
i_c
ommit
)
-
if
c
ommit
.
status
=
render_ci_status
(
commit
)
=
clipboard_button
(
clipboard_text:
commit
.
id
)
=
link_to
commit
.
short_id
,
namespace_project_commit_path
(
project
.
namespace
,
project
,
commit
),
class:
"commit_short_id"
...
...
app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
View file @
3b39214f
...
...
@@ -15,12 +15,13 @@
-
if
defined?
(
commit_sha
)
&&
commit_sha
%td
=
link_to
generic_commit_status
.
short_sha
,
namespace_project_commit_path
(
generic_commit_status
.
project
.
namespace
,
generic_commit_status
.
project
,
generic_commit_status
.
sha
),
class:
"monospace"
%td
-
if
generic_commit_status
.
ref
=
link_to
generic_commit_status
.
ref
,
namespace_project_commits_path
(
generic_commit_status
.
project
.
namespace
,
generic_commit_status
.
project
,
generic_commit_status
.
ref
)
-
else
.light
none
-
if
defined?
(
ref
)
&&
ref
%td
-
if
generic_commit_status
.
ref
=
link_to
generic_commit_status
.
ref
,
namespace_project_commits_path
(
generic_commit_status
.
project
.
namespace
,
generic_commit_status
.
project
,
generic_commit_status
.
ref
)
-
else
.light
none
-
if
defined?
(
runner
)
&&
runner
%td
...
...
app/views/projects/issues/_related_branches.html.haml
View file @
3b39214f
...
...
@@ -5,7 +5,7 @@
-
@related_branches
.
each
do
|
branch
|
%li
-
sha
=
@project
.
repository
.
find_branch
(
branch
).
target
-
ci_commit
=
@project
.
ci_commit
(
sha
)
if
sha
-
ci_commit
=
@project
.
ci_commit
(
sha
,
branch
)
if
sha
-
if
ci_commit
%span
.related-branch-ci-status
=
render_ci_status
(
ci_commit
)
...
...
app/views/projects/merge_requests/invalid.html.haml
View file @
3b39214f
-
page_title
"
#{
@merge_request
.
title
}
(
#{
merge_request
.
to_reference
}
"
,
"Merge Requests"
-
page_title
"
#{
@merge_request
.
title
}
(
#{
@
merge_request
.
to_reference
}
"
,
"Merge Requests"
=
render
"header_title"
.merge-request
...
...
app/views/projects/merge_requests/show/_builds.html.haml
View file @
3b39214f
=
render
"projects/commit/builds"
,
link_to_commit:
true
=
render
"projects/commit/ci_commit"
,
ci_commit:
@ci_commit
,
link_to_commit:
true
app/views/shared/projects/_project.html.haml
View file @
3b39214f
...
...
@@ -6,9 +6,8 @@
-
css_class
=
''
unless
local_assigns
[
:css_class
]
-
show_last_commit_as_description
=
false
unless
local_assigns
[
:show_last_commit_as_description
]
==
true
&&
project
.
commit
-
css_class
+=
" no-description"
if
project
.
description
.
blank?
&&
!
show_last_commit_as_description
-
ci_commit
=
project
.
ci_commit
(
project
.
commit
.
sha
)
if
ci
&&
!
project
.
empty_repo?
&&
project
.
commit
-
cache_key
=
[
project
.
namespace
,
project
,
controller
.
controller_name
,
controller
.
action_name
,
current_application_settings
,
'v2.3'
]
-
cache_key
.
push
(
ci_commit
.
status
)
if
ci_commit
-
cache_key
.
push
(
project
.
commit
.
status
)
if
project
.
commit
.
try
(
:status
)
%li
.project-row
{
class:
css_class
}
=
cache
(
cache_key
)
do
...
...
@@ -16,9 +15,9 @@
-
if
project
.
main_language
%span
=
project
.
main_language
-
if
ci_commit
-
if
project
.
commit
.
try
(
:status
)
%span
=
render_ci_status
(
ci_
commit
)
=
render_ci_status
(
project
.
commit
)
-
if
forks
%span
=
icon
(
'code-fork'
)
...
...
db/fixtures/development/14_builds.rb
View file @
3b39214f
...
...
@@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds
commits
=
@project
.
repository
.
commits
(
'master'
,
nil
,
5
)
commits_sha
=
commits
.
map
{
|
commit
|
commit
.
raw
.
id
}
commits_sha
.
map
do
|
sha
|
@project
.
ensure_ci_commit
(
sha
)
@project
.
ensure_ci_commit
(
sha
,
'master'
)
end
rescue
[]
...
...
db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
View file @
3b39214f
...
...
@@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
def
up
return
unless
Gitlab
::
Database
.
postgresql?
create_trigrams_extension
unless
trigrams_enabled?
raise
'You must enable the pg_trgm extension. You can do so by running '
\
'"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be '
\
...
...
@@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
row
&&
row
[
'enabled'
]
==
't'
?
true
:
false
end
def
create_trigrams_extension
# This may not work if the user doesn't have permission. We attempt in
# case we do have permission, particularly for test/dev environments.
begin
enable_extension
'pg_trgm'
rescue
end
end
def
to_index
{
ci_runners:
[
:token
,
:description
],
...
...
db/migrate/20160412173416_add_fields_to_ci_commit.rb
0 → 100644
View file @
3b39214f
class
AddFieldsToCiCommit
<
ActiveRecord
::
Migration
def
change
add_column
:ci_commits
,
:status
,
:string
add_column
:ci_commits
,
:started_at
,
:timestamp
add_column
:ci_commits
,
:finished_at
,
:timestamp
add_column
:ci_commits
,
:duration
,
:integer
end
end
db/migrate/20160412173417_update_ci_commit.rb
0 → 100644
View file @
3b39214f
class
UpdateCiCommit
<
ActiveRecord
::
Migration
# This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers
# Otherwise Offline migration should be used.
def
change
execute
(
"UPDATE ci_commits SET status=
#{
status
}
, ref=
#{
ref
}
, tag=
#{
tag
}
WHERE status IS NULL"
)
end
private
def
status
builds
=
'(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)'
success
=
"(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')"
ignored
=
"(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)"
pending
=
"(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')"
running
=
"(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')"
canceled
=
"(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')"
"(CASE
WHEN
#{
builds
}
=0 THEN 'skipped'
WHEN
#{
builds
}
=
#{
success
}
+
#{
ignored
}
THEN 'success'
WHEN
#{
builds
}
=
#{
pending
}
THEN 'pending'
WHEN
#{
builds
}
=
#{
canceled
}
THEN 'canceled'
WHEN
#{
running
}
+
#{
pending
}
>0 THEN 'running'
ELSE 'failed'
END)"
end
def
ref
'(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
end
def
tag
'(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
end
end
db/migrate/20160412173418_add_ci_commit_indexes.rb
0 → 100644
View file @
3b39214f
class
AddCiCommitIndexes
<
ActiveRecord
::
Migration
disable_ddl_transaction!
def
change
add_index
:ci_commits
,
[
:gl_project_id
,
:sha
],
index_options
add_index
:ci_commits
,
[
:gl_project_id
,
:status
],
index_options
add_index
:ci_commits
,
[
:status
],
index_options
end
private
def
index_options
if
Gitlab
::
Database
.
postgresql?
{
algorithm: :concurrently
}
else
{
}
end
end
end
db/schema.rb
View file @
3b39214f
...
...
@@ -171,14 +171,21 @@ ActiveRecord::Schema.define(version: 20160419120017) do
t
.
text
"yaml_errors"
t
.
datetime
"committed_at"
t
.
integer
"gl_project_id"
t
.
string
"status"
t
.
datetime
"started_at"
t
.
datetime
"finished_at"
t
.
integer
"duration"
end
add_index
"ci_commits"
,
[
"gl_project_id"
,
"sha"
],
name:
"index_ci_commits_on_gl_project_id_and_sha"
,
using: :btree
add_index
"ci_commits"
,
[
"gl_project_id"
,
"status"
],
name:
"index_ci_commits_on_gl_project_id_and_status"
,
using: :btree
add_index
"ci_commits"
,
[
"gl_project_id"
],
name:
"index_ci_commits_on_gl_project_id"
,
using: :btree
add_index
"ci_commits"
,
[
"project_id"
,
"committed_at"
,
"id"
],
name:
"index_ci_commits_on_project_id_and_committed_at_and_id"
,
using: :btree
add_index
"ci_commits"
,
[
"project_id"
,
"committed_at"
],
name:
"index_ci_commits_on_project_id_and_committed_at"
,
using: :btree
add_index
"ci_commits"
,
[
"project_id"
,
"sha"
],
name:
"index_ci_commits_on_project_id_and_sha"
,
using: :btree
add_index
"ci_commits"
,
[
"project_id"
],
name:
"index_ci_commits_on_project_id"
,
using: :btree
add_index
"ci_commits"
,
[
"sha"
],
name:
"index_ci_commits_on_sha"
,
using: :btree
add_index
"ci_commits"
,
[
"status"
],
name:
"index_ci_commits_on_status"
,
using: :btree
create_table
"ci_events"
,
force: :cascade
do
|
t
|
t
.
integer
"project_id"
...
...
features/steps/profile/active_tab.rb
View file @
3b39214f
...
...
@@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
step
'the active main tab should be Audit Log'
do
ensure_active_main_tab
(
'Audit Log'
)
end
def
ensure_active_main_tab
(
content
)
expect
(
find
(
'.layout-nav li.active'
)).
to
have_content
(
content
)
end
end
features/steps/project/merge_requests.rb
View file @
3b39214f
...
...
@@ -519,7 +519,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step
'"Bug NS-05" has CI status'
do
project
=
merge_request
.
source_project
project
.
enable_ci
ci_commit
=
create
:ci_commit
,
project:
project
,
sha:
merge_request
.
last_commit
.
id
ci_commit
=
create
:ci_commit
,
project:
project
,
sha:
merge_request
.
last_commit
.
id
,
ref:
merge_request
.
source_branch
create
:ci_build
,
commit:
ci_commit
end
...
...
features/steps/shared/builds.rb
View file @
3b39214f
...
...
@@ -10,16 +10,16 @@ module SharedBuilds
end
step
'project has a recent build'
do
@ci_commit
=
create
(
:ci_commit
,
project:
@project
,
sha:
@project
.
commit
.
sha
)
@ci_commit
=
create
(
:ci_commit
,
project:
@project
,
sha:
@project
.
commit
.
sha
,
ref:
'master'
)
@build
=
create
(
:ci_build_with_coverage
,
commit:
@ci_commit
)
end
step
'recent build is successful'
do
@build
.
update
_column
(
:status
,
'success'
)
@build
.
update
(
status:
'success'
)
end
step
'recent build failed'
do
@build
.
update
_column
(
:status
,
'failed'
)
@build
.
update
(
status:
'failed'
)
end
step
'project has another build that is running'
do
...
...
features/steps/shared/project.rb
View file @
3b39214f
...
...
@@ -234,7 +234,7 @@ module SharedProject
step
'project "Shop" has CI build'
do
project
=
Project
.
find_by
(
name:
"Shop"
)
create
:ci_commit
,
project:
project
,
sha:
project
.
commit
.
sha
create
:ci_commit
,
project:
project
,
sha:
project
.
commit
.
sha
,
ref:
'master'
,
status:
'skipped'
end
step
'I should see last commit with CI status'
do
...
...
lib/api/commit_statuses.rb
View file @
3b39214f
...
...
@@ -21,10 +21,9 @@ module API
authorize!
(
:read_commit_status
,
user_project
)
not_found!
(
'Commit'
)
unless
user_project
.
commit
(
params
[
:sha
])
ci_commit
=
user_project
.
ci_commit
(
params
[
:sha
])
return
[]
unless
ci_commit
statuses
=
ci_commit
.
statuses
ci_commits
=
user_project
.
ci_commits
.
where
(
sha:
params
[
:sha
])
statuses
=
::
CommitStatus
.
where
(
commit:
ci_commits
)
statuses
=
statuses
.
latest
unless
parse_boolean
(
params
[
:all
])
statuses
=
statuses
.
where
(
ref:
params
[
:ref
])
if
params
[
:ref
].
present?
statuses
=
statuses
.
where
(
stage:
params
[
:stage
])
if
params
[
:stage
].
present?
...
...
@@ -51,7 +50,21 @@ module API
commit
=
@project
.
commit
(
params
[
:sha
])
not_found!
'Commit'
unless
commit
ci_commit
=
@project
.
ensure_ci_commit
(
commit
.
sha
)
# Since the CommitStatus is attached to Ci::Commit (in the future Pipeline)
# We need to always have the pipeline object
# To have a valid pipeline object that can be attached to specific MR
# Other CI service needs to send `ref`
# If we don't receive it, we will attach the CommitStatus to
# the first found branch on that commit
ref
=
params
[
:ref
]
unless
ref
branches
=
@project
.
repository
.
branch_names_contains
(
commit
.
sha
)
not_found!
'References for commit'
if
branches
.
none?
ref
=
branches
.
first
end
ci_commit
=
@project
.
ensure_ci_commit
(
commit
.
sha
,
ref
)
name
=
params
[
:name
]
||
params
[
:context
]
status
=
GenericCommitStatus
.
running_or_pending
.
find_by
(
commit:
ci_commit
,
name:
name
,
ref:
params
[
:ref
])
...
...
lib/banzai/filter/label_reference_filter.rb
View file @
3b39214f
...
...
@@ -18,9 +18,7 @@ module Banzai
def
references_in
(
text
,
pattern
=
Label
.
reference_pattern
)
text
.
gsub
(
pattern
)
do
|
match
|
project
=
project_from_ref
(
$~
[
:project
])
params
=
label_params
(
$~
[
:label_id
].
to_i
,
$~
[
:label_name
])
label
=
project
.
labels
.
find_by
(
params
)
label
=
find_label
(
$~
[
:project
],
$~
[
:label_id
],
$~
[
:label_name
])
if
label
yield
match
,
label
.
id
,
$~
[
:project
],
$~
...
...
@@ -30,18 +28,12 @@ module Banzai
end
end
def
url_for_object
(
label
,
project
)
h
=
Gitlab
::
Routing
.
url_helpers
h
.
namespace_project_issues_url
(
project
.
namespace
,
project
,
label_name:
label
.
name
,
only_path:
context
[
:only_path
])
end
def
find_label
(
project_ref
,
label_id
,
label_name
)
project
=
project_from_ref
(
project_ref
)
return
unless
project
def
object_link_text
(
object
,
matches
)
if
context
[
:project
]
==
object
.
project
LabelsHelper
.
render_colored_label
(
object
)
else
LabelsHelper
.
render_colored_cross_project_label
(
object
)
end
label_params
=
label_params
(
label_id
,
label_name
)
project
.
labels
.
find_by
(
label_params
)
end
# Parameters to pass to `Label.find_by` based on the given arguments
...
...
@@ -55,7 +47,21 @@ module Banzai
if
name
{
name:
name
.
tr
(
'"'
,
''
)
}
else
{
id:
id
}
{
id:
id
.
to_i
}
end
end
def
url_for_object
(
label
,
project
)
h
=
Gitlab
::
Routing
.
url_helpers
h
.
namespace_project_issues_url
(
project
.
namespace
,
project
,
label_name:
label
.
name
,
only_path:
context
[
:only_path
])
end
def
object_link_text
(
object
,
matches
)
if
context
[
:project
]
==
object
.
project
LabelsHelper
.
render_colored_label
(
object
)
else
LabelsHelper
.
render_colored_cross_project_label
(
object
)
end
end
end
...
...
lib/ci/status.rb
deleted
100644 → 0
View file @
d4f6d398
module
Ci
class
Status
def
self
.
get_status
(
statuses
)
if
statuses
.
none?
'skipped'
elsif
statuses
.
all?
{
|
status
|
status
.
success?
||
status
.
ignored?
}
'success'
elsif
statuses
.
all?
(
&
:pending?
)
'pending'
elsif
statuses
.
any?
(
&
:running?
)
||
statuses
.
any?
(
&
:pending?
)
'running'
elsif
statuses
.
all?
(
&
:canceled?
)
'canceled'
else
'failed'
end
end
end
end
lib/gitlab/gon_helper.rb
View file @
3b39214f
...
...
@@ -8,7 +8,6 @@ module Gitlab
gon
.
relative_url_root
=
Gitlab
.
config
.
gitlab
.
relative_url_root
gon
.
shortcuts_path
=
help_shortcuts_path
gon
.
user_color_scheme
=
Gitlab
::
ColorSchemes
.
for_user
(
current_user
).
css_class
gon
.
sentry_dsn
=
ApplicationSetting
.
current
.
sentry_dsn
if
Rails
.
env
.
production?
if
current_user
gon
.
current_user_id
=
current_user
.
id
...
...
lib/gitlab/sidekiq_middleware/memory_killer.rb
View file @
3b39214f
...
...
@@ -29,8 +29,8 @@ module Gitlab
"in
#{
GRACE_TIME
}
seconds"
sleep
(
GRACE_TIME
)
Sidekiq
.
logger
.
warn
"sending SIG
USR1
to PID
#{
Process
.
pid
}
"
Process
.
kill
(
'SIG
USR1
'
,
Process
.
pid
)
Sidekiq
.
logger
.
warn
"sending SIG
TERM
to PID
#{
Process
.
pid
}
"
Process
.
kill
(
'SIG
TERM
'
,
Process
.
pid
)
Sidekiq
.
logger
.
warn
"waiting
#{
SHUTDOWN_WAIT
}
seconds before sending "
\
"
#{
SHUTDOWN_SIGNAL
}
to PID
#{
Process
.
pid
}
"
...
...
spec/controllers/projects/project_members_controller_spec.rb
View file @
3b39214f
...
...
@@ -46,4 +46,20 @@ describe Projects::ProjectMembersController do
end
end
end
describe
'#index'
do
let
(
:project
)
{
create
(
:project
,
:private
)
}
context
'when user is member'
do
let
(
:member
)
{
create
(
:user
)
}
before
do
project
.
team
<<
[
member
,
:guest
]
sign_in
(
member
)
get
:index
,
namespace_id:
project
.
namespace
.
to_param
,
project_id:
project
.
to_param
end
it
{
expect
(
response
.
status
).
to
eq
(
200
)
}
end
end
end
spec/features/issues/filter_by_labels_spec.rb
View file @
3b39214f
require
'rails_helper'
feature
'Issue filtering by Labels'
,
feature:
true
do
include
WaitForAjax
let
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:user
)
{
create
(
:user
)}
let!
(
:label
)
{
create
(
:label
,
project:
project
)
}
before
do
[
'bug'
,
'feature'
,
'enhancement'
].
each
do
|
title
|
create
(
:label
,
project:
project
,
title:
title
)
end
bug
=
create
(
:label
,
project:
project
,
title:
'bug'
)
feature
=
create
(
:label
,
project:
project
,
title:
'feature'
)
enhancement
=
create
(
:label
,
project:
project
,
title:
'enhancement'
)
issue1
=
create
(
:issue
,
title:
"Bugfix1"
,
project:
project
)
issue1
.
labels
<<
project
.
labels
.
find_by
(
title:
'bug'
)
issue1
.
labels
<<
bug
issue2
=
create
(
:issue
,
title:
"Bugfix2"
,
project:
project
)
issue2
.
labels
<<
project
.
labels
.
find_by
(
title:
'bug'
)
issue2
.
labels
<<
project
.
labels
.
find_by
(
title:
'enhancement'
)
issue2
.
labels
<<
bug
issue2
.
labels
<<
enhancement
issue3
=
create
(
:issue
,
title:
"Feature1"
,
project:
project
)
issue3
.
labels
<<
project
.
labels
.
find_by
(
title:
'feature'
)
issue3
.
labels
<<
feature
project
.
team
<<
[
user
,
:master
]
login_as
(
user
)
...
...
@@ -31,10 +31,10 @@ feature 'Issue filtering by Labels', feature: true do
context
'filter by label bug'
,
js:
true
do
before
do
page
.
find
(
'.js-label-select'
).
click
sleep
0.5
wait_for_ajax
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
bug
\"
) a').click()"
)
page
.
first
(
'.labels-filter .dropdown-title .dropdown-menu-close-icon'
).
click
sleep
2
wait_for_ajax
end
it
'should show issue "Bugfix1" and "Bugfix2" in issues list'
do
...
...
@@ -59,10 +59,10 @@ feature 'Issue filtering by Labels', feature: true do
context
'filter by label feature'
,
js:
true
do
before
do
page
.
find
(
'.js-label-select'
).
click
sleep
0.5
wait_for_ajax
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
feature
\"
) a').click()"
)
page
.
first
(
'.labels-filter .dropdown-title .dropdown-menu-close-icon'
).
click
sleep
2
wait_for_ajax
end
it
'should show issue "Feature1" in issues list'
do
...
...
@@ -87,10 +87,10 @@ feature 'Issue filtering by Labels', feature: true do
context
'filter by label enhancement'
,
js:
true
do
before
do
page
.
find
(
'.js-label-select'
).
click
sleep
0.5
wait_for_ajax
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
enhancement
\"
) a').click()"
)
page
.
first
(
'.labels-filter .dropdown-title .dropdown-menu-close-icon'
).
click
sleep
2
wait_for_ajax
end
it
'should show issue "Bugfix2" in issues list'
do
...
...
@@ -115,20 +115,16 @@ feature 'Issue filtering by Labels', feature: true do
context
'filter by label enhancement or feature'
,
js:
true
do
before
do
page
.
find
(
'.js-label-select'
).
click
sleep
0.5
wait_for_ajax
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
enhancement
\"
) a').click()"
)
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
feature
\"
) a').click()"
)
page
.
first
(
'.labels-filter .dropdown-title .dropdown-menu-close-icon'
).
click
sleep
2
wait_for_ajax
end
it
'should show issue "Bugfix2" or "Feature1" in issues list'
do
expect
(
page
).
to
have_content
"Bugfix2"
expect
(
page
).
to
have_content
"Feature1"
end
it
'should not show "Bugfix1" in issues list'
do
it
'should not show "Bugfix1" or "Feature1" in issues list'
do
expect
(
page
).
not_to
have_content
"Bugfix1"
expect
(
page
).
not_to
have_content
"Feature1"
end
it
'should show label "enhancement" and "feature" in filtered-labels'
do
...
...
@@ -141,19 +137,18 @@ feature 'Issue filtering by Labels', feature: true do
end
end
context
'filter by label enhancement
or
bug in issues list'
,
js:
true
do
context
'filter by label enhancement
and
bug in issues list'
,
js:
true
do
before
do
page
.
find
(
'.js-label-select'
).
click
sleep
0.5
wait_for_ajax
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
enhancement
\"
) a').click()"
)
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
bug
\"
) a').click()"
)
page
.
first
(
'.labels-filter .dropdown-title .dropdown-menu-close-icon'
).
click
sleep
2
wait_for_ajax
end
it
'should show issue "Bugfix2"
or "Bugfix1"
in issues list'
do
it
'should show issue "Bugfix2" in issues list'
do
expect
(
page
).
to
have_content
"Bugfix2"
expect
(
page
).
to
have_content
"Bugfix1"
end
it
'should not show "Feature1"'
do
...
...
spec/features/security/project/internal_access_spec.rb
View file @
3b39214f
...
...
@@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do
it
{
is_expected
.
to
be_allowed_for
:admin
}
it
{
is_expected
.
to
be_allowed_for
owner
}
it
{
is_expected
.
to
be_allowed_for
master
}
it
{
is_expected
.
to
be_denied_for
developer
}
it
{
is_expected
.
to
be_denied_for
reporter
}
it
{
is_expected
.
to
be_denied_for
guest
}
it
{
is_expected
.
to
be_denied_for
:user
}
it
{
is_expected
.
to
be_denied_for
:external
}
it
{
is_expected
.
to
be_allowed_for
developer
}
it
{
is_expected
.
to
be_allowed_for
reporter
}
it
{
is_expected
.
to
be_allowed_for
guest
}
it
{
is_expected
.
to
be_allowed_for
:user
}
it
{
is_expected
.
to
be_denied_for
:visitor
}
it
{
is_expected
.
to
be_denied_for
:external
}
end
describe
"GET /:project_path/blob"
do
...
...
spec/features/security/project/private_access_spec.rb
View file @
3b39214f
...
...
@@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do
it
{
is_expected
.
to
be_allowed_for
:admin
}
it
{
is_expected
.
to
be_allowed_for
owner
}
it
{
is_expected
.
to
be_allowed_for
master
}
it
{
is_expected
.
to
be_
deni
ed_for
developer
}
it
{
is_expected
.
to
be_
deni
ed_for
reporter
}
it
{
is_expected
.
to
be_
deni
ed_for
guest
}
it
{
is_expected
.
to
be_
allow
ed_for
developer
}
it
{
is_expected
.
to
be_
allow
ed_for
reporter
}
it
{
is_expected
.
to
be_
allow
ed_for
guest
}
it
{
is_expected
.
to
be_denied_for
:user
}
it
{
is_expected
.
to
be_denied_for
:external
}
it
{
is_expected
.
to
be_denied_for
:visitor
}
...
...
spec/features/security/project/public_access_spec.rb
View file @
3b39214f
...
...
@@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do
it
{
is_expected
.
to
be_allowed_for
:admin
}
it
{
is_expected
.
to
be_allowed_for
owner
}
it
{
is_expected
.
to
be_allowed_for
master
}
it
{
is_expected
.
to
be_
deni
ed_for
developer
}
it
{
is_expected
.
to
be_
deni
ed_for
reporter
}
it
{
is_expected
.
to
be_
deni
ed_for
guest
}
it
{
is_expected
.
to
be_
deni
ed_for
:user
}
it
{
is_expected
.
to
be_
denied_for
:external
}
it
{
is_expected
.
to
be_
denied_for
:visitor
}
it
{
is_expected
.
to
be_
allow
ed_for
developer
}
it
{
is_expected
.
to
be_
allow
ed_for
reporter
}
it
{
is_expected
.
to
be_
allow
ed_for
guest
}
it
{
is_expected
.
to
be_
allow
ed_for
:user
}
it
{
is_expected
.
to
be_
allowed_for
:visitor
}
it
{
is_expected
.
to
be_
allowed_for
:external
}
end
describe
"GET /:project_path/builds"
do
...
...
spec/features/todos/todos_spec.rb
0 → 100644
View file @
3b39214f
require
'spec_helper'
describe
'Dashboard Todos'
,
feature:
true
do
let
(
:user
){
create
(
:user
)
}
let
(
:author
){
create
(
:user
)
}
let
(
:project
){
create
(
:project
)
}
let
(
:issue
){
create
(
:issue
)
}
let
(
:todos_per_page
){
Todo
.
default_per_page
}
let
(
:todos_total
){
todos_per_page
+
1
}
describe
'GET /dashboard/todos'
do
context
'User does not have todos'
do
before
do
login_as
(
user
)
visit
dashboard_todos_path
end
it
'shows "All done" message'
do
expect
(
page
).
to
have_content
"You're all done!"
end
end
context
'User has a todo'
,
js:
true
do
before
do
create
(
:todo
,
:mentioned
,
user:
user
,
project:
project
,
target:
issue
,
author:
author
)
login_as
(
user
)
visit
dashboard_todos_path
end
it
'todo is present'
do
expect
(
page
).
to
have_selector
(
'.todos-list .todo'
,
count:
1
)
end
describe
'deleting the todo'
do
before
do
first
(
'.done-todo'
).
click
end
it
'is removed from the list'
do
expect
(
page
).
not_to
have_selector
(
'.todos-list .todo'
)
end
it
'shows "All done" message'
do
expect
(
page
).
to
have_content
(
"You're all done!"
)
end
end
end
context
'User has multiple pages of Todos'
do
let
(
:todo_total_pages
){
(
todos_total
.
to_f
/
todos_per_page
).
ceil
}
before
do
todos_total
.
times
do
create
(
:todo
,
:mentioned
,
user:
user
,
project:
project
,
target:
issue
,
author:
author
)
end
login_as
(
user
)
visit
dashboard_todos_path
end
it
'is paginated'
do
expect
(
page
).
to
have_selector
(
'.gl-pagination'
)
end
it
'is has the right number of pages'
do
expect
(
page
).
to
have_selector
(
'.gl-pagination .page'
,
count:
todo_total_pages
)
end
describe
'deleting last todo from last page'
,
js:
true
do
it
'redirects to the previous page'
do
page
.
within
(
'.gl-pagination'
)
do
click_link
todo_total_pages
.
to_s
end
first
(
'.done-todo'
).
click
expect
(
page
).
to
have_content
(
Todo
.
last
.
body
)
end
end
end
end
end
spec/helpers/ci_status_helper_spec.rb
View file @
3b39214f
...
...
@@ -6,8 +6,8 @@ describe CiStatusHelper do
let
(
:success_commit
)
{
double
(
"Ci::Commit"
,
status:
'success'
)
}
let
(
:failed_commit
)
{
double
(
"Ci::Commit"
,
status:
'failed'
)
}
describe
'ci_
status_icon
'
do
it
{
expect
(
helper
.
ci_
status_icon
(
success_commit
)).
to
include
(
'fa-check'
)
}
it
{
expect
(
helper
.
ci_
status_icon
(
failed_commit
)).
to
include
(
'fa-close'
)
}
describe
'ci_
icon_for_status
'
do
it
{
expect
(
helper
.
ci_
icon_for_status
(
success_commit
.
status
)).
to
include
(
'fa-check'
)
}
it
{
expect
(
helper
.
ci_
icon_for_status
(
failed_commit
.
status
)).
to
include
(
'fa-close'
)
}
end
end
spec/lib/banzai/filter/label_reference_filter_spec.rb
View file @
3b39214f
...
...
@@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
describe
'cross project label references'
do
let
(
:another_project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:project_name
)
{
another_project
.
name_with_namespace
}
let
(
:label
)
{
create
(
:label
,
project:
another_project
,
color:
'#00ff00'
)
}
let
(
:reference
)
{
label
.
to_reference
(
project
)
}
context
'valid project referenced'
do
let
(
:another_project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:project_name
)
{
another_project
.
name_with_namespace
}
let
(
:label
)
{
create
(
:label
,
project:
another_project
,
color:
'#00ff00'
)
}
let
(
:reference
)
{
label
.
to_reference
(
project
)
}
let!
(
:result
)
{
reference_filter
(
"See
#{
reference
}
"
)
}
let!
(
:result
)
{
reference_filter
(
"See
#{
reference
}
"
)
}
it
'points to referenced project issues page'
do
expect
(
result
.
css
(
'a'
).
first
.
attr
(
'href'
))
.
to
eq
urls
.
namespace_project_issues_url
(
another_project
.
namespace
,
another_project
,
label_name:
label
.
name
)
end
it
'points to referenced project issues page'
do
expect
(
result
.
css
(
'a'
).
first
.
attr
(
'href'
))
.
to
eq
urls
.
namespace_project_issues_url
(
another_project
.
namespace
,
another_project
,
label_name:
label
.
name
)
end
it
'has valid color'
do
expect
(
result
.
css
(
'a span'
).
first
.
attr
(
'style'
))
.
to
match
/background-color: #00ff00/
end
it
'has valid color
'
do
expect
(
result
.
css
(
'a span'
).
first
.
attr
(
'style'
))
.
to
match
/background-color: #00ff00/
it
'contains cross project content
'
do
expect
(
result
.
css
(
'a'
).
first
.
text
).
to
eq
"
#{
label
.
name
}
in
#{
project_name
}
"
end
end
it
'contains cross project content'
do
expect
(
result
.
css
(
'a'
).
first
.
text
).
to
eq
"
#{
label
.
name
}
in
#{
project_name
}
"
context
'project that does not exist referenced'
do
let
(
:result
)
{
reference_filter
(
'aaa/bbb~ccc'
)
}
it
'does not link reference'
do
expect
(
result
.
to_html
).
to
eq
'aaa/bbb~ccc'
end
end
end
end
spec/lib/gitlab/badge/build_spec.rb
View file @
3b39214f
...
...
@@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do
end
context
'build exists'
do
let
(
:ci_commit
)
{
create
(
:ci_commit
,
project:
project
,
sha:
sha
)
}
let
(
:ci_commit
)
{
create
(
:ci_commit
,
project:
project
,
sha:
sha
,
ref:
branch
)
}
let!
(
:build
)
{
create
(
:ci_build
,
commit:
ci_commit
)
}
...
...
@@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do
describe
'#data'
do
let
(
:data
)
{
badge
.
data
}
it
'contains inf
ro
mation about success'
do
it
'contains inf
or
mation about success'
do
expect
(
status_node
(
data
,
'success'
)).
to
be_truthy
end
end
...
...
@@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do
describe
'#data'
do
let
(
:data
)
{
badge
.
data
}
it
'contains inf
ro
mation about failure'
do
it
'contains inf
or
mation about failure'
do
expect
(
status_node
(
data
,
'failed'
)).
to
be_truthy
end
end
...
...
spec/models/ci/commit_spec.rb
View file @
3b39214f
...
...
@@ -27,6 +27,8 @@ describe Ci::Commit, models: true do
it
{
is_expected
.
to
have_many
(
:trigger_requests
)
}
it
{
is_expected
.
to
have_many
(
:builds
)
}
it
{
is_expected
.
to
validate_presence_of
:sha
}
it
{
is_expected
.
to
validate_presence_of
:status
}
it
{
is_expected
.
to
delegate_method
(
:stages
).
to
(
:statuses
)
}
it
{
is_expected
.
to
respond_to
:git_author_name
}
it
{
is_expected
.
to
respond_to
:git_author_email
}
...
...
@@ -52,57 +54,9 @@ describe Ci::Commit, models: true do
it
{
expect
(
commit
.
sha
).
to
start_with
(
subject
)
}
end
describe
:stage
do
subject
{
commit
.
stage
}
before
do
@second
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'deploy'
,
stage:
'deploy'
,
stage_idx:
1
,
status:
'pending'
@first
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'test'
,
stage:
'test'
,
stage_idx:
0
,
status:
'pending'
end
it
'returns first running stage'
do
is_expected
.
to
eq
(
'test'
)
end
context
'first build succeeded'
do
before
do
@first
.
success
end
it
'returns last running stage'
do
is_expected
.
to
eq
(
'deploy'
)
end
end
context
'all builds succeeded'
do
before
do
@first
.
success
@second
.
success
end
it
'returns nil'
do
is_expected
.
to
be_nil
end
end
end
describe
:create_next_builds
do
end
describe
:refs
do
subject
{
commit
.
refs
}
before
do
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'deploy'
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'deploy'
,
ref:
'develop'
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'deploy'
,
ref:
'master'
end
it
'returns all refs'
do
is_expected
.
to
contain_exactly
(
'master'
,
'develop'
,
nil
)
end
end
describe
:retried
do
subject
{
commit
.
retried
}
...
...
@@ -117,10 +71,10 @@ describe Ci::Commit, models: true do
end
describe
:create_builds
do
let!
(
:commit
)
{
FactoryGirl
.
create
:ci_commit
,
project:
project
}
let!
(
:commit
)
{
FactoryGirl
.
create
:ci_commit
,
project:
project
,
ref:
'master'
,
tag:
false
}
def
create_builds
(
trigger_request
=
nil
)
commit
.
create_builds
(
'master'
,
false
,
nil
,
trigger_request
)
commit
.
create_builds
(
nil
,
trigger_request
)
end
def
create_next_builds
...
...
@@ -143,67 +97,6 @@ describe Ci::Commit, models: true do
expect
(
create_next_builds
).
to
be_falsey
end
context
'for different ref'
do
def
create_develop_builds
commit
.
create_builds
(
'develop'
,
false
,
nil
,
nil
)
end
it
'creates builds'
do
expect
(
create_builds
).
to
be_truthy
commit
.
builds
.
update_all
(
status:
"success"
)
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
2
)
expect
(
create_develop_builds
).
to
be_truthy
commit
.
builds
.
update_all
(
status:
"success"
)
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
4
)
expect
(
commit
.
refs
.
size
).
to
eq
(
2
)
expect
(
commit
.
builds
.
pluck
(
:name
).
uniq
.
size
).
to
eq
(
2
)
end
end
context
'for build triggers'
do
let
(
:trigger
)
{
FactoryGirl
.
create
:ci_trigger
,
project:
project
}
let
(
:trigger_request
)
{
FactoryGirl
.
create
:ci_trigger_request
,
commit:
commit
,
trigger:
trigger
}
it
'creates builds'
do
expect
(
create_builds
(
trigger_request
)).
to
be_truthy
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
2
)
end
it
'rebuilds commit'
do
expect
(
create_builds
).
to
be_truthy
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
2
)
expect
(
create_builds
(
trigger_request
)).
to
be_truthy
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
4
)
end
it
'creates next builds'
do
expect
(
create_builds
(
trigger_request
)).
to
be_truthy
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
2
)
commit
.
builds
.
update_all
(
status:
"success"
)
expect
(
create_next_builds
).
to
be_truthy
expect
(
commit
.
builds
.
count
(
:all
)).
to
eq
(
4
)
end
context
'for [ci skip]'
do
before
do
allow
(
commit
).
to
receive
(
:git_commit_message
)
{
'message [ci skip]'
}
end
it
'rebuilds commit'
do
expect
(
commit
.
status
).
to
eq
(
'skipped'
)
expect
(
create_builds
).
to
be_truthy
# since everything in Ci::Commit is cached we need to fetch a new object
new_commit
=
Ci
::
Commit
.
find_by_id
(
commit
.
id
)
expect
(
new_commit
.
status
).
to
eq
(
'pending'
)
end
end
end
context
'custom stage with first job allowed to fail'
do
let
(
:yaml
)
do
{
...
...
@@ -284,6 +177,7 @@ describe Ci::Commit, models: true do
commit
.
builds
.
running_or_pending
.
each
(
&
:success
)
expect
(
commit
.
builds
.
pluck
(
:status
)).
to
contain_exactly
(
'success'
,
'success'
,
'success'
,
'success'
)
commit
.
reload
expect
(
commit
.
status
).
to
eq
(
'success'
)
end
...
...
@@ -306,6 +200,7 @@ describe Ci::Commit, models: true do
commit
.
builds
.
running_or_pending
.
each
(
&
:success
)
expect
(
commit
.
builds
.
pluck
(
:status
)).
to
contain_exactly
(
'success'
,
'failed'
,
'success'
,
'success'
)
commit
.
reload
expect
(
commit
.
status
).
to
eq
(
'failed'
)
end
...
...
@@ -329,6 +224,7 @@ describe Ci::Commit, models: true do
expect
(
commit
.
builds
.
pluck
(
:name
)).
to
contain_exactly
(
'build'
,
'test'
,
'test_failure'
,
'cleanup'
)
expect
(
commit
.
builds
.
pluck
(
:status
)).
to
contain_exactly
(
'success'
,
'failed'
,
'failed'
,
'success'
)
commit
.
reload
expect
(
commit
.
status
).
to
eq
(
'failed'
)
end
...
...
@@ -351,6 +247,7 @@ describe Ci::Commit, models: true do
commit
.
builds
.
running_or_pending
.
each
(
&
:success
)
expect
(
commit
.
builds
.
pluck
(
:status
)).
to
contain_exactly
(
'success'
,
'success'
,
'failed'
,
'success'
)
commit
.
reload
expect
(
commit
.
status
).
to
eq
(
'failed'
)
end
end
...
...
@@ -402,4 +299,98 @@ describe Ci::Commit, models: true do
expect
(
commit
.
coverage
).
to
be_nil
end
end
describe
'#retryable?'
do
subject
{
commit
.
retryable?
}
context
'no failed builds'
do
before
do
FactoryGirl
.
create
:ci_build
,
name:
"rspec"
,
commit:
commit
,
status:
'success'
end
it
'be not retryable'
do
is_expected
.
to
be_falsey
end
end
context
'with failed builds'
do
before
do
FactoryGirl
.
create
:ci_build
,
name:
"rspec"
,
commit:
commit
,
status:
'running'
FactoryGirl
.
create
:ci_build
,
name:
"rubocop"
,
commit:
commit
,
status:
'failed'
end
it
'be retryable'
do
is_expected
.
to
be_truthy
end
end
end
describe
'#stages'
do
let
(
:commit2
)
{
FactoryGirl
.
create
:ci_commit
,
project:
project
}
subject
{
CommitStatus
.
where
(
commit:
[
commit
,
commit2
]).
stages
}
before
do
FactoryGirl
.
create
:ci_build
,
commit:
commit2
,
stage:
'test'
,
stage_idx:
1
FactoryGirl
.
create
:ci_build
,
commit:
commit
,
stage:
'build'
,
stage_idx:
0
end
it
'return all stages'
do
is_expected
.
to
eq
(
%w(build test)
)
end
end
describe
'#update_state'
do
it
'execute update_state after touching object'
do
expect
(
commit
).
to
receive
(
:update_state
).
and_return
(
true
)
commit
.
touch
end
context
'dependent objects'
do
let
(
:commit_status
)
{
build
:commit_status
,
commit:
commit
}
it
'execute update_state after saving dependent object'
do
expect
(
commit
).
to
receive
(
:update_state
).
and_return
(
true
)
commit_status
.
save
end
end
context
'update state'
do
let
(
:current
)
{
Time
.
now
.
change
(
usec:
0
)
}
let
(
:build
)
{
FactoryGirl
.
create
:ci_build
,
:success
,
commit:
commit
,
started_at:
current
-
120
,
finished_at:
current
-
60
}
before
do
build
end
[
:status
,
:started_at
,
:finished_at
,
:duration
].
each
do
|
param
|
it
"update
#{
param
}
"
do
expect
(
commit
.
send
(
param
)).
to
eq
(
build
.
send
(
param
))
end
end
end
end
describe
'#branch?'
do
subject
{
commit
.
branch?
}
context
'is not a tag'
do
before
do
commit
.
tag
=
false
end
it
'return true when tag is set to false'
do
is_expected
.
to
be_truthy
end
end
context
'is not a tag'
do
before
do
commit
.
tag
=
true
end
it
'return false when tag is set to true'
do
is_expected
.
to
be_falsey
end
end
end
end
spec/models/commit_spec.rb
View file @
3b39214f
...
...
@@ -163,4 +163,12 @@ eos
it
{
expect
(
commit
.
reverts_commit?
(
another_commit
)).
to
be_truthy
}
end
end
describe
'#ci_commits'
do
# TODO: kamil
end
describe
'#status'
do
# TODO: kamil
end
end
spec/models/commit_status_spec.rb
View file @
3b39214f
...
...
@@ -163,37 +163,73 @@ describe CommitStatus, models: true do
end
it
'return unique statuses'
do
is_expected
.
to
eq
([
@commit
2
,
@commit3
,
@commit
4
,
@commit5
])
is_expected
.
to
eq
([
@commit4
,
@commit5
])
end
end
describe
:
for_ref
do
subject
{
CommitStatus
.
for_ref
(
'bb'
)
.
order
(
:id
)
}
describe
:
running_or_pending
do
subject
{
CommitStatus
.
running_or_pending
.
order
(
:id
)
}
before
do
@commit1
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'aa'
,
ref:
'bb'
,
status:
'running'
@commit2
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'cc'
,
ref:
'cc'
,
status:
'pending'
@commit3
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'aa'
,
ref:
nil
,
status:
'success'
@commit4
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'dd'
,
ref:
nil
,
status:
'failed'
@commit5
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'ee'
,
ref:
nil
,
status:
'canceled'
end
it
'return statuses
with equal and nil ref set
'
do
is_expected
.
to
eq
([
@commit1
])
it
'return statuses
that are running or pending
'
do
is_expected
.
to
eq
([
@commit1
,
@commit2
])
end
end
describe
:running_or_pending
do
subject
{
CommitStatus
.
running_or_pending
.
order
(
:id
)
}
describe
'#before_sha'
do
subject
{
commit_status
.
before_sha
}
context
'when no before_sha is set for ci::commit'
do
before
{
commit
.
before_sha
=
nil
}
it
'return blank sha'
do
is_expected
.
to
eq
(
Gitlab
::
Git
::
BLANK_SHA
)
end
end
context
'for before_sha set for ci::commit'
do
let
(
:value
)
{
'1234'
}
before
{
commit
.
before_sha
=
value
}
it
'return the set value'
do
is_expected
.
to
eq
(
value
)
end
end
end
describe
'#stages'
do
before
do
@commit1
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'aa'
,
ref:
'bb'
,
status:
'running'
@commit2
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'cc'
,
ref:
'cc'
,
status:
'pending'
@commit3
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'aa'
,
ref:
nil
,
status:
'success'
@commit4
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'dd'
,
ref:
nil
,
status:
'failed'
@commit5
=
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
name:
'ee'
,
ref:
nil
,
status:
'canceled'
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
stage:
'build'
,
stage_idx:
0
,
status:
'success'
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
stage:
'build'
,
stage_idx:
0
,
status:
'failed'
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
stage:
'deploy'
,
stage_idx:
2
,
status:
'running'
FactoryGirl
.
create
:commit_status
,
commit:
commit
,
stage:
'test'
,
stage_idx:
1
,
status:
'success'
end
it
'return statuses that are running or pending'
do
is_expected
.
to
eq
([
@commit1
,
@commit2
])
context
'stages list'
do
subject
{
CommitStatus
.
where
(
commit:
commit
).
stages
}
it
'return ordered list of stages'
do
is_expected
.
to
eq
(
%w(build test deploy)
)
end
end
context
'stages with statuses'
do
subject
{
CommitStatus
.
where
(
commit:
commit
).
stages_status
}
it
'return list of stages with statuses'
do
is_expected
.
to
eq
({
'build'
=>
'failed'
,
'test'
=>
'success'
,
'deploy'
=>
'running'
})
end
end
end
end
spec/models/concerns/issuable_spec.rb
View file @
3b39214f
...
...
@@ -212,4 +212,34 @@ describe Issue, "Issuable" do
expect
(
issue
.
downvotes
).
to
eq
(
1
)
end
end
describe
".with_label"
do
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:bug
)
{
create
(
:label
,
project:
project
,
title:
'bug'
)
}
let
(
:feature
)
{
create
(
:label
,
project:
project
,
title:
'feature'
)
}
let
(
:enhancement
)
{
create
(
:label
,
project:
project
,
title:
'enhancement'
)
}
let
(
:issue1
)
{
create
(
:issue
,
title:
"Bugfix1"
,
project:
project
)
}
let
(
:issue2
)
{
create
(
:issue
,
title:
"Bugfix2"
,
project:
project
)
}
let
(
:issue3
)
{
create
(
:issue
,
title:
"Feature1"
,
project:
project
)
}
before
(
:each
)
do
issue1
.
labels
<<
bug
issue1
.
labels
<<
feature
issue2
.
labels
<<
bug
issue2
.
labels
<<
enhancement
issue3
.
labels
<<
feature
end
it
'finds the correct issue containing just enhancement label'
do
expect
(
Issue
.
with_label
(
enhancement
.
title
)).
to
match_array
([
issue2
])
end
it
'finds the correct issues containing the same label'
do
expect
(
Issue
.
with_label
(
bug
.
title
)).
to
match_array
([
issue1
,
issue2
])
end
it
'finds the correct issues containing only both labels'
do
expect
(
Issue
.
with_label
([
bug
.
title
,
enhancement
.
title
])).
to
match_array
([
issue2
])
end
end
end
spec/
lib/ci/status
_spec.rb
→
spec/
models/concerns/statuseable
_spec.rb
View file @
3b39214f
require
'spec_helper'
describe
Ci
::
Status
do
describe
'.get_status'
do
subject
{
described_class
.
get_status
(
statuses
)
}
describe
Statuseable
do
before
do
@object
=
Object
.
new
@object
.
extend
(
Statuseable
::
ClassMethods
)
end
describe
'.status'
do
before
do
allow
(
@object
).
to
receive
(
:all
).
and_return
(
CommitStatus
.
where
(
id:
statuses
))
end
subject
{
@object
.
status
}
shared_examples
'build status summary'
do
context
'all successful'
do
...
...
spec/models/merge_request_spec.rb
View file @
3b39214f
...
...
@@ -404,12 +404,12 @@ describe MergeRequest, models: true do
describe
'when the source project exists'
do
it
'returns the latest commit'
do
commit
=
double
(
:commit
,
id:
'123abc'
)
ci_commit
=
double
(
:ci_commit
)
ci_commit
=
double
(
:ci_commit
,
ref:
'master'
)
allow
(
subject
).
to
receive
(
:last_commit
).
and_return
(
commit
)
expect
(
subject
.
source_project
).
to
receive
(
:ci_commit
).
with
(
'123abc'
).
with
(
'123abc'
,
'master'
).
and_return
(
ci_commit
)
expect
(
subject
.
ci_commit
).
to
eq
(
ci_commit
)
...
...
spec/models/project_spec.rb
View file @
3b39214f
...
...
@@ -441,9 +441,22 @@ describe Project, models: true do
describe
:ci_commit
do
let
(
:project
)
{
create
:project
}
let
(
:commit
)
{
create
:ci_commit
,
project:
project
}
let
(
:commit
)
{
create
:ci_commit
,
project:
project
,
ref:
'master'
}
it
{
expect
(
project
.
ci_commit
(
commit
.
sha
)).
to
eq
(
commit
)
}
subject
{
project
.
ci_commit
(
commit
.
sha
,
'master'
)
}
it
{
is_expected
.
to
eq
(
commit
)
}
context
'return latest'
do
let
(
:commit2
)
{
create
:ci_commit
,
project:
project
,
ref:
'master'
}
before
do
commit
commit2
end
it
{
is_expected
.
to
eq
(
commit2
)
}
end
end
describe
:builds_enabled
do
...
...
spec/requests/api/builds_spec.rb
View file @
3b39214f
...
...
@@ -59,7 +59,7 @@ describe API::API, api: true do
describe
'GET /projects/:id/repository/commits/:sha/builds'
do
before
do
project
.
ensure_ci_commit
(
commit
.
sha
)
project
.
ensure_ci_commit
(
commit
.
sha
,
'master'
)
get
api
(
"/projects/
#{
project
.
id
}
/repository/commits/
#{
commit
.
sha
}
/builds"
,
api_user
)
end
...
...
spec/requests/api/commit_status_spec.rb
View file @
3b39214f
...
...
@@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do
let
(
:get_url
)
{
"/projects/
#{
project
.
id
}
/repository/commits/
#{
sha
}
/statuses"
}
context
'ci commit exists'
do
let!
(
:ci_commit
)
{
project
.
ensure_ci_commit
(
commit
.
id
)
}
let!
(
:master
)
{
project
.
ci_commits
.
create
(
sha:
commit
.
id
,
ref:
'master'
)
}
let!
(
:develop
)
{
project
.
ci_commits
.
create
(
sha:
commit
.
id
,
ref:
'develop'
)
}
it_behaves_like
'a paginated resources'
do
let
(
:request
)
{
get
api
(
get_url
,
reporter
)
}
...
...
@@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do
context
"reporter user"
do
let
(
:statuses_id
)
{
json_response
.
map
{
|
status
|
status
[
'id'
]
}
}
def
create_status
(
opts
=
{})
create
(
:commit_status
,
{
commit:
c
i_commit
}.
merge
(
opts
))
def
create_status
(
commit
,
opts
=
{})
create
(
:commit_status
,
{
commit:
c
ommit
,
ref:
commit
.
ref
}.
merge
(
opts
))
end
let!
(
:status1
)
{
create_status
(
status:
'running'
)
}
let!
(
:status2
)
{
create_status
(
name:
'coverage'
,
status:
'pending'
)
}
let!
(
:status3
)
{
create_status
(
ref:
'develop'
,
status:
'running'
,
allow_failure:
true
)
}
let!
(
:status4
)
{
create_status
(
name:
'coverage'
,
status:
'success'
)
}
let!
(
:status5
)
{
create_status
(
name:
'coverage'
,
ref:
'develop
'
,
status:
'success'
)
}
let!
(
:status6
)
{
create_status
(
status:
'success'
)
}
let!
(
:status1
)
{
create_status
(
master
,
status:
'running'
)
}
let!
(
:status2
)
{
create_status
(
master
,
name:
'coverage'
,
status:
'pending'
)
}
let!
(
:status3
)
{
create_status
(
develop
,
status:
'running'
,
allow_failure:
true
)
}
let!
(
:status4
)
{
create_status
(
master
,
name:
'coverage'
,
status:
'success'
)
}
let!
(
:status5
)
{
create_status
(
develop
,
name:
'coverage
'
,
status:
'success'
)
}
let!
(
:status6
)
{
create_status
(
master
,
status:
'success'
)
}
context
'latest commit statuses'
do
before
{
get
api
(
get_url
,
reporter
)
}
...
...
spec/requests/api/commits_spec.rb
View file @
3b39214f
...
...
@@ -48,14 +48,14 @@ describe API::API, api: true do
expect
(
response
.
status
).
to
eq
(
404
)
end
it
"should return n
ot_found for CI status
"
do
it
"should return n
il for commit without CI
"
do
get
api
(
"/projects/
#{
project
.
id
}
/repository/commits/
#{
project
.
repository
.
commit
.
id
}
"
,
user
)
expect
(
response
.
status
).
to
eq
(
200
)
expect
(
json_response
[
'status'
]).
to
eq
(
'not_found'
)
expect
(
json_response
[
'status'
]).
to
be_nil
end
it
"should return status for CI"
do
ci_commit
=
project
.
ensure_ci_commit
(
project
.
repository
.
commit
.
sha
)
ci_commit
=
project
.
ensure_ci_commit
(
project
.
repository
.
commit
.
sha
,
'master'
)
get
api
(
"/projects/
#{
project
.
id
}
/repository/commits/
#{
project
.
repository
.
commit
.
id
}
"
,
user
)
expect
(
response
.
status
).
to
eq
(
200
)
expect
(
json_response
[
'status'
]).
to
eq
(
ci_commit
.
status
)
...
...
spec/requests/ci/api/builds_spec.rb
View file @
3b39214f
...
...
@@ -20,8 +20,8 @@ describe Ci::API::API do
describe
"POST /builds/register"
do
it
"should start a build"
do
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
)
commit
.
create_builds
(
'master'
,
false
,
nil
)
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
,
ref:
'master'
)
commit
.
create_builds
(
nil
)
build
=
commit
.
builds
.
first
post
ci_api
(
"/builds/register"
),
token:
runner
.
token
,
info:
{
platform: :darwin
}
...
...
@@ -56,8 +56,8 @@ describe Ci::API::API do
end
it
"returns options"
do
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
)
commit
.
create_builds
(
'master'
,
false
,
nil
)
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
,
ref:
'master'
)
commit
.
create_builds
(
nil
)
post
ci_api
(
"/builds/register"
),
token:
runner
.
token
,
info:
{
platform: :darwin
}
...
...
@@ -66,8 +66,8 @@ describe Ci::API::API do
end
it
"returns variables"
do
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
)
commit
.
create_builds
(
'master'
,
false
,
nil
)
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
,
ref:
'master'
)
commit
.
create_builds
(
nil
)
project
.
variables
<<
Ci
::
Variable
.
new
(
key:
"SECRET_KEY"
,
value:
"secret_value"
)
post
ci_api
(
"/builds/register"
),
token:
runner
.
token
,
info:
{
platform: :darwin
}
...
...
@@ -83,10 +83,10 @@ describe Ci::API::API do
it
"returns variables for triggers"
do
trigger
=
FactoryGirl
.
create
(
:ci_trigger
,
project:
project
)
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
)
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
,
ref:
'master'
)
trigger_request
=
FactoryGirl
.
create
(
:ci_trigger_request_with_variables
,
commit:
commit
,
trigger:
trigger
)
commit
.
create_builds
(
'master'
,
false
,
nil
,
trigger_request
)
commit
.
create_builds
(
nil
,
trigger_request
)
project
.
variables
<<
Ci
::
Variable
.
new
(
key:
"SECRET_KEY"
,
value:
"secret_value"
)
post
ci_api
(
"/builds/register"
),
token:
runner
.
token
,
info:
{
platform: :darwin
}
...
...
@@ -103,8 +103,8 @@ describe Ci::API::API do
end
it
"returns dependent builds"
do
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
)
commit
.
create_builds
(
'master'
,
false
,
nil
,
nil
)
commit
=
FactoryGirl
.
create
(
:ci_commit
,
project:
project
,
ref:
'master'
)
commit
.
create_builds
(
nil
,
nil
)
commit
.
builds
.
where
(
stage:
'test'
).
each
(
&
:success
)
post
ci_api
(
"/builds/register"
),
token:
runner
.
token
,
info:
{
platform: :darwin
}
...
...
spec/services/ci/create_builds_service_spec.rb
View file @
3b39214f
require
'spec_helper'
describe
Ci
::
CreateBuildsService
,
services:
true
do
let
(
:commit
)
{
create
(
:ci_commit
)
}
let
(
:commit
)
{
create
(
:ci_commit
,
ref:
'master'
)
}
let
(
:user
)
{
create
(
:user
)
}
describe
'#execute'
do
...
...
@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do
#
subject
do
described_class
.
new
.
execute
(
commit
,
'test'
,
'master'
,
nil
,
user
,
nil
,
status
)
described_class
.
new
(
commit
).
execute
(
commit
,
nil
,
user
,
status
)
end
context
'next builds available'
do
...
...
spec/services/ci/image_for_build_service_spec.rb
View file @
3b39214f
...
...
@@ -5,7 +5,7 @@ module Ci
let
(
:service
)
{
ImageForBuildService
.
new
}
let
(
:project
)
{
FactoryGirl
.
create
(
:empty_project
)
}
let
(
:commit_sha
)
{
'01234567890123456789'
}
let
(
:commit
)
{
project
.
ensure_ci_commit
(
commit_sha
)
}
let
(
:commit
)
{
project
.
ensure_ci_commit
(
commit_sha
,
'master'
)
}
let
(
:build
)
{
FactoryGirl
.
create
(
:ci_build
,
commit:
commit
)
}
describe
:execute
do
...
...
vendor/assets/javascripts/raven.js
deleted
100644 → 0
View file @
d4f6d398
/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */
/*
* Includes TraceKit
* https://github.com/getsentry/TraceKit
*
* Copyright 2016 Matt Robenolt and other contributors
* Released under the BSD license
* https://github.com/getsentry/raven-js/blob/master/LICENSE
*
*/
(
function
(
f
){
if
(
typeof
exports
===
"
object
"
&&
typeof
module
!==
"
undefined
"
){
module
.
exports
=
f
()}
else
if
(
typeof
define
===
"
function
"
&&
define
.
amd
){
define
([],
f
)}
else
{
var
g
;
if
(
typeof
window
!==
"
undefined
"
){
g
=
window
}
else
if
(
typeof
global
!==
"
undefined
"
){
g
=
global
}
else
if
(
typeof
self
!==
"
undefined
"
){
g
=
self
}
else
{
g
=
this
}
g
.
Raven
=
f
()}})(
function
(){
var
define
,
module
,
exports
;
return
(
function
e
(
t
,
n
,
r
){
function
s
(
o
,
u
){
if
(
!
n
[
o
]){
if
(
!
t
[
o
]){
var
a
=
typeof
require
==
"
function
"
&&
require
;
if
(
!
u
&&
a
)
return
a
(
o
,
!
0
);
if
(
i
)
return
i
(
o
,
!
0
);
var
f
=
new
Error
(
"
Cannot find module '
"
+
o
+
"
'
"
);
throw
f
.
code
=
"
MODULE_NOT_FOUND
"
,
f
}
var
l
=
n
[
o
]
=
{
exports
:{}};
t
[
o
][
0
].
call
(
l
.
exports
,
function
(
e
){
var
n
=
t
[
o
][
1
][
e
];
return
s
(
n
?
n
:
e
)},
l
,
l
.
exports
,
e
,
t
,
n
,
r
)}
return
n
[
o
].
exports
}
var
i
=
typeof
require
==
"
function
"
&&
require
;
for
(
var
o
=
0
;
o
<
r
.
length
;
o
++
)
s
(
r
[
o
]);
return
s
})({
1
:[
function
(
_dereq_
,
module
,
exports
){
'
use strict
'
;
function
RavenConfigError
(
message
)
{
this
.
name
=
'
RavenConfigError
'
;
this
.
message
=
message
;
}
RavenConfigError
.
prototype
=
new
Error
();
RavenConfigError
.
prototype
.
constructor
=
RavenConfigError
;
module
.
exports
=
RavenConfigError
;
},{}],
2
:[
function
(
_dereq_
,
module
,
exports
){
/*global XDomainRequest:false*/
'
use strict
'
;
var
TraceKit
=
_dereq_
(
5
);
var
RavenConfigError
=
_dereq_
(
1
);
var
utils
=
_dereq_
(
4
);
var
isFunction
=
utils
.
isFunction
;
var
isUndefined
=
utils
.
isUndefined
;
var
isError
=
utils
.
isError
;
var
isEmptyObject
=
utils
.
isEmptyObject
;
var
hasKey
=
utils
.
hasKey
;
var
joinRegExp
=
utils
.
joinRegExp
;
var
each
=
utils
.
each
;
var
objectMerge
=
utils
.
objectMerge
;
var
truncate
=
utils
.
truncate
;
var
urlencode
=
utils
.
urlencode
;
var
uuid4
=
utils
.
uuid4
;
var
dsnKeys
=
'
source protocol user pass host port path
'
.
split
(
'
'
),
dsnPattern
=
/^
(?:(\w
+
)
:
)?\/\/(?:(\w
+
)(
:
\w
+
)?
@
)?([\w\.
-
]
+
)(?:
:
(\d
+
))?(\/
.*
)
/
;
function
now
()
{
return
+
new
Date
();
}
// First, check for JSON support
// If there is no JSON, we no-op the core features of Raven
// since JSON is required to encode the payload
function
Raven
()
{
this
.
_hasJSON
=
!!
(
typeof
JSON
===
'
object
'
&&
JSON
.
stringify
);
// Raven can run in contexts where there's no document (react-native)
this
.
_hasDocument
=
typeof
document
!==
'
undefined
'
;
this
.
_lastCapturedException
=
null
;
this
.
_lastEventId
=
null
;
this
.
_globalServer
=
null
;
this
.
_globalKey
=
null
;
this
.
_globalProject
=
null
;
this
.
_globalContext
=
{};
this
.
_globalOptions
=
{
logger
:
'
javascript
'
,
ignoreErrors
:
[],
ignoreUrls
:
[],
whitelistUrls
:
[],
includePaths
:
[],
crossOrigin
:
'
anonymous
'
,
collectWindowErrors
:
true
,
maxMessageLength
:
0
,
stackTraceLimit
:
50
};
this
.
_ignoreOnError
=
0
;
this
.
_isRavenInstalled
=
false
;
this
.
_originalErrorStackTraceLimit
=
Error
.
stackTraceLimit
;
// capture references to window.console *and* all its methods first
// before the console plugin has a chance to monkey patch
this
.
_originalConsole
=
window
.
console
||
{};
this
.
_originalConsoleMethods
=
{};
this
.
_plugins
=
[];
this
.
_startTime
=
now
();
this
.
_wrappedBuiltIns
=
[];
for
(
var
method
in
this
.
_originalConsole
)
{
// eslint-disable-line guard-for-in
this
.
_originalConsoleMethods
[
method
]
=
this
.
_originalConsole
[
method
];
}
}
/*
* The core Raven singleton
*
* @this {Raven}
*/
Raven
.
prototype
=
{
// Hardcode version string so that raven source can be loaded directly via
// webpack (using a build step causes webpack #1617). Grunt verifies that
// this value matches package.json during build.
// See: https://github.com/getsentry/raven-js/issues/465
VERSION
:
'
2.3.0
'
,
debug
:
false
,
TraceKit
:
TraceKit
,
// alias to TraceKit
/*
* Configure Raven with a DSN and extra options
*
* @param {string} dsn The public Sentry DSN
* @param {object} options Optional set of of global options [optional]
* @return {Raven}
*/
config
:
function
(
dsn
,
options
)
{
var
self
=
this
;
if
(
this
.
_globalServer
)
{
this
.
_logDebug
(
'
error
'
,
'
Error: Raven has already been configured
'
);
return
this
;
}
if
(
!
dsn
)
return
this
;
// merge in options
if
(
options
)
{
each
(
options
,
function
(
key
,
value
){
// tags and extra are special and need to be put into context
if
(
key
===
'
tags
'
||
key
===
'
extra
'
)
{
self
.
_globalContext
[
key
]
=
value
;
}
else
{
self
.
_globalOptions
[
key
]
=
value
;
}
});
}
var
uri
=
this
.
_parseDSN
(
dsn
),
lastSlash
=
uri
.
path
.
lastIndexOf
(
'
/
'
),
path
=
uri
.
path
.
substr
(
1
,
lastSlash
);
this
.
_dsn
=
dsn
;
// "Script error." is hard coded into browsers for errors that it can't read.
// this is the result of a script being pulled in from an external domain and CORS.
this
.
_globalOptions
.
ignoreErrors
.
push
(
/^Script error
\.?
$/
);
this
.
_globalOptions
.
ignoreErrors
.
push
(
/^Javascript error: Script error
\.?
on line 0$/
);
// join regexp rules into one big rule
this
.
_globalOptions
.
ignoreErrors
=
joinRegExp
(
this
.
_globalOptions
.
ignoreErrors
);
this
.
_globalOptions
.
ignoreUrls
=
this
.
_globalOptions
.
ignoreUrls
.
length
?
joinRegExp
(
this
.
_globalOptions
.
ignoreUrls
)
:
false
;
this
.
_globalOptions
.
whitelistUrls
=
this
.
_globalOptions
.
whitelistUrls
.
length
?
joinRegExp
(
this
.
_globalOptions
.
whitelistUrls
)
:
false
;
this
.
_globalOptions
.
includePaths
=
joinRegExp
(
this
.
_globalOptions
.
includePaths
);
this
.
_globalKey
=
uri
.
user
;
this
.
_globalSecret
=
uri
.
pass
&&
uri
.
pass
.
substr
(
1
);
this
.
_globalProject
=
uri
.
path
.
substr
(
lastSlash
+
1
);
this
.
_globalServer
=
this
.
_getGlobalServer
(
uri
);
this
.
_globalEndpoint
=
this
.
_globalServer
+
'
/
'
+
path
+
'
api/
'
+
this
.
_globalProject
+
'
/store/
'
;
if
(
this
.
_globalOptions
.
fetchContext
)
{
TraceKit
.
remoteFetching
=
true
;
}
if
(
this
.
_globalOptions
.
linesOfContext
)
{
TraceKit
.
linesOfContext
=
this
.
_globalOptions
.
linesOfContext
;
}
TraceKit
.
collectWindowErrors
=
!!
this
.
_globalOptions
.
collectWindowErrors
;
// return for chaining
return
this
;
},
/*
* Installs a global window.onerror error handler
* to capture and report uncaught exceptions.
* At this point, install() is required to be called due
* to the way TraceKit is set up.
*
* @return {Raven}
*/
install
:
function
()
{
var
self
=
this
;
if
(
this
.
isSetup
()
&&
!
this
.
_isRavenInstalled
)
{
TraceKit
.
report
.
subscribe
(
function
()
{
self
.
_handleOnErrorStackInfo
.
apply
(
self
,
arguments
);
});
this
.
_wrapBuiltIns
();
// Install all of the plugins
this
.
_drainPlugins
();
this
.
_isRavenInstalled
=
true
;
}
Error
.
stackTraceLimit
=
this
.
_globalOptions
.
stackTraceLimit
;
return
this
;
},
/*
* Wrap code within a context so Raven can capture errors
* reliably across domains that is executed immediately.
*
* @param {object} options A specific set of options for this context [optional]
* @param {function} func The callback to be immediately executed within the context
* @param {array} args An array of arguments to be called with the callback [optional]
*/
context
:
function
(
options
,
func
,
args
)
{
if
(
isFunction
(
options
))
{
args
=
func
||
[];
func
=
options
;
options
=
undefined
;
}
return
this
.
wrap
(
options
,
func
).
apply
(
this
,
args
);
},
/*
* Wrap code within a context and returns back a new function to be executed
*
* @param {object} options A specific set of options for this context [optional]
* @param {function} func The function to be wrapped in a new context
* @return {function} The newly wrapped functions with a context
*/
wrap
:
function
(
options
,
func
)
{
var
self
=
this
;
// 1 argument has been passed, and it's not a function
// so just return it
if
(
isUndefined
(
func
)
&&
!
isFunction
(
options
))
{
return
options
;
}
// options is optional
if
(
isFunction
(
options
))
{
func
=
options
;
options
=
undefined
;
}
// At this point, we've passed along 2 arguments, and the second one
// is not a function either, so we'll just return the second argument.
if
(
!
isFunction
(
func
))
{
return
func
;
}
// We don't wanna wrap it twice!
try
{
if
(
func
.
__raven__
)
{
return
func
;
}
}
catch
(
e
)
{
// Just accessing the __raven__ prop in some Selenium environments
// can cause a "Permission denied" exception (see raven-js#495).
// Bail on wrapping and return the function as-is (defers to window.onerror).
return
func
;
}
// If this has already been wrapped in the past, return that
if
(
func
.
__raven_wrapper__
){
return
func
.
__raven_wrapper__
;
}
function
wrapped
()
{
var
args
=
[],
i
=
arguments
.
length
,
deep
=
!
options
||
options
&&
options
.
deep
!==
false
;
// Recursively wrap all of a function's arguments that are
// functions themselves.
while
(
i
--
)
args
[
i
]
=
deep
?
self
.
wrap
(
options
,
arguments
[
i
])
:
arguments
[
i
];
try
{
return
func
.
apply
(
this
,
args
);
}
catch
(
e
)
{
self
.
_ignoreNextOnError
();
self
.
captureException
(
e
,
options
);
throw
e
;
}
}
// copy over properties of the old function
for
(
var
property
in
func
)
{
if
(
hasKey
(
func
,
property
))
{
wrapped
[
property
]
=
func
[
property
];
}
}
func
.
__raven_wrapper__
=
wrapped
;
wrapped
.
prototype
=
func
.
prototype
;
// Signal that this function has been wrapped already
// for both debugging and to prevent it to being wrapped twice
wrapped
.
__raven__
=
true
;
wrapped
.
__inner__
=
func
;
return
wrapped
;
},
/*
* Uninstalls the global error handler.
*
* @return {Raven}
*/
uninstall
:
function
()
{
TraceKit
.
report
.
uninstall
();
this
.
_restoreBuiltIns
();
Error
.
stackTraceLimit
=
this
.
_originalErrorStackTraceLimit
;
this
.
_isRavenInstalled
=
false
;
return
this
;
},
/*
* Manually capture an exception and send it over to Sentry
*
* @param {error} ex An exception to be logged
* @param {object} options A specific set of options for this error [optional]
* @return {Raven}
*/
captureException
:
function
(
ex
,
options
)
{
// If not an Error is passed through, recall as a message instead
if
(
!
isError
(
ex
))
return
this
.
captureMessage
(
ex
,
options
);
// Store the raw exception object for potential debugging and introspection
this
.
_lastCapturedException
=
ex
;
// TraceKit.report will re-raise any exception passed to it,
// which means you have to wrap it in try/catch. Instead, we
// can wrap it here and only re-raise if TraceKit.report
// raises an exception different from the one we asked to
// report on.
try
{
var
stack
=
TraceKit
.
computeStackTrace
(
ex
);
this
.
_handleStackInfo
(
stack
,
options
);
}
catch
(
ex1
)
{
if
(
ex
!==
ex1
)
{
throw
ex1
;
}
}
return
this
;
},
/*
* Manually send a message to Sentry
*
* @param {string} msg A plain message to be captured in Sentry
* @param {object} options A specific set of options for this message [optional]
* @return {Raven}
*/
captureMessage
:
function
(
msg
,
options
)
{
// config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
// early call; we'll error on the side of logging anything called before configuration since it's
// probably something you should see:
if
(
!!
this
.
_globalOptions
.
ignoreErrors
.
test
&&
this
.
_globalOptions
.
ignoreErrors
.
test
(
msg
))
{
return
;
}
// Fire away!
this
.
_send
(
objectMerge
({
message
:
msg
+
''
// Make sure it's actually a string
},
options
)
);
return
this
;
},
addPlugin
:
function
(
plugin
/*arg1, arg2, ... argN*/
)
{
var
pluginArgs
=
Array
.
prototype
.
slice
.
call
(
arguments
,
1
);
this
.
_plugins
.
push
([
plugin
,
pluginArgs
]);
if
(
this
.
_isRavenInstalled
)
{
this
.
_drainPlugins
();
}
return
this
;
},
/*
* Set/clear a user to be sent along with the payload.
*
* @param {object} user An object representing user data [optional]
* @return {Raven}
*/
setUserContext
:
function
(
user
)
{
// Intentionally do not merge here since that's an unexpected behavior.
this
.
_globalContext
.
user
=
user
;
return
this
;
},
/*
* Merge extra attributes to be sent along with the payload.
*
* @param {object} extra An object representing extra data [optional]
* @return {Raven}
*/
setExtraContext
:
function
(
extra
)
{
this
.
_mergeContext
(
'
extra
'
,
extra
);
return
this
;
},
/*
* Merge tags to be sent along with the payload.
*
* @param {object} tags An object representing tags [optional]
* @return {Raven}
*/
setTagsContext
:
function
(
tags
)
{
this
.
_mergeContext
(
'
tags
'
,
tags
);
return
this
;
},
/*
* Clear all of the context.
*
* @return {Raven}
*/
clearContext
:
function
()
{
this
.
_globalContext
=
{};
return
this
;
},
/*
* Get a copy of the current context. This cannot be mutated.
*
* @return {object} copy of context
*/
getContext
:
function
()
{
// lol javascript
return
JSON
.
parse
(
JSON
.
stringify
(
this
.
_globalContext
));
},
/*
* Set release version of application
*
* @param {string} release Typically something like a git SHA to identify version
* @return {Raven}
*/
setRelease
:
function
(
release
)
{
this
.
_globalOptions
.
release
=
release
;
return
this
;
},
/*
* Set the dataCallback option
*
* @param {function} callback The callback to run which allows the
* data blob to be mutated before sending
* @return {Raven}
*/
setDataCallback
:
function
(
callback
)
{
this
.
_globalOptions
.
dataCallback
=
callback
;
return
this
;
},
/*
* Set the shouldSendCallback option
*
* @param {function} callback The callback to run which allows
* introspecting the blob before sending
* @return {Raven}
*/
setShouldSendCallback
:
function
(
callback
)
{
this
.
_globalOptions
.
shouldSendCallback
=
callback
;
return
this
;
},
/**
* Override the default HTTP transport mechanism that transmits data
* to the Sentry server.
*
* @param {function} transport Function invoked instead of the default
* `makeRequest` handler.
*
* @return {Raven}
*/
setTransport
:
function
(
transport
)
{
this
.
_globalOptions
.
transport
=
transport
;
return
this
;
},
/*
* Get the latest raw exception that was captured by Raven.
*
* @return {error}
*/
lastException
:
function
()
{
return
this
.
_lastCapturedException
;
},
/*
* Get the last event id
*
* @return {string}
*/
lastEventId
:
function
()
{
return
this
.
_lastEventId
;
},
/*
* Determine if Raven is setup and ready to go.
*
* @return {boolean}
*/
isSetup
:
function
()
{
if
(
!
this
.
_hasJSON
)
return
false
;
// needs JSON support
if
(
!
this
.
_globalServer
)
{
if
(
!
this
.
ravenNotConfiguredError
)
{
this
.
ravenNotConfiguredError
=
true
;
this
.
_logDebug
(
'
error
'
,
'
Error: Raven has not been configured.
'
);
}
return
false
;
}
return
true
;
},
afterLoad
:
function
()
{
// TODO: remove window dependence?
// Attempt to initialize Raven on load
var
RavenConfig
=
window
.
RavenConfig
;
if
(
RavenConfig
)
{
this
.
config
(
RavenConfig
.
dsn
,
RavenConfig
.
config
).
install
();
}
},
showReportDialog
:
function
(
options
)
{
if
(
!
window
.
document
)
// doesn't work without a document (React native)
return
;
options
=
options
||
{};
var
lastEventId
=
options
.
eventId
||
this
.
lastEventId
();
if
(
!
lastEventId
)
{
throw
new
RavenConfigError
(
'
Missing eventId
'
);
}
var
dsn
=
options
.
dsn
||
this
.
_dsn
;
if
(
!
dsn
)
{
throw
new
RavenConfigError
(
'
Missing DSN
'
);
}
var
encode
=
encodeURIComponent
;
var
qs
=
''
;
qs
+=
'
?eventId=
'
+
encode
(
lastEventId
);
qs
+=
'
&dsn=
'
+
encode
(
dsn
);
var
user
=
options
.
user
||
this
.
_globalContext
.
user
;
if
(
user
)
{
if
(
user
.
name
)
qs
+=
'
&name=
'
+
encode
(
user
.
name
);
if
(
user
.
email
)
qs
+=
'
&email=
'
+
encode
(
user
.
email
);
}
var
globalServer
=
this
.
_getGlobalServer
(
this
.
_parseDSN
(
dsn
));
var
script
=
document
.
createElement
(
'
script
'
);
script
.
async
=
true
;
script
.
src
=
globalServer
+
'
/api/embed/error-page/
'
+
qs
;
(
document
.
head
||
document
.
body
).
appendChild
(
script
);
},
/**** Private functions ****/
_ignoreNextOnError
:
function
()
{
var
self
=
this
;
this
.
_ignoreOnError
+=
1
;
setTimeout
(
function
()
{
// onerror should trigger before setTimeout
self
.
_ignoreOnError
-=
1
;
});
},
_triggerEvent
:
function
(
eventType
,
options
)
{
// NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it
var
evt
,
key
;
if
(
!
this
.
_hasDocument
)
return
;
options
=
options
||
{};
eventType
=
'
raven
'
+
eventType
.
substr
(
0
,
1
).
toUpperCase
()
+
eventType
.
substr
(
1
);
if
(
document
.
createEvent
)
{
evt
=
document
.
createEvent
(
'
HTMLEvents
'
);
evt
.
initEvent
(
eventType
,
true
,
true
);
}
else
{
evt
=
document
.
createEventObject
();
evt
.
eventType
=
eventType
;
}
for
(
key
in
options
)
if
(
hasKey
(
options
,
key
))
{
evt
[
key
]
=
options
[
key
];
}
if
(
document
.
createEvent
)
{
// IE9 if standards
document
.
dispatchEvent
(
evt
);
}
else
{
// IE8 regardless of Quirks or Standards
// IE9 if quirks
try
{
document
.
fireEvent
(
'
on
'
+
evt
.
eventType
.
toLowerCase
(),
evt
);
}
catch
(
e
)
{
// Do nothing
}
}
},
/**
* Install any queued plugins
*/
_wrapBuiltIns
:
function
()
{
var
self
=
this
;
function
fill
(
obj
,
name
,
replacement
,
noUndo
)
{
var
orig
=
obj
[
name
];
obj
[
name
]
=
replacement
(
orig
);
if
(
!
noUndo
)
{
self
.
_wrappedBuiltIns
.
push
([
obj
,
name
,
orig
]);
}
}
function
wrapTimeFn
(
orig
)
{
return
function
(
fn
,
t
)
{
// preserve arity
// Make a copy of the arguments
var
args
=
[].
slice
.
call
(
arguments
);
var
originalCallback
=
args
[
0
];
if
(
isFunction
(
originalCallback
))
{
args
[
0
]
=
self
.
wrap
(
originalCallback
);
}
// IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
// also supports only two arguments and doesn't care what this is, so we
// can just call the original function directly.
if
(
orig
.
apply
)
{
return
orig
.
apply
(
this
,
args
);
}
else
{
return
orig
(
args
[
0
],
args
[
1
]);
}
};
}
fill
(
window
,
'
setTimeout
'
,
wrapTimeFn
);
fill
(
window
,
'
setInterval
'
,
wrapTimeFn
);
if
(
window
.
requestAnimationFrame
)
{
fill
(
window
,
'
requestAnimationFrame
'
,
function
(
orig
)
{
return
function
(
cb
)
{
return
orig
(
self
.
wrap
(
cb
));
};
});
}
// event targets borrowed from bugsnag-js:
// https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
'
EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload
'
.
replace
(
/
\w
+/g
,
function
(
global
)
{
var
proto
=
window
[
global
]
&&
window
[
global
].
prototype
;
if
(
proto
&&
proto
.
hasOwnProperty
&&
proto
.
hasOwnProperty
(
'
addEventListener
'
))
{
fill
(
proto
,
'
addEventListener
'
,
function
(
orig
)
{
return
function
(
evt
,
fn
,
capture
,
secure
)
{
// preserve arity
try
{
if
(
fn
&&
fn
.
handleEvent
)
{
fn
.
handleEvent
=
self
.
wrap
(
fn
.
handleEvent
);
}
}
catch
(
err
)
{
// can sometimes get 'Permission denied to access property "handle Event'
}
return
orig
.
call
(
this
,
evt
,
self
.
wrap
(
fn
),
capture
,
secure
);
};
});
fill
(
proto
,
'
removeEventListener
'
,
function
(
orig
)
{
return
function
(
evt
,
fn
,
capture
,
secure
)
{
fn
=
fn
&&
(
fn
.
__raven_wrapper__
?
fn
.
__raven_wrapper__
:
fn
);
return
orig
.
call
(
this
,
evt
,
fn
,
capture
,
secure
);
};
});
}
});
if
(
'
XMLHttpRequest
'
in
window
)
{
fill
(
XMLHttpRequest
.
prototype
,
'
send
'
,
function
(
origSend
)
{
return
function
(
data
)
{
// preserve arity
var
xhr
=
this
;
'
onreadystatechange onload onerror onprogress
'
.
replace
(
/
\w
+/g
,
function
(
prop
)
{
if
(
prop
in
xhr
&&
Object
.
prototype
.
toString
.
call
(
xhr
[
prop
])
===
'
[object Function]
'
)
{
fill
(
xhr
,
prop
,
function
(
orig
)
{
return
self
.
wrap
(
orig
);
},
true
/* noUndo */
);
// don't track filled methods on XHR instances
}
});
return
origSend
.
apply
(
this
,
arguments
);
};
});
}
var
$
=
window
.
jQuery
||
window
.
$
;
if
(
$
&&
$
.
fn
&&
$
.
fn
.
ready
)
{
fill
(
$
.
fn
,
'
ready
'
,
function
(
orig
)
{
return
function
(
fn
)
{
return
orig
.
call
(
this
,
self
.
wrap
(
fn
));
};
});
}
},
_restoreBuiltIns
:
function
()
{
// restore any wrapped builtins
var
builtin
;
while
(
this
.
_wrappedBuiltIns
.
length
)
{
builtin
=
this
.
_wrappedBuiltIns
.
shift
();
var
obj
=
builtin
[
0
],
name
=
builtin
[
1
],
orig
=
builtin
[
2
];
obj
[
name
]
=
orig
;
}
},
_drainPlugins
:
function
()
{
var
self
=
this
;
// FIX ME TODO
each
(
this
.
_plugins
,
function
(
_
,
plugin
)
{
var
installer
=
plugin
[
0
];
var
args
=
plugin
[
1
];
installer
.
apply
(
self
,
[
self
].
concat
(
args
));
});
},
_parseDSN
:
function
(
str
)
{
var
m
=
dsnPattern
.
exec
(
str
),
dsn
=
{},
i
=
7
;
try
{
while
(
i
--
)
dsn
[
dsnKeys
[
i
]]
=
m
[
i
]
||
''
;
}
catch
(
e
)
{
throw
new
RavenConfigError
(
'
Invalid DSN:
'
+
str
);
}
if
(
dsn
.
pass
&&
!
this
.
_globalOptions
.
allowSecretKey
)
{
throw
new
RavenConfigError
(
'
Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key
'
);
}
return
dsn
;
},
_getGlobalServer
:
function
(
uri
)
{
// assemble the endpoint from the uri pieces
var
globalServer
=
'
//
'
+
uri
.
host
+
(
uri
.
port
?
'
:
'
+
uri
.
port
:
''
);
if
(
uri
.
protocol
)
{
globalServer
=
uri
.
protocol
+
'
:
'
+
globalServer
;
}
return
globalServer
;
},
_handleOnErrorStackInfo
:
function
()
{
// if we are intentionally ignoring errors via onerror, bail out
if
(
!
this
.
_ignoreOnError
)
{
this
.
_handleStackInfo
.
apply
(
this
,
arguments
);
}
},
_handleStackInfo
:
function
(
stackInfo
,
options
)
{
var
self
=
this
;
var
frames
=
[];
if
(
stackInfo
.
stack
&&
stackInfo
.
stack
.
length
)
{
each
(
stackInfo
.
stack
,
function
(
i
,
stack
)
{
var
frame
=
self
.
_normalizeFrame
(
stack
);
if
(
frame
)
{
frames
.
push
(
frame
);
}
});
}
this
.
_triggerEvent
(
'
handle
'
,
{
stackInfo
:
stackInfo
,
options
:
options
});
this
.
_processException
(
stackInfo
.
name
,
stackInfo
.
message
,
stackInfo
.
url
,
stackInfo
.
lineno
,
frames
.
slice
(
0
,
this
.
_globalOptions
.
stackTraceLimit
),
options
);
},
_normalizeFrame
:
function
(
frame
)
{
if
(
!
frame
.
url
)
return
;
// normalize the frames data
var
normalized
=
{
filename
:
frame
.
url
,
lineno
:
frame
.
line
,
colno
:
frame
.
column
,
'
function
'
:
frame
.
func
||
'
?
'
},
context
=
this
.
_extractContextFromFrame
(
frame
),
i
;
if
(
context
)
{
var
keys
=
[
'
pre_context
'
,
'
context_line
'
,
'
post_context
'
];
i
=
3
;
while
(
i
--
)
normalized
[
keys
[
i
]]
=
context
[
i
];
}
normalized
.
in_app
=
!
(
// determine if an exception came from outside of our app
// first we check the global includePaths list.
!!
this
.
_globalOptions
.
includePaths
.
test
&&
!
this
.
_globalOptions
.
includePaths
.
test
(
normalized
.
filename
)
||
// Now we check for fun, if the function name is Raven or TraceKit
/
(
Raven|TraceKit
)\.
/
.
test
(
normalized
[
'
function
'
])
||
// finally, we do a last ditch effort and check for raven.min.js
/raven
\.(
min
\.)?
js$/
.
test
(
normalized
.
filename
)
);
return
normalized
;
},
_extractContextFromFrame
:
function
(
frame
)
{
// immediately check if we should even attempt to parse a context
if
(
!
frame
.
context
||
!
this
.
_globalOptions
.
fetchContext
)
return
;
var
context
=
frame
.
context
,
pivot
=
~~
(
context
.
length
/
2
),
i
=
context
.
length
,
isMinified
=
false
;
while
(
i
--
)
{
// We're making a guess to see if the source is minified or not.
// To do that, we make the assumption if *any* of the lines passed
// in are greater than 300 characters long, we bail.
// Sentry will see that there isn't a context
if
(
context
[
i
].
length
>
300
)
{
isMinified
=
true
;
break
;
}
}
if
(
isMinified
)
{
// The source is minified and we don't know which column. Fuck it.
if
(
isUndefined
(
frame
.
column
))
return
;
// If the source is minified and has a frame column
// we take a chunk of the offending line to hopefully shed some light
return
[
[],
// no pre_context
context
[
pivot
].
substr
(
frame
.
column
,
50
),
// grab 50 characters, starting at the offending column
[]
// no post_context
];
}
return
[
context
.
slice
(
0
,
pivot
),
// pre_context
context
[
pivot
],
// context_line
context
.
slice
(
pivot
+
1
)
// post_context
];
},
_processException
:
function
(
type
,
message
,
fileurl
,
lineno
,
frames
,
options
)
{
var
stacktrace
,
fullMessage
;
if
(
!!
this
.
_globalOptions
.
ignoreErrors
.
test
&&
this
.
_globalOptions
.
ignoreErrors
.
test
(
message
))
return
;
message
+=
''
;
message
=
truncate
(
message
,
this
.
_globalOptions
.
maxMessageLength
);
fullMessage
=
(
type
?
type
+
'
:
'
:
''
)
+
message
;
fullMessage
=
truncate
(
fullMessage
,
this
.
_globalOptions
.
maxMessageLength
);
if
(
frames
&&
frames
.
length
)
{
fileurl
=
frames
[
0
].
filename
||
fileurl
;
// Sentry expects frames oldest to newest
// and JS sends them as newest to oldest
frames
.
reverse
();
stacktrace
=
{
frames
:
frames
};
}
else
if
(
fileurl
)
{
stacktrace
=
{
frames
:
[{
filename
:
fileurl
,
lineno
:
lineno
,
in_app
:
true
}]
};
}
if
(
!!
this
.
_globalOptions
.
ignoreUrls
.
test
&&
this
.
_globalOptions
.
ignoreUrls
.
test
(
fileurl
))
return
;
if
(
!!
this
.
_globalOptions
.
whitelistUrls
.
test
&&
!
this
.
_globalOptions
.
whitelistUrls
.
test
(
fileurl
))
return
;
var
data
=
objectMerge
({
// sentry.interfaces.Exception
exception
:
{
values
:
[{
type
:
type
,
value
:
message
,
stacktrace
:
stacktrace
}]
},
culprit
:
fileurl
,
message
:
fullMessage
},
options
);
// Fire away!
this
.
_send
(
data
);
},
_trimPacket
:
function
(
data
)
{
// For now, we only want to truncate the two different messages
// but this could/should be expanded to just trim everything
var
max
=
this
.
_globalOptions
.
maxMessageLength
;
data
.
message
=
truncate
(
data
.
message
,
max
);
if
(
data
.
exception
)
{
var
exception
=
data
.
exception
.
values
[
0
];
exception
.
value
=
truncate
(
exception
.
value
,
max
);
}
return
data
;
},
_getHttpData
:
function
()
{
if
(
!
this
.
_hasDocument
||
!
document
.
location
||
!
document
.
location
.
href
)
{
return
;
}
var
httpData
=
{
headers
:
{
'
User-Agent
'
:
navigator
.
userAgent
}
};
httpData
.
url
=
document
.
location
.
href
;
if
(
document
.
referrer
)
{
httpData
.
headers
.
Referer
=
document
.
referrer
;
}
return
httpData
;
},
_send
:
function
(
data
)
{
var
self
=
this
;
var
globalOptions
=
this
.
_globalOptions
;
var
baseData
=
{
project
:
this
.
_globalProject
,
logger
:
globalOptions
.
logger
,
platform
:
'
javascript
'
},
httpData
=
this
.
_getHttpData
();
if
(
httpData
)
{
baseData
.
request
=
httpData
;
}
data
=
objectMerge
(
baseData
,
data
);
// Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
data
.
tags
=
objectMerge
(
objectMerge
({},
this
.
_globalContext
.
tags
),
data
.
tags
);
data
.
extra
=
objectMerge
(
objectMerge
({},
this
.
_globalContext
.
extra
),
data
.
extra
);
// Send along our own collected metadata with extra
data
.
extra
[
'
session:duration
'
]
=
now
()
-
this
.
_startTime
;
// If there are no tags/extra, strip the key from the payload alltogther.
if
(
isEmptyObject
(
data
.
tags
))
delete
data
.
tags
;
if
(
this
.
_globalContext
.
user
)
{
// sentry.interfaces.User
data
.
user
=
this
.
_globalContext
.
user
;
}
// Include the release if it's defined in globalOptions
if
(
globalOptions
.
release
)
data
.
release
=
globalOptions
.
release
;
// Include server_name if it's defined in globalOptions
if
(
globalOptions
.
serverName
)
data
.
server_name
=
globalOptions
.
serverName
;
if
(
isFunction
(
globalOptions
.
dataCallback
))
{
data
=
globalOptions
.
dataCallback
(
data
)
||
data
;
}
// Why??????????
if
(
!
data
||
isEmptyObject
(
data
))
{
return
;
}
// Check if the request should be filtered or not
if
(
isFunction
(
globalOptions
.
shouldSendCallback
)
&&
!
globalOptions
.
shouldSendCallback
(
data
))
{
return
;
}
// Send along an event_id if not explicitly passed.
// This event_id can be used to reference the error within Sentry itself.
// Set lastEventId after we know the error should actually be sent
this
.
_lastEventId
=
data
.
event_id
||
(
data
.
event_id
=
uuid4
());
// Try and clean up the packet before sending by truncating long values
data
=
this
.
_trimPacket
(
data
);
this
.
_logDebug
(
'
debug
'
,
'
Raven about to send:
'
,
data
);
if
(
!
this
.
isSetup
())
return
;
var
auth
=
{
sentry_version
:
'
7
'
,
sentry_client
:
'
raven-js/
'
+
this
.
VERSION
,
sentry_key
:
this
.
_globalKey
};
if
(
this
.
_globalSecret
)
{
auth
.
sentry_secret
=
this
.
_globalSecret
;
}
var
url
=
this
.
_globalEndpoint
;
(
globalOptions
.
transport
||
this
.
_makeRequest
).
call
(
this
,
{
url
:
url
,
auth
:
auth
,
data
:
data
,
options
:
globalOptions
,
onSuccess
:
function
success
()
{
self
.
_triggerEvent
(
'
success
'
,
{
data
:
data
,
src
:
url
});
},
onError
:
function
failure
()
{
self
.
_triggerEvent
(
'
failure
'
,
{
data
:
data
,
src
:
url
});
}
});
},
_makeImageRequest
:
function
(
opts
)
{
// Tack on sentry_data to auth options, which get urlencoded
opts
.
auth
.
sentry_data
=
JSON
.
stringify
(
opts
.
data
);
var
img
=
this
.
_newImage
(),
src
=
opts
.
url
+
'
?
'
+
urlencode
(
opts
.
auth
),
crossOrigin
=
opts
.
options
.
crossOrigin
;
if
(
crossOrigin
||
crossOrigin
===
''
)
{
img
.
crossOrigin
=
crossOrigin
;
}
img
.
onload
=
opts
.
onSuccess
;
img
.
onerror
=
img
.
onabort
=
opts
.
onError
;
img
.
src
=
src
;
},
_makeXhrRequest
:
function
(
opts
)
{
var
request
;
var
url
=
opts
.
url
;
function
handler
()
{
if
(
request
.
status
===
200
)
{
if
(
opts
.
onSuccess
)
{
opts
.
onSuccess
();
}
}
else
if
(
opts
.
onError
)
{
opts
.
onError
();
}
}
request
=
new
XMLHttpRequest
();
if
(
'
withCredentials
'
in
request
)
{
request
.
onreadystatechange
=
function
()
{
if
(
request
.
readyState
!==
4
)
{
return
;
}
handler
();
};
}
else
{
request
=
new
XDomainRequest
();
// xdomainrequest cannot go http -> https (or vice versa),
// so always use protocol relative
url
=
url
.
replace
(
/^https
?
:/
,
''
);
// onreadystatechange not supported by XDomainRequest
request
.
onload
=
handler
;
}
// NOTE: auth is intentionally sent as part of query string (NOT as custom
// HTTP header) so as to avoid preflight CORS requests
request
.
open
(
'
POST
'
,
url
+
'
?
'
+
urlencode
(
opts
.
auth
));
request
.
send
(
JSON
.
stringify
(
opts
.
data
));
},
_makeRequest
:
function
(
opts
)
{
var
hasCORS
=
'
withCredentials
'
in
new
XMLHttpRequest
()
||
typeof
XDomainRequest
!==
'
undefined
'
;
return
(
hasCORS
?
this
.
_makeXhrRequest
:
this
.
_makeImageRequest
)(
opts
);
},
// Note: this is shitty, but I can't figure out how to get
// sinon to stub document.createElement without breaking everything
// so this wrapper is just so I can stub it for tests.
_newImage
:
function
()
{
return
document
.
createElement
(
'
img
'
);
},
_logDebug
:
function
(
level
)
{
if
(
this
.
_originalConsoleMethods
[
level
]
&&
this
.
debug
)
{
// In IE<10 console methods do not have their own 'apply' method
Function
.
prototype
.
apply
.
call
(
this
.
_originalConsoleMethods
[
level
],
this
.
_originalConsole
,
[].
slice
.
call
(
arguments
,
1
)
);
}
},
_mergeContext
:
function
(
key
,
context
)
{
if
(
isUndefined
(
context
))
{
delete
this
.
_globalContext
[
key
];
}
else
{
this
.
_globalContext
[
key
]
=
objectMerge
(
this
.
_globalContext
[
key
]
||
{},
context
);
}
}
};
// Deprecations
Raven
.
prototype
.
setUser
=
Raven
.
prototype
.
setUserContext
;
Raven
.
prototype
.
setReleaseContext
=
Raven
.
prototype
.
setRelease
;
module
.
exports
=
Raven
;
},{
"
1
"
:
1
,
"
4
"
:
4
,
"
5
"
:
5
}],
3
:[
function
(
_dereq_
,
module
,
exports
){
/**
* Enforces a single instance of the Raven client, and the
* main entry point for Raven. If you are a consumer of the
* Raven library, you SHOULD load this file (vs raven.js).
**/
'
use strict
'
;
var
RavenConstructor
=
_dereq_
(
2
);
var
_Raven
=
window
.
Raven
;
var
Raven
=
new
RavenConstructor
();
/*
* Allow multiple versions of Raven to be installed.
* Strip Raven from the global context and returns the instance.
*
* @return {Raven}
*/
Raven
.
noConflict
=
function
()
{
window
.
Raven
=
_Raven
;
return
Raven
;
};
Raven
.
afterLoad
();
module
.
exports
=
Raven
;
},{
"
2
"
:
2
}],
4
:[
function
(
_dereq_
,
module
,
exports
){
'
use strict
'
;
var
objectPrototype
=
Object
.
prototype
;
function
isUndefined
(
what
)
{
return
what
===
void
0
;
}
function
isFunction
(
what
)
{
return
typeof
what
===
'
function
'
;
}
function
isString
(
what
)
{
return
objectPrototype
.
toString
.
call
(
what
)
===
'
[object String]
'
;
}
function
isObject
(
what
)
{
return
typeof
what
===
'
object
'
&&
what
!==
null
;
}
function
isEmptyObject
(
what
)
{
for
(
var
_
in
what
)
return
false
;
// eslint-disable-line guard-for-in, no-unused-vars
return
true
;
}
// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560
// with some tiny modifications
function
isError
(
what
)
{
var
toString
=
objectPrototype
.
toString
.
call
(
what
);
return
isObject
(
what
)
&&
toString
===
'
[object Error]
'
||
toString
===
'
[object Exception]
'
||
// Firefox NS_ERROR_FAILURE Exceptions
what
instanceof
Error
;
}
function
each
(
obj
,
callback
)
{
var
i
,
j
;
if
(
isUndefined
(
obj
.
length
))
{
for
(
i
in
obj
)
{
if
(
hasKey
(
obj
,
i
))
{
callback
.
call
(
null
,
i
,
obj
[
i
]);
}
}
}
else
{
j
=
obj
.
length
;
if
(
j
)
{
for
(
i
=
0
;
i
<
j
;
i
++
)
{
callback
.
call
(
null
,
i
,
obj
[
i
]);
}
}
}
}
function
objectMerge
(
obj1
,
obj2
)
{
if
(
!
obj2
)
{
return
obj1
;
}
each
(
obj2
,
function
(
key
,
value
){
obj1
[
key
]
=
value
;
});
return
obj1
;
}
function
truncate
(
str
,
max
)
{
return
!
max
||
str
.
length
<=
max
?
str
:
str
.
substr
(
0
,
max
)
+
'
\
u2026
'
;
}
/**
* hasKey, a better form of hasOwnProperty
* Example: hasKey(MainHostObject, property) === true/false
*
* @param {Object} host object to check property
* @param {string} key to check
*/
function
hasKey
(
object
,
key
)
{
return
objectPrototype
.
hasOwnProperty
.
call
(
object
,
key
);
}
function
joinRegExp
(
patterns
)
{
// Combine an array of regular expressions and strings into one large regexp
// Be mad.
var
sources
=
[],
i
=
0
,
len
=
patterns
.
length
,
pattern
;
for
(;
i
<
len
;
i
++
)
{
pattern
=
patterns
[
i
];
if
(
isString
(
pattern
))
{
// If it's a string, we need to escape it
// Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
sources
.
push
(
pattern
.
replace
(
/
([
.*+?^=!:${}()|
\[\]\/\\])
/g
,
'
\\
$1
'
));
}
else
if
(
pattern
&&
pattern
.
source
)
{
// If it's a regexp already, we want to extract the source
sources
.
push
(
pattern
.
source
);
}
// Intentionally skip other cases
}
return
new
RegExp
(
sources
.
join
(
'
|
'
),
'
i
'
);
}
function
urlencode
(
o
)
{
var
pairs
=
[];
each
(
o
,
function
(
key
,
value
)
{
pairs
.
push
(
encodeURIComponent
(
key
)
+
'
=
'
+
encodeURIComponent
(
value
));
});
return
pairs
.
join
(
'
&
'
);
}
function
uuid4
()
{
var
crypto
=
window
.
crypto
||
window
.
msCrypto
;
if
(
!
isUndefined
(
crypto
)
&&
crypto
.
getRandomValues
)
{
// Use window.crypto API if available
var
arr
=
new
Uint16Array
(
8
);
crypto
.
getRandomValues
(
arr
);
// set 4 in byte 7
arr
[
3
]
=
arr
[
3
]
&
0xFFF
|
0x4000
;
// set 2 most significant bits of byte 9 to '10'
arr
[
4
]
=
arr
[
4
]
&
0x3FFF
|
0x8000
;
var
pad
=
function
(
num
)
{
var
v
=
num
.
toString
(
16
);
while
(
v
.
length
<
4
)
{
v
=
'
0
'
+
v
;
}
return
v
;
};
return
pad
(
arr
[
0
])
+
pad
(
arr
[
1
])
+
pad
(
arr
[
2
])
+
pad
(
arr
[
3
])
+
pad
(
arr
[
4
])
+
pad
(
arr
[
5
])
+
pad
(
arr
[
6
])
+
pad
(
arr
[
7
]);
}
else
{
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
return
'
xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx
'
.
replace
(
/
[
xy
]
/g
,
function
(
c
)
{
var
r
=
Math
.
random
()
*
16
|
0
,
v
=
c
===
'
x
'
?
r
:
r
&
0x3
|
0x8
;
return
v
.
toString
(
16
);
});
}
}
module
.
exports
=
{
isUndefined
:
isUndefined
,
isFunction
:
isFunction
,
isString
:
isString
,
isObject
:
isObject
,
isEmptyObject
:
isEmptyObject
,
isError
:
isError
,
each
:
each
,
objectMerge
:
objectMerge
,
truncate
:
truncate
,
hasKey
:
hasKey
,
joinRegExp
:
joinRegExp
,
urlencode
:
urlencode
,
uuid4
:
uuid4
};
},{}],
5
:[
function
(
_dereq_
,
module
,
exports
){
'
use strict
'
;
var
utils
=
_dereq_
(
4
);
var
hasKey
=
utils
.
hasKey
;
var
isString
=
utils
.
isString
;
var
isUndefined
=
utils
.
isUndefined
;
/*
TraceKit - Cross brower stack traces - github.com/occ/TraceKit
MIT license
*/
var
TraceKit
=
{
remoteFetching
:
false
,
collectWindowErrors
:
true
,
// 3 lines before, the offending line, 3 lines after
linesOfContext
:
7
,
debug
:
false
};
// global reference to slice
var
_slice
=
[].
slice
;
var
UNKNOWN_FUNCTION
=
'
?
'
;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
var
ERROR_TYPES_RE
=
/^
(?:
Uncaught
)?((?:
Eval|Internal|Range|Reference|Syntax|Type|URI
)
Error
)\:
?(
.*
)
$/
;
function
getLocationHref
()
{
if
(
typeof
document
===
'
undefined
'
)
return
''
;
return
document
.
location
.
href
;
}
/**
* TraceKit.report: cross-browser processing of unhandled exceptions
*
* Syntax:
* TraceKit.report.subscribe(function(stackInfo) { ... })
* TraceKit.report.unsubscribe(function(stackInfo) { ... })
* TraceKit.report(exception)
* try { ...code... } catch(ex) { TraceKit.report(ex); }
*
* Supports:
* - Firefox: full stack trace with line numbers, plus column number
* on top frame; column number is not guaranteed
* - Opera: full stack trace with line and column numbers
* - Chrome: full stack trace with line and column numbers
* - Safari: line and column number for the top frame only; some frames
* may be missing, and column number is not guaranteed
* - IE: line and column number for the top frame only; some frames
* may be missing, and column number is not guaranteed
*
* In theory, TraceKit should work on all of the following versions:
* - IE5.5+ (only 8.0 tested)
* - Firefox 0.9+ (only 3.5+ tested)
* - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
* Exceptions Have Stacktrace to be enabled in opera:config)
* - Safari 3+ (only 4+ tested)
* - Chrome 1+ (only 5+ tested)
* - Konqueror 3.5+ (untested)
*
* Requires TraceKit.computeStackTrace.
*
* Tries to catch all unhandled exceptions and report them to the
* subscribed handlers. Please note that TraceKit.report will rethrow the
* exception. This is REQUIRED in order to get a useful stack trace in IE.
* If the exception does not reach the top of the browser, you will only
* get a stack trace from the point where TraceKit.report was called.
*
* Handlers receive a stackInfo object as described in the
* TraceKit.computeStackTrace docs.
*/
TraceKit
.
report
=
(
function
reportModuleWrapper
()
{
var
handlers
=
[],
lastArgs
=
null
,
lastException
=
null
,
lastExceptionStack
=
null
;
/**
* Add a crash handler.
* @param {Function} handler
*/
function
subscribe
(
handler
)
{
installGlobalHandler
();
handlers
.
push
(
handler
);
}
/**
* Remove a crash handler.
* @param {Function} handler
*/
function
unsubscribe
(
handler
)
{
for
(
var
i
=
handlers
.
length
-
1
;
i
>=
0
;
--
i
)
{
if
(
handlers
[
i
]
===
handler
)
{
handlers
.
splice
(
i
,
1
);
}
}
}
/**
* Remove all crash handlers.
*/
function
unsubscribeAll
()
{
uninstallGlobalHandler
();
handlers
=
[];
}
/**
* Dispatch stack information to all handlers.
* @param {Object.<string, *>} stack
*/
function
notifyHandlers
(
stack
,
isWindowError
)
{
var
exception
=
null
;
if
(
isWindowError
&&
!
TraceKit
.
collectWindowErrors
)
{
return
;
}
for
(
var
i
in
handlers
)
{
if
(
hasKey
(
handlers
,
i
))
{
try
{
handlers
[
i
].
apply
(
null
,
[
stack
].
concat
(
_slice
.
call
(
arguments
,
2
)));
}
catch
(
inner
)
{
exception
=
inner
;
}
}
}
if
(
exception
)
{
throw
exception
;
}
}
var
_oldOnerrorHandler
,
_onErrorHandlerInstalled
;
/**
* Ensures all global unhandled exceptions are recorded.
* Supported by Gecko and IE.
* @param {string} message Error message.
* @param {string} url URL of script that generated the exception.
* @param {(number|string)} lineNo The line number at which the error
* occurred.
* @param {?(number|string)} colNo The column number at which the error
* occurred.
* @param {?Error} ex The actual Error object.
*/
function
traceKitWindowOnError
(
message
,
url
,
lineNo
,
colNo
,
ex
)
{
var
stack
=
null
;
if
(
lastExceptionStack
)
{
TraceKit
.
computeStackTrace
.
augmentStackTraceWithInitialElement
(
lastExceptionStack
,
url
,
lineNo
,
message
);
processLastException
();
}
else
if
(
ex
)
{
// New chrome and blink send along a real error object
// Let's just report that like a normal error.
// See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
stack
=
TraceKit
.
computeStackTrace
(
ex
);
notifyHandlers
(
stack
,
true
);
}
else
{
var
location
=
{
'
url
'
:
url
,
'
line
'
:
lineNo
,
'
column
'
:
colNo
};
location
.
func
=
TraceKit
.
computeStackTrace
.
guessFunctionName
(
location
.
url
,
location
.
line
);
location
.
context
=
TraceKit
.
computeStackTrace
.
gatherContext
(
location
.
url
,
location
.
line
);
var
name
=
undefined
;
var
msg
=
message
;
// must be new var or will modify original `arguments`
var
groups
;
if
(
isString
(
message
))
{
var
groups
=
message
.
match
(
ERROR_TYPES_RE
);
if
(
groups
)
{
name
=
groups
[
1
];
msg
=
groups
[
2
];
}
}
stack
=
{
'
name
'
:
name
,
'
message
'
:
msg
,
'
url
'
:
getLocationHref
(),
'
stack
'
:
[
location
]
};
notifyHandlers
(
stack
,
true
);
}
if
(
_oldOnerrorHandler
)
{
return
_oldOnerrorHandler
.
apply
(
this
,
arguments
);
}
return
false
;
}
function
installGlobalHandler
()
{
if
(
_onErrorHandlerInstalled
)
{
return
;
}
_oldOnerrorHandler
=
window
.
onerror
;
window
.
onerror
=
traceKitWindowOnError
;
_onErrorHandlerInstalled
=
true
;
}
function
uninstallGlobalHandler
()
{
if
(
!
_onErrorHandlerInstalled
)
{
return
;
}
window
.
onerror
=
_oldOnerrorHandler
;
_onErrorHandlerInstalled
=
false
;
_oldOnerrorHandler
=
undefined
;
}
function
processLastException
()
{
var
_lastExceptionStack
=
lastExceptionStack
,
_lastArgs
=
lastArgs
;
lastArgs
=
null
;
lastExceptionStack
=
null
;
lastException
=
null
;
notifyHandlers
.
apply
(
null
,
[
_lastExceptionStack
,
false
].
concat
(
_lastArgs
));
}
/**
* Reports an unhandled Error to TraceKit.
* @param {Error} ex
* @param {?boolean} rethrow If false, do not re-throw the exception.
* Only used for window.onerror to not cause an infinite loop of
* rethrowing.
*/
function
report
(
ex
,
rethrow
)
{
var
args
=
_slice
.
call
(
arguments
,
1
);
if
(
lastExceptionStack
)
{
if
(
lastException
===
ex
)
{
return
;
// already caught by an inner catch block, ignore
}
else
{
processLastException
();
}
}
var
stack
=
TraceKit
.
computeStackTrace
(
ex
);
lastExceptionStack
=
stack
;
lastException
=
ex
;
lastArgs
=
args
;
// If the stack trace is incomplete, wait for 2 seconds for
// slow slow IE to see if onerror occurs or not before reporting
// this exception; otherwise, we will end up with an incomplete
// stack trace
window
.
setTimeout
(
function
()
{
if
(
lastException
===
ex
)
{
processLastException
();
}
},
(
stack
.
incomplete
?
2000
:
0
));
if
(
rethrow
!==
false
)
{
throw
ex
;
// re-throw to propagate to the top level (and cause window.onerror)
}
}
report
.
subscribe
=
subscribe
;
report
.
unsubscribe
=
unsubscribe
;
report
.
uninstall
=
unsubscribeAll
;
return
report
;
}());
/**
* TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
*
* Syntax:
* s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
* Returns:
* s.name - exception name
* s.message - exception message
* s.stack[i].url - JavaScript or HTML file URL
* s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
* s.stack[i].args - arguments passed to the function, if known
* s.stack[i].line - line number, if known
* s.stack[i].column - column number, if known
* s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
*
* Supports:
* - Firefox: full stack trace with line numbers and unreliable column
* number on top frame
* - Opera 10: full stack trace with line and column numbers
* - Opera 9-: full stack trace with line numbers
* - Chrome: full stack trace with line and column numbers
* - Safari: line and column number for the topmost stacktrace element
* only
* - IE: no line numbers whatsoever
*
* Tries to guess names of anonymous functions by looking for assignments
* in the source code. In IE and Safari, we have to guess source file names
* by searching for function bodies inside all page scripts. This will not
* work for scripts that are loaded cross-domain.
* Here be dragons: some function names may be guessed incorrectly, and
* duplicate functions may be mismatched.
*
* TraceKit.computeStackTrace should only be used for tracing purposes.
* Logging of unhandled exceptions should be done with TraceKit.report,
* which builds on top of TraceKit.computeStackTrace and provides better
* IE support by utilizing the window.onerror event to retrieve information
* about the top of the stack.
*
* Note: In IE and Safari, no stack trace is recorded on the Error object,
* so computeStackTrace instead walks its *own* chain of callers.
* This means that:
* * in Safari, some methods may be missing from the stack trace;
* * in IE, the topmost function in the stack trace will always be the
* caller of computeStackTrace.
*
* This is okay for tracing (because you are likely to be calling
* computeStackTrace from the function you want to be the topmost element
* of the stack trace anyway), but not okay for logging unhandled
* exceptions (because your catch block will likely be far away from the
* inner function that actually caused the exception).
*
*/
TraceKit
.
computeStackTrace
=
(
function
computeStackTraceWrapper
()
{
var
sourceCache
=
{};
/**
* Attempts to retrieve source code via XMLHttpRequest, which is used
* to look up anonymous function names.
* @param {string} url URL of source code.
* @return {string} Source contents.
*/
function
loadSource
(
url
)
{
if
(
!
TraceKit
.
remoteFetching
)
{
//Only attempt request if remoteFetching is on.
return
''
;
}
try
{
var
getXHR
=
function
()
{
try
{
return
new
window
.
XMLHttpRequest
();
}
catch
(
e
)
{
// explicitly bubble up the exception if not found
return
new
window
.
ActiveXObject
(
'
Microsoft.XMLHTTP
'
);
}
};
var
request
=
getXHR
();
request
.
open
(
'
GET
'
,
url
,
false
);
request
.
send
(
''
);
return
request
.
responseText
;
}
catch
(
e
)
{
return
''
;
}
}
/**
* Retrieves source code from the source code cache.
* @param {string} url URL of source code.
* @return {Array.<string>} Source contents.
*/
function
getSource
(
url
)
{
if
(
!
isString
(
url
))
return
[];
if
(
!
hasKey
(
sourceCache
,
url
))
{
// URL needs to be able to fetched within the acceptable domain. Otherwise,
// cross-domain errors will be triggered.
var
source
=
''
;
var
domain
=
''
;
try
{
domain
=
document
.
domain
;
}
catch
(
e
)
{}
if
(
url
.
indexOf
(
domain
)
!==
-
1
)
{
source
=
loadSource
(
url
);
}
sourceCache
[
url
]
=
source
?
source
.
split
(
'
\n
'
)
:
[];
}
return
sourceCache
[
url
];
}
/**
* Tries to use an externally loaded copy of source code to determine
* the name of a function by looking at the name of the variable it was
* assigned to, if any.
* @param {string} url URL of source code.
* @param {(string|number)} lineNo Line number in source code.
* @return {string} The function name, if discoverable.
*/
function
guessFunctionName
(
url
,
lineNo
)
{
var
reFunctionArgNames
=
/function
([^
(
]
*
)\(([^
)
]
*
)\)
/
,
reGuessFunction
=
/
[
'"
]?([
0-9A-Za-z$_
]
+
)[
'"
]?\s
*
[
:=
]\s
*
(
function|eval|new Function
)
/
,
line
=
''
,
maxLines
=
10
,
source
=
getSource
(
url
),
m
;
if
(
!
source
.
length
)
{
return
UNKNOWN_FUNCTION
;
}
// Walk backwards from the first line in the function until we find the line which
// matches the pattern above, which is the function definition
for
(
var
i
=
0
;
i
<
maxLines
;
++
i
)
{
line
=
source
[
lineNo
-
i
]
+
line
;
if
(
!
isUndefined
(
line
))
{
if
((
m
=
reGuessFunction
.
exec
(
line
)))
{
return
m
[
1
];
}
else
if
((
m
=
reFunctionArgNames
.
exec
(
line
)))
{
return
m
[
1
];
}
}
}
return
UNKNOWN_FUNCTION
;
}
/**
* Retrieves the surrounding lines from where an exception occurred.
* @param {string} url URL of source code.
* @param {(string|number)} line Line number in source code to centre
* around for context.
* @return {?Array.<string>} Lines of source code.
*/
function
gatherContext
(
url
,
line
)
{
var
source
=
getSource
(
url
);
if
(
!
source
.
length
)
{
return
null
;
}
var
context
=
[],
// linesBefore & linesAfter are inclusive with the offending line.
// if linesOfContext is even, there will be one extra line
// *before* the offending line.
linesBefore
=
Math
.
floor
(
TraceKit
.
linesOfContext
/
2
),
// Add one extra line if linesOfContext is odd
linesAfter
=
linesBefore
+
(
TraceKit
.
linesOfContext
%
2
),
start
=
Math
.
max
(
0
,
line
-
linesBefore
-
1
),
end
=
Math
.
min
(
source
.
length
,
line
+
linesAfter
-
1
);
line
-=
1
;
// convert to 0-based index
for
(
var
i
=
start
;
i
<
end
;
++
i
)
{
if
(
!
isUndefined
(
source
[
i
]))
{
context
.
push
(
source
[
i
]);
}
}
return
context
.
length
>
0
?
context
:
null
;
}
/**
* Escapes special characters, except for whitespace, in a string to be
* used inside a regular expression as a string literal.
* @param {string} text The string.
* @return {string} The escaped string literal.
*/
function
escapeRegExp
(
text
)
{
return
text
.
replace
(
/
[\-\[\]
{}()*+?.,
\\\^
$|#
]
/g
,
'
\\
$&
'
);
}
/**
* Escapes special characters in a string to be used inside a regular
* expression as a string literal. Also ensures that HTML entities will
* be matched the same as their literal friends.
* @param {string} body The string.
* @return {string} The escaped string.
*/
function
escapeCodeAsRegExpForMatchingInsideHTML
(
body
)
{
return
escapeRegExp
(
body
).
replace
(
'
<
'
,
'
(?:<|<)
'
).
replace
(
'
>
'
,
'
(?:>|>)
'
).
replace
(
'
&
'
,
'
(?:&|&)
'
).
replace
(
'
"
'
,
'
(?:"|")
'
).
replace
(
/
\s
+/g
,
'
\\
s+
'
);
}
/**
* Determines where a code fragment occurs in the source code.
* @param {RegExp} re The function definition.
* @param {Array.<string>} urls A list of URLs to search.
* @return {?Object.<string, (string|number)>} An object containing
* the url, line, and column number of the defined function.
*/
function
findSourceInUrls
(
re
,
urls
)
{
var
source
,
m
;
for
(
var
i
=
0
,
j
=
urls
.
length
;
i
<
j
;
++
i
)
{
// console.log('searching', urls[i]);
if
((
source
=
getSource
(
urls
[
i
])).
length
)
{
source
=
source
.
join
(
'
\n
'
);
if
((
m
=
re
.
exec
(
source
)))
{
// console.log('Found function in ' + urls[i]);
return
{
'
url
'
:
urls
[
i
],
'
line
'
:
source
.
substring
(
0
,
m
.
index
).
split
(
'
\n
'
).
length
,
'
column
'
:
m
.
index
-
source
.
lastIndexOf
(
'
\n
'
,
m
.
index
)
-
1
};
}
}
}
// console.log('no match');
return
null
;
}
/**
* Determines at which column a code fragment occurs on a line of the
* source code.
* @param {string} fragment The code fragment.
* @param {string} url The URL to search.
* @param {(string|number)} line The line number to examine.
* @return {?number} The column number.
*/
function
findSourceInLine
(
fragment
,
url
,
line
)
{
var
source
=
getSource
(
url
),
re
=
new
RegExp
(
'
\\
b
'
+
escapeRegExp
(
fragment
)
+
'
\\
b
'
),
m
;
line
-=
1
;
if
(
source
&&
source
.
length
>
line
&&
(
m
=
re
.
exec
(
source
[
line
])))
{
return
m
.
index
;
}
return
null
;
}
/**
* Determines where a function was defined within the source code.
* @param {(Function|string)} func A function reference or serialized
* function definition.
* @return {?Object.<string, (string|number)>} An object containing
* the url, line, and column number of the defined function.
*/
function
findSourceByFunctionBody
(
func
)
{
if
(
typeof
document
===
'
undefined
'
)
return
;
var
urls
=
[
window
.
location
.
href
],
scripts
=
document
.
getElementsByTagName
(
'
script
'
),
body
,
code
=
''
+
func
,
codeRE
=
/^function
(?:\s
+
([\w
$
]
+
))?\s
*
\(([\w\s
,
]
*
)\)\s
*
\{\s
*
(\S[\s\S]
*
\S)\s
*
\}\s
*$/
,
eventRE
=
/^function on
([\w
$
]
+
)\s
*
\(
event
\)\s
*
\{\s
*
(\S[\s\S]
*
\S)\s
*
\}\s
*$/
,
re
,
parts
,
result
;
for
(
var
i
=
0
;
i
<
scripts
.
length
;
++
i
)
{
var
script
=
scripts
[
i
];
if
(
script
.
src
)
{
urls
.
push
(
script
.
src
);
}
}
if
(
!
(
parts
=
codeRE
.
exec
(
code
)))
{
re
=
new
RegExp
(
escapeRegExp
(
code
).
replace
(
/
\s
+/g
,
'
\\
s+
'
));
}
// not sure if this is really necessary, but I don’t have a test
// corpus large enough to confirm that and it was in the original.
else
{
var
name
=
parts
[
1
]
?
'
\\
s+
'
+
parts
[
1
]
:
''
,
args
=
parts
[
2
].
split
(
'
,
'
).
join
(
'
\\
s*,
\\
s*
'
);
body
=
escapeRegExp
(
parts
[
3
]).
replace
(
/;$/
,
'
;?
'
);
// semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
re
=
new
RegExp
(
'
function
'
+
name
+
'
\\
s*
\\
(
\\
s*
'
+
args
+
'
\\
s*
\\
)
\\
s*{
\\
s*
'
+
body
+
'
\\
s*}
'
);
}
// look for a normal function definition
if
((
result
=
findSourceInUrls
(
re
,
urls
)))
{
return
result
;
}
// look for an old-school event handler function
if
((
parts
=
eventRE
.
exec
(
code
)))
{
var
event
=
parts
[
1
];
body
=
escapeCodeAsRegExpForMatchingInsideHTML
(
parts
[
2
]);
// look for a function defined in HTML as an onXXX handler
re
=
new
RegExp
(
'
on
'
+
event
+
'
=[
\\\'
"]
\\
s*
'
+
body
+
'
\\
s*[
\\\'
"]
'
,
'
i
'
);
if
((
result
=
findSourceInUrls
(
re
,
urls
[
0
])))
{
return
result
;
}
// look for ???
re
=
new
RegExp
(
body
);
if
((
result
=
findSourceInUrls
(
re
,
urls
)))
{
return
result
;
}
}
return
null
;
}
// Contents of Exception in various browsers.
//
// SAFARI:
// ex.message = Can't find variable: qq
// ex.line = 59
// ex.sourceId = 580238192
// ex.sourceURL = http://...
// ex.expressionBeginOffset = 96
// ex.expressionCaretOffset = 98
// ex.expressionEndOffset = 98
// ex.name = ReferenceError
//
// FIREFOX:
// ex.message = qq is not defined
// ex.fileName = http://...
// ex.lineNumber = 59
// ex.columnNumber = 69
// ex.stack = ...stack trace... (see the example below)
// ex.name = ReferenceError
//
// CHROME:
// ex.message = qq is not defined
// ex.name = ReferenceError
// ex.type = not_defined
// ex.arguments = ['aa']
// ex.stack = ...stack trace...
//
// INTERNET EXPLORER:
// ex.message = ...
// ex.name = ReferenceError
//
// OPERA:
// ex.message = ...message... (see the example below)
// ex.name = ReferenceError
// ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
// ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
/**
* Computes stack trace information from the stack property.
* Chrome and Gecko use this property.
* @param {Error} ex
* @return {?Object.<string, *>} Stack trace information.
*/
function
computeStackTraceFromStackProp
(
ex
)
{
if
(
isUndefined
(
ex
.
stack
)
||
!
ex
.
stack
)
return
;
var
chrome
=
/^
\s
*at
(
.*
?)
?\(((?:
file|https
?
|blob|chrome-extension|native|eval|<anonymous>
)
.*
?)(?:
:
(\d
+
))?(?:
:
(\d
+
))?\)?\s
*$/i
,
gecko
=
/^
\s
*
(
.*
?)(?:\((
.*
?)\))?(?:
^|@
)((?:
file|https
?
|blob|chrome|
\[
native
)
.*
?)(?:
:
(\d
+
))?(?:
:
(\d
+
))?\s
*$/i
,
winjs
=
/^
\s
*at
(?:((?:\[
object object
\])?
.+
)
)?\(?((?:
ms-appx|https
?
|blob
)
:.*
?)
:
(\d
+
)(?:
:
(\d
+
))?\)?\s
*$/i
,
lines
=
ex
.
stack
.
split
(
'
\n
'
),
stack
=
[],
parts
,
element
,
reference
=
/^
(
.*
)
is undefined$/
.
exec
(
ex
.
message
);
for
(
var
i
=
0
,
j
=
lines
.
length
;
i
<
j
;
++
i
)
{
if
((
parts
=
chrome
.
exec
(
lines
[
i
])))
{
var
isNative
=
parts
[
2
]
&&
parts
[
2
].
indexOf
(
'
native
'
)
!==
-
1
;
element
=
{
'
url
'
:
!
isNative
?
parts
[
2
]
:
null
,
'
func
'
:
parts
[
1
]
||
UNKNOWN_FUNCTION
,
'
args
'
:
isNative
?
[
parts
[
2
]]
:
[],
'
line
'
:
parts
[
3
]
?
+
parts
[
3
]
:
null
,
'
column
'
:
parts
[
4
]
?
+
parts
[
4
]
:
null
};
}
else
if
(
parts
=
winjs
.
exec
(
lines
[
i
])
)
{
element
=
{
'
url
'
:
parts
[
2
],
'
func
'
:
parts
[
1
]
||
UNKNOWN_FUNCTION
,
'
args
'
:
[],
'
line
'
:
+
parts
[
3
],
'
column
'
:
parts
[
4
]
?
+
parts
[
4
]
:
null
};
}
else
if
((
parts
=
gecko
.
exec
(
lines
[
i
])))
{
element
=
{
'
url
'
:
parts
[
3
],
'
func
'
:
parts
[
1
]
||
UNKNOWN_FUNCTION
,
'
args
'
:
parts
[
2
]
?
parts
[
2
].
split
(
'
,
'
)
:
[],
'
line
'
:
parts
[
4
]
?
+
parts
[
4
]
:
null
,
'
column
'
:
parts
[
5
]
?
+
parts
[
5
]
:
null
};
}
else
{
continue
;
}
if
(
!
element
.
func
&&
element
.
line
)
{
element
.
func
=
guessFunctionName
(
element
.
url
,
element
.
line
);
}
if
(
element
.
line
)
{
element
.
context
=
gatherContext
(
element
.
url
,
element
.
line
);
}
stack
.
push
(
element
);
}
if
(
!
stack
.
length
)
{
return
null
;
}
if
(
stack
[
0
].
line
&&
!
stack
[
0
].
column
&&
reference
)
{
stack
[
0
].
column
=
findSourceInLine
(
reference
[
1
],
stack
[
0
].
url
,
stack
[
0
].
line
);
}
else
if
(
!
stack
[
0
].
column
&&
!
isUndefined
(
ex
.
columnNumber
))
{
// FireFox uses this awesome columnNumber property for its top frame
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
// so adding 1
stack
[
0
].
column
=
ex
.
columnNumber
+
1
;
}
return
{
'
name
'
:
ex
.
name
,
'
message
'
:
ex
.
message
,
'
url
'
:
getLocationHref
(),
'
stack
'
:
stack
};
}
/**
* Computes stack trace information from the stacktrace property.
* Opera 10 uses this property.
* @param {Error} ex
* @return {?Object.<string, *>} Stack trace information.
*/
function
computeStackTraceFromStacktraceProp
(
ex
)
{
// Access and store the stacktrace property before doing ANYTHING
// else to it because Opera is not very good at providing it
// reliably in other circumstances.
var
stacktrace
=
ex
.
stacktrace
;
if
(
isUndefined
(
ex
.
stacktrace
)
||
!
ex
.
stacktrace
)
return
;
var
opera10Regex
=
/ line
(\d
+
)
.*script
(?:
in
)?(\S
+
)(?:
: in function
(\S
+
))?
$/i
,
opera11Regex
=
/ line
(\d
+
)
, column
(\d
+
)\s
*
(?:
in
(?:
<anonymous function:
([^
>
]
+
)
>|
([^\)]
+
))\((
.*
)\))?
in
(
.*
)
:
\s
*$/i
,
lines
=
stacktrace
.
split
(
'
\n
'
),
stack
=
[],
parts
;
for
(
var
line
=
0
;
line
<
lines
.
length
;
line
+=
2
)
{
var
element
=
null
;
if
((
parts
=
opera10Regex
.
exec
(
lines
[
line
])))
{
element
=
{
'
url
'
:
parts
[
2
],
'
line
'
:
+
parts
[
1
],
'
column
'
:
null
,
'
func
'
:
parts
[
3
],
'
args
'
:[]
};
}
else
if
((
parts
=
opera11Regex
.
exec
(
lines
[
line
])))
{
element
=
{
'
url
'
:
parts
[
6
],
'
line
'
:
+
parts
[
1
],
'
column
'
:
+
parts
[
2
],
'
func
'
:
parts
[
3
]
||
parts
[
4
],
'
args
'
:
parts
[
5
]
?
parts
[
5
].
split
(
'
,
'
)
:
[]
};
}
if
(
element
)
{
if
(
!
element
.
func
&&
element
.
line
)
{
element
.
func
=
guessFunctionName
(
element
.
url
,
element
.
line
);
}
if
(
element
.
line
)
{
try
{
element
.
context
=
gatherContext
(
element
.
url
,
element
.
line
);
}
catch
(
exc
)
{}
}
if
(
!
element
.
context
)
{
element
.
context
=
[
lines
[
line
+
1
]];
}
stack
.
push
(
element
);
}
}
if
(
!
stack
.
length
)
{
return
null
;
}
return
{
'
name
'
:
ex
.
name
,
'
message
'
:
ex
.
message
,
'
url
'
:
getLocationHref
(),
'
stack
'
:
stack
};
}
/**
* NOT TESTED.
* Computes stack trace information from an error message that includes
* the stack trace.
* Opera 9 and earlier use this method if the option to show stack
* traces is turned on in opera:config.
* @param {Error} ex
* @return {?Object.<string, *>} Stack information.
*/
function
computeStackTraceFromOperaMultiLineMessage
(
ex
)
{
// Opera includes a stack trace into the exception message. An example is:
//
// Statement on line 3: Undefined variable: undefinedFunc
// Backtrace:
// Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
// undefinedFunc(a);
// Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
// zzz(x, y, z);
// Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
// yyy(a, a, a);
// Line 1 of function script
// try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
// ...
var
lines
=
ex
.
message
.
split
(
'
\n
'
);
if
(
lines
.
length
<
4
)
{
return
null
;
}
var
lineRE1
=
/^
\s
*Line
(\d
+
)
of linked script
((?:
file|https
?
|blob
)\S
+
)(?:
: in function
(\S
+
))?\s
*$/i
,
lineRE2
=
/^
\s
*Line
(\d
+
)
of inline#
(\d
+
)
script in
((?:
file|https
?
|blob
)\S
+
)(?:
: in function
(\S
+
))?\s
*$/i
,
lineRE3
=
/^
\s
*Line
(\d
+
)
of function script
\s
*$/i
,
stack
=
[],
scripts
=
document
.
getElementsByTagName
(
'
script
'
),
inlineScriptBlocks
=
[],
parts
;
for
(
var
s
in
scripts
)
{
if
(
hasKey
(
scripts
,
s
)
&&
!
scripts
[
s
].
src
)
{
inlineScriptBlocks
.
push
(
scripts
[
s
]);
}
}
for
(
var
line
=
2
;
line
<
lines
.
length
;
line
+=
2
)
{
var
item
=
null
;
if
((
parts
=
lineRE1
.
exec
(
lines
[
line
])))
{
item
=
{
'
url
'
:
parts
[
2
],
'
func
'
:
parts
[
3
],
'
args
'
:
[],
'
line
'
:
+
parts
[
1
],
'
column
'
:
null
};
}
else
if
((
parts
=
lineRE2
.
exec
(
lines
[
line
])))
{
item
=
{
'
url
'
:
parts
[
3
],
'
func
'
:
parts
[
4
],
'
args
'
:
[],
'
line
'
:
+
parts
[
1
],
'
column
'
:
null
// TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.
};
var
relativeLine
=
(
+
parts
[
1
]);
// relative to the start of the <SCRIPT> block
var
script
=
inlineScriptBlocks
[
parts
[
2
]
-
1
];
if
(
script
)
{
var
source
=
getSource
(
item
.
url
);
if
(
source
)
{
source
=
source
.
join
(
'
\n
'
);
var
pos
=
source
.
indexOf
(
script
.
innerText
);
if
(
pos
>=
0
)
{
item
.
line
=
relativeLine
+
source
.
substring
(
0
,
pos
).
split
(
'
\n
'
).
length
;
}
}
}
}
else
if
((
parts
=
lineRE3
.
exec
(
lines
[
line
])))
{
var
url
=
window
.
location
.
href
.
replace
(
/#.*$/
,
''
);
var
re
=
new
RegExp
(
escapeCodeAsRegExpForMatchingInsideHTML
(
lines
[
line
+
1
]));
var
src
=
findSourceInUrls
(
re
,
[
url
]);
item
=
{
'
url
'
:
url
,
'
func
'
:
''
,
'
args
'
:
[],
'
line
'
:
src
?
src
.
line
:
parts
[
1
],
'
column
'
:
null
};
}
if
(
item
)
{
if
(
!
item
.
func
)
{
item
.
func
=
guessFunctionName
(
item
.
url
,
item
.
line
);
}
var
context
=
gatherContext
(
item
.
url
,
item
.
line
);
var
midline
=
(
context
?
context
[
Math
.
floor
(
context
.
length
/
2
)]
:
null
);
if
(
context
&&
midline
.
replace
(
/^
\s
*/
,
''
)
===
lines
[
line
+
1
].
replace
(
/^
\s
*/
,
''
))
{
item
.
context
=
context
;
}
else
{
// if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
item
.
context
=
[
lines
[
line
+
1
]];
}
stack
.
push
(
item
);
}
}
if
(
!
stack
.
length
)
{
return
null
;
// could not parse multiline exception message as Opera stack trace
}
return
{
'
name
'
:
ex
.
name
,
'
message
'
:
lines
[
0
],
'
url
'
:
getLocationHref
(),
'
stack
'
:
stack
};
}
/**
* Adds information about the first frame to incomplete stack traces.
* Safari and IE require this to get complete data on the first frame.
* @param {Object.<string, *>} stackInfo Stack trace information from
* one of the compute* methods.
* @param {string} url The URL of the script that caused an error.
* @param {(number|string)} lineNo The line number of the script that
* caused an error.
* @param {string=} message The error generated by the browser, which
* hopefully contains the name of the object that caused the error.
* @return {boolean} Whether or not the stack information was
* augmented.
*/
function
augmentStackTraceWithInitialElement
(
stackInfo
,
url
,
lineNo
,
message
)
{
var
initial
=
{
'
url
'
:
url
,
'
line
'
:
lineNo
};
if
(
initial
.
url
&&
initial
.
line
)
{
stackInfo
.
incomplete
=
false
;
if
(
!
initial
.
func
)
{
initial
.
func
=
guessFunctionName
(
initial
.
url
,
initial
.
line
);
}
if
(
!
initial
.
context
)
{
initial
.
context
=
gatherContext
(
initial
.
url
,
initial
.
line
);
}
var
reference
=
/ '
([^
'
]
+
)
' /
.
exec
(
message
);
if
(
reference
)
{
initial
.
column
=
findSourceInLine
(
reference
[
1
],
initial
.
url
,
initial
.
line
);
}
if
(
stackInfo
.
stack
.
length
>
0
)
{
if
(
stackInfo
.
stack
[
0
].
url
===
initial
.
url
)
{
if
(
stackInfo
.
stack
[
0
].
line
===
initial
.
line
)
{
return
false
;
// already in stack trace
}
else
if
(
!
stackInfo
.
stack
[
0
].
line
&&
stackInfo
.
stack
[
0
].
func
===
initial
.
func
)
{
stackInfo
.
stack
[
0
].
line
=
initial
.
line
;
stackInfo
.
stack
[
0
].
context
=
initial
.
context
;
return
false
;
}
}
}
stackInfo
.
stack
.
unshift
(
initial
);
stackInfo
.
partial
=
true
;
return
true
;
}
else
{
stackInfo
.
incomplete
=
true
;
}
return
false
;
}
/**
* Computes stack trace information by walking the arguments.caller
* chain at the time the exception occurred. This will cause earlier
* frames to be missed but is the only way to get any stack trace in
* Safari and IE. The top frame is restored by
* {@link augmentStackTraceWithInitialElement}.
* @param {Error} ex
* @return {?Object.<string, *>} Stack trace information.
*/
function
computeStackTraceByWalkingCallerChain
(
ex
,
depth
)
{
var
functionName
=
/function
\s
+
([
_$a-zA-Z
\x
A0-
\u
FFFF
][
_$a-zA-Z0-9
\x
A0-
\u
FFFF
]
*
)?\s
*
\(
/i
,
stack
=
[],
funcs
=
{},
recursion
=
false
,
parts
,
item
,
source
;
for
(
var
curr
=
computeStackTraceByWalkingCallerChain
.
caller
;
curr
&&
!
recursion
;
curr
=
curr
.
caller
)
{
if
(
curr
===
computeStackTrace
||
curr
===
TraceKit
.
report
)
{
// console.log('skipping internal function');
continue
;
}
item
=
{
'
url
'
:
null
,
'
func
'
:
UNKNOWN_FUNCTION
,
'
line
'
:
null
,
'
column
'
:
null
};
if
(
curr
.
name
)
{
item
.
func
=
curr
.
name
;
}
else
if
((
parts
=
functionName
.
exec
(
curr
.
toString
())))
{
item
.
func
=
parts
[
1
];
}
if
(
typeof
item
.
func
===
'
undefined
'
)
{
try
{
item
.
func
=
parts
.
input
.
substring
(
0
,
parts
.
input
.
indexOf
(
'
{
'
));
}
catch
(
e
)
{
}
}
if
((
source
=
findSourceByFunctionBody
(
curr
)))
{
item
.
url
=
source
.
url
;
item
.
line
=
source
.
line
;
if
(
item
.
func
===
UNKNOWN_FUNCTION
)
{
item
.
func
=
guessFunctionName
(
item
.
url
,
item
.
line
);
}
var
reference
=
/ '
([^
'
]
+
)
' /
.
exec
(
ex
.
message
||
ex
.
description
);
if
(
reference
)
{
item
.
column
=
findSourceInLine
(
reference
[
1
],
source
.
url
,
source
.
line
);
}
}
if
(
funcs
[
''
+
curr
])
{
recursion
=
true
;
}
else
{
funcs
[
''
+
curr
]
=
true
;
}
stack
.
push
(
item
);
}
if
(
depth
)
{
// console.log('depth is ' + depth);
// console.log('stack is ' + stack.length);
stack
.
splice
(
0
,
depth
);
}
var
result
=
{
'
name
'
:
ex
.
name
,
'
message
'
:
ex
.
message
,
'
url
'
:
getLocationHref
(),
'
stack
'
:
stack
};
augmentStackTraceWithInitialElement
(
result
,
ex
.
sourceURL
||
ex
.
fileName
,
ex
.
line
||
ex
.
lineNumber
,
ex
.
message
||
ex
.
description
);
return
result
;
}
/**
* Computes a stack trace for an exception.
* @param {Error} ex
* @param {(string|number)=} depth
*/
function
computeStackTrace
(
ex
,
depth
)
{
var
stack
=
null
;
depth
=
(
depth
==
null
?
0
:
+
depth
);
try
{
// This must be tried first because Opera 10 *destroys*
// its stacktrace property if you try to access the stack
// property first!!
stack
=
computeStackTraceFromStacktraceProp
(
ex
);
if
(
stack
)
{
return
stack
;
}
}
catch
(
e
)
{
if
(
TraceKit
.
debug
)
{
throw
e
;
}
}
try
{
stack
=
computeStackTraceFromStackProp
(
ex
);
if
(
stack
)
{
return
stack
;
}
}
catch
(
e
)
{
if
(
TraceKit
.
debug
)
{
throw
e
;
}
}
try
{
stack
=
computeStackTraceFromOperaMultiLineMessage
(
ex
);
if
(
stack
)
{
return
stack
;
}
}
catch
(
e
)
{
if
(
TraceKit
.
debug
)
{
throw
e
;
}
}
try
{
stack
=
computeStackTraceByWalkingCallerChain
(
ex
,
depth
+
1
);
if
(
stack
)
{
return
stack
;
}
}
catch
(
e
)
{
if
(
TraceKit
.
debug
)
{
throw
e
;
}
}
return
{
'
name
'
:
ex
.
name
,
'
message
'
:
ex
.
message
,
'
url
'
:
getLocationHref
()
};
}
computeStackTrace
.
augmentStackTraceWithInitialElement
=
augmentStackTraceWithInitialElement
;
computeStackTrace
.
computeStackTraceFromStackProp
=
computeStackTraceFromStackProp
;
computeStackTrace
.
guessFunctionName
=
guessFunctionName
;
computeStackTrace
.
gatherContext
=
gatherContext
;
return
computeStackTrace
;
}());
module
.
exports
=
TraceKit
;
},{
"
4
"
:
4
}]},{},[
3
])(
3
)
});
\ No newline at end of file
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