Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/project-import_url

This commit is contained in:
James Lopez
2016-04-14 18:12:10 +02:00
344 changed files with 8178 additions and 1766 deletions
+1 -1
View File
@@ -691,7 +691,7 @@ Style/ZeroLengthPredicate:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 70
Max: 60
# Avoid excessive block nesting.
Metrics/BlockNesting:
+62 -2
View File
@@ -1,31 +1,58 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
- The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse)
- Fix revoking of authorized OAuth applications (Connor Shea)
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse)
- Developers can now add custom tags to transactions (Yorick Peterse)
- Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse)
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API
- API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling)
- Fix Error 500 after renaming a project path (Stan Hu)
- Fix a bug whith trailing slash in teamcity_url (Charles May)
- Allow back dating on issues when created or updated through the API
- Allow back dating on issue notes when created through the API
- Fix avatar stretching by providing a cropping feature
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530
- Allow Omniauth providers to be marked as `external` !3657
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups
- Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
- API: Ability to retrieve a single tag (Robert Schilling)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Improved UX of the navigation sidebar
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
- API: Expose user location (Robert Schilling)
@@ -34,9 +61,33 @@ v 8.7.0 (unreleased)
v 8.6.5 (unreleased)
- Only update repository language if it is not set to improve performance
- Check permissions when user attempts to import members from another project
- API: Do not leak group existence via return code (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
- Update number of Todos in the sidebar when it's marked as "Done". !3600
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling)
- API: User can leave a project through the API when not master or owner. !3613
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
- Diffs load at the correct point when linking from from number
- Selected diff rows highlight
v 8.6.6
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Project switcher uses new dropdown styling
v 8.6.5
- Fix importing from GitHub Enterprise. !3529
- Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533
- Check permissions when user attempts to import members from another project. !3535
- Only update repository language if it is not set to improve performance. !3556
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583
- Unblock user when active_directory is disabled and it can be found !3550
- Fix a 2FA authentication spoofing vulnerability.
v 8.6.4
- Don't attempt to fetch any tags from a forked repo (Stan Hu)
- Redesign the Labels page
v 8.6.3
- Mentions on confidential issues doesn't create todos for non-members. !3374
@@ -153,6 +204,9 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
v 8.5.10
- Fix a 2FA authentication spoofing vulnerability.
v 8.5.9
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
@@ -297,6 +351,9 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
v 8.4.8
- Fix a 2FA authentication spoofing vulnerability.
v 8.4.7
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
@@ -416,6 +473,9 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
v 8.3.7
- Fix a 2FA authentication spoofing vulnerability.
v 8.3.6
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
+1 -1
View File
@@ -448,7 +448,7 @@ merge request:
- multi-line method chaining style **Option B**: dot `.` on previous line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Testing](https://github.com/thoughtbot/guides/tree/master/style/testing)
1. [Testing](doc/development/testing.md)
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
+1 -1
View File
@@ -1 +1 @@
2.7.0
2.7.2
+9 -6
View File
@@ -1,6 +1,6 @@
source "https://rubygems.org"
gem 'rails', '4.2.5.2'
gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -8,7 +8,7 @@ gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
gem 'sprockets', '~> 3.3.5'
gem 'sprockets', '~> 3.6.0'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -149,6 +149,10 @@ gem 'version_sorter', '~> 2.0.0'
# Cache
gem "redis-rails", '~> 4.0.0'
# Redis
gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0'
# Campfire integration
gem 'tinder', '~> 1.10.0'
@@ -229,14 +233,13 @@ group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false
gem 'connection_pool', '~> 2.0', require: false
end
group :development do
gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0"
gem "annotate", "~> 2.7.0"
gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
@@ -282,9 +285,9 @@ group :development, :test do
gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.6.4'
gem 'spring', '~> 1.7.0'
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.38.0', require: false
+50 -48
View File
@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
actionmailer (4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
actionmailer (4.2.6)
actionpack (= 4.2.6)
actionview (= 4.2.6)
activejob (= 4.2.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.2)
actionview (= 4.2.5.2)
activesupport (= 4.2.5.2)
actionpack (4.2.6)
actionview (= 4.2.6)
activesupport (= 4.2.6)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.2)
activesupport (= 4.2.5.2)
actionview (4.2.6)
activesupport (= 4.2.6)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.2)
activesupport (= 4.2.5.2)
activejob (4.2.6)
activesupport (= 4.2.6)
globalid (>= 0.3.0)
activemodel (4.2.5.2)
activesupport (= 4.2.5.2)
activemodel (4.2.6)
activesupport (= 4.2.6)
builder (~> 3.1)
activerecord (4.2.5.2)
activemodel (= 4.2.5.2)
activesupport (= 4.2.5.2)
activerecord (4.2.6)
activemodel (= 4.2.6)
activesupport (= 4.2.6)
arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5)
activesupport (4.2.5.2)
activesupport (4.2.6)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -51,8 +51,8 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.4)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
annotate (2.7.0)
activerecord (>= 3.2, < 6.0)
rake (~> 10.4)
arel (6.0.3)
asana (0.4.0)
@@ -145,7 +145,7 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
css_parser (1.3.7)
css_parser (1.4.1)
addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
@@ -459,8 +459,8 @@ GEM
nokogiri (>= 1.5.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail (2.6.4)
mime-types (>= 1.16, < 4)
mail_room (0.6.1)
method_source (0.8.2)
mime-types (1.25.1)
@@ -559,8 +559,8 @@ GEM
premailer (1.8.6)
css_parser (>= 1.3.6)
htmlentities (>= 4.0.0)
premailer-rails (1.9.0)
actionmailer (>= 3, < 5)
premailer-rails (1.9.2)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
pry (0.10.3)
coderay (~> 1.1.0)
@@ -589,16 +589,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.5.2)
actionmailer (= 4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
activemodel (= 4.2.5.2)
activerecord (= 4.2.5.2)
activesupport (= 4.2.5.2)
rails (4.2.6)
actionmailer (= 4.2.6)
actionpack (= 4.2.6)
actionview (= 4.2.6)
activejob (= 4.2.6)
activemodel (= 4.2.6)
activerecord (= 4.2.6)
activesupport (= 4.2.6)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.2)
railties (= 4.2.6)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -608,9 +608,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
railties (4.2.5.2)
actionpack (= 4.2.5.2)
activesupport (= 4.2.5.2)
railties (4.2.6)
actionpack (= 4.2.6)
activesupport (= 4.2.6)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -769,19 +769,20 @@ GEM
spinach (>= 0.4)
spinach-rerun-reporter (0.0.2)
spinach (~> 0.8)
spring (1.6.4)
spring (1.7.1)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
spring-commands-spinach (1.1.0)
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
sprockets (3.3.5)
sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (2.3.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
sprockets-rails (3.0.4)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
state_machines (0.4.0)
state_machines-activemodel (0.3.0)
activemodel (~> 4.1)
@@ -887,7 +888,7 @@ DEPENDENCIES
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
annotate (~> 2.7.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
@@ -992,13 +993,14 @@ DEPENDENCIES
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rails (= 4.2.5.2)
rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
recaptcha
redcarpet (~> 3.3.3)
redis (~> 3.2)
redis-namespace
redis-rails (~> 4.0.0)
request_store (~> 1.3.0)
@@ -1028,11 +1030,11 @@ DEPENDENCIES
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
spinach-rerun-reporter (~> 0.0.2)
spring (~> 1.6.4)
spring (~> 1.7.0)
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0)
spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.3.5)
sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
teaspoon (~> 1.1.0)
+13 -2
View File
@@ -22,7 +22,17 @@
#= require cal-heatmap
#= require turbolinks
#= require autosave
#= require bootstrap
#= require bootstrap/affix
#= require bootstrap/alert
#= require bootstrap/button
#= require bootstrap/collapse
#= require bootstrap/dropdown
#= require bootstrap/modal
#= require bootstrap/scrollspy
#= require bootstrap/tab
#= require bootstrap/transition
#= require bootstrap/tooltip
#= require bootstrap/popover
#= require select2
#= require raphael
#= require g.raphael
@@ -41,6 +51,7 @@
#= require shortcuts_issuable
#= require shortcuts_network
#= require jquery.nicescroll
#= require date.format
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
@@ -163,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
$('abbr.timeago, .js-timeago').timeago()
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false)
# Flash
if (flash = $(".flash-container")).length > 0
+13 -2
View File
@@ -22,8 +22,19 @@ class @AwardsHandler
emoji = $(this)
.find(".icon")
.data "emoji"
if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
awards_handler.addAward "thumbsdown"
else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
awards_handler.addAward "thumbsup"
awards_handler.addAward emoji
didUserClickEmoji: (that, emoji) ->
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
$(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
showEmojiMenu: ->
if $(".emoji-menu").length
if $(".emoji-menu").is ".is-visible"
@@ -105,7 +116,7 @@ class @AwardsHandler
if origTitle
authors = origTitle.split(', ')
authors.push("me")
award_block.attr("title", authors.join(", "))
award_block.attr("data-original-title", authors.join(", "))
@resetTooltip(award_block)
resetTooltip: (award) ->
@@ -122,7 +133,7 @@ class @AwardsHandler
nodes = []
nodes.push(
"<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>",
"</button>"
@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault()
$form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable()
$submit_button = $form.find('input[type=submit], button[type=submit]')
return if $submit_button.attr('disabled')
$submit_button.disable()
$form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
@@ -35,4 +35,18 @@ $.fn.requiresInput = ->
$form.on 'change input', fieldSelector, requireInput
$ ->
$('form.js-requires-input').requiresInput()
$form = $('form.js-requires-input')
$form.requiresInput()
# Hide or Show the help block when creating a new project
# based on the option selected
hideOrShowHelpBlock = (form) ->
selected = $('.js-select-namespace option:selected')
if selected.length and selected.data('options-parent') is 'groups'
return form.find('.help-block').hide()
else if selected.length
form.find('.help-block').show()
hideOrShowHelpBlock($form)
$('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form)
+67
View File
@@ -0,0 +1,67 @@
class @Compare
constructor: (@opts) ->
@source_loading = $ ".js-source-loading"
@target_loading = $ ".js-target-loading"
$('.js-compare-dropdown').each (i, dropdown) =>
$dropdown = $(dropdown)
$dropdown.glDropdown(
selectable: true
fieldName: $dropdown.data 'field-name'
filterable: true
id: (obj, $el) ->
$el.data 'id'
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e, el) =>
if $dropdown.is '.js-target-branch'
@getTargetHtml()
else if $dropdown.is '.js-source-branch'
@getSourceHtml()
else if $dropdown.is '.js-target-project'
@getTargetProject()
)
@initialState()
initialState: ->
@getSourceHtml()
@getTargetHtml()
getTargetProject: ->
$.ajax(
url: @opts.targetProjectUrl
data:
target_project_id: $("input[name='merge_request[target_project_id]']").val()
beforeSend: ->
$('.mr_target_commit').empty()
success: (html) ->
$('.js-target-branch-dropdown .dropdown-content').html html
)
getSourceHtml: ->
@sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
ref: $("input[name='merge_request[source_branch]']").val()
)
getTargetHtml: ->
@sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
target_project_id: $("input[name='merge_request[target_project_id]']").val()
ref: $("input[name='merge_request[target_branch]']").val()
)
sendAjax: (url, loading, target, data) ->
$target = $(target)
$.ajax(
url: url
data: data
beforeSend: ->
loading.show()
$target.empty()
success: (html) ->
loading.hide()
$target.html html
$('.js-timeago', $target).timeago()
)
+6 -6
View File
@@ -28,26 +28,26 @@ class Dispatcher
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
new DropzoneInput($('.milestone-form'))
new GLForm($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show'
new Diff()
when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.issue-form'))
new GLForm($('.issue-form'))
new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff()
shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.merge-request-form'))
new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form'))
when 'projects:tags:new'
new ZenMode()
new DropzoneInput($('.tag-form'))
new GLForm($('.tag-form'))
when 'projects:releases:edit'
new ZenMode()
new DropzoneInput($('.release-form'))
new GLForm($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
@@ -137,7 +137,7 @@ class Dispatcher
new Wikis()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
new DropzoneInput($('.wiki-form'))
new GLForm($('.wiki-form'))
when 'snippets'
shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show'
@@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea = $(form).find(".js-gfm-input")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
$mdArea = $(form_textarea).closest('.md-area')
$(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
@@ -49,17 +51,16 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close"
dragover: ->
form_textarea.addClass "div-dropzone-focus"
$mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7
return
dragleave: ->
form_textarea.removeClass "div-dropzone-focus"
$mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0
return
drop: ->
form_textarea.removeClass "div-dropzone-focus"
form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus()
return
+35 -10
View File
@@ -57,14 +57,30 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
results = data
if search_text isnt ""
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
if data?
results = data
@options.callback results
if search_text isnt ''
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
@options.callback results
else
elements = @options.elements()
if search_text
elements.each ->
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
class GitLabDropdownRemote
constructor: (@dataEndpoint, @options) ->
@@ -106,7 +122,9 @@ class GitLabDropdown
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) ->
@dropdown = $(@el).parent()
self = @
selector = $(@el).data "target"
@dropdown = if selector? then $(selector) else $(@el).parent()
# Set Defaults
{
@@ -123,7 +141,7 @@ class GitLabDropdown
if _.isString(@filterInput)
@filterInput = @getElement(@filterInput)
search_fields = if @options.search then @options.search.fields else [];
searchFields = if @options.search then @options.search.fields else [];
if @options.data
# If data is an array
@@ -147,7 +165,14 @@ class GitLabDropdown
filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
keys: searchFields
elements: =>
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find('.dropdown-toggle-page').length
selector = ".dropdown-page-one #{selector}"
return $(selector)
data: =>
return @fullData
callback: (data) =>
@@ -376,7 +401,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
if value?
if !field.length and fieldName
# Create hidden input for form
+51
View File
@@ -0,0 +1,51 @@
class @GLForm
constructor: (@form) ->
@textarea = @form.find('textarea.js-gfm-input')
# Before we start, we should clean up any previous data for this form
@destroy()
# Setup the form
@setupForm()
@form.data 'gl-form', @
destroy: ->
# Clean form listeners
@clearEventListeners()
@form.data 'gl-form', null
setupForm: ->
isNewForm = @form.is(':not(.gfm-form)')
@form.removeClass 'js-new-note-form'
if isNewForm
@form.find('.div-dropzone').remove()
@form.addClass('gfm-form')
disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(@form)
autosize(@textarea)
# form and textarea event listeners
@addEventListeners()
# hide discard button
@form.find('.js-note-discard').hide()
@form.show()
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
addEventListeners: ->
@textarea.on 'focus', ->
$(@).closest('.md-area').addClass 'is-focused'
@textarea.on 'blur', ->
$(@).closest('.md-area').removeClass 'is-focused'
+23
View File
@@ -10,6 +10,9 @@ class @Issue
@initTaskList()
@initIssueBtnEventListeners()
@initMergeRequests()
@initRelatedBranches()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
initMergeRequests: ->
$container = $('#merge-requests')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load referenced merge requests', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
initRelatedBranches: ->
$container = $('#related-branches')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load related branches', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
@@ -34,7 +34,7 @@ class @LabelsSelect
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
<span class="label color-label" style="background-color: <%= label.color %>;">
<span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
<%= label.title %>
</span>
</a>
@@ -165,6 +165,8 @@ class @LabelsSelect
.html(template)
$sidebarCollapsedValue.text(labelCount)
$('.has-tooltip', $value).tooltip(container: 'body')
$value
.find('a')
.each((i) ->
@@ -218,7 +220,7 @@ class @LabelsSelect
selectable: true
toggleLabel: (selected) ->
if selected and selected.title isnt 'Any Label'
if selected and selected.title?
selected.title
else
defaultLabel
@@ -0,0 +1,17 @@
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.formatDate = (datetime) ->
dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
$timeagoEls.each( ->
$el = $(@)
$el.attr('title', gl.utils.formatDate($el.attr('datetime')))
)
$timeagoEls.timeago() if setTimeago
) window
@@ -2,6 +2,11 @@
notificationGranted = (message, opts, onclick) ->
notification = new Notification(message, opts)
# Hide the notification after X amount of seconds
setTimeout ->
notification.close()
, 8000
if onclick
notification.onclick = onclick
@@ -73,7 +73,8 @@ class @MergeRequestTabs
@expandView()
else if action == 'diffs'
@loadDiff($target.attr('href'))
@shrinkView()
if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
@expandView()
@@ -84,8 +85,10 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
$el = $("div#{container} #{window.location.hash}")
$('body').scrollTo($el.offset().top) if $el.length
navBarHeight = $('.navbar-gitlab').outerHeight()
$el = $("#{container} #{window.location.hash}")
$.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -141,7 +144,7 @@ class @MergeRequestTabs
url: "#{source}.json"
success: (data) =>
document.querySelector("div#commits").innerHTML = data.html
$('.js-timeago').timeago()
gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
@commitsLoaded = true
@scrollToElement("#commits")
@@ -151,12 +154,38 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html
$('.js-timeago').timeago()
$('div#diffs .js-syntax-highlight').syntaxHighlight()
$('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
@highlighSelectedLine()
$(document)
.off 'click', '.diff-line-num a'
.on 'click', '.diff-line-num a', (e) =>
e.preventDefault()
window.location.hash = $(e.currentTarget).attr 'href'
@highlighSelectedLine()
@scrollToElement("#diffs")
highlighSelectedLine: ->
$('.hll').removeClass 'hll'
locationHash = window.location.hash
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
$diffLine = $(locationHash)
if $diffLine.is ':not(tr)'
$diffLine = $("td#{locationHash}, td#{hashClassString}")
else
$diffLine = $('td', $diffLine)
$diffLine.addClass 'hll'
diffLineTop = $diffLine.offset().top
navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) ->
return if @buildsLoaded
@@ -165,7 +194,7 @@ class @MergeRequestTabs
url: "#{source}.json"
success: (data) =>
document.querySelector("div#builds").innerHTML = data.html
$('.js-timeago').timeago()
gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
@buildsLoaded = true
@scrollToElement("#builds")
@@ -12,10 +12,19 @@ class @MergeRequestWidget
@readyForCICheck = true
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
@addEventListeners()
@pollCIStatus()
notifyPermissions()
setOpts: (@opts) ->
clearEventListeners: ->
$(document).off 'page:change.merge_request'
addEventListeners: ->
$(document).on 'page:change.merge_request', =>
if $('body').data('page') isnt 'projects:merge_requests:show'
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
@@ -38,7 +47,7 @@ class @MergeRequestWidget
$('.mr-state-widget').replaceWith(data)
ciLabelForStatus: (status) ->
if status == 'success'
if status is 'success'
'passed'
else
status
@@ -67,18 +76,28 @@ class @MergeRequestWidget
@opts.ci_status = data.status
return
if data.status isnt @opts.ci_status
if data.status isnt @opts.ci_status and data.status?
@showCIStatus data.status
if data.coverage
@showCICoverage data.coverage
if showNotification
message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
status = @ciLabelForStatus(data.status)
if status is "preparing"
title = @opts.ci_title.preparing
status = status.charAt(0).toUpperCase() + status.slice(1);
message = @opts.ci_message.preparing.replace('{{status}}', status)
else
title = @opts.ci_title.normal
message = @opts.ci_message.normal.replace('{{status}}', status)
title = title.replace('{{status}}', status)
message = message.replace('{{sha}}', data.sha)
message = message.replace('{{title}}', data.title)
notify(
"Build #{@ciLabelForStatus(data.status)}",
title,
message,
@opts.gitlab_icon,
->
@@ -98,6 +117,8 @@ class @MergeRequestWidget
@setMergeButtonClass('btn-danger')
when "running", "pending"
@setMergeButtonClass('btn-warning')
when "success"
@setMergeButtonClass('btn-create')
else
$('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
@@ -107,4 +128,6 @@ class @MergeRequestWidget
$('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) ->
$('.accept_merge_request').removeClass("btn-create").addClass(css_class)
$('.accept_merge_request')
.removeClass('btn-danger btn-warning btn-create')
.addClass(css_class)
@@ -85,15 +85,21 @@ class @MilestoneSelect
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass 'js-filter-bulk-update'
return
if $dropdown.hasClass('js-filter-submit')
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
selected = $selectbox
.find('input[type="hidden"]')
+19 -53
View File
@@ -163,9 +163,15 @@ class @Notes
else if @isNewNote(note)
@note_ids.push(note.id)
$('ul.main-notes-list')
$notesList = $('ul.main-notes-list')
$notesList
.append(note.html)
.syntaxHighlight()
# Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
@initTaskList()
@updateNotesCount(1)
@@ -217,6 +223,8 @@ class @Notes
# append new note to all matching discussions
discussionContainer.append note_html
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
@updateNotesCount(1)
###
@@ -275,32 +283,10 @@ class @Notes
show the form
###
setupNoteForm: (form) ->
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
form.removeClass "js-new-note-form"
form.find('.div-dropzone').remove()
# hide discard button
form.find('.js-note-discard').hide()
# setup preview buttons
previewButton = form.find(".js-md-preview-button")
new GLForm form
textarea = form.find(".js-note-text")
textarea.on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
textarea.on 'focus', ->
$(this).closest('.md-area').addClass 'is-focused'
textarea.on 'blur', ->
$(this).closest('.md-area').removeClass 'is-focused'
autosize(textarea)
new Autosave textarea, [
"Note"
form.find("#note_commit_id").val()
@@ -309,11 +295,6 @@ class @Notes
form.find("#note_noteable_id").val()
]
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
form.show()
###
Called in response to the new note form being submitted
@@ -345,7 +326,9 @@ class @Notes
updateNote: (_xhr, note, _status) =>
# Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html)
$('.js-timeago', $html).timeago()
gl.utils.localTimeAgo($('.js-timeago', $html))
$html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable')
@@ -365,34 +348,15 @@ class @Notes
note = $(this).closest(".note")
note.addClass "is-editting"
form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form')
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
# Setup markdown form
if isNewForm
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
new GLForm form
textarea = form.find("textarea")
textarea.focus()
if isNewForm
autosize(textarea)
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
# The textarea has the correct value, Chrome just won't show it unless we
# modify it, so let's clear it and re-set it!
value = textarea.val()
textarea.val ""
textarea.val value
if isNewForm
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
form.find(".js-note-text").focus()
###
Called in response to clicking the edit note link
@@ -549,6 +513,9 @@ class @Notes
removeDiscussionNoteForm: (form)->
row = form.closest("tr")
glForm = form.data 'gl-form'
glForm.destroy()
form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies)
@@ -560,7 +527,6 @@ class @Notes
# only remove the form
form.remove()
cancelDiscussionForm: (e) =>
e.preventDefault()
form = $(e.target).closest(".js-discussion-note-form")
+5 -2
View File
@@ -18,8 +18,11 @@ class @Profile
$(this).find('.btn-save').enable()
$(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable()
$('.update-notifications').on 'ajax:success', (e, data) ->
if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
@bindEvents()
+9 -8
View File
@@ -37,19 +37,20 @@ class @Project
$('.update-notification').on 'click', (e) ->
e.preventDefault()
notification_level = $(@).data 'notification-level'
$('#notification_level').val(notification_level)
label = $(@).data 'notification-title'
$('#notification_setting_level').val(notification_level)
$('#notification-form').submit()
label = null
switch notification_level
when 0 then label = ' Disabled '
when 1 then label = ' Participating '
when 2 then label = ' Watching '
when 3 then label = ' Global '
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
$('#notification-form').on 'ajax:success', (e, data) ->
if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
@projectSelectDropdown()
projectSelectDropdown: ->
@@ -1,5 +1,37 @@
class @ProjectSelect
constructor: ->
$('.js-projects-dropdown-toggle').each (i, dropdown) ->
$dropdown = $(dropdown)
$dropdown.glDropdown(
filterable: true
filterRemote: true
search:
fields: ['name_with_namespace']
data: (term, callback) ->
finalCallback = (projects) ->
callback projects
if @includeGroups
projectsCallback = (projects) ->
groupsCallback = (groups) ->
data = groups.concat(projects)
finalCallback(data)
Api.groups term, false, groupsCallback
else
projectsCallback = finalCallback
if @groupId
Api.groupProjects @groupId, term, projectsCallback
else
Api.projects term, @orderBy, projectsCallback
url: (project) ->
project.web_url
text: (project) ->
project.name_with_namespace
)
$('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups')
+1
View File
@@ -4,6 +4,7 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
@@ -10,10 +10,10 @@ class @Subscription
btn = $(event.currentTarget)
action = btn.find('span').text()
current_status = @subscription_status.attr('data-status')
btn.prop('disabled', true)
btn.addClass('disabled')
$.post @url, =>
btn.prop('disabled', false)
btn.removeClass('disabled')
status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
@subscription_status.attr('data-status', status)
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
+9 -2
View File
@@ -57,5 +57,12 @@ class @Todos
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
goToTodoUrl: ->
Turbolinks.visit($(this).data('url'))
goToTodoUrl: (e)->
todoLink = $(this).data('url')
return unless todoLink
if e.metaKey
e.preventDefault()
window.open(todoLink,'_blank')
else
Turbolinks.visit(todoLink)
+27 -2
View File
@@ -7,6 +7,7 @@
&:focus,
&:active {
outline: none;
background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background);
}
}
@@ -27,7 +28,8 @@
color: $color;
}
&:active {
&:active,
&.active {
@include box-shadow ($gl-btn-active-background);
background-color: $dark;
@@ -61,7 +63,7 @@
}
@mixin btn-white {
@include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236);
@include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
}
.btn {
@@ -218,3 +220,26 @@
margin-right: 5px;
}
}
.btn-text-field {
width: 100%;
text-align: left;
padding: 6px 16px;
border-color: $border-color;
color: $btn-placeholder-gray;
background-color: $background-color;
&:hover,
&:active,
&:focus {
cursor: text;
box-shadow: none;
border-color: $border-color;
color: $btn-placeholder-gray;
background-color: $background-color;
}
}
.btn-file-option {
background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
}
@@ -248,7 +248,7 @@
.dropdown-title {
position: relative;
padding: 0 0 15px;
padding: 0 25px 15px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
@@ -275,7 +275,7 @@
}
.dropdown-menu-close {
right: 7px;
right: 5px;
width: 20px;
height: 20px;
top: -1px;
+3 -2
View File
@@ -15,12 +15,13 @@
.file-title {
position: relative;
background: $background-color;
background-color: $background-color;
border-bottom: 1px solid $border-color;
margin: 0;
text-align: left;
padding: 10px $gl-padding;
word-wrap: break-word;
border-radius: 3px 3px 0 0;
.file-actions {
float: right;
@@ -49,7 +50,7 @@
}
}
a {
a:not(.btn) {
color: $gl-dark-link-color;
}
-18
View File
@@ -1,24 +1,6 @@
/**
* Styles that apply to all GFM related forms.
*/
.issue-form, .merge-request-form, .wiki-form {
.description {
height: 16em;
border-top-left-radius: 0;
}
}
.wiki-form {
.description {
height: 26em;
}
}
.milestone-form {
.description {
height: 14em;
}
}
.gfm-commit, .gfm-commit_range {
font-family: $monospace_font;
@@ -33,15 +33,10 @@
background: $color;
}
.complex-sidebar .nav-primary {
border-right: 1px solid lighten($color, 3%);
}
.sidebar-wrapper {
background: $color-darker;
.sidebar-user {
border-top: 1px solid lighten($color, 3%);
background: $color-darker;
color: $color-light;
@@ -67,6 +62,7 @@
.count {
color: $color-light;
background: $color-dark;
}
}
+7 -2
View File
@@ -69,6 +69,7 @@ header {
}
.header-content {
position: relative;
height: $header-height;
padding-right: 20px;
@@ -76,6 +77,10 @@ header {
padding-right: 0;
}
.dropdown-menu {
margin-top: -5px;
}
.title {
margin: 0;
font-size: 19px;
@@ -123,11 +128,11 @@ header {
}
@mixin collapsed-header {
margin-left: 40px;
margin-left: $sidebar_collapsed_width;
}
.header-collapsed {
margin-left: 40px;
margin-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) {
@include collapsed-header;
@@ -1,22 +1,15 @@
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
margin-bottom: -5px;
.div-dropzone-focus {
border-color: #66afe9 !important;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
outline: 0 !important;
}
.div-dropzone-hover {
position: absolute;
top: 50%;
left: 50%;
margin-top: -0.5em;
margin-left: -0.6em;
margin-top: -11.5px;
margin-left: -15px;
opacity: 0;
font-size: 50px;
font-size: 30px;
transition: opacity 200ms ease-in-out;
pointer-events: none;
}
+3 -3
View File
@@ -58,12 +58,12 @@
.nav-search {
display: inline-block;
width: 50%;
width: 100%;
padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
@media (min-width: $screen-sm-min) {
width: 50%;
}
}
+49 -74
View File
@@ -144,7 +144,7 @@
}
a {
padding: 7px 12px;
padding: 7px 15px;
font-size: $gl-font-size;
line-height: 24px;
color: $gray;
@@ -169,12 +169,10 @@
}
.count {
&:before {
content: '(';
}
&:after {
content: ')';
}
float: right;
background: #eee;
padding: 0 8px;
@include border-radius(6px);
}
&.back-link i {
@@ -193,27 +191,6 @@
}
}
.expand-nav a {
color: $gl-icon-color;
width: 60px;
position: fixed;
top: 0;
left: 0;
font-size: 20px;
background: #fff;
height: 59px;
text-align: center;
line-height: 59px;
border-bottom: 1px solid #eee;
transition-duration: .3s;
outline: none;
z-index: 100;
&:hover {
text-decoration: none;
}
}
.collapse-nav a {
width: $sidebar_width;
position: fixed;
@@ -233,12 +210,55 @@
}
.page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
.sidebar-wrapper {
display: none;
width: $sidebar_collapsed_width;
.header-logo {
width: $sidebar_collapsed_width;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
}
}
}
.nav-sidebar {
width: $sidebar_collapsed_width;
li {
width: auto;
a {
span {
display: none;
}
}
}
}
.collapse-nav a {
width: $sidebar_collapsed_width;
}
.sidebar-user {
padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
.username {
display: none;
}
}
}
}
.page-sidebar-expanded {
padding-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
@@ -289,48 +309,3 @@
padding-right: $sidebar_collapsed_width;
}
}
.complex-sidebar {
display: inline-block;
.nav-primary {
width: 61px;
float: left;
height: 100vh;
.nav-sidebar {
width: 60px;
li a {
width: 60px;
span {
display: none;
}
}
}
}
.nav-secondary {
$nav-secondary-width: 168px;
float: left;
width: $nav-secondary-width;
.nav-sidebar {
width: $nav-secondary-width;
li {
width: $nav-secondary-width;
a {
width: $nav-secondary-width;
i {
display: none;
}
}
}
}
}
}
@@ -14,10 +14,6 @@
background: $row-hover;
}
&:last-child {
border-bottom: none;
}
.avatar {
margin-right: 15px;
}
@@ -43,7 +43,6 @@
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
@import "bootstrap/carousel";
// Utility classes
.clearfix {
@@ -250,14 +250,6 @@ a > code {
* Textareas intended for GFM
*
*/
.js-gfm-input {
font-family: $monospace_font;
color: $gl-text-color;
}
.md-preview {
}
.strikethrough {
text-decoration: line-through;
}
@@ -10,10 +10,10 @@ $gutter_inner_width: 258px;
/*
* UI elements
*/
$border-color: #efeff1;
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
$table-border-color: #eef0f2;
$background-color: #faf9f9;
$background-color: #fafafa;
/*
* Text
@@ -28,6 +28,7 @@ $gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
@@ -81,7 +82,7 @@ $provider-btn-not-active-color: #4688f1;
$white-light: #fff;
$white-normal: #ededed;
$white-dark: #ededed;
$white-dark: #ececec;
$gray-light: #faf9f9;
$gray-normal: #f5f5f5;
@@ -108,6 +109,8 @@ $red-light: #e52c5a;
$red-normal: #d22852;
$red-dark: darken($red-normal, 5%);
$black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: #f1f2f4;
$border-white-normal: #d6dae2;
$border-white-dark: #c6cacf;
@@ -147,18 +150,26 @@ $light-grey-header: #faf9f9;
*/
$gl-primary: $blue-normal;
$gl-success: $green-normal;
$gl-success-focus: rgba($gl-success, .4);
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
$gl-btn-active-background: rgba(0, 0, 0, 0.12);
$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background;
$gl-btn-active-background: rgba(0, 0, 0, 0.16);
$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
* Commit Diff Colors
*/
$added: #63c363;
$deleted: #f77;
$line-added: #ecfdf0;
$line-added-dark: #c7f0d2;
$line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
/*
* Fonts
*/
@@ -191,6 +202,13 @@ $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%);
$dropdown-toggle-icon-color: #c4c4c4;
$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
/*
* Buttons
*/
$btn-active-gray: #ececec;
$btn-placeholder-gray: #c7c7c7;
$btn-white-active: #848484;
/*
* Award emoji
*/
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #557;
border-color: darken(#557, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
}
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #49483e;
border-color: darken(#49483e, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
}
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #174652;
border-color: darken(#174652, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
}
@@ -6,7 +6,7 @@
}
.diff-line-num, .diff-line-num a {
color: rgba(0, 0, 0, 0.3);
color: $black-transparent;
}
// Code itself
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #ddd8c5;
border-color: darken(#ddd8c5, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
}
@@ -30,7 +36,7 @@
}
.line_content.match {
color: rgba(0, 0, 0, 0.3);
color: $black-transparent;
background: rgba(255, 255, 255, 0.4);
}
}
+18 -12
View File
@@ -6,12 +6,12 @@
}
.diff-line-num, .diff-line-num a {
color: rgba(0, 0, 0, 0.3);
color: $black-transparent;
}
// Code itself
pre.code, .diff-line-num {
border-color: $border-color;
border-color: $table-border-gray;
}
&, pre.code, .line_holder .line_content {
@@ -21,38 +21,44 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #f8eec7;
border-color: darken(#f8eec7, 15%);
}
.diff-line-num {
&.old {
background: #fdd;
border-color: #f1c0c0;
background-color: $line-number-old;
border-color: $line-removed-dark;
}
&.new {
background: #dbffdb;
border-color: #c1e9c1;
background-color: $line-number-new;
border-color: $line-added-dark;
}
}
.line_content {
&.old {
background: #ffecec;
background: $line-removed;
span.idiff {
background-color: #f8cbcb;
background-color: $line-removed-dark;
}
}
&.new {
background: #eaffea;
background-color: $line-added;
span.idiff {
background-color: #a6f3a6;
background-color: $line-added-dark;
}
}
&.match {
color: rgba(0, 0, 0, 0.3);
background: #fafafa;
color: $black-transparent;
background: $match-line;
}
}
}
+2
View File
@@ -20,6 +20,8 @@
margin: 0;
padding: 0;
margin-top: 10px;
word-break: normal;
white-space: pre-wrap;
}
.commit-info-row {
@@ -47,6 +47,7 @@ li.commit {
.commit_short_id {
min-width: 65px;
color: $gl-dark-link-color;
font-family: $monospace_font;
}
@@ -88,6 +89,10 @@ li.commit {
padding: 0;
margin: 0;
}
a {
color: $gl-dark-link-color;
}
}
.commit-row-info {
+20 -3
View File
@@ -2,6 +2,7 @@
.diff-file {
border: 1px solid $border-color;
margin-bottom: $gl-padding;
border-radius: 3px;
.diff-header {
position: relative;
@@ -10,6 +11,7 @@
padding: 10px 16px;
color: #555;
z-index: 10;
border-radius: 3px 3px 0 0;
.diff-title {
font-family: $monospace_font;
@@ -31,6 +33,7 @@
overflow-y: hidden;
background: #fff;
color: #333;
border-radius: 0 0 3px 3px;
.unfold {
cursor: pointer;
@@ -63,10 +66,10 @@
.line_holder td {
line-height: $code_line_height;
font-size: $code_font_size;
}
td {
white-space: nowrap;
span {
white-space: pre;
}
}
}
@@ -109,6 +112,10 @@
display: table-cell;
}
}
.text-file.diff-wrap-lines table .line_holder td span {
white-space: pre-wrap;
}
}
.image {
background: #ddd;
@@ -321,6 +328,16 @@
float: right;
}
.diffs {
.content-block {
border-bottom: none;
}
}
.files-changed {
border-bottom: none;
}
// Mobile
@media (max-width: 480px) {
.diff-title {
+10 -1
View File
@@ -41,8 +41,17 @@
word-wrap: break-word;
.md {
color: #7f8fa4;
color: $gl-grayish-blue;
font-size: $gl-font-size;
.label {
color: $gl-text-color;
font-size: inherit;
}
iframe.twitter-share-button {
vertical-align: bottom;
}
}
pre {
+3
View File
@@ -59,6 +59,9 @@
position: relative;
overflow-y: auto;
padding: 15px;
.form-actions {
margin: -$gl-padding+1;
}
}
body.modal-open {
@@ -263,6 +263,12 @@
}
}
.dropdown-content {
a:hover {
color: inherit;
}
}
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
@@ -316,3 +322,9 @@
color: #8c8c8c;
}
}
.issuable-form-padding-top {
@media (min-width: $screen-sm-min) {
padding-top: 7px;
}
}
+69
View File
@@ -49,6 +49,15 @@
}
.label-row {
.label-name {
display: inline-block;
width: 200px;
@media (max-width: $screen-xs-min) {
display: block;
}
}
.label {
padding: 9px;
font-size: 14px;
@@ -69,3 +78,63 @@
background-color: $gl-danger;
color: $white-light;
}
@mixin labels-mobile {
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
}
.manage-labels-list {
.prepend-left-10, .prepend-description-left {
display: inline-block;
width: 40%;
vertical-align: middle;
@include labels-mobile;
}
.prepend-description-left {
width: 57%;
@include labels-mobile;
}
.pull-info-right {
float: right;
@media (max-width: $screen-xs-min) {
float: none;
}
.action-buttons {
border-color: transparent;
padding: 6px;
color: $gl-text-color;
&.subscribe-button {
padding-left: 0;
}
}
i {
color: $gl-text-color;
}
.append-right-20 {
a {
color: $gl-text-color;
}
@media (max-width: $screen-xs-min) {
display: block;
margin-bottom: 10px;
}
}
}
}
@@ -123,6 +123,8 @@
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
.commit {
margin: 0;
padding: 2px 0;
@@ -140,6 +142,7 @@
overflow: hidden;
font-size: 90%;
margin: 0 3px;
word-break: break-all;
}
.mr-list {
@@ -174,10 +177,6 @@
display: none;
}
.merge-request-form .select2-container {
width: 250px !important;
}
#modal_merge_info .modal-dialog {
width: 600px;
@@ -200,3 +199,76 @@
overflow-x: scroll;
}
}
.panel-new-merge-request {
.panel-heading {
padding: 5px 10px;
font-weight: 600;
line-height: 25px;
}
.panel-body {
padding: 10px 5px;
}
.panel-footer {
padding: 5px 10px;
}
.commit {
.commit-row-title {
margin-bottom: 4px;
}
.avatar {
width: 20px;
height: 20px;
margin-right: 5px;
}
.commit-row-info {
line-height: 20px;
}
}
.btn-clipboard {
margin-right: 5px;
padding: 0;
background: transparent;
}
.ci-status-link {
margin-right: 5px;
}
}
.merge-request-select {
padding-left: 5px;
padding-right: 5px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
@media (min-width: $screen-sm-min) {
float: left;
width: 50%;
margin-bottom: 0;
}
.dropdown-menu-toggle {
width: 100%;
}
.dropdown-menu {
left: 5px;
right: 5px;
width: auto;
}
}
.issuable-form-select-holder {
display: inline-block;
width: 250px;
}
+31 -8
View File
@@ -1,10 +1,10 @@
/**
* Note Form
*/
.reply-btn {
@extend .btn-primary;
margin: 10px $gl-padding;
.comment-btn {
@extend .btn-create;
}
.diff-file .diff-content {
tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
@@ -40,6 +40,7 @@
}
.note-textarea {
display: block;
padding: 10px 0;
font-family: $regular_font;
border: 0;
@@ -63,7 +64,7 @@
&.is-focused {
border-color: $focus-border-color;
box-shadow: 0 0 2px rgba(#000, .2),
box-shadow: 0 0 2px $black-transparent,
0 0 4px rgba($focus-border-color, .4);
.comment-toolbar,
@@ -71,12 +72,35 @@
border-color: $focus-border-color;
}
}
&.is-dropzone-hover {
border-color: $gl-success;
box-shadow: 0 0 2px $black-transparent,
0 0 4px $gl-success-focus;
.comment-toolbar,
.nav-links {
border-color: $gl-success;
}
}
p {
code {
white-space: normal;
}
pre {
code {
white-space: pre;
}
}
}
}
}
.discussion-form {
padding: $gl-padding-top $gl-padding;
background-color: #fff;
background-color: $white-light;
}
.note-edit-form {
@@ -113,13 +137,12 @@
.discussion-body,
.diff-file {
.notes .note {
border-color: #ddd;
padding: 10px 15px;
}
.discussion-reply-holder {
background: $background-color;
border-top: 1px solid $border-color;
background-color: $white-light;
padding: 10px 16px;
}
}
+40 -15
View File
@@ -58,6 +58,7 @@ ul.notes {
.note {
display: block;
position: relative;
border-bottom: 1px solid $table-border-gray;
&.is-editting {
.note-header,
@@ -80,9 +81,15 @@ ul.notes {
@include md-typography;
// On diffs code should wrap nicely and not overflow
pre {
p {
code {
white-space: pre;
white-space: normal;
}
pre {
code {
white-space: pre;
}
}
}
@@ -111,15 +118,16 @@ ul.notes {
margin: 10px 0;
}
}
a {
word-break: break-all;
}
}
.note-header {
padding-bottom: 3px;
}
&:last-child {
border-bottom: 1px solid $border-color;
}
}
}
@@ -129,7 +137,7 @@ ul.notes {
margin-right: 10px;
}
.line_content {
white-space: pre-wrap;
white-space: pre;
}
}
@@ -137,29 +145,37 @@ ul.notes {
font-family: $regular_font;
td {
border: 1px solid #ddd;
border: 1px solid $table-border-gray;
border-left: none;
&.notes_line {
vertical-align: middle;
text-align: center;
padding: 10px 0;
background: #fff;
background: $background-color;
color: $text-color;
}
&.notes_line2 {
text-align: center;
padding: 10px 0;
border-left: 1px solid #ddd !important;
}
&.notes_content {
background-color: #fff;
background-color: $background-color;
border-width: 1px 0;
padding: 0;
vertical-align: top;
white-space: normal;
&.parallel {
border-width: 1px;
}
.notes {
background-color: $white-light;
}
}
}
}
@@ -175,9 +191,6 @@ ul.notes {
}
}
.author_link {
font-weight: 600;
}
}
.note-headline-light,
@@ -203,14 +216,26 @@ ul.notes {
line-height: 24px;
.fa {
color: $notes-action-color;
position: relative;
top: 1px;
font-size: 17px;
}
.fa-trash-o {
top: 0;
font-size: 16px;
&.js-note-delete {
i {
&:hover {
color: $gl-text-red;
}
}
}
&.js-note-edit {
i {
&:hover {
color: $gl-link-color;
}
}
}
}
+5
View File
@@ -34,6 +34,11 @@
color: #7f8fa4;
font-size: $gl-font-size;
.label {
color: $gl-text-color;
font-size: inherit;
}
p {
color: #5c5d5e;
}
+11 -14
View File
@@ -3,6 +3,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
@@ -47,6 +48,16 @@ class ApplicationController < ActionController::Base
email: current_user.email,
username: current_user.username,
)
Raven.tags_context(program: sentry_program_context)
end
end
def sentry_program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end
end
@@ -148,20 +159,6 @@ class ApplicationController < ActionController::Base
end
end
def add_gon_variables
gon.api_version = API::API.version
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if current_user
gon.current_user_id = current_user.id
gon.api_token = current_user.private_token
end
end
def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets]
@@ -18,14 +18,14 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def create
project_ids = params[:milestone][:project_ids]
project_ids = params[:milestone][:project_ids].reject(&:blank?)
title = milestone_params[:title]
@projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute
if create_milestones(project_ids)
redirect_to milestone_path(title)
else
render_new_with_error(project_ids.empty?)
end
redirect_to milestone_path(title)
end
def show
@@ -41,6 +41,27 @@ class Groups::MilestonesController < Groups::ApplicationController
private
def create_milestones(project_ids)
return false unless project_ids.present?
ActiveRecord::Base.transaction do
@projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute
end
end
true
rescue ActiveRecord::ActiveRecordError => e
flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}"
false
end
def render_new_with_error(empty_project_ids)
@milestone = Milestone.new(milestone_params)
@milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids
render :new
end
def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group)
end
@@ -0,0 +1,16 @@
class Groups::NotificationSettingsController < Groups::ApplicationController
before_action :authenticate_user!
def update
notification_setting = current_user.notification_settings_for(group)
saved = notification_setting.update_attributes(notification_setting_params)
render json: { saved: saved }
end
private
def notification_setting_params
params.require(:notification_setting).permit(:level)
end
end
@@ -1,9 +1,11 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
include Gitlab::GonHelper
include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
before_action :add_gon_variables
layout 'profile'
@@ -60,6 +60,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
handle_signup_error
end
def omniauth_error
@@ -92,16 +94,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled?
message << " Create a GitLab account first, and then connect it to your #{label} account."
end
flash[:notice] = message
redirect_to new_user_session_path
handle_signup_error
end
def handle_service_ticket provider, ticket
@@ -122,6 +115,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
def handle_signup_error
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled?
message << " Create a GitLab account first, and then connect it to your #{label} account."
end
flash[:notice] = message
redirect_to new_user_session_path
end
def oauth
@oauth ||= request.env['omniauth.auth']
end
@@ -1,39 +1,18 @@
class Profiles::NotificationsController < Profiles::ApplicationController
def show
@user = current_user
@notification = current_user.notification
@project_members = current_user.project_members
@group_members = current_user.group_members
@group_notifications = current_user.notification_settings.for_groups
@project_notifications = current_user.notification_settings.for_projects
end
def update
type = params[:notification_type]
@saved = if type == 'global'
current_user.update_attributes(user_params)
elsif type == 'group'
group_member = current_user.group_members.find(params[:notification_id])
group_member.notification_level = params[:notification_level]
group_member.save
else
project_member = current_user.project_members.find(params[:notification_id])
project_member.notification_level = params[:notification_level]
project_member.save
end
respond_to do |format|
format.html do
if @saved
flash[:notice] = "Notification settings saved"
else
flash[:alert] = "Failed to save new settings"
end
redirect_back_or_default(default: profile_notifications_path)
end
format.js
if current_user.update_attributes(user_params)
flash[:notice] = "Notification settings saved"
else
flash[:alert] = "Failed to save new settings"
end
redirect_back_or_default(default: profile_notifications_path)
end
def user_params
+30 -10
View File
@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show]
before_action :issue,
only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
# Cross-reference merge requests
before_action :closed_by_merge_requests, only: [:show]
respond_to :html
def index
@@ -65,8 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format|
format.html
@@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
def referenced_merge_requests
@merge_requests = @issue.referenced_merge_requests(current_user)
@closed_by_merge_requests = @issue.closed_by_merge_requests(current_user)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_merge_requests')
}
end
end
end
def related_branches
merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches -
merge_requests.map(&:source_branch)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_related_branches')
}
end
end
end
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected
def issue
@@ -207,20 +207,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController
#This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def branch_to
@target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
respond_to do |format|
format.js
end
render layout: false
end
def ci_status
@@ -237,6 +237,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
status = "preparing" if status.nil?
response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
+1 -2
View File
@@ -39,8 +39,7 @@ class Projects::NotesController < Projects::ApplicationController
def destroy
if note.editable?
note.destroy
note.reset_events_cache
Notes::DeleteService.new(project, current_user).execute(note)
end
respond_to do |format|
@@ -0,0 +1,16 @@
class Projects::NotificationSettingsController < Projects::ApplicationController
before_action :authenticate_user!
def update
notification_setting = current_user.notification_settings_for(project)
saved = notification_setting.update_attributes(notification_setting_params)
render json: { saved: saved }
end
private
def notification_setting_params
params.require(:notification_setting).permit(:level)
end
end
@@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
RepositoryArchiveCacheWorker.perform_async
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok
rescue => ex
+8 -4
View File
@@ -101,14 +101,18 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
format.html do
if current_user
@membership = @project.team.find_member(current_user.id)
if @membership
@notification_setting = current_user.notification_settings_for(@project)
end
end
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
if current_user
@membership = @project.team.find_member(current_user.id)
end
render :show
end
else
+1 -1
View File
@@ -184,7 +184,7 @@ module ApplicationHelper
element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}",
datetime: time.to_time.getutc.iso8601,
title: time.in_time_zone.to_s(:medium),
title: time.to_time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
unless skip_js
+3 -3
View File
@@ -27,9 +27,9 @@ module BlobHelper
link_opts)
if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn btn-default disabled has-tooltip", 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)
link_to "Edit", edit_path, class: 'btn'
link_to "Edit", edit_path, class: 'btn btn-file-option'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
@@ -38,7 +38,7 @@ module BlobHelper
}
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to "Edit", fork_path, class: 'btn', method: :post
link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post
end
end
+3 -3
View File
@@ -28,7 +28,7 @@ module CommitsHelper
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
render "projects/commits/#{template}", commit: commit, project: project unless commit.nil?
end
# Breadcrumb links for a Project and, if applicable, a tree path
@@ -117,7 +117,7 @@ module CommitsHelper
end
end
link_to(
"Browse Files »",
"Browse Files",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
@@ -197,7 +197,7 @@ module CommitsHelper
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)),
class: 'btn view-file js-view-file'
class: 'btn view-file js-view-file btn-file-option'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
+18
View File
@@ -0,0 +1,18 @@
module FormHelper
def form_errors(model)
return unless model.errors.any?
pluralized = 'error'.pluralize(model.errors.count)
headline = "The form contains the following #{pluralized}:"
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) <<
content_tag(:ul) do
model.errors.full_messages.
map { |msg| content_tag(:li, msg) }.
join.
html_safe
end
end
end
end
-23
View File
@@ -116,29 +116,6 @@ module GitlabMarkdownHelper
end
end
MARKDOWN_TIPS = [
"End a line with two or more spaces for a line-break, or soft-return",
"Inline code can be denoted by `surrounding it with backticks`",
"Blocks of code can be denoted by three backticks ``` or four leading spaces",
"Emoji can be added by :emoji_name:, for example :thumbsup:",
"Notify other participants using @user_name",
"Notify a specific group using @group_name",
"Notify the entire team using @all",
"Reference an issue using a hash, for example issue #123",
"Reference a merge request using an exclamation point, for example MR !123",
"Italicize words or phrases using *asterisks* or _underscores_",
"Bold words or phrases using **double asterisks** or __double underscores__",
"Strikethrough words or phrases using ~~two tildes~~",
"Make a bulleted list using + pluses, - minuses, or * asterisks",
"Denote blockquotes using > at the beginning of a line",
"Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___"
].freeze
# Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip
MARKDOWN_TIPS.sample
end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
+25 -9
View File
@@ -52,6 +52,7 @@ module IssuesHelper
def milestone_options(object)
milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(object.milestone) if object.milestone.present? && object.milestone.closed?
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
@@ -115,17 +116,32 @@ module IssuesHelper
icon('eye-slash') if issue.confidential?
end
def emoji_icon(name, unicode = nil, aliases = [])
def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
unicode ||= Emoji.emoji_filename(name) rescue ""
content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}",
title: name,
data: {
aliases: aliases.join(' '),
emoji: name,
unicode_name: unicode
}
data = {
aliases: aliases.join(" "),
emoji: name,
unicode_name: unicode
}
if sprite
# Emoji icons for the emoji menu, these use a spritesheet.
content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}",
title: name,
data: data
else
# Emoji icons displayed separately, used for the awards already given
# to an issue or merge request.
content_tag :img, "",
class: "icon emoji",
title: name,
height: "20px",
width: "20px",
src: url_to_image("#{unicode}.png"),
data: data
end
end
def emoji_author_list(notes, current_user)
+10 -2
View File
@@ -3,8 +3,16 @@ module NamespacesHelper
groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
data_attr_group = { 'data-options-parent' => 'groups' }
data_attr_users = { 'data-options-parent' => 'users' }
group_opts = [
"Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.path : g.human_name, g.id, data_attr_group] }
]
users_opts = [
"Users", users.sort_by(&:human_name).map { |u| [display_path ? u.path : u.human_name, u.id, data_attr_users] }
]
options = []
options << group_opts
+2 -5
View File
@@ -69,10 +69,7 @@ module NotesHelper
line_type: line_type
}
button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = icon('comment')
link_text << ' Reply'
end
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply'
end
end
+34 -34
View File
@@ -1,48 +1,48 @@
module NotificationsHelper
include IconsHelper
def notification_icon(notification)
if notification.disabled?
icon('volume-off', class: 'ns-mute')
elsif notification.participating?
icon('volume-down', class: 'ns-part')
elsif notification.watch?
icon('volume-up', class: 'ns-watch')
else
icon('circle-o', class: 'ns-default')
def notification_icon_class(level)
case level.to_sym
when :disabled
'microphone-slash'
when :participating
'volume-up'
when :watch
'eye'
when :mention
'at'
when :global
'globe'
end
end
def notification_list_item(notification_level, user_membership)
case notification_level
when Notification::N_DISABLED
update_notification_link(Notification::N_DISABLED, user_membership, 'Disabled', 'microphone-slash')
when Notification::N_PARTICIPATING
update_notification_link(Notification::N_PARTICIPATING, user_membership, 'Participate', 'volume-up')
when Notification::N_WATCH
update_notification_link(Notification::N_WATCH, user_membership, 'Watch', 'eye')
when Notification::N_MENTION
update_notification_link(Notification::N_MENTION, user_membership, 'On mention', 'at')
when Notification::N_GLOBAL
update_notification_link(Notification::N_GLOBAL, user_membership, 'Global', 'globe')
def notification_icon(level, text = nil)
icon("#{notification_icon_class(level)} fw", text: text)
end
def notification_title(level)
case level.to_sym
when :participating
'Participate'
when :mention
'On mention'
else
# do nothing
level.to_s.titlecase
end
end
def update_notification_link(notification_level, user_membership, title, icon)
content_tag(:li, class: active_level_for(user_membership, notification_level)) do
link_to '#', class: 'update-notification', data: { notification_level: notification_level } do
icon("#{icon} fw", text: title)
def notification_list_item(level, setting)
title = notification_title(level)
data = {
notification_level: level,
notification_title: title
}
content_tag(:li, class: ('active' if setting.level == level)) do
link_to '#', class: 'update-notification', data: data do
notification_icon(level, title)
end
end
end
def notification_label(user_membership)
Notification.new(user_membership).to_s
end
def active_level_for(user_membership, level)
'active' if user_membership.notification_level == level
end
end
+5 -12
View File
@@ -65,21 +65,14 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
project_link = link_to project_path(project), { class: "project-item-select-holder" } do
link_output = simple_sanitize(project.name)
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
if current_user
link_output += project_select_tag :project_path,
class: "project-item-select js-projects-dropdown",
data: { include_groups: false, order_by: 'last_activity_at' }
end
link_output
if current_user
project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" })
end
project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user
full_title = namespace_link + ' / ' + project_link
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
full_title = "#{namespace_link} / #{project_link}".html_safe
full_title << ' &middot; '.html_safe << link_to(simple_sanitize(name), url) if name
full_title
end
+2
View File
@@ -20,6 +20,8 @@ module TodosHelper
end
def todo_target_path(todo)
return unless todo.target.present?
anchor = dom_id(todo.note) if todo.note.present?
if todo.for_commit?
+1 -3
View File
@@ -150,13 +150,11 @@ class Commit
end
def hook_attrs(with_changed_files: false)
path_with_namespace = project.path_with_namespace
data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
url: Gitlab::UrlBuilder.build(self),
author: {
name: author_name,
email: author_email
-15
View File
@@ -1,15 +0,0 @@
# == Notifiable concern
#
# Contains notification functionality
#
module Notifiable
extend ActiveSupport::Concern
included do
validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true
end
def notification
@notification ||= Notification.new(self)
end
end
+1
View File
@@ -27,6 +27,7 @@ class Group < Namespace
has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project
has_many :notification_settings, dependent: :destroy, as: :source
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects
+2 -2
View File
@@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base
def related_branches
project.repository.branch_names.select do |branch|
branch.end_with?("-#{iid}")
branch =~ /\A#{iid}-(?!\d+-stable)/i
end
end
@@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base
end
def to_branch_name
"#{title.parameterize}-#{iid}"
"#{iid}-#{title.parameterize}"
end
def can_be_worked_on?(current_user)
+11 -1
View File
@@ -19,7 +19,6 @@
class Member < ActiveRecord::Base
include Sortable
include Notifiable
include Gitlab::Access
attr_accessor :raw_invite_token
@@ -56,12 +55,15 @@ class Member < ActiveRecord::Base
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
after_create :send_invite, if: :invite?
after_create :create_notification_setting, unless: :invite?
after_create :post_create_hook, unless: :invite?
after_update :post_update_hook, unless: :invite?
after_destroy :post_destroy_hook, unless: :invite?
delegate :name, :username, :email, to: :user, prefix: true
default_value_for :notification_level, NotificationSetting.levels[:global]
class << self
def find_by_invite_token(invite_token)
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
@@ -160,6 +162,14 @@ class Member < ActiveRecord::Base
send_invite
end
def create_notification_setting
user.notification_settings.find_or_create_for(source)
end
def notification_setting
@notification_setting ||= user.notification_settings_for(source)
end
private
def send_invite
-1
View File
@@ -24,7 +24,6 @@ class GroupMember < Member
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\ANamespace\z/
default_scope { where(source_type: SOURCE_TYPE) }
-1
View File
@@ -27,7 +27,6 @@ class ProjectMember < Member
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\AProject\z/
default_scope { where(source_type: SOURCE_TYPE) }
+3 -3
View File
@@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches
validate :validate_branches, unless: :allow_broken
validate :validate_fork
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
@@ -218,7 +218,7 @@ class MergeRequest < ActiveRecord::Base
end
if opened? || reopened?
similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :validate_branches,
@@ -345,7 +345,7 @@ class MergeRequest < ActiveRecord::Base
def hook_attrs
attrs = {
source: source_project.hook_attrs,
source: source_project.try(:hook_attrs),
target: target_project.hook_attrs,
last_commit: nil,
work_in_progress: work_in_progress?
-77
View File
@@ -1,77 +0,0 @@
class Notification
#
# Notification levels
#
N_DISABLED = 0
N_PARTICIPATING = 1
N_WATCH = 2
N_GLOBAL = 3
N_MENTION = 4
attr_accessor :target
class << self
def notification_levels
[N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH]
end
def options_with_labels
{
disabled: N_DISABLED,
participating: N_PARTICIPATING,
watch: N_WATCH,
mention: N_MENTION,
global: N_GLOBAL
}
end
def project_notification_levels
[N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL]
end
end
def initialize(target)
@target = target
end
def disabled?
target.notification_level == N_DISABLED
end
def participating?
target.notification_level == N_PARTICIPATING
end
def watch?
target.notification_level == N_WATCH
end
def global?
target.notification_level == N_GLOBAL
end
def mention?
target.notification_level == N_MENTION
end
def level
target.notification_level
end
def to_s
case level
when N_DISABLED
'Disabled'
when N_PARTICIPATING
'Participating'
when N_WATCH
'Watching'
when N_MENTION
'On mention'
when N_GLOBAL
'Global'
else
# do nothing
end
end
end
+28
View File
@@ -0,0 +1,28 @@
class NotificationSetting < ActiveRecord::Base
enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 }
default_value_for :level, NotificationSetting.levels[:global]
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :level, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source",
allow_nil: true }
scope :for_groups, -> { where(source_type: 'Namespace') }
scope :for_projects, -> { where(source_type: 'Project') }
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
unless setting.persisted?
setting.save
end
setting
end
end
+19
View File
@@ -0,0 +1,19 @@
# == Schema Information
#
# Table name: oauth_access_tokens
#
# id :integer not null, primary key
# resource_owner_id :integer
# application_id :integer
# token :string not null
# refresh_token :string
# expires_in :integer
# revoked_at :datetime
# created_at :datetime not null
# scopes :string
#
class OauthAccessToken < ActiveRecord::Base
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
end
+9 -2
View File
@@ -154,6 +154,7 @@ class Project < ActiveRecord::Base
has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy, as: :source
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
@@ -388,9 +389,15 @@ class Project < ActiveRecord::Base
def add_import_job
if forked?
RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
else
RepositoryImportWorker.perform_async(self.id)
job_id = RepositoryImportWorker.perform_async(self.id)
end
if job_id
Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}"
else
Rails.logger.error "Import job failed to start for #{path_with_namespace}"
end
end
+10 -10
View File
@@ -82,17 +82,17 @@ class BambooService < CiService
end
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s
if username.blank? && password.blank?
@response = HTTParty.get(parsed_url.to_s, verify: false)
@response = HTTParty.get(url, verify: false)
else
get_url = "#{url}&os_authType=basic"
url << '&os_authType=basic'
auth = {
username: username,
password: password,
username: username,
password: password
}
@response = HTTParty.get(get_url, verify: false, basic_auth: auth)
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end
@@ -101,11 +101,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page.
"#{bamboo_url}/browse/#{build_key}"
URI.join(bamboo_url, "/browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key']
"#{bamboo_url}/browse/#{result_key}"
URI.join(bamboo_url, "/browse/#{result_key}").to_s
end
end
@@ -134,7 +134,7 @@ class BambooService < CiService
return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data.
self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
verify: false)
url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
self.class.get(url, verify: false)
end
end
@@ -23,7 +23,7 @@ class BuildsEmailService < Service
prop_accessor :recipients
boolean_accessor :add_pusher
boolean_accessor :notify_only_broken_builds
validates :recipients, presence: true, if: :activated?
validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? }
def initialize_properties
if properties.nil?
@@ -87,10 +87,14 @@ class BuildsEmailService < Service
end
def all_recipients(data)
all_recipients = recipients.split(',').compact.reject(&:blank?)
all_recipients = []
unless recipients.blank?
all_recipients += recipients.split(',').compact.reject(&:blank?)
end
if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}"
all_recipients << data[:user][:email]
end
all_recipients
+19 -14
View File
@@ -85,13 +85,15 @@ class TeamcityService < CiService
end
def build_info(sha)
url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
"branch:unspecified:any,number:#{sha}")
url = URI.join(
teamcity_url,
"/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
).to_s
auth = {
username: username,
password: password,
password: password
}
@response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end
def build_page(sha, ref)
@@ -100,12 +102,14 @@ class TeamcityService < CiService
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
"#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
"#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
"&buildTypeId=#{build_type}"
URI.join(
teamcity_url,
"/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
).to_s
end
end
@@ -140,12 +144,13 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref])
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
self.class.post(
URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
end
end
+5 -3
View File
@@ -253,6 +253,8 @@ class Repository
# This ensures this particular cache is flushed after the first commit to a
# new repository.
expire_emptiness_caches if empty?
expire_branch_count_cache
expire_tag_count_cache
end
def expire_branch_cache(branch_name = nil)
@@ -896,9 +898,9 @@ class Repository
end
def main_language
unless empty?
Linguist::Repository.new(rugged, rugged.head.target_id).language
end
return if empty? || rugged.head_unborn?
Linguist::Repository.new(rugged, rugged.head.target_id).language
end
def avatar
+13 -5
View File
@@ -143,6 +143,7 @@ class User < ActiveRecord::Base
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
#
# Validations
@@ -157,7 +158,7 @@ class User < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validates :notification_level, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
@@ -190,6 +191,13 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files]
# Notification level
# Note: When adding an option, it MUST go on the end of the array.
#
# TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813
# Because user.notification_disabled? is much better than user.disabled?
enum notification_level: [:disabled, :participating, :watch, :global, :mention]
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
@@ -349,10 +357,6 @@ class User < ActiveRecord::Base
"#{self.class.reference_prefix}#{username}"
end
def notification
@notification ||= Notification.new(self)
end
def generate_password
if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8)
@@ -827,6 +831,10 @@ class User < ActiveRecord::Base
end
end
def notification_settings_for(source)
notification_settings.find_or_initialize_by(source: source)
end
private
def projects_union
+1 -1
View File
@@ -3,7 +3,7 @@ module Issues
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_url = Gitlab::UrlBuilder.build(issue)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue_data
end
+1 -2
View File
@@ -20,8 +20,7 @@ module MergeRequests
def hook_data(merge_request, action)
hook_data = merge_request.to_hook_data(current_user)
merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
hook_data[:object_attributes][:url] = merge_request_url
hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:action] = action
hook_data
end

Some files were not shown because too many files have changed in this diff Show More