Commit 7d9ef112 authored by Marin Jankovski's avatar Marin Jankovski

Merge remote-tracking branch 'ce/master' into merge_ce

Conflicts:
	README.md
	app/assets/javascripts/project.js.coffee
	app/models/concerns/mentionable.rb
	app/models/project.rb
	app/models/project_services/jira_service.rb
	app/models/service.rb
	app/models/user.rb
	app/services/git_push_service.rb
	config/initializers/1_settings.rb
	config/routes.rb
	db/schema.rb
	doc/integration/README.md
	doc/integration/external-issue-tracker.md
	doc/integration/github.md
	spec/controllers/import/github_controller_spec.rb
	spec/helpers/merge_requests_helper.rb
	spec/lib/gitlab/upgrader_spec.rb
	spec/models/note_spec.rb
	spec/requests/api/groups_spec.rb
	spec/services/git_push_service_spec.rb
parents 7f5e67a6 6fa752a8
CHANGELOG merge=union
\ No newline at end of file
Note: The upcoming release contains empty lines to reduce the number of merge conflicts, scroll down to see past releases.
v 7.8.0
v 7.8.0 (unreleased)
- Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
- Make project search case insensitive (Hannes Rosenögger)
- Include issue/mr participants in list of recipients for reassign/close/reopen emails
......@@ -10,63 +8,53 @@ v 7.8.0
- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
- Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
- View note image attachments in new tab when clicked instead of downloading them
-
- Improve sorting logic in UI and API. Explicitly define what sorting method is used by default
- Allow more variations for commit messages closing issues (Julien Bianchi and Hannes Rosenögger)
-
-
- Fix overflow at sidebar when have several itens
- Add notes for label changes in issue and merge requests
- Show tags in commit view (Hannes Rosenögger)
- Only count a user's vote once on a merge request or issue (Michael Clarke)
-
- Increate font size when browse source files and diffs
- Create new file in empty repository using GitLab UI
-
- Ability to clone project using oauth2 token
-
- Upgrade Sidekiq gem to version 3.3.0
- Stop git zombie creation during force push check
- Show success/error messages for test setting button in services
- Added Rubocop for code style checks
- Fix commits pagination
-
- Async load a branch information at the commit page
- Disable blacklist validation for project names
- Allow configuring protection of the default branch upon first push (Marco Wessel)
-
- Add gitlab.com importer
- Add an ability to login with gitlab.com
-
- Add a commit calendar to the user profile (Hannes Rosenögger)
-
- Submit comment on command-enter
-
- Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
- Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close"
- Fix long broadcast message cut-off on left sidebar (Visay Keo)
- Add Project Avatars (Steven Thonus and Hannes Rosenögger)
-
-
- Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
-
-
- Edit group members via API
- Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
-
-
- Add action property to merge request hook (Julien Bianchi)
-
- Remove duplicates from group milestone participants list.
-
-
- Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
-
-
- API: Access groups with their path (Julien Bianchi)
- Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
-
-
- Allow notification email to be set separately from primary email.
- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
-
-
- Don't have Markdown preview fail for long comments/wiki pages.
- When test web hook - show error message instead of 500 error page if connection to hook url was reset
- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
- Added persistent collapse button for left side nav bar (Jason Blanchard)
- Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
- Don't allow page to be scaled on mobile.
- Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up.
- Show assignees in merge request index page (Kelvin Mutuma)
- Link head panel titles to relevant root page.
- Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S).
- Show users button to share their newly created public or internal projects on twitter
- Add quick help links to the GitLab pricing and feature comparison pages.
v 7.7.2
- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
......@@ -102,9 +90,9 @@ v 7.7.0
- When accept merge request - do merge using sidaekiq job
- Enable web signups by default
- Fixes for diff comments: drag-n-drop images, selecting images
- Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
- Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
- Remove password strength indicator
v 7.6.0
......
......@@ -63,7 +63,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Fork the project on GitLab Cloud
1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
......
......@@ -154,6 +154,9 @@ gem "gemnasium-gitlab-service", "~> 0.2"
# Slack integration
gem "slack-notifier", "~> 1.0.0"
# Asana integration
gem 'asana', '~> 0.0.6'
# d3
gem "d3_rails", "~> 3.1.4"
......@@ -221,7 +224,7 @@ group :development, :test do
gem 'rubocop', '0.28.0', require: false
# gem 'rails-dev-tweaks'
gem 'spinach-rails'
gem "rspec-rails"
gem "rspec-rails", '2.99'
gem "capybara", '~> 2.2.1'
gem "pry-rails"
gem "awesome_print"
......
......@@ -23,6 +23,10 @@ GEM
activemodel (= 4.1.1)
activesupport (= 4.1.1)
arel (~> 5.0.0)
activeresource (4.0.0)
activemodel (~> 4.0)
activesupport (~> 4.0)
rails-observers (~> 0.1.1)
activesupport (4.1.1)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
......@@ -36,6 +40,8 @@ GEM
activerecord (>= 2.3.0)
rake (>= 0.8.7)
arel (5.0.1.20140414130214)
asana (0.0.6)
activeresource (>= 3.2.3)
asciidoctor (0.1.4)
ast (2.0.0)
astrolabe (1.3.0)
......@@ -410,6 +416,8 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.1)
sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
rails_autolink (1.1.6)
rails (> 3.1)
railties (4.1.1)
......@@ -452,21 +460,25 @@ GEM
mime-types (>= 1.16)
rinku (1.7.3)
rouge (1.7.4)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.7)
rspec-expectations (2.14.4)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-collection_matchers (1.1.2)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.4)
rspec-rails (2.14.0)
rspec-mocks (2.99.3)
rspec-rails (2.99.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-collection_matchers
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rubocop (0.28.0)
astrolabe (~> 1.3)
parser (>= 2.2.0.pre.7, < 3.0)
......@@ -632,6 +644,7 @@ DEPENDENCIES
acts-as-taggable-on
addressable
annotate (~> 2.6.0.beta2)
asana (~> 0.0.6)
asciidoctor (= 0.1.4)
awesome_print
better_errors
......@@ -721,7 +734,7 @@ DEPENDENCIES
redcarpet (~> 3.1.2)
redis-rails
request_store
rspec-rails
rspec-rails (= 2.99)
rubocop (= 0.28.0)
rugments
sanitize (~> 2.0)
......
......@@ -22,7 +22,7 @@ https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md#updating-
If you need help with your GitLab installation and for any technical questions please contact us at subscribers@gitlab.com.
For all other questions, contact us at sales@gitlab.com
For all other questions, contact us at sales@gitlab.com
## Open source software to collaborate on code
......@@ -33,14 +33,14 @@ For all other questions, contact us at sales@gitlab.com
- Each project can also have an issue tracker and a wiki
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
- Completely free and open source (MIT Expat license)
- Powered by Ruby on Rails
- Powered by [Ruby on Rails](https://github.com/rails/rails)
## Editions
There are two editions of GitLab.
GitLab [Community Edition](https://about.gitlab.com/features/) (CE) is available without any costs under an MIT license.
*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license.
GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/).
## Canonical source
......@@ -72,42 +72,45 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Requirements
- Ubuntu/Debian/CentOS/RHEL**
GitLab requires the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.0 or 2.1
- git 1.7.10+
- redis 2.0+
- Git 1.7.10+
- Redis 2.0+
- MySQL or PostgreSQL
** More details are in the [requirements doc](doc/install/requirements.md).
Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## Installation
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
You can access new installation with the login `root` and password `5iveL!fe`, after login you are required to set a unique password.
The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information.
You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password.
## Third-party applications
There are a lot of applications and API wrappers for GitLab.
Find them [on our website](https://about.gitlab.com/applications/).
There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages.
## New versions
## GitLab release cycle
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
## Upgrading
For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update).
For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For installations from source there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update) detailing all necessary commands to migrate to the next version.
## Install a development environment
We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the GitLab Development Development kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb
Instructions on how to start Gitlab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
## Documentation
......
......@@ -17,6 +17,7 @@
#= require jquery.blockUI
#= require jquery.turbolinks
#= require turbolinks
#= require autosave
#= require bootstrap
#= require select2
#= require raphael
......
class @Autosave
constructor: (field, key) ->
@field = field
key = key.join("/") if key.join?
@key = "autosave/#{key}"
@field.data "autosave", this
@restore()
@field.on "input", => @save()
restore: ->
return unless window.localStorage?
text = window.localStorage.getItem @key
@field.val text if text?.length > 0
@field.trigger "input"
save: ->
return unless window.localStorage?
text = @field.val()
if text?.length > 0
window.localStorage.setItem @key, text
else
@reset()
reset: ->
return unless window.localStorage?
window.localStorage.removeItem @key
\ No newline at end of file
......@@ -50,7 +50,7 @@ class @DropzoneInput
preview.text "Nothing to preview."
else
preview.text "Loading..."
$.get($(this).data("url"),
$.post($(this).data("url"),
md_text: mdText
).success (previewData) ->
preview.html previewData
......
......@@ -4,7 +4,7 @@ class @ImporterStatus
this.setAutoUpdate()
initStatusPage: ->
$(".btn-add-to-import").click (event) =>
$(".js-add-to-import").click (event) =>
new_namespace = null
tr = $(event.currentTarget).closest("tr")
id = tr.attr("id").replace("repo_", "")
......@@ -12,6 +12,10 @@ class @ImporterStatus
new_namespace = tr.find(".import-target input").prop("value")
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name"))
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
$(".js-import-all").click (event) =>
$(".js-add-to-import").each ->
$(this).click()
setAutoUpdate: ->
setInterval (=>
......
......@@ -58,7 +58,8 @@ class @Notes
$(document).on "visibilitychange", @visibilityChange
@notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
$(document).on('keypress', @notes_forms, (e)->
# Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
$(document).on('keydown', @notes_forms, (e) ->
if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
$(@).parents('form').submit()
)
......@@ -170,6 +171,8 @@ class @Notes
form.find(".js-md-write-button").click()
form.find(".js-note-text").val("").trigger "input"
form.find(".js-note-text").data("autosave").reset()
###
Called when clicking the "Choose File" button.
......@@ -220,12 +223,22 @@ class @Notes
# setup preview buttons
form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
previewButton = form.find(".js-md-preview-button")
form.find(".js-note-text").on "input", ->
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"
new Autosave textarea, [
"Note"
form.find("#note_commit_id").val()
form.find("#note_line_code").val()
form.find("#note_noteable_type").val()
form.find("#note_noteable_id").val()
]
# remove notify commit author checkbox for non-commit notes
form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit"
......@@ -233,7 +246,6 @@ class @Notes
new DropzoneInput(form)
form.show()
###
Called in response to the new note form being submitted
......@@ -407,6 +419,8 @@ class @Notes
removeDiscussionNoteForm: (form)->
row = form.closest("tr")
form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies)
form.prev(".js-discussion-reply-button").show()
if row.is(".js-temp-notes-holder")
......
......@@ -16,5 +16,11 @@ class @Project
$('.hide-no-ssh-message').on 'click', (e) ->
path = '/'
$.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
\ No newline at end of file
$(@).parents('.no-ssh-key-message').remove()
e.preventDefault()
$('.hide-no-password-message').on 'click', (e) ->
path = '/'
$.cookie('hide_no_password_message', 'false', { path: path })
$(@).parents('.no-password-message').remove()
e.preventDefault()
......@@ -15,7 +15,7 @@
&.s24 { margin-right: 4px; }
}
&.avatar-tile {
&.group-avatar, &.project-avatar, &.avatar-tile {
@include border-radius(0px);
}
......
......@@ -342,6 +342,10 @@ table {
margin-bottom: 9px;
}
.wiki .code {
overflow-x: auto;
}
.footer-links a {
margin-right: 15px;
}
......
......@@ -59,6 +59,7 @@
box-shadow: none;
background: $box_bg;
padding: 1em;
overflow-x: auto;
code {
font-family: $monospace_font;
......
......@@ -75,9 +75,6 @@
}
}
}
.project-avatar {
float: left;
}
.project-description {
overflow: hidden;
......
......@@ -64,6 +64,10 @@
.md {
font-size: 13px;
iframe.twitter-share-button {
vertical-align: bottom;
}
}
pre {
......
......@@ -40,12 +40,15 @@
.login-heading h3 {
font-weight: 300;
line-height: 1.5;
margin: 0;
display: none;
margin: 0 0 10px 0;
}
.login-footer {
margin-top: 10px;
p:last-child {
margin-bottom: 0;
}
}
a.forgot {
......@@ -88,6 +91,7 @@
.devise-errors {
h2 {
margin-top: 0;
font-size: 14px;
color: #a00;
}
......
......@@ -126,7 +126,7 @@
.nav-sidebar {
margin-top: 20px;
position: absolute;
position: fixed;
top: 45px;
width: 52px;
......
......@@ -32,7 +32,6 @@
.avatar {
width: 70px;
height: 70px;
@include border-radius(0px);
}
.identicon {
......
......@@ -26,6 +26,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:signup_enabled,
:signin_enabled,
:gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text,
:home_page_url
)
......
class Admin::DashboardController < Admin::ApplicationController
def index
@projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10)
@groups = Group.order("created_at DESC").limit(10)
@projects = Project.limit(10)
@users = User.limit(10)
@groups = Group.limit(10)
end
end
......@@ -2,7 +2,8 @@ class Admin::GroupsController < Admin::ApplicationController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
def index
@groups = Group.order('name ASC')
@groups = Group.all
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]).per(20)
end
......
class Admin::ServicesController < Admin::ApplicationController
before_filter :service, only: [:edit, :update]
def index
@services = services_templates
end
def edit
unless service.present?
redirect_to admin_application_settings_services_path,
alert: "Service is unknown or it doesn't exist"
end
end
def update
if service.update_attributes(application_services_params[:service])
redirect_to admin_application_settings_services_path,
notice: 'Application settings saved successfully'
else
render :edit
end
end
private
def services_templates
templates = []
Service.available_services_names.each do |service_name|
service_template = service_name.concat("_service").camelize.constantize
templates << service_template.where(template: true).first_or_create
end
templates
end
def service
@service ||= Service.where(id: params[:id], template: true).first
end
def application_services_params
params.permit(:id,
service: [
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch
])
end
end
......@@ -2,16 +2,16 @@ class Admin::UsersController < Admin::ApplicationController
before_filter :user, only: [:show, :edit, :update, :destroy]
def index
@users = User.filter(params[:filter])
@users = User.order_name_asc.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.alphabetically.page(params[:page])
@users = @users.page(params[:page])
end
def show
@personal_projects = user.personal_projects
@joined_projects = user.projects.joined(@user)
@keys = user.keys.order('id DESC')
@keys = user.keys
end
def new
......@@ -102,6 +102,9 @@ class Admin::UsersController < Admin::ApplicationController
email = user.emails.find(params[:email_id])
email.destroy
user.set_notification_email
user.save if user.notification_email_changed?
respond_to do |format|
format.html { redirect_to :back, notice: "Successfully removed email." }
format.js { render nothing: true }
......@@ -118,7 +121,7 @@ class Admin::UsersController < Admin::ApplicationController
params.require(:user).permit(
:email, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
:extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
:extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :hide_no_password,
:projects_limit, :can_create_group, :admin, :key_id
)
end
......
......@@ -260,7 +260,7 @@ class ApplicationController < ActionController::Base
end
def set_filters_params
params[:sort] ||= 'newest'
params[:sort] ||= 'created_desc'
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
......@@ -286,7 +286,7 @@ class ApplicationController < ActionController::Base
author_id = @filter_params[:author_id]
milestone_id = @filter_params[:milestone_id]
@sort = @filter_params[:sort].try(:humanize)
@sort = @filter_params[:sort]
@assignees = User.where(id: collection.pluck(:assignee_id))
@authors = User.where(id: collection.pluck(:author_id))
@milestones = Milestone.where(id: collection.pluck(:milestone_id))
......
......@@ -9,7 +9,7 @@ class DashboardController < ApplicationController
# If user needs more - point to Dashboard#projects page
@projects_limit = 30
@groups = current_user.authorized_groups.sort_by(&:human_name)
@groups = current_user.authorized_groups.order_name_asc
@has_authorized_projects = @projects.count > 0
@projects_count = @projects.count
@projects = @projects.limit(@projects_limit)
......
......@@ -45,7 +45,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if current_user
# Add new authentication method
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
redirect_to profile_path
redirect_to profile_account_path, notice: 'Authentication method updated'
else
@user = Gitlab::OAuth::User.new(oauth)
@user.save
......
......@@ -18,6 +18,9 @@ class Profiles::EmailsController < ApplicationController
@email = current_user.emails.find(params[:id])
@email.destroy
current_user.set_notification_email
current_user.save if current_user.notification_email_changed?
respond_to do |format|
format.html { redirect_to profile_emails_url }
format.js { render nothing: true }
......
......@@ -3,7 +3,7 @@ class Profiles::KeysController < ApplicationController
skip_before_filter :authenticate_user!, only: [:get_keys]
def index
@keys = current_user.keys.order('id DESC')
@keys = current_user.keys
end
def show
......
......@@ -2,6 +2,7 @@ class Profiles::NotificationsController < ApplicationController
layout 'profile'
def show
@user = current_user
@notification = current_user.notification
@project_members = current_user.project_members
@group_members = current_user.group_members
......@@ -11,8 +12,7 @@ class Profiles::NotificationsController < ApplicationController
type = params[:notification_type]
@saved = if type == 'global'
current_user.notification_level = params[:notification_level]
current_user.save
current_user.update_attributes(user_params)
elsif type == 'group'
users_group = current_user.group_members.find(params[:notification_id])
users_group.notification_level = params[:notification_level]
......@@ -22,5 +22,23 @@ class Profiles::NotificationsController < ApplicationController
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_to :back
end
format.js
end
end
def user_params
params.require(:user).permit(:notification_email, :notification_level)
end
end
......@@ -11,7 +11,7 @@ class Profiles::PasswordsController < ApplicationController
end
def create
unless @user.valid_password?(user_params[:current_password])
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
return
end
......@@ -21,7 +21,8 @@ class Profiles::PasswordsController < ApplicationController
result = @user.update_attributes(
password: new_password,
password_confirmation: new_password_confirmation
password_confirmation: new_password_confirmation,
password_automatically_set: false
)
if result
......@@ -39,8 +40,9 @@ class Profiles::PasswordsController < ApplicationController
password_attributes = user_params.select do |key, value|
%w(password password_confirmation).include?(key.to_s)
end
password_attributes[:password_automatically_set] = false
unless @user.valid_password?(user_params[:current_password])
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
redirect_to edit_profile_password_path, alert: 'You must provide a valid current password'
return
end
......
......@@ -67,7 +67,7 @@ class ProfilesController < ApplicationController
params.require(:user).permit(
:email, :password, :password_confirmation, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id,
:avatar, :hide_no_ssh_key,
:avatar, :hide_no_ssh_key, :hide_no_password
)
end
end
......@@ -13,7 +13,7 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
group(:commit_id).count
respond_to do |format|
format.html
......
......@@ -7,7 +7,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to :js, :html
def index
@labels = @project.labels.order_by_name.page(params[:page]).per(20)
@labels = @project.labels.page(params[:page]).per(20)
end
def new
......
......@@ -23,7 +23,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def show
@note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
group(:commit_id).count
group(:commit_id).count
respond_to do |format|
format.html
......
......@@ -29,7 +29,7 @@ class Projects::ServicesController < Projects::ApplicationController
if @service.execute(data)
message = { notice: 'We sent a request to the provided URL' }
else
message = { alert: 'We tried to send a request to the provided URL but error occured' }
message = { alert: 'We tried to send a request to the provided URL but an error occured' }
end
redirect_to :back, message
......@@ -47,7 +47,7 @@ class Projects::ServicesController < Projects::ApplicationController
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url
:description, :issues_url, :new_issue_url, :restrict_to_branch
)
end
end
......@@ -27,7 +27,7 @@ class Projects::TagsController < Projects::ApplicationController
tag = @repository.find_tag(params[:id])
if tag && @repository.rm_tag(tag.name)
Event.create_ref_event(@project, current_user, tag, 'rm', 'refs/tags')
EventCreateService.new.push_ref(@project, current_user, tag, 'rm', 'refs/tags')
end
respond_to do |format|
......
......@@ -102,7 +102,7 @@ class ProjectsController < ApplicationController
note_type = params['type']
note_id = params['type_id']
autocomplete = ::Projects::AutocompleteService.new(@project)
participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
emojis: autocomplete_emojis,
......
......@@ -106,6 +106,7 @@ class SnippetsController < ApplicationController
def set_title
@title = 'Snippets'
@title_url = snippets_path
end
def snippet_params
......
......@@ -19,6 +19,7 @@ class UsersController < ApplicationController
where(project_id: authorized_projects_ids).limit(30)
@title = @user.name
@title_url = user_path(@user)
respond_to do |format|
format.html
......
......@@ -10,18 +10,18 @@ class NotesFinder
notes =
case target_type
when "commit"
project.notes.for_commit_id(target_id).not_inline.fresh
project.notes.for_commit_id(target_id).not_inline
when "issue"
project.issues.find(target_id).notes.inc_author.fresh
project.issues.find(target_id).notes.inc_author
when "merge_request"
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
when "snippet", "project_snippet"
project.snippets.find(target_id).notes.fresh
project.snippets.find(target_id).notes
else
raise 'invalid target_type'
end
# Use overlapping intervals to avoid worrying about race conditions
notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP)
notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP).fresh
end
end
......@@ -3,6 +3,10 @@ module ApplicationSettingsHelper
current_application_settings.gravatar_enabled?
end
def twitter_sharing_enabled?
current_application_settings.twitter_sharing_enabled?
end
def signup_enabled?
current_application_settings.signup_enabled?
end
......
......@@ -10,11 +10,15 @@ module EventsHelper
end
def event_action_name(event)
target = if event.target_type
event.target_type.titleize.downcase
else
'project'
end
target = if event.target_type
if event.note?
event.note_target_type
else
event.target_type.titleize.downcase
end
else
'project'
end
[event.action_name, target].join(" ")
end
......@@ -42,21 +46,30 @@ module EventsHelper
end
def event_feed_title(event)
if event.issue?
"#{event.author_name} #{event.action_name} issue ##{event.target_iid}: #{event.issue_title} at #{event.project_name}"
elsif event.merge_request?
"#{event.author_name} #{event.action_name} MR ##{event.target_iid}: #{event.merge_request_title} at #{event.project_name}"
elsif event.push?
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed?
"#{event.author_name} #{event.action_name} #{event.project_name}"
elsif event.note? && event.note_commit?
"#{event.author_name} commented on #{event.note_target_type} #{event.note_short_commit_id} at #{event.project_name}"
elsif event.note?
"#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
else
""
words = []
words << event.author_name
words << event_action_name(event)
if event.push?
words << event.ref_type
words << event.ref_name
words << "at"
elsif event.commented?
if event.note_commit?
words << event.note_short_commit_id
else
words << "##{truncate event.note_target_iid}"
end
words << "at"
elsif event.target
words << "##{event.target_iid}:"
words << event.target.title if event.target.respond_to?(:title)
words << "at"
end
words << event.project_name
words.join(" ")
end
def event_feed_url(event)
......@@ -96,8 +109,6 @@ module EventsHelper
render "events/event_push", event: event
elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request
elsif event.push?
render "events/event_push", event: event
elsif event.note?
render "events/event_note", note: event.note
end
......
......@@ -7,21 +7,34 @@ module LabelsHelper
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do
content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do
label.name
end
end
def suggested_colors
[
'#D9534F',
'#F0AD4E',
'#0033CC',
'#428BCA',
'#44AD8E',
'#A8D695',
'#5CB85C',
'#69D100',
'#004E00',
'#34495E',
'#7F8C8D',
'#A295D6',
'#5843AD',
'#8E44AD',
'#FFECDB'
'#FFECDB',
'#AD4363',
'#D10069',
'#CC0033',
'#FF0000',
'#D9534F',
'#D1D100',
'#F0AD4E',
'#AD8D43'
]
end
......
module SortingHelper
def sort_options_hash
{
sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created,
sort_value_milestone_soon => sort_title_milestone_soon,
sort_value_milestone_later => sort_title_milestone_later,
sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
}
end
def sort_title_oldest_updated
'Oldest updated'
end
......@@ -14,4 +29,68 @@ module SortingHelper
def sort_title_recently_created
'Recently created'
end
def sort_title_milestone_soon
'Milestone due soon'
end
def sort_title_milestone_later
'Milestone due later'
end
def sort_title_name
'Name'
end
def sort_title_largest_repo
'Largest repository'
end
def sort_title_recently_signin
'Recent sign in'
end
def sort_title_oldest_signin
'Oldest sign in'
end
def sort_value_oldest_updated
'updated_asc'
end
def sort_value_recently_updated
'updated_desc'
end
def sort_value_oldest_created
'created_asc'
end
def sort_value_recently_created
'created_desc'
end
def sort_value_milestone_soon
'milestone_due_asc'
end
def sort_value_milestone_later
'milestone_due_desc'
end
def sort_value_name
'name_asc'
end
def sort_value_largest_repo
'repository_size_desc'
end
def sort_value_recently_signin
'recent_sign_in'
end
def sort_value_oldest_signin
'oldest_sign_in'
end
end
......@@ -4,20 +4,20 @@ module Emails
@user = User.find(user_id)
@target_url = user_url(@user)
@token = token
mail(to: @user.email, subject: subject("Account was created for you"))
mail(to: @user.notification_email, subject: subject("Account was created for you"))
end
def new_email_email(email_id)
@email = Email.find(email_id)
@user = @email.user
mail(to: @user.email, subject: subject("Email was added to your account"))
mail(to: @user.notification_email, subject: subject("Email was added to your account"))
end
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
@target_url = user_url(@user)
mail(to: @user.email, subject: subject("SSH key was added to your account"))
mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
end
end
end
......@@ -12,7 +12,7 @@ module Emails
@user = User.find user_id
@project = Project.find project_id
@target_url = project_url(@project)
mail(to: @user.email,
mail(to: @user.notification_email,
subject: subject("Project was moved"))
end
......
......@@ -61,7 +61,7 @@ class Notify < ActionMailer::Base
# Returns a String containing the User's email address.
def recipient(recipient_id)
if recipient = User.find(recipient_id)
recipient.email
recipient.notification_email
end
end
......@@ -112,6 +112,7 @@ class Notify < ActionMailer::Base
# See: mail_answer_thread
def mail_new_thread(model, headers = {}, &block)
headers['Message-ID'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
mail(headers, &block)
end
......@@ -126,6 +127,7 @@ class Notify < ActionMailer::Base
def mail_answer_thread(model, headers = {}, &block)
headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
if (headers[:subject])
headers[:subject].prepend('Re: ')
......
......@@ -8,6 +8,7 @@
# signup_enabled :boolean
# signin_enabled :boolean
# gravatar_enabled :boolean
# twitter_sharing_enabled :boolean
# sign_in_text :text
# created_at :datetime
# updated_at :datetime
......@@ -30,6 +31,7 @@ class ApplicationSetting < ActiveRecord::Base
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
)
......
......@@ -14,6 +14,8 @@
#
class BroadcastMessage < ActiveRecord::Base
include Sortable
validates :message, presence: true
validates :starts_at, presence: true
validates :ends_at, presence: true
......
......@@ -29,6 +29,8 @@ module Issuable
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
delegate :name,
:email,
......@@ -55,13 +57,10 @@ module Issuable
def sort(method)
case method.to_s
when 'newest' then reorder("#{table_name}.created_at DESC")
when 'oldest' then reorder("#{table_name}.created_at ASC")
when 'recently_updated' then reorder("#{table_name}.updated_at DESC")
when 'last_updated' then reorder("#{table_name}.updated_at ASC")
when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC")
when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC")
else reorder("#{table_name}.created_at DESC")
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
else
order_by(method)
end
end
end
......
......@@ -51,9 +51,12 @@ module Mentionable
identifier = match.delete "@"
if identifier == "all"
users.push(*project.team.members.flatten)
else
id = User.find_by(username: identifier).try(:id)
users << User.find(id) unless id.blank?
elsif namespace = Namespace.find_by(path: identifier)
if namespace.type == "Group"
users.push(*namespace.users)
else
users << namespace.owner
end
end
end
users.uniq
......@@ -64,6 +67,7 @@ module Mentionable
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
ext.analyze(text, p)
(ext.issues_for(p) +
ext.merge_requests_for(p) +
ext.commits_for(p)
......
# == Sortable concern
#
# Set default scope for ordering objects
#
module Sortable
extend ActiveSupport::Concern
included do
# By default all models should be ordered
# by created_at field starting from newest
default_scope { order(created_at: :desc, id: :desc) }
scope :order_created_desc, -> { reorder(created_at: :desc, id: :desc) }
scope :order_created_asc, -> { reorder(created_at: :asc, id: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc, id: :desc) }
scope :order_updated_asc, -> { reorder(updated_at: :asc, id: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) }
end
module ClassMethods
def order_by(method)
case method.to_s
when 'name_asc' then order_name_asc
when 'name_desc' then order_name_desc
when 'updated_asc' then order_updated_asc
when 'updated_desc' then order_updated_desc
when 'created_asc' then order_created_asc
when 'created_desc' then order_created_desc
else
all
end
end
end
end
......@@ -10,6 +10,8 @@
#
class Email < ActiveRecord::Base
include Sortable
belongs_to :user
validates :user_id, presence: true
......
......@@ -15,6 +15,7 @@
#
class Event < ActiveRecord::Base
include Sortable
default_scope { where.not(author_id: nil) }
CREATED = 1
......@@ -48,29 +49,6 @@ class Event < ActiveRecord::Base
scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
class << self
def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads')
commit = project.repository.commit(ref.target)
if action.to_s == 'add'
before = '00000000'
after = commit.id
else
before = commit.id
after = '00000000'
end
Event.create(
project: project,
action: Event::PUSHED,
data: {
ref: "#{prefix}/#{ref.name}",
before: before,
after: after
},
author_id: user.id
)
end
def reset_event_cache_for(target)
Event.where(target_id: target.id, target_type: target.class.to_s).
order('id DESC').limit(100).
......@@ -83,6 +61,8 @@ class Event < ActiveRecord::Base
true
elsif membership_changed?
true
elsif created_project?
true
else
(issue? || merge_request? || note? || milestone?) && target
end
......@@ -97,25 +77,51 @@ class Event < ActiveRecord::Base
end
def target_title
if target && target.respond_to?(:title)
target.title
end
target.title if target && target.respond_to?(:title)
end
def created?
action == CREATED
end
def push?
action == self.class::PUSHED && valid_push?
action == PUSHED && valid_push?
end
def merged?
action == self.class::MERGED
action == MERGED
end
def closed?
action == self.class::CLOSED
action == CLOSED
end
def reopened?
action == self.class::REOPENED
action == REOPENED
end
def joined?
action == JOINED
end
def left?
action == LEFT
end
def commented?
action == COMMENTED
end
def membership_changed?
joined? || left?
end
def created_project?
created? && !target
end
def created_target?
created? && target
end
def milestone?
......@@ -134,32 +140,32 @@ class Event < ActiveRecord::Base
target_type == "MergeRequest"
end
def joined?
action == JOINED
end
def left?
action == LEFT
end
def membership_changed?
joined? || left?
def milestone
target if milestone?
end
def issue
target if target_type == "Issue"
target if issue?
end
def merge_request
target if target_type == "MergeRequest"
target if merge_request?
end
def note
target if target_type == "Note"
target if note?
end
def action_name
if closed?
if push?
if new_ref?
"pushed new"
elsif rm_ref?
"deleted"
else
"pushed to"
end
elsif closed?
"closed"
elsif merged?
"accepted"
......@@ -167,6 +173,10 @@ class Event < ActiveRecord::Base
'joined'
elsif left?
'left'
elsif commented?
"commented on"
elsif created_project?
"created"
else
"opened"
end
......@@ -235,16 +245,6 @@ class Event < ActiveRecord::Base
tag? ? "tag" : "branch"
end
def push_action_name
if new_ref?
"pushed new"
elsif rm_ref?
"deleted"
else
"pushed to"
end
end
def push_with_commits?
md_ref? && commits.any? && commit_from && commit_to
end
......
......@@ -31,6 +31,16 @@ class Group < Namespace
after_create :post_create_hook
after_destroy :post_destroy_hook
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%")
end
def sort(method)
order_by(method)
end
end
def human_name
name
end
......@@ -108,20 +118,4 @@ class Group < Namespace
def system_hook_service
SystemHooksService.new
end
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%")
end
def sort(method)
case method.to_s
when "newest" then reorder("namespaces.created_at DESC")
when "oldest" then reorder("namespaces.created_at ASC")
when "recently_updated" then reorder("namespaces.updated_at DESC")
when "last_updated" then reorder("namespaces.updated_at ASC")
else reorder("namespaces.path, namespaces.name ASC")
end
end
end
end
......@@ -16,6 +16,7 @@
#
class WebHook < ActiveRecord::Base
include Sortable
include HTTParty
default_value_for :push_events, true
......
......@@ -9,6 +9,7 @@
#
class Identity < ActiveRecord::Base
include Sortable
belongs_to :user
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
......
......@@ -24,6 +24,7 @@ class Issue < ActiveRecord::Base
include Issuable
include InternalId
include Taskable
include Sortable
ActsAsTaggableOn.strict_case_match = true
......
......@@ -15,6 +15,7 @@
require 'digest/md5'
class Key < ActiveRecord::Base
include Sortable
include Gitlab::Popen
belongs_to :user
......
......@@ -28,7 +28,7 @@ class Label < ActiveRecord::Base
format: { with: /\A[^&\?,&]+\z/ },
uniqueness: { scope: :project_id }
scope :order_by_name, -> { reorder("labels.title ASC") }
default_scope { order(title: :asc) }
alias_attribute :name, :title
......
......@@ -14,6 +14,7 @@
#
class Member < ActiveRecord::Base
include Sortable
include Notifiable
include Gitlab::Access
......
......@@ -114,13 +114,11 @@ class ProjectMember < Member
end
def post_create_hook
Event.create(
project_id: self.project.id,
action: Event::JOINED,
author_id: self.user.id
)
notification_service.new_team_member(self) unless owner?
unless owner?
event_service.join_project(self.project, self.user)
notification_service.new_team_member(self)
end
system_hook_service.execute_hooks_for(self, :create)
end
......@@ -129,15 +127,14 @@ class ProjectMember < Member
end
def post_destroy_hook
Event.create(
project_id: self.project.id,
action: Event::LEFT,
author_id: self.user.id
)
event_service.leave_project(self.project, self.user)
system_hook_service.execute_hooks_for(self, :destroy)
end
def event_service
EventCreateService.new
end
def notification_service
NotificationService.new
end
......
......@@ -28,6 +28,7 @@ class MergeRequest < ActiveRecord::Base
include Issuable
include Taskable
include InternalId
include Sortable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
......
......@@ -14,6 +14,8 @@
require Rails.root.join("app/models/commit")
class MergeRequestDiff < ActiveRecord::Base
include Sortable
# Prevent store of diff
# if commits amount more then 200
COMMITS_SAFE_SIZE = 200
......
......@@ -15,6 +15,7 @@
class Milestone < ActiveRecord::Base
include InternalId
include Sortable
belongs_to :project
has_many :issues
......
......@@ -14,6 +14,7 @@
#
class Namespace < ActiveRecord::Base
include Sortable
include Gitlab::ShellAdapter
has_many :projects, dependent: :destroy
......@@ -43,6 +44,10 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') }
def self.by_path(path)
where('lower(path) = :value', value: path.downcase).first
end
def self.search(query)
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
......
......@@ -49,7 +49,7 @@ class Note < ActiveRecord::Base
scope :not_inline, ->{ where(line_code: [nil, '']) }
scope :system, ->{ where(system: true) }
scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order("created_at ASC, id ASC") }
scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
......@@ -126,6 +126,36 @@ class Note < ActiveRecord::Base
})
end
def create_labels_change_note(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count
added_labels = added_labels.map{ |label| "~#{label.id}" }.join(' ')
removed_labels = removed_labels.map{ |label| "~#{label.id}" }.join(' ')
message = ''
if added_labels.present?
message << "added #{added_labels}"
end
if added_labels.present? && removed_labels.present?
message << ' and '
end
if removed_labels.present?
message << "removed #{removed_labels}"
end
message << ' ' << 'label'.pluralize(labels_count)
body = "_#{message.capitalize}_"
create(
noteable: noteable,
project: project,
author: author,
note: body,
system: true
)
end
def create_new_commits_note(noteable, project, author, commits)
commits_text = ActionController::Base.helpers.pluralize(commits.size, 'new commit')
body = "Added #{commits_text}:\n\n"
......
......@@ -33,6 +33,7 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Project < ActiveRecord::Base
include Sortable
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Gitlab::ConfigHelper
......@@ -53,7 +54,7 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch
# Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace
......@@ -69,6 +70,7 @@ class Project < ActiveRecord::Base
has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy
has_one :assembla_service, dependent: :destroy
has_one :asana_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :jira_service, dependent: :destroy
......@@ -90,7 +92,7 @@ class Project < ActiveRecord::Base
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy
has_many :issues, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
......@@ -146,14 +148,16 @@ class Project < ActiveRecord::Base
mount_uploader :avatar, AttachmentUploader
# Scopes
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_team, ->(team) { where('projects.id IN (:ids)', ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :in_group_namespace, -> { joins(:group) }
scope :sorted_by_activity, -> { reorder('projects.last_activity_at DESC') }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
......@@ -235,13 +239,10 @@ class Project < ActiveRecord::Base
end
def sort(method)
case method.to_s
when 'newest' then reorder('projects.created_at DESC')
when 'oldest' then reorder('projects.created_at ASC')
when 'recently_updated' then reorder('projects.updated_at DESC')
when 'last_updated' then reorder('projects.updated_at ASC')
when 'largest_repository' then reorder('projects.repository_size DESC')
else reorder('namespaces.path, projects.name ASC')
if method == 'repository_size_desc'
reorder(repository_size: :desc, id: :desc)
else
order_by(method)
end
end
end
......@@ -327,7 +328,7 @@ class Project < ActiveRecord::Base
end
def default_issue_tracker
gitlab_issue_tracker_service ||= create_gitlab_issue_tracker_service
gitlab_issue_tracker_service || create_gitlab_issue_tracker_service
end
def issues_tracker
......@@ -359,19 +360,28 @@ class Project < ActiveRecord::Base
end
def build_missing_services
available_services_names.each do |service_name|
service = services.find { |service| service.to_param == service_name }
services_templates = Service.where(template: true)
Service.available_services_names.each do |service_name|
service = find_service(services, service_name)
# If service is available but missing in db
# we should create an instance. Ex `create_gitlab_ci_service`
service = self.send :"create_#{service_name}_service" if service.nil?
if service.nil?
# We should check if template for the service exists
template = find_service(services_templates, service_name)
if template.nil?
# If no template, we should create an instance. Ex `create_gitlab_ci_service`
service = self.send :"create_#{service_name}_service"
else
Service.create_from_template(self.id, template)
end
end
end
end
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jenkins jira redmine custom_issue_tracker
)
def find_service(list, name)
list.find { |service| service.to_param == name }
end
def gitlab_ci?
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
require 'asana'
class AsanaService < Service
prop_accessor :api_key, :restrict_to_branch
validates :api_key, presence: true, if: :activated?
def title
'Asana'
end
def description
'Asana - Teamwork without email'
end
def help
'This service adds commit messages as comments to Asana tasks.
Once enabled, commit messages are checked for Asana task URLs
(for example, `https://app.asana.com/0/123456/987654`) or task IDs
starting with # (for example, `#987654`). Every task ID found will
get the commit comment added to it.
You can also close a task with a message containing: `fix #123456`.
You can find your Api Keys here:
http://developer.asana.com/documentation/#api_keys'
end
def to_param
'asana'
end
def fields
[
{
type: 'text',
name: 'api_key',
placeholder: 'User API token. User must have access to task,
all comments will be attributed to this user.'
},
{
type: 'text',
name: 'restrict_to_branch',
placeholder: 'Comma-separated list of branches which will be
automatically inspected. Leave blank to include all branches.'
}
]
end
def execute(push)
Asana.configure do |client|
client.api_key = api_key
end
user = push[:user_name]
branch = push[:ref].gsub('refs/heads/', '')
branch_restriction = restrict_to_branch.to_s
# check the branch restriction is poplulated and branch is not included
if branch_restriction.length > 0 && branch_restriction.index(branch) == nil
return
end
project_name = project.name_with_namespace
push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name
push[:commits].each do |commit|
check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg)
end
end
def check_commit(message, push_msg)
task_list = []
close_list = []
message.split("\n").each do |line|
# look for a task ID or a full Asana url
task_list.concat(line.scan(/#(\d+)/))
task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/))
# look for a word starting with 'fix' followed by a task ID
close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i))
end
# post commit to every taskid found
task_list.each do |taskid|
task = Asana::Task.find(taskid[0])
if task
task.create_story(text: push_msg + ' ' + message)
end
end
# close all tasks that had 'fix(ed/es/ing) #:id' in them
close_list.each do |taskid|
task = Asana::Task.find(taskid.last)
if task
task.modify(completed: true)
end
end
end
end
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class AssemblaService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class BambooService < CiService
......
......@@ -5,13 +5,13 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
require "addressable/uri"
class BuildboxService < CiService
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class CampfireService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
# Base class for CI services
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class CustomIssueTrackerService < IssueTrackerService
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class EmailsOnPushService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
require "flowdock-git-hook"
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
require "gemnasium/gitlab_service"
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class GitlabCiService < CiService
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class GitlabIssueTrackerService < IssueTrackerService
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class HipchatService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class IssueTrackerService < Service
......@@ -68,6 +69,29 @@ class IssueTrackerService < Service
end
end
def execute(data)
message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again."
result = false
begin
url = URI.parse(self.project_url)
if url.host && url.port
http = Net::HTTP.start(url.host, url.port, { open_timeout: 5, read_timeout: 5 })
response = http.head("/")
if response
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
result = true
end
end
rescue Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
Rails.logger.info(message)
result
end
private
def enabled_in_gitlab_config
......@@ -81,12 +105,14 @@ class IssueTrackerService < Service
end
def set_project_url
id = self.project.issues_tracker_id
if self.project
id = self.project.issues_tracker_id
if id
issues_tracker['project_url'].gsub(":issues_tracker_id", id)
else
issues_tracker['project_url']
if id
issues_tracker['project_url'].gsub(":issues_tracker_id", id)
end
end
issues_tracker['project_url']
end
end
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class JiraService < IssueTrackerService
......@@ -21,6 +22,19 @@ class JiraService < IssueTrackerService
before_validation :set_api_version, :set_jira_issue_transition_id
def help
issue_tracker_link = help_page_path("integration", "external-issue-tracker")
line1 = "Setting `project_url`, `issues_url` and `new_issue_url` will "\
"allow a user to easily navigate to the Jira issue tracker. "\
"See the [integration doc](#{issue_tracker_link}) for details."
line2 = 'Support for referencing commits and automatic closing of Jira issues directly ' \
'from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)'
[line1, line2].join("\n\n")
end
def title
if self.properties && self.properties['title'].present?
self.properties['title']
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class PivotaltrackerService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class PushoverService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class RedmineService < IssueTrackerService
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class SlackService < Service
......
......@@ -5,11 +5,12 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
#
class TeamcityService < CiService
......
......@@ -5,16 +5,17 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
# template :boolean default(FALSE)
# To add new service you should build a class inherited from Service
# and implement a set of methods
class Service < ActiveRecord::Base
include Sortable
serialize :properties, JSON
default_value_for :active, false
......@@ -24,7 +25,7 @@ class Service < ActiveRecord::Base
belongs_to :project
has_one :service_hook
validates :project_id, presence: true
validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
......@@ -32,6 +33,10 @@ class Service < ActiveRecord::Base
active
end
def template?
template
end
def category
:common
end
......@@ -92,4 +97,16 @@ class Service < ActiveRecord::Base
def issue_tracker?
self.category == :issue_tracker
end
def self.available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker)
end
def self.create_from_template(project_id, template)
service = template.dup
service.template = false
service.project_id = project_id
service if service.save
end
end
......@@ -16,6 +16,7 @@
#
class Snippet < ActiveRecord::Base
include Sortable
include Linguist::BlobHelper
include Gitlab::VisibilityLevel
......
......@@ -40,15 +40,18 @@
# confirmation_sent_at :datetime
# unconfirmed_email :string(255)
# hide_no_ssh_key :boolean default(FALSE)
# hide_no_password :boolean default(FALSE)
# website_url :string(255) default(""), not null
# last_credential_check_at :datetime
# github_access_token :string(255)
# notification_email :string(255)
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class User < ActiveRecord::Base
include Sortable
include Gitlab::ConfigHelper
include TokenAuthenticatable
extend Gitlab::ConfigHelper
......@@ -58,6 +61,7 @@ class User < ActiveRecord::Base
default_value_for :can_create_group, gitlab_config.default_can_create_group
default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false
default_value_for :hide_no_password, false
default_value_for :projects_limit, current_application_settings.default_projects_limit
default_value_for :theme_id, gitlab_config.default_theme
......@@ -114,6 +118,7 @@ class User < ActiveRecord::Base
#
validates :name, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
validates :notification_email, presence: true, email: { strict_mode: true }
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
......@@ -127,10 +132,12 @@ class User < ActiveRecord::Base
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_save :ensure_authentication_token
after_save :ensure_namespace_correct
......@@ -176,7 +183,6 @@ class User < ActiveRecord::Base
scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_state(:blocked) }
scope :active, -> { with_state(:active) }
scope :alphabetically, -> { order('name ASC') }
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
......@@ -201,11 +207,10 @@ class User < ActiveRecord::Base
def sort(method)
case method.to_s
when 'recent_sign_in' then reorder('users.last_sign_in_at DESC')
when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC')
when 'recently_created' then reorder('users.created_at DESC')
when 'late_created' then reorder('users.created_at ASC')
else reorder("users.name ASC")
when 'recent_sign_in' then reorder(last_sign_in_at: :desc)
when 'oldest_sign_in' then reorder(last_sign_in_at: :asc)
else
order_by(method)
end
end
......@@ -251,6 +256,22 @@ class User < ActiveRecord::Base
joins('LEFT JOIN identities ON identities.user_id = users.id').
where('identities.provider IS NULL OR identities.provider NOT LIKE ?', 'ldap%')
end
def clean_username(username)
username.gsub!(/@.*\z/, "")
username.gsub!(/\.git\z/, "")
username.gsub!(/\A-/, "")
username.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
counter = 0
base = username
while User.by_login(username).present? || Namespace.by_path(username).present?
counter += 1
username = "#{base}#{counter}"
end
username
end
end
#
......@@ -282,7 +303,8 @@ class User < ActiveRecord::Base
def namespace_uniq
namespace_name = self.username
if Namespace.find_by(path: namespace_name)
existing_namespace = Namespace.by_path(namespace_name)
if existing_namespace && existing_namespace != self.namespace
self.errors.add :username, "already exists"
end
end
......@@ -297,11 +319,15 @@ class User < ActiveRecord::Base
self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email)
end
def owns_notification_email
self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end
# Groups user has access to
def authorized_groups
@authorized_groups ||= begin
group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
Group.where(id: group_ids).order('namespaces.name ASC')
Group.where(id: group_ids)
end
end
......@@ -313,7 +339,7 @@ class User < ActiveRecord::Base
project_ids.push(*groups_projects.pluck(:id))
project_ids.push(*projects.pluck(:id).uniq)
project_ids.push(*groups.joins(:shared_projects).pluck(:project_id))
Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
Project.where(id: project_ids)
end
end
......@@ -443,6 +469,12 @@ class User < ActiveRecord::Base
end
end
def set_notification_email
if self.notification_email.blank? || !self.all_emails.include?(self.notification_email)
self.notification_email = self.email
end
end
def requires_ldap_check?
if !Gitlab.config.ldap.enabled
false
......@@ -516,6 +548,10 @@ class User < ActiveRecord::Base
end
end
def all_emails
[self.email, *self.emails.map(&:email)]
end
def hook_attrs
{
name: name,
......@@ -535,7 +571,7 @@ class User < ActiveRecord::Base
def post_create_hook
log_info("User \"#{self.name}\" (#{self.email}) was created")
notification_service.new_user(self, @reset_token)
notification_service.new_user(self, @reset_token) if self.created_by_id
system_hook_service.execute_hooks_for(self, :create)
end
......
......@@ -17,7 +17,7 @@ class CreateBranchService < BaseService
new_branch = repository.find_branch(branch_name)
if new_branch
Event.create_ref_event(project, current_user, new_branch, 'add')
EventCreateService.new.push_ref(project, current_user, new_branch, 'add')
return success(new_branch)
else
return error('Invalid reference name')
......
......@@ -26,7 +26,7 @@ class CreateTagService < BaseService
project.gitlab_ci_service.async_execute(push_data)
end
Event.create_ref_event(project, current_user, new_tag, 'add', 'refs/tags')
EventCreateService.new.push_ref(project, current_user, new_tag, 'add', 'refs/tags')
success(new_tag)
else
error('Invalid reference name')
......
......@@ -25,7 +25,7 @@ class DeleteBranchService < BaseService
end
if repository.rm_branch(branch_name)
Event.create_ref_event(project, current_user, branch, 'rm')
EventCreateService.new.push_ref(project, current_user, branch, 'rm')
success('Branch was removed')
else
return error('Failed to remove branch')
......
......@@ -7,58 +7,98 @@
#
class EventCreateService
def open_issue(issue, current_user)
create_event(issue, current_user, Event::CREATED)
create_record_event(issue, current_user, Event::CREATED)
end
def close_issue(issue, current_user)
create_event(issue, current_user, Event::CLOSED)
create_record_event(issue, current_user, Event::CLOSED)
end
def reopen_issue(issue, current_user)
create_event(issue, current_user, Event::REOPENED)
create_record_event(issue, current_user, Event::REOPENED)
end
def open_mr(merge_request, current_user)
create_event(merge_request, current_user, Event::CREATED)
create_record_event(merge_request, current_user, Event::CREATED)
end
def close_mr(merge_request, current_user)
create_event(merge_request, current_user, Event::CLOSED)
create_record_event(merge_request, current_user, Event::CLOSED)
end
def reopen_mr(merge_request, current_user)
create_event(merge_request, current_user, Event::REOPENED)
create_record_event(merge_request, current_user, Event::REOPENED)
end
def merge_mr(merge_request, current_user)
create_event(merge_request, current_user, Event::MERGED)
create_record_event(merge_request, current_user, Event::MERGED)
end
def open_milestone(milestone, current_user)
create_event(milestone, current_user, Event::CREATED)
create_record_event(milestone, current_user, Event::CREATED)
end
def close_milestone(milestone, current_user)
create_event(milestone, current_user, Event::CLOSED)
create_record_event(milestone, current_user, Event::CLOSED)
end
def reopen_milestone(milestone, current_user)
create_event(milestone, current_user, Event::REOPENED)
create_record_event(milestone, current_user, Event::REOPENED)
end
def leave_note(note, current_user)
create_event(note, current_user, Event::COMMENTED)
create_record_event(note, current_user, Event::COMMENTED)
end
def join_project(project, current_user)
create_event(project, current_user, Event::JOINED)
end
def leave_project(project, current_user)
create_event(project, current_user, Event::LEFT)
end
def create_project(project, current_user)
create_event(project, current_user, Event::CREATED)
end
def push_ref(project, current_user, ref, action = 'add', prefix = 'refs/heads')
commit = project.repository.commit(ref.target)
if action.to_s == 'add'
before = '00000000'
after = commit.id
else
before = commit.id
after = '00000000'
end
data = {
ref: "#{prefix}/#{ref.name}",
before: before,
after: after
}
push(project, current_user, data)
end
def push(project, current_user, push_data)
create_event(project, current_user, Event::PUSHED, data: push_data)
end
private
def create_event(record, current_user, status)
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
def create_record_event(record, current_user, status)
create_event(record.project, current_user, status, target_id: record.id, target_type: record.class.name)
end
def create_event(project, current_user, status, attributes = {})
attributes.reverse_merge!(
project: project,
action: status,
author_id: current_user.id
)
Event.create(attributes)
end
end
......@@ -52,8 +52,7 @@ class GitPushService
end
@push_data = post_receive_data(oldrev, newrev, ref)
create_push_event(@push_data)
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup)
end
......@@ -61,15 +60,6 @@ class GitPushService
protected
def create_push_event(push_data)
Event.create!(
project: project,
action: Event::PUSHED,
data: push_data,
author_id: push_data[:user_id]
)
end
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
def process_commit_messages(ref)
......
......@@ -5,7 +5,7 @@ class GitTagPushService
@project, @user = project, user
@push_data = create_push_data(oldrev, newrev, ref)
create_push_event
EventCreateService.new.push(project, user, @push_data)
project.repository.expire_cache
project.execute_hooks(@push_data.dup, :tag_push_hooks)
......@@ -22,13 +22,4 @@ class GitTagPushService
Gitlab::PushDataBuilder.
build(project, user, oldrev, newrev, ref, [])
end
def create_push_event
Event.create!(
project: project,
action: Event::PUSHED,
data: push_data,
author_id: push_data[:user_id]
)
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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