Commit d2971315 authored by Felipe Artur's avatar Felipe Artur

Merge branch 'master' into issue_3359_3

parents 36d48120 bef4294c
......@@ -18,6 +18,7 @@ variables:
SIMPLECOV: "true"
USE_DB: "true"
USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
before_script:
- source ./scripts/prepare_build.sh
......@@ -134,6 +135,11 @@ spinach 9 10: *spinach-knapsack
image: "ruby:2.3"
only:
- master
cache:
key: "ruby-23"
paths:
- vendor/apt
- vendor/ruby
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
<<: *rspec-knapsack
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.10.0(unreleased)
- Add notifications dropdown for groups
v 8.10.0 (unreleased)
- Fix commit builds API, return all builds for all pipelines for given commit. !4849
- Replace Haml with Hamlit to make view rendering faster. !3666
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Display last commit of deleted branch in push events !4699 (winniehell)
- Add Sidekiq queue duration to transaction metrics.
- Let Workhorse serve format-patch diffs
- Make images fit to the size of the viewport !4810
- Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836
- Fix pagination when sorting by columns with lots of ties (like priority)
- Exclude email check from the standard health check
- Fix changing issue state columns in milestone view
- Add notification settings dropdown for groups
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- PipelinesFinder uses git cache data
- Check for conflicts with existing Project's wiki path when creating a new project.
- Remove unused front-end variable -> default_issues_tracker
- Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg)
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
- Add basic system information like memory and disk usage to the admin panel
v 8.9.3 (unreleased)
- MergeRequestDiff reload content use update_columns to avoid multiple YAML de/serializations
- Decreased min width of screen to 1280px for pinned sidebar
- Fix encrypted data backwards compatibility after upgrading attr_encrypted gem
- Update mobile button icons to be more inline with typical UI paradigms
- Fixes missing avatar on system notes. !4954
- Improve performance of obtaining the maximum access of a user in a project
v 8.9.2
- Fix visibility of snippets when searching.
- Fix an information disclosure when requesting access to a group containing private projects.
- Update omniauth-saml to 1.6.0 !4951
v 8.9.1
- Refactor labels documentation. !3347
- Eager load award emoji on notes. !4628
- Fix some CI wording in documentation. !4660
- Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720
- Add documentation for the export & import features. !4732
- Add some docs for Docker Registry configuration. !4738
- Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744
- Display group/project access requesters separately in the admin area. !4798
- Add documentation and examples for configuring cloud storage for registry images. !4812
- Clarifies documentation about artifact expiry. !4831
- Fix the Network graph links. !4832
- Fix MR-auto-close text added to description. !4836
- Add documentation for award emoji now that comments can be awarded with emojis. !4839
- Fix typo in export failure email. !4847
- Fix header vertical centering. !4170
- Fix subsequent SAML sign ins. !4718
- Set button label when picking an option from status dropdown. !4771
- Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775
- Handle external issues in IssueReferenceFilter. !4789
- Support for rendering/redacting multiple documents. !4828
- Update Todos documentation and screenshots to include new functionality. !4840
- Hide nav arrows by default. !4843
- Added bottom padding to label color suggestion link. !4845
- Use jQuery objects in ref dropdown. !4850
- Fix GitLab project import issues related to notes and builds. !4855
- Restrict header logo to 36px so it doesn't overflow. !4861
- Fix unwanted label unassignment. !4863
- Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869
- Restore old behavior around diff notes to outdated discussions. !4870
- Fix merge requests project settings help link anchor. !4873
- Fix 404 when accessing pipelines as guest user on public projects. !4881
- Remove width restriction for logo on sign-in page. !4888
- Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
- Apply selected value as label. !4886
- Fix temp file being deleted after the request while importing a GitLab project. !4894
- Fix pagination when sorting by columns with lots of ties (like priority)
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912
- Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915
- Remove duplicate 'New Page' button on edit wiki page
v 8.9.0
v 8.9.0 (unreleased)
- Fix group visibility form layout in application settings
- Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline
......@@ -15,7 +94,6 @@ v 8.9.0 (unreleased)
- Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI
- 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 an issue where note polling stopped working if a window was in the
background during a refresh.
......@@ -39,7 +117,6 @@ v 8.9.0 (unreleased)
- Implement a fair usage of shared runners
- Remove project notification settings associated with deleted 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
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages
......@@ -100,6 +177,7 @@ v 8.9.0 (unreleased)
- Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment
- Updated project creation page to match new UI #2542
- Cache project build count in sidebar nav
- Add milestone expire date to the right sidebar
- Manually mark a issue or merge request as a todo
......@@ -155,6 +233,10 @@ v 8.9.0 (unreleased)
- Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.6
- Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951
v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166
- Fix todos page throwing errors when you have a project pending deletion !4300
......@@ -285,6 +367,10 @@ v 8.8.0
- When creating a .gitignore file a dropdown with templates will be provided
- Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562
v 8.7.8
- Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951
v 8.7.7
- Fix import by `Any Git URL` broken if the URL contains a space
- Prevent unauthorized access to other projects build traces
......
......@@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.5.0'
gem 'omniauth-saml', '~> 1.6.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
......@@ -76,7 +76,7 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
gem "kaminari", "~> 0.17.0"
# HAML
gem "haml-rails", '~> 0.9.0'
gem 'hamlit', '~> 2.5'
# Files attachments
gem "carrierwave", '~> 0.10.0'
......@@ -91,6 +91,7 @@ gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1'
# for aws storage
gem "unf", '~> 0.1.4'
......@@ -234,7 +235,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
gem 'sentry-raven', '~> 1.1.0'
gem 'premailer-rails', '~> 1.9.0'
......@@ -346,3 +347,6 @@ gem "paranoia", "~> 2.0"
# Health check
gem 'health_check', '~> 1.5.1'
# System information
gem 'vmstat', '~> 2.1.0'
......@@ -243,6 +243,11 @@ GEM
fog-core (>= 1.39)
fog-json (>= 1.0)
ipaddress (>= 0.8)
fog-rackspace (0.1.1)
fog-core (>= 1.35)
fog-json (>= 1.0)
fog-xml (>= 0.1)
ipaddress (>= 0.8)
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
......@@ -277,7 +282,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.2.0)
gitlab_git (10.2.3)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -320,14 +325,10 @@ GEM
grape-entity (0.4.8)
activesupport
multi_json (>= 1.3.2)
haml (4.0.7)
hamlit (2.5.0)
temple (~> 0.7.6)
thor
tilt
haml-rails (0.9.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
......@@ -337,11 +338,6 @@ GEM
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
html2haml (2.0.0)
erubis (~> 2.7.0)
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4)
http_parser.rb (0.5.3)
httparty (0.13.7)
......@@ -468,9 +464,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-saml (1.5.0)
omniauth-saml (1.6.0)
omniauth (~> 1.3)
ruby-saml (~> 1.1, >= 1.1.1)
ruby-saml (~> 1.3)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
......@@ -631,9 +627,8 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.8.1)
ruby-saml (1.1.2)
ruby-saml (1.3.0)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby_parser (3.8.2)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
......@@ -665,7 +660,7 @@ GEM
activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3)
thor (~> 0.14)
sentry-raven (0.15.6)
sentry-raven (1.1.0)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.7.0)
......@@ -733,6 +728,7 @@ GEM
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.7)
term-ansicolor (1.3.2)
tins (~> 1.0)
test_after_commit (0.4.2)
......@@ -789,6 +785,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
vmstat (2.1.0)
warden (1.2.6)
rack (>= 1.0)
web-console (2.3.0)
......@@ -866,6 +863,7 @@ DEPENDENCIES
fog-google (~> 0.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.6.1)
foreman
fuubar (~> 2.0.0)
......@@ -882,7 +880,7 @@ DEPENDENCIES
gon (~> 6.0.1)
grape (~> 0.13.0)
grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0)
hamlit (~> 2.5)
health_check (~> 1.5.1)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
......@@ -920,7 +918,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.5.0)
omniauth-saml (~> 1.6.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
......@@ -960,7 +958,7 @@ DEPENDENCIES
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 0.15)
sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
......@@ -993,6 +991,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0)
virtus (~> 1.0.1)
vmstat (~> 2.1.0)
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
......
8.9.0-pre
8.10.0-pre
......@@ -50,7 +50,7 @@
#= require_directory ./ci
#= require_directory ./commit
#= require_directory ./extensions
#= require_directory ./lib
#= require_directory ./lib/utils
#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
......@@ -199,7 +199,6 @@ $ ->
$('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
$body.on "click", ".js-toggle-diff-comments", (e) ->
......@@ -261,7 +260,7 @@ $ ->
new Aside()
# Sidenav pinning
if $window.width() < 1440 and $.cookie('pin_nav') is 'true'
if $window.width() < 1280 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
......
......@@ -341,7 +341,9 @@ class @AwardsHandler
for emoji in frequentlyUsedEmojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
$('.emoji-menu-content')
.prepend(ul)
.prepend($('<h5>').text('Frequently used'))
@frequentEmojiBlockRendered = true
......@@ -356,7 +358,7 @@ class @AwardsHandler
if term
# Generate a search result block
h5 = $('<h5>').text('Search results').addClass('emoji-search')
h5 = $('<h5>').text('Search results')
found_emojis = @searchEmojis(term).show()
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
$('.emoji-menu-content ul, .emoji-menu-content h5').hide()
......
......@@ -19,6 +19,7 @@ class @TemplateSelector
data: @data,
filterable: true,
selectable: true,
toggleLabel: @toggleLabel,
search:
fields: ['name']
clicked: @onClick
......@@ -31,6 +32,9 @@ class @TemplateSelector
@onFilenameUpdate()
)
toggleLabel: (item) ->
item.name
onFilenameUpdate: ->
return unless @$input.length
......
......@@ -186,6 +186,8 @@ class GitLabDropdown
@fullData = data
@parseData @fullData
@filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
}
# Init filterable
......@@ -280,7 +282,7 @@ class GitLabDropdown
html = @renderData(data)
# Render the full menu
full_html = @renderMenu(html.join(""))
full_html = @renderMenu(html)
@appendMenu(full_html)
......@@ -351,7 +353,8 @@ class GitLabDropdown
if @options.renderMenu
menu_html = @options.renderMenu(html)
else
menu_html = "<ul>#{html}</ul>"
menu_html = $('<ul />')
.append(html)
return menu_html
......@@ -360,7 +363,9 @@ class GitLabDropdown
selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content"
$(selector, @dropdown).html html
$(selector, @dropdown)
.empty()
.append(html)
# Render the row
renderItem: (data, group = false, index = false) ->
......@@ -459,7 +464,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
@updateLabel()
@updateLabel(selectedObject, el, @)
else
selectedObject
else if el.hasClass(INDETERMINATE_CLASS)
......@@ -486,7 +491,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
@updateLabel(selectedObject, el)
@updateLabel(selectedObject, el, @)
if value?
if !field.length and fieldName
@addInput(fieldName, value)
......@@ -585,8 +590,8 @@ class GitLabDropdown
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
updateLabel: (selected = null, el = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el)
updateLabel: (selected = null, el = null, instance = null) =>
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
$.fn.glDropdown = (opts) ->
return @.each ->
......
......@@ -4,5 +4,4 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require Chart
#= require_tree .
......@@ -59,21 +59,23 @@ issuable_created = false
filterResults: (form) =>
formData = form.serialize()
$('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
formAction = form.attr('action')
issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData
Turbolinks.visit(issuesUrl);
Turbolinks.visit(issuesUrl)
initChecks: ->
@issuableBulkActions = $('.bulk-update').data('bulkActions')
$('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked)
Issuable.checkChanged()
)
$('.selected_issue').off('change').on('change', Issuable.checkChanged)
$('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
checkChanged: ->
checked_issues = $('.selected_issue:checked')
......@@ -88,3 +90,6 @@ issuable_created = false
$('#update_issues_ids').val []
$('.issues_bulk_update').hide()
$('.issues-other-filters').show()
@issuableBulkActions.willUpdateLabels = false
return true
......@@ -99,7 +99,7 @@ class @Issue
# If the user doesn't have the required permissions the container isn't
# rendered at all.
return unless $container
return if $container.length is 0
$.getJSON($container.data('path'))
.error ->
......
......@@ -6,6 +6,13 @@ class @IssueStatusSelect
$(el).glDropdown(
selectable: true
fieldName: fieldName
toggleLabel: (selected, el, instance) =>
label = 'Author'
$item = instance.dropdown.find('.is-active')
label = $item.text() if $item.length
label
clicked: (item, $el, e)->
e.preventDefault()
id: (obj, el) ->
$(el).data("id")
)
......@@ -7,6 +7,11 @@ class @IssuableBulkActions
@issues = @getElement('.issues-list .issue')
} = opts
# Save instance
@form.data 'bulkActions', @
@willUpdateLabels = false
@bindEvents()
# Fixes bulk-assign not working when navigating through pages
......@@ -87,6 +92,7 @@ class @IssuableBulkActions
add_label_ids : []
remove_label_ids : []
if @willUpdateLabels
@getLabelsToApply().map (id) ->
formData.update.add_label_ids.push id
......
......@@ -319,6 +319,8 @@ class @LabelsSelect
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
_this.enableBulkLabelDropdown()
if $dropdown.hasClass('js-filter-bulk-update')
return
......@@ -377,3 +379,8 @@ class @LabelsSelect
label_ids.push $("#issue_#{issue_id}").data('labels')
_.intersection.apply _, label_ids
enableBulkLabelDropdown: ->
if $('.selected_issue:checked').length
issuableBulkActions = $('.bulk-update').data('bulkActions')
issuableBulkActions.willUpdateLabels = true
......@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) ->
$this = $(@)
$this
.find('.fade-right')
.toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth'))
.siblings('.fade-right')
.toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
$ ->
$('.fade-left').addClass('end-scroll')
hideEndFade($('.scrolling-tabs'))
......@@ -21,5 +20,5 @@ $ ->
currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
$this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
$this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
#= require raphael
#= require g.raphael
#= require g.bar
......@@ -10,11 +10,35 @@
gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) ->
gl.text.lineBefore = (text, textarea) ->
split = text.substring(0, textarea.selectionStart).trim().split('\n')
split[split.length - 1]
gl.text.lineAfter = (text, textarea) ->
text.substring(textarea.selectionEnd).trim().split('\n')[0]
gl.text.blockTagText = (text, textArea, blockTag, selected) ->
lineBefore = @lineBefore(text, textArea)
lineAfter = @lineAfter(text, textArea)
if lineBefore is blockTag and lineAfter is blockTag
# To remove the block tag we have to select the line before & after
if blockTag?
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1)
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1)
selected
else
"#{blockTag}\n#{selected}\n#{blockTag}"
gl.text.insertText = (textArea, text, tag, blockTag, selected, wrap) ->
selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap
if selectedSplit.length > 1 and (not wrap or blockTag?)
if blockTag?
insertText = @blockTagText(text, textArea, blockTag, selected)
else
insertText = selectedSplit.map((val) ->
if val.indexOf(tag) is 0
"#{val.replace(tag, '')}"
......@@ -51,7 +75,7 @@
textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) ->
gl.text.updateText = (textArea, tag, blockTag, wrap) ->
$textArea = $(textArea)
oldVal = $textArea.val()
textArea = $textArea.get(0)
......@@ -59,7 +83,7 @@
selected = @selectedText(text, textArea)
$textArea.focus()
@insertText(textArea, text, tag, selected, wrap)
@insertText(textArea, text, tag, blockTag, selected, wrap)
gl.text.init = (form) ->
self = @
......@@ -70,6 +94,7 @@
self.updateText(
$this.closest('.md-area').find('textarea'),
$this.data('md-tag'),
$this.data('md-block'),
not $this.data('md-prepend')
)
......
......@@ -4,17 +4,9 @@ class @Milestone
type: "PUT"
url: issue_url
data: data
success: (data) ->
if data.saved == true
if data.assignee_avatar_url
img_tag = $('<img/>')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
else
success: (_data) =>
@successCallback(_data, li)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
......@@ -25,8 +17,9 @@ class @Milestone
type: "PUT"
url: sort_issues_url
data: data
success: (data) ->
if data.saved != true
success: (_data) =>
@successCallback(_data)
error: ->
new Flash("Issues update failed", 'alert')
dataType: "json"
......@@ -37,9 +30,10 @@ class @Milestone
type: "PUT"
url: sort_mr_url
data: data
success: (data) ->
if data.saved != true
new Flash("MR update failed", 'alert')
success: (_data) =>
@successCallback(_data)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@updateMergeRequest: (li, merge_request_url, data) ->
......@@ -47,19 +41,22 @@ class @Milestone
type: "PUT"
url: merge_request_url
data: data
success: (data) ->
if data.saved == true
if data.assignee_avatar_url
success: (_data) =>
@successCallback(_data, li)
error: (data) ->
new Flash("Issue update failed", 'alert')
dataType: "json"
@successCallback: (data, element) =>
if data.assignee
img_tag = $('<img/>')
img_tag.attr('src', data.assignee_avatar_url)
img_tag.attr('src', data.assignee.avatar_url)
img_tag.addClass('avatar s16')
$(li).find('.assignee-icon').html(img_tag)
else
$(li).find('.assignee-icon').html('')
$(li).effect 'highlight'
$(element).find('.assignee-icon').html(img_tag)
else
new Flash("Issue update failed", 'alert')
dataType: "json"
$(element).find('.assignee-icon').html('')
$(element).effect 'highlight'
constructor: ->
oldMouseStart = $.ui.sortable.prototype._mouseStart
......@@ -81,6 +78,8 @@ class @Milestone
stop: (event, ui) ->
$(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) ->
# Prevents sorting from container which element has been removed.
if $(this).find(ui.item).length > 0
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
......
......@@ -4,9 +4,6 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree .
$ ->
......
......@@ -70,17 +70,20 @@ class @Project
fieldName: 'ref'
renderRow: (ref) ->
if ref.header?
"<li class='dropdown-header'>#{ref.header}</li>"
$('<li />')
.addClass('dropdown-header')
.text(ref.header)
else
isActiveClass = if ref is selected then 'is-active' else ''
"<li>
<a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'>
#{ref}
</a>
</li>"
link = $('<a />')
.attr('href', '#')
.addClass(if ref is selected then 'is-active' else '')
.text(ref)
.attr('data-ref', escape(ref))
$('<li />')
.append(link)
id: (obj, $el) ->
$el.data('ref')
$el.attr('data-ref')
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e) ->
......
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require d3
#= require_tree .
......@@ -26,7 +26,6 @@ header {
text-align: center;
#tanuki-logo, img {
width: 36px;
height: 36px;
}
}
......@@ -132,6 +131,10 @@ header {
transition-duration: .3s;
z-index: 999;
svg, img {
height: 36px;
}
&:hover {
cursor: pointer;
}
......
@mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible;
opacity: 1;
visibility: hidden;
opacity: 0;
z-index: 2;
position: absolute;
bottom: 12px;
......@@ -13,17 +13,16 @@
background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
&.end-scroll {
visibility: hidden;
opacity: 0;
&.scrolling {
visibility: visible;
opacity: 1;
transition-duration: .3s;
}
.fa {
position: relative;
top: 3px;
font-size: 13px;
color: $btn-placeholder-gray;
top: 5px;
font-size: 18px;
}
}
......@@ -32,6 +31,7 @@
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
}
......@@ -272,7 +272,7 @@
float: right;
padding: 7px 0 0;
@media (max-width: $screen-xs-max) {
@media (max-width: $screen-sm-max) {
display: none;
}
......@@ -303,41 +303,9 @@
}
.nav-links {
@include scrolling-links();
border-bottom: none;
height: 51px;
svg {
position: relative;
top: 2px;
margin-right: 2px;
height: 15px;
width: auto;
path,
polygon {
fill: $layout-link-gray;
}
}
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0;
.fa {
left: -7px;
}
}
li {
a {
......@@ -373,18 +341,6 @@
}
}
}
.nav-control {
.fade-right {
@media (min-width: $screen-xs-max) {
right: 68px;
}
@media (max-width: $screen-xs-min) {
right: 0;
}
}
}
}
.scrolling-tabs-container {
......@@ -392,15 +348,42 @@
.nav-links {
@include scrolling-links();
}
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: 0;
right: -5px;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: -5px;
.fa {
left: -7px;
}
}
&.sub-nav-scroll {
.fade-right {
right: 0;
.fa {
right: -23px;
}
}
.fade-left {
left: 0;
.fa {
left: 10px;
}
}
}
}
......@@ -413,21 +396,19 @@
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $white-light);
right: 0;
right: -5px;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $white-light);
left: 0;
}
&.event-filter {
.fade-right {
visibility: hidden;
left: -5px;
@media (max-width: $screen-xs-max) {
visibility: visible;
}
.fa {
left: -7px;
}
}
}
......
.page-with-sidebar {
padding-top: $header-height;
padding-bottom: 25px;
transition: padding $sidebar-transition-duration;
.sidebar-wrapper {
......
......@@ -7,7 +7,7 @@ $gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1440px;
$sidebar-breakpoint: 1280px;
/*
* UI elements
......
......@@ -8,8 +8,9 @@
.emoji-menu {
position: absolute;
margin-top: 3px;
z-index: 1000;
min-width: 160px;
padding: $gl-padding;
z-index: 9;
width: 300px;
font-size: 14px;
background-color: $award-emoji-menu-bg;
border: 1px solid $award-emoji-menu-border;
......@@ -33,18 +34,16 @@
}
.emoji-menu-content {
padding: $gl-padding;
width: 300px;
height: 300px;
overflow-y: scroll;
}
}
input.emoji-search {
.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
}
}
}
.emoji-menu-list {
......
......@@ -63,5 +63,6 @@
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
max-height: calc(100vh - 100px);
}
}
......@@ -10,6 +10,7 @@
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
max-height: calc(100vh - 100px);
}
}
......
......@@ -6,6 +6,7 @@
height: 30px;
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
}
&.suggest-colors-dropdown {
......
......@@ -264,8 +264,15 @@
margin-bottom: 4px;
}
.item-title {
@media (min-width: $screen-sm-min) {
width: 49%;
}
}
.avatar {
margin-left: 0;
left: 0;
top: 2px;
}
.commit-row-info {
......
......@@ -41,6 +41,10 @@ ul.notes {
.timeline-icon {
.avatar {
visibility: hidden;
.discussion-body & {
visibility: visible;
}
}
}
}
......@@ -113,6 +117,7 @@ ul.notes {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px 0;
max-height: calc(100vh - 100px);
}
}
}
......
......@@ -13,10 +13,53 @@
.new_project,
.edit-project {
fieldset.features {
.control-label {
fieldset {
&.features .control-label {
font-weight: normal;
}
.form-group {
margin-bottom: 5px;
}
&> .form-group {
padding-left: 0;
}
}
.help-block {
margin-bottom: 10px;
}
.project-path {
padding-right: 0;
.form-control {
border-radius: $border-radius-base;
}
}
.input-group > div {
&:last-child {
padding-right: 0;
}
}
@media (max-width: $screen-xs-max) {
.input-group > div {
margin-bottom: 14px;
&:last-child {
margin-bottom: 0;
}
}
fieldset > .form-group:first-child {
padding-right: 0;
}
}
.input-group-addon {
&.static-namespace {
height: 35px;
border-radius: 3px;
border: 1px solid #e5e5e5;
}
&+ .select2 a {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
......@@ -365,10 +408,28 @@ a.deploy-project-label {
}
}
.project-import .btn {
float: left;
margin-bottom: 10px;
.project-import {
.form-group {
margin-bottom: 0;
}
.import-buttons {
padding-left: 0;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
.btn {
margin-right: 10px;
padding: 8px 12px;
}
&> div {
margin-bottom: 14px;
padding-left: 0;
&:last-child {
margin-bottom: 0;
}
}
}
}
.project-stats {
......
class Admin::SystemInfoController < Admin::ApplicationController
def show
system_info = Vmstat.snapshot
@cpus = system_info.cpus.length
@mem_used = system_info.memory.active_bytes
@mem_total = system_info.memory.total_bytes
@disk_used = system_info.disks[0].used_bytes
@disk_total = system_info.disks[0].total_bytes
end
end
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
@group_members = current_user.group_members.page(params[:page])
@group_members = current_user.group_members.includes(:source).page(params[:page])
end
end
......@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
imported_file = project_params[:file].path + "-import"
FileUtils.copy_entry(project_params[:file].path, imported_file)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
File.expand_path(project_params[:file].path),
File.expand_path(imported_file),
project_params[:path]).execute
if @project.saved?
......
......@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
def new
commit unless @repository.empty?
......@@ -146,4 +147,10 @@ class Projects::BlobController < Projects::ApplicationController
file_content_encoding: params[:encoding]
}
end
def validate_diff_params
if [:since, :to, :offset].any? { |key| params[key].blank? }
render nothing: true
end
end
end
......@@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie!
@grouped_diff_notes = commit.notes.grouped_diff_notes
@notes = commit.notes.non_diff_notes.fresh
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten + @notes,
@project,
current_user,
)
@note = @project.build_commit_note(commit)
@notes = commit.notes.non_diff_notes.fresh
@noteable = @commit
@comments_target = {
noteable_type: 'Commit',
......
......@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
raw_notes = @issue.notes_with_associations.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_to do |format|
......@@ -111,6 +115,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :edit
end
end
format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
......
......@@ -59,7 +59,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json { render json: @merge_request }
format.patch { render text: @merge_request.to_patch }
format.patch do
headers.store(*Gitlab::Workhorse.send_git_patch(@project.repository,
@merge_request.diff_base_commit.id,
@merge_request.last_commit.id))
headers['Content-Disposition'] = 'inline'
head :ok
end
format.diff do
return render_404 unless @merge_request.diff_refs
......@@ -85,6 +91,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format|
format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
......@@ -190,7 +205,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
unless @merge_request.mergeable?
# Disable the CI check if merge_when_build_succeeds is enabled since we have
# to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
@status = :failed
return
end
......@@ -205,7 +222,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present?
if @merge_request.pipeline && @merge_request.pipeline.active?
unless @merge_request.pipeline
@status = :failed
return
end
if @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
......@@ -320,8 +342,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@discussions = @notes.discussions
@discussions = @merge_request.mr_and_commit_notes.
inc_author_project_award_emoji.
fresh.
discussions
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
@noteable = @merge_request
# Get commits from repository
......@@ -368,4 +403,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ensure_ref_fetched
@merge_request.ensure_ref_fetched
end
def merge_when_build_succeeds_active?
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
end
......@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController
def create
@note = Notes::CreateService.new(project, current_user, note_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
......@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
......@@ -118,6 +126,8 @@ class Projects::NotesController < Projects::ApplicationController
name: note.name
}
elsif note.valid?
Banzai::NoteRenderer.render([note], @project, current_user)
{
valid: true,
id: note.id,
......
......@@ -29,10 +29,10 @@ class PipelinesFinder
end
def branches
project.repository.branches.map(&:name)
project.repository.branch_names
end
def tags
project.repository.tags.map(&:name)
project.repository.tag_names
end
end
......@@ -197,7 +197,7 @@ module ApplicationHelper
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content))
Hamlit::RailsHelpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
elsif plain?(file_name)
......
module BlobHelper
def highlighter(blob_name, blob_content, nowrap: false)
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
def highlighter(blob_name, blob_content, repository: nil, nowrap: false)
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository)
end
def highlight(blob_name, blob_content, nowrap: false, plain: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain)
def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository)
end
def no_highlight_files
......
module JavascriptHelper
def page_specific_javascripts(js = nil)
@page_specific_javascripts = js unless js.nil?
@page_specific_javascripts
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js), { "data-turbolinks-track" => true }
end
end
......@@ -196,7 +196,8 @@ class Ability
@public_project_rules ||= project_guest_rules + [
:download_code,
:fork_project,
:read_commit_status
:read_commit_status,
:read_pipeline
]
end
......
......@@ -163,13 +163,26 @@ module Ci
end
def skip_ci?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
end
def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
# Manually set the notes for a Ci::Pipeline
# There is no ActiveRecord relation between Ci::Pipeline and notes
# as they are related to a commit sha. This method helps importing
# them using the +Gitlab::ImportExport::RelationFactory+ class.
def notes=(notes)
notes.each do |note|
note[:id] = nil
note[:commit_id] = sha
note[:noteable_id] = self['id']
note.save!
end
end
def notes
Note.for_commit_id(sha)
end
......
......@@ -13,6 +13,7 @@ module Ci
attr_encrypted :value,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
end
......
......@@ -2,10 +2,11 @@ module Awardable
extend ActiveSupport::Concern
included do
has_many :award_emoji, as: :awardable, dependent: :destroy
has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy
if self < Participable
participant :award_emoji_with_associations
# By default we always load award_emoji user association
participant :award_emoji
end
end
......@@ -34,12 +35,9 @@ module Awardable
end
end
def award_emoji_with_associations
award_emoji.includes(:user)
end
def grouped_awards(with_thumbs: true)
awards = award_emoji_with_associations.group_by(&:name)
# By default we always load award_emoji user association
awards = award_emoji.group_by(&:name)
if with_thumbs
awards[AwardEmoji::UPVOTE_NAME] ||= []
......
......@@ -19,9 +19,14 @@ module Issuable
belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy do
def authors_loaded?
# We check first if we're loaded to not load unnecesarily.
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? }
end
def award_emojis_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:award_emoji).loaded? }
end
end
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
......@@ -49,7 +54,7 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :inc_notes_with_associations, -> { includes(notes: :author) }
scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
......@@ -112,7 +117,7 @@ module Issuable
end
def sort(method, excluded_labels: [])
case method.to_s
sorted = case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc
......@@ -121,6 +126,9 @@ module Issuable
else
order_by(method)
end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
end
def order_labels_priority(excluded_labels: [])
......@@ -257,7 +265,14 @@ module Issuable
# already have their authors loaded (possibly because the scope
# `inc_notes_with_associations` was used) and skip the inclusion if that's
# the case.
notes.authors_loaded? ? notes : notes.includes(:author)
includes = []
includes << :author unless notes.authors_loaded?
includes << :award_emoji unless notes.award_emojis_loaded?
if includes.any?
notes.includes(includes)
else
notes
end
end
def updated_tasks
......
......@@ -315,7 +315,7 @@ class Event < ActiveRecord::Base
def body?
if push?
push_with_commits?
push_with_commits? || rm_ref?
elsif note?
true
else
......
......@@ -11,7 +11,7 @@ class Group < Namespace
has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members
has_many :owners,
-> { where(members: { access_level: Gitlab::Access::OWNER }) },
-> { where(members: { requested_at: nil, access_level: Gitlab::Access::OWNER }) },
through: :group_members,
source: :user
......
......@@ -20,7 +20,7 @@ class LegacyDiffNote < Note
end
def discussion_id
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end
def diff_file_hash
......
......@@ -32,6 +32,7 @@ class Member < ActiveRecord::Base
scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
scope :non_pending, -> { non_request.non_invite }
scope :has_access, -> { where('access_level > 0') }
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
......
......@@ -264,19 +264,19 @@ class MergeRequest < ActiveRecord::Base
self.title.sub(WIP_REGEX, "")
end
def mergeable?
return false unless mergeable_state?
def mergeable?(skip_ci_check: false)
return false unless mergeable_state?(skip_ci_check: skip_ci_check)
check_if_can_be_merged
can_be_merged?
end
def mergeable_state?
def mergeable_state?(skip_ci_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
return false unless mergeable_ci_state?
return false unless skip_ci_check || mergeable_ci_state?
true
end
......@@ -319,13 +319,6 @@ class MergeRequest < ActiveRecord::Base
)
end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
def to_patch
target_project.repository.format_patch(diff_base_commit.sha, source_sha)
end
def hook_attrs
attrs = {
source: source_project.try(:hook_attrs),
......
......@@ -108,44 +108,46 @@ class MergeRequestDiff < ActiveRecord::Base
# Reload all commits related to current merge request from repo
# and save it as array of hashes in st_commits db field
def reload_commits
new_attributes = {}
commit_objects = unmerged_commits
if commit_objects.present?
self.st_commits = dump_commits(commit_objects)
new_attributes[:st_commits] = dump_commits(commit_objects)
end
save
update_columns_serialized(new_attributes)
end
# Reload diffs between branches related to current merge request from repo
# and save it as array of hashes in st_diffs db field
def reload_diffs
new_attributes = {}
new_diffs = []
if commits.size.zero?
self.state = :empty
new_attributes[:state] = :empty
else
diff_collection = unmerged_diffs
if diff_collection.overflow?
# Set our state to 'overflow' to make the #empty? and #collected?
# methods (generated by StateMachine) return false.
self.state = :overflow
new_attributes[:state] = :overflow
end
self.real_size = diff_collection.real_size
new_attributes[:real_size] = diff_collection.real_size
if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
self.state = :collected
new_attributes[:state] = :collected
end
end
self.st_diffs = new_diffs
self.base_commit_sha = self.repository.merge_base(self.head, self.base)
new_attributes[:st_diffs] = new_diffs
new_attributes[:base_commit_sha] = self.repository.merge_base(self.head, self.base)
self.save
update_columns_serialized(new_attributes)
end
# Collect array of Git::Diff objects
......@@ -190,4 +192,29 @@ class MergeRequestDiff < ActiveRecord::Base
)
end
end
private
#
# #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance.
# Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide.
# As a tradeoff we need to reload the current instance to properly manage time objects on those serialized
# attributes. So to keep the same behaviour as the attribute assignment we reload the instance.
# The difference is in the usage of
# #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns)
#
# Ex:
#
# new_attributes[:st_commits].first.slice(:committed_date)
# => {:committed_date=>2014-02-27 11:01:38 +0200}
# YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date)))
# => {:committed_date=>2014-02-27 10:01:38 +0100}
#
def update_columns_serialized(new_attributes)
return unless new_attributes.any?
update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone))
reload
end
end
......@@ -6,6 +6,10 @@ class Note < ActiveRecord::Base
include Awardable
include Importable
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
attr_accessor :note_html
default_value_for :system, false
attr_mentionable :note, pipeline: :note
......@@ -49,11 +53,13 @@ class Note < ActiveRecord::Base
scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
scope :inc_author_project_award_emoji, ->{ includes(:project, :author, :award_emoji) }
scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do
# FYI noteable cannot be loaded for LegacyDiffNote for commits
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
......
......@@ -81,6 +81,7 @@ class Project < ActiveRecord::Base
has_one :jira_service, dependent: :destroy
has_one :redmine_service, dependent: :destroy
has_one :custom_issue_tracker_service, dependent: :destroy
has_one :bugzilla_service, dependent: :destroy
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy
......@@ -163,6 +164,7 @@ class Project < ActiveRecord::Base
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
validate :visibility_level_allowed_by_group
validate :visibility_level_allowed_as_fork
validate :check_wiki_path_conflict
add_authentication_token_field :runners_token
before_save :ensure_runners_token
......@@ -539,6 +541,16 @@ class Project < ActiveRecord::Base
self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
end
def check_wiki_path_conflict
return if path.blank?
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
errors.add(:name, 'has already been taken')
end
end
def to_param
path
end
......
......@@ -7,6 +7,7 @@ class ProjectImportData < ActiveRecord::Base
marshal: true,
encode: true,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
algorithm: 'aes-256-cbc'
serialize :data, JSON
......
class BugzillaService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title
if self.properties && self.properties['title'].present?
self.properties['title']
else
'Bugzilla'
end
end
def description
if self.properties && self.properties['description'].present?
self.properties['description']
else
'Bugzilla issue tracker'
end
end
def to_param
'bugzilla'
end
end
......@@ -32,7 +32,4 @@ class CustomIssueTrackerService < IssueTrackerService
]
end
def initialize_properties
self.properties = {} if properties.nil?
end
end
......@@ -137,20 +137,10 @@ class ProjectTeam
def max_member_access(user_id)
access = []
project.members.non_request.each do |member|
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
access += project.members.non_request.where(user_id: user_id).has_access.pluck(:access_level)
if group
group.members.non_request.each do |member|
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
access += group.members.non_request.where(user_id: user_id).has_access.pluck(:access_level)
end
if project.invited_groups.any? && project.allowed_to_share_with_group?
......
......@@ -130,7 +130,7 @@ class Repository
end
def find_tag(name)
raw_repository.tags.find { |tag| tag.name == name }
tags.find { |tag| tag.name == name }
end
def add_branch(user, branch_name, target)
......@@ -978,6 +978,10 @@ class Repository
raw_repository.ls_files(actual_ref)
end
def gitattribute(path, name)
raw_repository.attributes(path)[name]
end
def copy_gitattributes(ref)
actual_ref = ref || root_ref
begin
......
......@@ -170,6 +170,7 @@ class Service < ActiveRecord::Base
bamboo
buildkite
builds_email
bugzilla
campfire
custom_issue_tracker
drone_ci
......
......@@ -20,6 +20,7 @@ class Snippet < ActiveRecord::Base
length: { within: 0..255 },
format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
......@@ -81,6 +82,11 @@ class Snippet < ActiveRecord::Base
0
end
# alias for compatibility with blobs and highlighting
def path
file_name
end
def name
file_name
end
......@@ -135,7 +141,16 @@ class Snippet < ActiveRecord::Base
end
def accessible_to(user)
where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user)
return are_public unless user.present?
return all if user.admin?
where(
'visibility_level IN (:visibility_levels)
OR author_id = :author_id
OR project_id IN (:project_ids)',
visibility_levels: [Snippet::PUBLIC, Snippet::INTERNAL],
author_id: user.id,
project_ids: user.authorized_projects.select(:id))
end
end
end
......@@ -25,6 +25,7 @@ class User < ActiveRecord::Base
attr_encrypted :otp_secret,
key: Gitlab::Application.config.secret_key_base,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
algorithm: 'aes-256-cbc'
devise :two_factor_authenticatable,
......@@ -57,7 +58,7 @@ class User < ActiveRecord::Base
# Groups
has_many :members, dependent: :destroy
has_many :group_members, dependent: :destroy, source: 'GroupMember'
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember'
has_many :groups, through: :group_members
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
......@@ -65,7 +66,7 @@ class User < ActiveRecord::Base
# Projects
has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects
has_many :project_members, dependent: :destroy, class_name: 'ProjectMember'
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, class_name: 'ProjectMember'
has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy
......@@ -308,7 +309,7 @@ class User < ActiveRecord::Base
def generate_password
if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8)
self.password = self.password_confirmation = Devise.friendly_token.first(Devise.password_length.min)
end
end
......
......@@ -159,8 +159,9 @@ class TodoService
def create_todos(users, attributes)
Array(users).map do |user|
next if pending_todos(user, attributes).exists?
Todo.create(attributes.merge(user_id: user.id))
todo = Todo.create(attributes.merge(user_id: user.id))
user.update_todos_count_cache
todo
end
end
......
......@@ -15,7 +15,7 @@
= f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10
= render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
.form-group.group-visibility-level-holder
.form-group.project-visibility-level-holder
= f.label :default_group_visibility, class: 'control-label col-sm-2'
.col-sm-10
= render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
......
.nav-links.sub-nav
%ul{ class: (container_class) }
= nav_link(controller: :system_info) do
= link_to admin_system_info_path, title: 'System Info' do
%span
System Info
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
%span
......
- @no_container = true
- page_title "System Info"
= render 'admin/background_jobs/head'
%div{ class: (container_class) }
.prepend-top-default
.row
.col-sm-4
.light-well
%h4 CPU
.data
%h1= "#{@cpus} cores"
.col-sm-4
.light-well
%h4 Memory
.data
%h1= "#{number_to_human_size(@mem_used)} / #{number_to_human_size(@mem_total)}"
.col-sm-4
.light-well
%h4 Disk
.data
%h1= "#{number_to_human_size(@disk_used)} / #{number_to_human_size(@disk_total)}"
- page_title "Groups", @user.name, "Users"
= render 'admin/users/head'
- if @user.group_members.present?
- group_members = @user.group_members.includes(:source)
- if group_members.any?
.panel.panel-default
.panel-heading Groups:
%ul.well-list
- @user.group_members.each do |group_member|
- group_members.each do |group_member|
- group = group_member.group
%li.group_member
%span{class: ("list-item-name" unless group_member.owner?)}
......
.bs-callout.help-callout
%h4 How to setup CI for this project
%ol
%li
Add at least one runner to the project.
Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
%li
Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
#{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
%li
Return to this page and refresh it, it should show a new build.
.alert.alert-danger
%p
Now you need Runners to process your builds.
%span
Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
.emoji-menu
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Seach emojis"
.emoji-menu-content
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
- Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
%h5.emoji-menu-title
= Gitlab::AwardEmoji::CATEGORIES[category]
......
- project = event.project
.event-title
%span.author_name= link_to_author event
%span.event_label.pushed #{event.action_name} #{event.ref_type}
......@@ -5,19 +7,18 @@
%strong= event.ref_name
- else
%strong
= link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), title: h(event.target_title)
= link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title)
at
= link_to_project event.project
= link_to_project project
- if event.push_with_commits?
- project = event.project
.event-body
%ul.well-list.event_commits
- few_commits = event.commits[0...2]
- few_commits.each do |commit|
= render "events/commit", commit: commit, project: project, event: event
- create_mr = event.new_ref? && create_mr_button?(event.project.default_branch, event.ref_name, event.project)
- create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project)
- if event.commits_count > 1
%li.commits-stat
- if event.commits_count > 2
......@@ -27,18 +28,26 @@
- from = event.commit_from
- from_label = truncate_sha(from)
- else
- from = event.project.default_branch
- from = project.default_branch
- from_label = from
= link_to namespace_project_compare_path(event.project.namespace, event.project, from: from, to: event.commit_to) do
= link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do
Compare #{from_label}...#{truncate_sha(event.commit_to)}
- if create_mr
%span{"data-user-is" => event.author_id, "data-display" => "inline"}
or
= link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do
= link_to create_mr_path(project.default_branch, event.ref_name, project) do
create a merge request
- elsif create_mr
%li.commits-stat{"data-user-is" => event.author_id}
= link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do
= link_to create_mr_path(project.default_branch, event.ref_name, project) do
Create Merge Request
- elsif event.rm_ref?
- repository = project.repository
- last_commit = repository.commit(event.commit_from)
- if last_commit
.event-body
%ul.well-list.event_commits
= render "events/commit", commit: last_commit, project: project, event: event
......@@ -30,8 +30,8 @@
= javascript_include_tag "application"
- if page_specific_javascripts
= javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true}
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
= csrf_meta_tags
......
- if current_user && current_user.is_admin? && Ci::Runner.count.zero?
= render 'ci/shared/no_runners'
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
- if defined?(sidebar) && sidebar
= render "layouts/ci/#{sidebar}"
- elsif current_user
= render 'layouts/nav/dashboard'
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
= render "layouts/flash"
= render 'layouts/ci/info'
%div{ class: container_class }
.content
.clearfix
= yield
%html{lang: "en"}
%head
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
GitLab CI
%body
= yield :header
%table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
%tr
%td{align: "left", style: "margin: 0; padding: 10px;"}
= yield
%br
%tr
%td{align: "left", style: "margin: 0; padding: 10px;"}
%p{style: "font-size:small;color:#777"}
- if @project
You're receiving this notification because you are the one who triggered a build on the #{@project.name} project.
......@@ -6,7 +6,7 @@
= icon('bars')
%button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation
= icon('angle-left')
= icon('ellipsis-v')
.navbar-collapse.collapse
%ul.nav.navbar-nav
......
%div{ class: nav_control_class }
.scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/admin_settings'
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
= 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
= nav_link(controller: %w(system_info background_jobs logs health_check)) do
= link_to admin_system_info_path, title: 'Monitoring' do
%span
Monitoring
= nav_link(controller: :broadcast_messages) do
......@@ -37,5 +38,3 @@
= link_to admin_spam_logs_path, title: "Spam Logs" do
%span
Spam Logs
%li.fade-right
= icon('arrow-right')
%div{ class: nav_control_class }
.scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/group_settings'
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
%span
......@@ -32,5 +33,3 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
%li.fade-right
= icon('arrow-right')
%ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
.scrolling-tabs-container
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
%span
......@@ -44,5 +47,3 @@
= link_to audit_log_profile_path, title: 'Audit Log' do
%span
Audit Log
%li.fade-right
= icon('arrow-right')
......@@ -24,10 +24,12 @@
data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do
Leave Project
%div{ class: nav_control_class }
.scrolling-tabs-container{ class: nav_control_class }
.fade-left
= icon('angle-left')
.fade-right
= icon('angle-right')
%ul.nav-links.scrolling-tabs
%li.fade-left
= icon('arrow-left')
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%span
......@@ -111,5 +113,3 @@
%li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits
%li.fade-right
= icon('arrow-right')
......@@ -18,7 +18,7 @@
= 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: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, 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" })
......
......@@ -8,4 +8,4 @@
%strong Only allow merge requests to be merged if the build succeeds
.help-block
Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
= link_to icon('question-circle'), help_page_path('workflow', 'merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
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