mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-26 04:56:16 +10:00
Merge branch 'master' into group-navigation-redesign
This commit is contained in:
+2
-2
@@ -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.
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
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)
|
||||
|
||||
+6
-6
@@ -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'
|
||||
|
||||
+5
-5
@@ -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)
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
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";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.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
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
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)
|
||||
"[](#{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
|
||||
|
||||
+9
-10
@@ -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
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
= render 'note_message'
|
||||
@@ -0,0 +1,8 @@
|
||||
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'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%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
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
- 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
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
%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)}
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
%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"
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
- 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
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
- 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
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
%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')
|
||||
@@ -1,88 +1,84 @@
|
||||
- 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.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
- 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
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
- 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
|
||||
= 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
|
||||
= 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
|
||||
·
|
||||
#{time_ago_with_tooltip(commit.committed_date)}
|
||||
- else
|
||||
(branch was removed from repository)
|
||||
%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"
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
- 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"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
%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"
|
||||
|
||||
@@ -1,71 +1,70 @@
|
||||
- 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'
|
||||
|
||||
%hr.clearfix
|
||||
|
||||
-if @triggers.any?
|
||||
%h3
|
||||
Use CURL
|
||||
|
||||
%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 token above and set your branch or tag name. This is the reference that will be rebuild.
|
||||
|
||||
|
||||
%pre
|
||||
:plain
|
||||
curl -X POST \
|
||||
-F token=TOKEN \
|
||||
-F ref=REF_NAME \
|
||||
#{builds_trigger_url(@project.id)}
|
||||
%h3
|
||||
Use .gitlab-ci.yml
|
||||
%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 snippet to
|
||||
%i .gitlab-ci.yml
|
||||
of dependent project.
|
||||
At the end of your build it will trigger this project to rebuilt.
|
||||
%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
|
||||
%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
|
||||
|
||||
%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
|
||||
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.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
.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
|
||||
);
|
||||
|
||||
+24
-1
@@ -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
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
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.
|
||||
|
||||
1. You can now use `docker` from build script:
|
||||
|
||||
```yaml
|
||||
before_script:
|
||||
- docker info
|
||||
|
||||
build_image:
|
||||
script:
|
||||
- docker build -t my-docker-image .
|
||||
- docker run my-docker-image /script/to/run/tests
|
||||
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
|
||||
```
|
||||
|
||||
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).
|
||||
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:
|
||||
stage: build
|
||||
script:
|
||||
- docker build -t my-docker-image .
|
||||
- docker run my-docker-image /script/to/run/tests
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user