Commit 5e16aa9c authored by Sacred Seven's avatar Sacred Seven

Merged 7.5.1

parents 6bca0b43 36679b57
......@@ -40,3 +40,4 @@ public/assets/
dump.rdb
tags
.AppleDouble
.gitlab_shell_secret
v 7.5.1
- Add missing timestamps to 'members' table
v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert)
- Add time zone configuration on gitlab.yml (Sullivan Senechal)
- Fix LDAP authentication for Git HTTP access
- Run 'GC.start' after every EmailsOnPushWorker job
- Fix LDAP config lookup for provider 'ldap'
- Drop all sequences during Postgres database restore
- Project title links to project homepage (Ben Bodenmiller)
- Add Atlassian Bamboo CI service (Drew Blessing)
- Mentioned @user will receive email even if he is not participating in issue or commit
- Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
- Tie up loose ends with annotated tags: API & UI (Sean Edge)
- Return valid json for deleting branch via API (sponsored by O'Reilly Media)
- Expose username in project events API (sponsored by O'Reilly Media)
- Adds comments to commits in the API
- Performance improvements
- Fix post-receive issue for projects with deleted forks
- New gitlab-shell version with custom hooks support
- Improve code
- GitLab CI 5.2+ support (does not support older versions)
- Fixed bug when you can not push commits starting with 000000 to protected branches
- Added a password strength indicator
- Change project name and path in one form
- Display renamed files in diff views (Vinnie Okada)
- Add timezone configuration to gitlab.yml
- Fix raw view for public snippets
- Use secret token with GitLab internal API.
v 7.4.3
- Fix raw snippets view
- Fix security issue for member api
- Fix buildbox integration
v 7.4.2
- Fix internal snippet exposing for unauthenticated users
......@@ -41,7 +77,7 @@ v 7.4.0
- Fix ambiguous sha problem with mentioned commit
- Fixed bug with apostrophe when at mentioning users
- Add active directory ldap option
- Developers can push to wiki repo. Protected branches does not affect wiki repo any more
- Developers can push to wiki repo. Protected branches does not affect wiki repo any more
- Faster rev list
- Fix branch removal
......
......@@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and
Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
### Merge request guidelines
If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
......@@ -101,7 +103,11 @@ For examples of feedback on merge requests please look at already [closed merge
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
1. It conforms to the following style guides
1. It conforms to the following style guides.
If your change touches a line that does not follow the style,
modify the entire line to follow it. This prevents linting tools from generating warnings.
Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
may leave style non-compliant.
## Style guides
......
source "https://rubygems.org"
source "http://rubygems.org"
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
......@@ -34,13 +34,13 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '7.0.0.rc10'
gem "gitlab_git", '7.0.0.rc11'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
# Git Wiki
gem 'gollum-lib', '~> 3.0.0'
......@@ -146,7 +146,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2"
gem "gemnasium-gitlab-service", "~> 0.2"
# Slack integration
gem "slack-notifier", "~> 0.3.2"
gem "slack-notifier", "~> 1.0.0"
# d3
gem "d3_rails", "~> 3.1.4"
......@@ -189,6 +189,7 @@ gem "gon", '~> 5.0.0'
gem 'nprogress-rails'
gem 'request_store'
gem "virtus"
gem 'addressable'
group :development do
gem "annotate", "~> 2.6.0.beta2"
......
GEM
remote: https://rubygems.org/
remote: http://rubygems.org/
specs:
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
......@@ -179,17 +179,17 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
gitlab_git (7.0.0.rc10)
gitlab_git (7.0.0.rc11)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
rugged (~> 0.21.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.1.0)
net-ldap (~> 0.7.0)
gitlab_omniauth-ldap (1.2.0)
net-ldap (~> 0.9)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1)
rubyntlm (~> 0.3)
gollum-lib (3.0.0)
github-markup (~> 1.1.0)
gitlab-grit (~> 2.6.5)
......@@ -300,7 +300,7 @@ GEM
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
net-ldap (0.7.0)
net-ldap (0.9.0)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
......@@ -446,7 +446,7 @@ GEM
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-progressbar (1.2.0)
rubyntlm (0.1.1)
rubyntlm (0.4.0)
rubypants (0.2.0)
rugged (0.21.0)
safe_yaml (0.9.7)
......@@ -489,7 +489,7 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
slack-notifier (0.3.2)
slack-notifier (1.0.0)
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
......@@ -593,6 +593,7 @@ DEPENDENCIES
RedCloth
ace-rails-ap
acts-as-taggable-on
addressable
annotate (~> 2.6.0.beta2)
asciidoctor (= 0.1.4)
awesome_print
......@@ -625,9 +626,9 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc10)
gitlab_git (= 7.0.0.rc11)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.1.0)
gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0)
gon (~> 5.0.0)
grape (~> 0.6.1)
......@@ -690,7 +691,7 @@ DEPENDENCIES
simplecov
sinatra
six
slack-notifier (~> 0.3.2)
slack-notifier (~> 1.0.0)
slim
spinach-rails
spring (= 1.1.3)
......
......@@ -55,14 +55,8 @@ Since a manual installation is a lot of work and error prone we strongly recomme
## Third-party applications
Access GitLab from multiple platforms with applications below.
These applications are maintained by contributors, GitLab B.V. does not offer support for them.
- [iPhone app](http://gitlabcontrol.com/)
- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
- [Command line client](https://github.com/drewblessing/gitlab-cli)
- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
There are a lot of applications and API wrappers for GitLab.
Find them [on our website](https://about.gitlab.com/applications/).
### New versions
......
7.4.2
7.5.1
\ No newline at end of file
class Activities
class @Activities
constructor: ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) =>
......@@ -27,5 +27,3 @@ class Activities
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
@Activities = Activities
class Admin
class @Admin
constructor: ->
$('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation')
......@@ -51,5 +51,3 @@ class Admin
$('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
@Admin = Admin
......@@ -18,6 +18,7 @@
#= require jquery.turbolinks
#= require turbolinks
#= require bootstrap
#= require password_strength
#= require select2
#= require raphael
#= require g.raphael-min
......@@ -68,7 +69,7 @@ window.extractLast = (term) ->
return split( term ).pop()
window.rstrip = (val) ->
return val.replace(/\s+$/, '')
return if val then val.replace(/\s+$/, '') else val
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
......
class BlobView
class @BlobView
constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
......@@ -71,6 +71,3 @@ class BlobView
# Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines)
@BlobView = BlobView
class Commit
class @Commit
constructor: ->
$('.files .diff-file').each ->
new CommitFile(this)
@Commit = Commit
class CommitFile
class @CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
@CommitFile = CommitFile
class ImageFile
class @ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
......@@ -124,5 +124,3 @@ class ImageFile
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
@ImageFile = ImageFile
class CommitsList
class @CommitsList
@data =
ref: null
limit: 0
......@@ -53,5 +53,3 @@ class CommitsList
@disable
callback: =>
this.getOld()
this.CommitsList = CommitsList
class ConfirmDangerModal
class @ConfirmDangerModal
constructor: (form, text) ->
@form = form
$('.js-confirm-text').text(text || '')
......@@ -16,5 +16,3 @@ class ConfirmDangerModal
$('.js-confirm-danger-submit').on 'click', =>
@form.submit()
@ConfirmDangerModal = ConfirmDangerModal
class Dashboard
class @Dashboard
constructor: ->
@initSidebarTab()
......@@ -28,6 +28,3 @@ class Dashboard
# show tab from cookie
sidebar_filter = $.cookie(key)
$("#" + sidebar_filter).tab('show') if sidebar_filter
@Dashboard = Dashboard
class Diff
class @Diff
UNFOLD_COUNT = 20
constructor: ->
$(document).on('click', '.js-unfold', (event) =>
......@@ -41,6 +41,3 @@ class Diff
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
@Diff = Diff
......@@ -58,15 +58,11 @@ class Dispatcher
when 'groups:show', 'projects:show'
new Activities()
shortcut_handler = new ShortcutsNavigation()
when 'projects:new'
new Project()
when 'projects:edit'
new Project()
shortcut_handler = new ShortcutsNavigation()
when 'projects:teams:members:index'
new TeamMembers()
when 'groups:members'
new GroupMembers()
new UsersSelect()
when 'groups:new', 'groups:edit', 'admin:groups:edit'
new GroupAvatar()
when 'projects:tree:show'
new TreeView()
shortcut_handler = new ShortcutsNavigation()
......@@ -79,13 +75,33 @@ class Dispatcher
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
when 'users:show'
new User()
switch path.first()
when 'admin' then new Admin()
when 'admin'
new Admin()
switch path[1]
when 'groups'
new UsersSelect()
when 'projects'
new NamespaceSelect()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
new Profile()
when 'projects'
new Project()
switch path[1]
when 'edit'
shortcut_handler = new ShortcutsNavigation()
new ProjectNew()
when 'new'
new ProjectNew()
when 'show'
new ProjectShow()
when 'issues', 'merge_requests'
new ProjectUsersSelect()
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
......@@ -94,6 +110,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
new UsersSelect()
# If we haven't installed a custom shortcut handler, install the default one
......
class Flash
class @Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
......@@ -10,5 +10,3 @@ class Flash
flash.click -> $(@).fadeOut()
flash.show()
@Flash = Flash
class @GroupAvatar
constructor: ->
$('.js-choose-group-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-group-avatar-input").click()
$('.js-group-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
class GroupMembers
class @GroupMembers
constructor: ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
@GroupMembers = GroupMembers
$ ->
# avatar
$('.js-choose-group-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-group-avatar-input").click()
$('.js-group-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
class Issue
class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", ->
......@@ -15,5 +15,3 @@ class Issue
"issue"
updateTaskState
)
@Issue = Issue
class Labels
class @Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
......@@ -31,5 +31,3 @@ class Labels
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
@Labels = Labels
class MergeRequest
class @MergeRequest
constructor: (@opts) ->
@initContextWidget()
this.$el = $('.merge-request')
......@@ -132,5 +132,3 @@ class MergeRequest
this.$('.automerge_widget').hide()
this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show()
this.MergeRequest = MergeRequest
class Milestone
class @Milestone
@updateIssue: (li, issue_url, data) ->
$.ajax
type: "PUT"
......@@ -115,5 +115,3 @@ class Milestone
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
).disableSelection()
@Milestone = Milestone
$ ->
namespaceFormatResult = (namespace) ->
markup = "<div class='namespace-result'>"
markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
markup += "<span class='namespace-path'>" + namespace.path + "</span>"
markup += "</div>"
markup
class @NamespaceSelect
constructor: ->
namespaceFormatResult = (namespace) ->
markup = "<div class='namespace-result'>"
markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
markup += "<span class='namespace-path'>" + namespace.path + "</span>"
markup += "</div>"
markup
formatSelection = (namespace) ->
namespace.kind + ": " + namespace.path
formatSelection = (namespace) ->
namespace.kind + ": " + namespace.path
$('.ajax-namespace-select').each (i, select) ->
$(select).select2
placeholder: "Search for namespace"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.namespaces query.term, (namespaces) ->
data = { results: namespaces }
query.callback(data)
$('.ajax-namespace-select').each (i, select) ->
$(select).select2
placeholder: "Search for namespace"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.namespaces query.term, (namespaces) ->
data = { results: namespaces }
query.callback(data)
dropdownCssClass: "ajax-namespace-dropdown"
formatResult: namespaceFormatResult
formatSelection: formatSelection
dropdownCssClass: "ajax-namespace-dropdown"
formatResult: namespaceFormatResult
formatSelection: formatSelection
class Notes
class @Notes
@interval: null
constructor: (notes_url, note_ids, last_fetched_at) ->
......@@ -514,7 +514,3 @@ class Notes
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
@Notes = Notes
class NotesVotes
class @NotesVotes
updateVotes: ->
votes = $("#votes .votes")
notes = $("#notes-list .note .vote")
......@@ -18,5 +18,3 @@ class NotesVotes
# replace vote numbers
votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes)
votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes)
@NotesVotes = NotesVotes
#= require pwstrength-bootstrap-1.2.2
overwritten_messages =
wordSimilarToUsername: "Your password should not contain your username"
overwritten_rules =
wordSequences: false
options =
showProgressBar: false
showVerdicts: false
showPopover: true
showErrors: true
showStatus: true
errorMessages: overwritten_messages
$(document).ready ->
profileOptions = {}
profileOptions.ui = options
profileOptions.rules =
activated: overwritten_rules
deviseOptions = {}
deviseOptions.common =
usernameField: "#user_username"
deviseOptions.ui = options
deviseOptions.rules =
activated: overwritten_rules
$("#user_password_profile").pwstrength profileOptions
$("#user_password_sign_up").pwstrength deviseOptions
$("#user_password_recover").pwstrength deviseOptions
$ ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
# Submit the form
$('.edit_user').submit()
class @Profile
constructor: ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
# Submit the form
$('.edit_user').submit()
new Flash("Appearance settings saved", "notice")
new Flash("Appearance settings saved", "notice")
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
$(this).find('.update-success').hide()
$(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
$(this).find('.update-success').hide()
$(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
$(this).find('.loading-gif').hide()
$('.update-username form').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
$(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
$('.js-user-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
$('.profile-groups-avatars').tooltip("placement": "top")
$('.js-user-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
class Project
class @Project
constructor: ->
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
@initEvents()
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
@Project = Project
$ ->
# Git clone panel switcher
scope = $ '.git-clone-holder'
if scope.length > 0
$('a, button', scope).click ->
$('a, button', scope).removeClass 'active'
$(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone'
$(".clone").text("").append $(@).data 'clone'
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
$('.hide-no-ssh-message').on 'click', (e) ->
path = '/'
$.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
# Git clone panel switcher
scope = $ '.git-clone-holder'
if scope.length > 0
$('a, button', scope).click ->
$('a, button', scope).removeClass 'active'
$(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone'
$(".clone").text("").append $(@).data 'clone'
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
$('.hide-no-ssh-message').on 'click', (e) ->
path = '/'
$.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
class ProjectImport
class @ProjectImport
constructor: ->
setTimeout ->
Turbolinks.visit(location.href)
, 5000
@ProjectImport = ProjectImport
class @ProjectNew
constructor: ->
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
@initEvents()
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
class @ProjectShow
constructor: ->
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
@projectUsersSelect =
init: ->
$('.ajax-project-users-select').each (i, select) ->
class @ProjectUsersSelect
constructor: ->
$('.ajax-project-users-select').each (i, select) =>
project_id = $(select).data('project-id') || $('body').data('project-id')
$(select).select2
......@@ -28,14 +28,16 @@
Api.user(id, callback)
formatResult: projectUsersSelect.projectUserFormatResult
formatSelection: projectUsersSelect.projectUserFormatSelection
formatResult: (args...) =>
@formatResult(args...)
formatSelection: (args...) =>
@formatSelection(args...)
dropdownCssClass: "ajax-project-users-dropdown"
dropdownAutoWidth: true
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
projectUserFormatResult: (user) ->
formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
......@@ -52,8 +54,5 @@
<div class='user-username'>#{user.username}</div>
</div>"
projectUserFormatSelection: (user) ->
formatSelection: (user) ->
user.name
$ ->
projectUsersSelect.init()
class SearchAutocomplete
class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id
project_ref = '' unless project_ref
......@@ -9,5 +9,3 @@ class SearchAutocomplete
minLength: 1
select: (event, ui) ->
location.href = ui.item.url
@SearchAutocomplete = SearchAutocomplete
class window.StatGraph
class @StatGraph
@log: {}
@get_log: ->
@log
......
class window.ContributorsStatGraph
class @ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits")
......
class window.ContributorsGraph
class @ContributorsGraph
MARGIN:
top: 20
right: 20
......@@ -44,7 +44,7 @@ class window.ContributorsGraph
set_data: (data) ->
@data = data
class window.ContributorsMasterGraph extends ContributorsGraph
class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width() - 70
@height = 200
......@@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph
@svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis)
class window.ContributorsAuthorGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width()/2 - 100
@height = 200
......
class TeamMembers
constructor: ->
$('.team-members .project-access-select').on "change", ->
$(this.form).submit()
@TeamMembers = TeamMembers
class TreeView
class @TreeView
constructor: ->
@initKeyNav()
......@@ -39,5 +39,3 @@ class TreeView
else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href')
Turbolinks.visit(path)
@TreeView = TreeView
class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
$ ->
userFormatResult = (user) ->
class @UsersSelect
constructor: ->
$('.ajax-users-select').each (i, select) =>
$(select).select2
placeholder: "Search for a user"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.users query.term, (users) ->
data = { results: users }
query.callback(data)
initSelection: (element, callback) ->
id = $(element).val()
if id isnt ""
Api.user(id, callback)
formatResult: (args...) =>
@formatResult(args...)
formatSelection: (args...) =>
@formatSelection(args...)
dropdownCssClass: "ajax-users-dropdown"
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
......@@ -11,27 +36,5 @@ $ ->
<div class='user-username'>#{user.username}</div>
</div>"
userFormatSelection = (user) ->
formatSelection: (user) ->
user.name
$('.ajax-users-select').each (i, select) ->
$(select).select2
placeholder: "Search for a user"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.users query.term, (users) ->
data = { results: users }
query.callback(data)
initSelection: (element, callback) ->
id = $(element).val()
if id isnt ""
Api.user(id, callback)
formatResult: userFormatResult
formatSelection: userFormatSelection
dropdownCssClass: "ajax-users-dropdown"
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
class Wikis
class @Wikis
constructor: ->
$('.build-new-wiki').bind "click", ->
field = $('#new_wiki_path')
......@@ -7,6 +7,3 @@ class Wikis
if(slug.length > 0)
location.href = path + "/" + slug
@Wikis = Wikis
// Details
//--------
.js-details-container .content { display: none; }
.js-details-container .content.hide { display: block; }
.js-details-container.open .content { display: block; }
.js-details-container.open .content.hide { display: none; }
.js-details-container {
.content {
display: none;
&.hide { display: block; }
}
&.open .content {
display: block;
&.hide { display: none; }
}
}
// Toggle between two states.
.js-toggler-container .turn-on { display: block; }
.js-toggler-container .turn-off { display: none; }
.js-toggler-container.on .turn-on { display: none; }
.js-toggler-container.on .turn-off { display: block; }
.js-toggler-container {
.turn-on { display: block; }
.turn-off { display: none; }
&.on {
.turn-on { display: none; }
.turn-off { display: block; }
}
}
......@@ -186,3 +186,11 @@
}
}
}
.readme-holder .wiki, .note-body, .wiki-holder {
.white {
.highlight, pre, .hljs {
background: #F9F9F9;
}
}
}
/** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
......@@ -75,7 +75,7 @@
}
.participants {
margin-bottom: 10px;
margin-bottom: 20px;
}
.issues_bulk_update {
......
......@@ -113,30 +113,36 @@
font-size: 15px;
border-bottom: 1px solid #BBB;
color: #777;
background-color: #F5F5F5;
&.ci-success {
color: $bg_success;
border-color: $border_success;
background-color: #F1FAF1;
}
&.ci-pending {
color: #548;
border-color: #548;
background-color: #F4F1FA;
}
&.ci-running {
color: $bg_warning;
border-color: $border_warning;
background-color: #FAF5F1;
}
&.ci-failed {
color: $bg_danger;
border-color: $border_danger;
background-color: #FAF1F1;
}
&.ci-error {
color: $bg_danger;
border-color: $border_danger;
background-color: #FAF1F1;
}
}
......
......@@ -111,3 +111,20 @@
height: 50px;
}
}
//CSS for password-strength indicator
#password-strength {
margin-bottom: 0;
}
.has-success input {
background-color: #D6F1D7 !important;
}
.has-error input {
background-color: #F3CECE !important;
}
.has-warning input {
background-color: #FFE9A4 !important;
}
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end
end
......@@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController
protected
def project
id = params[:project_id] || params[:id]
@project = Project.find_with_namespace(id)
@project = Project.find_with_namespace(params[:id])
@project || render_404
end
def group
@group ||= project.group
end
def repository
@repository ||= project.repository
@group ||= @project.group
end
end
......@@ -5,9 +5,7 @@ class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :reject_blocked!
before_filter :check_password_expiration
before_filter :add_abilities
before_filter :ldap_security_check
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
......@@ -73,7 +71,7 @@ class ApplicationController < ActionController::Base
end
def abilities
@abilities ||= Six.new
Ability.abilities
end
def can?(object, action, subject)
......@@ -81,28 +79,31 @@ class ApplicationController < ActionController::Base
end
def project
id = params[:project_id] || params[:id]
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\Z/, '') and return
end
unless @project
id = params[:project_id] || params[:id]
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\Z/, '') and return
end
@project = Project.find_with_namespace(id)
@project = Project.find_with_namespace(id)
if @project and can?(current_user, :read_project, @project)
@project
elsif current_user.nil?
@project = nil
authenticate_user!
else
@project = nil
render_404 and return
if @project and can?(current_user, :read_project, @project)
@project
elsif current_user.nil?
@project = nil
authenticate_user!
else
@project = nil
render_404 and return
end
end
@project
end
def repository
......@@ -111,22 +112,10 @@ class ApplicationController < ActionController::Base
nil
end
def add_abilities
abilities << Ability
end
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
def authorize_code_access!
return access_denied! unless can?(current_user, :download_code, project)
end
def authorize_push!
return access_denied! unless can?(current_user, :push_code, project)
end
def authorize_labels!
# Labels should be accessible for issues and/or merge requests
authorize_read_issue! || authorize_read_merge_request!
......@@ -170,9 +159,6 @@ class ApplicationController < ActionController::Base
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
def dev_tools
end
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
......
class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked, :set_current_user_for_observers,
:add_abilities
:reject_blocked, :set_current_user_for_observers
layout "explore"
......
class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked,
:add_abilities
:reject_blocked
layout 'explore'
......
class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
......@@ -3,8 +3,7 @@ class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
......@@ -3,10 +3,9 @@ class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :authorize_push!, only: [:destroy]
before_filter :authorize_push_code!, only: [:destroy]
before_filter :blob
......
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
# Authorize
before_filter :authorize_read_project!
before_filter :require_non_empty_project
before_filter :authorize_code_access!
before_filter :authorize_push!, only: [:create, :destroy]
before_filter :authorize_download_code!
before_filter :authorize_push_code!, only: [:create, :destroy]
def index
@sort = params[:sort] || 'name'
......@@ -17,8 +17,11 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
branch_name = sanitize(strip_tags(params[:branch_name]))
ref = sanitize(strip_tags(params[:ref]))
result = CreateBranchService.new(project, current_user).
execute(params[:branch_name], params[:ref])
execute(branch_name, ref)
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
......
......@@ -3,20 +3,19 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :commit
def show
return git_not_found! unless @commit
@line_notes = project.notes.for_commit_id(commit.id).inline
@branches = project.repository.branch_names_contains(commit.id)
@line_notes = @project.notes.for_commit_id(commit.id).inline
@branches = @project.repository.branch_names_contains(commit.id)
@diffs = @commit.diffs
@note = project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
@note = @project.build_commit_note(commit)
@notes_count = @project.notes.for_commit_id(commit.id).count
@notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh
@noteable = @commit
@comments_allowed = @reply_allowed = true
@comments_target = {
......@@ -32,6 +31,6 @@ class Projects::CommitController < Projects::ApplicationController
end
def commit
@commit ||= project.repository.commit(params[:id])
@commit ||= @project.repository.commit(params[:id])
end
end
......@@ -4,8 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
class Projects::CompareController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def index
......
......@@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def enable
project.deploy_keys << available_keys.find(params[:id])
@project.deploy_keys << available_keys.find(params[:id])
redirect_to project_deploy_keys_path(@project)
end
......
class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
before_filter :authorize_push!
before_filter :authorize_push_code!
before_filter :from_merge_request
before_filter :after_edit_path
......
class Projects::GraphsController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
......@@ -3,8 +3,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ApplicationHelper
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :authorize_push!
before_filter :authorize_push_code!
def show
end
......
......@@ -3,8 +3,7 @@ class Projects::RawController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
......@@ -2,8 +2,7 @@ class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def switch
......
class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def archive
......
......@@ -41,7 +41,8 @@ class Projects::ServicesController < Projects::ApplicationController
params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key
)
end
end
class Projects::TagsController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :require_non_empty_project
before_filter :authorize_code_access!
before_filter :authorize_push!, only: [:create]
before_filter :authorize_download_code!
before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy]
def index
......
......@@ -10,7 +10,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def new
@user_project_relation = project.project_members.new
@user_project_relation = @project.project_members.new
end
def create
......@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def update
@user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid?
......@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def destroy
@user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy
respond_to do |format|
......@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def leave
project.project_members.find_by(user_id: current_user).destroy
@project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
format.html { redirect_to :back }
......
......@@ -4,9 +4,7 @@ class ProjectsController < ApplicationController
before_filter :repository, except: [:new, :create]
# Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create]
......@@ -53,8 +51,6 @@ class ProjectsController < ApplicationController
return
end
return authenticate_user! unless @project.public? || current_user
limit = (params[:limit] || 20).to_i
@events = @project.events.recent
@events = event_filter.apply_filter(@events)
......@@ -76,7 +72,7 @@ class ProjectsController < ApplicationController
end
def import
if project.import_finished?
if @project.import_finished?
redirect_to @project
return
end
......@@ -98,7 +94,7 @@ class ProjectsController < ApplicationController
end
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
......@@ -148,8 +144,8 @@ class ProjectsController < ApplicationController
end
def archive
return access_denied! unless can?(current_user, :archive_project, project)
project.archive!
return access_denied! unless can?(current_user, :archive_project, @project)
@project.archive!
respond_to do |format|
format.html { redirect_to @project }
......@@ -157,8 +153,8 @@ class ProjectsController < ApplicationController
end
def unarchive
return access_denied! unless can?(current_user, :archive_project, project)
project.unarchive!
return access_denied! unless can?(current_user, :archive_project, @project)
@project.unarchive!
respond_to do |format|
format.html { redirect_to @project }
......
......@@ -9,7 +9,7 @@ class SnippetsController < ApplicationController
before_filter :set_title
skip_before_filter :authenticate_user!, only: [:index, :user_index, :show]
skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw]
respond_to :html
......
......@@ -48,7 +48,7 @@ class IssuableFinder
else
[]
end
elsif current_user && params[:authorized_only].presence
elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
......@@ -142,4 +142,8 @@ class IssuableFinder
def project
Project.where(id: params[:project_id]).first if params[:project_id].present?
end
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
end
......@@ -87,8 +87,8 @@ module CommitsHelper
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
source_name = commit.send "#{options[:source]}_name".to_sym
source_email = commit.send "#{options[:source]}_email".to_sym
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
user = User.find_for_commit(source_email, source_name)
person_name = user.nil? ? source_name : user.name
......@@ -124,4 +124,8 @@ module CommitsHelper
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
def clean(string)
Sanitize.clean(string, remove_contents: true)
end
end
module EmailsHelper
# Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url)
name = action_title(url)
if name
data = {
"@context" => "http://schema.org",
"@type" => "EmailMessage",
"action" => {
"@type" => "ViewAction",
"name" => name,
"url" => url,
}
}
content_tag :script, type: 'application/ld+json' do
data.to_json.html_safe
end
end
end
def action_title(url)
return unless url
["merge_requests", "issues", "commit"].each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
end
end
end
......@@ -62,6 +62,19 @@ module IssuesHelper
''
end
def issue_timestamp(issue)
# Shows the created at time and the updated at time if different
ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}"
if issue.updated_at != issue.created_at
ts << capture_haml do
haml_tag :small do
haml_concat " (Edited #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')})"
end
end
end
ts.html_safe
end
# Checks if issues_tracker setting exists in gitlab.yml
def external_issues_tracker_enabled?
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
......
......@@ -42,12 +42,12 @@ module ProjectsHelper
def project_title(project)
if project.group
content_tag :span do
link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
link_to(simple_sanitize(project.group.name), group_path(project.group)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end
else
owner = project.namespace.owner
content_tag :span do
link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name
link_to(simple_sanitize(owner.name), user_path(owner)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end
end
end
......
......@@ -66,7 +66,7 @@ module TreeHelper
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
parts = @path.split("\/")
parts = @path.split('/')
yield('..', nil) if parts.count > max_links
......
......@@ -11,6 +11,7 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
......
......@@ -262,5 +262,13 @@ class Ability
end
rules
end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << self
abilities
end
end
end
end
......@@ -131,9 +131,10 @@ module Issuable
users.concat(mentions.reduce([], :|)).uniq
end
def to_hook_data
def to_hook_data(user)
{
object_kind: self.class.name.underscore,
user: user.hook_attrs,
object_attributes: hook_attrs
}
end
......
......@@ -52,11 +52,7 @@ module Mentionable
if identifier == "all"
users += project.team.members.flatten
else
if has_project
id = project.team.members.find_by(username: identifier).try(:id)
else
id = User.find_by(username: identifier).try(:id)
end
id = User.find_by(username: identifier).try(:id)
users << User.find(id) unless id.blank?
end
end
......
......@@ -186,10 +186,6 @@ class Event < ActiveRecord::Base
data[:ref]["refs/heads"]
end
def new_branch?
commit_from =~ /^00000/
end
def new_ref?
commit_from =~ /^00000/
end
......
......@@ -80,7 +80,7 @@ class Note < ActiveRecord::Base
note_options = {
project: project,
author: author,
note: "_mentioned in #{gfm_reference}_",
note: cross_reference_note_content(gfm_reference),
system: true
}
......@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base
note_options.merge!(noteable: noteable)
end
create(note_options)
create(note_options) unless cross_reference_disallowed?(noteable, mentioner)
end
def create_milestone_change_note(noteable, project, author, milestone)
......@@ -165,6 +165,15 @@ class Note < ActiveRecord::Base
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym
end
# Determine if cross reference note should be created.
# eg. mentioning a commit in MR comments which exists inside a MR
# should not create "mentioned in" note.
def cross_reference_disallowed?(noteable, mentioner)
if mentioner.kind_of?(MergeRequest)
mentioner.commits.map(&:id).include? noteable.id
end
end
# Determine whether or not a cross-reference note already exists.
def cross_reference_exists?(noteable, mentioner)
gfm_reference = mentioner_gfm_ref(noteable, mentioner)
......@@ -174,7 +183,7 @@ class Note < ActiveRecord::Base
where(noteable_id: noteable.id)
end
notes.where('note like ?', "_mentioned in #{gfm_reference}_").
notes.where('note like ?', cross_reference_note_content(gfm_reference)).
system.any?
end
......@@ -182,8 +191,16 @@ class Note < ActiveRecord::Base
where("note like :query", query: "%#{query}%")
end
def cross_reference_note_prefix
'_mentioned in '
end
private
def cross_reference_note_content(gfm_reference)
cross_reference_note_prefix + "#{gfm_reference}_"
end
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
......@@ -243,12 +260,16 @@ class Note < ActiveRecord::Base
def commit_author
@commit_author ||=
project.users.find_by(email: noteable.author_email) ||
project.users.find_by(name: noteable.author_name)
project.team.users.find_by(email: noteable.author_email) ||
project.team.users.find_by(name: noteable.author_name)
rescue
nil
end
def cross_reference?
note.start_with?(self.class.cross_reference_note_prefix)
end
def find_diff
return nil unless noteable && noteable.diffs.present?
......@@ -296,7 +317,7 @@ class Note < ActiveRecord::Base
end
def diff_file_index
line_code.split('_')[0]
line_code.split('_')[0] if line_code
end
def diff_file_name
......@@ -312,11 +333,11 @@ class Note < ActiveRecord::Base
end
def diff_old_line
line_code.split('_')[1].to_i
line_code.split('_')[1].to_i if line_code
end
def diff_new_line
line_code.split('_')[2].to_i
line_code.split('_')[2].to_i if line_code
end
def generate_line_code(line)
......@@ -337,6 +358,20 @@ class Note < ActiveRecord::Base
@diff_line
end
def diff_line_type
return @diff_line_type if @diff_line_type
if diff
diff_lines.each do |line|
if generate_line_code(line) == self.line_code
@diff_line_type = line.type
end
end
end
@diff_line_type
end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
......
......@@ -65,6 +65,7 @@ class Project < ActiveRecord::Base
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
......@@ -173,7 +174,7 @@ class Project < ActiveRecord::Base
end
def with_push
includes(:events).where('events.action = ?', Event::PUSHED)
joins(:events).where('events.action = ?', Event::PUSHED)
end
def active
......@@ -313,7 +314,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox)
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo)
end
def gitlab_ci?
......@@ -401,43 +402,8 @@ class Project < ActiveRecord::Base
end
def update_merge_requests(oldrev, newrev, ref, user)
return true unless ref =~ /heads/
branch_name = ref.gsub("refs/heads/", "")
commits = self.repository.commits_between(oldrev, newrev)
c_ids = commits.map(&:id)
# Close merge requests
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
mrs.uniq.each do |merge_request|
MergeRequests::MergeService.new.execute(merge_request, user, nil)
end
# Update code for merge requests into project between project branches
mrs = self.merge_requests.opened.by_branch(branch_name).to_a
# Update code for merge requests between project and project fork
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
mrs.uniq.each do |merge_request|
merge_request.reload_code
merge_request.mark_as_unchecked
end
# Add comment about pushing new commits to merge requests
comment_mr_with_commits(branch_name, commits, user)
true
end
def comment_mr_with_commits(branch_name, commits, user)
mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a
mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
mrs.uniq.each do |merge_request|
Note.create_new_commits_note(merge_request, merge_request.project,
user, commits)
end
MergeRequests::RefreshService.new(self, user).
execute(oldrev, newrev, ref)
end
def valid_repo?
......
class BambooService < CiService
include HTTParty
prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true,
format: { with: URI::regexp }, if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username, presence: true,
if: ->(service) { service.password? }, if: :activated?
validates :password, presence: true,
if: ->(service) { service.username? }, if: :activated?
attr_accessor :response
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def title
'Atlassian Bamboo CI'
end
def description
'A continuous integration and build server'
end
def help
'You must set up automatic revision labeling and a repository trigger in Bamboo.'
end
def to_param
'bamboo'
end
def fields
[
{ type: 'text', name: 'bamboo_url',
placeholder: 'Bamboo root URL like https://bamboo.example.com' },
{ type: 'text', name: 'build_key',
placeholder: 'Bamboo build plan key like KEY' },
{ type: 'text', name: 'username',
placeholder: 'A user with API access, if applicable' },
{ type: 'password', name: 'password' },
]
end
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
if username.blank? && password.blank?
@response = HTTParty.get(parsed_url.to_s, verify: false)
else
get_url = "#{url}&os_authType=basic"
auth = {
username: username,
password: password,
}
@response = HTTParty.get(get_url, verify: false, basic_auth: auth)
end
end
def build_page(sha)
build_info(sha) if @response.nil? || !@response.code
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}"
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}"
end
end
def commit_status(sha)
build_info(sha) if @response.nil? || !@response.code
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404 || @response['results']['results']['size'] == '0'
'Pending'
else
@response['results']['results']['result']['buildState']
end
if status.include?('Success')
'success'
elsif status.include?('Failed')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def execute(_data)
# Bamboo requires a GET and does not take any data.
self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
verify: false)
end
end
......@@ -12,6 +12,8 @@
# properties :text
#
require "addressable/uri"
class BuildboxService < CiService
prop_accessor :project_url, :token
......
......@@ -37,13 +37,12 @@ class FlowdockService < Service
end
def execute(push_data)
repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Flowdock::Git.post(
push_data[:ref],
push_data[:before],
push_data[:after],
token: token,
repo: repo_path,
repo: project.repository.path_to_repo,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
......
......@@ -38,14 +38,13 @@ class GemnasiumService < Service
end
def execute(push_data)
repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Gemnasium::GitlabService.execute(
ref: push_data[:ref],
before: push_data[:before],
after: push_data[:after],
token: token,
api_key: api_key,
repo: repo_path
repo: project.repository.path_to_repo
)
end
end
......@@ -28,7 +28,7 @@ class GitlabCiService < CiService
end
def commit_status_path(sha)
project_url + "/builds/#{sha}/status.json?token=#{token}"
project_url + "/commits/#{sha}/status.json?token=#{token}"
end
def get_ci_build(sha)
......@@ -55,7 +55,7 @@ class GitlabCiService < CiService
end
def build_page(sha)
project_url + "/builds/#{sha}"
project_url + "/commits/#{sha}"
end
def builds_path
......
......@@ -30,24 +30,20 @@ class SlackService < Service
def fields
[
{ type: 'text', name: 'webhook', placeholder: '' }
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }
]
end
def execute(push_data)
return unless webhook.present?
message = SlackMessage.new(push_data.merge(
project_url: project_url,
project_name: project_name
))
credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/)
if credentials.present?
subdomain = credentials[1]
token = credentials[2].split("token=").last
notifier = Slack::Notifier.new(subdomain, token)
notifier.ping(message.pretext, attachments: message.attachments)
end
notifier = Slack::Notifier.new(webhook)
notifier.ping(message.pretext, attachments: message.attachments)
end
private
......
......@@ -226,6 +226,11 @@ class User < ActiveRecord::Base
where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
def by_login(login)
where('lower(username) = :value OR lower(email) = :value',
value: login.to_s.downcase).first
end
def by_username_or_id(name_or_id)
where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end
......@@ -330,11 +335,7 @@ class User < ActiveRecord::Base
end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
Ability.abilities
end
def can_select_namespace?
......@@ -497,6 +498,14 @@ class User < ActiveRecord::Base
end
end
def hook_attrs
{
name: name,
username: username,
avatar_url: avatar_url
}
end
def ensure_namespace_correct
# Ensure user has namespace
self.create_namespace!(path: self.username, name: self.username) unless self.namespace
......
......@@ -6,11 +6,7 @@ class BaseService
end
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
Ability.abilities
end
def can?(object, action, subject)
......
......@@ -83,9 +83,14 @@ class GitPushService
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
issues_to_close = commit.closes_issues(project)
author = commit_user(commit)
if !issues_to_close.empty? && is_default_branch
# Load commit author only if needed.
# For push with 1k commits it prevents 900+ requests in database
author = nil
if issues_to_close.present? && is_default_branch
author ||= commit_user(commit)
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
......@@ -96,8 +101,13 @@ class GitPushService
# being pushed to a different branch).
refs = commit.references(project) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) }
refs.each do |r|
Note.create_cross_reference_note(r, commit, author, project)
if refs.present?
author ||= commit_user(commit)
refs.each do |r|
Note.create_cross_reference_note(r, commit, author, project)
end
end
end
end
......@@ -160,19 +170,19 @@ class GitPushService
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000"
ref_parts[1] =~ /heads/ && oldrev != Gitlab::Git::BLANK_SHA
end
def push_to_new_branch?(ref, oldrev)
ref_parts = ref.split('/')
ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000"
ref_parts[1] =~ /heads/ && oldrev == Gitlab::Git::BLANK_SHA
end
def push_remove_branch?(ref, newrev)
ref_parts = ref.split('/')
ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000"
ref_parts[1] =~ /heads/ && newrev == Gitlab::Git::BLANK_SHA
end
def push_to_branch?(ref)
......
......@@ -4,7 +4,7 @@ module Issues
private
def execute_hooks(issue, action = 'open')
issue_data = issue.to_hook_data
issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue.project.execute_hooks(issue_data, :issue_hooks)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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