Commit 27e0c772 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into ci-commit-as-pipeline

# Conflicts:
#	db/schema.rb
parents ee2dc062 0063194a
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased) v 8.7.0 (unreleased)
- 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
- Add support for environment variables on a job level in CI configuration file
- SQL query counts are now tracked per transaction
- The Projects::HousekeepingService class has extra instrumentation - The Projects::HousekeepingService class has extra instrumentation
- All service classes (those residing in app/services) are now instrumented - All service classes (those residing in app/services) are now instrumented
- Developers can now add custom tags to transactions - Developers can now add custom tags to transactions
...@@ -9,18 +14,21 @@ v 8.7.0 (unreleased) ...@@ -9,18 +14,21 @@ v 8.7.0 (unreleased)
- Project switcher uses new dropdown styling - Project switcher uses new dropdown styling
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
- Restrict user profiles when public visibility level is restricted.
- All images in discussions and wikis now link to their source files !3464 (Connor Shea). - All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Add setting for customizing the list of trusted proxies !3524 - Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group - Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389 - Improved Markdown rendering performance !3389
- Make shared runners text in box configurable
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings - Expose project badges in project settings
- Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717
- Preserve time notes/comments have been updated at when moving issue - Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu) - Make HTTP(s) label consistent on clone bar (Stan Hu)
- Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński)
- Expose label description in API (Mariusz Jachimowicz) - Expose label description in API (Mariusz Jachimowicz)
- API: Ability to update a group (Robert Schilling) - API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling) - API: Ability to move issues (Robert Schilling)
...@@ -45,6 +53,7 @@ v 8.7.0 (unreleased) ...@@ -45,6 +53,7 @@ v 8.7.0 (unreleased)
- Use rugged to change HEAD in Project#change_head (P.S.V.R) - Use rugged to change HEAD in Project#change_head (P.S.V.R)
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling) - API: Fix milestone filtering by `iid` (Robert Schilling)
- Make before_script and after_script overridable on per-job (Kamil Trzciński)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups - Better errors handling when creating milestones inside groups
...@@ -57,6 +66,7 @@ v 8.7.0 (unreleased) ...@@ -57,6 +66,7 @@ v 8.7.0 (unreleased)
- Decouple membership and notifications - Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu)
- API: Ability to retrieve a single tag (Robert Schilling) - API: Ability to retrieve a single tag (Robert Schilling)
- While signing up, don't persist the user password across form redisplays
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Fix admin/projects when using visibility levels on search (PotHix) - Fix admin/projects when using visibility levels on search (PotHix)
...@@ -74,13 +84,29 @@ v 8.7.0 (unreleased) ...@@ -74,13 +84,29 @@ v 8.7.0 (unreleased)
- Diffs load at the correct point when linking from from number - Diffs load at the correct point when linking from from number
- Selected diff rows highlight - Selected diff rows highlight
- Fix emoji categories in the emoji picker - Fix emoji categories in the emoji picker
- API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling)
- Add encrypted credentials for imported projects and migrate old ones - Add encrypted credentials for imported projects and migrate old ones
- Author and participants are displayed first on users autocompletion - Author and participants are displayed first on users autocompletion
- Show number sign on external issue reference text (Florent Baldino)
- Updated print style for issues
- Use GitHub Issue/PR number as iid to keep references
- Import GitHub labels
- Import GitHub milestones
- Fix emoji catgories in the emoji picker
- Execute system web hooks on push to the project
- Allow enable/disable push events for system hooks
v 8.6.7 (unreleased)
- Fix vulnerability that made it possible to enumerate private projects belonging to group
v 8.6.6 v 8.6.6
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
- Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
- Fix revoking of authorized OAuth applications (Connor Shea). !3690 - Fix revoking of authorized OAuth applications (Connor Shea). !3690
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Project switcher uses new dropdown styling
- Issuable header is consistent between issues and merge requests
- Improved spacing in issuable header on mobile
v 8.6.5 v 8.6.5
- Fix importing from GitHub Enterprise. !3529 - Fix importing from GitHub Enterprise. !3529
......
...@@ -315,7 +315,7 @@ end ...@@ -315,7 +315,7 @@ end
gem "newrelic_rpm", '~> 3.14' gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 3.8.0' gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.6.1" gem "mail_room", "~> 0.6.1"
......
...@@ -485,8 +485,8 @@ GEM ...@@ -485,8 +485,8 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (~> 1.2) rack (~> 1.2)
octokit (3.8.0) octokit (4.3.0)
sawyer (~> 0.6.0, >= 0.5.3) sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
...@@ -712,8 +712,8 @@ GEM ...@@ -712,8 +712,8 @@ GEM
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3) tilt (>= 1.1, < 3)
sawyer (0.6.0) sawyer (0.7.0)
addressable (~> 2.3.5) addressable (>= 2.3.5, < 2.5)
faraday (~> 0.8, < 0.10) faraday (~> 0.8, < 0.10)
scss_lint (0.47.1) scss_lint (0.47.1)
rake (>= 0.9, < 11) rake (>= 0.9, < 11)
...@@ -968,7 +968,7 @@ DEPENDENCIES ...@@ -968,7 +968,7 @@ DEPENDENCIES
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 4.3.0)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
......
...@@ -174,7 +174,7 @@ $ -> ...@@ -174,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', -> $('.trigger-submit').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false) gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
# Flash # Flash
if (flash = $(".flash-container")).length > 0 if (flash = $(".flash-container")).length > 0
......
...@@ -61,6 +61,7 @@ class @DropzoneInput ...@@ -61,6 +61,7 @@ class @DropzoneInput
return return
drop: -> drop: ->
$mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus() form_textarea.focus()
return return
......
...@@ -32,10 +32,8 @@ class GitLabDropdownFilter ...@@ -32,10 +32,8 @@ class GitLabDropdownFilter
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS $inputContainer.removeClass HAS_VALUE_CLASS
if keyCode is 13 and @input.val() isnt "" if keyCode is 13
if @options.enterCallback return false
@options.enterCallback()
return
clearTimeout timeout clearTimeout timeout
timeout = setTimeout => timeout = setTimeout =>
...@@ -132,7 +130,6 @@ class GitLabDropdown ...@@ -132,7 +130,6 @@ class GitLabDropdown
@filterInput = @getElement(FILTER_INPUT) @filterInput = @getElement(FILTER_INPUT)
@highlight = false @highlight = false
@filterInputBlur = true @filterInputBlur = true
@enterCallback = true
} = @options } = @options
self = @ self = @
...@@ -157,6 +154,9 @@ class GitLabDropdown ...@@ -157,6 +154,9 @@ class GitLabDropdown
@fullData = data @fullData = data
@parseData @fullData @parseData @fullData
if @options.filterable
@filterInput.trigger 'keyup'
} }
# Init filterable # Init filterable
...@@ -178,9 +178,6 @@ class GitLabDropdown ...@@ -178,9 +178,6 @@ class GitLabDropdown
callback: (data) => callback: (data) =>
currentIndex = -1 currentIndex = -1
@parseData data @parseData data
enterCallback: =>
if @enterCallback
@selectRowAtIndex 0
# Event listeners # Event listeners
......
...@@ -4,18 +4,33 @@ class @ImporterStatus ...@@ -4,18 +4,33 @@ class @ImporterStatus
this.setAutoUpdate() this.setAutoUpdate()
initStatusPage: -> initStatusPage: ->
$(".js-add-to-import").click (event) => $('.js-add-to-import')
.off 'click'
.on 'click', (e) =>
new_namespace = null new_namespace = null
tr = $(event.currentTarget).closest("tr") $btn = $(e.currentTarget)
id = tr.attr("id").replace("repo_", "") $tr = $btn.closest('tr')
if tr.find(".import-target input").length > 0 id = $tr.attr('id').replace('repo_', '')
new_namespace = tr.find(".import-target input").prop("value") if $tr.find('.import-target input').length > 0
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) new_namespace = $tr.find('.import-target input').prop('value')
$tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
$btn
.disable()
.addClass 'is-loading'
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
$(".js-import-all").click (event) => $('.js-import-all')
$(".js-add-to-import").each -> .off 'click'
$(this).click() .on 'click', (e) ->
$btn = $(@)
$btn
.disable()
.addClass 'is-loading'
$('.js-add-to-import').each ->
$(this).trigger('click')
setAutoUpdate: -> setAutoUpdate: ->
setInterval (=> setInterval (=>
......
...@@ -183,6 +183,7 @@ class @MergeRequestTabs ...@@ -183,6 +183,7 @@ class @MergeRequestTabs
else else
$diffLine = $('td', $diffLine) $diffLine = $('td', $diffLine)
if $diffLine.length
$diffLine.addClass 'hll' $diffLine.addClass 'hll'
diffLineTop = $diffLine.offset().top diffLineTop = $diffLine.offset().top
navBarHeight = $('.navbar-gitlab').outerHeight() navBarHeight = $('.navbar-gitlab').outerHeight()
......
...@@ -75,6 +75,9 @@ class @Notes ...@@ -75,6 +75,9 @@ class @Notes
# when issue status changes, we need to refresh data # when issue status changes, we need to refresh data
$(document).on "issuable:change", @refresh $(document).on "issuable:change", @refresh
# when a key is clicked on the notes
$(document).on "keydown", ".js-note-text", @keydownNoteText
cleanBinding: -> cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
...@@ -92,10 +95,19 @@ class @Notes ...@@ -92,10 +95,19 @@ class @Notes
$(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close" $(document).off "click", ".js-note-target-close"
$(document).off "click", ".js-note-discard" $(document).off "click", ".js-note-discard"
$(document).off "keydown", ".js-note-text"
$('.note .js-task-list-container').taskList('disable') $('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container' $(document).off 'tasklist:changed', '.note .js-task-list-container'
keydownNoteText: (e) ->
$this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote])
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
...@@ -343,7 +355,7 @@ class @Notes ...@@ -343,7 +355,7 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels if the user cancels
### ###
showEditForm: (e) -> showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.addClass "is-editting" note.addClass "is-editting"
...@@ -354,9 +366,27 @@ class @Notes ...@@ -354,9 +366,27 @@ class @Notes
# Show the attachment delete link # Show the attachment delete link
note.find(".js-note-attachment-delete").show() note.find(".js-note-attachment-delete").show()
new GLForm form done = ($noteText) ->
# Neat little trick to put the cursor at the end
noteTextVal = $noteText.val()
$noteText.val('').val(noteTextVal);
form.find(".js-note-text").focus() new GLForm form
if scrollTo? and myLastNote?
# scroll to the bottom
# so the open of the last element doesn't make a jump
$('html, body').scrollTop($(document).height());
$('html, body').animate({
scrollTop: myLastNote.offset().top - 150
}, 500, ->
$noteText = form.find(".js-note-text")
$noteText.focus()
done($noteText)
);
else
$noteText = form.find('.js-note-text')
$noteText.focus()
done($noteText)
### ###
Called in response to clicking the edit note link Called in response to clicking the edit note link
......
...@@ -45,9 +45,10 @@ class @Profile ...@@ -45,9 +45,10 @@ class @Profile
saveForm: -> saveForm: ->
self = @ self = @
formData = new FormData(@form[0]) formData = new FormData(@form[0])
formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
avatarBlob = @avatarGlCrop.getBlob()
formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
$.ajax $.ajax
url: @form.attr('action') url: @form.attr('action')
......
...@@ -71,7 +71,7 @@ header { ...@@ -71,7 +71,7 @@ header {
.header-content { .header-content {
position: relative; position: relative;
height: $header-height; height: $header-height;
padding-right: 20px; padding-right: 40px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
...@@ -122,6 +122,10 @@ header { ...@@ -122,6 +122,10 @@ header {
} }
} }
.project-item-select-holder {
display: inline;
}
.impersonation i { .impersonation i {
color: $red-normal; color: $red-normal;
} }
......
...@@ -70,13 +70,6 @@ ...@@ -70,13 +70,6 @@
display: none; display: none;
} }
.issue-details {
.creator,
.page-title .btn-close {
display: none;
}
}
%ul.notes .note-role, .note-actions { %ul.notes .note-role, .note-actions {
display: none; display: none;
} }
......
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
.diff-file { .diff-file {
border: 1px solid $border-color; border: 1px solid $border-color;
border-bottom: none; border-bottom: none;
margin-left: 0; margin: 0;
margin-right: 0;
} }
} }
......
.detail-page-header { .detail-page-header {
padding: 11px 0; padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: #5c5d5e; color: #5c5d5e;
font-size: 16px; font-size: 16px;
...@@ -16,11 +16,6 @@ ...@@ -16,11 +16,6 @@
.issue_created_ago, .author_link { .issue_created_ago, .author_link {
white-space: nowrap; white-space: nowrap;
} }
.issue-meta {
display: inline-block;
line-height: 20px;
}
} }
.detail-page-description { .detail-page-description {
......
...@@ -16,3 +16,24 @@ i.icon-gitorious-big { ...@@ -16,3 +16,24 @@ i.icon-gitorious-big {
width: 18px; width: 18px;
height: 18px; height: 18px;
} }
.import-jobs-from-col,
.import-jobs-to-col {
width: 40%;
}
.import-jobs-status-col {
width: 20%;
}
.btn-import {
.loading-icon {
display: none;
}
&.is-loading {
.loading-icon {
display: inline-block;
}
}
}
...@@ -273,10 +273,6 @@ ...@@ -273,10 +273,6 @@
} }
} }
.btn-default.gutter-toggle {
margin-top: 4px;
}
.detail-page-description { .detail-page-description {
small { small {
color: $gray-darkest; color: $gray-darkest;
...@@ -322,3 +318,50 @@ ...@@ -322,3 +318,50 @@
padding-top: 7px; padding-top: 7px;
} }
} }
.issuable-status-box {
float: none;
display: inline-block;
margin-top: 0;
@media (max-width: $screen-xs-max) {
position: absolute;
top: 0;
left: 0;
}
}
.issuable-header {
position: relative;
padding-left: 45px;
padding-right: 45px;
line-height: 35px;
@media (min-width: $screen-sm-min) {
float: left;
padding-left: 0;
padding-right: 0;
}
}
.issuable-actions {
padding-top: 10px;
@media (min-width: $screen-sm-min) {
float: right;
padding-top: 0;
}
}
.issuable-gutter-toggle {
@media (max-width: $screen-sm-max) {
position: absolute;
top: 0;
right: 0;
}
}
.issuable-meta {
display: inline-block;
line-height: 18px;
}
...@@ -86,41 +86,9 @@ form.edit-issue { ...@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issue-btn-group { .issue-btn-group {
width: 100%; width: 100%;
margin-top: 5px;
.btn-group {
width: 100%;
ul {
width: 100%;
text-align: center;
}
}
.btn { .btn {
width: 100%; width: 100%;
&:first-child:not(:last-child) {
}
&:not(:first-child):not(:last-child) {
margin-top: 10px;
}
&:last-child:not(:first-child) {
margin-top: 10px;
}
}
}
.issue {
&:hover .issue-actions {
display: none !important;
}
.issue-updated-at {
display: none;
} }
} }
} }
...@@ -133,11 +101,3 @@ form.edit-issue { ...@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color; color: $gl-text-color;
margin-left: 52px; margin-left: 52px;
} }
.editor-details {
display: block;
@media (min-width: $screen-sm-min) {
display: inline-block;
}
}
...@@ -61,11 +61,11 @@ ...@@ -61,11 +61,11 @@
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
border: 1px solid $note-form-border-color; border: 1px solid $note-form-border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
transition: border-color ease-in-out 0.15s,
box-shadow ease-in-out 0.15s;
&.is-focused { &.is-focused {
border-color: $focus-border-color; @extend .form-control:focus;
box-shadow: 0 0 2px $black-transparent,
0 0 4px rgba($focus-border-color, .4);
.comment-toolbar, .comment-toolbar,
.nav-links { .nav-links {
......
...@@ -171,6 +171,11 @@ ul.notes { ...@@ -171,6 +171,11 @@ ul.notes {
&.parallel { &.parallel {
border-width: 1px; border-width: 1px;
.code,
code {
white-space: pre-wrap;
}
} }
.notes { .notes {
...@@ -198,6 +203,12 @@ ul.notes { ...@@ -198,6 +203,12 @@ ul.notes {
color: $notes-light-color; color: $notes-light-color;
} }
.discussion-headline-light {
a {
color: $gl-link-color;
}
}
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
...@@ -209,6 +220,17 @@ ul.notes { ...@@ -209,6 +220,17 @@ ul.notes {
color: $notes-action-color; color: $notes-action-color;
} }
.discussion-actions {
@media (max-width: $screen-sm-max) {
float: none;
margin-left: 0;
.note-action-button {
margin-left: 0;
}
}
}
.note-action-button, .note-action-button,
.discussion-action-button { .discussion-action-button {
display: inline-block; display: inline-block;
......
/* Generic print styles */
header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
.profiler-results {display: none;}
/* Styles targeted specifically at printing files */
.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
.file-title {display: none;}
.file-holder {border: none;}
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; } .wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
.wiki h1 {font-size: 30px;} .wiki h1 {font-size: 30px;}
.wiki h2 {font-size: 22px;} .wiki h2 {font-size: 22px;}
.wiki h3 {font-size: 18px; font-weight: bold; } .wiki h3 {font-size: 18px; font-weight: bold; }
.sidebar-wrapper { display: none; } header,
.nav { display: none; } nav,
.btn { display: none; } nav.main-nav,
nav.navbar-collapse,
nav.navbar-collapse.collapse,
.profiler-results,
.tree-ref-holder,
.tree-holder .breadcrumb,
.blob-commit-info,
.file-title,
.file-holder,
.sidebar-wrapper,
.nav,
.btn,
ul.notes-form,
.merge-request-ci-status .ci-status-link:after,
.issuable-gutter-toggle,
.gutter-toggle,
.issuable-details .content-block-small,
.edit-link,
.note-action-button {
display: none!important;
}
.page-gutter {
padding-top: 0;
padding-left: 0;
}
.right-sidebar {
top: 0;
}
...@@ -75,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -75,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:admin_notification_email, :admin_notification_email,
:user_oauth_applications, :user_oauth_applications,
:shared_runners_enabled, :shared_runners_enabled,
:shared_runners_text,
:max_artifacts_size, :max_artifacts_size,
:metrics_enabled, :metrics_enabled,
:metrics_host, :metrics_host,
......
...@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController
end end
def hook_params def hook_params
params.require(:hook).permit(:url, :enable_ssl_verification) params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
end end
end end
...@@ -51,6 +51,7 @@ class HelpController < ApplicationController ...@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end end
def ui def ui
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end end
private private
......
...@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController
end end
def create def create
link = project.project_group_links.new group = Group.find(params[:link_group_id])
link.group_id = params[:link_group_id] return render_404 unless can?(current_user, :read_group, group)
link.group_access = params[:link_group_access]
link.save project.project_group_links.create(
group: group, group_access: params[:link_group_access]
)
redirect_to namespace_project_group_links_path(project.namespace, project) redirect_to namespace_project_group_links_path(project.namespace, project)
end end
......
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :set_user before_action :user
before_action :authorize_read_user!, only: [:show]
def show def show
respond_to do |format| respond_to do |format|
...@@ -75,22 +76,26 @@ class UsersController < ApplicationController ...@@ -75,22 +76,26 @@ class UsersController < ApplicationController
private private
def set_user def authorize_read_user!
@user = User.find_by_username!(params[:username]) render_404 unless can?(current_user, :read_user, user)
end
def user
@user ||= User.find_by_username!(params[:username])
end end
def contributed_projects def contributed_projects
ContributedProjectsFinder.new(@user).execute(current_user) ContributedProjectsFinder.new(user).execute(current_user)
end end
def contributions_calendar def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar. @contributions_calendar ||= Gitlab::ContributionsCalendar.
new(contributed_projects, @user) new(contributed_projects, user)
end end
def load_events def load_events
# Get user activity feed for projects common for both users # Get user activity feed for projects common for both users
@events = @user.recent_events. @events = user.recent_events.
merge(projects_for_current_user). merge(projects_for_current_user).
references(:project). references(:project).
with_associations. with_associations.
...@@ -99,16 +104,16 @@ class UsersController < ApplicationController ...@@ -99,16 +104,16 @@ class UsersController < ApplicationController
def load_projects def load_projects
@projects = @projects =
PersonalProjectsFinder.new(@user).execute(current_user) PersonalProjectsFinder.new(user).execute(current_user)
.page(params[:page]) .page(params[:page])
end end
def load_contributed_projects def load_contributed_projects
@contributed_projects = contributed_projects.joined(@user) @contributed_projects = contributed_projects.joined(user)
end end
def load_groups def load_groups
@groups = JoinedGroupsFinder.new(@user).execute(current_user) @groups = JoinedGroupsFinder.new(user).execute(current_user)
end end
def projects_for_current_user def projects_for_current_user
......
...@@ -15,6 +15,10 @@ module ApplicationSettingsHelper ...@@ -15,6 +15,10 @@ module ApplicationSettingsHelper
current_application_settings.sign_in_text current_application_settings.sign_in_text
end end
def shared_runners_text
current_application_settings.shared_runners_text
end
def user_oauth_applications? def user_oauth_applications?
current_application_settings.user_oauth_applications current_application_settings.user_oauth_applications
end end
......
...@@ -55,6 +55,15 @@ module IssuablesHelper ...@@ -55,6 +55,15 @@ module IssuablesHelper
h(milestone_title.presence || default_label) h(milestone_title.presence || default_label)
end end
def issuable_meta(issuable, project, text)
output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
end
end
private private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer ...@@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer
mail( mail(
to: User.admins.pluck(:email), to: User.admins.pluck(:email),
subject: @message subject: "GitLab Admin | #{@message}"
) )
end end
end end
...@@ -18,6 +18,7 @@ class Ability ...@@ -18,6 +18,7 @@ class Ability
when Namespace then namespace_abilities(user, subject) when Namespace then namespace_abilities(user, subject)
when GroupMember then group_member_abilities(user, subject) when GroupMember then group_member_abilities(user, subject)
when ProjectMember then project_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject)
when User then user_abilities
else [] else []
end.concat(global_abilities(user)) end.concat(global_abilities(user))
end end
...@@ -35,6 +36,8 @@ class Ability ...@@ -35,6 +36,8 @@ class Ability
anonymous_project_abilities(subject) anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group) when subject.is_a?(Group) || subject.respond_to?(:group)
anonymous_group_abilities(subject) anonymous_group_abilities(subject)
when subject.is_a?(User)
anonymous_user_abilities
else else
[] []
end end
...@@ -81,17 +84,17 @@ class Ability ...@@ -81,17 +84,17 @@ class Ability
end end
def anonymous_group_abilities(subject) def anonymous_group_abilities(subject)
rules = []
group = if subject.is_a?(Group) group = if subject.is_a?(Group)
subject subject
else else
subject.group subject.group
end end
if group && group.public? rules << :read_group if group.public?
[:read_group]
else rules
[]
end
end end
def anonymous_personal_snippet_abilities(snippet) def anonymous_personal_snippet_abilities(snippet)
...@@ -110,9 +113,14 @@ class Ability ...@@ -110,9 +113,14 @@ class Ability
end end
end end
def anonymous_user_abilities
[:read_user] unless restricted_public_level?
end
def global_abilities(user) def global_abilities(user)
rules = [] rules = []
rules << :create_group if user.can_create_group rules << :create_group if user.can_create_group
rules << :read_users_list
rules rules
end end
...@@ -163,7 +171,7 @@ class Ability ...@@ -163,7 +171,7 @@ class Ability
@public_project_rules ||= project_guest_rules + [ @public_project_rules ||= project_guest_rules + [
:download_code, :download_code,
:fork_project, :fork_project,
:read_commit_status, :read_commit_status
] ]
end end
...@@ -284,7 +292,6 @@ class Ability ...@@ -284,7 +292,6 @@ class Ability
def group_abilities(user, group) def group_abilities(user, group)
rules = [] rules = []
rules << :read_group if can_read_group?(user, group) rules << :read_group if can_read_group?(user, group)
# Only group masters and group owners can create new projects # Only group masters and group owners can create new projects
...@@ -456,6 +463,10 @@ class Ability ...@@ -456,6 +463,10 @@ class Ability
rules rules
end end
def user_abilities
[:read_user]
end
def abilities def abilities
@abilities ||= begin @abilities ||= begin
abilities = Six.new abilities = Six.new
...@@ -470,6 +481,10 @@ class Ability ...@@ -470,6 +481,10 @@ class Ability
private private
def restricted_public_level?
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
def named_abilities(name) def named_abilities(name)
[ [
:"read_#{name}", :"read_#{name}",
......
...@@ -359,11 +359,23 @@ module Ci ...@@ -359,11 +359,23 @@ module Ci
self.update(erased_by: user, erased_at: Time.now) self.update(erased_by: user, erased_at: Time.now)
end end
private
def yaml_variables def yaml_variables
global_yaml_variables + job_yaml_variables
end
def global_yaml_variables
if commit.config_processor
commit.config_processor.global_variables.map do |key, value|
{ key: key, value: value, public: true }
end
else
[]
end
end
def job_yaml_variables
if commit.config_processor if commit.config_processor
commit.config_processor.variables.map do |key, value| commit.config_processor.job_variables(name).map do |key, value|
{ key: key, value: value, public: true } { key: key, value: value, public: true }
end end
else else
......
...@@ -7,12 +7,14 @@ module InternalId ...@@ -7,12 +7,14 @@ module InternalId
end end
def set_iid def set_iid
if iid.blank?
records = project.send(self.class.name.tableize) records = project.send(self.class.name.tableize)
records = records.with_deleted if self.paranoid? records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid) max_iid = records.maximum(:iid)
self.iid = max_iid.to_i + 1 self.iid = max_iid.to_i + 1
end end
end
def to_param def to_param
iid.to_s iid.to_s
......
...@@ -37,4 +37,10 @@ class ExternalIssue ...@@ -37,4 +37,10 @@ class ExternalIssue
def to_reference(_from_project = nil) def to_reference(_from_project = nil)
id id
end end
def reference_link_text(from_project = nil)
return "##{id}" if /^\d+$/.match(id)
id
end
end end
...@@ -21,8 +21,6 @@ ...@@ -21,8 +21,6 @@
class ProjectHook < WebHook class ProjectHook < WebHook
belongs_to :project belongs_to :project
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) } scope :issue_hooks, -> { where(issues_events: true) }
scope :note_hooks, -> { where(note_events: true) } scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
......
...@@ -19,4 +19,7 @@ ...@@ -19,4 +19,7 @@
# #
class SystemHook < WebHook class SystemHook < WebHook
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
end
end end
...@@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base ...@@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base
default_value_for :build_events, false default_value_for :build_events, false
default_value_for :enable_ssl_verification, true default_value_for :enable_ssl_verification, true
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
# HTTParty timeout # HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout default_timeout Gitlab.config.gitlab.webhook_timeout
......
...@@ -831,8 +831,8 @@ class Project < ActiveRecord::Base ...@@ -831,8 +831,8 @@ class Project < ActiveRecord::Base
end end
end end
def hook_attrs def hook_attrs(backward: true)
{ attrs = {
name: name, name: name,
description: description, description: description,
web_url: web_url, web_url: web_url,
...@@ -843,12 +843,19 @@ class Project < ActiveRecord::Base ...@@ -843,12 +843,19 @@ class Project < ActiveRecord::Base
visibility_level: visibility_level, visibility_level: visibility_level,
path_with_namespace: path_with_namespace, path_with_namespace: path_with_namespace,
default_branch: default_branch, default_branch: default_branch,
}
# Backward compatibility # Backward compatibility
if backward
attrs.merge!({
homepage: web_url, homepage: web_url,
url: url_to_repo, url: url_to_repo,
ssh_url: ssh_url_to_repo, ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo http_url: http_url_to_repo
} })
end
attrs
end end
# Reset events cache related to this project # Reset events cache related to this project
......
...@@ -12,12 +12,14 @@ class Repository ...@@ -12,12 +12,14 @@ class Repository
attr_accessor :path_with_namespace, :project attr_accessor :path_with_namespace, :project
def self.clean_old_archives def self.clean_old_archives
Gitlab::Metrics.measure(:clean_old_archives) do
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
return unless File.directory?(repository_downloads_path) return unless File.directory?(repository_downloads_path)
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end end
end
def initialize(path_with_namespace, project) def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace @path_with_namespace = path_with_namespace
......
...@@ -73,6 +73,7 @@ class GitPushService < BaseService ...@@ -73,6 +73,7 @@ class GitPushService < BaseService
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
EventCreateService.new.push(@project, current_user, build_push_data) EventCreateService.new.push(@project, current_user, build_push_data)
SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data) CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
...@@ -138,6 +139,11 @@ class GitPushService < BaseService ...@@ -138,6 +139,11 @@ class GitPushService < BaseService
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits) build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end end
def build_push_data_system_hook
@push_data_system ||= Gitlab::PushDataBuilder.
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
end
def push_to_existing_branch? def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
......
class GitTagPushService class GitTagPushService < BaseService
attr_accessor :project, :user, :push_data attr_accessor :push_data
def execute(project, user, oldrev, newrev, ref) def execute
project.repository.before_push_tag project.repository.before_push_tag
@project, @user = project, user @push_data = build_push_data
@push_data = build_push_data(oldrev, newrev, ref)
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, current_user, @push_data)
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks)
CreateCommitBuildsService.new.execute(project, @user, @push_data) CreateCommitBuildsService.new.execute(project, current_user, @push_data)
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
true true
...@@ -18,14 +18,14 @@ class GitTagPushService ...@@ -18,14 +18,14 @@ class GitTagPushService
private private
def build_push_data(oldrev, newrev, ref) def build_push_data
commits = [] commits = []
message = nil message = nil
if !Gitlab::Git.blank_ref?(newrev) if !Gitlab::Git.blank_ref?(params[:newrev])
tag_name = Gitlab::Git.ref_name(ref) tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name) tag = project.repository.find_tag(tag_name)
if tag && tag.target == newrev if tag && tag.target == params[:newrev]
commit = project.commit(tag.target) commit = project.commit(tag.target)
commits = [commit].compact commits = [commit].compact
message = tag.message message = tag.message
...@@ -33,6 +33,11 @@ class GitTagPushService ...@@ -33,6 +33,11 @@ class GitTagPushService
end end
Gitlab::PushDataBuilder. Gitlab::PushDataBuilder.
build(project, user, oldrev, newrev, ref, commits, message) build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
end
def build_system_push_data
Gitlab::PushDataBuilder.
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
end end
end end
...@@ -3,17 +3,13 @@ class SystemHooksService ...@@ -3,17 +3,13 @@ class SystemHooksService
execute_hooks(build_event_data(model, event)) execute_hooks(build_event_data(model, event))
end end
private def execute_hooks(data, hooks_scope = :all)
SystemHook.send(hooks_scope).each do |hook|
def execute_hooks(data) hook.async_execute(data, 'system_hooks')
SystemHook.all.each do |sh|
async_execute_hook(sh, data, 'system_hooks')
end end
end end
def async_execute_hook(hook, data, hook_name) private
Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
end
def build_event_data(model, event) def build_event_data(model, event)
data = { data = {
......
...@@ -26,7 +26,9 @@ ...@@ -26,7 +26,9 @@
.btn-group{ data: data_attrs } .btn-group{ data: data_attrs }
- restricted_level_checkboxes('restricted-visibility-help').each do |level| - restricted_level_checkboxes('restricted-visibility-help').each do |level|
= level = level
%span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets %span.help-block#restricted-visibility-help
Selected levels cannot be used by non-admin users for projects or snippets.
If the public level is restricted, user profiles are only visible to logged in users.
.form-group .form-group
= f.label :import_sources, class: 'control-label col-sm-2' = f.label :import_sources, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
...@@ -153,7 +155,11 @@ ...@@ -153,7 +155,11 @@
= f.label :shared_runners_enabled do = f.label :shared_runners_enabled do
= f.check_box :shared_runners_enabled = f.check_box :shared_runners_enabled
Enable shared runners for new projects Enable shared runners for new projects
.form-group
= f.label :shared_runners_text, class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :shared_runners_text, class: 'form-control', rows: 4
.help-block Markdown enabled
.form-group .form-group
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -16,6 +16,27 @@ ...@@ -16,6 +16,27 @@
= f.label :url, "URL:", class: 'control-label' = f.label :url, "URL:", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :url, class: "form-control" = f.text_field :url, class: "form-control"
.form-group
= f.label :url, "Trigger", class: 'control-label'
.col-sm-10.prepend-top-10
%div
System hook will be triggered on set of events like creating project
or adding ssh key. But you can also enable extra triggers like Push events.
%div.prepend-top-default
= f.check_box :push_events, class: 'pull-left'
.prepend-left-20
= f.label :push_events, class: 'list-label' do
%strong Push events
%p.light
This url will be triggered by a push to the repository
%div
= f.check_box :tag_push_events, class: 'pull-left'
.prepend-left-20
= f.label :tag_push_events, class: 'list-label' do
%strong Tag push events
%p.light
This url will be triggered when a new tag is pushed to the repository
.form-group .form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox' = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
.col-sm-10 .col-sm-10
...@@ -31,13 +52,16 @@ ...@@ -31,13 +52,16 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
System hooks (#{@hooks.count}) System hooks (#{@hooks.count})
%ul.well-list %ul.content-list
- @hooks.each do |hook| - @hooks.each do |hook|
%li %li
.list-item-name .controls
%strong= hook.url
%p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
.pull-right
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
.monospace= hook.url
%div
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
...@@ -6,18 +6,17 @@ ...@@ -6,18 +6,17 @@
.login-heading .login-heading
%h3 Create an account %h3 Create an account
.login-body .login-body
- user = params[:user].present? ? params[:user] : {}
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
%div %div
= f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
%div %div
= f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div %div
= f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength .form-group.append-bottom-20#password-strength
= f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
%div %div
- if current_application_settings.recaptcha_enabled - if current_application_settings.recaptcha_enabled
= recaptcha_tags = recaptcha_tags
......
...@@ -345,11 +345,11 @@ ...@@ -345,11 +345,11 @@
%ul %ul
%li %li
%a.dropdown-menu-user-link.is-active{href: "#"} %a.dropdown-menu-user-link.is-active{href: "#"}
= link_to_member_avatar(current_user, size: 30) = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name %strong.dropdown-menu-user-full-name
= current_user.name = @user.name
.dropdown-menu-user-username .dropdown-menu-user-username
= current_user.to_reference = @user.to_reference
.example .example
%div %div
...@@ -372,11 +372,11 @@ ...@@ -372,11 +372,11 @@
%ul %ul
%li %li
%a.dropdown-menu-user-link.is-active{href: "#"} %a.dropdown-menu-user-link.is-active{href: "#"}
= link_to_member_avatar(current_user, size: 30) = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name %strong.dropdown-menu-user-full-name
= current_user.name = @user.name
.dropdown-menu-user-username .dropdown-menu-user-username
= current_user.to_reference = @user.to_reference
.dropdown-page-two .dropdown-page-two
.dropdown-title .dropdown-title
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}} %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
......
...@@ -20,10 +20,10 @@ ...@@ -20,10 +20,10 @@
job.attr("id", "project_#{@project.id}") job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target") target_field = job.find(".import-target")
target_field.empty() target_field.empty()
target_field.append('<strong>#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}</strong>') target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
$("table.import-jobs tbody").prepend(job) $("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else - else
:plain :plain
job = $("tr#repo_#{@repo_id}") job = $("tr#repo_#{@repo_id}")
job.find(".import-actions").html("<i class='fa fa-exclamation-circle'> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}</i>") job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}")
...@@ -10,13 +10,19 @@ ...@@ -10,13 +10,19 @@
%hr %hr
%p %p
- if @incompatible_repos.any? - if @incompatible_repos.any?
= button_tag 'Import all compatible projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-import btn-success js-import-all" do
Import all compatible projects
= icon("spinner spin", class: "loading-icon")
- else - else
= button_tag 'Import all projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-responsive
.table-holder
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Bitbucket %th From Bitbucket
...@@ -28,7 +34,7 @@ ...@@ -28,7 +34,7 @@
%td %td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
%td %td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -47,7 +53,9 @@ ...@@ -47,7 +53,9 @@
%td.import-target %td.import-target
= "#{repo["owner"]}/#{repo["slug"]}" = "#{repo["owner"]}/#{repo["slug"]}"
%td.import-actions.job-status %td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import" = button_tag class: "btn btn-import js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
%td %td
......
...@@ -13,10 +13,15 @@ ...@@ -13,10 +13,15 @@
how FogBugz email addresses and usernames are imported into GitLab. how FogBugz email addresses and usernames are imported into GitLab.
%hr %hr
%p %p
= button_tag 'Import all projects', class: 'btn btn-success js-import-all' = button_tag class: 'btn btn-import btn-success js-import-all' do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-holder .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From FogBugz %th From FogBugz
...@@ -28,7 +33,7 @@ ...@@ -28,7 +33,7 @@
%td %td
= project.import_source = project.import_source
%td %td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -47,7 +52,9 @@ ...@@ -47,7 +52,9 @@
%td.import-target %td.import-target
= "#{current_user.username}/#{repo.name}" = "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status %td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import" = button_tag class: "btn btn-import js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
:javascript :javascript
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}"); new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
...@@ -8,10 +8,15 @@ ...@@ -8,10 +8,15 @@
Select projects you want to import. Select projects you want to import.
%hr %hr
%p %p
= button_tag 'Import all projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-holder .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From GitHub %th From GitHub
...@@ -23,7 +28,7 @@ ...@@ -23,7 +28,7 @@
%td %td
= link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
%td %td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
%td.import-target %td.import-target
= repo.full_name = repo.full_name
%td.import-actions.job-status %td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import" = button_tag class: "btn btn-import js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
:javascript :javascript
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}"); new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
...@@ -8,10 +8,15 @@ ...@@ -8,10 +8,15 @@
Select projects you want to import. Select projects you want to import.
%hr %hr
%p %p
= button_tag 'Import all projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-holder .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From GitLab.com %th From GitLab.com
...@@ -23,7 +28,7 @@ ...@@ -23,7 +28,7 @@
%td %td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td %td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
%td.import-target %td.import-target
= repo["path_with_namespace"] = repo["path_with_namespace"]
%td.import-actions.job-status %td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import" = button_tag class: "btn js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
:javascript :javascript
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}"); new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
...@@ -8,10 +8,15 @@ ...@@ -8,10 +8,15 @@
Select projects you want to import. Select projects you want to import.
%hr %hr
%p %p
= button_tag 'Import all projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-holder .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Gitorious.org %th From Gitorious.org
...@@ -23,7 +28,7 @@ ...@@ -23,7 +28,7 @@
%td %td
= link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
%td %td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
%td.import-target %td.import-target
= repo.full_name = repo.full_name
%td.import-actions.job-status %td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import" = button_tag class: "btn btn-import js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
:javascript :javascript
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}"); new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
...@@ -14,12 +14,19 @@ ...@@ -14,12 +14,19 @@
%hr %hr
%p %p
- if @incompatible_repos.any? - if @incompatible_repos.any?
= button_tag 'Import all compatible projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-import btn-success js-import-all" do
Import all compatible projects
= icon("spinner spin", class: "loading-icon")
- else - else
= button_tag 'Import all projects', class: "btn btn-success js-import-all" = button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-holder .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Google Code %th From Google Code
...@@ -31,7 +38,7 @@ ...@@ -31,7 +38,7 @@
%td %td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
%td %td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -50,7 +57,9 @@ ...@@ -50,7 +57,9 @@
%td.import-target %td.import-target
= "#{current_user.username}/#{repo.name}" = "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status %td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import" = button_tag class: "btn btn-import js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo.id}"} %tr{id: "repo_#{repo.id}"}
%td %td
......
.md-area .md-area
.md-header .md-header
%ul.nav-links %ul.nav-links.clearfix
%li.active %li.active
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write Write
......
...@@ -74,16 +74,15 @@ ...@@ -74,16 +74,15 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Webhooks (#{@hooks.count}) Webhooks (#{@hooks.count})
%ul.well-list %ul.content-list
- @hooks.each do |hook| - @hooks.each do |hook|
%li %li
.pull-right .controls
= link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped" = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
= link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
.clearfix .monospace= hook.url
%span.monospace= hook.url %div
%p
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger| - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- if hook.send(trigger) - if hook.send(trigger)
%span.label.label-gray= trigger.titleize %span.label.label-gray= trigger.titleize
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
- page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description - page_description @issue.description
- page_card_attributes @issue.card_attributes - page_card_attributes @issue.card_attributes
- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
= render "header_title" .clearfix.detail-page-header
.issuable-header
.issue .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
.detail-page-header.issuable-header = icon('check', class: "hidden-sm hidden-md hidden-lg")
.pull-left
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
%span.hidden-xs %span.hidden-xs
Closed Closed
%span.hidden-sm.hidden-md.hidden-lg .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
= icon('check') = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} %span.hidden-xs Open
%span.hidden-xs
Open
%span.hidden-sm.hidden-md.hidden-lg
= icon('circle-o')
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" } %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left') = icon('angle-double-left')
.issue-meta .issuable-meta
= confidential_icon(@issue) = confidential_icon(@issue)
%strong.identifier = issuable_meta(@issue, @project, "Issue")
Issue ##{@issue.iid}
%span.creator
opened
.editor-details
.editor-details
= time_ago_with_tooltip(@issue.created_at)
by
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
.pull-right.issue-btn-group - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
%span.caret
Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can?(current_user, :create_issue, @project)
%li
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- if can?(current_user, :update_issue, @issue)
%li
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
%li
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
= icon('plus') = icon('plus')
New issue New issue
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block .detail-page-description.content-block
%h2.title %h2.title
= markdown escape_once(@issue.title), pipeline: :single_line = markdown escape_once(@issue.title), pipeline: :single_line
%div
- if @issue.description.present? - if @issue.description.present?
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
.wiki .wiki
= preserve do = preserve do
= markdown(@issue.description, cache_key: [@issue, "description"]) = markdown(@issue.description, cache_key: [@issue, "description"])
...@@ -64,19 +63,17 @@ ...@@ -64,19 +63,17 @@
= @issue.description = @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
#merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
#related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
.content-block.content-block-small .content-block.content-block-small
= render 'new_branch' = render 'new_branch'
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
.row %section.issuable-discussion
%section.col-md-12
.issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable: @issue
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('aside.right-sidebar').effect('highlight');
new IssuableContext();
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description - page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes - page_card_attributes @merge_request.card_attributes
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
= render "header_title"
- if params[:view] == 'parallel' - if params[:view] == 'parallel'
- fluid_layout true - fluid_layout true
...@@ -32,7 +31,8 @@ ...@@ -32,7 +31,8 @@
%span Request to merge %span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request) %span.label-branch= source_branch_with_namespace(@merge_request)
%span into %span into
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" %span.label-branch
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
- if @merge_request.open? && @merge_request.diverged_from_target_branch? - if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
......
.detail-page-header .clearfix.detail-page-header
.status-box{ class: status_box_class(@merge_request) } .issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
= icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs %span.hidden-xs
= @merge_request.state_human_name = @merge_request.state_human_name
%span.hidden-sm.hidden-md.hidden-lg
= icon(@merge_request.state_icon_name) %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left') = icon('angle-double-left')
.issue-meta
%strong.identifier
%span.hidden-sm.hidden-md.hidden-lg
MR
%span.hidden-xs
Merge Request
!#{@merge_request.iid}
%span.creator
opened
.editor-details
= time_ago_with_tooltip(@merge_request.created_at)
by
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
.issue-btn-group.pull-right .issuable-meta
= issuable_meta(@merge_request, @project, "Merge Request")
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open? .issuable-actions
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request' .clearfix.issue-btn-group.dropdown
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
%span.caret
Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
%li{ class: issue_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: issue_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request'
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('aside.right-sidebar').effect('highlight');
new IssuableContext();
- note = discussion_notes.first - note = discussion_notes.first
.timeline-entry %li.note.note-discussion.timeline-entry
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
= link_to user_path(note.author) do = link_to user_path(note.author) do
......
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] } - note_editable = note_editable?(note)
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
%a{href: user_path(note.author)} %a{href: user_path(note.author)}
...@@ -15,16 +16,16 @@ ...@@ -15,16 +16,16 @@
- if access - if access
%span.note-role %span.note-role
= access = access
- if note_editable?(note) - if note_editable
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil') = icon('pencil')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= icon('trash-o') = icon('trash-o')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text .note-text
= preserve do = preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"]) = markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note) - if note_editable
= render 'projects/notes/edit_form', note: note = render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
......
%ul#notes-list.notes.main-notes-list.timeline %ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes" = render "projects/notes/notes"
%ul.notes.timeline %ul.notes.notes-form.timeline
%li.timeline-entry %li.timeline-entry
- if can? current_user, :create_note, @project - if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm .timeline-icon.hidden-xs.hidden-sm
......
...@@ -6,15 +6,11 @@ ...@@ -6,15 +6,11 @@
= "#{note.author.to_reference} started a discussion" = "#{note.author.to_reference} started a discussion"
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
on the diff on the diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions .discussion-actions
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up %i.fa.fa-chevron-up
Show/hide discussion Show/hide discussion
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content .discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
...@@ -8,21 +8,18 @@ ...@@ -8,21 +8,18 @@
= "#{note.author.to_reference} started a discussion on #{commit_description}" = "#{note.author.to_reference} started a discussion on #{commit_description}"
- if commit - if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace') = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions .discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up %i.fa.fa-chevron-up
Show/hide discussion Show/hide discussion
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content .discussion-body.js-toggle-content
- if note.for_diff_line? - if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else - else
.panel.panel-default .panel.panel-default
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } } .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
%ul.notes.timeline
= render discussion_notes = render discussion_notes
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(discussion_notes.first) = link_to_reply_diff(discussion_notes.first)
...@@ -5,14 +5,10 @@ ...@@ -5,14 +5,10 @@
.inline.discussion-headline-light .inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion" = "#{note.author.to_reference} started a discussion"
on the outdated diff on the outdated diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions .discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down %i.fa.fa-chevron-down
Show/hide discussion Show/hide discussion
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide .discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
%h3 Shared runners %h3 Shared runners
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning.shared-runners-description
- if shared_runners_text.present?
= markdown(shared_runners_text, pipeline: 'plain_markdown')
- else
GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X. GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
%hr %hr
- if @project.shared_runners_enabled? - if @project.shared_runners_enabled?
......
...@@ -3,3 +3,6 @@ ...@@ -3,3 +3,6 @@
%p %p
= link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1) = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
%p
You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}.
#{@message}. #{@message}.
\ \
View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)} View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
You are receiving this message because you are a GitLab administrator
for #{Gitlab.config.gitlab.url}.
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input', required: true class: 'form-control pad', required: true
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
%p.help-block %p.help-block
......
...@@ -118,6 +118,7 @@ ...@@ -118,6 +118,7 @@
Manage labels Manage labels
- else - else
View labels View labels
= dropdown_loading
= render "shared/issuable/participants", participants: issuable.participants(current_user) = render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user - if current_user
......
...@@ -6,8 +6,6 @@ ...@@ -6,8 +6,6 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
= render 'shared/show_aside'
.user-profile .user-profile
.cover-block .cover-block
.cover-controls .cover-controls
......
...@@ -39,7 +39,7 @@ class PostReceive ...@@ -39,7 +39,7 @@ class PostReceive
end end
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
elsif Gitlab::Git.branch_ref?(ref) elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end end
......
...@@ -15,10 +15,10 @@ module RepositoryCheck ...@@ -15,10 +15,10 @@ module RepositoryCheck
private private
def check(project) def check(project)
repositories = [project.repository]
repositories << project.wiki.repository if project.wiki_enabled?
# Use 'map do', not 'all? do', to prevent short-circuiting # Use 'map do', not 'all? do', to prevent short-circuiting
[project.repository, project.wiki.repository].map do |repository| repositories.map { |repository| git_fsck(repository.path_to_repo) }.all?
git_fsck(repository.path_to_repo)
end.all?
end end
def git_fsck(path) def git_fsck(path)
......
class MigrateRepoSize < ActiveRecord::Migration class MigrateRepoSize < ActiveRecord::Migration
def up def up
Project.reset_column_information project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
Project.find_each(batch_size: 500) do |project|
project_data.each do |project|
id = project['id']
namespace_path = project['namespace_path'] || ''
path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git')
begin begin
if project.empty_repo? repo = Gitlab::Git::Repository.new(path)
if repo.empty?
print '-' print '-'
else else
project.update_repository_size size = repo.size
print '.' print '.'
execute("UPDATE projects SET repository_size = #{size} WHERE id = #{id}")
end end
rescue rescue => e
print 'F' puts "\nFailed to update project #{id}: #{e}"
end end
end end
puts 'Done' puts "\nDone"
end end
def down def down
......
class AddSharedRunnersTextToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :shared_runners_text, :text
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160412175417) do ActiveRecord::Schema.define(version: 20160415133440) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -78,6 +78,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do ...@@ -78,6 +78,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do
t.boolean "email_author_in_body", default: false t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility" t.integer "default_group_visibility"
t.boolean "repository_checks_enabled", default: true t.boolean "repository_checks_enabled", default: true
t.text "shared_runners_text"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
......
...@@ -7,6 +7,10 @@ through the coordinator API of GitLab CI. ...@@ -7,6 +7,10 @@ through the coordinator API of GitLab CI.
A runner can be specific to a certain project or serve any project A runner can be specific to a certain project or serve any project
in GitLab CI. A runner that serves all projects is called a shared runner. in GitLab CI. A runner that serves all projects is called a shared runner.
Ideally, GitLab Runner should not be installed on the same machine as GitLab.
Read the [requirements documentation](../../install/requirements.md#gitlab-runner)
for more information.
## Shared vs. Specific Runners ## Shared vs. Specific Runners
A runner that is specific only runs for the specified project. A shared runner A runner that is specific only runs for the specified project. A shared runner
...@@ -140,7 +144,7 @@ to it. This means that if you have shared runners setup for a project and ...@@ -140,7 +144,7 @@ to it. This means that if you have shared runners setup for a project and
someone forks that project, the shared runners will also serve jobs of this someone forks that project, the shared runners will also serve jobs of this
project. project.
# Attack vectors in runners ## Attack vectors in Runners
Mentioned briefly earlier, but the following things of runners can be exploited. Mentioned briefly earlier, but the following things of runners can be exploited.
We're always looking for contributions that can mitigate these [Security Considerations](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md). We're always looking for contributions that can mitigate these [Security Considerations](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md).
## Variables ## Variables
When receiving a build from GitLab CI, the runner prepares the build environment. When receiving a build from GitLab CI, the runner prepares the build environment.
It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables** It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables**
The variables can be overwritten. They take precedence over each other in this order: The variables can be overwritten. They take precedence over each other in this order:
1. Trigger variables
1. Secure variables 1. Secure variables
1. YAML-defined variables 1. YAML-defined job-level variables
1. YAML-defined global variables
1. Predefined variables 1. Predefined variables
For example, if you define: For example, if you define:
1. API_TOKEN=SECURE as Secure Variable 1. `API_TOKEN=SECURE` as Secure Variable
1. API_TOKEN=YAML as YAML-defined variable 1. `API_TOKEN=YAML` as YAML-defined variable
The API_TOKEN will take the Secure Variable value: `SECURE`. The `API_TOKEN` will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables) ### Predefined variables (Environment Variables)
...@@ -70,15 +73,20 @@ These variables can be later used in all executed commands and scripts. ...@@ -70,15 +73,20 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them.
Variables can be defined at a global level, but also at a job level.
More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md).
### User-defined variables (Secure Variables) ### User-defined variables (Secure Variables)
**This feature requires GitLab Runner 0.4.0 or higher** **This feature requires GitLab Runner 0.4.0 or higher**
GitLab CI allows you to define per-project **Secure Variables** that are set in build environment. GitLab CI allows you to define per-project **Secure Variables** that are set in
the build environment.
The secure variables are stored out of the repository (the `.gitlab-ci.yml`). The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
The variables are securely passed to GitLab Runner and are available in build environment. The variables are securely passed to GitLab Runner and are available in the
It's desired method to use them for storing passwords, secret keys or whatever you want. build environment.
It's desired method to use them for storing passwords, secret keys or whatever
you want.
**The value of the variable can be shown in build log if explicitly asked to do so.** **The value of the variable can be shown in build log if explicitly asked to do so.**
If your project is public or internal you can make the builds private. If your project is public or internal you can make the builds private.
......
...@@ -15,6 +15,7 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -15,6 +15,7 @@ If you want a quick introduction to GitLab CI, follow our
- [.gitlab-ci.yml](#gitlab-ci-yml) - [.gitlab-ci.yml](#gitlab-ci-yml)
- [image and services](#image-and-services) - [image and services](#image-and-services)
- [before_script](#before_script) - [before_script](#before_script)
- [after_script](#after_script)
- [stages](#stages) - [stages](#stages)
- [types](#types) - [types](#types)
- [variables](#variables) - [variables](#variables)
...@@ -23,12 +24,14 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -23,12 +24,14 @@ If you want a quick introduction to GitLab CI, follow our
- [Jobs](#jobs) - [Jobs](#jobs)
- [script](#script) - [script](#script)
- [stage](#stage) - [stage](#stage)
- [job variables](#job-variables)
- [only and except](#only-and-except) - [only and except](#only-and-except)
- [tags](#tags) - [tags](#tags)
- [when](#when) - [when](#when)
- [artifacts](#artifacts) - [artifacts](#artifacts)
- [artifacts:name](#artifacts-name) - [artifacts:name](#artifacts-name)
- [dependencies](#dependencies) - [dependencies](#dependencies)
- [before_script and after_script](#before_script-and-after_script)
- [Hidden jobs](#hidden-jobs) - [Hidden jobs](#hidden-jobs)
- [Special YAML features](#special-yaml-features) - [Special YAML features](#special-yaml-features)
- [Anchors](#anchors) - [Anchors](#anchors)
...@@ -80,6 +83,9 @@ services: ...@@ -80,6 +83,9 @@ services:
before_script: before_script:
- bundle install - bundle install
after_script:
- rm secrets
stages: stages:
- build - build
- test - test
...@@ -104,6 +110,7 @@ There are a few reserved `keywords` that **cannot** be used as job names: ...@@ -104,6 +110,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| stages | no | Define build stages | | stages | no | Define build stages |
| types | no | Alias for `stages` | | types | no | Alias for `stages` |
| before_script | no | Define commands that run before each job's script | | before_script | no | Define commands that run before each job's script |
| after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables | | variables | no | Define build variables |
| cache | no | Define list of files that should be cached between subsequent runs | | cache | no | Define list of files that should be cached between subsequent runs |
...@@ -118,6 +125,14 @@ used for time of the build. The configuration of this feature is covered in ...@@ -118,6 +125,14 @@ used for time of the build. The configuration of this feature is covered in
`before_script` is used to define the command that should be run before all `before_script` is used to define the command that should be run before all
builds, including deploy builds. This can be an array or a multi-line string. builds, including deploy builds. This can be an array or a multi-line string.
### after_script
>**Note:**
Introduced in GitLab 8.7 and GitLab Runner v1.2.
`after_script` is used to define the command that will be run after for all
builds. This has to be an array or a multi-line string.
### stages ### stages
`stages` is used to define build stages that can be used by jobs. `stages` is used to define build stages that can be used by jobs.
...@@ -174,6 +189,8 @@ These variables can be later used in all executed commands and scripts. ...@@ -174,6 +189,8 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers, The YAML-defined variables are also set to all created service containers,
thus allowing to fine tune them. thus allowing to fine tune them.
Variables can be also defined on [job level](#job-variables).
### cache ### cache
>**Note:** >**Note:**
...@@ -324,6 +341,7 @@ job_name: ...@@ -324,6 +341,7 @@ job_name:
| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | | services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| stage | no | Defines a build stage (default: `test`) | | stage | no | Defines a build stage (default: `test`) |
| type | no | Alias for `stage` | | type | no | Alias for `stage` |
| variables | no | Define build variables on a job level |
| only | no | Defines a list of git refs for which build is created | | only | no | Defines a list of git refs for which build is created |
| except | no | Defines a list of git refs for which build is not created | | except | no | Defines a list of git refs for which build is not created |
| tags | no | Defines a list of tags which are used to select Runner | | tags | no | Defines a list of tags which are used to select Runner |
...@@ -332,6 +350,8 @@ job_name: ...@@ -332,6 +350,8 @@ job_name:
| dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them| | dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them|
| artifacts | no | Define list build artifacts | | artifacts | no | Define list build artifacts |
| cache | no | Define list of files that should be cached between subsequent runs | | cache | no | Define list of files that should be cached between subsequent runs |
| before_script | no | Override a set of commands that are executed before build |
| after_script | no | Override a set of commands that are executed after build |
### script ### script
...@@ -414,6 +434,18 @@ job: ...@@ -414,6 +434,18 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`, The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master. except master.
### job variables
It is possible to define build variables using a `variables` keyword on a job
level. It works basically the same way as its global-level equivalent but
allows you to define job-specific build variables.
When the `variables` keyword is used on a job level, it overrides global YAML
build variables and predefined variables.
Build variables priority is defined in
[variables documentation](../variables/README.md).
### tags ### tags
`tags` is used to select specific Runners from the list of all Runners that are `tags` is used to select specific Runners from the list of all Runners that are
...@@ -676,6 +708,23 @@ deploy: ...@@ -676,6 +708,23 @@ deploy:
script: make deploy script: make deploy
``` ```
### before_script and after_script
It's possible to overwrite globally defined `before_script` and `after_script`:
```yaml
before_script
- global before script
job:
before_script:
- execute this instead of global before script
script:
- my command
after_script:
- execute this after my script
```
## Hidden jobs ## Hidden jobs
>**Note:** >**Note:**
......
...@@ -79,6 +79,26 @@ With less memory GitLab will give strange errors during the reconfigure run and ...@@ -79,6 +79,26 @@ With less memory GitLab will give strange errors during the reconfigure run and
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those. Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
## Gitlab Runner
We strongly advise against installing GitLab Runner on the same machine you plan
to install GitLab on. Depending on how you decide to configure GitLab Runner and
what tools you use to exercise your application in the CI environment, GitLab
Runner can consume significant amount of available memory.
Memory consumption calculations, that are available above, will not be valid if
you decide to run GitLab Runner and the GitLab Rails application on the same
machine.
It is also not safe to install everything on a single machine, because of the
[security reasons] - especially when you plan to use shell executor with GitLab
Runner.
We recommend using a separate machine for each GitLab Runner, if you plan to
use the CI features.
[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md
## Unicorn Workers ## Unicorn Workers
It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests. It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests.
......
...@@ -58,6 +58,9 @@ you are logged in or not. ...@@ -58,6 +58,9 @@ you are logged in or not.
When visiting the public page of a user, you can only see the projects which When visiting the public page of a user, you can only see the projects which
you are privileged to. you are privileged to.
If the public level is restricted, user profiles are only visible to logged in users.
## Restricting the use of public or internal projects ## Restricting the use of public or internal projects
In the Admin area under **Settings** (`/admin/application_settings`), you can In the Admin area under **Settings** (`/admin/application_settings`), you can
......
...@@ -4,6 +4,12 @@ Your GitLab instance can perform HTTP POST requests on the following events: `pr ...@@ -4,6 +4,12 @@ Your GitLab instance can perform HTTP POST requests on the following events: `pr
System hooks can be used, e.g. for logging or changing information in a LDAP server. System hooks can be used, e.g. for logging or changing information in a LDAP server.
> **Note:**
>
> We follow the same structure from Webhooks for Push and Tag events, but we never display commits.
>
> Same deprecations from Webhooks are valid here.
## Hooks request example ## Hooks request example
**Request header**: **Request header**:
...@@ -240,3 +246,110 @@ X-Gitlab-Event: System Hook ...@@ -240,3 +246,110 @@ X-Gitlab-Event: System Hook
"user_id": 41 "user_id": 41
} }
``` ```
## Push events
Triggered when you push to the repository except when pushing tags.
**Request header**:
```
X-Gitlab-Event: System Hook
```
**Request body:**
```json
{
"event_name": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"user_id": 4,
"user_name": "John Smith",
"user_email": "john@example.com",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 15,
"project":{
"name":"Diaspora",
"description":"",
"web_url":"http://example.com/mike/diaspora",
"avatar_url":null,
"git_ssh_url":"git@example.com:mike/diaspora.git",
"git_http_url":"http://example.com/mike/diaspora.git",
"namespace":"Mike",
"visibility_level":0,
"path_with_namespace":"mike/diaspora",
"default_branch":"master",
"homepage":"http://example.com/mike/diaspora",
"url":"git@example.com:mike/diaspora.git",
"ssh_url":"git@example.com:mike/diaspora.git",
"http_url":"http://example.com/mike/diaspora.git"
},
"repository":{
"name": "Diaspora",
"url": "git@example.com:mike/diaspora.git",
"description": "",
"homepage": "http://example.com/mike/diaspora",
"git_http_url":"http://example.com/mike/diaspora.git",
"git_ssh_url":"git@example.com:mike/diaspora.git",
"visibility_level":0
},
"commits": [],
"total_commits_count": 0
}
```
## Tag events
Triggered when you create (or delete) tags to the repository.
**Request header**:
```
X-Gitlab-Event: System Hook
```
**Request body:**
```json
{
"event_name": "tag_push",
"before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"ref": "refs/tags/v1.0.0",
"checkout_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
"user_id": 1,
"user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 1,
"project":{
"name":"Example",
"description":"",
"web_url":"http://example.com/jsmith/example",
"avatar_url":null,
"git_ssh_url":"git@example.com:jsmith/example.git",
"git_http_url":"http://example.com/jsmith/example.git",
"namespace":"Jsmith",
"visibility_level":0,
"path_with_namespace":"jsmith/example",
"default_branch":"master",
"homepage":"http://example.com/jsmith/example",
"url":"git@example.com:jsmith/example.git",
"ssh_url":"git@example.com:jsmith/example.git",
"http_url":"http://example.com/jsmith/example.git"
},
"repository":{
"name": "Example",
"url": "ssh://git@example.com/jsmith/example.git",
"description": "",
"homepage": "http://example.com/jsmith/example",
"git_http_url":"http://example.com/jsmith/example.git",
"git_ssh_url":"git@example.com:jsmith/example.git",
"visibility_level":0
},
"commits": [],
"total_commits_count": 0
}
```
...@@ -41,6 +41,7 @@ X-Gitlab-Event: Push Hook ...@@ -41,6 +41,7 @@ X-Gitlab-Event: Push Hook
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22", "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"user_id": 4, "user_id": 4,
"user_name": "John Smith", "user_name": "John Smith",
"user_email": "john@example.com", "user_email": "john@example.com",
...@@ -118,9 +119,10 @@ X-Gitlab-Event: Tag Push Hook ...@@ -118,9 +119,10 @@ X-Gitlab-Event: Tag Push Hook
```json ```json
{ {
"object_kind": "tag_push", "object_kind": "tag_push",
"ref": "refs/tags/v1.0.0",
"before": "0000000000000000000000000000000000000000", "before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"ref": "refs/tags/v1.0.0",
"checkout_sha": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1, "user_id": 1,
"user_name": "John Smith", "user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
......
...@@ -2,7 +2,7 @@ module SharedIssuable ...@@ -2,7 +2,7 @@ module SharedIssuable
include Spinach::DSL include Spinach::DSL
def edit_issuable def edit_issuable
find(:css, '.issuable-edit').click find('.issuable-edit', visible: true).click
end end
step 'project "Community" has "Community issue" open issue' do step 'project "Community" has "Community issue" open issue' do
......
...@@ -23,6 +23,8 @@ module API ...@@ -23,6 +23,8 @@ module API
end end
post "/allowed" do post "/allowed" do
Gitlab::Metrics.tag_transaction('action', 'Grape#/internal/allowed')
status 200 status 200
actor = actor =
......
...@@ -12,7 +12,7 @@ module API ...@@ -12,7 +12,7 @@ module API
# Example Request: # Example Request:
# GET /projects/:id/repository/tags # GET /projects/:id/repository/tags
get ":id/repository/tags" do get ":id/repository/tags" do
present user_project.repo.tags.sort_by(&:name).reverse, present user_project.repository.tags.sort_by(&:name).reverse,
with: Entities::RepoTag, project: user_project with: Entities::RepoTag, project: user_project
end end
......
...@@ -11,6 +11,10 @@ module API ...@@ -11,6 +11,10 @@ module API
# GET /users?search=Admin # GET /users?search=Admin
# GET /users?username=root # GET /users?username=root
get do get do
unless can?(current_user, :read_users_list, nil)
render_api_error!("Not authorized.", 403)
end
if params[:username].present? if params[:username].present?
@users = User.where(username: params[:username]) @users = User.where(username: params[:username])
else else
...@@ -36,10 +40,12 @@ module API ...@@ -36,10 +40,12 @@ module API
get ":id" do get ":id" do
@user = User.find(params[:id]) @user = User.find(params[:id])
if current_user.is_admin? if current_user && current_user.is_admin?
present @user, with: Entities::UserFull present @user, with: Entities::UserFull
else elsif can?(current_user, :read_user, @user)
present @user, with: Entities::User present @user, with: Entities::User
else
render_api_error!("User not found.", 404)
end end
end end
......
...@@ -4,12 +4,12 @@ module Ci ...@@ -4,12 +4,12 @@ module Ci
DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test' DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
:allow_failure, :type, :stage, :when, :artifacts, :cache, :allow_failure, :type, :stage, :when, :artifacts, :cache,
:dependencies] :dependencies, :before_script, :after_script, :variables]
attr_reader :before_script, :image, :services, :variables, :path, :cache attr_reader :before_script, :after_script, :image, :services, :path, :cache
def initialize(config, path = nil) def initialize(config, path = nil)
@config = YAML.safe_load(config, [Symbol], [], true) @config = YAML.safe_load(config, [Symbol], [], true)
...@@ -40,10 +40,22 @@ module Ci ...@@ -40,10 +40,22 @@ module Ci
@stages || DEFAULT_STAGES @stages || DEFAULT_STAGES
end end
def global_variables
@variables
end
def job_variables(name)
job = @jobs[name.to_sym]
return [] unless job
job.fetch(:variables, [])
end
private private
def initial_parsing def initial_parsing
@before_script = @config[:before_script] || [] @before_script = @config[:before_script] || []
@after_script = @config[:after_script]
@image = @config[:image] @image = @config[:image]
@services = @config[:services] @services = @config[:services]
@stages = @config[:stages] || @config[:types] @stages = @config[:stages] || @config[:types]
...@@ -72,7 +84,7 @@ module Ci ...@@ -72,7 +84,7 @@ module Ci
{ {
stage_idx: stages.index(job[:stage]), stage_idx: stages.index(job[:stage]),
stage: job[:stage], stage: job[:stage],
commands: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}", commands: [job[:before_script] || @before_script, job[:script]].flatten.join("\n"),
tag_list: job[:tags] || [], tag_list: job[:tags] || [],
name: name, name: name,
only: job[:only], only: job[:only],
...@@ -85,23 +97,32 @@ module Ci ...@@ -85,23 +97,32 @@ module Ci
artifacts: job[:artifacts], artifacts: job[:artifacts],
cache: job[:cache] || @cache, cache: job[:cache] || @cache,
dependencies: job[:dependencies], dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact }.compact
} }
end end
def normalize_script(script) def validate!
if script.is_a? Array validate_global!
script.join("\n")
else @jobs.each do |name, job|
script validate_job!(name, job)
end end
true
end end
def validate! private
def validate_global!
unless validate_array_of_strings(@before_script) unless validate_array_of_strings(@before_script)
raise ValidationError, "before_script should be an array of strings" raise ValidationError, "before_script should be an array of strings"
end end
unless @after_script.nil? || validate_array_of_strings(@after_script)
raise ValidationError, "after_script should be an array of strings"
end
unless @image.nil? || @image.is_a?(String) unless @image.nil? || @image.is_a?(String)
raise ValidationError, "image should be a string" raise ValidationError, "image should be a string"
end end
...@@ -115,10 +136,13 @@ module Ci ...@@ -115,10 +136,13 @@ module Ci
end end
unless @variables.nil? || validate_variables(@variables) unless @variables.nil? || validate_variables(@variables)
raise ValidationError, "variables should be a map of key-valued strings" raise ValidationError, "variables should be a map of key-value strings"
end end
if @cache validate_global_cache! if @cache
end
def validate_global_cache!
if @cache[:key] && !validate_string(@cache[:key]) if @cache[:key] && !validate_string(@cache[:key])
raise ValidationError, "cache:key parameter should be a string" raise ValidationError, "cache:key parameter should be a string"
end end
...@@ -132,26 +156,19 @@ module Ci ...@@ -132,26 +156,19 @@ module Ci
end end
end end
@jobs.each do |name, job|
validate_job!(name, job)
end
true
end
def validate_job!(name, job) def validate_job!(name, job)
validate_job_name!(name) validate_job_name!(name)
validate_job_keys!(name, job) validate_job_keys!(name, job)
validate_job_types!(name, job) validate_job_types!(name, job)
validate_job_script!(name, job)
validate_job_stage!(name, job) if job[:stage] validate_job_stage!(name, job) if job[:stage]
validate_job_variables!(name, job) if job[:variables]
validate_job_cache!(name, job) if job[:cache] validate_job_cache!(name, job) if job[:cache]
validate_job_artifacts!(name, job) if job[:artifacts] validate_job_artifacts!(name, job) if job[:artifacts]
validate_job_dependencies!(name, job) if job[:dependencies] validate_job_dependencies!(name, job) if job[:dependencies]
end end
private
def validate_job_name!(name) def validate_job_name!(name)
if name.blank? || !validate_string(name) if name.blank? || !validate_string(name)
raise ValidationError, "job name should be non-empty string" raise ValidationError, "job name should be non-empty string"
...@@ -167,10 +184,6 @@ module Ci ...@@ -167,10 +184,6 @@ module Ci
end end
def validate_job_types!(name, job) def validate_job_types!(name, job)
if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
raise ValidationError, "#{name} job: script should be a string or an array of a strings"
end
if job[:image] && !validate_string(job[:image]) if job[:image] && !validate_string(job[:image])
raise ValidationError, "#{name} job: image should be a string" raise ValidationError, "#{name} job: image should be a string"
end end
...@@ -200,12 +213,33 @@ module Ci ...@@ -200,12 +213,33 @@ module Ci
end end
end end
def validate_job_script!(name, job)
if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
raise ValidationError, "#{name} job: script should be a string or an array of a strings"
end
if job[:before_script] && !validate_array_of_strings(job[:before_script])
raise ValidationError, "#{name} job: before_script should be an array of strings"
end
if job[:after_script] && !validate_array_of_strings(job[:after_script])
raise ValidationError, "#{name} job: after_script should be an array of strings"
end
end
def validate_job_stage!(name, job) def validate_job_stage!(name, job)
unless job[:stage].is_a?(String) && job[:stage].in?(stages) unless job[:stage].is_a?(String) && job[:stage].in?(stages)
raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}" raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
end end
end end
def validate_job_variables!(name, job)
unless validate_variables(job[:variables])
raise ValidationError,
"#{name} job: variables should be a map of key-value strings"
end
end
def validate_job_cache!(name, job) def validate_job_cache!(name, job)
if job[:cache][:key] && !validate_string(job[:cache][:key]) if job[:cache][:key] && !validate_string(job[:cache][:key])
raise ValidationError, "#{name} job: cache:key parameter should be a string" raise ValidationError, "#{name} job: cache:key parameter should be a string"
......
...@@ -16,7 +16,8 @@ module Gitlab ...@@ -16,7 +16,8 @@ module Gitlab
end end
def execute def execute
import_issues && import_pull_requests && import_wiki import_labels && import_milestones && import_issues &&
import_pull_requests && import_wiki
end end
private private
...@@ -25,6 +26,26 @@ module Gitlab ...@@ -25,6 +26,26 @@ module Gitlab
@import_data_credentials ||= project.import_data.credentials if project.import_data @import_data_credentials ||= project.import_data.credentials if project.import_data
end end
def import_labels
client.labels(project.import_source).each do |raw_data|
Label.create!(LabelFormatter.new(project, raw_data).attributes)
end
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
end
def import_milestones
client.list_milestones(project.import_source, state: :all).each do |raw_data|
Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes)
end
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
end
def import_issues def import_issues
client.list_issues(project.import_source, state: :all, client.list_issues(project.import_source, state: :all,
sort: :created, sort: :created,
...@@ -33,6 +54,7 @@ module Gitlab ...@@ -33,6 +54,7 @@ module Gitlab
if gh_issue.valid? if gh_issue.valid?
issue = Issue.create!(gh_issue.attributes) issue = Issue.create!(gh_issue.attributes)
apply_labels(gh_issue.number, issue)
if gh_issue.has_comments? if gh_issue.has_comments?
import_comments(gh_issue.number, issue) import_comments(gh_issue.number, issue)
...@@ -55,6 +77,7 @@ module Gitlab ...@@ -55,6 +77,7 @@ module Gitlab
merge_request = MergeRequest.new(pull_request.attributes) merge_request = MergeRequest.new(pull_request.attributes)
if merge_request.save if merge_request.save
apply_labels(pull_request.number, merge_request)
import_comments(pull_request.number, merge_request) import_comments(pull_request.number, merge_request)
import_comments_on_diff(pull_request.number, merge_request) import_comments_on_diff(pull_request.number, merge_request)
end end
...@@ -66,6 +89,18 @@ module Gitlab ...@@ -66,6 +89,18 @@ module Gitlab
raise Projects::ImportService::Error, e.message raise Projects::ImportService::Error, e.message
end end
def apply_labels(number, issuable)
issue = client.issue(project.import_source, number)
if issue.labels.count > 0
label_ids = issue.labels.map do |raw|
Label.find_by(LabelFormatter.new(project, raw).attributes).try(:id)
end
issuable.update_attribute(:label_ids, label_ids)
end
end
def import_comments(issue_number, noteable) def import_comments(issue_number, noteable)
comments = client.issue_comments(project.import_source, issue_number) comments = client.issue_comments(project.import_source, issue_number)
create_comments(comments, noteable) create_comments(comments, noteable)
......
...@@ -3,7 +3,9 @@ module Gitlab ...@@ -3,7 +3,9 @@ module Gitlab
class IssueFormatter < BaseFormatter class IssueFormatter < BaseFormatter
def attributes def attributes
{ {
iid: number,
project: project, project: project,
milestone: milestone,
title: raw_data.title, title: raw_data.title,
description: description, description: description,
state: state, state: state,
...@@ -54,6 +56,12 @@ module Gitlab ...@@ -54,6 +56,12 @@ module Gitlab
@formatter.author_line(author) + body @formatter.author_line(author) + body
end end
def milestone
if raw_data.milestone.present?
project.milestones.find_by(iid: raw_data.milestone.number)
end
end
def state def state
raw_data.state == 'closed' ? 'closed' : 'opened' raw_data.state == 'closed' ? 'closed' : 'opened'
end end
......
module Gitlab
module GithubImport
class LabelFormatter < BaseFormatter
def attributes
{
project: project,
title: title,
color: color
}
end
private
def color
"##{raw_data.color}"
end
def title
raw_data.name
end
end
end
end
module Gitlab
module GithubImport
class MilestoneFormatter < BaseFormatter
def attributes
{
iid: number,
project: project,
title: title,
description: description,
due_date: due_date,
state: state,
created_at: created_at,
updated_at: updated_at
}
end
private
def number
raw_data.number
end
def title
raw_data.title
end
def description
raw_data.description
end
def due_date
raw_data.due_on
end
def state
raw_data.state == 'closed' ? 'closed' : 'active'
end
def created_at
raw_data.created_at
end
def updated_at
state == 'closed' ? raw_data.closed_at : raw_data.updated_at
end
end
end
end
...@@ -3,6 +3,7 @@ module Gitlab ...@@ -3,6 +3,7 @@ module Gitlab
class PullRequestFormatter < BaseFormatter class PullRequestFormatter < BaseFormatter
def attributes def attributes
{ {
iid: number,
title: raw_data.title, title: raw_data.title,
description: description, description: description,
source_project: source_project, source_project: source_project,
...@@ -10,6 +11,7 @@ module Gitlab ...@@ -10,6 +11,7 @@ module Gitlab
target_project: target_project, target_project: target_project,
target_branch: target_branch.name, target_branch: target_branch.name,
state: state, state: state,
milestone: milestone,
author_id: author_id, author_id: author_id,
assignee_id: assignee_id, assignee_id: assignee_id,
created_at: raw_data.created_at, created_at: raw_data.created_at,
...@@ -57,6 +59,12 @@ module Gitlab ...@@ -57,6 +59,12 @@ module Gitlab
formatter.author_line(author) + body formatter.author_line(author) + body
end end
def milestone
if raw_data.milestone.present?
project.milestones.find_by(iid: raw_data.milestone.number)
end
end
def source_project def source_project
project project
end end
......
...@@ -11,6 +11,8 @@ module Gitlab ...@@ -11,6 +11,8 @@ module Gitlab
module Instrumentation module Instrumentation
SERIES = 'method_calls' SERIES = 'method_calls'
PROXY_IVAR = :@__gitlab_instrumentation_proxy
def self.configure def self.configure
yield self yield self
end end
...@@ -91,6 +93,18 @@ module Gitlab ...@@ -91,6 +93,18 @@ module Gitlab
end end
end end
# Returns true if a module is instrumented.
#
# mod - The module to check
def self.instrumented?(mod)
mod.instance_variable_defined?(PROXY_IVAR)
end
# Returns the proxy module (if any) of `mod`.
def self.proxy_module(mod)
mod.instance_variable_get(PROXY_IVAR)
end
# Instruments a method. # Instruments a method.
# #
# type - The type (:class or :instance) of method to instrument. # type - The type (:class or :instance) of method to instrument.
...@@ -100,7 +114,6 @@ module Gitlab ...@@ -100,7 +114,6 @@ module Gitlab
return unless Metrics.enabled? return unless Metrics.enabled?
name = name.to_sym name = name.to_sym
alias_name = :"_original_#{name}"
target = type == :instance ? mod : mod.singleton_class target = type == :instance ? mod : mod.singleton_class
if type == :instance if type == :instance
...@@ -113,6 +126,12 @@ module Gitlab ...@@ -113,6 +126,12 @@ module Gitlab
method = mod.method(name) method = mod.method(name)
end end
unless instrumented?(target)
target.instance_variable_set(PROXY_IVAR, Module.new)
end
proxy_module = self.proxy_module(target)
# Some code out there (e.g. the "state_machine" Gem) checks the arity of # Some code out there (e.g. the "state_machine" Gem) checks the arity of
# a method to make sure it only passes arguments when the method expects # a method to make sure it only passes arguments when the method expects
# any. If we were to always overwrite a method to take an `*args` # any. If we were to always overwrite a method to take an `*args`
...@@ -125,17 +144,13 @@ module Gitlab ...@@ -125,17 +144,13 @@ module Gitlab
args_signature = '*args, &block' args_signature = '*args, &block'
end end
send_signature = "__send__(#{alias_name.inspect}, #{args_signature})" proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
target.class_eval <<-EOF, __FILE__, __LINE__ + 1
alias_method #{alias_name.inspect}, #{name.inspect}
def #{name}(#{args_signature}) def #{name}(#{args_signature})
trans = Gitlab::Metrics::Instrumentation.transaction trans = Gitlab::Metrics::Instrumentation.transaction
if trans if trans
start = Time.now start = Time.now
retval = #{send_signature} retval = super
duration = (Time.now - start) * 1000.0 duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold if duration >= Gitlab::Metrics.method_call_threshold
...@@ -148,10 +163,12 @@ module Gitlab ...@@ -148,10 +163,12 @@ module Gitlab
retval retval
else else
#{send_signature} super
end end
end end
EOF EOF
target.prepend(proxy_module)
end end
# Small layer of indirection to make it easier to stub out the current # Small layer of indirection to make it easier to stub out the current
......
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
return unless current_transaction return unless current_transaction
current_transaction.increment(:sql_duration, event.duration) current_transaction.increment(:sql_duration, event.duration)
current_transaction.increment(:sql_count, 1)
end end
private private
......
...@@ -36,11 +36,12 @@ module Gitlab ...@@ -36,11 +36,12 @@ module Gitlab
commit.hook_attrs(with_changed_files: true) commit.hook_attrs(with_changed_files: true)
end end
type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
# Hash to be passed as post_receive_data # Hash to be passed as post_receive_data
data = { data = {
object_kind: type, object_kind: type,
event_name: type,
before: oldrev, before: oldrev,
after: newrev, after: newrev,
ref: ref, ref: ref,
......
...@@ -11,7 +11,7 @@ retry() { ...@@ -11,7 +11,7 @@ retry() {
return 1 return 1
} }
if [ -f /.dockerinit ]; then if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
mkdir -p vendor mkdir -p vendor
# Install phantomjs package # Install phantomjs package
......
require 'spec_helper'
describe Groups::GroupMembersController do
let(:user) { create(:user) }
let(:group) { create(:group) }
context "index" do
before do
group.add_owner(user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it 'renders index with group members' do
get :index, group_id: group.path
expect(response.status).to eq(200)
expect(response).to render_template(:index)
end
end
end
require 'spec_helper'
describe Projects::GroupLinksController do
let(:project) { create(:project, :private) }
let(:group) { create(:group, :private) }
let(:user) { create(:user) }
before do
project.team << [user, :master]
sign_in(user)
end
describe '#create' do
shared_context 'link project to group' do
before do
post(:create, namespace_id: project.namespace.to_param,
project_id: project.to_param,
link_group_id: group.id,
link_group_access: ProjectGroupLink.default_access)
end
end
context 'when user has access to group he want to link project to' do
before { group.add_developer(user) }
include_context 'link project to group'
it 'links project with selected group' do
expect(group.shared_projects).to include project
end
it 'redirects to project group links page'do
expect(response).to redirect_to(
namespace_project_group_links_path(project.namespace, project)
)
end
end
context 'when user doers not have access to group he want to link to' do
include_context 'link project to group'
it 'renders 404' do
expect(response.status).to eq 404
end
it 'does not share project with that group' do
expect(group.shared_projects).to_not include project
end
end
end
end
...@@ -33,7 +33,30 @@ describe UsersController do ...@@ -33,7 +33,30 @@ describe UsersController do
it 'renders the show template' do it 'renders the show template' do
get :show, username: user.username get :show, username: user.username
expect(response).to be_success expect(response.status).to eq(200)
expect(response).to render_template('show')
end
end
end
context 'when public visibility level is restricted' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
context 'when logged out' do
it 'renders 404' do
get :show, username: user.username
expect(response.status).to eq(404)
end
end
context 'when logged in' do
before { sign_in(user) }
it 'renders show' do
get :show, username: user.username
expect(response.status).to eq(200)
expect(response).to render_template('show') expect(response).to render_template('show')
end end
end end
......
...@@ -42,12 +42,10 @@ feature 'issue move to another project' do ...@@ -42,12 +42,10 @@ feature 'issue move to another project' do
expect(current_url).to include project_path(new_project) expect(current_url).to include project_path(new_project)
page.within('.issue') do
expect(page).to have_content("Text with #{cross_reference}!1") expect(page).to have_content("Text with #{cross_reference}!1")
expect(page).to have_content("Moved from #{cross_reference}#1") expect(page).to have_content("Moved from #{cross_reference}#1")
expect(page).to have_content(issue.title) expect(page).to have_content(issue.title)
end end
end
context 'projects user does not have permission to move issue to exist' do context 'projects user does not have permission to move issue to exist' do
let!(:private_project) { create(:project, :private) } let!(:private_project) { create(:project, :private) }
...@@ -74,7 +72,7 @@ feature 'issue move to another project' do ...@@ -74,7 +72,7 @@ feature 'issue move to another project' do
def edit_issue(issue) def edit_issue(issue)
visit issue_path(issue) visit issue_path(issue)
page.within('.issuable-header') { click_link 'Edit' } page.within('.issuable-actions') { first(:link, 'Edit').click }
end end
def issue_path(issue) def issue_path(issue)
......
...@@ -292,6 +292,23 @@ describe 'Issues', feature: true do ...@@ -292,6 +292,23 @@ describe 'Issues', feature: true do
end end
end end
describe 'new issue' do
context 'dropzone upload file', js: true do
before do
visit new_namespace_project_issue_path(project.namespace, project)
end
it 'should upload file when dragging into textarea' do
drop_in_dropzone test_image_file
# Wait for the file to upload
sleep 1
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
end
end
def first_issue def first_issue
page.all('ul.issues-list > li').first.text page.all('ul.issues-list > li').first.text
end end
...@@ -299,4 +316,25 @@ describe 'Issues', feature: true do ...@@ -299,4 +316,25 @@ describe 'Issues', feature: true do
def last_issue def last_issue
page.all('ul.issues-list > li').last.text page.all('ul.issues-list > li').last.text
end end
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
var fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type: 'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector with Capybara
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array and trigger the fake drop event
page.execute_script <<-JS
var fileList = [$('#fakeFileInput')[0].files[0]];
var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
$('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
JS
end
def test_image_file
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
end
end end
...@@ -80,6 +80,22 @@ describe "Runners" do ...@@ -80,6 +80,22 @@ describe "Runners" do
end end
end end
describe "shared runners description" do
let(:shared_runners_text) { 'custom **shared** runners description' }
let(:shared_runners_html) { 'custom shared runners description' }
before do
stub_application_setting(shared_runners_text: shared_runners_text)
project = FactoryGirl.create :empty_project, shared_runners_enabled: false
project.team << [user, :master]
visit runners_path(project)
end
it "sees shared runners description" do
expect(page.find(".shared-runners-description")).to have_content(shared_runners_html)
end
end
describe "show page" do describe "show page" do
before do before do
@project = FactoryGirl.create :empty_project @project = FactoryGirl.create :empty_project
......
require 'spec_helper'
feature 'Signup', feature: true do
describe 'signup with no errors' do
it 'creates the user account and sends a confirmation email' do
user = build(:user)
visit root_path
fill_in 'user_name', with: user.name
fill_in 'user_username', with: user.username
fill_in 'user_email', with: user.email
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
expect(current_path).to eq user_session_path
expect(page).to have_content("A message with a confirmation link has been sent to your email address.")
end
end
describe 'signup with errors' do
it "displays the errors" do
existing_user = create(:user)
user = build(:user)
visit root_path
fill_in 'user_name', with: user.name
fill_in 'user_username', with: user.username
fill_in 'user_email', with: existing_user.email
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
expect(page).to have_content("error prohibited this user from being saved")
expect(page).to have_content("Email has already been taken")
end
it 'does not redisplay the password' do
existing_user = create(:user)
user = build(:user)
visit root_path
fill_in 'user_name', with: user.name
fill_in 'user_username', with: user.username
fill_in 'user_email', with: existing_user.email
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
expect(page.body).not_to match(/#{user.password}/)
end
end
end
...@@ -287,6 +287,81 @@ module Ci ...@@ -287,6 +287,81 @@ module Ci
end end
describe "Scripts handling" do
let(:config_data) { YAML.dump(config) }
let(:config_processor) { GitlabCiYamlProcessor.new(config_data, path) }
subject { config_processor.builds_for_stage_and_ref("test", "master").first }
describe "before_script" do
context "in global context" do
let(:config) do
{
before_script: ["global script"],
test: { script: ["script"] }
}
end
it "return commands with scripts concencaced" do
expect(subject[:commands]).to eq("global script\nscript")
end
end
context "overwritten in local context" do
let(:config) do
{
before_script: ["global script"],
test: { before_script: ["local script"], script: ["script"] }
}
end
it "return commands with scripts concencaced" do
expect(subject[:commands]).to eq("local script\nscript")
end
end
end
describe "script" do
let(:config) do
{
test: { script: ["script"] }
}
end
it "return commands with scripts concencaced" do
expect(subject[:commands]).to eq("script")
end
end
describe "after_script" do
context "in global context" do
let(:config) do
{
after_script: ["after_script"],
test: { script: ["script"] }
}
end
it "return after_script in options" do
expect(subject[:options][:after_script]).to eq(["after_script"])
end
end
context "overwritten in local context" do
let(:config) do
{
after_script: ["local after_script"],
test: { after_script: ["local after_script"], script: ["script"] }
}
end
it "return after_script in options" do
expect(subject[:options][:after_script]).to eq(["local after_script"])
end
end
end
end
describe "Image and service handling" do describe "Image and service handling" do
it "returns image and service when defined" do it "returns image and service when defined" do
config = YAML.dump({ config = YAML.dump({
...@@ -345,20 +420,76 @@ module Ci ...@@ -345,20 +420,76 @@ module Ci
end end
end end
describe "Variables" do describe 'Variables' do
it "returns variables when defined" do context 'when global variables are defined' do
it 'returns global variables' do
variables = { variables = {
var1: "value1", VAR1: 'value1',
var2: "value2", VAR2: 'value2',
} }
config = YAML.dump({ config = YAML.dump({
variables: variables, variables: variables,
before_script: ["pwd"], before_script: ['pwd'],
rspec: { script: "rspec" } rspec: { script: 'rspec' }
}) })
config_processor = GitlabCiYamlProcessor.new(config, path) config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.variables).to eq(variables)
expect(config_processor.global_variables).to eq(variables)
end
end
context 'when job variables are defined' do
context 'when syntax is correct' do
it 'returns job variables' do
variables = {
KEY1: 'value1',
SOME_KEY_2: 'value2'
}
config = YAML.dump(
{ before_script: ['pwd'],
rspec: {
variables: variables,
script: 'rspec' }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.job_variables(:rspec)).to eq variables
end
end
context 'when syntax is incorrect' do
it 'raises error' do
variables = [:KEY1, 'value1', :KEY2, 'value2']
config = YAML.dump(
{ before_script: ['pwd'],
rspec: {
variables: variables,
script: 'rspec' }
})
expect { GitlabCiYamlProcessor.new(config, path) }
.to raise_error(GitlabCiYamlProcessor::ValidationError,
/job: variables should be a map/)
end
end
end
context 'when job variables are not defined' do
it 'returns empty array' do
config = YAML.dump({
before_script: ['pwd'],
rspec: { script: 'rspec' }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.job_variables(:rspec)).to eq []
end
end end
end end
...@@ -536,7 +667,7 @@ module Ci ...@@ -536,7 +667,7 @@ module Ci
stage_idx: 1, stage_idx: 1,
name: :normal_job, name: :normal_job,
only: nil, only: nil,
commands: "\ntest", commands: "test",
tag_list: [], tag_list: [],
options: {}, options: {},
when: "on_success", when: "on_success",
...@@ -563,7 +694,7 @@ EOT ...@@ -563,7 +694,7 @@ EOT
stage_idx: 1, stage_idx: 1,
name: :job1, name: :job1,
only: nil, only: nil,
commands: "\nexecute-script-for-job", commands: "execute-script-for-job",
tag_list: [], tag_list: [],
options: {}, options: {},
when: "on_success", when: "on_success",
...@@ -575,7 +706,7 @@ EOT ...@@ -575,7 +706,7 @@ EOT
stage_idx: 1, stage_idx: 1,
name: :job2, name: :job2,
only: nil, only: nil,
commands: "\nexecute-script-for-job", commands: "execute-script-for-job",
tag_list: [], tag_list: [],
options: {}, options: {},
when: "on_success", when: "on_success",
...@@ -607,6 +738,27 @@ EOT ...@@ -607,6 +738,27 @@ EOT
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings")
end end
it "returns errors if job before_script parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: before_script should be an array of strings")
end
it "returns errors if after_script parameter is invalid" do
config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "after_script should be an array of strings")
end
it "returns errors if job after_script parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: after_script should be an array of strings")
end
it "returns errors if image parameter is invalid" do it "returns errors if image parameter is invalid" do
config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
expect do expect do
...@@ -730,14 +882,14 @@ EOT ...@@ -730,14 +882,14 @@ EOT
config = YAML.dump({ variables: "test", rspec: { script: "test" } }) config = YAML.dump({ variables: "test", rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
end end
it "returns errors if variables is not a map of key-valued strings" do it "returns errors if variables is not a map of key-value strings" do
config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
end end
it "returns errors if job when is not on_success, on_failure or always" do it "returns errors if job when is not on_success, on_failure or always" do
......
...@@ -2,13 +2,14 @@ require 'spec_helper' ...@@ -2,13 +2,14 @@ require 'spec_helper'
describe Gitlab::GithubImport::IssueFormatter, lib: true do describe Gitlab::GithubImport::IssueFormatter, lib: true do
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') } let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do let(:base_data) do
{ {
number: 1347, number: 1347,
milestone: nil,
state: 'open', state: 'open',
title: 'Found a bug', title: 'Found a bug',
body: "I'm having a problem with this.", body: "I'm having a problem with this.",
...@@ -26,11 +27,13 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -26,11 +27,13 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
describe '#attributes' do describe '#attributes' do
context 'when issue is open' do context 'when issue is open' do
let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) } let(:raw_data) { double(base_data.merge(state: 'open')) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
iid: 1347,
project: project, project: project,
milestone: nil,
title: 'Found a bug', title: 'Found a bug',
description: "*Created by: octocat*\n\nI'm having a problem with this.", description: "*Created by: octocat*\n\nI'm having a problem with this.",
state: 'opened', state: 'opened',
...@@ -46,11 +49,13 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -46,11 +49,13 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
context 'when issue is closed' do context 'when issue is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') } let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) } let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
iid: 1347,
project: project, project: project,
milestone: nil,
title: 'Found a bug', title: 'Found a bug',
description: "*Created by: octocat*\n\nI'm having a problem with this.", description: "*Created by: octocat*\n\nI'm having a problem with this.",
state: 'closed', state: 'closed',
...@@ -65,7 +70,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -65,7 +70,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
context 'when it is assigned to someone' do context 'when it is assigned to someone' do
let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) } let(:raw_data) { double(base_data.merge(assignee: octocat)) }
it 'returns nil as assignee_id when is not a GitLab user' do it 'returns nil as assignee_id when is not a GitLab user' do
expect(issue.attributes.fetch(:assignee_id)).to be_nil expect(issue.attributes.fetch(:assignee_id)).to be_nil
...@@ -78,8 +83,23 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -78,8 +83,23 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
end end
context 'when it has a milestone' do
let(:milestone) { double(number: 45) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exist' do
expect(issue.attributes.fetch(:milestone)).to be_nil
end
it 'returns milestone when it exists' do
milestone = create(:milestone, project: project, iid: 45)
expect(issue.attributes.fetch(:milestone)).to eq milestone
end
end
context 'when author is a GitLab user' do context 'when author is a GitLab user' do
let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) } let(:raw_data) { double(base_data.merge(user: octocat)) }
it 'returns project#creator_id as author_id when is not a GitLab user' do it 'returns project#creator_id as author_id when is not a GitLab user' do
expect(issue.attributes.fetch(:author_id)).to eq project.creator_id expect(issue.attributes.fetch(:author_id)).to eq project.creator_id
...@@ -95,7 +115,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -95,7 +115,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
describe '#has_comments?' do describe '#has_comments?' do
context 'when number of comments is greater than zero' do context 'when number of comments is greater than zero' do
let(:raw_data) { OpenStruct.new(base_data.merge(comments: 1)) } let(:raw_data) { double(base_data.merge(comments: 1)) }
it 'returns true' do it 'returns true' do
expect(issue.has_comments?).to eq true expect(issue.has_comments?).to eq true
...@@ -103,7 +123,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -103,7 +123,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
context 'when number of comments is equal to zero' do context 'when number of comments is equal to zero' do
let(:raw_data) { OpenStruct.new(base_data.merge(comments: 0)) } let(:raw_data) { double(base_data.merge(comments: 0)) }
it 'returns false' do it 'returns false' do
expect(issue.has_comments?).to eq false expect(issue.has_comments?).to eq false
...@@ -112,7 +132,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -112,7 +132,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
describe '#number' do describe '#number' do
let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) } let(:raw_data) { double(base_data.merge(number: 1347)) }
it 'returns pull request number' do it 'returns pull request number' do
expect(issue.number).to eq 1347 expect(issue.number).to eq 1347
...@@ -121,7 +141,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -121,7 +141,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
describe '#valid?' do describe '#valid?' do
context 'when mention a pull request' do context 'when mention a pull request' do
let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: OpenStruct.new)) } let(:raw_data) { double(base_data.merge(pull_request: double)) }
it 'returns false' do it 'returns false' do
expect(issue.valid?).to eq false expect(issue.valid?).to eq false
...@@ -129,7 +149,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -129,7 +149,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
context 'when does not mention a pull request' do context 'when does not mention a pull request' do
let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: nil)) } let(:raw_data) { double(base_data.merge(pull_request: nil)) }
it 'returns true' do it 'returns true' do
expect(issue.valid?).to eq true expect(issue.valid?).to eq true
......
require 'spec_helper'
describe Gitlab::GithubImport::LabelFormatter, lib: true do
describe '#attributes' do
it 'returns formatted attributes' do
project = create(:project)
raw = double(name: 'improvements', color: 'e6e6e6')
formatter = described_class.new(project, raw)
expect(formatter.attributes).to eq({
project: project,
title: 'improvements',
color: '#e6e6e6'
})
end
end
end
require 'spec_helper'
describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
let(:project) { create(:empty_project) }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do
{
number: 1347,
state: 'open',
title: '1.0',
description: 'Version 1.0',
due_on: nil,
created_at: created_at,
updated_at: updated_at,
closed_at: nil
}
end
subject(:formatter) { described_class.new(project, raw_data)}
describe '#attributes' do
context 'when milestone is open' do
let(:raw_data) { double(base_data.merge(state: 'open')) }
it 'returns formatted attributes' do
expected = {
iid: 1347,
project: project,
title: '1.0',
description: 'Version 1.0',
state: 'active',
due_date: nil,
created_at: created_at,
updated_at: updated_at
}
expect(formatter.attributes).to eq(expected)
end
end
context 'when milestone is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
it 'returns formatted attributes' do
expected = {
iid: 1347,
project: project,
title: '1.0',
description: 'Version 1.0',
state: 'closed',
due_date: nil,
created_at: created_at,
updated_at: closed_at
}
expect(formatter.attributes).to eq(expected)
end
end
context 'when milestone has a due date' do
let(:due_date) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { double(base_data.merge(due_on: due_date)) }
it 'returns formatted attributes' do
expected = {
iid: 1347,
project: project,
title: '1.0',
description: 'Version 1.0',
state: 'active',
due_date: due_date,
created_at: created_at,
updated_at: updated_at
}
expect(formatter.attributes).to eq(expected)
end
end
end
end
...@@ -2,17 +2,18 @@ require 'spec_helper' ...@@ -2,17 +2,18 @@ require 'spec_helper'
describe Gitlab::GithubImport::PullRequestFormatter, lib: true do describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:repository) { OpenStruct.new(id: 1, fork: false) } let(:repository) { double(id: 1, fork: false) }
let(:source_repo) { repository } let(:source_repo) { repository }
let(:source_branch) { OpenStruct.new(ref: 'feature', repo: source_repo) } let(:source_branch) { double(ref: 'feature', repo: source_repo) }
let(:target_repo) { repository } let(:target_repo) { repository }
let(:target_branch) { OpenStruct.new(ref: 'master', repo: target_repo) } let(:target_branch) { double(ref: 'master', repo: target_repo) }
let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') } let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do let(:base_data) do
{ {
number: 1347, number: 1347,
milestone: nil,
state: 'open', state: 'open',
title: 'New feature', title: 'New feature',
body: 'Please pull these awesome changes', body: 'Please pull these awesome changes',
...@@ -31,10 +32,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -31,10 +32,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
describe '#attributes' do describe '#attributes' do
context 'when pull request is open' do context 'when pull request is open' do
let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) } let(:raw_data) { double(base_data.merge(state: 'open')) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
iid: 1347,
title: 'New feature', title: 'New feature',
description: "*Created by: octocat*\n\nPlease pull these awesome changes", description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project, source_project: project,
...@@ -42,6 +44,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -42,6 +44,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
target_project: project, target_project: project,
target_branch: 'master', target_branch: 'master',
state: 'opened', state: 'opened',
milestone: nil,
author_id: project.creator_id, author_id: project.creator_id,
assignee_id: nil, assignee_id: nil,
created_at: created_at, created_at: created_at,
...@@ -54,10 +57,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -54,10 +57,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
context 'when pull request is closed' do context 'when pull request is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') } let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) } let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
iid: 1347,
title: 'New feature', title: 'New feature',
description: "*Created by: octocat*\n\nPlease pull these awesome changes", description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project, source_project: project,
...@@ -65,6 +69,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -65,6 +69,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
target_project: project, target_project: project,
target_branch: 'master', target_branch: 'master',
state: 'closed', state: 'closed',
milestone: nil,
author_id: project.creator_id, author_id: project.creator_id,
assignee_id: nil, assignee_id: nil,
created_at: created_at, created_at: created_at,
...@@ -77,10 +82,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -77,10 +82,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
context 'when pull request is merged' do context 'when pull request is merged' do
let(:merged_at) { DateTime.strptime('2011-01-28T13:01:12Z') } let(:merged_at) { DateTime.strptime('2011-01-28T13:01:12Z') }
let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', merged_at: merged_at)) } let(:raw_data) { double(base_data.merge(state: 'closed', merged_at: merged_at)) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
iid: 1347,
title: 'New feature', title: 'New feature',
description: "*Created by: octocat*\n\nPlease pull these awesome changes", description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project, source_project: project,
...@@ -88,6 +94,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -88,6 +94,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
target_project: project, target_project: project,
target_branch: 'master', target_branch: 'master',
state: 'merged', state: 'merged',
milestone: nil,
author_id: project.creator_id, author_id: project.creator_id,
assignee_id: nil, assignee_id: nil,
created_at: created_at, created_at: created_at,
...@@ -99,7 +106,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -99,7 +106,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when it is assigned to someone' do context 'when it is assigned to someone' do
let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) } let(:raw_data) { double(base_data.merge(assignee: octocat)) }
it 'returns nil as assignee_id when is not a GitLab user' do it 'returns nil as assignee_id when is not a GitLab user' do
expect(pull_request.attributes.fetch(:assignee_id)).to be_nil expect(pull_request.attributes.fetch(:assignee_id)).to be_nil
...@@ -113,7 +120,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -113,7 +120,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when author is a GitLab user' do context 'when author is a GitLab user' do
let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) } let(:raw_data) { double(base_data.merge(user: octocat)) }
it 'returns project#creator_id as author_id when is not a GitLab user' do it 'returns project#creator_id as author_id when is not a GitLab user' do
expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id
...@@ -125,10 +132,25 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -125,10 +132,25 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
end end
end end
context 'when it has a milestone' do
let(:milestone) { double(number: 45) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exists' do
expect(pull_request.attributes.fetch(:milestone)).to be_nil
end
it 'returns milestone when is exists' do
milestone = create(:milestone, project: project, iid: 45)
expect(pull_request.attributes.fetch(:milestone)).to eq milestone
end
end
end end
describe '#number' do describe '#number' do
let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) } let(:raw_data) { double(base_data.merge(number: 1347)) }
it 'returns pull request number' do it 'returns pull request number' do
expect(pull_request.number).to eq 1347 expect(pull_request.number).to eq 1347
...@@ -136,11 +158,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -136,11 +158,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
describe '#valid?' do describe '#valid?' do
let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') } let(:invalid_branch) { double(ref: 'invalid-branch').as_null_object }
context 'when source, and target repositories are the same' do context 'when source, and target repositories are the same' do
context 'and source and target branches exists' do context 'and source and target branches exists' do
let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) } let(:raw_data) { double(base_data.merge(head: source_branch, base: target_branch)) }
it 'returns true' do it 'returns true' do
expect(pull_request.valid?).to eq true expect(pull_request.valid?).to eq true
...@@ -148,7 +170,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -148,7 +170,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'and source branch doesn not exists' do context 'and source branch doesn not exists' do
let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) } let(:raw_data) { double(base_data.merge(head: invalid_branch, base: target_branch)) }
it 'returns false' do it 'returns false' do
expect(pull_request.valid?).to eq false expect(pull_request.valid?).to eq false
...@@ -156,7 +178,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -156,7 +178,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'and target branch doesn not exists' do context 'and target branch doesn not exists' do
let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) } let(:raw_data) { double(base_data.merge(head: source_branch, base: invalid_branch)) }
it 'returns false' do it 'returns false' do
expect(pull_request.valid?).to eq false expect(pull_request.valid?).to eq false
...@@ -165,8 +187,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -165,8 +187,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when source repo is a fork' do context 'when source repo is a fork' do
let(:source_repo) { OpenStruct.new(id: 2, fork: true) } let(:source_repo) { double(id: 2, fork: true) }
let(:raw_data) { OpenStruct.new(base_data) } let(:raw_data) { double(base_data) }
it 'returns false' do it 'returns false' do
expect(pull_request.valid?).to eq false expect(pull_request.valid?).to eq false
...@@ -174,8 +196,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -174,8 +196,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when target repo is a fork' do context 'when target repo is a fork' do
let(:target_repo) { OpenStruct.new(id: 2, fork: true) } let(:target_repo) { double(id: 2, fork: true) }
let(:raw_data) { OpenStruct.new(base_data) } let(:raw_data) { double(base_data) }
it 'returns false' do it 'returns false' do
expect(pull_request.valid?).to eq false expect(pull_request.valid?).to eq false
......
...@@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do
described_class.instrument_method(@dummy, :foo) described_class.instrument_method(@dummy, :foo)
end end
it 'renames the original method' do it 'instruments the Class' do
expect(@dummy).to respond_to(:_original_foo) target = @dummy.singleton_class
expect(described_class.instrumented?(target)).to eq(true)
end
it 'defines a proxy method' do
mod = described_class.proxy_module(@dummy.singleton_class)
expect(mod.method_defined?(:foo)).to eq(true)
end end
it 'calls the instrumented method with the correct arguments' do it 'calls the instrumented method with the correct arguments' do
...@@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do
expect(dummy.method(:test).arity).to eq(0) expect(dummy.method(:test).arity).to eq(0)
end end
describe 'when a module is instrumented multiple times' do
it 'calls the instrumented method with the correct arguments' do
described_class.instrument_method(@dummy, :foo)
expect(@dummy.foo).to eq('foo')
end
end
end end
describe 'with metrics disabled' do describe 'with metrics disabled' do
...@@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do
it 'does not instrument the method' do it 'does not instrument the method' do
described_class.instrument_method(@dummy, :foo) described_class.instrument_method(@dummy, :foo)
expect(@dummy).to_not respond_to(:_original_foo) target = @dummy.singleton_class
expect(described_class.instrumented?(target)).to eq(false)
end end
end end
end end
...@@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do
instrument_instance_method(@dummy, :bar) instrument_instance_method(@dummy, :bar)
end end
it 'renames the original method' do it 'instruments instances of the Class' do
expect(@dummy.method_defined?(:_original_bar)).to eq(true) expect(described_class.instrumented?(@dummy)).to eq(true)
end
it 'defines a proxy method' do
mod = described_class.proxy_module(@dummy)
expect(mod.method_defined?(:bar)).to eq(true)
end end
it 'calls the instrumented method with the correct arguments' do it 'calls the instrumented method with the correct arguments' do
...@@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do
described_class. described_class.
instrument_instance_method(@dummy, :bar) instrument_instance_method(@dummy, :bar)
expect(@dummy.method_defined?(:_original_bar)).to eq(false) expect(described_class.instrumented?(@dummy)).to eq(false)
end end
end end
end end
...@@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do
it 'recursively instruments a class hierarchy' do it 'recursively instruments a class hierarchy' do
described_class.instrument_class_hierarchy(@dummy) described_class.instrument_class_hierarchy(@dummy)
expect(@child1).to respond_to(:_original_child1_foo) expect(described_class.instrumented?(@child1.singleton_class)).to eq(true)
expect(@child2).to respond_to(:_original_child2_foo) expect(described_class.instrumented?(@child2.singleton_class)).to eq(true)
expect(@child1.method_defined?(:_original_child1_bar)).to eq(true) expect(described_class.instrumented?(@child1)).to eq(true)
expect(@child2.method_defined?(:_original_child2_bar)).to eq(true) expect(described_class.instrumented?(@child2)).to eq(true)
end end
it 'does not instrument the root module' do it 'does not instrument the root module' do
described_class.instrument_class_hierarchy(@dummy) described_class.instrument_class_hierarchy(@dummy)
expect(@dummy).to_not respond_to(:_original_foo) expect(described_class.instrumented?(@dummy)).to eq(false)
expect(@dummy.method_defined?(:_original_bar)).to eq(false)
end end
end end
...@@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do
it 'instruments all public class methods' do it 'instruments all public class methods' do
described_class.instrument_methods(@dummy) described_class.instrument_methods(@dummy)
expect(@dummy).to respond_to(:_original_foo) expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
end end
it 'only instruments methods directly defined in the module' do it 'only instruments methods directly defined in the module' do
...@@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do
it 'instruments all public instance methods' do it 'instruments all public instance methods' do
described_class.instrument_instance_methods(@dummy) described_class.instrument_instance_methods(@dummy)
expect(@dummy.method_defined?(:_original_bar)).to eq(true) expect(described_class.instrumented?(@dummy)).to eq(true)
end end
it 'only instruments methods directly defined in the module' do it 'only instruments methods directly defined in the module' do
......
...@@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do ...@@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
expect(transaction).to receive(:increment). expect(transaction).to receive(:increment).
with(:sql_duration, 0.2) with(:sql_duration, 0.2)
expect(transaction).to receive(:increment).
with(:sql_count, 1)
subscriber.sql(event) subscriber.sql(event)
end end
end end
......
...@@ -14,11 +14,11 @@ describe Gitlab::PushDataBuilder, lib: true do ...@@ -14,11 +14,11 @@ describe Gitlab::PushDataBuilder, lib: true do
it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) } it { expect(data[:commits].size).to eq(3) }
it { expect(data[:total_commits_count]).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) } it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) } it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) }
it { expect(data[:commits].first[:removed]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) }
include_examples 'project hook data' include_examples 'project hook data with deprecateds'
include_examples 'deprecated repository hook data' include_examples 'deprecated repository hook data'
end end
...@@ -34,9 +34,18 @@ describe Gitlab::PushDataBuilder, lib: true do ...@@ -34,9 +34,18 @@ describe Gitlab::PushDataBuilder, lib: true do
it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') } it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') }
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:user_id]).to eq(user.id) }
it { expect(data[:user_name]).to eq(user.name) }
it { expect(data[:user_email]).to eq(user.email) }
it { expect(data[:user_avatar]).to eq(user.avatar_url) }
it { expect(data[:project_id]).to eq(project.id) }
it { expect(data[:project]).to be_a(Hash) }
it { expect(data[:commits]).to be_empty } it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero } it { expect(data[:total_commits_count]).to be_zero }
include_examples 'project hook data with deprecateds'
include_examples 'deprecated repository hook data'
it 'does not raise an error when given nil commits' do it 'does not raise an error when given nil commits' do
expect { described_class.build(spy, spy, spy, spy, spy, nil) }. expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
not_to raise_error not_to raise_error
......
...@@ -15,7 +15,7 @@ describe RepositoryCheckMailer do ...@@ -15,7 +15,7 @@ describe RepositoryCheckMailer do
it 'mentions the number of failed checks' do it 'mentions the number of failed checks' do
mail = described_class.notify(3) mail = described_class.notify(3)
expect(mail).to have_subject '3 projects failed their last repository check' expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check'
end end
end end
end end
...@@ -238,6 +238,22 @@ describe Ci::Build, models: true do ...@@ -238,6 +238,22 @@ describe Ci::Build, models: true do
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) } it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end end
context 'when job variables are defined' do
##
# Job-level variables are defined in gitlab_ci.yml fixture
#
context 'when job variables are unique' do
let(:build) { create(:ci_build, name: 'staging') }
it 'includes job variables' do
expect(subject).to include(
{ key: :KEY1, value: 'value1', public: true },
{ key: :KEY2, value: 'value2', public: true }
)
end
end
end
end end
end end
end end
......
...@@ -36,4 +36,19 @@ describe ExternalIssue, models: true do ...@@ -36,4 +36,19 @@ describe ExternalIssue, models: true do
expect(issue.title).to eq "External Issue #{issue}" expect(issue.title).to eq "External Issue #{issue}"
end end
end end
describe '#reference_link_text' do
context 'if issue id has a prefix' do
it 'returns the issue ID' do
expect(issue.reference_link_text).to eq 'EXT-1234'
end
end
context 'if issue id is a number' do
let(:issue) { described_class.new('1234', project) }
it 'returns the issue ID prefixed by #' do
expect(issue.reference_link_text).to eq '#1234'
end
end
end
end end
...@@ -910,9 +910,32 @@ describe Repository, models: true do ...@@ -910,9 +910,32 @@ describe Repository, models: true do
end end
end end
describe '.clean_old_archives' do
let(:path) { Gitlab.config.gitlab.repository_downloads_path }
context 'when the downloads directory does not exist' do
it 'does not remove any archives' do
expect(File).to receive(:directory?).with(path).and_return(false)
expect(Gitlab::Popen).not_to receive(:popen)
described_class.clean_old_archives
end
end
context 'when the downloads directory exists' do
it 'removes old archives' do
expect(File).to receive(:directory?).with(path).and_return(true)
expect(Gitlab::Popen).to receive(:popen)
described_class.clean_old_archives
end
end
end
def create_remote_branch(remote_name, branch_name, target) def create_remote_branch(remote_name, branch_name, target)
rugged = repository.rugged rugged = repository.rugged
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
end end
end end
...@@ -32,9 +32,11 @@ describe API::API, api: true do ...@@ -32,9 +32,11 @@ describe API::API, api: true do
it "should return an array of project tags with release info" do it "should return an array of project tags with release info" do
get api("/projects/#{project.id}/repository/tags", user) get api("/projects/#{project.id}/repository/tags", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name) expect(json_response.first['name']).to eq(tag_name)
expect(json_response.first['message']).to eq('Version 1.1.0')
expect(json_response.first['release']['description']).to eq(description) expect(json_response.first['release']['description']).to eq(description)
end end
end end
......
...@@ -20,6 +20,24 @@ describe API::API, api: true do ...@@ -20,6 +20,24 @@ describe API::API, api: true do
end end
context "when authenticated" do context "when authenticated" do
#These specs are written just in case API authentication is not required anymore
context "when public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true)
end
it "renders 403" do
get api("/users")
expect(response.status).to eq(403)
end
it "renders 404" do
get api("/users/#{user.id}")
expect(response.status).to eq(404)
end
end
it "should return an array of users" do it "should return an array of users" do
get api("/users", user) get api("/users", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
......
...@@ -5,19 +5,17 @@ describe GitTagPushService, services: true do ...@@ -5,19 +5,17 @@ describe GitTagPushService, services: true do
let(:user) { create :user } let(:user) { create :user }
let(:project) { create :project } let(:project) { create :project }
let(:service) { GitTagPushService.new } let(:service) { GitTagPushService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
before do let(:oldrev) { Gitlab::Git::BLANK_SHA }
@oldrev = Gitlab::Git::BLANK_SHA let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
@newrev = "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" # gitlab-test: git rev-parse refs/tags/v1.1.0 let(:ref) { 'refs/tags/v1.1.0' }
@ref = 'refs/tags/v1.1.0'
end
describe "Git Tag Push Data" do describe "Git Tag Push Data" do
before do before do
service.execute(project, user, @oldrev, @newrev, @ref) service.execute
@push_data = service.push_data @push_data = service.push_data
@tag_name = Gitlab::Git.ref_name(@ref) @tag_name = Gitlab::Git.ref_name(ref)
@tag = project.repository.find_tag(@tag_name) @tag = project.repository.find_tag(@tag_name)
@commit = project.commit(@tag.target) @commit = project.commit(@tag.target)
end end
...@@ -25,9 +23,9 @@ describe GitTagPushService, services: true do ...@@ -25,9 +23,9 @@ describe GitTagPushService, services: true do
subject { @push_data } subject { @push_data }
it { is_expected.to include(object_kind: 'tag_push') } it { is_expected.to include(object_kind: 'tag_push') }
it { is_expected.to include(ref: @ref) } it { is_expected.to include(ref: ref) }
it { is_expected.to include(before: @oldrev) } it { is_expected.to include(before: oldrev) }
it { is_expected.to include(after: @newrev) } it { is_expected.to include(after: newrev) }
it { is_expected.to include(message: @tag.message) } it { is_expected.to include(message: @tag.message) }
it { is_expected.to include(user_id: user.id) } it { is_expected.to include(user_id: user.id) }
it { is_expected.to include(user_name: user.name) } it { is_expected.to include(user_name: user.name) }
...@@ -80,9 +78,11 @@ describe GitTagPushService, services: true do ...@@ -80,9 +78,11 @@ describe GitTagPushService, services: true do
describe "Webhooks" do describe "Webhooks" do
context "execute webhooks" do context "execute webhooks" do
let(:service) { GitTagPushService.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') }
it "when pushing tags" do it "when pushing tags" do
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0') service.execute
end end
end end
end end
......
...@@ -33,6 +33,9 @@ spinach: ...@@ -33,6 +33,9 @@ spinach:
- tags - tags
staging: staging:
variables:
KEY1: value1
KEY2: value2
script: "cap deploy stating" script: "cap deploy stating"
type: deploy type: deploy
tags: tags:
...@@ -42,6 +45,8 @@ staging: ...@@ -42,6 +45,8 @@ staging:
- stable - stable
production: production:
variables:
DB_NAME: mysql
type: deploy type: deploy
script: script:
- cap deploy production - cap deploy production
......
RSpec.shared_examples 'project hook data' do |project_key: :project| RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :project|
it 'contains project data' do it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name) expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description) expect(data[project_key][:description]).to eq(project.description)
...@@ -17,6 +17,21 @@ RSpec.shared_examples 'project hook data' do |project_key: :project| ...@@ -17,6 +17,21 @@ RSpec.shared_examples 'project hook data' do |project_key: :project|
end end
end end
RSpec.shared_examples 'project hook data' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description)
expect(data[project_key][:web_url]).to eq(project.web_url)
expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
expect(data[project_key][:namespace]).to eq(project.namespace.name)
expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
expect(data[project_key][:default_branch]).to eq(project.default_branch)
end
end
RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project| RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
it 'contains deprecated repository data' do it 'contains deprecated repository data' do
expect(data[:repository][:name]).to eq(project.name) expect(data[:repository][:name]).to eq(project.name)
......
require 'spec_helper'
require 'fileutils'
describe RepositoryCheck::SingleRepositoryWorker do
subject { described_class.new }
it 'fails if the wiki repository is broken' do
project = create(:project_empty_repo, wiki_enabled: true)
project.create_wiki
# Test sanity: everything should be fine before the wiki repo is broken
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
destroy_wiki(project)
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(true)
end
it 'skips wikis when disabled' do
project = create(:project_empty_repo, wiki_enabled: false)
# Make sure the test would fail if it checked the wiki repo
destroy_wiki(project)
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
end
def destroy_wiki(project)
FileUtils.rm_rf(project.wiki.repository.path_to_repo)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment