Commit f6fbd0fb authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'master' into 8-7-stable

In doing this I'm breaking my own rules of not merging master into a
stable branch during the feature freeze. I'm breaking this rule for two
reasons:

1. Somewhere down the line I messed up picking commits into the stable
   branch and as a result I basically can't merge anything anymore
   without resolving 9000 merge conflicts.

2. All merge requests merged into master had the "Pick into Stable"
   label anyway except for one commit that bumps the version. As such
   merging master into this branch is not really different from picking
   things manually.
parents 83e7e5c0 18a8844f
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)
- The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse) - The number of InfluxDB points stored per UDP packet can now be configured
- Fix revoking of authorized OAuth applications (Connor Shea) - Transactions for /internal/allowed now have an "action" tag set
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Method instrumentation now uses Module#prepend instead of aliasing methods
- Developers can now add custom tags to transactions (Yorick Peterse) - Repository.clean_old_archives is now instrumented
- Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) - 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
- All service classes (those residing in app/services) are now instrumented
- Developers can now add custom tags to transactions
- Loading of an issue's referenced merge requests and related branches is now done asynchronously
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- 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 (Yorick Peterse) - 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)
...@@ -42,18 +51,22 @@ v 8.7.0 (unreleased) ...@@ -42,18 +51,22 @@ v 8.7.0 (unreleased)
- Add default scope to projects to exclude projects pending deletion - Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted. - Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService - Ensure empty recipients are rejected in BuildsEmailService
- 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
- Fix high CPU usage when PostReceive receives refs/merge-requests/<id> - Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
- Hide `Create a group` help block when creating a new project in a group - Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
- Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- 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)
...@@ -62,18 +75,39 @@ v 8.7.0 (unreleased) ...@@ -62,18 +75,39 @@ v 8.7.0 (unreleased)
- API: Do not leak group existence via return code (Robert Schilling) - API: Do not leak group existence via return code (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
- Update number of Todos in the sidebar when it's marked as "Done". !3600 - Update number of Todos in the sidebar when it's marked as "Done". !3600
- Sanitize branch names created for confidential issues
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling)
- API: User can leave a project through the API when not master or owner. !3613 - API: User can leave a project through the API when not master or owner. !3613
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms - Improved markdown forms
- Delete tags using Rugged for performance reasons (Robert Schilling)
- 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
- API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling)
- Add encrypted credentials for imported projects and migrate old ones
- 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 - 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
- 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 error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Project switcher uses new dropdown styling - 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
...@@ -273,7 +307,7 @@ v 8.5.1 ...@@ -273,7 +307,7 @@ v 8.5.1
v 8.5.0 v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse) - Cache various Repository methods to improve performance
- Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu) - Update New Relic gem to 3.14.1.311 (Stan Hu)
......
...@@ -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)
......
8.7.0-rc5 8.7.0-rc5
...@@ -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
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false
dataSource: '' dataSource: ''
# Emoji # Emoji
...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete = ...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input. # Add GFM auto-completion to all input fields, that accept GFM input.
setup: -> setup: (wrap) ->
input = $('.js-gfm-input') @input = $('.js-gfm-input')
# destroy previous instances
@destroyAtWho()
# set up instances
@setupAtWho()
if @dataSource
if !@dataLoading
@dataLoading = true
# We should wait until initializations are done
# and only trigger the last .setup since
# The previous .dataSource belongs to the previous issuable
# and the last one will have the **proper** .dataSource property
# TODO: Make this a singleton and turn off events when moving to another page
setTimeout( =>
fetch = @fetchData(@dataSource)
fetch.done (data) =>
@dataLoading = false
@loadData(data)
, 1000)
setupAtWho: ->
# Emoji # Emoji
input.atwho @input.atwho
at: ':' at: ':'
displayTpl: @Emoji.template displayTpl: @Emoji.template
insertTpl: ':${name}:' insertTpl: ':${name}:'
# Team Members # Team Members
input.atwho @input.atwho
at: '@' at: '@'
displayTpl: @Members.template displayTpl: @Members.template
insertTpl: '${atwho-at}${username}' insertTpl: '${atwho-at}${username}'
...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete = ...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title) title: sanitize(title)
search: sanitize("#{m.username} #{m.name}") search: sanitize("#{m.username} #{m.name}")
input.atwho @input.atwho
at: '#' at: '#'
alias: 'issues' alias: 'issues'
searchKey: 'search' searchKey: 'search'
...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete = ...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title) title: sanitize(i.title)
search: "#{i.iid} #{i.title}" search: "#{i.iid} #{i.title}"
input.atwho @input.atwho
at: '!' at: '!'
alias: 'mergerequests' alias: 'mergerequests'
searchKey: 'search' searchKey: 'search'
...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete = ...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
if @dataSource destroyAtWho: ->
$.getJSON(@dataSource).done (data) -> @input.atwho('destroy')
fetchData: (dataSource) ->
$.getJSON(dataSource)
loadData: (data) ->
# load members # load members
input.atwho 'load', '@', data.members @input.atwho 'load', '@', data.members
# load issues # load issues
input.atwho 'load', 'issues', data.issues @input.atwho 'load', 'issues', data.issues
# load merge requests # load merge requests
input.atwho 'load', 'mergerequests', data.mergerequests @input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis # load emojis
input.atwho 'load', ':', data.emojis @input.atwho 'load', ':', data.emojis
...@@ -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')
......
...@@ -12,6 +12,7 @@ class @UsersSelect ...@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user') showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user') showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user') firstUser = $dropdown.data('first-user')
@authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected') selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate') issueURL = $dropdown.data('issueUpdate')
...@@ -207,6 +208,7 @@ class @UsersSelect ...@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id') @projectId = $(select).data('project-id')
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user') @showCurrentUser = $(select).data('current-user')
@authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user') showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user') showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user') showEmailUser = $(select).data('email-user')
...@@ -312,6 +314,7 @@ class @UsersSelect ...@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId project_id: @projectId
group_id: @groupId group_id: @groupId
current_user: @showCurrentUser current_user: @showCurrentUser
author_id: @authorId
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)
......
...@@ -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;
} }
} }
......
...@@ -75,6 +75,11 @@ li.commit { ...@@ -75,6 +75,11 @@ li.commit {
} }
} }
.item-title {
display: inline-block;
max-width: 70%;
}
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid #eee; border-left: 1px solid #eee;
......
.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,
...@@ -92,6 +93,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -92,6 +93,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key, :akismet_api_key,
:email_author_in_body, :email_author_in_body,
:repository_checks_enabled, :repository_checks_enabled,
:metrics_packet_size,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
...@@ -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
...@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController ...@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user] && current_user if params[:current_user] && current_user
@users = [*@users, current_user].uniq @users = [*@users, current_user]
end end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
@users = [author, *@users] if author
end
@users.uniq!
end end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
......
...@@ -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
......
...@@ -128,10 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -128,10 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def related_branches def related_branches
merge_requests = @issue.referenced_merge_requests(current_user) @related_branches = @issue.related_branches(current_user)
@related_branches = @issue.related_branches -
merge_requests.map(&:source_branch)
respond_to do |format| respond_to do |format|
format.json do format.json do
......
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
......
...@@ -183,7 +183,7 @@ module CommitsHelper ...@@ -183,7 +183,7 @@ module CommitsHelper
options = { options = {
class: "commit-#{options[:source]}-link has-tooltip", class: "commit-#{options[:source]}-link has-tooltip",
data: { 'original-title'.to_sym => sanitize(source_email) } title: source_email
} }
if user.nil? if user.nil?
......
...@@ -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?
......
...@@ -52,7 +52,7 @@ module ProjectsHelper ...@@ -52,7 +52,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else else
title = opts[:title].sub(":name", sanitize(author.name)) title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
end end
end end
......
...@@ -11,6 +11,7 @@ module SelectsHelper ...@@ -11,6 +11,7 @@ module SelectsHelper
email_user = opts[:email_user] || false email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false current_user = opts[:current_user] || false
author_id = opts[:author_id] || ''
project = opts[:project] || @project project = opts[:project] || @project
html = { html = {
...@@ -21,7 +22,8 @@ module SelectsHelper ...@@ -21,7 +22,8 @@ module SelectsHelper
any_user: any_user, any_user: any_user,
email_user: email_user, email_user: email_user,
first_user: first_user, first_user: first_user,
current_user: current_user current_user: current_user,
author_id: author_id
} }
} }
......
...@@ -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}",
......
...@@ -365,11 +365,23 @@ module Ci ...@@ -365,11 +365,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
......
...@@ -104,10 +104,16 @@ class Issue < ActiveRecord::Base ...@@ -104,10 +104,16 @@ class Issue < ActiveRecord::Base
end end
end end
def related_branches # All branches containing the current issue's ID, except for
project.repository.branch_names.select do |branch| # those with a merge request open referencing the current issue.
def related_branches(current_user)
branches_with_iid = project.repository.branch_names.select do |branch|
branch =~ /\A#{iid}-(?!\d+-stable)/i branch =~ /\A#{iid}-(?!\d+-stable)/i
end end
branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch)
branches_with_iid - branches_with_merge_request
end end
# Reset issue events cache # Reset issue events cache
...@@ -151,13 +157,17 @@ class Issue < ActiveRecord::Base ...@@ -151,13 +157,17 @@ class Issue < ActiveRecord::Base
end end
def to_branch_name def to_branch_name
if self.confidential?
"#{iid}-confidential-issue"
else
"#{iid}-#{title.parameterize}" "#{iid}-#{title.parameterize}"
end end
end
def can_be_worked_on?(current_user) def can_be_worked_on?(current_user)
!self.closed? && !self.closed? &&
!self.project.forked? && !self.project.forked? &&
self.related_branches.empty? && self.related_branches(current_user).empty? &&
self.closed_by_merge_requests(current_user).empty? self.closed_by_merge_requests(current_user).empty?
end end
end end
...@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base ...@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data self.import_data.destroy if self.import_data
end end
def import_url=(value)
import_url = Gitlab::ImportUrl.new(value)
create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url)
end
def import_url
if import_data && super
import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
import_url.full_url
else
super
end
end
def create_or_update_import_data(data: nil, credentials: nil)
project_import_data = import_data || build_import_data
if data
project_import_data.data ||= {}
project_import_data.data = project_import_data.data.merge(data)
end
if credentials
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
end
project_import_data.save
end
def import? def import?
external_import? || forked? external_import? || forked?
end end
...@@ -802,8 +831,8 @@ class Project < ActiveRecord::Base ...@@ -802,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,
...@@ -814,12 +843,19 @@ class Project < ActiveRecord::Base ...@@ -814,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
...@@ -865,7 +901,9 @@ class Project < ActiveRecord::Base ...@@ -865,7 +901,9 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
repository.before_change_head repository.before_change_head
gitlab_shell.update_repository_head(self.path_with_namespace, branch) repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
reload_default_branch reload_default_branch
end end
......
...@@ -12,8 +12,20 @@ require 'file_size_validator' ...@@ -12,8 +12,20 @@ require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base class ProjectImportData < ActiveRecord::Base
belongs_to :project belongs_to :project
attr_encrypted :credentials,
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
encode: true,
mode: :per_attribute_iv_and_salt
serialize :data, JSON serialize :data, JSON
validates :project, presence: true validates :project, presence: true
before_validation :symbolize_credentials
def symbolize_credentials
# bang doesn't work here - attr_encrypted makes it not to work
self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
end
end end
...@@ -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
...@@ -169,7 +171,12 @@ class Repository ...@@ -169,7 +171,12 @@ class Repository
def rm_tag(tag_name) def rm_tag(tag_name)
before_remove_tag before_remove_tag
gitlab_shell.rm_tag(path_with_namespace, tag_name) begin
rugged.tags.delete(tag_name)
true
rescue Rugged::ReferenceError
false
end
end end
def branch_names def branch_names
......
...@@ -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
module Projects module Projects
class ParticipantsService < BaseService class ParticipantsService < BaseService
def execute(note_type, note_id) def execute(noteable_type, noteable_id)
participating = @noteable_type = noteable_type
if note_type && note_id @noteable_id = noteable_id
participants_in(note_type, note_id)
else
[]
end
project_members = sorted(project.team.members) project_members = sorted(project.team.members)
participants = all_members + groups + project_members + participating participants = target_owner + participants_in_target + all_members + groups + project_members
participants.uniq participants.uniq
end end
def participants_in(type, id) def target
target = @target ||=
case type case @noteable_type
when "Issue" when "Issue"
project.issues.find_by_iid(id) project.issues.find_by_iid(@noteable_id)
when "MergeRequest" when "MergeRequest"
project.merge_requests.find_by_iid(id) project.merge_requests.find_by_iid(@noteable_id)
when "Commit" when "Commit"
project.commit(id) project.commit(@noteable_id)
else
nil
end
end
def target_owner
return [] unless target && target.author.present?
[{
name: target.author.name,
username: target.author.username
}]
end end
def participants_in_target
return [] unless target return [] unless target
users = target.participants(current_user) users = target.participants(current_user)
......
...@@ -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
...@@ -212,6 +218,13 @@ ...@@ -212,6 +218,13 @@
.help-block .help-block
The sampling interval in seconds. Sampled data includes memory usage, The sampling interval in seconds. Sampled data includes memory usage,
retained Ruby objects, file descriptors and so on. retained Ruby objects, file descriptors and so on.
.form-group
= f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :metrics_packet_size, class: 'form-control'
.help-block
The amount of points to store in a single UDP packet. More points
results in fewer but larger UDP packets being sent.
%fieldset %fieldset
%legend Spam and Anti-bot Protection %legend Spam and Anti-bot Protection
......
...@@ -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
......
...@@ -52,6 +52,9 @@ ...@@ -52,6 +52,9 @@
%li %li
phpunit --coverage-text --colors=never (PHP) - phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\% %code ^\s*Lines:\s*\d+.\d+\%
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
.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
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title .commit-row-title
%span.item-title.str-truncated %span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description? - if commit.description?
%a.text-expander.js-toggle-button ... %a.text-expander.js-toggle-button ...
......
...@@ -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();
...@@ -16,11 +16,9 @@ ...@@ -16,11 +16,9 @@
= dropdown_title("Select source project") = dropdown_title("Select source project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
= dropdown_content do = dropdown_content do
- is_active = f.object.source_project_id == @merge_request.source_project.id = render 'projects/merge_requests/dropdowns/project',
%ul projects: [@merge_request.source_project],
%li selected: f.object.source_project_id
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
= @merge_request.source_project_path
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :source_branch = f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
...@@ -28,11 +26,9 @@ ...@@ -28,11 +26,9 @@
= dropdown_title("Select source branch") = dropdown_title("Select source branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/branch',
- @merge_request.source_branches.each do |branch| branches: @merge_request.source_branches,
%li selected: f.object.source_branch
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
= icon('spinner spin', class: 'js-source-loading') = icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit %ul.list-unstyled.mr_source_commit
...@@ -50,11 +46,9 @@ ...@@ -50,11 +46,9 @@
= dropdown_title("Select target project") = dropdown_title("Select target project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/project',
- projects.each do |project| projects: projects,
%li selected: f.object.target_project_id
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :target_branch = f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
...@@ -62,11 +56,9 @@ ...@@ -62,11 +56,9 @@
= dropdown_title("Select target branch") = dropdown_title("Select target branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/branch',
- @merge_request.target_branches.each do |branch| branches: @merge_request.target_branches,
%li selected: f.object.target_branch
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
= icon('spinner spin', class: "js-target-loading") = icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit %ul.list-unstyled.mr_target_commit
......
- 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,8 +31,8 @@ ...@@ -32,8 +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 namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do %span.label-branch
= @merge_request.target_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)
......
%ul
- branches.each do |branch|
%li
%a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
= branch
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.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();
%ul = render 'projects/merge_requests/dropdowns/branch',
- @target_branches.each do |branch| branches: @target_branches,
%li selected: nil
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
= branch
- 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
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
.selectbox.hide-collapsed .selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }) = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone .block.milestone
.sidebar-collapsed-icon .sidebar-collapsed-icon
...@@ -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
......
...@@ -23,5 +23,5 @@ ...@@ -23,5 +23,5 @@
- if assignee - if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '') - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
...@@ -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 AddImportCredentialsToProjectImportData < ActiveRecord::Migration
def change
add_column :project_import_data, :encrypted_credentials, :text
add_column :project_import_data, :encrypted_credentials_iv, :string
add_column :project_import_data, :encrypted_credentials_salt, :string
end
end
# Loops through old importer projects that kept a token/password in the import URL
# and encrypts the credentials into a separate field in project#import_data
# #down method not supported
class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
class ProjectImportDataFake
extend AttrEncrypted
attr_accessor :credentials
attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt
end
def up
say("Encrypting and migrating project import credentials...")
# This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs.
in_transaction(message: "Projects including GitHub and GitLab projects with an unsecured URL.") { process_projects_with_wrong_url }
in_transaction(message: "Migrating Bitbucket credentials...") { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) }
in_transaction(message: "Migrating FogBugz credentials...") { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) }
end
def process_projects_with_wrong_url
projects_with_wrong_import_url.each do |project|
begin
import_url = Gitlab::ImportUrl.new(project["import_url"])
update_import_url(import_url, project)
update_import_data(import_url, project)
rescue URI::InvalidURIError
nullify_import_url(project)
end
end
end
def process_project(import_type:, credentials_keys: [])
unencrypted_import_data(import_type: import_type).each do |data|
replace_data_credentials(data, credentials_keys)
end
end
def replace_data_credentials(data, credentials_keys)
data_hash = JSON.load(data['data']) if data['data']
unless data_hash.blank?
encrypted_data_hash = encrypt_data(data_hash, credentials_keys)
unencrypted_data = data_hash.empty? ? ' NULL ' : quote(data_hash.to_json)
update_with_encrypted_data(encrypted_data_hash, data['id'], unencrypted_data)
end
end
def encrypt_data(data_hash, credentials_keys)
new_data_hash = {}
credentials_keys.each do |key|
new_data_hash[key.to_sym] = data_hash.delete(key) if data_hash[key]
end
new_data_hash.deep_symbolize_keys
end
def in_transaction(message:)
say_with_time(message) do
ActiveRecord::Base.transaction do
yield
end
end
end
def update_import_data(import_url, project)
fake_import_data = ProjectImportDataFake.new
fake_import_data.credentials = import_url.credentials
import_data_id = project['import_data_id']
if import_data_id
execute(update_import_data_sql(import_data_id, fake_import_data))
else
execute(insert_import_data_sql(project['id'], fake_import_data))
end
end
def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ')
fake_import_data = ProjectImportDataFake.new
fake_import_data.credentials = data_hash
execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data))
end
def update_import_url(import_url, project)
execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}")
end
def nullify_import_url(project)
execute("UPDATE projects SET import_url = NULL WHERE id = #{project['id']}")
end
def insert_import_data_sql(project_id, fake_import_data)
%(
INSERT INTO project_import_data
(encrypted_credentials,
project_id,
encrypted_credentials_iv,
encrypted_credentials_salt)
VALUES ( #{quote(fake_import_data.encrypted_credentials)},
'#{project_id}',
#{quote(fake_import_data.encrypted_credentials_iv)},
#{quote(fake_import_data.encrypted_credentials_salt)})
).squish
end
def update_import_data_sql(id, fake_import_data, data = 'NULL')
%(
UPDATE project_import_data
SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)},
encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)},
encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)},
data = #{data}
WHERE id = '#{id}'
).squish
end
#GitHub projects with token, and any user:password@ based URL
def projects_with_wrong_import_url
select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url <> '' AND p.import_url LIKE '%//%@%'")
end
# All imports with data for import_type
def unencrypted_import_data(import_type: )
select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url <> '' AND p.import_type = '#{import_type}' ")
end
def quote(value)
ActiveRecord::Base.connection.quote(value)
end
end
class AddSharedRunnersTextToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :shared_runners_text, :text
end
end
class AddMetricsPacketSize < ActiveRecord::Migration
def change
add_column :application_settings, :metrics_packet_size, :integer, default: 1
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: 20160412140240) do ActiveRecord::Schema.define(version: 20160419120017) 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,8 @@ ActiveRecord::Schema.define(version: 20160412140240) do ...@@ -78,6 +78,8 @@ ActiveRecord::Schema.define(version: 20160412140240) 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.integer "metrics_packet_size", default: 1
t.text "shared_runners_text"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
...@@ -704,6 +706,9 @@ ActiveRecord::Schema.define(version: 20160412140240) do ...@@ -704,6 +706,9 @@ ActiveRecord::Schema.define(version: 20160412140240) do
create_table "project_import_data", force: :cascade do |t| create_table "project_import_data", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
t.text "data" t.text "data"
t.text "encrypted_credentials"
t.text "encrypted_credentials_iv"
t.text "encrypted_credentials_salt"
end end
create_table "projects", force: :cascade do |t| create_table "projects", force: :cascade do |t|
......
...@@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you ...@@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you
and configure your GitLab project to use a [Runner], then each merge request or and configure your GitLab project to use a [Runner], then each merge request or
push triggers a build. push triggers a build.
The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
runs three [stages]: `build`, `test`, and `deploy`. runs three [stages]: `build`, `test`, and `deploy`.
If everything runs OK (no non-zero return values), you'll get a nice green If everything runs OK (no non-zero return values), you'll get a nice green
......
...@@ -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",
......
...@@ -44,7 +44,7 @@ check it into your Git repository: ...@@ -44,7 +44,7 @@ check it into your Git repository:
```bash ```bash
git clone git@gitlab.example.com:group/project.git git clone git@gitlab.example.com:group/project.git
git lfs init # initialize the Git LFS project project git lfs install # initialize the Git LFS project project
git lfs track "*.iso" # select the file extensions that you want to treat as large files git lfs track "*.iso" # select the file extensions that you want to treat as large files
``` ```
......
...@@ -29,8 +29,9 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps ...@@ -29,8 +29,9 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
def check_author_link(email, user) def check_author_link(email, user)
author_link = find('.commit-author-link') author_link = find('.commit-author-link')
expect(author_link['href']).to eq user_path(user) expect(author_link['href']).to eq user_path(user)
expect(author_link['data-original-title']).to eq email expect(author_link['title']).to eq email
expect(find('.commit-author-name').text).to eq user.name expect(find('.commit-author-name').text).to eq user.name
end end
......
...@@ -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"
......
...@@ -54,19 +54,6 @@ module Gitlab ...@@ -54,19 +54,6 @@ module Gitlab
"#{path}.git", "#{new_path}.git"]) "#{path}.git", "#{new_path}.git"])
end end
# Update HEAD for repository
#
# path - project path with namespace
# branch - repository branch name
#
# Ex.
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
"#{path}.git", branch])
end
# Fork repository to new namespace # Fork repository to new namespace
# #
# path - project path with namespace # path - project path with namespace
...@@ -92,33 +79,6 @@ module Gitlab ...@@ -92,33 +79,6 @@ module Gitlab
'rm-project', "#{name}.git"]) 'rm-project', "#{name}.git"])
end end
# Add repository branch from passed ref
#
# path - project path with namespace
# branch_name - new branch name
# ref - HEAD for new branch
#
# Ex.
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
"#{path}.git", branch_name, ref])
end
# Remove repository branch
#
# path - project path with namespace
# branch_name - branch name to remove
#
# Ex.
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
"#{path}.git", branch_name])
end
# Add repository tag from passed ref # Add repository tag from passed ref
# #
# path - project path with namespace # path - project path with namespace
...@@ -137,19 +97,6 @@ module Gitlab ...@@ -137,19 +97,6 @@ module Gitlab
Gitlab::Utils.system_silent(cmd) Gitlab::Utils.system_silent(cmd)
end end
# Remove repository tag
#
# path - project path with namespace
# tag_name - tag name to remove
#
# Ex.
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
"#{path}.git", tag_name])
end
# Gc repository # Gc repository
# #
# path - project path with namespace # path - project path with namespace
......
...@@ -5,6 +5,17 @@ module Gitlab ...@@ -5,6 +5,17 @@ module Gitlab
attr_reader :consumer, :api attr_reader :consumer, :api
def self.from_project(project)
import_data_credentials = project.import_data.credentials if project.import_data
if import_data_credentials && import_data_credentials[:bb_session]
token = import_data_credentials[:bb_session][:bitbucket_access_token]
token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret]
new(token, token_secret)
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end
def initialize(access_token = nil, access_token_secret = nil) def initialize(access_token = nil, access_token_secret = nil)
@consumer = ::OAuth::Consumer.new( @consumer = ::OAuth::Consumer.new(
config.app_id, config.app_id,
...@@ -120,7 +131,7 @@ module Gitlab ...@@ -120,7 +131,7 @@ module Gitlab
end end
def config def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket" }
end end
def bitbucket_options def bitbucket_options
......
...@@ -5,10 +5,7 @@ module Gitlab ...@@ -5,10 +5,7 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
import_data = project.import_data.try(:data) @client = Client.from_project(@project)
bb_session = import_data["bb_session"] if import_data
@client = Client.new(bb_session["bitbucket_access_token"],
bb_session["bitbucket_access_token_secret"])
@formatter = Gitlab::ImportFormatter.new @formatter = Gitlab::ImportFormatter.new
end end
......
...@@ -6,10 +6,7 @@ module Gitlab ...@@ -6,10 +6,7 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
@current_user = project.creator @current_user = project.creator
import_data = project.import_data.try(:data) @client = Client.from_project(@project)
bb_session = import_data["bb_session"] if import_data
@client = Client.new(bb_session["bitbucket_access_token"],
bb_session["bitbucket_access_token_secret"])
end end
def execute def execute
......
...@@ -23,7 +23,8 @@ module Gitlab ...@@ -23,7 +23,8 @@ module Gitlab
import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git",
).execute ).execute
project.create_import_data(data: { "bb_session" => session_data } ) project.create_or_update_import_data(credentials: { bb_session: session_data })
project project
end end
end end
......
...@@ -8,17 +8,17 @@ module Gitlab ...@@ -8,17 +8,17 @@ module Gitlab
import_data = project.import_data.try(:data) import_data = project.import_data.try(:data)
repo_data = import_data['repo'] if import_data repo_data = import_data['repo'] if import_data
if repo_data
@repo = FogbugzImport::Repository.new(repo_data) @repo = FogbugzImport::Repository.new(repo_data)
@known_labels = Set.new @known_labels = Set.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end end
def execute def execute
return true unless repo.valid? return true unless repo.valid?
client = Gitlab::FogbugzImport::Client.new(token: fb_session[:token], uri: fb_session[:uri])
data = project.import_data.try(:data)
client = Gitlab::FogbugzImport::Client.new(token: data['fb_session']['token'], uri: data['fb_session']['uri'])
@cases = client.cases(@repo.id.to_i) @cases = client.cases(@repo.id.to_i)
@categories = client.categories @categories = client.categories
...@@ -30,6 +30,10 @@ module Gitlab ...@@ -30,6 +30,10 @@ module Gitlab
private private
def fb_session
@import_data_credentials ||= project.import_data.credentials[:fb_session] if project.import_data && project.import_data.credentials
end
def user_map def user_map
@user_map ||= begin @user_map ||= begin
user_map = Hash.new user_map = Hash.new
...@@ -236,9 +240,8 @@ module Gitlab ...@@ -236,9 +240,8 @@ module Gitlab
end end
def build_attachment_url(rel_url) def build_attachment_url(rel_url)
data = project.import_data.try(:data) uri = fb_session[:uri]
uri = data['fb_session']['uri'] token = fb_session[:token]
token = data['fb_session']['token']
"#{uri}/#{rel_url}&token=#{token}" "#{uri}/#{rel_url}&token=#{token}"
end end
......
...@@ -24,13 +24,7 @@ module Gitlab ...@@ -24,13 +24,7 @@ module Gitlab
import_url: Project::UNKNOWN_IMPORT_URL import_url: Project::UNKNOWN_IMPORT_URL
).execute ).execute
project.create_import_data( project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session })
data: {
'repo' => repo.raw_data,
'user_map' => user_map,
'fb_session' => fb_session
}
)
project project
end end
......
...@@ -7,18 +7,45 @@ module Gitlab ...@@ -7,18 +7,45 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
import_data = project.import_data.try(:data) if import_data_credentials
github_session = import_data["github_session"] if import_data @client = Client.new(import_data_credentials[:user])
@client = Client.new(github_session["github_access_token"])
@formatter = Gitlab::ImportFormatter.new @formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
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
def import_data_credentials
@import_data_credentials ||= project.import_data.credentials if project.import_data
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,
...@@ -27,6 +54,7 @@ module Gitlab ...@@ -27,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)
...@@ -49,6 +77,7 @@ module Gitlab ...@@ -49,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
...@@ -60,6 +89,18 @@ module Gitlab ...@@ -60,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
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
end end
def execute def execute
project = ::Projects::CreateService.new( ::Projects::CreateService.new(
current_user, current_user,
name: repo.name, name: repo.name,
path: repo.name, path: repo.name,
...@@ -23,9 +23,6 @@ module Gitlab ...@@ -23,9 +23,6 @@ module Gitlab
import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"),
wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later
).execute ).execute
project.create_import_data(data: { "github_session" => session_data } )
project
end end
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
......
...@@ -5,10 +5,13 @@ module Gitlab ...@@ -5,10 +5,13 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
import_data = project.import_data.try(:data) credentials = import_data
gitlab_session = import_data["gitlab_session"] if import_data if credentials && credentials[:password]
@client = Client.new(gitlab_session["gitlab_access_token"]) @client = Client.new(credentials[:password])
@formatter = Gitlab::ImportFormatter.new @formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end end
def execute def execute
......
...@@ -23,7 +23,6 @@ module Gitlab ...@@ -23,7 +23,6 @@ module Gitlab
import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@") import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@")
).execute ).execute
project.create_import_data(data: { "gitlab_session" => session_data } )
project project
end end
end end
......
...@@ -24,12 +24,7 @@ module Gitlab ...@@ -24,12 +24,7 @@ module Gitlab
import_url: repo.import_url import_url: repo.import_url
).execute ).execute
project.create_import_data( project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map })
data: {
"repo" => repo.raw_data,
"user_map" => user_map
}
)
project project
end end
......
module Gitlab
class ImportUrl
def initialize(url, credentials: nil)
@url = URI.parse(URI.encode(url))
@credentials = credentials
end
def sanitized_url
@sanitized_url ||= safe_url.to_s
end
def credentials
@credentials ||= { user: @url.user, password: @url.password }
end
def full_url
@full_url ||= generate_full_url.to_s
end
private
def generate_full_url
return @url unless valid_credentials?
@full_url = @url.dup
@full_url.user = credentials[:user]
@full_url.password = credentials[:password]
@full_url
end
def safe_url
safe_url = @url.dup
safe_url.password = nil
safe_url.user = nil
safe_url
end
def valid_credentials?
credentials && credentials.is_a?(Hash) && credentials.any?
end
end
end
...@@ -14,7 +14,8 @@ module Gitlab ...@@ -14,7 +14,8 @@ module Gitlab
method_call_threshold: current_application_settings[:metrics_method_call_threshold], method_call_threshold: current_application_settings[:metrics_method_call_threshold],
host: current_application_settings[:metrics_host], host: current_application_settings[:metrics_host],
port: current_application_settings[:metrics_port], port: current_application_settings[:metrics_port],
sample_interval: current_application_settings[:metrics_sample_interval] || 15 sample_interval: current_application_settings[:metrics_sample_interval] || 15,
packet_size: current_application_settings[:metrics_packet_size] || 1
} }
end end
...@@ -41,9 +42,9 @@ module Gitlab ...@@ -41,9 +42,9 @@ module Gitlab
prepared = prepare_metrics(metrics) prepared = prepare_metrics(metrics)
pool.with do |connection| pool.with do |connection|
prepared.each do |metric| prepared.each_slice(settings[:packet_size]) do |slice|
begin begin
connection.write_points([metric]) connection.write_points(slice)
rescue StandardError rescue StandardError
end end
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
......
...@@ -12,13 +12,13 @@ describe AutocompleteController do ...@@ -12,13 +12,13 @@ describe AutocompleteController do
project.team << [user, :master] project.team << [user, :master]
end end
let(:body) { JSON.parse(response.body) }
describe 'GET #users with project ID' do describe 'GET #users with project ID' do
before do before do
get(:users, project_id: project.id) get(:users, project_id: project.id)
end end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) } it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 } it { expect(body.size).to eq 1 }
it { expect(body.map { |u| u["username"] }).to include(user.username) } it { expect(body.map { |u| u["username"] }).to include(user.username) }
...@@ -143,4 +143,24 @@ describe AutocompleteController do ...@@ -143,4 +143,24 @@ describe AutocompleteController do
it { expect(body.size).to eq 0 } it { expect(body.size).to eq 0 }
end end
end end
context 'author of issuable included' do
before do
sign_in(user)
end
let(:body) { JSON.parse(response.body) }
it 'includes the author' do
get(:users, author_id: non_member.id)
expect(body.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
expect(body.collect { |u| u['id'] }).not_to include(99999)
end
end
end end
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)
......
...@@ -48,7 +48,7 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -48,7 +48,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
click_update_issues_button click_update_issues_button
page.within('.issue .controls') do page.within('.issue .controls') do
expect(find('.author_link')["data-original-title"]).to have_content(user.name) expect(find('.author_link')["title"]).to have_content(user.name)
end end
end end
......
...@@ -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
require 'spec_helper'
feature 'Member autocomplete', feature: true do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:participant) { create(:user) }
let(:author) { create(:user) }
before do
allow_any_instance_of(Commit).to receive(:author).and_return(author)
login_as user
end
shared_examples "open suggestions" do
it 'suggestions are displayed' do
expect(page).to have_selector('.atwho-view', visible: true)
end
it 'author is suggested' do
page.within('.atwho-view', visible: true) do
expect(page).to have_content(author.username)
end
end
it 'participant is suggested' do
page.within('.atwho-view', visible: true) do
expect(page).to have_content(participant.username)
end
end
end
context 'adding a new note on a Issue', js: true do
before do
issue = create(:issue, author: author, project: project)
create(:note, note: 'Ultralight Beam', noteable: issue, author: participant)
visit_issue(project, issue)
end
context 'when typing @' do
include_examples "open suggestions"
before do
open_member_suggestions
end
end
end
context 'adding a new note on a Merge Request ', js: true do
before do
merge = create(:merge_request, source_project: project, target_project: project, author: author)
create(:note, note: 'Ultralight Beam', noteable: merge, author: participant)
visit_merge_request(project, merge)
end
context 'when typing @' do
include_examples "open suggestions"
before do
open_member_suggestions
end
end
end
context 'adding a new note on a Commit ', js: true do
let(:commit) { project.commit }
before do
allow(commit).to receive(:author).and_return(author)
create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA')
visit_commit(project, commit)
end
context 'when typing @' do
include_examples "open suggestions"
before do
open_member_suggestions
end
end
end
def open_member_suggestions
sleep 1
page.within('.new-note') do
sleep 1
find('#note_note').native.send_keys('@')
end
end
def visit_issue(project, issue)
visit namespace_project_issue_path(project.namespace, project, issue)
end
def visit_merge_request(project, merge)
visit namespace_project_merge_request_path(project.namespace, project, merge)
end
def visit_commit(project, commit)
visit namespace_project_commit_path(project.namespace, project, commit)
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
require 'rails_helper'
describe CommitsHelper do
describe 'commit_author_link' do
it 'escapes the author email' do
commit = double(
author: nil,
author_name: 'Persistent XSS',
author_email: 'my@email.com" onmouseover="alert(1)'
)
expect(helper.commit_author_link(commit)).
not_to include('onmouseover="alert(1)"')
end
end
describe 'commit_committer_link' do
it 'escapes the committer email' do
commit = double(
committer: nil,
committer_name: 'Persistent XSS',
committer_email: 'my@email.com" onmouseover="alert(1)'
)
expect(helper.commit_committer_link(commit)).
not_to include('onmouseover="alert(1)"')
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
......
...@@ -34,9 +34,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do ...@@ -34,9 +34,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
let(:project_identifier) { 'namespace/repo' } let(:project_identifier) { 'namespace/repo' }
let(:data) do let(:data) do
{ {
bb_session: { 'bb_session' => {
bitbucket_access_token: "123456", 'bitbucket_access_token' => "123456",
bitbucket_access_token_secret: "secret" 'bitbucket_access_token_secret' => "secret"
} }
} }
end end
...@@ -44,7 +44,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do ...@@ -44,7 +44,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
create( create(
:project, :project,
import_source: project_identifier, import_source: project_identifier,
import_data: ProjectImportData.new(data: data) import_data: ProjectImportData.new(credentials: data)
) )
end end
let(:importer) { Gitlab::BitbucketImport::Importer.new(project) } let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
......
...@@ -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
...@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do ...@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
owner: OpenStruct.new(login: "john") owner: OpenStruct.new(login: "john")
) )
end end
let(:namespace){ create(:group, owner: user) } let(:namespace) { create(:group, owner: user) }
let(:token) { "asdffg" } let(:token) { "asdffg" }
let(:access_params) { { github_access_token: token } } let(:access_params) { { github_access_token: token } }
...@@ -27,6 +27,8 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do ...@@ -27,6 +27,8 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
project = project_creator.execute project = project_creator.execute
expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git")
expect(project.import_data.credentials).to eq(user: "asdffg", password: nil)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
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
......
...@@ -2,11 +2,12 @@ require 'spec_helper' ...@@ -2,11 +2,12 @@ require 'spec_helper'
describe Gitlab::GithubImport::WikiFormatter, lib: true do describe Gitlab::GithubImport::WikiFormatter, lib: true do
let(:project) do let(:project) do
create(:project, namespace: create(:namespace, path: 'gitlabhq'), create(:project,
namespace: create(:namespace, path: 'gitlabhq'),
import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git') import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git')
end end
subject(:wiki) { described_class.new(project)} subject(:wiki) { described_class.new(project) }
describe '#path_with_namespace' do describe '#path_with_namespace' do
it 'appends .wiki to project path' do it 'appends .wiki to project path' do
......
require 'spec_helper'
describe Gitlab::ImportUrl do
let(:credentials) { { user: 'blah', password: 'password' } }
let(:import_url) do
Gitlab::ImportUrl.new("https://github.com/me/project.git", credentials: credentials)
end
describe :full_url do
it { expect(import_url.full_url).to eq("https://blah:password@github.com/me/project.git") }
end
describe :sanitized_url do
it { expect(import_url.sanitized_url).to eq("https://github.com/me/project.git") }
end
describe :credentials do
it { expect(import_url.credentials).to eq(credentials) }
end
end
...@@ -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
...@@ -191,18 +191,36 @@ describe Issue, models: true do ...@@ -191,18 +191,36 @@ describe Issue, models: true do
end end
describe '#related_branches' do describe '#related_branches' do
it 'selects the right branches' do let(:user) { build(:admin) }
before do
allow(subject.project.repository).to receive(:branch_names). allow(subject.project.repository).to receive(:branch_names).
and_return(['mpempe', "#{subject.iid}mepmep", subject.to_branch_name]) and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "#{subject.iid}-branch"])
# Without this stub, the `create(:merge_request)` above fails because it can't find
# the source branch. This seems like a reasonable compromise, in comparison with
# setting up a full repo here.
allow_any_instance_of(MergeRequest).to receive(:create_merge_request_diff)
end
it "selects the right branches when there are no referenced merge requests" do
expect(subject.related_branches(user)).to eq([subject.to_branch_name, "#{subject.iid}-branch"])
end
expect(subject.related_branches).to eq([subject.to_branch_name]) it "selects the right branches when there is a referenced merge request" do
merge_request = create(:merge_request, { description: "Closes ##{subject.iid}",
source_project: subject.project,
source_branch: "#{subject.iid}-branch" })
merge_request.create_cross_references!(user)
expect(subject.referenced_merge_requests).to_not be_empty
expect(subject.related_branches(user)).to eq([subject.to_branch_name])
end end
it 'excludes stable branches from the related branches' do it 'excludes stable branches from the related branches' do
allow(subject.project.repository).to receive(:branch_names). allow(subject.project.repository).to receive(:branch_names).
and_return(["#{subject.iid}-0-stable"]) and_return(["#{subject.iid}-0-stable"])
expect(subject.related_branches).to eq [] expect(subject.related_branches(user)).to eq []
end end
end end
...@@ -217,11 +235,20 @@ describe Issue, models: true do ...@@ -217,11 +235,20 @@ describe Issue, models: true do
let(:subject) { create :issue } let(:subject) { create :issue }
end end
describe '#to_branch_name' do describe "#to_branch_name" do
let(:issue) { create(:issue, title: 'a' * 30) } let(:issue) { create(:issue, title: 'testing-issue') }
it 'starts with the issue iid' do it 'starts with the issue iid' do
expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/ expect(issue.to_branch_name).to match /\A#{issue.iid}-[A-Za-z\-]+\z/
end
it "contains the issue title if not confidential" do
expect(issue.to_branch_name).to match /testing-issue\z/
end
it "does not contain the issue title if confidential" do
issue = create(:issue, title: 'testing-issue', confidential: true)
expect(issue.to_branch_name).to match /confidential-issue\z/
end end
end end
end end
...@@ -770,11 +770,9 @@ describe Repository, models: true do ...@@ -770,11 +770,9 @@ describe Repository, models: true do
describe '#rm_tag' do describe '#rm_tag' do
it 'removes a tag' do it 'removes a tag' do
expect(repository).to receive(:before_remove_tag) expect(repository).to receive(:before_remove_tag)
expect(repository.rugged.tags).to receive(:delete).with('v1.1.0')
expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag). repository.rm_tag('v1.1.0')
with(repository.path_with_namespace, '8.5')
repository.rm_tag('8.5')
end end
end end
...@@ -912,9 +910,32 @@ describe Repository, models: true do ...@@ -912,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)
......
...@@ -6,21 +6,12 @@ describe DeleteTagService, services: true do ...@@ -6,21 +6,12 @@ describe DeleteTagService, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:service) { described_class.new(project, user) } let(:service) { described_class.new(project, user) }
let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
describe '#execute' do describe '#execute' do
before do
allow(repository).to receive(:find_tag).and_return(tag)
end
it 'removes the tag' do it 'removes the tag' do
expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
and_return(true)
expect(repository).to receive(:before_remove_tag) expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success) expect(service).to receive(:success)
service.execute('8.5') service.execute('v1.1.0')
end end
end end
end end
...@@ -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