Commit 4d194502 authored by Timm Drevensek's avatar Timm Drevensek

Merge branch 'master' into request/relative_submodules

parents cb659e4c 7611ce72
......@@ -6,6 +6,13 @@ v 6.8.0
- Drop all tables before restoring a Postgres backup
- Make the repository downloads path configurable
- Create branches via API (sponsored by O'Reilly Media)
- Changed permission of gitlab-satellites directory not to be world accessible
- Protected branch does not allow force push
v 6.7.3
- Fix the merge notification email not being sent (Pierre de La Morinerie)
- Drop all tables before restoring a Postgres backup
- Remove yanked modernizr gem
v 6.7.2
- Fix upgrader script
......
......@@ -29,7 +29,9 @@ If something is wrong but it is not a regression compared to older versions of G
When submitting an issue please conform to the issue submission guidelines listed below.
Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
Do not use the issue tracker for feature requests.
We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
......
......@@ -12,8 +12,6 @@ gem "rails", "~> 4.0.0"
gem "protected_attributes"
gem 'rails-observers'
gem 'actionpack-page_caching'
gem 'actionpack-action_caching'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
......@@ -102,7 +100,7 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
gem 'sidekiq'
gem 'sidekiq', '2.17.0'
# HTTP requests
gem "httparty"
......@@ -161,7 +159,6 @@ gem 'select2-rails'
gem 'jquery-atwho-rails', "~> 0.3.3"
gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2"
gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0'
gem "font-awesome-rails", '~> 3.2'
......
......@@ -27,10 +27,6 @@ GEM
erubis (~> 2.7.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
actionpack-action_caching (1.1.0)
actionpack (>= 4.0.0, < 5.0)
actionpack-page_caching (1.0.2)
actionpack (>= 4.0.0, < 5)
activemodel (4.0.3)
activesupport (= 4.0.3)
builder (~> 3.1.0)
......@@ -289,8 +285,6 @@ GEM
method_source (0.8.2)
mime-types (1.25.1)
minitest (4.7.5)
modernizr (2.6.2)
sprockets (~> 2.0)
multi_json (1.8.4)
multi_xml (0.5.5)
multipart-post (1.2.0)
......@@ -567,8 +561,6 @@ PLATFORMS
DEPENDENCIES
ace-rails-ap
actionpack-action_caching
actionpack-page_caching
acts-as-taggable-on
annotate (~> 2.6.0.beta2)
asciidoctor
......@@ -622,7 +614,6 @@ DEPENDENCIES
launchy
letter_opener
minitest (~> 4.7.0)
modernizr (= 2.6.2)
mysql2
nprogress-rails
omniauth (~> 1.1.3)
......@@ -653,7 +644,7 @@ DEPENDENCIES
select2-rails
settingslogic
shoulda-matchers (~> 2.1.0)
sidekiq
sidekiq (= 2.17.0)
simplecov
sinatra
six
......
......@@ -18,7 +18,6 @@
#= require turbolinks
#= require jquery.turbolinks
#= require bootstrap
#= require modernizr
#= require select2
#= require raphael
#= require g.raphael-min
......
class CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
this.CommitFile = CommitFile
\ No newline at end of file
@CommitFile = CommitFile
......@@ -125,4 +125,4 @@ class ImageFile
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
this.ImageFile = ImageFile
\ No newline at end of file
@ImageFile = ImageFile
......@@ -2,7 +2,7 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery.ui.gitlab
*= require jquery.ui.datepicker
*= require jquery.atwho
*= require select2
*= require highlightjs.min
......@@ -43,6 +43,7 @@
@import "generic/forms.scss";
@import "generic/selects.scss";
@import "generic/highlight.scss";
@import "generic/jquery.scss";
/**
* Page specific styles (issues, projects etc):
......
.ui-widget {
font-family: $regular_font;
font-size: $font-size-base;
&.ui-datepicker-inline {
border: 1px solid #DDD;
padding: 10px;
width: 270px;
.ui-datepicker-header {
background: #EEE;
border-color: #DDD;
}
.ui-datepicker-calendar td a {
padding: 5px;
text-align: center;
}
}
}
This diff is collapsed.
/** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
......@@ -59,9 +59,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
@issue = @project.issues.new(params[:issue])
@issue.author = current_user
@issue.save
@issue = Issues::CreateService.new(project, current_user, params[:issue]).execute
respond_to do |format|
format.html do
......@@ -76,8 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def update
@issue.update_attributes(params[:issue])
@issue.reset_events_cache
@issue = Issues::UpdateService.new(project, current_user, params[:issue]).execute(issue)
respond_to do |format|
format.js
......
......@@ -76,10 +76,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def create
@merge_request = MergeRequest.new(params[:merge_request])
@merge_request.author = current_user
@target_branches ||= []
if @merge_request.save
@merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute
if @merge_request.valid?
redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
else
@source_project = @merge_request.source_project
......@@ -89,29 +89,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def update
# If we close MergeRequest we want to ignore validation
# so we can close broken one (Ex. fork project removed)
if params[:merge_request] == {"state_event"=>"close"}
@merge_request.allow_broken = true
if @merge_request.close
opts = { notice: 'Merge request was successfully closed.' }
else
opts = { alert: 'Failed to close merge request.' }
end
redirect_to [@merge_request.target_project, @merge_request], opts
return
end
# We dont allow change of source/target projects
# after merge request was created
params[:merge_request].delete(:source_project_id)
params[:merge_request].delete(:target_project_id)
if @merge_request.update_attributes(params[:merge_request])
@merge_request.reset_events_cache
@merge_request = MergeRequests::UpdateService.new(project, current_user, params[:merge_request]).execute(@merge_request)
if @merge_request.valid?
respond_to do |format|
format.js
format.html do
......
......@@ -20,7 +20,7 @@ module MergeRequestsHelper
target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref,
title: event.branch_name.humanize
title: event.branch_name.titleize.humanize
}
end
......
......@@ -13,14 +13,15 @@ class Email < ActiveRecord::Base
# Relations
#
belongs_to :user
#
# Validations
#
validates :user_id, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
validate :unique_email, if: ->(email) { email.email_changed? }
after_create :notify
before_validation :cleanup_email
def cleanup_email
......@@ -30,4 +31,8 @@ class Email < ActiveRecord::Base
def unique_email
self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
end
end
\ No newline at end of file
def notify
NotificationService.new.new_email(self)
end
end
......@@ -29,6 +29,10 @@ class Key < ActiveRecord::Base
delegate :name, :email, to: :user, prefix: true
after_create :add_to_shell
after_create :notify_user
after_destroy :remove_from_shell
def strip_white_space
self.key = key.strip unless key.blank?
end
......@@ -42,6 +46,26 @@ class Key < ActiveRecord::Base
"key-#{id}"
end
def add_to_shell
GitlabShellWorker.perform_async(
:add_key,
shell_id,
key
)
end
def notify_user
NotificationService.new.new_key(self)
end
def remove_from_shell
GitlabShellWorker.perform_async(
:remove_key,
shell_id,
key,
)
end
private
def generate_fingerpint
......
......@@ -97,6 +97,7 @@ class MergeRequest < ActiveRecord::Base
validates :target_project, presence: true
validates :target_branch, presence: true
validate :validate_branches
validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
......@@ -125,6 +126,22 @@ class MergeRequest < ActiveRecord::Base
end
end
def validate_fork
return true unless target_project && source_project
if target_project == source_project
true
else
# If source and target projects are different
# we should check if source project is actually a fork of target project
if source_project.forked_from?(target_project)
true
else
errors.add :base, "Source project is not a fork of target project"
end
end
end
def update_merge_request_diff
if source_branch_changed? || target_branch_changed?
reload_code
......
......@@ -552,4 +552,8 @@ class Project < ActiveRecord::Base
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch
end
def forked_from?(project)
forked? && project == forked_from_project
end
end
class EmailObserver < BaseObserver
def after_create(email)
notification.new_email(email)
end
end
class IssueObserver < BaseObserver
def after_create(issue)
notification.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
def after_close(issue, transition)
notification.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_reopen(issue, transition)
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_update(issue)
if issue.is_being_reassigned?
notification.reassigned_issue(issue, current_user)
create_assignee_note(issue)
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end
protected
# Create issue note with service comment like 'Status changed to closed'
def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end
class KeyObserver < BaseObserver
def after_create(key)
GitlabShellWorker.perform_async(
:add_key,
key.shell_id,
key.key
)
notification.new_key(key)
end
def after_destroy(key)
GitlabShellWorker.perform_async(
:remove_key,
key.shell_id,
key.key,
)
end
end
class MergeRequestObserver < BaseObserver
def after_create(merge_request)
event_service.open_mr(merge_request, current_user)
notification.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request)
end
def after_close(merge_request, transition)
event_service.close_mr(merge_request, current_user)
notification.close_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
end
def after_reopen(merge_request, transition)
event_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
merge_request.reload_code
merge_request.mark_as_unchecked
end
def after_update(merge_request)
notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
merge_request.notice_added_references(merge_request.project, current_user)
execute_hooks(merge_request)
end
private
# Create merge request note with service comment like 'Status changed to closed'
def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
def execute_hooks(merge_request)
if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
end
end
end
......@@ -10,7 +10,7 @@ class UsersProjectObserver < BaseObserver
end
def after_update(users_project)
notification.update_team_member(users_project)
notification.update_team_member(users_project) if users_project.project_access_changed?
end
def after_destroy(users_project)
......
......@@ -16,4 +16,16 @@ class BaseService
def can?(object, action, subject)
abilities.allowed?(object, action, subject)
end
def notification_service
NotificationService.new
end
def event_service
EventCreateService.new
end
def log_info message
Gitlab::AppLogger.info message
end
end
......@@ -86,10 +86,9 @@ class GitPushService
author = commit_user(commit)
if !issues_to_close.empty? && is_default_branch
Thread.current[:current_user] = author
Thread.current[:current_commit] = commit
issues_to_close.each { |i| i.close && i.save }
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
......
module Issues
class BaseService < ::BaseService
private
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end
end
module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
if issue.close
notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit)
execute_hooks(issue)
end
issue
end
private
def create_note(issue, current_commit)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
end
end
module Issues
class CreateService < Issues::BaseService
def execute
issue = project.issues.new(params)
issue.author = current_user
if issue.save
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
issue
end
end
end
module Issues
class ReopenService < Issues::BaseService
def execute(issue)
if issue.reopen
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
issue
end
private
def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, nil)
end
end
end
module Issues
class UpdateService < Issues::BaseService
def execute(issue)
state = params.delete('state_event')
case state
when 'reopen'
Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue)
end
if params.present? && issue.update_attributes(params)
issue.reset_events_cache
if issue.previous_changes.include?('assignee_id')
notification_service.reassigned_issue(issue, current_user)
create_assignee_note(issue)
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end
issue
end
end
end
module MergeRequests
class BaseService < ::BaseService
private
def create_assignee_note(merge_request)
Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee)
end
def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
def execute_hooks(merge_request)
if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
end
end
end
end
module MergeRequests
class CloseService < MergeRequests::BaseService
def execute(merge_request, commit = nil)
# If we close MergeRequest we want to ignore validation
# so we can close broken one (Ex. fork project removed)
merge_request.allow_broken = true
if merge_request.close
event_service.close_mr(merge_request, current_user)
notification_service.close_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
end
merge_request
end
end
end
module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
merge_request = MergeRequest.new(params)
merge_request.source_project = project
merge_request.target_project ||= project
merge_request.author = current_user
if merge_request.save
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request)
end
merge_request
end
end
end
module MergeRequests
class ReopenService < MergeRequests::BaseService
def execute(merge_request)
if merge_request.reopen
event_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
merge_request.reload_code
merge_request.mark_as_unchecked
end
merge_request
end
end
end
require_relative 'base_service'
require_relative 'reopen_service'
require_relative 'close_service'
module MergeRequests
class UpdateService < MergeRequests::BaseService
def execute(merge_request)
# We dont allow change of source/target projects
# after merge request was created
params.delete(:source_project_id)
params.delete(:target_project_id)
state = params.delete('state_event')
case state
when 'reopen'
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
end
if params.present? && merge_request.update_attributes(params)
merge_request.reset_events_cache
if merge_request.previous_changes.include?('assignee_id')
notification_service.reassigned_merge_request(merge_request, current_user)
create_assignee_note(merge_request)
end
merge_request.notice_added_references(merge_request.project, current_user)
execute_hooks(merge_request)
end
merge_request
end
end
end
......@@ -178,29 +178,29 @@ class NotificationService
# Get project users with WATCH notification level
def project_watchers(project)
# Gather all user ids that have WATCH notification setting for project
project_notification_uids = project_notification_list(project, Notification::N_WATCH)
project_members = users_project_notification(project)
# Gather all user ids that have WATCH notification setting for group
group_notification_uids = group_notification_list(project, Notification::N_WATCH)
users_with_project_level_global = users_project_notification(project, Notification::N_GLOBAL)
users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL)
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
# Gather all user ids that have GLOBAL setting
global_notification_uids = global_notification_list(project)
users_with_project_setting = select_users_project_setting(project, users_with_project_level_global, users)
users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users)
project_and_group_uids = [project_notification_uids, group_notification_uids].flatten.uniq
group_and_project_watchers = User.where(id: project_and_group_uids)
# Find all users that have WATCH as their GLOBAL setting
global_watchers = User.where(id: global_notification_uids, notification_level: Notification::N_WATCH)
[group_and_project_watchers, global_watchers].flatten.uniq
User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
end
def project_notification_list(project, notification_level)
project.users_projects.where(notification_level: notification_level).pluck(:user_id)
def users_project_notification(project, notification_level=nil)
project_members = project.users_projects
if notification_level
project_members.where(notification_level: notification_level).pluck(:user_id)
else
project_members.pluck(:user_id)
end
end
def group_notification_list(project, notification_level)
def users_group_notification(project, notification_level)
if project.group
project.group.users_groups.where(notification_level: notification_level).pluck(:user_id)
else
......@@ -208,11 +208,47 @@ class NotificationService
end
end
def global_notification_list(project)
[
project_notification_list(project, Notification::N_GLOBAL),
group_notification_list(project, Notification::N_GLOBAL)
].flatten
def users_with_global_level_watch(ids)
User.where(
id: ids,
notification_level: Notification::N_WATCH
).pluck(:id)
end
# Build a list of users based on project notifcation settings
def select_users_project_setting(project, global_setting, users_global_level_watch)
users = users_project_notification(project, Notification::N_WATCH)
# If project setting is global, add to watch list if global setting is watch
global_setting.each do |user_id|
if users_global_level_watch.include?(user_id)
users << user_id
end
end
users
end
# Build a list of users based on group notifcation settings
def select_users_group_setting(project, project_members, global_setting, users_global_level_watch)
uids = users_group_notification(project, Notification::N_WATCH)
# Group setting is watch, add to users list if user is not project member
users = []
uids.each do |user_id|
if project_members.exclude?(user_id)
users << user_id
end
end
# Group setting is global, add to users list if global setting is watch
global_setting.each do |user_id|
if project_members.exclude?(user_id) && users_global_level_watch.include?(user_id)
users << user_id
end
end
users
end
# Remove users with disabled notifications from array
......
......@@ -20,6 +20,11 @@
= form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']
- if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
%i.icon-plus
......
......@@ -33,7 +33,7 @@
.col-sm-10
.clearfix
.pull-left
- projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
.pull-left
&nbsp;
......
......@@ -3,5 +3,5 @@
var mrTitle = $('#merge_request_title');
if(mrTitle.val().length == 0) {
mrTitle.val("#{params[:ref].humanize}");
mrTitle.val("#{params[:ref].titleize.humanize}");
}
......@@ -9,6 +9,7 @@
%ul
%li keep stable branches secured
%li forced code review before merge to protected branches
%li prevents branch from force push
%p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"}
- if can? current_user, :admin_project, @project
......
......@@ -21,9 +21,6 @@ module Gitlab
# Activate observers that should always be running.
config.active_record.observers = :milestone_observer,
:project_activity_cache_observer,
:issue_observer,
:key_observer,
:merge_request_observer,
:note_observer,
:project_observer,
:system_hook_observer,
......@@ -64,6 +61,7 @@ module Gitlab
config.assets.enabled = true
config.assets.paths << Emoji.images_path
config.assets.precompile << "emoji/*.png"
config.assets.precompile << "print.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
......
## The GitLab Documentation covers the following subjects
**User documentation**
+ [API](api/README.md)
+ [Development](development/README.md)
+ [Install](install/README.md)
+ [Integration](integration/external-issue-tracker.md)
+ [Legal](legal/README.md)
+ [Markdown](markdown/markdown.md)
+ [Permissions](permissions/permissions.md)
+ [Public access](public_access/public_access.md)
+ [Raketasks](raketasks/README.md)
+ [Release](release/README.md)
+ [Security](security/README.md)
+ [SSH](ssh/README.md)
+ [System hooks](system_hooks/system_hooks.md)
+ [Update](update/README.md)
+ [Web hooks](web_hooks/web_hooks.md)
+ [Workflow](workflow/workflow.md)
+ [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
+ [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
+ [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
+ [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
+ [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
+ [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
+ [Workflow](workflow/workflow.md) Learn how to use Git and GitLab together.
**Administrator documentation**
+ [Install](install/README.md) Requirements, directory structures and manual installation.
+ [Integration](integration/external-issue-tracker.md) How to integrate JIRA and Redmine.
+ [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
+ [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
+ [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
+ [Update](update/README.md) Update guides to upgrade your installation.
**Contributor documentation**
+ [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
+ [Legal](legal/README.md) Contributor license agreements.
+ [Release](release/README.md) How to make the monthly and security releases.
......@@ -93,7 +93,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
# 2. Ruby
The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. Version managers are not supported and we stronly advise everyone to follow the instructions below to use a system ruby.
The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we stronly advise everyone to follow the instructions below to use a system ruby.
Remove the old Ruby 1.8 if present
......@@ -202,6 +202,7 @@ You can change `6-6-stable` to `master` if you want the *bleeding edge* version,
# Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites
sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
# Create directories for sockets/pids and make sure GitLab can write to them
sudo -u git -H mkdir tmp/pids/
......
......@@ -2,7 +2,7 @@ GitLab has a great issue tracker but you can also use an external issue tracker
- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
- clicking 'New issue' on the project dashboard creates a new JIRA issue;
- textual references to PROJECT-1234 in comments, commit messages get turned into HTML links to the corresponding JIRA issue.
- To reference JIRA issue PROJECT-1234 in comments, use syntax #PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
![jira screenshot](jira-intergration-points.png)
......
# GitLab LDAP integration
GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory.
The first time a user signs in with LDAP credentials, GitLab will create a new GitLab user associated with the LDAP Distinguished Name (DN) of the LDAP user.
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
If the LDAP email attribute is not found in GitLab's database, a new user is created.
In other words, if an existing GitLab user wants to enable LDAP sign-in for themselves, they should check that their GitLab email address matches their LDAP email address, and then sign into GitLab via their LDAP credentials.
GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`.
......@@ -61,9 +61,9 @@ After making the release branch new commits are cherry-picked from master. When
* 1-7th: official merge window (see contributing guide)
* 8-14th: work on bugfixes, sponsored features and GitLab EE
* 15th: code freeze (stop merging into master except essential bugfixes)
* 18th: release candidate 1 (VERSION x.x.0.rc1, tag and tweet about x.x.0.rc1, release on GitLab Cloud)
* 18th: release candidate 1 (VERSION x.x.0.rc1, annotated tag and tweet about x.x.0.rc1, release on GitLab Cloud)
* 20st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems)
* 22nd: release (VERSION x.x.0, create x-x-stable branch, tag, blog and tweet)
* 22nd: release (VERSION x.x.0, create x-x-stable branch, annotated tag tag, blog and tweet)
* 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems)
* 24-end of month: release GitLab EE and GitLab CI
......
......@@ -18,7 +18,7 @@ Please report suspected security vulnerabilities in private to support@gitlab.co
1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge the code feature branch into master
1. Cherry-pick the code into the latest stable branch
1. Create a git tag vX.X.X for CE and another patch release for EE
1. Create an annotated tag vX.X.X for CE and another patch release for EE
1. Push the code and the tags to all the CE and EE repositories
1. Apply the patch to GitLab Cloud and the private GitLab development server
1. Merge and publish the blog posts
......
......@@ -80,6 +80,9 @@ sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Close access to gitlab-satellites for others
sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
```
### 6. Update config files
......
......@@ -63,6 +63,9 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
# Close access to gitlab-satellites for others
sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
```
......
......@@ -40,3 +40,10 @@ To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations upgrade is complete!
### One line upgrade command
You've read through the entire guide, and probably did all the steps manually. Here is a one liner for convenience, the next time you upgrade:
cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; sudo service gitlab stop; sudo -u git -H ruby script/upgrade.rb -y; sudo service gitlab start; sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+ [Workflow](workflow/workflow.md)
+ [Project Features](workflow/project_features.md)
When in a Project -> Settings, you will find Features on the bottom of the page that you can toggle.
Below you will find a more elaborate explanation of each of these.
## Issues
Issues is a really powerful, but lightweight issue tracking system.
You can make tickets, assign them to people, file them under milestones, order them with labels and have discussion in them.
They integrate deeply into GitLab and are easily referenced from anywhere by using # and the issuenumber.
At GitLab.com, we use this for all our project management needs.
## Merge Requests
Using a merge request, you can review and discuss code before it is merged in the branch of your code.
As with issues, it can be assigned; people, issues, etc. can be refereced; milestones attached.
We see it as an integral part of working together on code and couldn't work without it.
## Wiki
This is a separate system for documentation, built right into GitLab.
It is source controlled and is very convenient if you don't want to keep you documentation in your source code, but you do want to keep it in your GitLab project.
## Wall
For simple, project specific conversations, the wall can be used.
It's very lightweight and simple and works well if you're not interested in using issues, but still want to occasionally communicate within a project.
## Snippets
Snippets are little bits of code or text.
This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
For example, a specific config file that is used by > the team that is only valid for the people that work on the code.
......@@ -53,15 +53,15 @@ class DashboardMergeRequests < Spinach::FeatureSteps
end
def assigned_merge_request
@assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project
@assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
end
def authored_merge_request
@authored_merge_request ||= create :merge_request, author: current_user, target_project: project
@authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
end
def other_merge_request
@other_merge_request ||= create :merge_request, target_project: project
@other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
end
def project
......
......@@ -52,6 +52,4 @@ Spinach.hooks.before_run do
RSpec::Mocks::setup self
include FactoryGirl::Syntax::Methods
MergeRequestObserver.any_instance.stub(current_user: create(:user))
end
......@@ -10,6 +10,7 @@ module API
# project - project path with namespace
# action - git action (git-upload-pack or git-receive-pack)
# ref - branch name
# forced_push - forced_push
#
get "/allowed" do
# Check for *.wiki repositories.
......@@ -35,7 +36,8 @@ module API
project,
params[:ref],
params[:oldrev],
params[:newrev]
params[:newrev],
params[:forced_push]
)
end
......
......@@ -48,17 +48,15 @@ module API
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
set_current_user_for_thread do
required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present?
@issue = user_project.issues.new attrs
@issue.author = current_user
if @issue.save
present @issue, with: Entities::Issue
else
not_found!
end
required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present?
issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
if issue.valid?
present issue, with: Entities::Issue
else
not_found!
end
end
......@@ -76,18 +74,18 @@ module API
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
set_current_user_for_thread do
@issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue
issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
if @issue.update_attributes attrs
present @issue, with: Entities::Issue
else
not_found!
end
if issue.valid?
present issue, with: Entities::Issue
else
not_found!
end
end
......
......@@ -13,14 +13,6 @@ module API
end
not_found!
end
def not_fork?(target_project_id, user_project)
target_project_id.nil? || target_project_id == user_project.id.to_s
end
def target_matches_fork(target_project_id,user_project)
user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
end
end
# List merge requests
......@@ -70,29 +62,15 @@ module API
# POST /projects/:id/merge_requests
#
post ":id/merge_requests" do
set_current_user_for_thread do
authorize! :write_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
merge_request = user_project.merge_requests.new(attrs)
merge_request.author = current_user
merge_request.source_project = user_project
target_project_id = attrs[:target_project_id]
if not_fork?(target_project_id, user_project)
merge_request.target_project = user_project
else
if target_matches_fork(target_project_id,user_project)
merge_request.target_project = Project.find_by(id: attrs[:target_project_id])
else
render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
end
end
if merge_request.save
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
end
authorize! :write_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
if merge_request.valid?
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
end
end
......@@ -111,17 +89,15 @@ module API
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
set_current_user_for_thread do
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
if merge_request.update_attributes attrs
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
end
if merge_request.valid?
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
end
end
......
......@@ -5,7 +5,7 @@ module Gitlab
attr_reader :params, :project, :git_cmd, :user
def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil)
def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false)
case cmd
when *DOWNLOAD_COMMANDS
if actor.is_a? User
......@@ -19,12 +19,12 @@ module Gitlab
end
when *PUSH_COMMANDS
if actor.is_a? User
push_allowed?(actor, project, ref, oldrev, newrev)
push_allowed?(actor, project, ref, oldrev, newrev, forced_push)
elsif actor.is_a? DeployKey
# Deploy key not allowed to push
return false
elsif actor.is_a? Key
push_allowed?(actor.user, project, ref, oldrev, newrev)
push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push)
else
raise 'Wrong actor'
end
......@@ -41,13 +41,17 @@ module Gitlab
end
end
def push_allowed?(user, project, ref, oldrev, newrev)
def push_allowed?(user, project, ref, oldrev, newrev, forced_push)
if user && user_allowed?(user)
action = if project.protected_branch?(ref)
:push_code_to_protected_branches
else
:push_code
end
if forced_push.to_s == 'true'
:force_push_code_to_protected_branches
else
:push_code_to_protected_branches
end
else
:push_code
end
user.can?(action, project)
else
false
......
......@@ -342,6 +342,7 @@ namespace :gitlab do
check_repo_base_is_not_symlink
check_repo_base_user_and_group
check_repo_base_permissions
check_satellites_permissions
check_update_hook_is_up_to_date
check_repos_update_hooks_is_link
check_gitlab_shell_self_test
......@@ -443,6 +444,29 @@ namespace :gitlab do
end
end
def check_satellites_permissions
print "Satellites access is drwxr-x---? ... "
satellites_path = Gitlab.config.satellites.path
unless File.exists?(satellites_path)
puts "can't check because of previous errors".magenta
return
end
if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
puts "yes".green
else
puts "no".red
try_fixing_it(
"sudo chmod u+rwx,g+rx,o-rwx #{satellites_path}",
)
for_more_information(
see_installation_guide_section "GitLab"
)
fix_and_rerun
end
end
def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
......
......@@ -24,6 +24,7 @@ namespace :gitlab do
puts "Gem Version:\t#{gem_version || "unknown".red}"
puts "Bundler Version:#{bunder_version || "unknown".red}"
puts "Rake Version:\t#{rake_version || "unknown".red}"
puts "Sidekiq Version:#{Sidekiq::VERSION}"
# check database adapter
......
......@@ -37,7 +37,7 @@ function start_no_deamonize
function start_sidekiq
{
bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
}
function load_ok
......
......@@ -5,10 +5,10 @@ describe MergeRequestsFinder do
let(:user2) { create :user }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let(:project2) { create(:project, forked_from_project: project1) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
before do
......@@ -21,7 +21,7 @@ describe MergeRequestsFinder do
it 'should filter by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = MergeRequestsFinder.new.execute(user, params)
merge_requests.size.should == 3
merge_requests.size.should == 2
end
it 'should filter by project' do
......
......@@ -13,7 +13,7 @@ describe 'Gitlab::Satellite::MergeAction' do
end
let(:project) { create(:project, namespace: create(:group)) }
let(:fork_project) { create(:project, namespace: create(:group)) }
let(:fork_project) { create(:project, namespace: create(:group), forked_from_project: project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) }
......
......@@ -68,4 +68,18 @@ describe Key do
build(:invalid_key).should_not be_valid
end
end
context 'callbacks' do
it 'should add new key to authorized_file' do
@key = build(:personal_key, id: 7)
GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
@key.save
end
it 'should remove key from authorized_file' do
@key = create(:personal_key)
GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
@key.destroy
end
end
end
......@@ -209,7 +209,7 @@ describe Note do
let(:project) { create(:project) }
let(:author) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:mergereq) { create(:merge_request, target_project: project) }
let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
let(:commit) { project.repository.commit }
# Test all of {issue, merge request, commit} in both the referenced and referencing
......
require 'spec_helper'
describe EmailObserver do
let(:email) { create(:email) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
subject { EmailObserver.instance }
describe '#after_create' do
it 'trigger notification to send emails' do
subject.should_receive(:notification)
subject.after_create(email)
end
end
end
require 'spec_helper'
describe IssueObserver do
let(:some_user) { create :user }
let(:assignee) { create :user }
let(:author) { create :user }
let(:mock_issue) { create(:issue, assignee: assignee, author: author) }
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(:current_commit).and_return(nil) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) }
subject { IssueObserver.instance }
describe '#after_create' do
it 'trigger notification to send emails' do
subject.should_receive(:notification)
subject.after_create(mock_issue)
end
it 'should create cross-reference notes' do
other_issue = create(:issue)
mock_issue.stub(references: [other_issue])
Note.should_receive(:create_cross_reference_note).with(other_issue, mock_issue,
some_user, mock_issue.project)
subject.after_create(mock_issue)
end
end
context '#after_close' do
context 'a status "closed"' do
before { mock_issue.stub(state: 'closed') }
it 'note is created if the issue is being closed' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', nil)
subject.after_close(mock_issue, nil)
end
it 'trigger notification to send emails' do
subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
subject.after_close(mock_issue, nil)
end
it 'appends a mention to the closing commit if one is present' do
commit = double('commit', gfm_reference: 'commit 123456')
subject.stub(current_commit: commit)
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', commit)
subject.after_close(mock_issue, nil)
end
end
context 'a status "reopened"' do
before { mock_issue.stub(state: 'reopened') }
it 'note is created if the issue is being reopened' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened', nil)
subject.after_reopen(mock_issue, nil)
end
end
end
context '#after_update' do
before(:each) do
mock_issue.stub(:is_being_reassigned?).and_return(false)
end
context 'notification' do
it 'triggered if the issue is being reassigned' do
mock_issue.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:notification)
subject.after_update(mock_issue)
end
it 'is not triggered if the issue is not being reassigned' do
mock_issue.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:notification)
subject.after_update(mock_issue)
end
end
context 'cross-references' do
it 'notices added references' do
mock_issue.should_receive(:notice_added_references)
subject.after_update(mock_issue)
end
end
end
end
require 'spec_helper'
describe KeyObserver do
before do
@key = create(:personal_key)
@observer = KeyObserver.instance
end
context :after_create do
it do
GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
@observer.after_create(@key)
end
end
context :after_destroy do
it do
GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
@observer.after_destroy(@key)
end
end
end
require 'spec_helper'
describe MergeRequestObserver do
let(:some_user) { create :user }
let(:assignee) { create :user }
let(:author) { create :user }
let(:project) { create :project }
let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author).as_null_object }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, source_project: project) }
let(:unassigned_mr) { create(:merge_request, author: author, source_project: project) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, source_project: project) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, source_project: project) }
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
before { mr_mock.stub(:author_id) }
before { mr_mock.stub(:source_project) }
before { mr_mock.stub(:source_project) }
before { mr_mock.stub(:project) }
before { mr_mock.stub(:create_cross_references!).and_return(true) }
before { Repository.any_instance.stub(commit: nil) }
before(:each) { enable_observers }
after(:each) { disable_observers }
subject { MergeRequestObserver.instance }
describe '#after_create' do
it 'trigger notification service' do
subject.should_receive(:notification)
subject.after_create(mr_mock)
end
it 'creates cross-reference notes' do
project = create :project
mr_mock.stub(title: "this mr references !#{assigned_mr.id}", project: project)
mr_mock.should_receive(:create_cross_references!).with(project, some_user)
subject.after_create(mr_mock)
end
end
context '#after_update' do
before(:each) do
mr_mock.stub(:is_being_reassigned?).and_return(false)
mr_mock.stub(:notice_added_references)
end
it 'is called when a merge request is changed' do
changed = create(:merge_request, source_project: project)
subject.should_receive(:after_update)
MergeRequest.observers.enable :merge_request_observer do
changed.title = 'I changed'
changed.save
end
end
it 'checks for new references' do
mr_mock.should_receive(:notice_added_references)
subject.after_update(mr_mock)
end
context 'a notification' do
it 'is sent if the merge request is being reassigned' do
mr_mock.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:notification)
subject.after_update(mr_mock)
end
it 'is not sent if the merge request is not being reassigned' do
mr_mock.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:notification)
subject.after_update(mr_mock)
end
end
end
context '#after_close' do
context 'a status "closed"' do
it 'note is created if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.source_project, some_user, 'closed', nil)
assigned_mr.close
end
it 'notification is delivered only to author if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.source_project, some_user, 'closed', nil)
unassigned_mr.close
end
end
end
context '#after_reopen' do
context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.source_project, some_user, 'reopened', nil)
closed_assigned_mr.reopen
end
it 'notification is delivered only to author if the merge request is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.source_project, some_user, 'reopened', nil)
closed_unassigned_mr.reopen
end
end
end
describe "Merge Request created" do
def self.it_should_be_valid_event
it { @event.should_not be_nil }
it { @event.should_not be_nil }
it { @event.project.should == project }
it { @event.project.should == project }
end
before do
@merge_request = create(:merge_request, source_project: project, target_project: project)
@event = Event.last
end
it_should_be_valid_event
it { @event.action.should == Event::CREATED }
it { @event.target.should == @merge_request }
end
end
......@@ -21,7 +21,7 @@ describe UsersProjectObserver do
it "should send email to user" do
subject.should_receive(:notification)
@users_project.update_attribute(:project_access, UsersProject::MASTER)
@users_project.update_attribute(:project_access, UsersProject::OWNER)
end
it "should not called after UsersProject destroyed" do
......
......@@ -6,7 +6,7 @@ describe API::API do
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) }
let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
before {
project.team << [user, :reporters]
......@@ -79,16 +79,12 @@ describe API::API do
end
context 'forked projects' do
let!(:user2) {create(:user)}
let!(:forked_project_link) { build(:forked_project_link) }
let!(:fork_project) { create(:project, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
let!(:user2) { create(:user) }
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
before :each do |each|
fork_project.team << [user2, :reporters]
forked_project_link.forked_from_project = project
forked_project_link.forked_to_project = fork_project
forked_project_link.save!
end
it "should return merge_request" do
......@@ -127,16 +123,16 @@ describe API::API do
response.status.should == 400
end
it "should return 400 when target_branch is specified and not a forked project" do
it "should return 404 when target_branch is specified and not a forked project" do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
response.status.should == 400
response.status.should == 404
end
it "should return 400 when target_branch is specified and for a different fork" do
it "should return 404 when target_branch is specified and for a different fork" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
response.status.should == 400
response.status.should == 404
end
it "should return 201 when target_branch is specified and for the same project" do
......
......@@ -170,16 +170,10 @@ describe GitPushService do
Issue.find(issue.id).should be_closed
end
it "passes the closing commit as a thread-local" do
service.execute(project, user, @oldrev, @newrev, @ref)
Thread.current[:current_commit].should == closing_commit
end
it "doesn't create cross-reference notes for a closing reference" do
expect {
service.execute(project, user, @oldrev, @newrev, @ref)
}.not_to change { Note.where(project_id: project.id, system: true).count }
}.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count }
end
it "doesn't close issues when pushed to non-default branches" do
......
require 'spec_helper'
describe Issues::CloseService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue, assignee: user2) }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
@issue = Issues::CloseService.new(project, user, {}).execute(issue)
end
it { @issue.should be_valid }
it { @issue.should be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
note.note.should include "Status changed to closed"
end
end
end
end
require 'spec_helper'
describe Issues::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
describe :execute do
context "valid params" do
before do
project.team << [user, :master]
opts = {
title: 'Awesome issue',
description: 'please fix'
}
@issue = Issues::CreateService.new(project, user, opts).execute
end
it { @issue.should be_valid }
it { @issue.title.should == 'Awesome issue' }
end
end
end
require 'spec_helper'
describe Issues::UpdateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue) }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
opts = {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close'
}
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
end
it { @issue.should be_valid }
it { @issue.title.should == 'New title' }
it { @issue.assignee.should == user2 }
it { @issue.should be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
note.note.should include "Reassigned to \@#{user2.username}"
end
end
end
end
require 'spec_helper'
describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user2) }
let(:project) { merge_request.project }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
@merge_request = MergeRequests::CloseService.new(project, user, {}).execute(merge_request)
end
it { @merge_request.should be_valid }
it { @merge_request.should be_closed }
it 'should send email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(merge_request.title)
end
it 'should create system note about merge_request reassign' do
note = @merge_request.notes.last
note.note.should include "Status changed to closed"
end
end
end
end
require 'spec_helper'
describe MergeRequests::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
describe :execute do
context "valid params" do
before do
project.team << [user, :master]
opts = {
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'stable',
target_branch: 'master'
}
@merge_request = MergeRequests::CreateService.new(project, user, opts).execute
end
it { @merge_request.should be_valid }
it { @merge_request.title.should == 'Awesome merge_request' }
end
end
end
require 'spec_helper'
describe MergeRequests::UpdateService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple) }
let(:project) { merge_request.project }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
opts = {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close'
}
@merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
end
it { @merge_request.should be_valid }
it { @merge_request.title.should == 'New title' }
it { @merge_request.assignee.should == user2 }
it { @merge_request.should be_closed }
it 'should send email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(merge_request.title)
end
it 'should create system note about merge_request reassign' do
note = @merge_request.notes.last
note.note.should include "Reassigned to \@#{user2.username}"
end
end
end
end
......@@ -5,7 +5,7 @@ describe NotificationService do
describe 'Keys' do
describe :new_key do
let(:key) { create(:personal_key) }
let!(:key) { create(:personal_key) }
it { notification.new_key(key).should be_true }
......@@ -18,7 +18,7 @@ describe NotificationService do
describe 'Email' do
describe :new_email do
let(:email) { create(:email) }
let!(:email) { create(:email) }
it { notification.new_email(email).should be_true }
......@@ -57,15 +57,42 @@ describe NotificationService do
Notify.should_not_receive(:note_issue_email)
notification.new_note(mentioned_note)
end
end
def should_email(user_id)
Notify.should_receive(:note_issue_email).with(user_id, note.id)
describe 'new note on issue in project that belongs to a group' do
let(:group) { create(:group) }
before do
note.project.namespace_id = group.id
note.project.group.add_user(@u_watcher, UsersGroup::MASTER)
note.project.save
user_project = note.project.users_projects.find_by_user_id(@u_watcher.id)
user_project.notification_level = Notification::N_PARTICIPATING
user_project.save
user_group = note.project.group.users_groups.find_by_user_id(@u_watcher.id)
user_group.notification_level = Notification::N_GLOBAL
user_group.save
end
def should_not_email(user_id)
Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
it do
should_email(note.noteable.author_id)
should_email(note.noteable.assignee_id)
should_email(@u_mentioned.id)
should_not_email(@u_watcher.id)
should_not_email(note.author_id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.new_note(note)
end
end
def should_email(user_id)
Notify.should_receive(:note_issue_email).with(user_id, note.id)
end
def should_not_email(user_id)
Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
end
end
context 'commit note' do
......
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