Commit 26b61225 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/import-url-validator

parents 4273e07e 6f6c6f68
...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Set import_url validation to be more strict - Set import_url validation to be more strict
- Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined - Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline - Fix pipeline status when there are no builds in pipeline
- Fix Error 500 when using closes_issues API with an external issue tracker - Fix Error 500 when using closes_issues API with an external issue tracker
...@@ -9,17 +10,22 @@ v 8.9.0 (unreleased) ...@@ -9,17 +10,22 @@ v 8.9.0 (unreleased)
- Bulk assign/unassign labels to issues. - Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Show Star and Fork buttons on mobile. - Show Star and Fork buttons on mobile.
- Performance improvements on RelativeLinkFilter
- Fix endless redirections when accessing user OAuth applications when they are disabled - Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI - Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0 - Bump rouge to 1.11.0
- Fix MR-auto-close text added to description
- Fix issue with arrow keys not working in search autocomplete dropdown - Fix issue with arrow keys not working in search autocomplete dropdown
- Fix an issue where note polling stopped working if a window was in the - Fix an issue where note polling stopped working if a window was in the
background during a refresh. background during a refresh.
- Pre-processing Markdown now only happens when needed
- Make EmailsOnPushWorker use Sidekiq mailers queue - Make EmailsOnPushWorker use Sidekiq mailers queue
- Redesign all Devise emails. !4297 - Redesign all Devise emails. !4297
- Don't show 'Leave Project' to group members - Don't show 'Leave Project' to group members
- Fix wiki page events' webhook to point to the wiki repository - Fix wiki page events' webhook to point to the wiki repository
- Add a border around images to differentiate them from the background.
- Don't show tags for revert and cherry-pick operations - Don't show tags for revert and cherry-pick operations
- Show image ID on registry page
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up - Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
...@@ -32,6 +38,7 @@ v 8.9.0 (unreleased) ...@@ -32,6 +38,7 @@ v 8.9.0 (unreleased)
- Implement a fair usage of shared runners - Implement a fair usage of shared runners
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Wrap code blocks on Activies and Todos page !4783 (winniehell)
- Add a metric for the number of new Redis connections created by a transaction - Add a metric for the number of new Redis connections created by a transaction
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages - Redesign navigation for project pages
...@@ -56,6 +63,7 @@ v 8.9.0 (unreleased) ...@@ -56,6 +63,7 @@ v 8.9.0 (unreleased)
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
- Don't allow MRs to be merged when commits were added since the last review / page load - Don't allow MRs to be merged when commits were added since the last review / page load
- Add DB index on users.state - Add DB index on users.state
- Limit email on push diff size to 30 files / 150 KB
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
- Changed the Slack build message to use the singular duration if necessary (Aran Koning) - Changed the Slack build message to use the singular duration if necessary (Aran Koning)
- Fix race condition on merge when build succeeds - Fix race condition on merge when build succeeds
...@@ -70,6 +78,7 @@ v 8.9.0 (unreleased) ...@@ -70,6 +78,7 @@ v 8.9.0 (unreleased)
- Todos will display target state if issuable target is 'Closed' or 'Merged' - Todos will display target state if issuable target is 'Closed' or 'Merged'
- Validate only and except regexp - Validate only and except regexp
- Fix bug when sorting issues by milestone due date and filtering by two or more labels - Fix bug when sorting issues by milestone due date and filtering by two or more labels
- POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
- Add support for using Yubikeys (U2F) for two-factor authentication - Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore - Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature - Remove 'main language' feature
...@@ -77,13 +86,16 @@ v 8.9.0 (unreleased) ...@@ -77,13 +86,16 @@ v 8.9.0 (unreleased)
- Pipelines can be canceled only when there are running builds - Pipelines can be canceled only when there are running builds
- Allow authentication using personal access tokens - Allow authentication using personal access tokens
- Use downcased path to container repository as this is expected path by Docker - Use downcased path to container repository as this is expected path by Docker
- Allow to use CI token to fetch LFS objects
- Custom notification settings - Custom notification settings
- Projects pending deletion will render a 404 page - Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails - Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels - Added Gfm autocomplete for labels
- Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114
- Make Omniauth providers specs to not modify global configuration - Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11 - Make authentication service for Container Registry to be compatible with < Docker 1.11
- Make it possible to lock a runner from being enabled for other projects
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav - Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment - Use Knapsack only in CI environment
...@@ -101,6 +113,7 @@ v 8.9.0 (unreleased) ...@@ -101,6 +113,7 @@ v 8.9.0 (unreleased)
- An indicator is now displayed at the top of the comment field for confidential issues. - An indicator is now displayed at the top of the comment field for confidential issues.
- Show categorised search queries in the search autocomplete - Show categorised search queries in the search autocomplete
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Dropdown for `.gitlab-ci.yml` templates
- Improve issuables APIs performance when accessing notes !4471 - Improve issuables APIs performance when accessing notes !4471
- Add sorting dropdown to tags page !4423 - Add sorting dropdown to tags page !4423
- External links now open in a new tab - External links now open in a new tab
...@@ -129,10 +142,17 @@ v 8.9.0 (unreleased) ...@@ -129,10 +142,17 @@ v 8.9.0 (unreleased)
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries - Set inverse_of for Project/Service association to reduce the number of queries
- Update tanuki logo highlight/loading colors - Update tanuki logo highlight/loading colors
- Remove explicit Gitlab::Metrics.action assignments, are already automatic.
- Use Git cached counters for branches and tags on project page - Use Git cached counters for branches and tags on project page
- Cache participable participants in an instance variable.
- Filter parameters for request_uri value on instrumented transactions. - Filter parameters for request_uri value on instrumented transactions.
- Remove duplicated keys add UNIQUE index to keys fingerprint column
- ExtractsPath get ref_names from repository cache, if not there access git.
- Cache user todo counts from TodoService - Cache user todo counts from TodoService
- Ensure Todos counters doesn't count Todos for projects pending delete - Ensure Todos counters doesn't count Todos for projects pending delete
- Add left/right arrows horizontal navigation
- Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.5 v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166 - Import GitHub repositories respecting the API rate limit !4166
......
...@@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0' ...@@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# Browser detection # Browser detection
gem "browser", '~> 2.0.3' gem "browser", '~> 2.2'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
...@@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14' ...@@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.7" gem "mail_room", "~> 0.8"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
......
...@@ -98,7 +98,7 @@ GEM ...@@ -98,7 +98,7 @@ GEM
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
brakeman (3.3.2) brakeman (3.3.2)
browser (2.0.3) browser (2.2.0)
builder (3.2.2) builder (3.2.2)
bullet (5.0.0) bullet (5.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
...@@ -398,7 +398,7 @@ GEM ...@@ -398,7 +398,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.4) mail (2.6.4)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.7.0) mail_room (0.8.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.2) mime-types (2.99.2)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -833,7 +833,7 @@ DEPENDENCIES ...@@ -833,7 +833,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0) brakeman (~> 3.3.0)
browser (~> 2.0.3) browser (~> 2.2)
bullet bullet
bundler-audit bundler-audit
byebug byebug
...@@ -899,7 +899,7 @@ DEPENDENCIES ...@@ -899,7 +899,7 @@ DEPENDENCIES
license_finder license_finder
licensee (~> 8.0.0) licensee (~> 8.0.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.7) mail_room (~> 0.8)
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
labelsPath: "/api/:version/projects/:id/labels" labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key" licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key" gitignorePath: "/api/:version/gitignores/:key"
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
group: (group_id, callback) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.groupPath) url = Api.buildUrl(Api.groupPath)
...@@ -110,6 +111,12 @@ ...@@ -110,6 +111,12 @@
$.get url, (gitignore) -> $.get url, (gitignore) ->
callback(gitignore) callback(gitignore)
gitlabCiYml: (key, callback) ->
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
$.get url, (file) ->
callback(file)
buildUrl: (url) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) return url.replace(':version', gon.api_version)
...@@ -121,6 +121,11 @@ window.onload = -> ...@@ -121,6 +121,11 @@ window.onload = ->
setTimeout shiftWindow, 100 setTimeout shiftWindow, 100
$ -> $ ->
$document = $(document)
$window = $(window)
$body = $('body')
gl.utils.preventDisabledButtons() gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
...@@ -152,7 +157,7 @@ $ -> ...@@ -152,7 +157,7 @@ $ ->
), 1 ), 1
# Initialize tooltips # Initialize tooltips
$('body').tooltip( $body.tooltip(
selector: '.has-tooltip, [data-toggle="tooltip"]' selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) -> placement: (_, el) ->
$el = $(el) $el = $(el)
...@@ -171,7 +176,7 @@ $ -> ...@@ -171,7 +176,7 @@ $ ->
flash.show() flash.show()
# Disable form buttons while a form is submitting # Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', @) buttons = $('[type="submit"]', @)
switch e.type switch e.type
...@@ -184,7 +189,7 @@ $ -> ...@@ -184,7 +189,7 @@ $ ->
$('.account-box').hover -> $(@).toggleClass('hover') $('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff # Commit show suppressed diff
$(document).on 'click', '.diff-content .js-show-suppressed-diff', -> $document.on 'click', '.diff-content .js-show-suppressed-diff', ->
$container = $(@).parent() $container = $(@).parent()
$container.next('table').show() $container.next('table').show()
$container.remove() $container.remove()
...@@ -197,13 +202,13 @@ $ -> ...@@ -197,13 +202,13 @@ $ ->
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff # Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) -> $body.on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active') $(@).toggleClass('active')
$(@).closest(".diff-file").find(".notes_holder").toggle() $(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault() e.preventDefault()
$(document).off "click", '.js-confirm-danger' $document.off "click", '.js-confirm-danger'
$(document).on "click", '.js-confirm-danger', (e) -> $document.on "click", '.js-confirm-danger', (e) ->
e.preventDefault() e.preventDefault()
btn = $(e.target) btn = $(e.target)
text = btn.data("confirm-danger-message") text = btn.data("confirm-danger-message")
...@@ -211,7 +216,7 @@ $ -> ...@@ -211,7 +216,7 @@ $ ->
new ConfirmDangerModal(form, text) new ConfirmDangerModal(form, text)
$(document).on 'click', 'button', -> $document.on 'click', 'button', ->
$(this).blur() $(this).blur()
$('input[type="search"]').each -> $('input[type="search"]').each ->
...@@ -219,7 +224,7 @@ $ -> ...@@ -219,7 +224,7 @@ $ ->
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
return return
$(document) $document
.off 'keyup', 'input[type="search"]' .off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) -> .on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this) $this = $(this)
...@@ -227,7 +232,7 @@ $ -> ...@@ -227,7 +232,7 @@ $ ->
$sidebarGutterToggle = $('.js-sidebar-toggle') $sidebarGutterToggle = $('.js-sidebar-toggle')
$(document) $document
.off 'breakpoint:change' .off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) -> .on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs' if breakpoint is 'sm' or breakpoint is 'xs'
...@@ -239,14 +244,14 @@ $ -> ...@@ -239,14 +244,14 @@ $ ->
oldBootstrapBreakpoint = bootstrapBreakpoint oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint]) $document.trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = -> checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm" if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint]) $document.trigger('breakpoint:change', [bootstrapBreakpoint])
$(window) $window
.off "resize.app" .off "resize.app"
.on "resize.app", (e) -> .on "resize.app", (e) ->
fitSidebarForSize() fitSidebarForSize()
...@@ -256,29 +261,45 @@ $ -> ...@@ -256,29 +261,45 @@ $ ->
new Aside() new Aside()
# Sidenav pinning # Sidenav pinning
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true' if $window.width() < 1440 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false') $.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar') $('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded') .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned') .removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').removeClass('header-pinned-nav') $('.navbar-fixed-top').removeClass('header-pinned-nav')
$(document) $document
.off 'click', '.js-nav-pin' .off 'click', '.js-nav-pin'
.on 'click', '.js-nav-pin', (e) -> .on 'click', '.js-nav-pin', (e) ->
e.preventDefault() e.preventDefault()
$pinBtn = $(e.currentTarget)
$page = $ '.page-with-sidebar'
$topNav = $ '.navbar-fixed-top'
$tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
doPinNav = not $page.is('.page-sidebar-pinned')
tooltipText = 'Pin navigation'
$(this).toggleClass 'is-active' $(this).toggleClass 'is-active'
if $.cookie('pin_nav') is 'true' if doPinNav
$.cookie 'pin_nav', 'false' $page.addClass('page-sidebar-pinned')
$('.page-with-sidebar') $topNav.addClass('header-pinned-nav')
.removeClass('page-sidebar-pinned')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$('.navbar-fixed-top')
.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
else else
$.cookie 'pin_nav', 'true' $tooltip.remove() # Remove it immediately when collapsing the sidebar
$('.page-with-sidebar').addClass('page-sidebar-pinned') $page.removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').addClass('header-pinned-nav') .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$topNav.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
# Save settings
$.cookie 'pin_nav', doPinNav, { path: '/' }
if $.cookie('pin_nav') is 'true' or doPinNav
tooltipText = 'Unpin navigation'
# Update tooltip text immediately
$tooltip.find('.tooltip-inner').text(tooltipText)
# Persist tooltip title
$pinBtn.attr('title', tooltipText).tooltip('fixTitle')
#= require blob/template_selector
class @BlobCiYamlSelector extends TemplateSelector
requestFile: (query) ->
Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
class @BlobCiYamlSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitlab-ci-yml-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobCiYamlSelector(
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)
...@@ -15,6 +15,7 @@ class @EditBlob ...@@ -15,6 +15,7 @@ class @EditBlob
new BlobLicenseSelectors { @editor } new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor } new BlobGitignoreSelectors { @editor }
new BlobCiYamlSelectors { @editor }
initModePanesAndLinks: -> initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane") @$editModePanes = $(".js-edit-mode-pane")
......
...@@ -58,7 +58,7 @@ class GitLabDropdownFilter ...@@ -58,7 +58,7 @@ class GitLabDropdownFilter
filter: (search_text) -> filter: (search_text) ->
data = @options.data() data = @options.data()
if data? if data? and not @options.filterByText
results = data results = data
if search_text isnt '' if search_text isnt ''
...@@ -102,10 +102,11 @@ class GitLabDropdownFilter ...@@ -102,10 +102,11 @@ class GitLabDropdownFilter
$el = $(@) $el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text) matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length unless $el.is('.dropdown-header')
$el.show() if matches.length
else $el.show()
$el.hide() else
$el.hide()
else else
elements.show() elements.show()
...@@ -191,6 +192,7 @@ class GitLabDropdown ...@@ -191,6 +192,7 @@ class GitLabDropdown
if @options.filterable if @options.filterable
@filter = new GitLabDropdownFilter @filterInput, @filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur filterInputBlur: @filterInputBlur
filterByText: @options.filterByText
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: searchFields keys: searchFields
......
...@@ -34,6 +34,8 @@ class @GLForm ...@@ -34,6 +34,8 @@ class @GLForm
# form and textarea event listeners # form and textarea event listeners
@addEventListeners() @addEventListeners()
gl.text.init(@form)
# hide discard button # hide discard button
@form.find('.js-note-discard').hide() @form.find('.js-note-discard').hide()
...@@ -42,6 +44,7 @@ class @GLForm ...@@ -42,6 +44,7 @@ class @GLForm
clearEventListeners: -> clearEventListeners: ->
@textarea.off 'focus' @textarea.off 'focus'
@textarea.off 'blur' @textarea.off 'blur'
gl.text.removeListeners(@form)
addEventListeners: -> addEventListeners: ->
@textarea.on 'focus', -> @textarea.on 'focus', ->
......
...@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph ...@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) -> constructor: (@data) ->
@width = $('.content').width()/2 - 100 # Don't split graph size in half for mobile devices.
if $(window).width() < 768
@width = $('.content').width() - 80
else
@width = ($('.content').width() / 2) - 100
@height = 200 @height = 200
@x = null @x = null
@y = null @y = null
......
((w) ->
w.gl ?= {}
w.gl.text ?= {}
gl.text.randomString = -> Math.random().toString(36).substring(7)
gl.text.replaceRange = (s, start, end, substitute) ->
s.substring(0, start) + substitute + s.substring(end);
gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) ->
selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap
insertText = selectedSplit.map((val) ->
if val.indexOf(tag) is 0
"#{val.replace(tag, '')}"
else
"#{tag}#{val}"
).join('\n')
else
insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
if document.queryCommandSupported('insertText')
document.execCommand 'insertText', false, insertText
else
try
document.execCommand("ms-beginUndoUnit")
textArea.value = @replaceRange(
text,
textArea.selectionStart,
textArea.selectionEnd,
insertText)
try
document.execCommand("ms-endUndoUnit")
@moveCursor(textArea, tag, wrap)
gl.text.moveCursor = (textArea, tag, wrapped) ->
return unless textArea.setSelectionRange
if textArea.selectionStart is textArea.selectionEnd
if wrapped
pos = textArea.selectionStart - tag.length
else
pos = textArea.selectionStart
textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) ->
$textArea = $(textArea)
oldVal = $textArea.val()
textArea = $textArea.get(0)
text = $textArea.val()
selected = @selectedText(text, textArea)
$textArea.focus()
@insertText(textArea, text, tag, selected, wrap)
gl.text.init = (form) ->
self = @
$('.js-md', form)
.off 'click'
.on 'click', ->
$this = $(@)
self.updateText(
$this.closest('.md-area').find('textarea'),
$this.data('md-tag'),
not $this.data('md-prepend')
)
gl.text.removeListeners = (form) ->
$('.js-md', form).off()
) window
...@@ -102,12 +102,15 @@ class @Notes ...@@ -102,12 +102,15 @@ class @Notes
keydownNoteText: (e) -> keydownNoteText: (e) ->
$this = $(this) $this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key if $this.val() is '' and e.which is 38 and not isMetaKey e
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit') myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote]) myLastNoteEditBtn.trigger('click', [true, myLastNote])
isMetaKey = (e) ->
(e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
......
class @NotificationsDropdown class @NotificationsDropdown
$ -> constructor: ->
$(document) $(document)
.off 'click', '.update-notification' .off 'click', '.update-notification'
.on 'click', '.update-notification', (e) -> .on 'click', '.update-notification', (e) ->
...@@ -18,7 +18,8 @@ class @NotificationsDropdown ...@@ -18,7 +18,8 @@ class @NotificationsDropdown
.off 'ajax:success', '.notification-form' .off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) -> .on 'ajax:success', '.notification-form', (e, data) ->
if data.saved if data.saved
new Flash('Notification settings saved', 'notice') $(e.currentTarget)
$(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html) .closest('.notification-dropdown')
.replaceWith(data.html)
else else
new Flash('Failed to save new settings', 'alert') new Flash('Failed to save new settings', 'alert')
...@@ -19,6 +19,7 @@ class @Project ...@@ -19,6 +19,7 @@ class @Project
$('.clone').text(url) $('.clone').text(url)
# Ref switcher # Ref switcher
@initRefSwitcher()
$('.project-refs-select').on 'change', -> $('.project-refs-select').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
...@@ -34,7 +35,6 @@ class @Project ...@@ -34,7 +35,6 @@ class @Project
$(@).parents('.no-password-message').remove() $(@).parents('.no-password-message').remove()
e.preventDefault() e.preventDefault()
@projectSelectDropdown() @projectSelectDropdown()
projectSelectDropdown: -> projectSelectDropdown: ->
...@@ -50,3 +50,39 @@ class @Project ...@@ -50,3 +50,39 @@ class @Project
changeProject: (url) -> changeProject: (url) ->
window.location = url window.location = url
initRefSwitcher: ->
$('.js-project-refs-dropdown').each ->
$dropdown = $(@)
selected = $dropdown.data('selected')
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: $dropdown.data('refs-url')
data:
ref: $dropdown.data('ref')
).done (refs) ->
callback(refs)
selectable: true
filterable: true
filterByText: true
fieldName: 'ref'
renderRow: (ref) ->
if ref.header?
"<li class='dropdown-header'>#{ref.header}</li>"
else
isActiveClass = if ref is selected then 'is-active' else ''
"<li>
<a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'>
#{ref}
</a>
</li>"
id: (obj, $el) ->
$el.data('ref')
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e) ->
$dropdown.closest('form').submit()
)
...@@ -37,3 +37,4 @@ ...@@ -37,3 +37,4 @@
@import "framework/timeline.scss"; @import "framework/timeline.scss";
@import "framework/typography.scss"; @import "framework/typography.scss";
@import "framework/zen.scss"; @import "framework/zen.scss";
@import "framework/blank";
.blank-state {
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}
.blank-state-no-icon {
padding-top: 40px;
padding-bottom: 40px;
}
.blank-state-title {
margin-top: 0;
margin-bottom: 5px;
font-size: 19px;
font-weight: normal;
}
.blank-state-text {
margin-top: 0;
margin-bottom: $gl-padding;
font-size: 15px;
}
...@@ -97,6 +97,22 @@ ...@@ -97,6 +97,22 @@
} }
} }
.sub-header-block {
background-color: $white-light;
border-bottom: 1px solid $white-dark;
padding: 11px 0;
margin-bottom: 11px;
.oneline {
line-height: 35px;
}
&.no-bottom-space {
border-bottom: 0;
margin-bottom: 0;
}
}
.cover-block { .cover-block {
text-align: center; text-align: center;
background: $background-color; background: $background-color;
......
...@@ -461,10 +461,12 @@ ...@@ -461,10 +461,12 @@
} }
} }
.ui-state-active, .ui-datepicker-calendar {
.ui-state-hover { .ui-state-hover,
color: $md-link-color; .ui-state-active {
background-color: $calendar-hover-bg; color: #fff;
border: 0;
}
} }
.ui-datepicker-prev, .ui-datepicker-prev,
......
...@@ -65,6 +65,11 @@ ...@@ -65,6 +65,11 @@
a { a {
padding-top: 0; padding-top: 0;
line-height: 1; line-height: 1;
border-bottom: 1px solid $border-color;
&.btn.btn-xs {
padding: 2px 5px;
}
} }
} }
} }
...@@ -97,5 +102,30 @@ ...@@ -97,5 +102,30 @@
white-space: pre-wrap; white-space: pre-wrap;
word-break: keep-all; word-break: keep-all;
} }
@include bulleted-list;
}
}
.toolbar-group {
float: left;
margin-right: -5px;
margin-left: $gl-padding;
&:first-child {
margin-left: 0;
}
}
.toolbar-btn {
float: left;
padding: 0 5px;
color: #959494;
background: transparent;
border: 0;
outline: 0;
&:hover {
color: $gl-link-color;
} }
} }
...@@ -110,3 +110,17 @@ ...@@ -110,3 +110,17 @@
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
} }
@mixin bulleted-list {
> ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
}
\ No newline at end of file
...@@ -18,6 +18,13 @@ ...@@ -18,6 +18,13 @@
opacity: 0; opacity: 0;
transition-duration: .3s; transition-duration: .3s;
} }
.fa {
position: relative;
top: 3px;
font-size: 13px;
color: $btn-placeholder-gray;
}
} }
@mixin scrolling-links() { @mixin scrolling-links() {
...@@ -104,10 +111,6 @@ ...@@ -104,10 +111,6 @@
width: 50%; width: 50%;
line-height: 28px; line-height: 28px;
&.wiki-page {
padding: 16px 10px 11px;
}
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
width: 100%; width: 100%;
...@@ -136,7 +139,7 @@ ...@@ -136,7 +139,7 @@
} }
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-max) { @media (max-width: $screen-xs-max) {
width: 100%; width: 100%;
} }
} }
...@@ -220,6 +223,7 @@ ...@@ -220,6 +223,7 @@
form { form {
display: block; display: block;
height: auto; height: auto;
margin-bottom: 14px;
input { input {
width: 100%; width: 100%;
...@@ -319,11 +323,19 @@ ...@@ -319,11 +323,19 @@
.fade-right { .fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color); @include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0; right: 0;
.fa {
right: -7px;
}
} }
.fade-left { .fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color); @include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0; left: 0;
.fa {
left: -7px;
}
} }
li { li {
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
margin-top: -2px; margin-top: -2px;
float: right; float: right;
} }
.dropdown-menu-toggle {
line-height: 20px;
}
} }
.panel-body { .panel-body {
......
...@@ -165,11 +165,6 @@ ...@@ -165,11 +165,6 @@
background-size: 16px 16px !important; background-size: 16px 16px !important;
} }
/** Branch/tag selector **/
.project-refs-form .select2-container {
width: 160px !important;
}
.select2-results .select2-no-results, .select2-results .select2-no-results,
.select2-results .select2-searching, .select2-results .select2-searching,
.select2-results .select2-ajax-error, .select2-results .select2-ajax-error,
......
...@@ -91,7 +91,6 @@ ...@@ -91,7 +91,6 @@
text-decoration: none; text-decoration: none;
font-weight: normal; font-weight: normal;
outline: none; outline: none;
white-space: nowrap;
&:hover, &:hover,
&:active, &:active,
......
...@@ -80,9 +80,14 @@ ...@@ -80,9 +80,14 @@
.commit { .commit {
padding: 10px 0; padding: 10px 0;
position: relative;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-left: 46px; padding-left: 20px;
.commit-info-block {
padding-left: 44px;
}
} }
&:not(:last-child) { &:not(:last-child) {
...@@ -95,8 +100,11 @@ ...@@ -95,8 +100,11 @@
vertical-align: baseline; vertical-align: baseline;
} }
.avatar { .avatar {
margin-left: -46px; position: absolute;
top: 10px;
left: 16px;
} }
.item-title { .item-title {
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
border-radius: 3px; border-radius: 3px;
.commit-short-id {
font-family: $regular_font;
font-weight: 400;
}
.diff-header { .diff-header {
position: relative; position: relative;
background: $background-color; background: $background-color;
......
...@@ -60,13 +60,14 @@ ...@@ -60,13 +60,14 @@
.encoding-selector, .encoding-selector,
.license-selector, .license-selector,
.gitignore-selector { .gitignore-selector,
.gitlab-ci-yml-selector {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
font-family: $regular_font; font-family: $regular_font;
} }
.gitignore-selector, .license-selector { .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
} }
...@@ -76,4 +77,10 @@ ...@@ -76,4 +77,10 @@
width: 220px; width: 220px;
} }
} }
.gitlab-ci-yml-selector {
.dropdown-menu-toggle {
width: 250px;
}
}
} }
...@@ -54,6 +54,10 @@ ...@@ -54,6 +54,10 @@
} }
} }
code {
white-space: pre-wrap;
}
pre { pre {
border: none; border: none;
background: #f9f9f9; background: #f9f9f9;
......
...@@ -57,4 +57,11 @@ ...@@ -57,4 +57,11 @@
.documentation { .documentation {
padding: 7px; padding: 7px;
// Border around images in the help pages.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
}
} }
...@@ -4,6 +4,13 @@ ...@@ -4,6 +4,13 @@
margin-right: 1px; margin-right: 1px;
} }
} }
// Border around images in issue and MR descriptions.
.description img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
}
} }
.issuable-filter-count { .issuable-filter-count {
......
...@@ -50,11 +50,10 @@ ...@@ -50,11 +50,10 @@
.label-row { .label-row {
.label-name { .label-name {
display: block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: inline-block;
width: 200px; width: 200px;
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -63,6 +62,7 @@ ...@@ -63,6 +62,7 @@
.label-description { .label-description {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
margin-left: 50px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: inline-block; display: inline-block;
......
...@@ -119,7 +119,12 @@ ...@@ -119,7 +119,12 @@
margin-bottom: 0; margin-bottom: 0;
} }
@media (max-width: $screen-sm-max) { .btn-grouped {
margin-left: 0;
margin-right: 7px;
}
@media (max-width: $screen-xs-max) {
h4 { h4 {
font-size: 15px; font-size: 15px;
} }
...@@ -131,10 +136,14 @@ ...@@ -131,10 +136,14 @@
.btn, .btn,
.btn-group, .btn-group,
.accept-action { .accept-action {
width: 100%;
margin-bottom: 4px; margin-bottom: 4px;
} }
.accept-action {
width: 100%;
text-align: center;
}
.accept-control { .accept-control {
width: 100%; width: 100%;
text-align: center; text-align: center;
...@@ -284,7 +293,7 @@ ...@@ -284,7 +293,7 @@
margin-bottom: 0; margin-bottom: 0;
} }
@media (min-width: $screen-sm-min) { @media (min-width: $screen-xs-min) {
float: left; float: left;
width: 50%; width: 50%;
margin-bottom: 0; margin-bottom: 0;
......
...@@ -179,6 +179,10 @@ ...@@ -179,6 +179,10 @@
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
} }
.md-helper {
padding-top: 10px;
}
.toolbar-button { .toolbar-button {
padding: 0; padding: 0;
background: none; background: none;
...@@ -219,3 +223,16 @@ ...@@ -219,3 +223,16 @@
float: left; float: left;
} }
} }
.note-form-actions {
@media (max-width: $screen-xs-max) {
.btn {
float: none;
width: 100%;
&:not(:last-child) {
margin-bottom: 10px;
}
}
}
}
...@@ -84,24 +84,14 @@ ul.notes { ...@@ -84,24 +84,14 @@ ul.notes {
word-wrap: break-word; word-wrap: break-word;
@include md-typography; @include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
// On diffs code should wrap nicely and not overflow // On diffs code should wrap nicely and not overflow
code { code {
white-space: pre-wrap; white-space: pre-wrap;
} }
// Reset ul style types since we're nested inside a ul already
& > ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
ul.task-list { ul.task-list {
ul:not(.task-list) { ul:not(.task-list) {
padding-left: 1.3em; padding-left: 1.3em;
...@@ -117,6 +107,13 @@ ul.notes { ...@@ -117,6 +107,13 @@ ul.notes {
code { code {
word-break: keep-all; word-break: keep-all;
} }
// Border around images in issue and MR comments.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px 0;
}
} }
} }
......
...@@ -101,7 +101,8 @@ ...@@ -101,7 +101,8 @@
.notifications-btn { .notifications-btn {
.fa-bell { .fa-bell,
.fa-spinner {
margin-right: 6px; margin-right: 6px;
} }
...@@ -373,7 +374,7 @@ a.deploy-project-label { ...@@ -373,7 +374,7 @@ a.deploy-project-label {
.project-stats { .project-stats {
margin-top: $gl-padding; margin-top: $gl-padding;
margin-bottom: 0; margin-bottom: 0;
padding: 16px 0; padding: 0;
background-color: $white-light; background-color: $white-light;
font-size: 0; font-size: 0;
...@@ -382,13 +383,14 @@ a.deploy-project-label { ...@@ -382,13 +383,14 @@ a.deploy-project-label {
} }
.nav li { .nav li {
display: inline; display: inline-block;
margin: 16px 0;
margin-right: 16px;
} }
.nav > li > a { .nav > li > a {
background-color: transparent; background-color: transparent;
margin-right: 12px; padding: 5px 10px;
padding: 0 10px;
font-size: 15px; font-size: 15px;
color: $notes-light-color; color: $notes-light-color;
} }
...@@ -402,12 +404,17 @@ a.deploy-project-label { ...@@ -402,12 +404,17 @@ a.deploy-project-label {
font-size: 17px; font-size: 17px;
} }
li.missing a { li.missing {
color: #5a6069; border: 1px dashed $border-gray-light;
border: 1px dashed #dce0e5; border-radius: $border-radius-default;
a {
color: $notes-light-color;
display: block;
}
&:hover { &:hover {
background-color: #f0f2f5; background-color: $gray-normal;
} }
} }
...@@ -616,3 +623,9 @@ pre.light-well { ...@@ -616,3 +623,9 @@ pre.light-well {
color: $gl-text-green; color: $gl-text-green;
} }
} }
.project-refs-form {
.dropdown-menu {
width: 300px;
}
}
...@@ -14,24 +14,38 @@ ...@@ -14,24 +14,38 @@
font-size: 10px; font-size: 10px;
} }
#contributors-master {
@include make-md-column(12);
svg {
width: 100%;
}
}
#contributors { #contributors {
.contributors-list { .contributors-list {
margin: 0 0 10px; margin: 0 0 10px;
list-style: none; list-style: none;
padding: 0; padding: 0;
svg {
width: 100%;
}
} }
.person { .person {
&:nth-child(even) { @include make-md-column(6);
float: right;
}
float: left;
margin-top: 10px; margin-top: 10px;
@media (max-width: $screen-sm-min) {
width: 100%;
}
} }
.person .spark { .person .spark {
display: block; display: block;
background: #f3f3f3; background: #f3f3f3;
width: 100%;
} }
.person .area-contributor { .person .area-contributor {
......
...@@ -62,6 +62,10 @@ ...@@ -62,6 +62,10 @@
} }
} }
code {
white-space: pre-wrap;
}
pre { pre {
border: none; border: none;
background: #f9f9f9; background: #f9f9f9;
......
...@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController ...@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController
end end
def preview def preview
render 'preview', layout: 'devise'
end end
def create def create
......
class Admin::RunnerProjectsController < Admin::ApplicationController class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create] before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user) return head(403) if @runner.is_shared? || @runner.locked?
runner_project = @runner.assign_to(@project, current_user)
if runner_project.persisted?
redirect_to admin_runner_path(@runner) redirect_to admin_runner_path(@runner)
else else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project' redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
......
...@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base ...@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
rescue_from Gitlab::Access::AccessDeniedError do |exception|
render_403
end
def redirect_back_or_default(default: root_path, options: {}) def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options redirect_to request.referer.present? ? :back : default, options
end end
......
...@@ -21,29 +21,18 @@ module MembershipActions ...@@ -21,29 +21,18 @@ module MembershipActions
def leave def leave
@member = membershipable.members.find_by(user_id: current_user) @member = membershipable.members.find_by(user_id: current_user)
return render_403 unless @member Members::DestroyService.new(@member, current_user).execute
source_type = @member.real_source_type.humanize(capitalize: false) source_type = @member.real_source_type.humanize(capitalize: false)
notice =
if can?(current_user, action_member_permission(:destroy, @member), @member) if @member.request?
notice = "Your access request to the #{source_type} has been withdrawn."
if @member.request?
"Your access request to the #{source_type} has been withdrawn."
else
"You left the \"#{@member.source.human_name}\" #{source_type}."
end
@member.destroy
redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice
else
if cannot_leave?
alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}."
alert << " Transfer or delete the #{source_type}."
redirect_to polymorphic_url(membershipable), alert: alert
else else
render_403 "You left the \"#{@member.source.human_name}\" #{source_type}."
end end
end redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
redirect_to redirect_path, notice: notice
end end
protected protected
...@@ -51,8 +40,4 @@ module MembershipActions ...@@ -51,8 +40,4 @@ module MembershipActions
def membershipable def membershipable
raise NotImplementedError raise NotImplementedError
end end
def cannot_leave?
raise NotImplementedError
end
end end
...@@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def destroy def destroy
@group_member = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_group_member, @group_member) Members::DestroyService.new(@group_member, current_user).execute
@group_member.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
...@@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController
# MembershipActions concern # MembershipActions concern
alias_method :membershipable, :group alias_method :membershipable, :group
def cannot_leave?
@group.last_owner?(current_user)
end
end end
...@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController ...@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
end end
def require_branch_head def require_branch_head
unless @repository.branch_names.include?(@ref) unless @repository.branch_exists?(@ref)
redirect_to( redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref), namespace_project_tree_path(@project.namespace, @project, @ref),
notice: "This action is not allowed unless you are on a branch" notice: "This action is not allowed unless you are on a branch"
......
...@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def commit def commit
@commit ||= @pipeline.commit_data @commit ||= @pipeline.commit
end end
end end
...@@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def destroy def destroy
@project_member = @project.project_members.find(params[:id]) @project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_project_member, @project_member) Members::DestroyService.new(@project_member, current_user).execute
@project_member.destroy
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# MembershipActions concern # MembershipActions concern
alias_method :membershipable, :project alias_method :membershipable, :project
def cannot_leave?
current_user == @project.owner
end
end end
...@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController ...@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) if @runner.is_shared? || @runner.locked?
return head(403) unless current_user.ci_authorized_runners.include?(@runner) return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project) path = runners_path(project)
runner_project = @runner.assign_to(project, current_user)
if @runner.assign_to(project, current_user) if runner_project.persisted?
redirect_to path redirect_to path
else else
redirect_to path, alert: 'Failed adding runner to project' redirect_to path, alert: 'Failed adding runner to project'
......
...@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings' layout 'project_settings'
def index def index
@runners = project.runners.ordered @project_runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners. @assignable_runners = current_user.ci_authorized_runners.
where.not(id: project.runners). assignable_for(project).ordered.page(params[:page]).per(20)
ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active @shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all) @shared_runners_count = @shared_runners.count(:all)
end end
......
class ProjectsController < Projects::ApplicationController class ProjectsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_action :authenticate_user!, except: [:show, :activity] before_action :authenticate_user!, except: [:show, :activity, :refs]
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
...@@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController ...@@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController
} }
end end
def refs
options = {
'Branches' => @repository.branch_names,
}
unless @repository.tag_count.zero?
options['Tags'] = VersionSorter.rsort(@repository.tag_names)
end
# If reference is commit id - we should add it to branch/tag selectbox
ref = Addressable::URI.unescape(params[:ref])
if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options['Commits'] = [ref]
end
render json: options.to_json
end
private private
def determine_layout def determine_layout
...@@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController ...@@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController
project.repository_exists? && !project.empty_repo? project.repository_exists? && !project.empty_repo?
end end
# Override get_id from ExtractsPath, which returns the branch and file path # Override extract_ref from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch. # for the blob/tree, which in this case is just the root of the default branch.
# This way we avoid to access the repository.ref_names.
def extract_ref(_id)
[get_id, '']
end
# Override get_id from ExtractsPath in this case is just the root of the default branch.
def get_id def get_id
project.repository.root_ref project.repository.root_ref
end end
......
...@@ -101,22 +101,6 @@ module ApplicationHelper ...@@ -101,22 +101,6 @@ module ApplicationHelper
'Never' 'Never'
end end
def grouped_options_refs
repository = @project.repository
options = [
['Branches', repository.branch_names],
['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
end
# Define whenever show last push event # Define whenever show last push event
# with suggestion to create MR # with suggestion to create MR
def show_last_push_widget?(event) def show_last_push_widget?(event)
...@@ -132,7 +116,7 @@ module ApplicationHelper ...@@ -132,7 +116,7 @@ module ApplicationHelper
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that # Skip if user removed branch right after that
return false unless project.repository.branch_names.include?(event.branch_name) return false unless project.repository.branch_exists?(event.branch_name)
true true
end end
......
...@@ -29,7 +29,7 @@ module BlobHelper ...@@ -29,7 +29,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn btn-file-option' link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
to: edit_path, to: edit_path,
...@@ -186,12 +186,16 @@ module BlobHelper ...@@ -186,12 +186,16 @@ module BlobHelper
end end
def gitignore_names def gitignore_names
return @gitignore_names if defined?(@gitignore_names) @gitignore_names ||=
Gitlab::Template::Gitignore.categories.keys.map do |k|
[k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
end.to_h
end
@gitignore_names = { def gitlab_ci_ymls
Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } }, @gitlab_ci_ymls ||=
# Note that the key here doesn't cover it really Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } } [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
} end.to_h
end end
end end
...@@ -10,7 +10,7 @@ module BranchesHelper ...@@ -10,7 +10,7 @@ module BranchesHelper
end end
def can_push_branch?(project, branch_name) def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name) return false unless project.repository.branch_exists?(branch_name)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end end
......
...@@ -50,8 +50,6 @@ module GitlabMarkdownHelper ...@@ -50,8 +50,6 @@ module GitlabMarkdownHelper
context[:project] ||= @project context[:project] ||= @project
text = Banzai.pre_process(text, context)
html = Banzai.render(text, context) html = Banzai.render(text, context)
context.merge!( context.merge!(
...@@ -185,4 +183,17 @@ module GitlabMarkdownHelper ...@@ -185,4 +183,17 @@ module GitlabMarkdownHelper
'' ''
end end
end end
def markdown_toolbar_button(options = {})
data = options[:data].merge({ container: "body" })
content_tag :button,
type: "button",
class: "toolbar-btn js-md has-tooltip hidden-xs",
tabindex: -1,
data: data,
title: options[:title],
aria: { label: options[:title] } do
icon(options[:icon])
end
end
end end
...@@ -12,6 +12,11 @@ module Emails ...@@ -12,6 +12,11 @@ module Emails
@member_id = member_id @member_id = member_id
admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email) admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
# A project in a group can have no explicit owners/masters, in that case
# we fallbacks to the group's owners/masters.
if admins.empty? && member_source.respond_to?(:group) && member_source.group
admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
end
mail(to: admins, mail(to: admins,
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
......
...@@ -300,18 +300,12 @@ module Ci ...@@ -300,18 +300,12 @@ module Ci
project.valid_runners_token? token project.valid_runners_token? token
end end
def can_be_served?(runner)
return false unless has_tags? || runner.run_untagged?
(tag_list - runner.tag_list).empty?
end
def has_tags? def has_tags?
tag_list.any? tag_list.any?
end end
def any_runners_online? def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
end end
def stuck? def stuck?
......
...@@ -37,22 +37,22 @@ module Ci ...@@ -37,22 +37,22 @@ module Ci
end end
def git_author_name def git_author_name
commit_data.author_name if commit_data commit.try(:author_name)
end end
def git_author_email def git_author_email
commit_data.author_email if commit_data commit.try(:author_email)
end end
def git_commit_message def git_commit_message
commit_data.message if commit_data commit.try(:message)
end end
def short_sha def short_sha
Ci::Pipeline.truncate_sha(sha) Ci::Pipeline.truncate_sha(sha)
end end
def commit_data def commit
@commit ||= project.commit(sha) @commit ||= project.commit(sha)
rescue rescue
nil nil
......
...@@ -4,7 +4,7 @@ module Ci ...@@ -4,7 +4,7 @@ module Ci
LAST_CONTACT_TIME = 5.minutes.ago LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = %w[specific shared active paused online] AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged] FORM_EDITABLE = %i[description tag_list active run_untagged locked]
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
...@@ -26,6 +26,13 @@ module Ci ...@@ -26,6 +26,13 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end end
scope :assignable_for, ->(project) do
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
# Without that, placeholders would miss one and couldn't match.
where(locked: false).
where.not("id IN (#{project.runners.select(:id).to_sql})").specific
end
validate :tag_constraints validate :tag_constraints
acts_as_taggable acts_as_taggable
...@@ -56,7 +63,7 @@ module Ci ...@@ -56,7 +63,7 @@ module Ci
def assign_to(project, current_user = nil) def assign_to(project, current_user = nil)
self.is_shared = false if shared? self.is_shared = false if shared?
self.save self.save
project.runner_projects.create!(runner_id: self.id) project.runner_projects.create(runner_id: self.id)
end end
def display_name def display_name
...@@ -91,6 +98,10 @@ module Ci ...@@ -91,6 +98,10 @@ module Ci
!shared? !shared?
end end
def can_pick?(build)
assignable_for?(build.project) && accepting_tags?(build)
end
def only_for?(project) def only_for?(project)
projects == [project] projects == [project]
end end
...@@ -111,5 +122,13 @@ module Ci ...@@ -111,5 +122,13 @@ module Ci
'can not be empty when runner is not allowed to pick untagged jobs') 'can not be empty when runner is not allowed to pick untagged jobs')
end end
end end
def assignable_for?(project)
!locked? || projects.exists?(id: project.id)
end
def accepting_tags?(build)
(run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty?
end
end end
end end
...@@ -271,6 +271,32 @@ class Commit ...@@ -271,6 +271,32 @@ class Commit
merged_merge_request ? 'merge request' : 'commit' merged_merge_request ? 'merge request' : 'commit'
end end
# Get the URI type of the given path
#
# Used to build URLs to files in the repository in GFM.
#
# path - String path to check
#
# Examples:
#
# uri_type('doc/README.md') # => :blob
# uri_type('doc/logo.png') # => :raw
# uri_type('doc/api') # => :tree
# uri_type('not/found') # => :nil
#
# Returns a symbol
def uri_type(path)
entry = @raw.tree.path(path)
if entry[:type] == :blob
blob = Gitlab::Git::Blob.new(name: entry[:name])
blob.image? ? :raw : :blob
else
entry[:type]
end
rescue Rugged::TreeError
nil
end
private private
def repo_changes def repo_changes
......
...@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base ...@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
belongs_to :user belongs_to :user
delegate :commit, to: :pipeline
validates :pipeline, presence: true, unless: :importing? validates :pipeline, presence: true, unless: :importing?
validates_presence_of :name validates_presence_of :name
......
...@@ -53,6 +53,16 @@ module Participable ...@@ -53,6 +53,16 @@ module Participable
# #
# Returns an Array of User instances. # Returns an Array of User instances.
def participants(current_user = nil) def participants(current_user = nil)
@participants ||= Hash.new do |hash, user|
hash[user] = raw_participants(user)
end
@participants[current_user]
end
private
def raw_participants(current_user = nil)
current_user ||= author current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user) ext = Gitlab::ReferenceExtractor.new(project, current_user)
participants = Set.new participants = Set.new
......
...@@ -9,7 +9,7 @@ class Key < ActiveRecord::Base ...@@ -9,7 +9,7 @@ class Key < ActiveRecord::Base
before_validation :strip_white_space, :generate_fingerprint before_validation :strip_white_space, :generate_fingerprint
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }
validates :key, format: { without: /\n|\r/, message: 'should be a single line' } validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
......
...@@ -48,7 +48,6 @@ class Member < ActiveRecord::Base ...@@ -48,7 +48,6 @@ class Member < ActiveRecord::Base
after_create :post_create_hook, unless: [:pending?, :importing?] after_create :post_create_hook, unless: [:pending?, :importing?]
after_update :post_update_hook, unless: [:pending?, :importing?] after_update :post_update_hook, unless: [:pending?, :importing?]
after_destroy :post_destroy_hook, unless: :pending? after_destroy :post_destroy_hook, unless: :pending?
after_destroy :post_decline_request, if: :request?
delegate :name, :username, :email, to: :user, prefix: true delegate :name, :username, :email, to: :user, prefix: true
...@@ -188,7 +187,7 @@ class Member < ActiveRecord::Base ...@@ -188,7 +187,7 @@ class Member < ActiveRecord::Base
end end
def send_request def send_request
# override in subclass notification_service.new_access_request(self)
end end
def post_create_hook def post_create_hook
...@@ -215,10 +214,6 @@ class Member < ActiveRecord::Base ...@@ -215,10 +214,6 @@ class Member < ActiveRecord::Base
post_create_hook post_create_hook
end end
def post_decline_request
# override in subclass
end
def system_hook_service def system_hook_service
SystemHooksService.new SystemHooksService.new
end end
......
...@@ -33,12 +33,6 @@ class GroupMember < Member ...@@ -33,12 +33,6 @@ class GroupMember < Member
super super
end end
def send_request
notification_service.new_group_access_request(self)
super
end
def post_create_hook def post_create_hook
notification_service.new_group_member(self) notification_service.new_group_member(self)
...@@ -64,10 +58,4 @@ class GroupMember < Member ...@@ -64,10 +58,4 @@ class GroupMember < Member
super super
end end
def post_decline_request
notification_service.decline_group_access_request(self)
super
end
end end
...@@ -111,12 +111,6 @@ class ProjectMember < Member ...@@ -111,12 +111,6 @@ class ProjectMember < Member
super super
end end
def send_request
notification_service.new_project_access_request(self)
super
end
def post_create_hook def post_create_hook
unless owner? unless owner?
event_service.join_project(self.project, self.user) event_service.join_project(self.project, self.user)
...@@ -152,12 +146,6 @@ class ProjectMember < Member ...@@ -152,12 +146,6 @@ class ProjectMember < Member
super super
end end
def post_decline_request
notification_service.decline_project_access_request(self)
super
end
def event_service def event_service
EventCreateService.new EventCreateService.new
end end
......
...@@ -191,8 +191,12 @@ class Repository ...@@ -191,8 +191,12 @@ class Repository
end end
end end
def ref_names
branch_names + tag_names
end
def branch_names def branch_names
cache.fetch(:branch_names) { branches.map(&:name) } @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
end end
def branch_exists?(branch_name) def branch_exists?(branch_name)
...@@ -267,6 +271,7 @@ class Repository ...@@ -267,6 +271,7 @@ class Repository
def expire_branches_cache def expire_branches_cache
cache.expire(:branch_names) cache.expire(:branch_names)
@branch_names = nil
@local_branches = nil @local_branches = nil
end end
...@@ -332,10 +337,6 @@ class Repository ...@@ -332,10 +337,6 @@ class Repository
@lookup_cache ||= {} @lookup_cache ||= {}
end end
def expire_branch_names
cache.expire(:branch_names)
end
def expire_avatar_cache(branch_name = nil, revision = nil) def expire_avatar_cache(branch_name = nil, revision = nil)
# Avatars are pulled from the default branch, thus if somebody pushes to a # Avatars are pulled from the default branch, thus if somebody pushes to a
# different branch there's no need to expire anything. # different branch there's no need to expire anything.
......
...@@ -487,9 +487,8 @@ class User < ActiveRecord::Base ...@@ -487,9 +487,8 @@ class User < ActiveRecord::Base
events.recent.find do |event| events.recent.find do |event|
project = Project.find_by_id(event.project_id) project = Project.find_by_id(event.project_id)
next unless project next unless project
repo = project.repository
if repo.branch_names.include?(event.branch_name) if project.repository.branch_exists?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at). merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
where(source_project_id: project.id, where(source_project_id: project.id,
source_branch: event.branch_name) source_branch: event.branch_name)
......
...@@ -21,7 +21,7 @@ module Ci ...@@ -21,7 +21,7 @@ module Ci
end end
build = builds.find do |build| build = builds.find do |build|
build.can_be_served?(current_runner) current_runner.can_pick?(build)
end end
if build if build
......
module Members
class DestroyService < BaseService
attr_accessor :member, :current_user
def initialize(member, user)
@member, @current_user = member, user
end
def execute
unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
raise Gitlab::Access::AccessDeniedError
end
member.destroy
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
end
end
end
...@@ -83,7 +83,7 @@ module MergeRequests ...@@ -83,7 +83,7 @@ module MergeRequests
closes_issue = "Closes ##{iid}" closes_issue = "Closes ##{iid}"
if merge_request.description.present? if merge_request.description.present?
merge_request.description << closes_issue.prepend("\n") merge_request.description += closes_issue.prepend("\n")
else else
merge_request.description = closes_issue merge_request.description = closes_issue
end end
......
...@@ -181,15 +181,16 @@ class NotificationService ...@@ -181,15 +181,16 @@ class NotificationService
end end
end end
# Project access request # Members
def new_project_access_request(project_member) def new_access_request(member)
mailer.member_access_requested_email(project_member.real_source_type, project_member.id).deliver_later mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later
end end
def decline_project_access_request(project_member) def decline_access_request(member)
mailer.member_access_denied_email(project_member.real_source_type, project_member.project.id, project_member.user.id).deliver_later mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
end end
# Project invite
def invite_project_member(project_member, token) def invite_project_member(project_member, token)
mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
end end
...@@ -216,15 +217,7 @@ class NotificationService ...@@ -216,15 +217,7 @@ class NotificationService
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
end end
# Group access request # Group invite
def new_group_access_request(group_member)
mailer.member_access_requested_email(group_member.real_source_type, group_member.id).deliver_later
end
def decline_group_access_request(group_member)
mailer.member_access_denied_email(group_member.real_source_type, group_member.group.id, group_member.user.id).deliver_later
end
def invite_group_member(group_member, token) def invite_group_member(group_member, token)
mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later
end end
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save append-right-10'
- if @appearance.persisted? - if @appearance.persisted?
= link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank'
......
- page_title "Preview | Appearance" - page_title "Preview | Appearance"
%h3.page-title .login-box
Appearance settings - Preview .login-heading
%hr %h3 Existing user? Sign in
%form
= text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
= password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
= button_tag "Sign in", class: "btn-create btn"
.ui-box
.title
Sign-in page
%div
.login-page
.container
.content
.login-title
%h1= brand_title
%hr
.container
.content
.row
.col-sm-7
.brand-image
= brand_image
.brand_text
= brand_text
.col-sm-4
.login-box
%h3.page-title Sign in
= text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
= password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
= button_tag "Sign in", class: "btn-create btn"
- page_title "Appearance" - page_title "Appearance"
%h3.page-title %h3.page-title
Appearance settings Appearance settings
%p.light %p.light
You can modify the look and feel of GitLab here You can modify the look and feel of GitLab here
%hr
= render 'form' = render 'form'
- page_title "Settings" - page_title "Settings"
%h3.page-title Settings %h3.page-title Settings
%hr %hr
= render 'form' = render 'form'
...@@ -88,28 +88,17 @@ ...@@ -88,28 +88,17 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr %hr
= button_tag 'Add users to group', class: "btn btn-create" = button_tag 'Add users to group', class: "btn btn-create"
= render 'shared/members/requests', membership_source: @group, members: @members.request
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h3.panel-title %strong= @group.name
Members group members
%span.badge %span.badge= @group.members.non_request.size
#{@group.group_members.count} .pull-right
%ul.well-list.group-users-list = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
- @members.each do |member| %ul.well-list.group-users-list.content-list
- user = member.user = render partial: 'shared/members/member', collection: @members.non_request, as: :member, locals: { show_controls: false }
%li{class: dom_class(member), id: (dom_id(user) if user)}
.list-item-name
- if user
%strong
= link_to user.name, admin_user_path(user)
- else
%strong
= member.invite_email
(invited)
%span.pull-right.light
= member.human_access
- if can?(current_user, :destroy_group_member, member)
= link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer .panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab' = paginate @members.non_request, param_name: 'members_page', theme: 'gitlab'
...@@ -135,44 +135,27 @@ ...@@ -135,44 +135,27 @@
- if @group - if @group
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%strong #{@group.name} %strong= @group.name
group members (#{@group.group_members.count}) group members
%span.badge= @group_members.non_request.size
.pull-right .pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do = link_to admin_group_path(@group), class: 'btn btn-xs' do
%i.fa.fa-pencil-square-o = icon('pencil-square-o', text: 'Manage Access')
%ul.well-list %ul.well-list.content-list
- @group_members.each do |member| = render partial: 'shared/members/member', collection: @group_members.non_request, as: :member, locals: { show_controls: false }
= render 'shared/members/member', member: member, show_controls: false
.panel-footer .panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' = paginate @group_members.non_request, param_name: 'group_members_page', theme: 'gitlab'
= render 'shared/members/requests', membership_source: @project, members: @project_members.request
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Project members %strong= @project.name
%small project members
(#{@project.users.count}) %span.badge= @project.users.size
.pull-right .pull-right
= link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
%i.fa.fa-pencil-square-o %ul.well-list.project_members.content-list
Manage Access = render partial: 'shared/members/member', collection: @project_members.non_request, as: :member, locals: { show_controls: false }
%ul.well-list.project_members
- @project_members.each do |project_member|
- user = project_member.user
%li.project_member
.list-item-name
- if user
%strong
= link_to user.name, admin_user_path(user)
- else
%strong
= project_member.invite_email
(invited)
.pull-right
- if project_member.owner?
%span.light Owner
- else
%span.light= project_member.human_access
= link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
%i.fa.fa-times
.panel-footer .panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' = paginate @project_members.non_request, param_name: 'project_members_page', theme: 'gitlab'
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.col-md-6 .col-md-6
%h4 Restrict projects for this runner %h4 Restrict projects for this runner
- if @runner.projects.any? - if @runner.projects.any?
%table.table %table.table.assigned-projects
%thead %thead
%tr %tr
%th Assigned projects %th Assigned projects
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
.pull-right .pull-right
= link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
%table.table %table.table.unassigned-projects
%thead %thead
%tr %tr
%th Project %th Project
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
Explore Projects Explore Projects
.nav-controls .nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2" = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
......
...@@ -17,8 +17,7 @@ ...@@ -17,8 +17,7 @@
.panel-heading .panel-heading
%strong #{@group.name} %strong #{@group.name}
group members group members
%small %span.badge= @members.non_request.size
(#{@members.total_count})
.controls .controls
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
= link_to "#shared", 'data-toggle' => 'tab' do = link_to "#shared", 'data-toggle' => 'tab' do
Shared Projects Shared Projects
.nav-controls .nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if can? current_user, :create_projects, @group - if can? current_user, :create_projects, @group
......
...@@ -28,8 +28,12 @@ ...@@ -28,8 +28,12 @@
.key &#8984; shift p .key &#8984; shift p
- else - else
.key ctrl shift p .key ctrl shift p
%td Toggle Markdown preview %td Toggle Markdown preview
%tr
%td.shortcut
.key
%i.fa.fa-arrow-up
%td Edit last comment (when focused on an empty textarea)
%tbody %tbody
%tr %tr
%th %th
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username .username
= current_user.username = current_user.username
= link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do = link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
%span.sr-only Toggle navigation pinning %span.sr-only Toggle navigation pinning
= icon('thumb-tack') = icon('thumb-tack')
- if defined?(nav) && nav - if defined?(nav) && nav
......
%ul.nav-links.scrolling-tabs %div{ class: nav_control_class }
.fade-left = render 'layouts/nav/admin_settings'
= nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span
Overview
= nav_link(controller: %w(background_jobs logs health_check)) do
= link_to admin_background_jobs_path, title: 'Monitoring' do
%span
Monitoring
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
%span
Deploy Keys
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do
%span
Messages
= nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks' do
%span
Hooks
= nav_link(controller: :appearances) do %ul.nav-links.scrolling-tabs
= link_to admin_appearances_path, title: 'Appearances' do %li.fade-left
%span = icon('arrow-left')
Appearance = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
= nav_link(controller: :applications) do %span
= link_to admin_applications_path, title: 'Applications' do Overview
%span = nav_link(controller: %w(background_jobs logs health_check)) do
Applications = link_to admin_background_jobs_path, title: 'Monitoring' do
%span
= nav_link(controller: :services) do Monitoring
= link_to admin_application_settings_services_path, title: 'Service Templates' do = nav_link(controller: :broadcast_messages) do
%span = link_to admin_broadcast_messages_path, title: 'Messages' do
Service Templates %span
Messages
= nav_link(controller: :labels) do = nav_link(controller: :hooks) do
= link_to admin_labels_path, title: 'Labels' do = link_to admin_hooks_path, title: 'Hooks' do
%span %span
Labels System Hooks
= nav_link(controller: :abuse_reports) do = nav_link(controller: :applications) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do = link_to admin_applications_path, title: 'Applications' do
%span %span
Abuse Reports Applications
%span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled? = nav_link(controller: :abuse_reports) do
= nav_link(controller: :spam_logs) do = link_to admin_abuse_reports_path, title: "Abuse Reports" do
= link_to admin_spam_logs_path, title: "Spam Logs" do
%span %span
Spam Logs Abuse Reports
%span.badge.count= number_with_delimiter(AbuseReport.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do - if askimet_enabled?
= link_to admin_application_settings_path, title: 'Settings' do = nav_link(controller: :spam_logs) do
%span = link_to admin_spam_logs_path, title: "Spam Logs" do
Settings %span
.fade-right Spam Logs
%li.fade-right
= icon('arrow-right')
.controls
.dropdown.admin-settings-dropdown
%a.dropdown-new.btn.btn-default{href: '#', 'data-toggle' => 'dropdown'}
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
%span
Deploy Keys
= nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do
%span
Service Templates
= nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels' do
%span
Labels
= nav_link(controller: :appearances) do
= link_to admin_appearances_path, title: 'Appearances' do
%span
Appearance
%li.divider
= nav_link(controller: :application_settings) do
= link_to admin_application_settings_path, title: 'Settings' do
%span
Settings
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
= render 'layouts/nav/group_settings' = render 'layouts/nav/group_settings'
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
.fade-left %li.fade-left
= icon('arrow-left')
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
%span %span
...@@ -31,4 +32,5 @@ ...@@ -31,4 +32,5 @@
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
%span %span
Members Members
.fade-right %li.fade-right
= icon('arrow-right')
- if current_user - if current_user
- if access = @group.users.find_by(id: current_user.id) - can_edit = can?(current_user, :admin_group, @group)
.controls - member = @group.members.non_request.find_by(user_id: current_user.id)
.dropdown.group-settings-dropdown - can_leave = member && can?(current_user, :destroy_group_member, member)
%a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
= icon('cog') .controls
= icon('caret-down') .dropdown.group-settings-dropdown
%ul.dropdown-menu.dropdown-menu-align-right %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
- if can?(current_user, :admin_group, @group) = icon('cog')
= nav_link(path: 'groups#projects') do = icon('caret-down')
= link_to projects_group_path(@group), title: 'Projects' do %ul.dropdown-menu.dropdown-menu-align-right
Projects = nav_link(path: 'groups#projects') do
%li.divider = link_to 'Projects', projects_group_path(@group), title: 'Projects'
%li %li.divider
= link_to edit_group_path(@group) do - if can_edit
Edit Group %li
= link_to 'Edit Group', edit_group_path(@group)
- if can_leave
%li
= link_to polymorphic_path([:leave, @group, :members]),
data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
Leave Group
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
.fade-left %li.fade-left
= icon('arrow-left')
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
%span %span
...@@ -43,4 +44,5 @@ ...@@ -43,4 +44,5 @@
= link_to audit_log_profile_path, title: 'Audit Log' do = link_to audit_log_profile_path, title: 'Audit Log' do
%span %span
Audit Log Audit Log
.fade-right %li.fade-right
= icon('arrow-right')
...@@ -5,19 +5,20 @@ ...@@ -5,19 +5,20 @@
= icon('cog') = icon('cog')
= icon('caret-down') = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
- is_project_member = @project.users.exists?(current_user.id)
- access = @project.team.max_member_access(current_user.id)
- can_edit = can?(current_user, :admin_project, @project) - can_edit = can?(current_user, :admin_project, @project)
-# We don't use @project.team.find_member because it searches for group members too...
- member = @project.members.non_request.find_by(user_id: current_user.id)
- can_leave = member && can?(current_user, :destroy_project_member, member)
= render 'layouts/nav/project_settings', access: access, can_edit: can_edit = render 'layouts/nav/project_settings', can_edit: can_edit
- if can_edit || is_project_member - if can_edit || can_leave
%li.divider %li.divider
- if can_edit - if can_edit
%li %li
= link_to edit_project_path(@project) do = link_to edit_project_path(@project) do
Edit Project Edit Project
- if is_project_member - if can_leave
%li %li
= link_to polymorphic_path([:leave, @project, :members]), = link_to polymorphic_path([:leave, @project, :members]),
data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do
...@@ -25,7 +26,8 @@ ...@@ -25,7 +26,8 @@
%div{ class: nav_control_class } %div{ class: nav_control_class }
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
.fade-left %li.fade-left
= icon('arrow-left')
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%span %span
...@@ -38,9 +40,9 @@ ...@@ -38,9 +40,9 @@
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
= link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span %span
Code Repository
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :environments]) do = nav_link(controller: [:pipelines, :builds, :environments]) do
...@@ -109,4 +111,5 @@ ...@@ -109,4 +111,5 @@
%li.hidden %li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits Commits
.fade-right %li.fade-right
= icon('arrow-right')
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
%span %span
Members Members
- if access && can_edit - if can_edit
- if @project.allowed_to_share_with_group? - if @project.allowed_to_share_with_group?
= nav_link(controller: :group_links) do = nav_link(controller: :group_links) do
= link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
%ul %ul
- @errors.each do |error| - @errors.each do |error|
%li %li
error #{error}
Project <%= @project.name %> couldn't be exported.
The errors we encountered were:
- @errors.each do |error|
<%= error %>
\ No newline at end of file
= "Project #{@project.name} couldn't be exported."
= "The errors we encountered were:"
- @errors.each do |error|
#{error}
\ No newline at end of file
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
= label_tag :global_notification_level, "Global notification level", class: "label-light" = label_tag :global_notification_level, "Global notification level", class: "label-light"
%br %br
.clearfix .clearfix
.form-group.pull-left .form-group.pull-left.global-notification-setting
= render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true
.clearfix .clearfix
......
...@@ -14,8 +14,17 @@ ...@@ -14,8 +14,17 @@
%span This is a confidential issue. Your comment will not be visible to the public. %span This is a confidential issue. Your comment will not be visible to the public.
%li.pull-right %li.pull-right
%button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } .toolbar-group
Go full screen = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
= markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" })
= markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
= markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" })
= markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
= markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
= markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
.toolbar-group
%button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
=icon("arrows-alt fw")
.md-write-holder .md-write-holder
= yield = yield
...@@ -24,7 +33,7 @@ ...@@ -24,7 +33,7 @@
- if defined?(referenced_users) && referenced_users - if defined?(referenced_users) && referenced_users
%div.referenced-users.hide %div.referenced-users.hide
%span %span
= icon('exclamation-triangle') = icon("exclamation-triangle")
You are about to add You are about to add
%strong %strong
%span.js-referenced-users-count 0 %span.js-referenced-users-count 0
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%b Builds badge &middot; %b Builds badge &middot;
= @build_badge.to_html = @build_badge.to_html
.pull-right .pull-right
= render 'shared/ref_switcher', destination: 'badges' = render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body .panel-body
.row .row
.col-md-2.text-center .col-md-2.text-center
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
= dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) = dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.hidden .gitignore-selector.js-gitignore-selector-wrap.hidden
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
= dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.encoding-selector .encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
%span.label.label-warning stuck %span.label.label-warning stuck
%p.commit-title %p.commit-title
- if commit_data = pipeline.commit_data - if commit = pipeline.commit
= link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message" = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
......
...@@ -10,29 +10,30 @@ ...@@ -10,29 +10,30 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= commit_author_avatar(commit, size: 36) = commit_author_avatar(commit, size: 36)
.commit-row-title .commit-info-block
%span.item-title .commit-row-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" %span.item-title
%span.commit-row-message.visible-xs-inline = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
&middot; %span.commit-row-message.visible-xs-inline
= commit.short_id &middot;
- if commit.status = commit.short_id
= render_commit_status(commit, cssclass: 'visible-xs-inline') - if commit.status
- if commit.description? = render_commit_status(commit, cssclass: 'visible-xs-inline')
%a.text-expander.hidden-xs.js-toggle-button ... - if commit.description?
%a.text-expander.hidden-xs.js-toggle-button ...
.commit-actions.hidden-xs .commit-actions.hidden-xs
- if commit.status - if commit.status
= render_commit_status(commit, cssclass: 'btn btn-transparent') = render_commit_status(commit, cssclass: 'btn btn-transparent')
= clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
- if commit.description? - if commit.description?
%pre.commit-row-description.js-toggle-content %pre.commit-row-description.js-toggle-content
= preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
.commit-row-info .commit-row-info
= commit_author_link(commit, avatar: false, size: 24) = commit_author_link(commit, avatar: false, size: 24)
authored authored
#{time_ago_with_tooltip(commit.committed_date)} #{time_ago_with_tooltip(commit.committed_date)}
.scrolling-tabs-container .scrolling-tabs-container
.nav-links.sub-nav.scrolling-tabs .nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) } %ul{ class: (container_class) }
.fade-left %li.fade-left
= icon('arrow-left')
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_files_path(@project) do = link_to project_files_path(@project) do
Files Files
...@@ -25,4 +26,5 @@ ...@@ -25,4 +26,5 @@
= nav_link(controller: [:tags, :releases]) do = nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do = link_to namespace_project_tags_path(@project.namespace, @project) do
Tags Tags
.fade-right %li.fade-right
= icon('arrow-right')
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= render "projects/commits/head" = render "projects/commits/head"
%div{ class: (container_class) } %div{ class: (container_class) }
.row-content-block.second-block.content-component-block .sub-header-block
Compare branches, tags or commit ranges. Compare branches, tags or commit ranges.
%br %br
Fill input field with commit id like Fill input field with commit id like
......
- @no_container = true
- page_title "#{params[:from]}...#{params[:to]}" - page_title "#{params[:from]}...#{params[:to]}"
= render "projects/commits/head" = render "projects/commits/head"
%div{ class: (container_class) }
.sub-header-block.no-bottom-space
= render "form"
.row-content-block - if @commits.present?
= render "form"
- if @commits.present?
.prepend-top-default
= render "projects/commits/commit_list" = render "projects/commits/commit_list"
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
- else - else
.light-well.prepend-top-default .light-well
.center .center
%h4 %h4
There isn't anything to compare. There isn't anything to compare.
%p.slead %p.slead
- if params[:to] == params[:from] - if params[:to] == params[:from]
%span.label-branch #{params[:from]} %span.label-branch #{params[:from]}
and and
%span.label-branch #{params[:to]} %span.label-branch #{params[:to]}
are the same. are the same.
- else - else
You'll need to use different branch names to get a valid comparison. You'll need to use different branch names to get a valid comparison.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment