Commit df5b73e1 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' into group-navigation-redesign

parents e9eeeaa0 f0c4f727
......@@ -244,11 +244,11 @@ linters:
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: false
enabled: true
# URLs should always be enclosed within quotes.
UrlQuotes:
enabled: false
enabled: true
# Properties, like color and font, are easier to read and maintain
# when defined using variables rather than literals.
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased)
- Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Make build status canceled if any of the jobs was canceled and none failed
- Remove future dates from contribution calendar graph.
- Support e-mail notifications for comments on project snippets
- Use ActionDispatch Remote IP for Akismet checking
- Fix error when visiting commit builds page before build was updated
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
- Updated search UI
- Display informative message when new milestone is created
- Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea)
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view
- Backport GitLab Enterprise support from EE
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
- Added multiple colors for labels in dropdowns when dups happen.
v 8.7.1 (unreleased)
v 8.7.2 (unreleased)
- The "New Branch" button is now loaded asynchronously
- Fix error 500 when trying to create a wiki page
v 8.7.1
- Throttle the update of `project.last_activity_at` to 1 minute. !3848
- Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849
- Fix license detection to detect all license files, not only known licenses. !3878
- Use the `can?` helper instead of `current_user.can?`. !3882
- Prevent users from deleting Webhooks via API they do not own
- Fix Error 500 due to stale cache when projects are renamed or transferred
- Update width of search box to fix Safari bug. !3900 (Jedidiah)
- Use the `can?` helper instead of `current_user.can?`
v 8.7.0
- Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented
......@@ -127,13 +142,25 @@ v 8.7.0
- Import GitHub labels
- Add option to filter by "Owned projects" on dashboard page
- Import GitHub milestones
- Fix emoji catgories in the emoji picker
- Execute system web hooks on push to the project
- Allow enable/disable push events for system hooks
- Fix GitHub project's link in the import page when provider has a custom URL
- Add RAW build trace output and button on build page
- Add incremental build trace update into CI API
v 8.6.8
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via Git branch and tag names
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent XSS via label drop-down
- Prevent information disclosure via milestone API
- Prevent information disclosure via snippet API
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.6.7
- Fix persistent XSS vulnerability in `commit_person_link` helper
- Fix persistent XSS vulnerability in Label and Milestone dropdowns
......@@ -275,6 +302,17 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
v 8.5.12
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via Git branch and tag names
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent information disclosure via snippet API
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.5.11
- Fix persistent XSS vulnerability in `commit_person_link` helper
......@@ -425,6 +463,17 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
v 8.4.10
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via Git branch and tag names
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent information disclosure via snippet API
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.4.9
- Fix persistent XSS vulnerability in `commit_person_link` helper
......@@ -550,6 +599,15 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
v 8.3.9
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.3.8
- Fix persistent XSS vulnerability in `commit_person_link` helper
......@@ -659,6 +717,17 @@ v 8.3.0
- Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
v 8.2.5
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via `window.opener`
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.2.4
- Bump Git version requirement to 2.7.4
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
......
......@@ -38,7 +38,7 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
abbreviation.
If you have read this guide and want to know how the GitLab [core team][core-team]
If you have read this guide and want to know how the GitLab [core team]
operates please see [the GitLab contributing process](PROCESS.md).
## Contributor license agreement
......@@ -135,8 +135,9 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team][core-team]
members to add the label `feature proposal` to the issue.
of the project cannot add labels. You can instead ask one of the [core team]
members to add the label `feature proposal` to the issue or add the following
code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
......@@ -344,8 +345,7 @@ is it will be merged (quickly). After that you can send more MRs to enhance it.
For examples of feedback on merge requests please look at already
[closed merge requests][closed-merge-requests]. If you would like quick feedback
on your merge request feel free to mention one of the Merge Marshalls in the
[core team][core-team] or one of the
[Merge request coaches](https://about.gitlab.com/team/).
[core team] or one of the [Merge request coaches](https://about.gitlab.com/team/).
Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the
......@@ -497,7 +497,7 @@ reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[core-team]: https://about.gitlab.com/core-team/
[core team]: https://about.gitlab.com/core-team/
[getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
......
......@@ -19,7 +19,7 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.4'
gem 'doorkeeper', '~> 2.2.0'
gem 'doorkeeper', '~> 3.1'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
......
......@@ -173,7 +173,7 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
doorkeeper (2.2.2)
doorkeeper (3.1.0)
railties (>= 3.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
......@@ -184,7 +184,7 @@ GEM
encryptor (1.3.0)
equalizer (0.0.11)
erubis (2.7.0)
escape_utils (1.1.0)
escape_utils (1.1.1)
eventmachine (1.0.8)
excon (0.45.4)
execjs (2.6.0)
......@@ -334,7 +334,7 @@ GEM
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
github-linguist (4.7.5)
github-linguist (4.7.6)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
......@@ -351,7 +351,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.0.0)
gitlab_git (10.0.1)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -922,7 +922,7 @@ DEPENDENCIES
devise (~> 3.5.4)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
doorkeeper (~> 2.2.0)
doorkeeper (~> 3.1)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
......
class @AwardsHandler
constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) ->
$(".js-add-award").on "click", (event) =>
constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
$('.js-add-award').on 'click', (event) =>
event.stopPropagation()
event.preventDefault()
@showEmojiMenu()
$("html").on 'click', (event) ->
if !$(event.target).closest(".emoji-menu").length
if $(".emoji-menu").is(":visible")
$(".emoji-menu").removeClass "is-visible"
$('html').on 'click', (event) ->
if !$(event.target).closest('.emoji-menu').length
if $('.emoji-menu').is(':visible')
$('.emoji-menu').removeClass 'is-visible'
$(".awards")
.off "click"
.on "click", ".js-emoji-btn", @handleClick
$('.awards')
.off 'click'
.on 'click', '.js-emoji-btn', @handleClick
@renderFrequentlyUsedBlock()
handleClick: (e) ->
e.preventDefault()
emoji = $(this)
.find(".icon")
.data "emoji"
.find('.icon')
.data 'emoji'
if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
awards_handler.addAward "thumbsdown"
if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
awardsHandler.addAward 'thumbsdown'
else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
awards_handler.addAward "thumbsup"
else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
awardsHandler.addAward 'thumbsup'
awards_handler.addAward emoji
awardsHandler.addAward emoji
$(this).trigger 'blur'
didUserClickEmoji: (that, emoji) ->
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
$(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title')
$(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1
showEmojiMenu: ->
if $(".emoji-menu").length
if $(".emoji-menu").is ".is-visible"
$(".emoji-menu").removeClass "is-visible"
$("#emoji_search").blur()
if $('.emoji-menu').length
if $('.emoji-menu').is '.is-visible'
$('.emoji-menu').removeClass 'is-visible'
$('#emoji_search').blur()
else
$(".emoji-menu").addClass "is-visible"
$("#emoji_search").focus()
$('.emoji-menu').addClass 'is-visible'
$('#emoji_search').focus()
else
$('.js-add-award').addClass "is-loading"
$.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response
$('.js-add-award').addClass 'is-loading'
$.get @getEmojisUrl, (response) =>
$('.js-add-award').removeClass 'is-loading'
$('.js-award-holder').append response
setTimeout =>
$(".emoji-menu").addClass "is-visible"
$("#emoji_search").focus()
$('.emoji-menu').addClass 'is-visible'
$('#emoji_search').focus()
@setupSearch()
, 200
......@@ -60,7 +60,7 @@ class @AwardsHandler
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
$(".emoji-menu").removeClass "is-visible"
$('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
......@@ -69,9 +69,9 @@ class @AwardsHandler
if @isActive(emoji)
@decrementCounter(emoji)
else
counter = @findEmojiIcon(emoji).siblings(".js-counter")
counter = @findEmojiIcon(emoji).siblings('.js-counter')
counter.text(parseInt(counter.text()) + 1)
counter.parent().addClass("active")
counter.parent().addClass('active')
@addMeToAuthorList(emoji)
else
@createEmoji(emoji)
......@@ -80,47 +80,47 @@ class @AwardsHandler
@findEmojiIcon(emoji).length > 0
isActive: (emoji) ->
@findEmojiIcon(emoji).parent().hasClass("active")
@findEmojiIcon(emoji).parent().hasClass('active')
decrementCounter: (emoji) ->
counter = @findEmojiIcon(emoji).siblings(".js-counter")
counter = @findEmojiIcon(emoji).siblings('.js-counter')
emojiIcon = counter.parent()
if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active")
emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
else if emoji == "thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy")
else if emoji == 'thumbsup' || emoji == 'thumbsdown'
emojiIcon.tooltip('destroy')
counter.text(0)
emojiIcon.removeClass("active")
emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
else
emojiIcon.tooltip("destroy")
emojiIcon.tooltip('destroy')
emojiIcon.remove()
removeMeFromAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent()
authors = award_block
.attr("data-original-title")
.split(", ")
authors.splice(authors.indexOf("me"),1)
award_block
.closest(".js-emoji-btn")
.attr("data-original-title", authors.join(", "))
@resetTooltip(award_block)
awardBlock = @findEmojiIcon(emoji).parent()
authors = awardBlock
.attr('data-original-title')
.split(', ')
authors.splice(authors.indexOf('me'),1)
awardBlock
.closest('.js-emoji-btn')
.attr('data-original-title', authors.join(', '))
@resetTooltip(awardBlock)
addMeToAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent()
origTitle = award_block.attr("data-original-title").trim()
awardBlock = @findEmojiIcon(emoji).parent()
origTitle = awardBlock.attr('data-original-title').trim()
authors = []
if origTitle
authors = origTitle.split(', ')
authors.push("me")
award_block.attr("data-original-title", authors.join(", "))
@resetTooltip(award_block)
authors.push('me')
awardBlock.attr('data-original-title', authors.join(', '))
@resetTooltip(awardBlock)
resetTooltip: (award) ->
award.tooltip("destroy")
award.tooltip('destroy')
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
......@@ -139,20 +139,28 @@ class @AwardsHandler
"</button>"
)
emoji_node = $(nodes.join("\n"))
.insertBefore(".js-award-holder")
.find(".emoji-icon")
.data("emoji", emoji)
$(nodes.join("\n"))
.insertBefore('.js-award-holder')
.find('.emoji-icon')
.data('emoji', emoji)
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) ->
"emoji-#{@unicodes[emoji]}"
emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
if emojiIcon.length > 0
unicodeName = emojiIcon.data('unicode-name')
else
# Find by alias
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
"emoji-#{unicodeName}"
postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: {
$.post @postEmojiUrl, { note: {
note: ":#{emoji}:"
noteable_type: @noteable_type
noteable_id: @noteable_id
noteable_type: @noteableType
noteable_id: @noteableId
}},(data) ->
if data.ok
callback.call()
......@@ -166,42 +174,42 @@ class @AwardsHandler
}, 200)
addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji)
$.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
frequentlyUsedEmojis.push(emoji)
$.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
getFrequentlyUsedEmojis: ->
frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
_.compact(_.uniq(frequently_used_emojis))
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
_.compact(_.uniq(frequentlyUsedEmojis))
renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul>")
ul = $('<ul>')
for emoji in frequently_used_emojis
for emoji in frequentlyUsedEmojis
do (emoji) ->
$(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: ->
$("input.emoji-search").keyup (ev) =>
$('input.emoji-search').keyup (ev) =>
term = $(ev.target).val()
# Clean previous search results
$("ul.emoji-menu-search, h5.emoji-search").remove()
$('ul.emoji-menu-search, h5.emoji-search').remove()
if term
# Generate a search result block
h5 = $("<h5>").text("Search results").addClass("emoji-search")
found_emojis = @searchEmojis(term).show()
ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis)
$(".emoji-menu-content ul, .emoji-menu-content h5").hide()
$(".emoji-menu-content").append(h5).append(ul)
h5 = $('<h5>').text('Search results').addClass('emoji-search')
foundEmojis = @searchEmojis(term).show()
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
$('.emoji-menu-content ul, .emoji-menu-content h5').hide()
$('.emoji-menu-content').append(h5).append(ul)
else
$(".emoji-menu-content").children().show()
$('.emoji-menu-content').children().show()
searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
......@@ -12,6 +12,7 @@ class @Issue
@initMergeRequests()
@initRelatedBranches()
@initCanCreateBranch()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
......@@ -92,3 +93,25 @@ class @Issue
.success (data) ->
if 'html' of data
$container.html(data.html)
initCanCreateBranch: ->
$container = $('div#new-branch')
# If the user doesn't have the required permissions the container isn't
# rendered at all.
return unless $container
$.getJSON($container.data('path'))
.error ->
$container.find('.checking').hide()
$container.find('.unavailable').show()
new Flash('Failed to check if a new branch can be created.', 'alert')
.success (data) ->
if data.can_create_branch
$container.find('.checking').hide()
$container.find('.available').show()
$container.find('a').attr('disabled', false)
else
$container.find('.checking').hide()
$container.find('.unavailable').show()
......@@ -163,6 +163,21 @@ class @LabelsSelect
$.ajax(
url: labelUrl
).done (data) ->
data = _.chain data
.groupBy (label) ->
label.title
.map (label) ->
color = _.map label, (dup) ->
dup.color
return {
id: label[0].id
title: label[0].title
color: color
duplicate: color.length > 1
}
.value()
if $dropdown.hasClass 'js-extra-options'
if showNo
data.unshift(
......@@ -178,6 +193,7 @@ class @LabelsSelect
if data.length > 2
data.splice 2, 0, 'divider'
callback data
renderRow: (label) ->
......@@ -192,11 +208,31 @@ class @LabelsSelect
if $dropdown.hasClass('js-multiselect') and removesAll
selectedClass.push 'dropdown-clear-active'
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
if label.duplicate
spacing = 100 / label.color.length
# Reduce the colors to 4
label.color = label.color.filter (color, i) ->
i < 4
color = _.map(label.color, (color, i) ->
percentFirst = Math.floor(spacing * i)
percentSecond = Math.floor(spacing * (i + 1))
"#{color} #{percentFirst}%,#{color} #{percentSecond}% "
).join(',')
color = "linear-gradient(#{color})"
else
if label.color?
color = label.color[0]
if color
colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
else
colorEl = ''
"<li>
<a href='#' class='#{selectedClass.join(' ')}'>
#{color}
#{colorEl}
#{_.escape(label.title)}
</a>
</li>"
......
......@@ -167,8 +167,8 @@ class @Notes
return
if note.award
awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards()
awardsHandler.addAwardToEmojiBar(note.note)
awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
......@@ -373,11 +373,11 @@ class @Notes
new GLForm form
if scrollTo? and myLastNote?
# scroll to the bottom
# 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
scrollTop: myLastNote.offset().top - 150
}, 500, ->
$noteText = form.find(".js-note-text")
$noteText.focus()
......
......@@ -25,6 +25,7 @@
@import "framework/lists.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/modal.scss";
@import "framework/nav.scss";
@import "framework/pagination.scss";
@import "framework/progress.scss";
......
.light-well {
background-color: #f8fafc;
background-color: $background-color;
padding: 15px;
}
......
......@@ -139,6 +139,10 @@
pointer-events: auto !important;
}
&[disabled] {
pointer-events: none !important;
}
.caret {
margin-left: 5px;
}
......
......@@ -78,6 +78,24 @@ label {
border-radius: 3px;
}
.select-wrapper {
position: relative;
.caret {
position: absolute;
right: 10px;
top: $gl-padding;
color: $gray-darkest;
pointer-events: none;
}
}
.select-control {
padding-left: 10px;
padding-right: 10px;
-webkit-appearance: none;
}
.form-control-inline {
display: inline;
}
......
......@@ -26,9 +26,9 @@ header {
z-index: 100;
margin-bottom: 0;
min-height: $header-height;
background-color: $gray-light;
background-color: $background-color;
border: none;
border-bottom: 1px solid #eee;
border-bottom: 1px solid $border-color;
.container-fluid {
width: 100% !important;
......@@ -47,7 +47,7 @@ header {
text-align: center;
&:hover, &:focus, &:active {
background-color: $gray-light;
background-color: $background-color;
}
}
......
.modal-body {
position: relative;
overflow-y: auto;
padding: 15px;
.form-actions {
margin: -$gl-padding+1;
margin-top: 15px;
}
.text-danger {
font-weight: bold;
}
}
body.modal-open {
overflow: hidden;
}
.modal .modal-dialog {
width: 860px;
}
......@@ -187,7 +187,7 @@
}
.layout-nav {
background: $gray-light;
background: $background-color;
border-bottom: 1px solid $border-color;
.controls {
......
......@@ -7,13 +7,11 @@
.select2-choice {
background: #fff;
border-color: $input-border;
border-color: $border-white-light;
height: 35px;
padding: $gl-vert-padding $gl-btn-padding;
font-size: $gl-font-size;
line-height: 1.42857143;
@include border-radius($border-radius-default);
border-radius: $border-radius-base;
.select2-arrow {
background-image: none;
......@@ -199,6 +197,14 @@
}
}
.select2-highlighted {
.group-result {
.group-path {
color: #fff;
}
}
}
.group-result {
.group-image {
float: left;
......
......@@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding;
//== Code
//
//##
$pre-bg: #f8fafc !default;
$pre-bg: $background-color !default;
$pre-color: $gl-gray !default;
$pre-border-color: #e7e9ed;
$pre-border-color: $border-color;
$table-bg-accent: $background-color;
......@@ -205,6 +205,10 @@ h1, h2, h3, h4, h5, h6 {
font-weight: 600;
}
.light-header {
font-weight: 600;
}
/** CODE **/
pre {
font-family: $monospace_font;
......@@ -259,3 +263,9 @@ h1, h2, h3, h4 {
color: $gl-gray;
}
}
.text-right-lg {
@media (min-width: $screen-lg-min) {
text-align: right;
}
}
......@@ -71,8 +71,7 @@ $gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
$border-radius-default: 2px;
$btn-transparent-color: #8f8f8f;
$ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px;
$settings-icon-size: 18px;
$provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1;
......
......@@ -55,25 +55,6 @@
}
}
.modal-body {
position: relative;
overflow-y: auto;
padding: 15px;
.form-actions {
margin: -$gl-padding+1;
margin-top: 15px;
}
}
body.modal-open {
overflow: hidden;
}
.modal .modal-dialog {
width: 860px;
}
.documentation {
padding: 7px;
}
......@@ -18,7 +18,8 @@
}
.account-btn-link,
.profile-settings-sidebar a {
.profile-settings-sidebar a,
.settings-sidebar a {
color: $md-link-color;
}
......@@ -123,12 +124,6 @@
}
}
.key-icon {
color: $ssh-key-icon-color;
font-size: $ssh-key-icon-size;
line-height: 42px;
}
.key-created-at {
line-height: 42px;
}
......@@ -180,14 +175,6 @@
}
}
.profile-settings-message {
line-height: 32px;
color: $warning-message-color;
background-color: $warning-message-bg;
border: 1px solid $warning-message-border;
border-radius: $border-radius-base;
}
.oauth-applications {
form {
display: inline-block;
......
......@@ -202,8 +202,31 @@
min-width: 200px;
}
.deploy-project-label {
margin: 1px;
.deploy-key-content {
@media (min-width: $screen-sm-min) {
float: left;
&:last-child {
float: right;
}
}
}
.deploy-key-projects {
@media (min-width: $screen-sm-min) {
line-height: 42px;
}
}
a.deploy-project-label {
padding: 5px;
margin-right: 5px;
color: $gl-gray;
background-color: $row-hover;
&:hover {
color: $gl-link-color;
}
}
.vs-public {
......@@ -256,12 +279,6 @@
}
}
table.table.protected-branches-list tr.no-border {
th, td {
border: 0;
}
}
.project-import .btn {
float: left;
margin-right: 10px;
......@@ -474,3 +491,14 @@ pre.light-well {
color: #fff;
}
}
.protected-branches-list {
a {
color: $gl-gray;
font-weight: 600;
&:hover {
color: $gl-link-color;
}
}
}
.settings-list-icon {
color: $gl-placeholder-color;
font-size: $settings-icon-size;
line-height: 42px;
}
.settings-message {
padding: 5px;
line-height: 1.3;
color: $warning-message-color;
background-color: $warning-message-bg;
border: 1px solid $warning-message-border;
border-radius: $border-radius-base;
}
......@@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
return render_404 unless current_user.is_admin?
end
def authorize_impersonator!
if session[:impersonator_id]
User.find_by!(username: session[:impersonator_id]).admin?
end
render_404 unless current_user.is_admin?
end
end
......@@ -39,6 +39,12 @@ class Admin::HooksController < Admin::ApplicationController
end
def hook_params
params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
params.require(:hook).permit(
:enable_ssl_verification,
:push_events,
:tag_push_events,
:token,
:url
)
end
end
class Admin::ImpersonationController < Admin::ApplicationController
skip_before_action :authenticate_admin!, only: :destroy
before_action :user
before_action :authorize_impersonator!
def create
if @user.blocked?
flash[:alert] = "You cannot impersonate a blocked user"
redirect_to admin_user_path(@user)
else
session[:impersonator_id] = current_user.username
session[:impersonator_return_to] = admin_user_path(@user)
warden.set_user(user, scope: 'user')
flash[:alert] = "You are impersonating #{user.username}."
redirect_to root_path
end
end
def destroy
redirect = session[:impersonator_return_to]
warden.set_user(user, scope: 'user')
session[:impersonator_return_to] = nil
session[:impersonator_id] = nil
redirect_to redirect || root_path
end
def user
@user ||= User.find_by!(username: params[:id] || session[:impersonator_id])
end
end
class Admin::ImpersonationsController < Admin::ApplicationController
skip_before_action :authenticate_admin!
before_action :authenticate_impersonator!
def destroy
original_user = current_user
warden.set_user(impersonator, scope: :user)
session[:impersonator_id] = nil
redirect_to admin_user_path(original_user)
end
private
def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
def authenticate_impersonator!
render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
end
end
......@@ -31,6 +31,22 @@ class Admin::UsersController < Admin::ApplicationController
user
end
def impersonate
if user.blocked?
flash[:alert] = "You cannot impersonate a blocked user"
redirect_to admin_user_path(user)
else
session[:impersonator_id] = current_user.id
warden.set_user(user, scope: :user)
flash[:alert] = "You are now impersonating #{user.username}"
redirect_to root_path
end
end
def block
if user.block
redirect_back_or_admin_user(notice: "Successfully blocked")
......
......@@ -7,31 +7,24 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings"
def index
@enabled_keys = @project.deploy_keys
@available_keys = accessible_keys - @enabled_keys
@available_project_keys = current_user.project_deploy_keys - @enabled_keys
@available_public_keys = DeployKey.are_public - @enabled_keys
# Public keys that are already used by another accessible project are already
# in @available_project_keys.
@available_public_keys -= @available_project_keys
@key = DeployKey.new
set_index_vars
end
def new
@key = @project.deploy_keys.new
respond_with(@key)
redirect_to namespace_project_deploy_keys_path(@project.namespace,
@project)
end
def create
@key = DeployKey.new(deploy_key_params)
set_index_vars
if @key.valid? && @project.deploy_keys << @key
redirect_to namespace_project_deploy_keys_path(@project.namespace,
@project)
else
render "new"
render "index"
end
end
......@@ -51,6 +44,18 @@ class Projects::DeployKeysController < Projects::ApplicationController
protected
def set_index_vars
@enabled_keys ||= @project.deploy_keys
@available_keys ||= accessible_keys - @enabled_keys
@available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
@available_public_keys ||= DeployKey.are_public - @enabled_keys
# Public keys that are already used by another accessible project are already
# in @available_project_keys.
@available_public_keys -= @available_project_keys
end
def accessible_keys
@accessible_keys ||= current_user.accessible_deploy_keys
end
......
......@@ -52,8 +52,16 @@ class Projects::HooksController < Projects::ApplicationController
end
def hook_params
params.require(:hook).permit(:url, :push_events, :issues_events,
:merge_requests_events, :tag_push_events, :note_events,
:build_events, :enable_ssl_verification)
params.require(:hook).permit(
:build_events,
:enable_ssl_verification,
:issues_events,
:merge_requests_events,
:note_events,
:push_events,
:tag_push_events,
:token,
:url
)
end
end
......@@ -3,8 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
before_action :issue,
only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
:related_branches, :can_create_branch]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
......@@ -96,6 +96,8 @@ class Projects::IssuesController < Projects::ApplicationController
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
return render_404 unless issue.can_move?(current_user, new_project)
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
end
......@@ -139,6 +141,18 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
def can_create_branch
can_create = current_user &&
can?(current_user, :push_code, @project) &&
@issue.can_be_worked_on?(current_user)
respond_to do |format|
format.json do
render json: { can_create_branch: can_create }
end
end
end
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
......
......@@ -40,10 +40,10 @@ class Projects::WikisController < Projects::ApplicationController
end
def update
@page = @project_wiki.find_page(params[:id])
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = @project_wiki.find_page(params[:id])
if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
......
......@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh
if current_user
if project.team.member?(current_user.id)
if project.team.member?(current_user.id) || current_user.admin?
snippets
else
snippets.public_and_internal
......
......@@ -3,8 +3,8 @@ module BlobHelper
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
end
def highlight(blob_name, blob_content, nowrap: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap)
def highlight(blob_name, blob_content, nowrap: false, plain: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain)
end
def no_highlight_files
......
module CiBadgeHelper
def markdown_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
link = namespace_project_commits_path(project.namespace, project, ref)
"[![build status](#{url})](#{link})"
end
def html_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
link = namespace_project_commits_path(project.namespace, project, ref)
"<a href='#{link}'><img src='#{url}' /></a>"
end
end
......@@ -16,31 +16,49 @@ module IssuesHelper
def url_for_project_issues(project = @project, options = {})
return '' if project.nil?
if options[:only_path]
project.issues_tracker.project_path
else
project.issues_tracker.project_url
end
url =
if options[:only_path]
project.issues_tracker.project_path
else
project.issues_tracker.project_url
end
# Ensure we return a valid URL to prevent possible XSS.
URI.parse(url).to_s
rescue URI::InvalidURIError
''
end
def url_for_new_issue(project = @project, options = {})
return '' if project.nil?
if options[:only_path]
project.issues_tracker.new_issue_path
else
project.issues_tracker.new_issue_url
end
url =
if options[:only_path]
project.issues_tracker.new_issue_path
else
project.issues_tracker.new_issue_url
end
# Ensure we return a valid URL to prevent possible XSS.
URI.parse(url).to_s
rescue URI::InvalidURIError
''
end
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
if options[:only_path]
project.issues_tracker.issue_path(issue_iid)
else
project.issues_tracker.issue_url(issue_iid)
end
url =
if options[:only_path]
project.issues_tracker.issue_path(issue_iid)
else
project.issues_tracker.issue_url(issue_iid)
end
# Ensure we return a valid URL to prevent possible XSS.
URI.parse(url).to_s
rescue URI::InvalidURIError
''
end
def bulk_update_milestone_options
......
......@@ -28,6 +28,14 @@ module Emails
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
def note_snippet_email(recipient_id, note_id)
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
@target_url = namespace_project_snippet_url(*note_target_url_options)
mail_answer_thread(@snippet, note_thread_options(recipient_id))
end
private
def note_target_url_options
......
......@@ -19,6 +19,14 @@ class Blob < SimpleDelegator
new(blob)
end
def no_highlighting?
size && size > 1.megabyte
end
def only_display_raw?
size && size > 5.megabytes
end
def svg?
text? && language && language.name == 'SVG'
end
......
......@@ -8,7 +8,7 @@ module Milestoneish
end
def complete?(user = nil)
total_items_count(user) == closed_items_count(user)
total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end
def percent_complete(user = nil)
......
......@@ -18,7 +18,7 @@ module Statuseable
WHEN (#{builds})=0 THEN NULL
WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
WHEN (#{builds})=(#{pending}) THEN 'pending'
WHEN (#{builds})=(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed'
......
......@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# token :string
#
class ProjectHook < WebHook
......
......@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# token :string
#
class ServiceHook < WebHook
......
......@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# token :string
#
class SystemHook < WebHook
......
......@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# token :string
#
class WebHook < ActiveRecord::Base
......@@ -43,23 +44,17 @@ class WebHook < ActiveRecord::Base
if parsed_url.userinfo.blank?
response = WebHook.post(url,
body: data.to_json,
headers: {
"Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize
},
headers: build_headers(hook_name),
verify: enable_ssl_verification)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
post_url = url.gsub("#{parsed_url.userinfo}@", '')
auth = {
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password),
}
response = WebHook.post(post_url,
body: data.to_json,
headers: {
"Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize
},
headers: build_headers(hook_name),
verify: enable_ssl_verification,
basic_auth: auth)
end
......@@ -73,4 +68,15 @@ class WebHook < ActiveRecord::Base
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name)
end
private
def build_headers(hook_name)
headers = {
'Content-Type' => 'application/json',
'X-Gitlab-Event' => hook_name.singularize.titleize
}
headers['X-Gitlab-Token'] = token if token.present?
headers
end
end
......@@ -735,19 +735,17 @@ class Project < ActiveRecord::Base
end
def open_branches
all_branches = repository.branches
# We're using a Set here as checking values in a large Set is faster than
# checking values in a large Array.
protected_set = Set.new(protected_branch_names)
if protected_branches.present?
all_branches.reject! do |branch|
protected_branches_names.include?(branch.name)
end
repository.branches.reject do |branch|
protected_set.include?(branch.name)
end
all_branches
end
def protected_branches_names
@protected_branches_names ||= protected_branches.map(&:name)
def protected_branch_names
@protected_branch_names ||= protected_branches.pluck(:name)
end
def root_ref?(branch)
......@@ -764,7 +762,7 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
protected_branches_names.include?(branch_name)
protected_branches.where(name: branch_name).any?
end
def developers_can_push_to_protected_branch?(branch_name)
......@@ -901,6 +899,7 @@ class Project < ActiveRecord::Base
repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
repository.copy_gitattributes(branch)
reload_default_branch
end
......
......@@ -26,7 +26,7 @@ class BuildkiteService < CiService
prop_accessor :project_url, :token, :enable_ssl_verification
validates :project_url, presence: true, if: :activated?
validates :project_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
......@@ -91,7 +91,7 @@ class BuildkiteService < CiService
{ type: 'text',
name: 'project_url',
placeholder: "#{ENDPOINT}/example/project" },
{ type: 'checkbox',
name: 'enable_ssl_verification',
title: "Enable SSL verification" }
......
......@@ -21,7 +21,7 @@
class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
default_value_for :category, 'issue_tracker'
......
......@@ -28,6 +28,8 @@ class JiraService < IssueTrackerService
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
validates :api_url, presence: true, url: true, if: :activated?
before_validation :set_api_url, :set_jira_issue_transition_id
before_update :reset_password
......
......@@ -22,7 +22,7 @@
class SlackService < Service
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
validates :webhook, presence: true, if: :activated?
validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
if properties.nil?
......
......@@ -22,4 +22,6 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
participant :author, :notes
end
......@@ -938,6 +938,16 @@ class Repository
raw_repository.ls_files(actual_ref)
end
def copy_gitattributes(ref)
actual_ref = ref || root_ref
begin
raw_repository.copy_gitattributes(actual_ref)
true
rescue Gitlab::Git::Repository::InvalidRef
false
end
end
def main_language
return if empty? || rugged.head_unborn?
......
......@@ -112,6 +112,10 @@ class Snippet < ActiveRecord::Base
visibility_level
end
def no_highlighting?
content.lines.count > 1000
end
class << self
# Searches for snippets with a matching title or file name.
#
......
......@@ -42,7 +42,12 @@ class GitPushService < BaseService
# Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
# Update the bare repositories info/attributes file using the contents of the default branches
# .gitattributes file
update_gitattributes if is_default_branch?
end
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
update_merge_requests
......@@ -54,6 +59,10 @@ class GitPushService < BaseService
perform_housekeeping
end
def update_gitattributes
@project.repository.copy_gitattributes(params[:ref])
end
def update_main_language
# Performance can be bad so for now only check main_language once
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
......
......@@ -7,6 +7,9 @@ module MergeRequests
merge_request.can_be_created = false
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
......
......@@ -5,6 +5,8 @@ module Notes
note.author = current_user
note.system = false
return unless valid_project?(note)
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
......@@ -13,5 +15,14 @@ module Notes
note
end
private
def valid_project?(note)
return false unless project
return true if note.for_commit?
note.noteable.try(:project) == project
end
end
end
module WikiPages
class CreateService < WikiPages::BaseService
def execute
page = WikiPage.new(@project.wiki)
project_wiki = ProjectWiki.new(@project, current_user)
page = WikiPage.new(project_wiki)
if page.create(@params)
execute_hooks(page, 'create')
......
......@@ -13,9 +13,15 @@
= form_errors(@hook)
.form-group
= f.label :url, "URL:", class: 'control-label'
= f.label :url, 'URL', class: 'control-label'
.col-sm-10
= f.text_field :url, class: "form-control"
= f.text_field :url, class: 'form-control'
.form-group
= f.label :token, 'Secret Token', class: 'control-label'
.col-sm-10
= f.text_field :token, class: 'form-control'
%p.help-block
Use this token to validate received payloads
.form-group
= f.label :url, "Trigger", class: 'control-label'
.col-sm-10.prepend-top-10
......
......@@ -44,7 +44,7 @@
= icon('pencil')
= render 'delete_form', application: application, small: true
- else
.profile-settings-message.text-center
.settings-message.text-center
You don't have any applications
.oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications?
......@@ -78,5 +78,5 @@
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
.profile-settings-message.text-center
.settings-message.text-center
You don't have any authorized applications
......@@ -15,7 +15,7 @@
- if current_user
- if session[:impersonator_id]
%li.impersonation
= link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
......
New comment for Snippet <%= @snippet.id %>
<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %>
Author: <%= @note.author_name %>
<%= @note.note %>
......@@ -45,4 +45,4 @@
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
%li.key-list-item
.pull-left.append-right-10
= icon 'key', class: "key-icon hidden-xs"
= icon 'key', class: "settings-list-icon hidden-xs"
.key-list-item-info
= link_to path_to_key(key, is_admin), class: "title" do
= key.title
......
......@@ -4,7 +4,7 @@
%ul.well-list
= render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin }
- else
%p.profile-settings-message.text-center
%p.settings-message.text-center
- if is_admin
There are no SSH keys associated with this account.
- else
......
- blob.load_all_data!(@repository)
- if markup?(blob.name)
.file-content.wiki
= render_markup(blob.name, blob.data)
- if blob.only_display_raw?
.file-content.code
.nothing-here-block
File too large, you can
= succeed '.' do
= link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank'
- else
- unless blob.empty?
= render 'shared/file_highlight', blob: blob
- blob.load_all_data!(@repository)
- if markup?(blob.name)
.file-content.wiki
= render_markup(blob.name, blob.data)
- else
.file-content.code
.nothing-here-block Empty file
- if blob.empty?
.file-content.code
.nothing-here-block Empty file
- else
= render 'shared/file_highlight', blob: blob
%li
.pull-right
.pull-left.append-right-10.hidden-xs
= icon "key", class: "key-icon"
.deploy-key-content.key-list-item-info
%strong.title
= deploy_key.title
.description
= deploy_key.fingerprint
.deploy-key-content.prepend-left-default.deploy-key-projects
- deploy_key.projects.each do |project|
- if can?(current_user, :read_project, project)
= link_to namespace_project_path(project.namespace, project), class: "label deploy-project-label" do
= project.name_with_namespace
.deploy-key-content
%span.key-created-at
created #{time_ago_with_tooltip(deploy_key.created_at)}
.visible-xs-block.visible-sm-block
- if @available_keys.include?(deploy_key)
= link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
= icon('plus')
= link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do
Enable
- else
- if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned?
= link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right"
= link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: "You are going to remove deploy key. Are you sure?" }, method: :put, class: "btn btn-warning btn-sm prepend-left-10" do
Remove
- else
= link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
= icon('power-off')
= link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-warning btn-sm prepend-left-10", method: :put do
Disable
= icon('key')
%strong= deploy_key.title
%br
%code.key-fingerprint= deploy_key.fingerprint
%p.light.prepend-top-10
- if deploy_key.public?
%span.label.label-info.deploy-project-label
Public deploy key
- deploy_key.projects.each do |project|
- if can?(current_user, :read_project, project)
%span.label.label-gray.deploy-project-label
= link_to namespace_project_path(project.namespace, project) do
= project.name_with_namespace
%small.pull-right
Created #{time_ago_with_tooltip(deploy_key.created_at)}
%div
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
= form_errors(@key)
.form-group
= f.label :title, class: "control-label"
.col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
.form-group
= f.label :key, class: "control-label"
.col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh", "README")
= f.text_area :key, class: "form-control thin_area", rows: 5, required: true
.form-actions
= f.submit 'Create Deploy Key', class: "btn-create btn"
= link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
= form_errors(@key)
.form-group
= f.label :title, class: "label-light"
= f.text_field :title, class: 'form-control', autofocus: true, required: true
.form-group
= f.label :key, class: "label-light"
= f.text_area :key, class: "form-control", rows: 5, required: true
.form-group
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh", "README")
= f.submit "Add key", class: "btn-create btn"
- page_title "Deploy Keys"
%h3.page-title
Deploy keys allow read-only access to the repository
= link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do
%i.fa.fa-plus
New Deploy Key
%p.light
Deploy keys can be used for CI, staging or production servers.
You can create a deploy key or add an existing one
%hr.clearfix
.row
.col-md-6.enabled-keys
%h5
%strong.cgreen Enabled deploy keys
for this project
%ul.bordered-list
= render @enabled_keys
- if @enabled_keys.blank?
.light-well
.nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one
.col-md-6.available-keys
- # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown.
- if @available_project_keys.any? || @available_public_keys.blank?
%h5
%strong Deploy keys
from projects you have access to
%ul.bordered-list
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
.col-lg-9
%h5.prepend-top-0
Create a new deploy key for this project
= render "form"
.col-lg-9.col-lg-offset-3
%hr
.col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
%h5.prepend-top-0
Enabled deploy keys for this project (#{@enabled_keys.size})
- if @enabled_keys.any?
%ul.well-list
= render @enabled_keys
- else
.profile-settings-message.text-center
No deploy keys found. Create one with the form above or add existing one below.
%h5.prepend-top-default
Deploy keys from projects you have access to (#{@available_project_keys.size})
- if @available_project_keys.any?
%ul.well-list
= render @available_project_keys
- if @available_project_keys.blank?
.light-well
.nothing-here-block Deploy keys from projects you have access to will be displayed here
- else
.profile-settings-message.text-center
No deploy keys from your projects could be found. Create one with the form above or add existing one below.
- if @available_public_keys.any?
%h5
%strong Public deploy keys
available to any project
%ul.bordered-list
%h5.prepend-top-default
Public deploy keys available to any project (#{@available_public_keys.size})
%ul.well-list
= render @available_public_keys
......@@ -40,19 +40,19 @@
= view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
- # Skip all non non-supported blobs
- return unless blob.respond_to?('text?')
- if diff_file.too_large?
.nothing-here-block
This diff could not be displayed because it is too large.
- else
- if blob_text_viewable?(blob)
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.image?
- old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
.nothing-here-block This diff could not be displayed because it is too large.
- elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif blob_text_viewable?(blob)
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
.nothing-here-block No preview for this file type
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.image?
- old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
- else
.nothing-here-block No preview for this file type
- page_title "Groups"
%h3.page_title Share project with other groups
%p.light
Projects can be stored in only one group at once. However you can share a project with other groups here.
%hr
- if @group_links.present?
.enabled-groups.panel.panel-default
.panel-heading
Already shared with
%ul.well-list
- @group_links.each do |group_link|
- group = group_link.group
%li
.pull-right
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: 'btn btn-sm' do
%i.icon-remove
disable sharing
= link_to group do
%strong
%i.icon-folder-open
= group.name
%br
.light up to #{group_link.human_access}
.available-groups
%h4
Can be shared with
%div
= form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post, class: 'form-horizontal' do
.row.prepend-top-default
.col-lg-3.settings-sidebar
%h4.prepend-top-0
Share project with other groups
%p
Projects can be stored in only one group at once. However you can share a project with other groups here.
.col-lg-9
%h5.prepend-top-0
Set a group to share
= form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post do
.form-group
= label_tag :link_group_id, 'Group', class: 'control-label'
.col-sm-10
= groups_select_tag(:link_group_id, skip_group: @project.group.try(:path))
= label_tag :link_group_id, "Group", class: "label-light"
= groups_select_tag(:link_group_id, skip_group: @project.group.try(:path))
.form-group
= label_tag :link_group_access, 'Max access level', class: 'control-label'
.col-sm-10
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control"
.form-actions
= submit_tag "Share", class: "btn btn-create"
= label_tag :link_group_access, "Max access level", class: "label-light"
.select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
%span.caret
= submit_tag "Share", class: "btn btn-create"
.col-lg-9.col-lg-offset-3
%hr
.col-lg-9.col-lg-offset-3.append-bottom-default.enabled-groups
%h5.prepend-top-0
Groups you share with (#{@group_links.size})
- if @group_links.present?
%ul.well-list
- @group_links.each do |group_link|
- group = group_link.group
%li
.pull-left.append-right-10.hidden-xs
= icon("folder-open-o", class: "settings-list-icon")
.pull-left
= link_to group do
= group.name
%br
up to #{group_link.human_access}
.pull-right
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do
%span.sr-only disable sharing
= icon("trash")
- else
.settings-message.text-center
There are no groups with access to your project, add one in the form above
%li
.row
.col-md-8.col-lg-7
%strong.light-header= 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.deploy-project-label= trigger.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
= link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
= link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
%span.sr-only Remove
= icon('trash')
- page_title "Webhooks"
%h3.page-title
Webhooks
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
#{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be
used for binding events when something is happening within the project.
.col-lg-9.append-bottom-default
%h5.prepend-top-0
Add new webhook
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project) do |f|
= form_errors(@hook)
%p.light
#{link_to "Webhooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be
used for binding events when something is happening within the project.
%hr.clearfix
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
= form_errors(@hook)
.form-group
= f.label :url, "URL", class: 'control-label'
.col-sm-10
= f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
.form-group
= f.label :url, "Trigger", class: 'control-label'
.col-sm-10.prepend-top-10
%div
= 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
%div
= f.check_box :note_events, class: 'pull-left'
.prepend-left-20
= f.label :note_events, class: 'list-label' do
%strong Comments
%p.light
This url will be triggered when someone adds a comment
%div
= f.check_box :issues_events, class: 'pull-left'
.prepend-left-20
= f.label :issues_events, class: 'list-label' do
%strong Issues events
%p.light
This url will be triggered when an issue is created/updated/merged
%div
= f.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20
= f.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events
%p.light
This url will be triggered when a merge request is created/updated/merged
%div
= f.check_box :build_events, class: 'pull-left'
.prepend-left-20
= f.label :build_events, class: 'list-label' do
%strong Build events
%p.light
This url will be triggered when the build status changes
.form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
.col-sm-10
.checkbox
= f.label :enable_ssl_verification do
= f.check_box :enable_ssl_verification
%strong Enable SSL verification
.form-actions
= f.submit "Add Webhook", class: "btn btn-create"
-if @hooks.any?
.panel.panel-default
.panel-heading
.form-group
= f.label :url, "URL", class: "label-light"
= f.text_field :url, class: "form-control", placeholder: "http://example.com/trigger-ci.json"
.form-group
= f.label :token, "Secret Token", class: 'label-light'
= f.text_field :token, class: "form-control", placeholder: ''
%p.help-block
Use this token to validate received payloads
.form-group
= f.label :url, "Trigger", class: "label-light"
%div
= f.check_box :push_events, class: "pull-left"
.prepend-left-20
= f.label :push_events, class: "label-light append-bottom-0" do
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: "label-light append-bottom-0" do
Tag push events
%p.light
This url will be triggered when a new tag is pushed to the repository
%div
= f.check_box :note_events, class: "pull-left"
.prepend-left-20
= f.label :note_events, class: "label-light append-bottom-0" do
Comments
%p.light
This url will be triggered when someone adds a comment
%div
= f.check_box :issues_events, class: "pull-left"
.prepend-left-20
= f.label :issues_events, class: "label-light append-bottom-0" do
Issues events
%p.light
This url will be triggered when an issue is created/updated/merged
%div
= f.check_box :merge_requests_events, class: "pull-left"
.prepend-left-20
= f.label :merge_requests_events, class: "label-light append-bottom-0" do
Merge Request events
%p.light
This url will be triggered when a merge request is created/updated/merged
%div
= f.check_box :build_events, class: "pull-left"
.prepend-left-20
= f.label :build_events, class: "label-light append-bottom-0" do
Build events
%p.light
This url will be triggered when the build status changes
.form-group
= f.label :enable_ssl_verification, "SSL verification", class: "label-light"
%div
= f.check_box :enable_ssl_verification, class: "pull-left"
.prepend-left-20
= f.label :enable_ssl_verification, class: "label-light append-bottom-0" do
Enable SSL verification
= f.submit "Add Webhook", class: "btn btn-create"
%hr
%h5.prepend-top-default
Webhooks (#{@hooks.count})
%ul.content-list
- @hooks.each do |hook|
%li
.controls
= 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"
.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"}
- if @hooks.any?
%ul.well-list
- @hooks.each do |hook|
= render "project_hook", hook: hook
- else
%p.profile-settings-message.text-center.append-bottom-0
No webhooks found, add one in the form above.
- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
- if can?(current_user, :push_code, @project)
.pull-right
= link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name do
= icon('code-fork')
New Branch
#new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
= link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
.checking
%i.fa.fa-spinner.fa-spin
Checking branches
.available(style="display: none")
%i.fa.fa-code-fork
New branch
.unavailable(style="display: none")
%i.fa.fa-exclamation-triangle
New branch unavailable
......@@ -42,9 +42,12 @@
= preserve do
= markdown @milestone.description
- if @milestone.complete?(current_user) && @milestone.active?
- if @milestone.total_items_count(current_user).zero?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
%span Assign some issues to this milestone.
- elsif @milestone.complete?(current_user) && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close this milestone now.
= render 'shared/milestones/summary', milestone: @milestone, project: @project
= render 'shared/milestones/tabs', milestone: @milestone
- unless @branches.empty?
%br
%h4 Already Protected:
.table-holder
%h5.prepend-top-0
Already Protected (#{@branches.size})
- if @branches.empty?
%p.profile-settings-message.text-center
No branches are protected, protect a branch with the form above.
- else
- can_admin_project = can?(current_user, :admin_project, @project)
.table-responsive
%table.table.protected-branches-list
%colgroup
%col{ width: "30%" }
%col{ width: "30%" }
%col{ width: "25%" }
- if can_admin_project
%col
%thead
%tr.no-border
%tr
%th Branch
%th Developers can push
%th Last commit
%th
%th Developers can push
- if can_admin_project
%th
%tbody
- @branches.each do |branch|
- @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
%tr
%td
= link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
%strong= branch.name
- if @project.root_ref?(branch.name)
%span.label.label-info default
%td
= check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
%td
- if commit = branch.commit
= link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
= commit.short_id
&middot;
#{time_ago_with_tooltip(commit.committed_date)}
- else
(branch was removed from repository)
= link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name))
- if @project.root_ref?(branch.name)
%span.label.label-info.prepend-left-5 default
%td
- if commit = branch.commit
= link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
#{time_ago_with_tooltip(commit.committed_date)}
- else
(branch was removed from repository)
%td
= check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url })
- if can_admin_project
%td
.pull-right
- if can? current_user, :admin_project, @project
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm"
- page_title "Protected branches"
%h3.page-title Protected branches
%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
.well
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
%li prevent anyone from force pushing to the branch
%li prevent anyone from deleting the branch
%p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
= page_title
%p Keep stable branches secure and force developers to use Merge Requests
.col-lg-9
%h5.prepend-top-0
Protect a branch
.account-well.append-bottom-default
%p.light-header.append-bottom-0 Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
%li prevent anyone from force pushing to the branch
%li prevent anyone from deleting the branch
%p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
= form_errors(@protected_branch)
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
= form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :developers_can_push do
= f.check_box :developers_can_push
%strong Developers can push
.help-block Allow developers to push to this branch
.form-actions
= f.submit 'Protect', class: "btn-create btn"
= render 'branches_list'
.form-group
= f.label :name, "Branch", class: "label-light"
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
.form-group
= f.check_box :developers_can_push, class: "pull-left"
.prepend-left-20
= f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
%p.light.append-bottom-0
Allow developers to push to this branch
= f.submit "Protect", class: "btn-create btn"
%hr
= render "branches_list"
%tr
%td
.clearfix
%span.monospace= trigger.token
%span.monospace= trigger.token
%td
- if trigger.last_trigger_request
......@@ -9,6 +8,5 @@
- else
Never
%td
.pull-right
= link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped"
%td.text-right
= link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-warning btn-sm"
- page_title "Triggers"
%h3.page-title
Triggers
%p.light
Triggers can be used to force a rebuild of a specific branch or tag with an API call.
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
= page_title
%p
Triggers can be used to force a rebuild of a specific branch or tag with an API call.
.col-lg-9
%h5.prepend-top-0
Your triggers
- if @triggers.any?
.table-responsive
%table.table
%thead
%th Token
%th Last used
%th
= render partial: 'trigger', collection: @triggers, as: :trigger
- else
%p.profile-settings-message.text-center.append-bottom-default
There are no triggers to use, add one by the button below.
%hr.clearfix
= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
= f.submit "Add Trigger", class: 'btn btn-success'
-if @triggers.any?
.table-holder
%table.table
%thead
%th Token
%th Last used
%th
= render partial: 'trigger', collection: @triggers, as: :trigger
- else
%h4 No triggers
%h5.prepend-top-default
Use CURL
= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f|
.clearfix
= f.submit "Add Trigger", class: 'btn btn-success pull-right'
%p.light
Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
%hr.clearfix
-if @triggers.any?
%h3
Use CURL
%pre
:plain
curl -X POST \
-F token=TOKEN \
-F ref=REF_NAME \
#{builds_trigger_url(@project.id)}
%h5.prepend-top-default
Use .gitlab-ci.yml
%p.light
Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
%p.light
Copy the snippet to
%i .gitlab-ci.yml
of dependent project.
At the end of your build it will trigger this project to rebuilt.
%pre
:plain
trigger:
type: deploy
script:
- "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
%h5.prepend-top-default
Pass build variables
%pre
:plain
curl -X POST \
-F token=TOKEN \
-F ref=REF_NAME \
#{builds_trigger_url(@project.id)}
%h3
Use .gitlab-ci.yml
%p.light
Add
%strong variables[VARIABLE]=VALUE
to API request.
The value of variable could then be used to distinguish triggered build from normal one.
%p.light
Copy the snippet to
%i .gitlab-ci.yml
of dependent project.
At the end of your build it will trigger this project to rebuilt.
%pre
:plain
trigger:
type: deploy
script:
- "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
%h3
Pass build variables
%p.light
Add
%strong variables[VARIABLE]=VALUE
to API request.
The value of variable could then be used to distinguish triggered build from normal one.
%pre
:plain
curl -X POST \
-F token=TOKEN \
-F "ref=REF_NAME" \
-F "variables[RUN_NIGHTLY_BUILD]=true" \
#{builds_trigger_url(@project.id)}
%pre.append-bottom-0
:plain
curl -X POST \
-F token=TOKEN \
-F "ref=REF_NAME" \
-F "variables[RUN_NIGHTLY_BUILD]=true" \
#{builds_trigger_url(@project.id)}
......@@ -7,7 +7,7 @@
Confirmation required
.modal-body
%p.cred.lead.js-confirm-text
%p.text-danger.js-confirm-text
%p
This action can lead to data loss.
......
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- link_icon = icon('link')
- blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
%i.fa.fa-link
= link_icon
= i
.blob-content{data: {blob_id: blob.id}}
= highlight(blob.name, blob.data)
= highlight(blob.name, blob.data, plain: blob.no_highlighting?)
......@@ -15,16 +15,16 @@
- if current_user
:javascript
var get_emojis_url = "#{emojis_path}";
var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
var noteable_type = "#{votable.class.name.underscore}";
var noteable_id = "#{votable.id}";
var getEmojisUrl = "#{emojis_path}";
var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
var noteableType = "#{votable.class.name.underscore}";
var noteableId = "#{votable.id}";
var unicodes = #{AwardEmoji.unicode.to_json};
window.awards_handler = new AwardsHandler(
get_emojis_url,
post_emoji_url,
noteable_type,
noteable_id,
window.awardsHandler = new AwardsHandler(
getEmojisUrl,
postEmojiUrl,
noteableType,
noteableId,
unicodes
);
......@@ -32,7 +32,30 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables, :import_url)
#
# Parameters filtered:
# - Password (:password, :password_confirmation)
# - Private tokens (:private_token)
# - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url)
# - Build variables (:variables)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
config.filter_parameters += %i(
certificate
encrypted_key
hook
import_url
key
otp_attempt
password
password_confirmation
private_token
sentry_dsn
variables
)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
......
......@@ -168,9 +168,9 @@ production: &base
# once per hour you will have concurrent 'git fsck' jobs.
repository_check_worker:
cron: "20 * * * *"
# Send admin emails once a day
# Send admin emails once a week
admin_email_worker:
cron: "0 0 * * *"
cron: "0 0 * * 0"
# Remove outdated repository archives
repository_archive_cache_worker:
......@@ -350,6 +350,8 @@ production: &base
# - { name: 'github',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# url: "https://github.com/",
# verify_ssl: true,
# args: { scope: 'user:email' } }
# - { name: 'bitbucket',
# app_id: 'YOUR_APP_ID',
......
......@@ -140,6 +140,30 @@ Settings.omniauth.cas3['session_duration'] ||= 8.hours
Settings.omniauth['session_tickets'] ||= Settingslogic.new({})
Settings.omniauth.session_tickets['cas3'] = 'ticket'
# Fill out omniauth-gitlab settings. It is needed for easy set up GHE or GH by just specifying url.
github_default_url = "https://github.com"
github_settings = Settings.omniauth['providers'].find { |provider| provider["name"] == "github" }
if github_settings
# For compatibility with old config files (before 7.8)
# where people dont have url in github settings
if github_settings['url'].blank?
github_settings['url'] = github_default_url
end
github_settings["args"] ||= Settingslogic.new({})
if github_settings["url"].include?(github_default_url)
github_settings["args"]["client_options"] = OmniAuth::Strategies::GitHub.default_options[:client_options]
else
github_settings["args"]["client_options"] = {
"site" => File.join(github_settings["url"], "api/v3"),
"authorize_url" => File.join(github_settings["url"], "login/oauth/authorize"),
"token_url" => File.join(github_settings["url"], "login/oauth/access_token")
}
end
end
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root)
......@@ -245,7 +269,7 @@ Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *'
Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::BatchWorker'
Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *'
......
......@@ -17,8 +17,9 @@ paths_to_be_protected = [
# Create one big regular expression that matches strings starting with any of
# the paths_to_be_protected.
paths_regex = Regexp.union(paths_to_be_protected.map { |path| /\A#{Regexp.escape(path)}/ })
rack_attack_enabled = Gitlab.config.rack_attack.git_basic_auth['enabled']
unless Rails.env.test?
unless Rails.env.test? || !rack_attack_enabled
Rack::Attack.throttle('protected paths', limit: 10, period: 60.seconds) do |req|
if req.post? && req.path =~ paths_regex
req.ip
......
unless Rails.env.test?
rack_attack_enabled = Gitlab.config.rack_attack.git_basic_auth['enabled']
unless Rails.env.test? || !rack_attack_enabled
# Tell the Rack::Attack Rack middleware to maintain an IP blacklist. We will
# update the blacklist from Grack::Auth#authenticate_user.
Rack::Attack.blacklist('Git HTTP Basic Auth') do |req|
......
......@@ -15,6 +15,9 @@ if Rails.env.production?
Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn
config.release = Gitlab::REVISION
# Sanitize fields based on those sanitized from Rails.
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
end
end
end
......@@ -22,7 +22,7 @@ else
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
expire_after: Settings.gitlab['session_expire_delay'] * 60,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
)
end
......@@ -214,8 +214,6 @@ Rails.application.routes.draw do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
member do
get :projects
get :keys
......@@ -225,12 +223,14 @@ Rails.application.routes.draw do
put :unblock
put :unlock
put :confirm
post 'impersonate' => 'impersonation#create'
post :impersonate
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
resource :impersonation, only: :destroy
resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
......@@ -710,6 +710,7 @@ Rails.application.routes.draw do
post :toggle_subscription
get :referenced_merge_requests
get :related_branches
get :can_create_branch
end
collection do
post :bulk_update
......
class AddTokenToWebHooks < ActiveRecord::Migration
def change
add_column :web_hooks, :token, :string
end
end
......@@ -1025,6 +1025,7 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
t.boolean "wiki_page_events", default: false, null: false
t.string "token"
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
......
......@@ -8,7 +8,7 @@ This is one of new trends in Continuous Integration/Deployment to:
1. create application image,
1. run test against created image,
1. push image to remote registry,
1. push image to remote registry,
1. deploy server from pushed image
It's also useful in case when your application already has the `Dockerfile` that can be used to create and test image:
......@@ -46,22 +46,22 @@ GitLab Runner then executes build scripts as `gitlab-runner` user.
For more information how to install Docker on different systems checkout the [Supported installations](https://docs.docker.com/installation/).
3. Add `gitlab-runner` user to `docker` group:
```bash
$ sudo usermod -aG docker gitlab-runner
```
4. Verify that `gitlab-runner` has access to Docker:
```bash
$ sudo -u gitlab-runner -H docker info
```
You can now verify that everything works by adding `docker info` to `.gitlab-ci.yml`:
```yaml
before_script:
- docker info
build_image:
script:
- docker build -t my-docker-image .
......@@ -75,37 +75,80 @@ For more information please checkout [On Docker security: `docker` group conside
## 2. Use docker-in-docker executor
Second approach is to use special Docker image with all tools installed (`docker` and `docker-compose`) and run build script in context of that image in privileged mode.
The second approach is to use the special Docker image with all tools installed
(`docker` and `docker-compose`) and run the build script in context of that
image in privileged mode.
In order to do that follow the steps:
1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
1. Register GitLab Runner from command line to use `docker` and `privileged` mode:
1. Register GitLab Runner from the command line to use `docker` and `privileged`
mode:
```bash
$ sudo gitlab-runner register -n \
sudo gitlab-runner register -n \
--url https://gitlab.com/ci \
--token RUNNER_TOKEN \
--executor docker \
--description "My Docker Runner" \
--docker-image "gitlab/dind:latest" \
--docker-image "docker:latest" \
--docker-privileged
```
The above command will register new Runner to use special [gitlab/dind](https://registry.hub.docker.com/u/gitlab/dind/) image which is provided by GitLab Inc.
The image at the start runs Docker daemon in [docker-in-docker](https://blog.docker.com/2013/09/docker-can-now-run-within-docker/) mode.
The above command will register a new Runner to use the special
`docker:latest` image which is provided by Docker. **Notice that it's using
the `privileged` mode to start the build and service containers.** If you
want to use [docker-in-docker] mode, you always have to use `privileged = true`
in your Docker containers.
The above command will create a `config.toml` entry similar to this:
```
[[runners]]
url = "https://gitlab.com/ci"
token = TOKEN
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:latest"
privileged = true
disable_cache = false
volumes = ["/cache"]
[runners.cache]
Insecure = false
```
If you want to use the Shared Runners available on your GitLab CE/EE
installation in order to build Docker images, then make sure that your
Shared Runners configuration has the `privileged` mode set to `true`.
1. You can now use `docker` from build script:
```yaml
image: docker:latest
services:
- docker:dind
before_script:
- docker info
build_image:
- docker info
build:
stage: build
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
```
1. However, by enabling `--docker-privileged` you are effectively disables all security mechanisms of containers and exposing your host to privilege escalation which can lead to container breakout.
For more information, check out [Runtime privilege](https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration).
\ No newline at end of file
1. However, by enabling `--docker-privileged` you are effectively disabling all
the security mechanisms of containers and exposing your host to privilege
escalation which can lead to container breakout.
For more information, check out the official Docker documentation on
[Runtime privilege and Linux capabilities][docker-cap].
An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker.
[docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
[docker-cap]: https://docs.docker.com/reference/run/#runtime-privilege-and-linux-capabilities
......@@ -9,7 +9,9 @@ GitHub will generate an application ID and secret key for you to use.
1. Navigate to your individual user settings or an organization's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
1. Select "Applications" in the left menu.
1. Select "OAuth applications" in the left menu.
1. If you already have applications listed, switch to the "Developer applications" tab.
1. Select "Register new application".
......@@ -60,12 +62,26 @@ GitHub will generate an application ID and secret key for you to use.
For installation from source:
For GitHub.com:
```
- { name: 'github', app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
args: { scope: 'user:email' } }
```
For GitHub Enterprise:
```
- { name: 'github', app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
url: "https://github.example.com/",
args: { scope: 'user:email' } }
```
__Replace `https://github.example.com/` with your GitHub URL.__
1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7.
1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
......
......@@ -37,4 +37,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
- [Grafana Install/Configuration](grafana_configuration.md
- [Grafana Install/Configuration](grafana_configuration.md)
......@@ -181,7 +181,7 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
- [Grafana Install/Configuration](grafana_configuration.md
- [Grafana Install/Configuration](grafana_configuration.md)
[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management
[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/
......
......@@ -85,4 +85,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [Grafana Install/Configuration](grafana_configuration.md
- [Grafana Install/Configuration](grafana_configuration.md)
......@@ -47,7 +47,7 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch
sudo -u git -H git checkout `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
sudo -u git -H make
```
......
......@@ -21,7 +21,6 @@ Feature: Project Deploy Keys
Scenario: I add new deploy key
Given I visit project deploy keys page
When I click 'New Deploy Key'
And I submit new deploy key
Then I should be on deploy keys page
And I should see newly created deploy key
......
......@@ -8,19 +8,19 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should see project deploy key' do
page.within '.enabled-keys' do
page.within '.deploy-keys' do
expect(page).to have_content deploy_key.title
end
end
step 'I should see other project deploy key' do
page.within '.available-keys' do
page.within '.deploy-keys' do
expect(page).to have_content other_deploy_key.title
end
end
step 'I should see public deploy key' do
page.within '.available-keys' do
page.within '.deploy-keys' do
expect(page).to have_content public_deploy_key.title
end
end
......@@ -32,7 +32,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
step 'I submit new deploy key' do
fill_in "deploy_key_title", with: "laptop"
fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
click_button "Create"
click_button "Add key"
end
step 'I should be on deploy keys page' do
......@@ -40,7 +40,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should see newly created deploy key' do
page.within '.enabled-keys' do
page.within '.deploy-keys' do
expect(page).to have_content(deploy_key.title)
end
end
......@@ -56,7 +56,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should only see the same deploy key once' do
page.within '.available-keys' do
page.within '.deploy-keys' do
expect(page).to have_selector('ul li', count: 1)
end
end
......@@ -66,7 +66,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I click attach deploy key' do
page.within '.available-keys' do
page.within '.deploy-keys' do
click_link 'Enable'
end
end
......
......@@ -48,12 +48,12 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
step 'I click test hook button' do
stub_request(:post, @hook.url).to_return(status: 200)
click_link 'Test Hook'
click_link 'Test'
end
step 'I click test hook button with invalid URL' do
stub_request(:post, @hook.url).to_raise(SocketError)
click_link 'Test Hook'
click_link 'Test'
end
step 'hook should be triggered' do
......
......@@ -24,8 +24,8 @@ module API
def create_spam_log(project, current_user, attrs)
params = attrs.merge({
source_ip: env['REMOTE_ADDR'],
user_agent: env['HTTP_USER_AGENT'],
source_ip: client_ip(env),
user_agent: user_agent(env),
noteable_type: 'Issue',
via_api: true
})
......
......@@ -105,7 +105,15 @@ module API
authorize! :read_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user
finder_params = {
project_id: user_project.id,
milestone_title: @milestone.title,
state: 'all'
}
issues = IssuesFinder.new(current_user, finder_params).execute
present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
......
......@@ -11,6 +11,11 @@ module API
end
not_found!
end
def snippets_for_current_user
finder_params = { filter: :by_project, project: user_project }
SnippetsFinder.new.execute(current_user, finder_params)
end
end
# Get a project snippets
......@@ -20,7 +25,7 @@ module API
# Example Request:
# GET /projects/:id/snippets
get ":id/snippets" do
present paginate(user_project.snippets), with: Entities::ProjectSnippet
present paginate(snippets_for_current_user), with: Entities::ProjectSnippet
end
# Get a project snippet
......@@ -31,7 +36,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id
get ":id/snippets/:snippet_id" do
@snippet = user_project.snippets.find(params[:snippet_id])
@snippet = snippets_for_current_user.find(params[:snippet_id])
present @snippet, with: Entities::ProjectSnippet
end
......@@ -73,7 +78,7 @@ module API
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
@snippet = user_project.snippets.find(params[:snippet_id])
@snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
attrs = attributes_for_keys [:title, :file_name, :visibility_level]
......@@ -97,7 +102,7 @@ module API
# DELETE /projects/:id/snippets/:snippet_id
delete ":id/snippets/:snippet_id" do
begin
@snippet = user_project.snippets.find(params[:snippet_id])
@snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
@snippet.destroy
rescue
......@@ -113,7 +118,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id/raw
get ":id/snippets/:snippet_id/raw" do
@snippet = user_project.snippets.find(params[:snippet_id])
@snippet = snippets_for_current_user.find(params[:snippet_id])
env['api.format'] = :txt
content_type 'text/plain'
......
......@@ -9,14 +9,22 @@ module Gitlab
Gitlab.config.gitlab.url)
end
def client_ip(env)
env['action_dispatch.remote_ip'].to_s
end
def user_agent(env)
env['HTTP_USER_AGENT']
end
def check_for_spam?(project, user)
akismet_enabled? && !project.team.member?(user)
end
def is_spam?(environment, user, text)
client = akismet_client
ip_address = environment['REMOTE_ADDR']
user_agent = environment['HTTP_USER_AGENT']
ip_address = client_ip(environment)
user_agent = user_agent(environment)
params = {
type: 'comment',
......
......@@ -7,12 +7,19 @@ module Gitlab
@client = ::OAuth2::Client.new(
config.app_id,
config.app_secret,
github_options
github_options.merge(ssl: { verify: config['verify_ssl'] })
)
if access_token
::Octokit.auto_paginate = true
@api = ::Octokit::Client.new(access_token: access_token)
@api = ::Octokit::Client.new(
access_token: access_token,
api_endpoint: github_options[:site],
connection_options: {
ssl: { verify: config['verify_ssl'] }
}
)
end
end
......@@ -42,11 +49,11 @@ module Gitlab
private
def config
Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"}
Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
end
def github_options
OmniAuth::Strategies::GitHub.default_options[:client_options].to_h.symbolize_keys
config["args"]["client_options"].deep_symbolize_keys
end
end
end
......
module Gitlab
class Highlight
def self.highlight(blob_name, blob_content, nowrap: true)
new(blob_name, blob_content, nowrap: nowrap).highlight(blob_content, continue: false)
def self.highlight(blob_name, blob_content, nowrap: true, plain: false)
new(blob_name, blob_content, nowrap: nowrap).
highlight(blob_content, continue: false, plain: plain)
end
def self.highlight_lines(repository, ref, file_name)
......@@ -17,8 +18,12 @@ module Gitlab
@lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText
end
def highlight(text, continue: true)
@formatter.format(@lexer.lex(text, continue: continue)).html_safe
def highlight(text, continue: true, plain: false)
if plain
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
else
@formatter.format(@lexer.lex(text, continue: continue)).html_safe
end
rescue
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end
......
......@@ -173,7 +173,7 @@ check_stale_pids(){
fi
fi
if [ "$hpid" != "0" ] && [ "$gitlab_workhorse_status" != "0" ]; then
echo "Removing stale gitlab-workhorse pid. This is most likely caused by gitlab-workhorse crashing the last time it ran."
echo "Removing stale GitLab Workhorse pid. This is most likely caused by GitLab Workhorse crashing the last time it ran."
if ! rm "$gitlab_workhorse_pid_path"; then
echo "Unable to remove stale pid, exiting"
exit 1
......@@ -208,7 +208,7 @@ start_gitlab() {
echo "Starting GitLab Sidekiq"
fi
if [ "$gitlab_workhorse_status" != "0" ]; then
echo "Starting gitlab-workhorse"
echo "Starting GitLab Workhorse"
fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then
echo "Starting GitLab MailRoom"
......@@ -232,7 +232,7 @@ start_gitlab() {
fi
if [ "$gitlab_workhorse_status" = "0" ]; then
echo "The gitlab-workhorse is already running with pid $spid, not restarting"
echo "The GitLab Workhorse is already running with pid $spid, not restarting"
else
# No need to remove a socket, gitlab-workhorse does this itself.
# Because gitlab-workhorse has multiple executables we need to fix
......@@ -271,7 +271,7 @@ stop_gitlab() {
RAILS_ENV=$RAILS_ENV bin/background_jobs stop
fi
if [ "$gitlab_workhorse_status" = "0" ]; then
echo "Shutting down gitlab-workhorse"
echo "Shutting down GitLab Workhorse"
kill -- $(cat $gitlab_workhorse_pid_path)
fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then
......@@ -320,9 +320,9 @@ print_status() {
printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n"
fi
if [ "$gitlab_workhorse_status" = "0" ]; then
echo "The gitlab-workhorse with pid $hpid is running."
echo "The GitLab Workhorse with pid $hpid is running."
else
printf "The gitlab-workhorse is \033[31mnot running\033[0m.\n"
printf "The GitLab Workhorse is \033[31mnot running\033[0m.\n"
fi
if [ "$mail_room_enabled" = true ]; then
if [ "$mail_room_status" = "0" ]; then
......
require 'spec_helper'
describe Admin::ImpersonationController do
let(:admin) { create(:admin) }
before do
sign_in(admin)
end
describe 'CREATE #impersonation when blocked' do
let(:blocked_user) { create(:user, state: :blocked) }
it 'does not allow impersonation' do
post :create, id: blocked_user.username
expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
end
end
end
require 'spec_helper'
describe Admin::ImpersonationsController do
let(:impersonator) { create(:admin) }
let(:user) { create(:user) }
describe "DELETE destroy" do
context "when not signed in" do
it "redirects to the sign in page" do
delete :destroy
expect(response).to redirect_to(new_user_session_path)
end
end
context "when signed in" do
before do
sign_in(user)
end
context "when not impersonating" do
it "responds with status 404" do
delete :destroy
expect(response.status).to eq(404)
end
it "doesn't sign us in" do
delete :destroy
expect(warden.user).to eq(user)
end
end
context "when impersonating" do
before do
session[:impersonator_id] = impersonator.id
end
context "when the impersonator is not admin (anymore)" do
before do
impersonator.admin = false
impersonator.save
end
it "responds with status 404" do
delete :destroy
expect(response.status).to eq(404)
end
it "doesn't sign us in as the impersonator" do
delete :destroy
expect(warden.user).to eq(user)
end
end
context "when the impersonator is admin" do
context "when the impersonator is blocked" do
before do
impersonator.block!
end
it "responds with status 404" do
delete :destroy
expect(response.status).to eq(404)
end
it "doesn't sign us in as the impersonator" do
delete :destroy
expect(warden.user).to eq(user)
end
end
context "when the impersonator is not blocked" do
it "redirects to the impersonated user's page" do
delete :destroy
expect(response).to redirect_to(admin_user_path(user))
end
it "signs us in as the impersonator" do
delete :destroy
expect(warden.user).to eq(impersonator)
end
end
end
end
end
end
end
......@@ -2,9 +2,10 @@ require 'spec_helper'
describe Admin::UsersController do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
before do
sign_in(create(:admin))
sign_in(admin)
end
describe 'DELETE #user with projects' do
......@@ -112,4 +113,50 @@ describe Admin::UsersController do
patch :disable_two_factor, id: user.to_param
end
end
describe "POST impersonate" do
context "when the user is blocked" do
before do
user.block!
end
it "shows a notice" do
post :impersonate, id: user.username
expect(flash[:alert]).to eq("You cannot impersonate a blocked user")
end
it "doesn't sign us in as the user" do
post :impersonate, id: user.username
expect(warden.user).to eq(admin)
end
end
context "when the user is not blocked" do
it "stores the impersonator in the session" do
post :impersonate, id: user.username
expect(session[:impersonator_id]).to eq(admin.id)
end
it "signs us in as the user" do
post :impersonate, id: user.username
expect(warden.user).to eq(user)
end
it "redirects to root" do
post :impersonate, id: user.username
expect(response).to redirect_to(root_path)
end
it "shows a notice" do
post :impersonate, id: user.username
expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
end
end
end
end
......@@ -22,6 +22,8 @@ describe Import::GithubController do
token = "asdasd12345"
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:get_token).and_return(token)
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:github_options).and_return({})
stub_omniauth_provider('github')
get :callback
......
......@@ -40,6 +40,45 @@ describe Projects::IssuesController do
end
end
describe 'PUT #update' do
context 'when moving issue to another private project' do
let(:another_project) { create(:project, :private) }
before do
sign_in(user)
project.team << [user, :developer]
end
context 'when user has access to move issue' do
before { another_project.team << [user, :reporter] }
it 'moves issue to another project' do
move_issue
expect(response).to have_http_status :found
expect(another_project.issues).to_not be_empty
end
end
context 'when user does not have access to move issue' do
it 'responds with 404' do
move_issue
expect(response).to have_http_status :not_found
end
end
def move_issue
put :update,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: issue.iid,
issue: { title: 'New title' },
move_to_project_id: another_project.id
end
end
end
describe 'Confidential Issues' do
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
......
FactoryGirl.define do
factory :project_hook do
url { FFaker::Internet.uri('http') }
trait :token do
token { SecureRandom.hex(10) }
end
end
end
require 'spec_helper'
describe 'Dashboard > label filter', feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) }
let(:label) { create(:label, title: 'bug', color: '#ff0000') }
let(:label2) { create(:label, title: 'bug') }
before do
project.labels << label
project2.labels << label2
login_as(user)
visit issues_dashboard_path
end
context 'duplicate labels' do
it 'should remove duplicate labels' do
page.within('.labels-filter') do
click_button 'Label'
end
page.within('.dropdown-menu-labels') do
expect(page).to have_selector('.dropdown-content a', text: 'bug', count: 1)
end
end
end
end
......@@ -11,10 +11,10 @@ feature 'Start new branch from an issue', feature: true do
login_as(user)
end
it 'shown the new branch button', js: false do
it 'shows the new branch button', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
expect(page).to have_link "New Branch"
expect(page).to have_css('#new-branch .available')
end
context "when there is a referenced merge request" do
......@@ -34,16 +34,17 @@ feature 'Start new branch from an issue', feature: true do
end
it "hides the new branch button", js: true do
expect(page).not_to have_link "New Branch"
expect(page).not_to have_css('#new-branch .available')
expect(page).to have_content /1 Related Merge Request/
end
end
end
context "for visiters" do
it 'no button is shown', js: false do
it 'no button is shown', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
expect(page).not_to have_link "New Branch"
expect(page).not_to have_css('#new-branch')
end
end
end
......@@ -30,4 +30,14 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
context 'when target project cannot be viewed by the current user' do
it 'does not leak the private project name & namespace' do
private_project = create(:project, :private)
visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
expect(page).not_to have_content private_project.to_reference
end
end
end
require 'rails_helper'
feature 'Milestone', feature: true do
include WaitForAjax
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project, title: 8.7) }
before do
project.team << [user, :master]
login_as(user)
end
feature 'Create a milestone' do
scenario 'should show an informative message for a new issue' do
visit new_namespace_project_milestone_path(project.namespace, project)
page.within '.milestone-form' do
fill_in "milestone_title", with: '8.7'
end
find('input[name="commit"]').click
expect(find('.alert-success')).to have_content('Assign some issues to this milestone.')
end
end
feature 'Open a milestone with closed issues' do
scenario 'should show an informative message' do
create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
visit namespace_project_milestone_path(project.namespace, project, milestone)
expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
end
end
end
require 'spec_helper'
feature 'Projects > Wiki > User creates wiki page', feature: true do
let(:user) { create(:user) }
background do
project.team << [user, :master]
login_as(user)
visit namespace_project_path(project.namespace, project)
click_link 'Wiki'
end
context 'in the user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
context 'when wiki is empty' do
scenario 'directly from the wiki home page' do
fill_in :wiki_content, with: 'My awesome wiki!'
click_button 'Create page'
expect(page).to have_content('Home')
expect(page).to have_content("last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
context 'when wiki is not empty' do
before do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
end
scenario 'via the "new wiki page" page', js: true do
click_link 'New Page'
fill_in :new_wiki_path, with: 'foo'
click_button 'Create Page'
fill_in :wiki_content, with: 'My awesome wiki!'
click_button 'Create page'
expect(page).to have_content('Foo')
expect(page).to have_content("last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
end
context 'in a group namespace' do
let(:project) { create(:project, namespace: create(:group, :public)) }
context 'when wiki is empty' do
scenario 'directly from the wiki home page' do
fill_in :wiki_content, with: 'My awesome wiki!'
click_button 'Create page'
expect(page).to have_content('Home')
expect(page).to have_content("last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
context 'when wiki is not empty' do
before do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
end
scenario 'via the "new wiki page" page', js: true do
click_link 'New Page'
fill_in :new_wiki_path, with: 'foo'
click_button 'Create Page'
fill_in :wiki_content, with: 'My awesome wiki!'
click_button 'Create page'
expect(page).to have_content('Foo')
expect(page).to have_content("last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
end
end
require 'spec_helper'
feature 'Projects > Wiki > User updates wiki page', feature: true do
let(:user) { create(:user) }
background do
project.team << [user, :master]
login_as(user)
visit namespace_project_path(project.namespace, project)
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
click_link 'Wiki'
end
context 'in the user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
scenario 'the home page' do
click_link 'Edit'
fill_in :wiki_content, with: 'My awesome wiki!'
click_button 'Save changes'
expect(page).to have_content('Home')
expect(page).to have_content("last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
context 'in a group namespace' do
let(:project) { create(:project, namespace: create(:group, :public)) }
scenario 'the home page' do
click_link 'Edit'
fill_in :wiki_content, with: 'My awesome wiki!'
click_button 'Save changes'
expect(page).to have_content('Home')
expect(page).to have_content("last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
end
end
......@@ -68,12 +68,12 @@ describe 'Dashboard Todos', feature: true do
describe 'completing last todo from last page', js: true do
it 'redirects to the previous page' do
visit dashboard_todos_path(page: 2)
expect(page).to have_content(Todo.first.body)
expect(page).to have_css("#todo_#{Todo.last.id}")
click_link('Done')
expect(current_path).to eq dashboard_todos_path
expect(page).to have_content(Todo.last.body)
expect(page).to have_css("#todo_#{Todo.first.id}")
end
end
end
......
......@@ -30,6 +30,18 @@ describe IssuesHelper do
expect(url_for_project_issues).to eq ""
end
it 'returns an empty string if project_url is invalid' do
expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
expect(url_for_project_issues(project)).to eq ''
end
it 'returns an empty string if project_path is invalid' do
expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
expect(url_for_project_issues(project, only_path: true)).to eq ''
end
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
......@@ -68,6 +80,18 @@ describe IssuesHelper do
expect(url_for_issue(issue.iid)).to eq ""
end
it 'returns an empty string if issue_url is invalid' do
expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' }
expect(url_for_issue(issue.iid, project)).to eq ''
end
it 'returns an empty string if issue_path is invalid' do
expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' }
expect(url_for_issue(issue.iid, project, only_path: true)).to eq ''
end
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
......@@ -105,6 +129,18 @@ describe IssuesHelper do
expect(url_for_new_issue).to eq ""
end
it 'returns an empty string if issue_url is invalid' do
expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
expect(url_for_new_issue(project)).to eq ''
end
it 'returns an empty string if issue_path is invalid' do
expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
expect(url_for_new_issue(project, only_path: true)).to eq ''
end
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
......
......@@ -24,7 +24,7 @@ describe Gitlab::AkismetHelper, type: :helper do
describe '#is_spam?' do
it 'returns true for spam' do
environment = {
'REMOTE_ADDR' => '127.0.0.1',
'action_dispatch.remote_ip' => '127.0.0.1',
'HTTP_USER_AGENT' => 'Test User Agent'
}
......
......@@ -2,15 +2,49 @@ require 'spec_helper'
describe Gitlab::GithubImport::Client, lib: true do
let(:token) { '123456' }
let(:client) { Gitlab::GithubImport::Client.new(token) }
let(:github_provider) { Settingslogic.new('app_id' => 'asd123', 'app_secret' => 'asd123', 'name' => 'github', 'args' => { 'client_options' => {} }) }
subject(:client) { described_class.new(token) }
before do
Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github")
allow(Gitlab.config.omniauth).to receive(:providers).and_return([github_provider])
end
it 'all OAuth2 client options are symbols' do
it 'convert OAuth2 client options to symbols' do
client.client.options.keys.each do |key|
expect(key).to be_kind_of(Symbol)
end
end
it 'does not crash (e.g. Settingslogic::MissingSetting) when verify_ssl config is not present' do
expect { client.api }.not_to raise_error
end
context 'allow SSL verification to be configurable on API' do
before do
github_provider['verify_ssl'] = false
end
it 'uses supplied value' do
expect(client.client.options[:connection_opts][:ssl]).to eq({ verify: false })
expect(client.api.connection_options[:ssl]).to eq({ verify: false })
end
end
context 'when provider does not specity an API endpoint' do
it 'uses GitHub root API endpoint' do
expect(client.api.api_endpoint).to eq 'https://api.github.com/'
end
end
context 'when provider specify a custom API endpoint' do
before do
github_provider['args']['client_options']['site'] = 'https://github.company.com/'
end
it 'uses the custom API endpoint' do
expect(OmniAuth::Strategies::GitHub).not_to receive(:default_options)
expect(client.api.api_endpoint).to eq 'https://github.company.com/'
end
end
end
......@@ -158,97 +158,123 @@ describe Ci::Commit, models: true do
stub_ci_commit_yaml_file(YAML.dump(yaml))
end
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
commit.reload
expect(commit.status).to eq('success')
context 'when builds are successful' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
commit.reload
expect(commit.status).to eq('success')
end
end
it 'properly creates builds when test fails' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
context 'when test job fails' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
commit.reload
expect(commit.status).to eq('failed')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
commit.reload
expect(commit.status).to eq('failed')
end
end
it 'properly creates builds when test and test_failure fails' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
context 'when test and test_failure jobs fail' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
commit.reload
expect(commit.status).to eq('failed')
end
end
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:drop)
context 'when deploy job fails' do
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
commit.reload
expect(commit.status).to eq('failed')
end
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
it 'properly creates builds when deploy fails' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
commit.reload
expect(commit.status).to eq('failed')
end
end
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:success)
context 'when build is canceled in the second stage' do
it 'does not schedule builds after build has been canceled' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.running_or_pending).to_not be_empty
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:cancel)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
commit.reload
expect(commit.status).to eq('failed')
expect(commit.builds.running_or_pending).to be_empty
expect(commit.reload.status).to eq('canceled')
end
end
end
end
......
......@@ -61,9 +61,35 @@ describe Statuseable do
let(:statuses) do
[create(type, status: :success), create(type, status: :canceled)]
end
it { is_expected.to eq 'canceled' }
end
context 'one failed and one canceled' do
let(:statuses) do
[create(type, status: :failed), create(type, status: :canceled)]
end
it { is_expected.to eq 'failed' }
end
context 'one failed but allowed to fail and one canceled' do
let(:statuses) do
[create(type, status: :failed, allow_failure: true),
create(type, status: :canceled)]
end
it { is_expected.to eq 'canceled' }
end
context 'one running one canceled' do
let(:statuses) do
[create(type, status: :running), create(type, status: :canceled)]
end
it { is_expected.to eq 'running' }
end
context 'all canceled' do
let(:statuses) do
[create(type, status: :canceled), create(type, status: :canceled)]
......
......@@ -43,51 +43,65 @@ describe WebHook, models: true do
end
describe "execute" do
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook) }
before(:each) do
@project_hook = create(:project_hook)
@project = create(:project)
@project.hooks << [@project_hook]
project.hooks << [project_hook]
@data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
WebMock.stub_request(:post, @project_hook.url)
WebMock.stub_request(:post, project_hook.url)
end
context 'when token is defined' do
let(:project_hook) { create(:project_hook, :token) }
it 'POSTs to the webhook URL' do
project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, project_hook.url).with(
headers: { 'Content-Type' => 'application/json',
'X-Gitlab-Event' => 'Push Hook',
'X-Gitlab-Token' => project_hook.token }
).once
end
end
it "POSTs to the webhook URL" do
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, project_hook.url).with(
headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
).once
end
it "POSTs the data as JSON" do
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, project_hook.url).with(
headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
).once
end
it "catches exceptions" do
expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
end
it "handles SSL exceptions" do
expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
end
it "handles 200 status code" do
WebMock.stub_request(:post, @project_hook.url).to_return(status: 200, body: "Success")
WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success")
expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
end
it "handles 2xx status codes" do
WebMock.stub_request(:post, @project_hook.url).to_return(status: 201, body: "Success")
WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success")
expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
end
end
end
......@@ -27,86 +27,51 @@ describe BambooService, models: true do
end
describe 'Validations' do
describe '#bamboo_url' do
it 'does not validate the presence of bamboo_url if service is not active' do
bamboo_service = service
bamboo_service.active = false
expect(bamboo_service).not_to validate_presence_of(:bamboo_url)
end
it 'validates the presence of bamboo_url if service is active' do
bamboo_service = service
bamboo_service.active = true
expect(bamboo_service).to validate_presence_of(:bamboo_url)
end
end
subject { service }
describe '#build_key' do
it 'does not validate the presence of build_key if service is not active' do
bamboo_service = service
bamboo_service.active = false
context 'when service is active' do
before { subject.active = true }
expect(bamboo_service).not_to validate_presence_of(:build_key)
end
it { is_expected.to validate_presence_of(:build_key) }
it { is_expected.to validate_presence_of(:bamboo_url) }
it_behaves_like 'issue tracker service URL attribute', :bamboo_url
it 'validates the presence of build_key if service is active' do
bamboo_service = service
bamboo_service.active = true
describe '#username' do
it 'does not validate the presence of username if password is nil' do
subject.password = nil
expect(bamboo_service).to validate_presence_of(:build_key)
end
end
expect(subject).not_to validate_presence_of(:username)
end
describe '#username' do
it 'does not validate the presence of username if service is not active' do
bamboo_service = service
bamboo_service.active = false
it 'validates the presence of username if password is present' do
subject.password = 'secret'
expect(bamboo_service).not_to validate_presence_of(:username)
expect(subject).to validate_presence_of(:username)
end
end
it 'does not validate the presence of username if username is nil' do
bamboo_service = service
bamboo_service.active = true
bamboo_service.password = nil
describe '#password' do
it 'does not validate the presence of password if username is nil' do
subject.username = nil
expect(bamboo_service).not_to validate_presence_of(:username)
end
expect(subject).not_to validate_presence_of(:password)
end
it 'validates the presence of username if service is active and username is present' do
bamboo_service = service
bamboo_service.active = true
bamboo_service.password = 'secret'
it 'validates the presence of password if username is present' do
subject.username = 'john'
expect(bamboo_service).to validate_presence_of(:username)
expect(subject).to validate_presence_of(:password)
end
end
end
describe '#password' do
it 'does not validate the presence of password if service is not active' do
bamboo_service = service
bamboo_service.active = false
expect(bamboo_service).not_to validate_presence_of(:password)
end
it 'does not validate the presence of password if username is nil' do
bamboo_service = service
bamboo_service.active = true
bamboo_service.username = nil
expect(bamboo_service).not_to validate_presence_of(:password)
end
it 'validates the presence of password if service is active and username is present' do
bamboo_service = service
bamboo_service.active = true
bamboo_service.username = 'john'
context 'when service is inactive' do
before { subject.active = false }
expect(bamboo_service).to validate_presence_of(:password)
end
it { is_expected.not_to validate_presence_of(:build_key) }
it { is_expected.not_to validate_presence_of(:bamboo_url) }
it { is_expected.not_to validate_presence_of(:username) }
it { is_expected.not_to validate_presence_of(:password) }
end
end
......
......@@ -26,6 +26,23 @@ describe BuildkiteService, models: true do
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:project_url) }
it { is_expected.to validate_presence_of(:token) }
it_behaves_like 'issue tracker service URL attribute', :project_url
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:project_url) }
it { is_expected.not_to validate_presence_of(:token) }
end
end
describe 'commits methods' do
before do
@project = Project.new
......
require 'spec_helper'
describe BuildsEmailService do
let(:build) { create(:ci_build) }
let(:data) { Gitlab::BuildDataBuilder.build(build) }
let!(:project) { create(:project, :public, ci_id: 1) }
let(:service) { described_class.new(project: project, active: true) }
let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) }
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:recipients) }
context 'when pusher is added' do
before { subject.add_pusher = true }
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
describe '#execute' do
it 'sends email' do
service.recipients = 'test@gitlab.com'
subject.recipients = 'test@gitlab.com'
data[:build_status] = 'failed'
expect(BuildEmailWorker).to receive(:perform_async)
service.execute(data)
subject.execute(data)
end
it 'does not send email with succeeded build and notify_only_broken_builds on' do
expect(service).to receive(:notify_only_broken_builds).and_return(true)
expect(subject).to receive(:notify_only_broken_builds).and_return(true)
data[:build_status] = 'success'
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
subject.execute(data)
end
it 'does not send email with failed build and build_allow_failure is true' do
data[:build_status] = 'failed'
data[:build_allow_failure] = true
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
subject.execute(data)
end
it 'does not send email with unknown build status' do
data[:build_status] = 'foo'
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
end
it 'does not send email when recipients list is empty' do
service.recipients = ' ,, '
data[:build_status] = 'failed'
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
end
end
describe 'validations' do
context 'when pusher is not added' do
before { service.add_pusher = false }
it 'does not allow empty recipient input' do
service.recipients = ''
expect(service.valid?).to be false
end
it 'does allow non-empty recipient input' do
service.recipients = 'test@example.com'
expect(service.valid?).to be true
end
subject.execute(data)
end
context 'when pusher is added' do
before { service.add_pusher = true }
it 'does not send email when recipients list is empty' do
subject.recipients = ' ,, '
data[:build_status] = 'failed'
it 'does allow empty recipient input' do
service.recipients = ''
expect(service.valid?).to be true
end
expect(BuildEmailWorker).not_to receive(:perform_async)
it 'does allow non-empty recipient input' do
service.recipients = 'test@example.com'
expect(service.valid?).to be true
end
subject.execute(data)
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe CampfireService, models: true do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe CustomIssueTrackerService, models: true do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:project_url) }
it { is_expected.to validate_presence_of(:issues_url) }
it { is_expected.to validate_presence_of(:new_issue_url) }
it_behaves_like 'issue tracker service URL attribute', :project_url
it_behaves_like 'issue tracker service URL attribute', :issues_url
it_behaves_like 'issue tracker service URL attribute', :new_issue_url
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:project_url) }
it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
end
end
......@@ -28,25 +28,18 @@ describe DroneCiService, models: true do
describe 'validations' do
context 'active' do
before { allow(subject).to receive(:activated?).and_return(true) }
before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:drone_url) }
it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
it_behaves_like 'issue tracker service URL attribute', :drone_url
end
context 'inactive' do
before { allow(subject).to receive(:activated?).and_return(false) }
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:drone_url) }
it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
end
end
......
require 'spec_helper'
describe EmailsOnPushService do
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:recipients) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
end
......@@ -28,13 +28,18 @@ describe ExternalWikiService, models: true do
it { should have_one :service_hook }
end
describe "Validations" do
context "active" do
before do
subject.active = true
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:external_wiki_url) }
it_behaves_like 'issue tracker service URL attribute', :external_wiki_url
end
context 'when service is inactive' do
before { subject.active = false }
it { should validate_presence_of :external_wiki_url }
it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end
end
......
......@@ -26,6 +26,20 @@ describe FlowdockService, models: true do
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
end
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
......
......@@ -26,6 +26,22 @@ describe GemnasiumService, models: true do
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:api_key) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:api_key) }
end
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
......
......@@ -26,6 +26,20 @@ describe GitlabIssueTrackerService, models: true do
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
subject { described_class.new(project: create(:project), active: true) }
it { is_expected.to validate_presence_of(:issues_url) }
it_behaves_like 'issue tracker service URL attribute', :issues_url
end
context 'when service is inactive' do
subject { described_class.new(project: create(:project), active: false) }
it { is_expected.not_to validate_presence_of(:issues_url) }
end
end
describe 'project and issue urls' do
let(:project) { create(:project) }
......
......@@ -26,6 +26,20 @@ describe HipchatService, models: true do
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
end
end
describe "Execute" do
let(:hipchat) { HipchatService.new }
let(:user) { create(:user, username: 'username') }
......
......@@ -29,14 +29,16 @@ describe IrkerService, models: true do
end
describe 'Validations' do
before do
subject.active = true
subject.properties['recipients'] = _recipients
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:recipients) }
end
context 'active' do
let(:_recipients) { nil }
it { should validate_presence_of :recipients }
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
......
......@@ -26,6 +26,30 @@ describe JiraService, models: true do
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:api_url) }
it { is_expected.to validate_presence_of(:project_url) }
it { is_expected.to validate_presence_of(:issues_url) }
it { is_expected.to validate_presence_of(:new_issue_url) }
it_behaves_like 'issue tracker service URL attribute', :api_url
it_behaves_like 'issue tracker service URL attribute', :project_url
it_behaves_like 'issue tracker service URL attribute', :issues_url
it_behaves_like 'issue tracker service URL attribute', :new_issue_url
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:api_url) }
it { is_expected.not_to validate_presence_of(:project_url) }
it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
......@@ -72,7 +96,7 @@ describe JiraService, models: true do
context "when a password was previously set" do
before do
@jira_service = JiraService.create(
@jira_service = JiraService.create!(
project: create(:project),
properties: {
api_url: 'http://jira.example.com/rest/api/2',
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe PivotaltrackerService, models: true do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
end
end
end
......@@ -27,14 +27,20 @@ describe PushoverService, models: true do
end
describe 'Validations' do
context 'active' do
before do
subject.active = true
end
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of :api_key }
it { is_expected.to validate_presence_of :user_key }
it { is_expected.to validate_presence_of :priority }
it { is_expected.to validate_presence_of(:api_key) }
it { is_expected.to validate_presence_of(:user_key) }
it { is_expected.to validate_presence_of(:priority) }
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:api_key) }
it { is_expected.not_to validate_presence_of(:user_key) }
it { is_expected.not_to validate_presence_of(:priority) }
end
end
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe RedmineService, models: true do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of(:project_url) }
it { is_expected.to validate_presence_of(:issues_url) }
it { is_expected.to validate_presence_of(:new_issue_url) }
it_behaves_like 'issue tracker service URL attribute', :project_url
it_behaves_like 'issue tracker service URL attribute', :issues_url
it_behaves_like 'issue tracker service URL attribute', :new_issue_url
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:project_url) }
it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
end
end
......@@ -26,13 +26,18 @@ describe SlackService, models: true do
it { is_expected.to have_one :service_hook }
end
describe "Validations" do
context "active" do
before do
subject.active = true
end
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
it { is_expected.to validate_presence_of :webhook }
it { is_expected.to validate_presence_of(:webhook) }
it_behaves_like 'issue tracker service URL attribute', :webhook
end
context 'when service is inactive' do
before { subject.active = false }
it { is_expected.not_to validate_presence_of(:webhook) }
end
end
......
......@@ -27,86 +27,51 @@ describe TeamcityService, models: true do
end
describe 'Validations' do
describe '#teamcity_url' do
it 'does not validate the presence of teamcity_url if service is not active' do
teamcity_service = service
teamcity_service.active = false
expect(teamcity_service).not_to validate_presence_of(:teamcity_url)
end
it 'validates the presence of teamcity_url if service is active' do
teamcity_service = service
teamcity_service.active = true
expect(teamcity_service).to validate_presence_of(:teamcity_url)
end
end
subject { service }
describe '#build_type' do
it 'does not validate the presence of build_type if service is not active' do
teamcity_service = service
teamcity_service.active = false
context 'when service is active' do
before { subject.active = true }
expect(teamcity_service).not_to validate_presence_of(:build_type)
end
it { is_expected.to validate_presence_of(:build_type) }
it { is_expected.to validate_presence_of(:teamcity_url) }
it_behaves_like 'issue tracker service URL attribute', :teamcity_url
it 'validates the presence of build_type if service is active' do
teamcity_service = service
teamcity_service.active = true
describe '#username' do
it 'does not validate the presence of username if password is nil' do
subject.password = nil
expect(teamcity_service).to validate_presence_of(:build_type)
end
end
expect(subject).not_to validate_presence_of(:username)
end
describe '#username' do
it 'does not validate the presence of username if service is not active' do
teamcity_service = service
teamcity_service.active = false
it 'validates the presence of username if password is present' do
subject.password = 'secret'
expect(teamcity_service).not_to validate_presence_of(:username)
expect(subject).to validate_presence_of(:username)
end
end
it 'does not validate the presence of username if username is nil' do
teamcity_service = service
teamcity_service.active = true
teamcity_service.password = nil
describe '#password' do
it 'does not validate the presence of password if username is nil' do
subject.username = nil
expect(teamcity_service).not_to validate_presence_of(:username)
end
expect(subject).not_to validate_presence_of(:password)
end
it 'validates the presence of username if service is active and username is present' do
teamcity_service = service
teamcity_service.active = true
teamcity_service.password = 'secret'
it 'validates the presence of password if username is present' do
subject.username = 'john'
expect(teamcity_service).to validate_presence_of(:username)
expect(subject).to validate_presence_of(:password)
end
end
end
describe '#password' do
it 'does not validate the presence of password if service is not active' do
teamcity_service = service
teamcity_service.active = false
expect(teamcity_service).not_to validate_presence_of(:password)
end
it 'does not validate the presence of password if username is nil' do
teamcity_service = service
teamcity_service.active = true
teamcity_service.username = nil
expect(teamcity_service).not_to validate_presence_of(:password)
end
it 'validates the presence of password if service is active and username is present' do
teamcity_service = service
teamcity_service.active = true
teamcity_service.username = 'john'
context 'when service is inactive' do
before { subject.active = false }
expect(teamcity_service).to validate_presence_of(:password)
end
it { is_expected.not_to validate_presence_of(:build_type) }
it { is_expected.not_to validate_presence_of(:teamcity_url) }
it { is_expected.not_to validate_presence_of(:username) }
it { is_expected.not_to validate_presence_of(:password) }
end
end
......
......@@ -798,4 +798,18 @@ describe Project, models: true do
end
end
end
describe '#protected_branch?' do
let(:project) { create(:empty_project) }
it 'returns true when a branch is a protected branch' do
project.protected_branches.create!(name: 'foo')
expect(project.protected_branch?('foo')).to eq(true)
end
it 'returns false when a branch is not a protected branch' do
expect(project.protected_branch?('foo')).to eq(false)
end
end
end
......@@ -795,6 +795,16 @@ describe Repository, models: true do
end
describe "#copy_gitattributes" do
it 'returns true with a valid ref' do
expect(repository.copy_gitattributes('master')).to be_truthy
end
it 'returns false with an invalid ref' do
expect(repository.copy_gitattributes('invalid')).to be_falsey
end
end
describe "#main_language" do
it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby")
......
......@@ -127,7 +127,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/milestones/:milestone_id/issues' do
before do
milestone.issues << create(:issue)
milestone.issues << create(:issue, project: project)
end
it 'should return project issues for a particular milestone' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
......@@ -140,5 +140,34 @@ describe API::API, api: true do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
expect(response.status).to eq(401)
end
describe 'confidential issues' do
let(:public_project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: public_project) }
let(:issue) { create(:issue, project: public_project) }
let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
before do
public_project.team << [user, :developer]
milestone.issues << issue << confidential_issue
end
it 'returns confidential issues to team members' do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
end
it 'does not return confidential issues to regular users' do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
end
end
end
end
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, author: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
let!(:snippet) { create(:project_snippet, project: project, author: user) }
......@@ -45,7 +45,7 @@ describe API::API, api: true do
end
it "should return a 404 error when issue id not found" do
get api("/projects/#{project.id}/issues/123/notes", user)
get api("/projects/#{project.id}/issues/12345/notes", user)
expect(response.status).to eq(404)
end
......@@ -106,7 +106,7 @@ describe API::API, api: true do
end
it "should return a 404 error if issue note not found" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
......@@ -134,7 +134,7 @@ describe API::API, api: true do
end
it "should return a 404 error if snippet note not found" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
expect(response.status).to eq(404)
end
end
......@@ -191,6 +191,27 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
context 'when user does not have access to create noteable' do
let(:private_issue) { create(:issue, project: create(:project, :private)) }
##
# We are posting to project user has access to, but we use issue id
# from a different project, see #15577
#
before do
post api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user),
body: 'Hi!'
end
it 'responds with 500' do
expect(response.status).to eq 500
end
it 'does not create new note' do
expect(private_issue.notes.reload).to be_empty
end
end
end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
......@@ -211,7 +232,7 @@ describe API::API, api: true do
end
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
body: 'Hello!'
expect(response.status).to eq(404)
end
......@@ -233,7 +254,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/123", user), body: "Hello!"
"notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
......@@ -248,7 +269,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
"notes/123", user), body: "Hello!"
"notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
......@@ -268,7 +289,7 @@ describe API::API, api: true do
end
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
......@@ -288,7 +309,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/123", user)
"notes/12345", user)
expect(response.status).to eq(404)
end
......@@ -308,7 +329,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/merge_requests/"\
"#{merge_request.id}/notes/123", user)
"#{merge_request.id}/notes/12345", user)
expect(response.status).to eq(404)
end
......
......@@ -15,4 +15,91 @@ describe API::API, api: true do
expect(json_response['expires_at']).to be_nil
end
end
describe 'GET /projects/:project_id/snippets/' do
it 'all snippets available to team member' do
project = create(:project, :public)
user = create(:user)
project.team << [user, :developer]
public_snippet = create(:project_snippet, :public, project: project)
internal_snippet = create(:project_snippet, :internal, project: project)
private_snippet = create(:project_snippet, :private, project: project)
get api("/projects/#{project.id}/snippets/", user)
expect(response.status).to eq(200)
expect(json_response.size).to eq(3)
expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
end
it 'hides private snippets from regular user' do
project = create(:project, :public)
user = create(:user)
create(:project_snippet, :private, project: project)
get api("/projects/#{project.id}/snippets/", user)
expect(response.status).to eq(200)
expect(json_response.size).to eq(0)
end
end
describe 'POST /projects/:project_id/snippets/' do
it 'creates a new snippet' do
admin = create(:admin)
project = create(:project)
params = {
title: 'Test Title',
file_name: 'test.rb',
code: 'puts "hello world"',
visibility_level: Gitlab::VisibilityLevel::PUBLIC
}
post api("/projects/#{project.id}/snippets/", admin), params
expect(response.status).to eq(201)
snippet = ProjectSnippet.find(json_response['id'])
expect(snippet.content).to eq(params[:code])
expect(snippet.title).to eq(params[:title])
expect(snippet.file_name).to eq(params[:file_name])
expect(snippet.visibility_level).to eq(params[:visibility_level])
end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
it 'updates snippet' do
admin = create(:admin)
snippet = create(:project_snippet, author: admin)
new_content = 'New content'
put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
expect(response.status).to eq(200)
snippet.reload
expect(snippet.content).to eq(new_content)
end
end
describe 'DELETE /projects/:project_id/snippets/:id/' do
it 'deletes snippet' do
admin = create(:admin)
snippet = create(:project_snippet, author: admin)
delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
expect(response.status).to eq(200)
end
end
describe 'GET /projects/:project_id/snippets/:id/raw' do
it 'returns raw text' do
admin = create(:admin)
snippet = create(:project_snippet, author: admin)
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
expect(response.status).to eq(200)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to eq(snippet.content)
end
end
end
......@@ -11,7 +11,7 @@ describe API::API, api: true do
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :master, user: user, project: project) }
let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
......
......@@ -201,6 +201,36 @@ describe GitPushService, services: true do
end
describe "Updates git attributes" do
context "for default branch" do
it "calls the copy attributes method for the first push to the default branch" do
expect(project.repository).to receive(:copy_gitattributes).with('master')
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master')
end
it "calls the copy attributes method for changes to the default branch" do
expect(project.repository).to receive(:copy_gitattributes).with('refs/heads/master')
execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master')
end
end
context "for non-default branch" do
before do
# Make sure the "default" branch is different
allow(project).to receive(:default_branch).and_return('not-master')
end
it "does not call copy attributes method" do
expect(project.repository).not_to receive(:copy_gitattributes)
execute_service(project, user, @oldrev, @newrev, @ref)
end
end
end
describe "Webhooks" do
context "execute webhooks" do
it "when pushing a branch for the first time" do
......
......@@ -10,7 +10,7 @@ describe NotificationService, services: true do
end
describe 'Keys' do
describe :new_key do
describe '#new_key' do
let!(:key) { create(:personal_key) }
it { expect(notification.new_key(key)).to be_truthy }
......@@ -22,7 +22,7 @@ describe NotificationService, services: true do
end
describe 'Email' do
describe :new_email do
describe '#new_email' do
let!(:email) { create(:email) }
it { expect(notification.new_email(email)).to be_truthy }
......@@ -147,8 +147,8 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
describe :new_note do
it do
describe '#new_note' do
it 'notifies the team members' do
notification.new_note(note)
# Notify all team members
......@@ -177,6 +177,39 @@ describe NotificationService, services: true do
end
end
context 'project snippet note' do
let(:project) { create(:empty_project, :public) }
let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') }
before do
build_team(note.project)
note.project.team << [note.author, :master]
ActionMailer::Base.deliveries.clear
end
describe '#new_note' do
it 'notifies the team members' do
notification.new_note(note)
# Notify all team members
note.project.team.members.each do |member|
# User with disabled notification should not be notified
next if member.id == @u_disabled.id
# Author should not be notified
next if member.id == note.author.id
should_email(member)
end
should_email(note.noteable.author)
should_not_email(note.author)
should_email(@u_mentioned)
should_not_email(@u_disabled)
should_email(@u_not_mentioned)
end
end
end
context 'commit note' do
let(:project) { create(:project, :public) }
let(:note) { create(:note_on_commit, project: project) }
......@@ -187,7 +220,7 @@ describe NotificationService, services: true do
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
end
describe :new_note, :perform_enqueued_jobs do
describe '#new_note, #perform_enqueued_jobs' do
it do
notification.new_note(note)
......@@ -230,7 +263,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
describe :new_issue do
describe '#new_issue' do
it do
notification.new_issue(issue, @u_disabled)
......@@ -289,7 +322,7 @@ describe NotificationService, services: true do
end
end
describe :reassigned_issue do
describe '#reassigned_issue' do
it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled)
......@@ -419,7 +452,7 @@ describe NotificationService, services: true do
end
end
describe :close_issue do
describe '#close_issue' do
it 'should sent email to issue assignee and issue author' do
notification.close_issue(issue, @u_disabled)
......@@ -435,7 +468,7 @@ describe NotificationService, services: true do
end
end
describe :reopen_issue do
describe '#reopen_issue' do
it 'should send email to issue assignee and issue author' do
notification.reopen_issue(issue, @u_disabled)
......@@ -461,7 +494,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
describe :new_merge_request do
describe '#new_merge_request' do
it do
notification.new_merge_request(merge_request, @u_disabled)
......@@ -483,7 +516,7 @@ describe NotificationService, services: true do
end
end
describe :reassigned_merge_request do
describe '#reassigned_merge_request' do
it do
notification.reassigned_merge_request(merge_request, merge_request.author)
......@@ -498,7 +531,7 @@ describe NotificationService, services: true do
end
end
describe :relabel_merge_request do
describe '#relabel_merge_request' do
let(:label) { create(:label, merge_requests: [merge_request]) }
let(:label2) { create(:label) }
let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
......@@ -527,7 +560,7 @@ describe NotificationService, services: true do
end
end
describe :closed_merge_request do
describe '#closed_merge_request' do
it do
notification.close_mr(merge_request, @u_disabled)
......@@ -542,7 +575,7 @@ describe NotificationService, services: true do
end
end
describe :merged_merge_request do
describe '#merged_merge_request' do
it do
notification.merge_mr(merge_request, @u_disabled)
......@@ -557,7 +590,7 @@ describe NotificationService, services: true do
end
end
describe :reopen_merge_request do
describe '#reopen_merge_request' do
it do
notification.reopen_mr(merge_request, @u_disabled)
......@@ -581,7 +614,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
describe :project_was_moved do
describe '#project_was_moved' do
it do
notification.project_was_moved(project, "gitlab/gitlab")
......
......@@ -112,9 +112,16 @@ describe Projects::ImportService, services: true do
def stub_github_omniauth_provider
provider = OpenStruct.new(
name: 'github',
app_id: 'asd123',
app_secret: 'asd123'
'name' => 'github',
'app_id' => 'asd123',
'app_secret' => 'asd123',
'args' => {
'client_options' => {
'site' => 'https://github.com/api/v3',
'authorize_url' => 'https://github.com/login/oauth/authorize',
'token_url' => 'https://github.com/login/oauth/access_token'
}
}
)
Gitlab.config.omniauth.providers << provider
......
RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
it { is_expected.to allow_value('https://example.com').for(url_attr) }
it { is_expected.not_to allow_value('example.com').for(url_attr) }
it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) }
it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) }
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