Commit 41b08e4a authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents fc4f5eef 8e05ee36
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.1.0 (unreleased) v 8.1.0 (unreleased)
- Include full path of source and target branch names in New Merge Request page (Stan Hu)
- Add user preference to view activities as default dashboard (Stan Hu) - Add user preference to view activities as default dashboard (Stan Hu)
- Add option to admin area to sign in as a specific user (Pavel Forkert) - Add option to admin area to sign in as a specific user (Pavel Forkert)
- Show CI status on all pages where commits list is rendered - Show CI status on all pages where commits list is rendered
...@@ -17,6 +18,9 @@ v 8.1.0 (unreleased) ...@@ -17,6 +18,9 @@ v 8.1.0 (unreleased)
- Move CI variables page to project settings area - Move CI variables page to project settings area
- Move CI triggers page to project settings area - Move CI triggers page to project settings area
- Move CI project settings page to CE project settings area - Move CI project settings page to CE project settings area
- Fix bug when removed file was not appearing in merge request diff
- Note the original location of a moved project when notifying users of the move
- Improve error message when merging fails
v 8.0.3 v 8.0.3
- Fix URL shown in Slack notifications - Fix URL shown in Slack notifications
......
...@@ -15,11 +15,12 @@ class @MergeRequestWidget ...@@ -15,11 +15,12 @@ class @MergeRequestWidget
type: 'GET' type: 'GET'
url: $('.merge-request').data('url') url: $('.merge-request').data('url')
success: (data) => success: (data) =>
switch data.state if data.state == "merged"
when 'merged' location.reload()
location.reload() else if data.merge_error
else $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
setTimeout(merge_request_widget.mergeInProgress, 2000) else
setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
......
...@@ -12,7 +12,10 @@ module Ci ...@@ -12,7 +12,10 @@ module Ci
def show def show
@builds = @runner.builds.order('id DESC').first(30) @builds = @runner.builds.order('id DESC').first(30)
@projects = Ci::Project.all @projects = Ci::Project.all
@projects = @projects.search(params[:search]) if params[:search].present? if params[:search].present?
@gl_projects = ::Project.search(params[:search])
@projects = @projects.where(gitlab_id: @gl_projects.select(:id))
end
@projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any? @projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
end end
......
...@@ -16,27 +16,6 @@ class PasswordsController < Devise::PasswordsController ...@@ -16,27 +16,6 @@ class PasswordsController < Devise::PasswordsController
end end
end end
# After a user resets their password, prompt for 2FA code if enabled instead
# of signing in automatically
#
# See http://git.io/vURrI
def update
super do |resource|
# TODO (rspeicher): In Devise master (> 3.4.1), we can set
# `Devise.sign_in_after_reset_password = false` and avoid this mess.
if resource.errors.empty? && resource.try(:two_factor_enabled?)
resource.unlock_access! if unlockable?(resource)
# Since we are not signing this user in, we use the :updated_not_active
# message which only contains "Your password was changed successfully."
set_flash_message(:notice, :updated_not_active) if is_flashing_format?
# Redirect to sign in so they can enter 2FA code
respond_with(resource, location: new_session_path(resource)) and return
end
end
end
def edit def edit
super super
reset_password_token = Devise.token_generator.digest( reset_password_token = Devise.token_generator.digest(
......
...@@ -150,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -150,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied! unless @merge_request.can_be_merged_by?(current_user) return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.mergeable? if @merge_request.mergeable?
@merge_request.update(merge_error: nil)
MergeWorker.perform_async(@merge_request.id, current_user.id, params) MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true @status = true
else else
......
...@@ -167,4 +167,23 @@ module DiffHelper ...@@ -167,4 +167,23 @@ module DiffHelper
content_tag(:span, commit_id, class: 'monospace'), content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe ].join(' ').html_safe
end end
def commit_for_diff(diff)
if diff.deleted_file
@merge_request ? @merge_request.commits.last : @commit.parent_id
else
@commit
end
end
def diff_file_html_data(project, diff_commit, diff_file)
{
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
tree_join(diff_commit.id, diff_file.file_path))
}
end
def editable_diff?(diff)
!diff.deleted_file && @merge_request && @merge_request.source_project
end
end end
...@@ -71,4 +71,17 @@ module MergeRequestsHelper ...@@ -71,4 +71,17 @@ module MergeRequestsHelper
merge_request.source_branch merge_request.source_branch
end end
end end
def format_mr_branch_names(merge_request)
source_path = merge_request.source_project_path
target_path = merge_request.target_project_path
source_branch = merge_request.source_branch
target_branch = merge_request.target_branch
if source_path == target_path
[source_branch, target_branch]
else
["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"]
end
end
end end
...@@ -50,10 +50,11 @@ module Emails ...@@ -50,10 +50,11 @@ module Emails
subject: subject("Invitation declined")) subject: subject("Invitation declined"))
end end
def project_was_moved_email(project_id, user_id) def project_was_moved_email(project_id, user_id, old_path_with_namespace)
@current_user = @user = User.find user_id @current_user = @user = User.find user_id
@project = Project.find project_id @project = Project.find project_id
@target_url = namespace_project_url(@project.namespace, @project) @target_url = namespace_project_url(@project.namespace, @project)
@old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email, mail(to: @user.notification_email,
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
......
...@@ -48,11 +48,12 @@ module Ci ...@@ -48,11 +48,12 @@ module Ci
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
# #
# Validations # Validations
# #
validates_presence_of :name, :timeout, :token, :default_ref, validates_presence_of :timeout, :token, :default_ref, :gitlab_id
:path, :ssh_url_to_repo, :gitlab_id
validates_uniqueness_of :gitlab_id validates_uniqueness_of :gitlab_id
...@@ -60,8 +61,6 @@ module Ci ...@@ -60,8 +61,6 @@ module Ci
presence: true, presence: true,
if: ->(project) { project.always_build.present? } if: ->(project) { project.always_build.present? }
scope :public_only, ->() { where(public: true) }
before_validation :set_default_values before_validation :set_default_values
class << self class << self
...@@ -76,12 +75,9 @@ module Ci ...@@ -76,12 +75,9 @@ module Ci
def parse(project) def parse(project)
params = { params = {
name: project.name_with_namespace, gitlab_id: project.id,
gitlab_id: project.id, default_ref: project.default_branch || 'master',
path: project.path_with_namespace, email_add_pusher: current_application_settings.add_pusher,
default_ref: project.default_branch || 'master',
ssh_url_to_repo: project.ssh_url_to_repo,
email_add_pusher: current_application_settings.add_pusher,
email_only_broken_builds: current_application_settings.all_broken_builds, email_only_broken_builds: current_application_settings.all_broken_builds,
} }
...@@ -105,11 +101,18 @@ module Ci ...@@ -105,11 +101,18 @@ module Ci
joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id"). joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC") order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
end end
end
def search(query) def name
where("LOWER(#{Ci::Project.table_name}.name) LIKE :query", name_with_namespace
query: "%#{query.try(:downcase)}%") end
end
def path
path_with_namespace
end
def gitlab_url
web_url
end end
def any_runners? def any_runners?
...@@ -123,9 +126,6 @@ module Ci ...@@ -123,9 +126,6 @@ module Ci
def set_default_values def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank? self.token = SecureRandom.hex(15) if self.token.blank?
self.default_ref ||= 'master' self.default_ref ||= 'master'
self.name ||= gl_project.name_with_namespace
self.path ||= gl_project.path_with_namespace
self.ssh_url_to_repo ||= gl_project.ssh_url_to_repo
end end
def tracked_refs def tracked_refs
...@@ -169,7 +169,7 @@ module Ci ...@@ -169,7 +169,7 @@ module Ci
# using http and basic auth # using http and basic auth
def repo_url_with_auth def repo_url_with_auth
auth = "gitlab-ci-token:#{token}@" auth = "gitlab-ci-token:#{token}@"
url = gitlab_url + ".git" url = http_url_to_repo + ".git"
url.sub(/^https?:\/\//) do |prefix| url.sub(/^https?:\/\//) do |prefix|
prefix + auth prefix + auth
end end
...@@ -201,10 +201,6 @@ module Ci ...@@ -201,10 +201,6 @@ module Ci
end end
end end
def gitlab_url
File.join(Gitlab.config.gitlab.url, path)
end
def setup_finished? def setup_finished?
commits.any? commits.any?
end end
......
...@@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base ...@@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base
end end
def send_update_instructions def send_update_instructions
projects.each(&:send_move_instructions) projects.each do |project|
project.send_move_instructions("#{path_was}/#{project.path}")
end
end end
def kind def kind
......
...@@ -481,8 +481,8 @@ class Project < ActiveRecord::Base ...@@ -481,8 +481,8 @@ class Project < ActiveRecord::Base
end end
end end
def send_move_instructions def send_move_instructions(old_path_with_namespace)
NotificationService.new.project_was_moved(self) NotificationService.new.project_was_moved(self, old_path_with_namespace)
end end
def owner def owner
...@@ -624,7 +624,7 @@ class Project < ActiveRecord::Base ...@@ -624,7 +624,7 @@ class Project < ActiveRecord::Base
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions send_move_instructions(old_path_with_namespace)
reset_events_cache reset_events_cache
rescue rescue
# Returning false does not rollback after_* transaction but gives # Returning false does not rollback after_* transaction but gives
......
...@@ -70,11 +70,7 @@ class GitlabCiService < CiService ...@@ -70,11 +70,7 @@ class GitlabCiService < CiService
def fork_registration(new_project, current_user) def fork_registration(new_project, current_user)
params = OpenStruct.new({ params = OpenStruct.new({
id: new_project.id, id: new_project.id,
name_with_namespace: new_project.name_with_namespace, default_branch: new_project.default_branch
path_with_namespace: new_project.path_with_namespace,
web_url: new_project.web_url,
default_branch: new_project.default_branch,
ssh_url_to_repo: new_project.ssh_url_to_repo
}) })
ci_project = Ci::Project.find_by!(gitlab_id: project.id) ci_project = Ci::Project.find_by!(gitlab_id: project.id)
......
...@@ -38,6 +38,10 @@ module MergeRequests ...@@ -38,6 +38,10 @@ module MergeRequests
} }
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
rescue Exception => e
merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message)
return false
end end
def after_merge def after_merge
......
...@@ -183,12 +183,12 @@ class NotificationService ...@@ -183,12 +183,12 @@ class NotificationService
mailer.group_access_granted_email(group_member.id) mailer.group_access_granted_email(group_member.id)
end end
def project_was_moved(project) def project_was_moved(project, old_path_with_namespace)
recipients = project.team.members recipients = project.team.members
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
recipients.each do |recipient| recipients.each do |recipient|
mailer.project_was_moved_email(project.id, recipient.id) mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace)
end end
end end
......
...@@ -38,7 +38,7 @@ module Projects ...@@ -38,7 +38,7 @@ module Projects
project.save! project.save!
# Notifications # Notifications
project.send_move_instructions project.send_move_instructions(old_path)
# Move main repository # Move main repository
unless gitlab_shell.mv_repository(old_path, new_path) unless gitlab_shell.mv_repository(old_path, new_path)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%h3.page-title %h3.page-title
System OAuth applications System OAuth applications
%p.light %p.light
System OAuth application does not belong to certain user and can be managed only by admins System OAuth applications don't belong to any user and can only be managed by admins
%hr %hr
%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success' %p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success'
%table.table.table-striped %table.table.table-striped
......
%p %p
Project was moved to another location Project #{@old_path_with_namespace} was moved to another location
%p %p
The project is now located under The project is now located under
= link_to namespace_project_url(@project.namespace, @project) do = link_to namespace_project_url(@project.namespace, @project) do
......
Project was moved to another location Project #{@old_path_with_namespace} was moved to another location
The project is now located under The project is now located under
<%= namespace_project_url(@project.namespace, @project) %> <%= namespace_project_url(@project.namespace, @project) %>
......
...@@ -15,7 +15,12 @@ ...@@ -15,7 +15,12 @@
.files .files
- diff_files.each_with_index do |diff_file, index| - diff_files.each_with_index do |diff_file, index|
= render 'projects/diffs/file', diff_file: diff_file, i: index, project: project - diff_commit = commit_for_diff(diff_file.diff)
- blob = project.repository.blob_for_diff(diff_commit, diff_file.diff)
- next unless blob
= render 'projects/diffs/file', i: index, project: project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob
- if @diff_timeout - if @diff_timeout
.alert.alert-danger .alert.alert-danger
......
- blob = project.repository.blob_for_diff(@commit, diff_file.diff) .diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
- return unless blob
- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path))
.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
.diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"} .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
- if diff_file.deleted_file - if diff_file.diff.submodule?
%span="#{diff_file.old_path} deleted"
.diff-btn-group
- if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project)
- elsif diff_file.diff.submodule?
%span %span
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
= submodule_link(submodule_item, @commit.id, project.repository) = submodule_link(submodule_item, @commit.id, project.repository)
- else - else
%span %span
- if diff_file.renamed_file - if diff_file.deleted_file
= "#{diff_file.old_path} deleted"
- elsif diff_file.renamed_file
= "#{diff_file.old_path} renamed to #{diff_file.new_path}" = "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- else - else
= diff_file.new_path = diff_file.new_path
- if diff_file.mode_changed? - if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}" %span.file-mode= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
...@@ -28,12 +22,12 @@ ...@@ -28,12 +22,12 @@
%i.fa.fa-comments %i.fa.fa-comments
&nbsp; &nbsp;
- if @merge_request && @merge_request.source_project - if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project, = edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path, @merge_request.source_branch, diff_file.new_path,
after: '&nbsp;', from_merge_request_id: @merge_request.id) after: '&nbsp;', from_merge_request_id: @merge_request.id)
= view_file_btn(@commit.id, diff_file, project) = view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines .diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs -# Skipp all non non-supported blobs
......
%h3.page-title %h3.page-title
New merge request New merge request
%p.slead %p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From From
%strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch} %strong.label-branch #{source_title}
%span into %span into
%strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch} %strong.label-branch #{target_title}
%span.pull-right %span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request) = link_to 'Change branches', mr_change_branches_path(@merge_request)
......
...@@ -148,6 +148,10 @@ Devise.setup do |config| ...@@ -148,6 +148,10 @@ Devise.setup do |config|
# When someone else invites you to GitLab this time is also used so it should be pretty long. # When someone else invites you to GitLab this time is also used so it should be pretty long.
config.reset_password_within = 2.days config.reset_password_within = 2.days
# When set to false, does not sign a user in automatically after their password is
# reset. Defaults to true, so a user is signed in automatically after a reset.
config.sign_in_after_reset_password = false
# ==> Configuration for :encryptable # ==> Configuration for :encryptable
# Allow you to use another encryption algorithm besides bcrypt (default). You can use # Allow you to use another encryption algorithm besides bcrypt (default). You can use
# :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
......
class MergeRequestErrorField < ActiveRecord::Migration
def up
add_column :merge_requests, :merge_error, :string
end
end
class AddNullToNameForCiProjects < ActiveRecord::Migration
def up
change_column_null :ci_projects, :name, true
end
def down
change_column_null :ci_projects, :name, false
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150924125436) do ActiveRecord::Schema.define(version: 20150930095736) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -158,7 +158,7 @@ ActiveRecord::Schema.define(version: 20150924125436) do ...@@ -158,7 +158,7 @@ ActiveRecord::Schema.define(version: 20150924125436) do
add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree
create_table "ci_projects", force: true do |t| create_table "ci_projects", force: true do |t|
t.string "name", null: false t.string "name"
t.integer "timeout", default: 3600, null: false t.integer "timeout", default: 3600, null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
...@@ -453,6 +453,7 @@ ActiveRecord::Schema.define(version: 20150924125436) do ...@@ -453,6 +453,7 @@ ActiveRecord::Schema.define(version: 20150924125436) do
t.integer "position", default: 0 t.integer "position", default: 0
t.datetime "locked_at" t.datetime "locked_at"
t.integer "updated_by_id" t.integer "updated_by_id"
t.string "merge_error"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......
...@@ -100,8 +100,6 @@ Parameters: ...@@ -100,8 +100,6 @@ Parameters:
* `name` (required) - The name of the project * `name` (required) - The name of the project
* `gitlab_id` (required) - The ID of the project on the Gitlab instance * `gitlab_id` (required) - The ID of the project on the Gitlab instance
* `path` (required) - The gitlab project path
* `ssh_url_to_repo` (required) - The gitlab SSH url to the repo
* `default_ref` (optional) - The branch to run on (default to `master`) * `default_ref` (optional) - The branch to run on (default to `master`)
### Update Project ### Update Project
...@@ -114,9 +112,6 @@ authenticated user has access to. ...@@ -114,9 +112,6 @@ authenticated user has access to.
Parameters: Parameters:
* `name` - The name of the project * `name` - The name of the project
* `gitlab_id` - The ID of the project on the Gitlab instance
* `path` - The gitlab project path
* `ssh_url_to_repo` - The gitlab SSH url to the repo
* `default_ref` - The branch to run on (default to `master`) * `default_ref` - The branch to run on (default to `master`)
### Remove Project ### Remove Project
......
...@@ -115,8 +115,9 @@ Remove the old Ruby 1.8 if present ...@@ -115,8 +115,9 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it: Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby mkdir /tmp/ruby && cd /tmp/ruby
curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.7.tar.gz
cd ruby-2.1.6 echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.1.7.tar.gz' | shasum -c - && tar xzf ruby-2.1.7.tar.gz
cd ruby-2.1.7
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
sudo make install sudo make install
...@@ -131,11 +132,11 @@ Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server. ...@@ -131,11 +132,11 @@ Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server.
This is a small daemon written in Go. This is a small daemon written in Go.
To install gitlab-git-http-server we need a Go compiler. To install gitlab-git-http-server we need a Go compiler.
curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \ echo '46eecd290d8803887dec718c691cc243f2175fe0 go1.5.1.linux-amd64.tar.gz' | shasum -c - && \
sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.5.linux-amd64.tar.gz rm go1.5.1.linux-amd64.tar.gz
## 4. System Users ## 4. System Users
......
...@@ -36,6 +36,11 @@ mv /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds.$(date +%s) ...@@ -36,6 +36,11 @@ mv /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds.$(date +%s)
and run `sudo gitlab-ctl reconfigure`. and run `sudo gitlab-ctl reconfigure`.
#### 0. Updating Omnibus from versions prior to 7.13
If you are updating from older versions you should first update to 7.14 and then to 8.0.
Otherwise it's pretty likely that you will encounter problems described in the [Troubleshooting](#troubleshooting).
#### 1. Verify that backups work #### 1. Verify that backups work
Make sure that the backup script on both servers can connect to the database. Make sure that the backup script on both servers can connect to the database.
...@@ -43,6 +48,7 @@ Make sure that the backup script on both servers can connect to the database. ...@@ -43,6 +48,7 @@ Make sure that the backup script on both servers can connect to the database.
``` ```
# On your CI server: # On your CI server:
# Omnibus # Omnibus
sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
sudo gitlab-ci-rake backup:create sudo gitlab-ci-rake backup:create
# Source # Source
...@@ -319,3 +325,102 @@ You should also make sure that you can: ...@@ -319,3 +325,102 @@ You should also make sure that you can:
If something went wrong and you need to restore a backup, consult the [Backup If something went wrong and you need to restore a backup, consult the [Backup
restoration](../raketasks/backup_restore.md) guide. restoration](../raketasks/backup_restore.md) guide.
### Troubleshooting
#### show:secrets problem (Omnibus-only)
If you see errors like this:
```
Missing `secret_key_base` or `db_key_base` for 'production' environment. The secrets will be generated and stored in `config/secrets.yml`
rake aborted!
Errno::EACCES: Permission denied @ rb_sysopen - config/secrets.yml
```
This can happen if you are updating from versions prior to 7.13 straight to 8.0.
The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
#### Permission denied when accessing /var/opt/gitlab/gitlab-ci/builds
To fix that issue you have to change builds/ folder permission before doing final backup:
```
chown -R gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
```
#### Problems when importing CI database to GitLab
If you were migrating CI database from MySQL to PostgreSQL manually you can see errros during import about missing sequences:
```
ALTER SEQUENCE
ERROR: relation "ci_builds_id_seq" does not exist
ERROR: relation "ci_commits_id_seq" does not exist
ERROR: relation "ci_events_id_seq" does not exist
ERROR: relation "ci_jobs_id_seq" does not exist
ERROR: relation "ci_projects_id_seq" does not exist
ERROR: relation "ci_runner_projects_id_seq" does not exist
ERROR: relation "ci_runners_id_seq" does not exist
ERROR: relation "ci_services_id_seq" does not exist
ERROR: relation "ci_taggings_id_seq" does not exist
ERROR: relation "ci_tags_id_seq" does not exist
CREATE TABLE
```
To fix that you need to apply this SQL statement before doing final backup:
```
# Omnibus
gitlab-ci-rails dbconsole <<EOF
-- ALTER TABLES - DROP DEFAULTS
ALTER TABLE ONLY ci_application_settings ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_builds ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_commits ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_events ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_jobs ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_projects ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_runner_projects ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_runners ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_services ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_taggings ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_tags ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_trigger_requests ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_triggers ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_variables ALTER COLUMN id DROP DEFAULT;
ALTER TABLE ONLY ci_web_hooks ALTER COLUMN id DROP DEFAULT;
-- ALTER SEQUENCES
ALTER SEQUENCE ci_application_settings_id_seq OWNED BY ci_application_settings.id;
ALTER SEQUENCE ci_builds_id_seq OWNED BY ci_builds.id;
ALTER SEQUENCE ci_commits_id_seq OWNED BY ci_commits.id;
ALTER SEQUENCE ci_events_id_seq OWNED BY ci_events.id;
ALTER SEQUENCE ci_jobs_id_seq OWNED BY ci_jobs.id;
ALTER SEQUENCE ci_projects_id_seq OWNED BY ci_projects.id;
ALTER SEQUENCE ci_runner_projects_id_seq OWNED BY ci_runner_projects.id;
ALTER SEQUENCE ci_runners_id_seq OWNED BY ci_runners.id;
ALTER SEQUENCE ci_services_id_seq OWNED BY ci_services.id;
ALTER SEQUENCE ci_taggings_id_seq OWNED BY ci_taggings.id;
ALTER SEQUENCE ci_tags_id_seq OWNED BY ci_tags.id;
ALTER SEQUENCE ci_trigger_requests_id_seq OWNED BY ci_trigger_requests.id;
ALTER SEQUENCE ci_triggers_id_seq OWNED BY ci_triggers.id;
ALTER SEQUENCE ci_variables_id_seq OWNED BY ci_variables.id;
ALTER SEQUENCE ci_web_hooks_id_seq OWNED BY ci_web_hooks.id;
-- ALTER TABLES - RE-APPLY DEFAULTS
ALTER TABLE ONLY ci_application_settings ALTER COLUMN id SET DEFAULT nextval('ci_application_settings_id_seq'::regclass);
ALTER TABLE ONLY ci_builds ALTER COLUMN id SET DEFAULT nextval('ci_builds_id_seq'::regclass);
ALTER TABLE ONLY ci_commits ALTER COLUMN id SET DEFAULT nextval('ci_commits_id_seq'::regclass);
ALTER TABLE ONLY ci_events ALTER COLUMN id SET DEFAULT nextval('ci_events_id_seq'::regclass);
ALTER TABLE ONLY ci_jobs ALTER COLUMN id SET DEFAULT nextval('ci_jobs_id_seq'::regclass);
ALTER TABLE ONLY ci_projects ALTER COLUMN id SET DEFAULT nextval('ci_projects_id_seq'::regclass);
ALTER TABLE ONLY ci_runner_projects ALTER COLUMN id SET DEFAULT nextval('ci_runner_projects_id_seq'::regclass);
ALTER TABLE ONLY ci_runners ALTER COLUMN id SET DEFAULT nextval('ci_runners_id_seq'::regclass);
ALTER TABLE ONLY ci_services ALTER COLUMN id SET DEFAULT nextval('ci_services_id_seq'::regclass);
ALTER TABLE ONLY ci_taggings ALTER COLUMN id SET DEFAULT nextval('ci_taggings_id_seq'::regclass);
ALTER TABLE ONLY ci_tags ALTER COLUMN id SET DEFAULT nextval('ci_tags_id_seq'::regclass);
ALTER TABLE ONLY ci_trigger_requests ALTER COLUMN id SET DEFAULT nextval('ci_trigger_requests_id_seq'::regclass);
ALTER TABLE ONLY ci_triggers ALTER COLUMN id SET DEFAULT nextval('ci_triggers_id_seq'::regclass);
ALTER TABLE ONLY ci_variables ALTER COLUMN id SET DEFAULT nextval('ci_variables_id_seq'::regclass);
ALTER TABLE ONLY ci_web_hooks ALTER COLUMN id SET DEFAULT nextval('ci_web_hooks_id_seq'::regclass);
EOF
# Source
cd /home/gitlab_ci/gitlab-ci
sudo -u gitlab_ci -H bundle exec rails dbconsole production <<EOF
... COPY SQL STATEMENTS FROM ABOVE ...
EOF
```
...@@ -168,6 +168,7 @@ git diff origin/7-14-stable:lib/support/nginx/gitlab origin/8-0-stable:lib/suppo ...@@ -168,6 +168,7 @@ git diff origin/7-14-stable:lib/support/nginx/gitlab origin/8-0-stable:lib/suppo
``` ```
If you are using Apache instead of NGINX please see the updated [Apache templates](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache). If you are using Apache instead of NGINX please see the updated [Apache templates](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache).
Also note that because Apache does not support upstreams behind Unix sockets you will need to let gitlab-git-http-server listen on a TCP port. You can do this via [/etc/default/gitlab](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-0-stable/lib/support/init.d/gitlab.default.example#L34).
### 9. Migrate GitLab CI to GitLab CE/EE ### 9. Migrate GitLab CI to GitLab CE/EE
......
app:
image: gitlab/gitlab-ce:latest
FROM ubuntu:14.04
MAINTAINER Sytse Sijbrandij
# Install required packages
RUN apt-get update -q \
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
ca-certificates \
openssh-server \
wget \
apt-transport-https \
vim \
nano
# Download & Install GitLab
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
# Manage SSHD through runit
RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& mkfifo /opt/gitlab/sv/sshd/supervise/ok \
&& printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
&& chmod a+x /opt/gitlab/sv/sshd/run \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
# Disabling use DNS in ssh since it tends to slow connecting
RUN echo "UseDNS no" >> /etc/ssh/sshd_config
# Prepare default configuration
RUN ( \
echo "" && \
echo "# Docker options" && \
echo "# Prevent Postgres from trying to allocate 25% of total memory" && \
echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb && \
mkdir -p /assets/ && \
cp /etc/gitlab/gitlab.rb /assets/gitlab.rb
# Expose web & ssh
EXPOSE 443 80 22
# Define data volumes
VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"]
# Copy assets
COPY assets/wrapper /usr/local/bin/
# Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"]
# GitLab Docker images # GitLab Docker images
The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/). * The official GitLab Community Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ee/).
## After starting a container * The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/)
* The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker)
After starting a container you can go to [http://localhost:8080/](http://localhost:8080/) or [http://192.168.59.103:8080/](http://192.168.59.103:8080/) if you use boot2docker. * Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#Build-Docker-image)
It might take a while before the docker container is responding to queries.
You can check the status with something like `sudo docker logs -f gitlab`.
You can login to the web interface with username `root` and password `5iveL!fe`.
Next time, you can just use docker start and stop to run the container.
## Run the image
Run the image:
```bash
sudo docker run --detach \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
This will download and start GitLab CE container and publish ports needed to access SSH, HTTP and HTTPS.
All GitLab data will be stored as subdirectories of `/srv/gitlab/`.
The container will automatically `restart` after system reboot.
After this you can login to the web interface as explained above in 'After starting a container'.
## Where is the data stored?
The GitLab container uses host mounted volumes to store persistent data:
- `/srv/gitlab/data` mounted as `/var/opt/gitlab` in the container is used for storing *application data*
- `/srv/gitlab/logs` mounted as `/var/log/gitlab` in the container is used for storing *logs*
- `/srv/gitlab/config` mounted as `/etc/gitlab` in the container is used for storing *configuration*
You can fine tune these directories to meet your requirements.
### Configure GitLab
This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
To access GitLab configuration, you can start an bash in a new the context of running container, you will be able to browse all directories and use your favorite text editor:
```bash
sudo docker exec -it gitlab /bin/bash
```
You can also edit just `/etc/gitlab/gitlab.rb`:
```bash
sudo docker exec -it gitlab vi /etc/gitlab/gitlab.rb
```
**You should set the `external_url` to point to a valid URL.**
**You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).**
**To receive e-mails from GitLab you have to configure the [SMTP settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md),
because Docker image doesn't have a SMTP server.**
**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab:
```bash
sudo docker restart gitlab
```
For more options for configuring the container please check [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
## Diagnose potential problems
Read container logs:
```bash
sudo docker logs gitlab
```
Enter running container:
```bash
sudo docker exec -it gitlab /bin/bash
```
From within container you can administrer GitLab container as you would normally administer Omnibus installation: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md.
### Upgrade GitLab to newer version
To upgrade GitLab to new version you have to do:
1. pull new image,
```bash
sudo docker stop gitlab
```
1. stop running container,
```bash
sudo docker rm gitlab
```
1. remove existing container,
```bash
sudo docker pull gitlab/gitlab-ce:latest
```
1. create the container once again with previously specified options.
```bash
sudo docker run --detach \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
On the first run GitLab will reconfigure and update itself.
### Run GitLab CE on public IP address
You can make Docker to use your IP address and forward all traffic to the GitLab CE container.
You can do that by modifying the `--publish` ([Binding container ports to the host](https://docs.docker.com/articles/networking/#binding-ports)):
> --publish=[] : Publish a container᾿s port or a range of ports to the host format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
To expose GitLab CE on IP 1.1.1.1:
```bash
sudo docker run --detach \
--publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
You can then access GitLab instance at http://1.1.1.1/ and https://1.1.1.1/.
### Build the image
This guide will also let you know how to build docker image yourself.
Please run the command from the GitLab repo root directory.
People using boot2docker should run all the commands without sudo.
```bash
sudo docker build --tag gitlab/gitlab-ce:latest docker/
```
### Publish the image to Dockerhub
- Ensure the containers are running
- Login to Dockerhub with `sudo docker login`
```bash
sudo docker login
sudo docker push gitlab/gitlab-ce:latest
```
## Troubleshooting
Please see the [troubleshooting](troubleshooting.md) file in this directory.
Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it.
Our docker image runs chef at every start to generate GitLab configuration.
#!/bin/bash
function sigterm_handler() {
echo "SIGTERM signal received, try to gracefully shutdown all services..."
gitlab-ctl stop
}
trap "sigterm_handler; exit" TERM
function entrypoint() {
/opt/gitlab/embedded/bin/runsvdir-start &
gitlab-ctl reconfigure # will also start everything
gitlab-ctl tail # tail all logs
}
if [[ ! -e /etc/gitlab/gitlab.rb ]]; then
cp /assets/gitlab.rb /etc/gitlab/gitlab.rb
chmod 0600 /etc/gitlab/gitlab.rb
fi
entrypoint
{
"id": "/gitlab",
"ports": [0,0],
"cpus": 2,
"mem": 2048.0,
"disk": 10240.0,
"container": {
"type": "DOCKER",
"docker": {
"network": "HOST",
"image": "gitlab/gitlab-ce:latest"
},
"volumes": [
{
"containerPath": "/etc/gitlab",
"hostPath": "/var/data/etc/gitlab",
"mode": "RW"
},
{
"containerPath": "/var/opt/gitlab",
"hostPath": "/var/data/opt/gitlab",
"mode": "RW"
},
{
"containerPath": "/var/log/gitlab",
"hostPath": "/var/data/log/gitlab",
"mode": "RW"
}
]
}
}
\ No newline at end of file
# Troubleshooting
This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
But it might contain useful commands for other cases as well.
The configuration to add the postgres log in vim is:
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands
```bash
sudo docker build --tag gitlab/gitlab-ce:latest docker/
sudo docker rm -f gitlab
sudo docker exec -it gitlab vim /etc/gitlab/gitlab.rb
sudo docker exec gitlab tail -f /var/log/gitlab/reconfigure.log
sudo docker exec gitlab tail -f /var/log/gitlab/postgresql/current
sudo docker exec gitlab cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
sudo docker exec gitlab cat /etc/gitlab/gitlab.rb
```
# Interactively
```bash
# First start a GitLab container without starting GitLab
# This is almost the same as starting the GitLab container except:
# - we run interactively (-t -i)
# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
# - we choose another startup command (bash)
sudo docker run --ti \
-e TERM=linux
--publish 80443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest \
bash
# Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
# Prevent Postgres from allocating 25% of total memory
echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
# You can now start GitLab manually from Bash (in the background)
# Maybe the command below is still missing something to run in the background
gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
# Inspect PostgreSQL config
cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
# And tail the logs (PostgreSQL log may not exist immediately)
tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
# And get the memory
cat /proc/meminfo
head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
free -m
```
# Cleanup
Remove ALL docker containers and images (also non GitLab ones).
**Be careful, because the `-v` also removes volumes attached to the images.**
```bash
# Remove all containers with attached volumes
docker rm -v $(docker ps -a -q)
# Remove all images
docker rmi $(docker images -q)
# Remove GitLab persistent data
rm -rf /srv/gitlab
```
...@@ -115,40 +115,40 @@ Feature: Project Merge Requests ...@@ -115,40 +115,40 @@ Feature: Project Merge Requests
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I click on the Changes tab And I click on the Changes tab
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the third file
And I click link "Hide inline discussion" of the second file And I click link "Hide inline discussion" of the third file
Then I should not see a comment like "Line is wrong here" in the second file Then I should not see a comment like "Line is wrong here" in the third file
@javascript @javascript
Scenario: I show comments on a merge request diff with comments in a single file Scenario: I show comments on a merge request diff with comments in a single file
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I click on the Changes tab And I click on the Changes tab
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the third file
Then I should see a comment like "Line is wrong" in the second file Then I should see a comment like "Line is wrong" in the third file
@javascript @javascript
Scenario: I hide comments on a merge request diff with comments in multiple files Scenario: I hide comments on a merge request diff with comments in multiple files
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I click on the Changes tab And I click on the Changes tab
And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is correct" on line 12 of the second file
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the third file
And I click link "Hide inline discussion" of the second file And I click link "Hide inline discussion" of the third file
Then I should not see a comment like "Line is wrong here" in the second file Then I should not see a comment like "Line is wrong here" in the third file
And I should still see a comment like "Line is correct" in the first file And I should still see a comment like "Line is correct" in the second file
@javascript @javascript
Scenario: I show comments on a merge request diff with comments in multiple files Scenario: I show comments on a merge request diff with comments in multiple files
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I click on the Changes tab And I click on the Changes tab
And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is correct" on line 12 of the second file
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the third file
And I click link "Hide inline discussion" of the second file And I click link "Hide inline discussion" of the third file
And I click link "Show inline discussion" of the second file And I click link "Show inline discussion" of the third file
Then I should see a comment like "Line is wrong" in the second file Then I should see a comment like "Line is wrong" in the third file
And I should still see a comment like "Line is correct" in the first file And I should still see a comment like "Line is correct" in the second file
@javascript @javascript
Scenario: I unfold diff Scenario: I unfold diff
...@@ -163,8 +163,8 @@ Feature: Project Merge Requests ...@@ -163,8 +163,8 @@ Feature: Project Merge Requests
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05" And I visit merge request page "Bug NS-05"
And I click on the Changes tab And I click on the Changes tab
And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is correct" on line 12 of the second file
And I leave a comment like "Line is wrong" on line 39 of the second file And I leave a comment like "Line is wrong" on line 39 of the third file
And I click Side-by-side Diff tab And I click Side-by-side Diff tab
Then I should see comments on the side-by-side diff page Then I should see comments on the side-by-side diff page
......
...@@ -224,43 +224,43 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -224,43 +224,43 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
end end
step 'I click link "Hide inline discussion" of the second file' do step 'I click link "Hide inline discussion" of the third file' do
page.within '.files [id^=diff]:nth-child(2)' do page.within '.files [id^=diff]:nth-child(3)' do
find('.js-toggle-diff-comments').trigger('click') find('.js-toggle-diff-comments').trigger('click')
end end
end end
step 'I click link "Show inline discussion" of the second file' do step 'I click link "Show inline discussion" of the third file' do
page.within '.files [id^=diff]:nth-child(2)' do page.within '.files [id^=diff]:nth-child(3)' do
find('.js-toggle-diff-comments').trigger('click') find('.js-toggle-diff-comments').trigger('click')
end end
end end
step 'I should not see a comment like "Line is wrong" in the second file' do step 'I should not see a comment like "Line is wrong" in the third file' do
page.within '.files [id^=diff]:nth-child(2)' do page.within '.files [id^=diff]:nth-child(3)' do
expect(page).not_to have_visible_content "Line is wrong" expect(page).not_to have_visible_content "Line is wrong"
end end
end end
step 'I should see a comment like "Line is wrong" in the second file' do step 'I should see a comment like "Line is wrong" in the third file' do
page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.within '.files [id^=diff]:nth-child(3) .note-body > .note-text' do
expect(page).to have_visible_content "Line is wrong" expect(page).to have_visible_content "Line is wrong"
end end
end end
step 'I should not see a comment like "Line is wrong here" in the second file' do step 'I should not see a comment like "Line is wrong here" in the third file' do
page.within '.files [id^=diff]:nth-child(2)' do page.within '.files [id^=diff]:nth-child(3)' do
expect(page).not_to have_visible_content "Line is wrong here" expect(page).not_to have_visible_content "Line is wrong here"
end end
end end
step 'I should see a comment like "Line is wrong here" in the second file' do step 'I should see a comment like "Line is wrong here" in the third file' do
page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.within '.files [id^=diff]:nth-child(3) .note-body > .note-text' do
expect(page).to have_visible_content "Line is wrong here" expect(page).to have_visible_content "Line is wrong here"
end end
end end
step 'I leave a comment like "Line is correct" on line 12 of the first file' do step 'I leave a comment like "Line is correct" on line 12 of the second file' do
init_diff_note_first_file init_diff_note_first_file
page.within(".js-discussion-note-form") do page.within(".js-discussion-note-form") do
...@@ -268,12 +268,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -268,12 +268,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Add Comment" click_button "Add Comment"
end end
page.within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do page.within ".files [id^=diff]:nth-child(2) .note-body > .note-text" do
expect(page).to have_content "Line is correct" expect(page).to have_content "Line is correct"
end end
end end
step 'I leave a comment like "Line is wrong" on line 39 of the second file' do step 'I leave a comment like "Line is wrong" on line 39 of the third file' do
init_diff_note_second_file init_diff_note_second_file
page.within(".js-discussion-note-form") do page.within(".js-discussion-note-form") do
...@@ -282,8 +282,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -282,8 +282,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
end end
step 'I should still see a comment like "Line is correct" in the first file' do step 'I should still see a comment like "Line is correct" in the second file' do
page.within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
expect(page).to have_visible_content "Line is correct" expect(page).to have_visible_content "Line is correct"
end end
end end
...@@ -303,7 +303,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -303,7 +303,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I should see comments on the side-by-side diff page' do step 'I should see comments on the side-by-side diff page' do
page.within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do page.within '.files [id^=diff]:nth-child(2) .parallel .note-body > .note-text' do
expect(page).to have_visible_content "Line is correct" expect(page).to have_visible_content "Line is correct"
end end
end end
......
...@@ -75,23 +75,17 @@ module Ci ...@@ -75,23 +75,17 @@ module Ci
# Create Gitlab CI project using Gitlab project info # Create Gitlab CI project using Gitlab project info
# #
# Parameters: # Parameters:
# name (required) - The name of the project
# gitlab_id (required) - The gitlab id of the project # gitlab_id (required) - The gitlab id of the project
# path (required) - The gitlab project path, ex. randx/six
# ssh_url_to_repo (required) - The gitlab ssh url to the repo
# default_ref - The branch to run against (defaults to `master`) # default_ref - The branch to run against (defaults to `master`)
# Example Request: # Example Request:
# POST /projects # POST /projects
post do post do
required_attributes! [:name, :gitlab_id, :ssh_url_to_repo] required_attributes! [:gitlab_id]
filtered_params = { filtered_params = {
name: params[:name],
gitlab_id: params[:gitlab_id], gitlab_id: params[:gitlab_id],
# we accept gitlab_url for backward compatibility for a while (added to 7.11) # we accept gitlab_url for backward compatibility for a while (added to 7.11)
path: params[:path] || params[:gitlab_url].sub(/.*\/(.*\/.*)$/, '\1'), default_ref: params[:default_ref] || 'master'
default_ref: params[:default_ref] || 'master',
ssh_url_to_repo: params[:ssh_url_to_repo]
} }
project = Ci::Project.new(filtered_params) project = Ci::Project.new(filtered_params)
...@@ -109,11 +103,7 @@ module Ci ...@@ -109,11 +103,7 @@ module Ci
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# name - The name of the project # default_ref - The branch to run against (defaults to `master`)
# gitlab_id - The gitlab id of the project
# path - The gitlab project path, ex. randx/six
# ssh_url_to_repo - The gitlab ssh url to the repo
# default_ref - The branch to run against (defaults to `master`)
# Example Request: # Example Request:
# PUT /projects/:id # PUT /projects/:id
put ":id" do put ":id" do
...@@ -121,12 +111,7 @@ module Ci ...@@ -121,12 +111,7 @@ module Ci
unauthorized! unless can?(current_user, :admin_project, project.gl_project) unauthorized! unless can?(current_user, :admin_project, project.gl_project)
attrs = attributes_for_keys [:name, :gitlab_id, :path, :gitlab_url, :default_ref, :ssh_url_to_repo] attrs = attributes_for_keys [:default_ref]
# we accept gitlab_url for backward compatibility for a while (added to 7.11)
if attrs[:gitlab_url] && !attrs[:path]
attrs[:path] = attrs[:gitlab_url].sub(/.*\/(.*\/.*)$/, '\1')
end
if project.update_attributes(attrs) if project.update_attributes(attrs)
present project, with: Entities::Project present project, with: Entities::Project
......
...@@ -29,20 +29,8 @@ ...@@ -29,20 +29,8 @@
FactoryGirl.define do FactoryGirl.define do
factory :ci_project_without_token, class: Ci::Project do factory :ci_project_without_token, class: Ci::Project do
sequence :name do |n|
"GitLab / gitlab-shell#{n}"
end
default_ref 'master' default_ref 'master'
sequence :path do |n|
"gitlab/gitlab-shell#{n}"
end
sequence :ssh_url_to_repo do |n|
"git@demo.gitlab.com:gitlab/gitlab-shell#{n}.git"
end
gl_project factory: :empty_project gl_project factory: :empty_project
factory :ci_project do factory :ci_project do
......
...@@ -2,8 +2,7 @@ require 'spec_helper' ...@@ -2,8 +2,7 @@ require 'spec_helper'
describe "Admin Runners" do describe "Admin Runners" do
before do before do
skip_ci_admin_auth login_as :admin
login_as :user
end end
describe "Runners page" do describe "Runners page" do
...@@ -20,16 +19,16 @@ describe "Admin Runners" do ...@@ -20,16 +19,16 @@ describe "Admin Runners" do
describe 'search' do describe 'search' do
before do before do
FactoryGirl.create :ci_runner, description: 'foo' FactoryGirl.create :ci_runner, description: 'runner-foo'
FactoryGirl.create :ci_runner, description: 'bar' FactoryGirl.create :ci_runner, description: 'runner-bar'
search_form = find('#runners-search') search_form = find('#runners-search')
search_form.fill_in 'search', with: 'foo' search_form.fill_in 'search', with: 'runner-foo'
search_form.click_button 'Search' search_form.click_button 'Search'
end end
it { expect(page).to have_content("foo") } it { expect(page).to have_content("runner-foo") }
it { expect(page).not_to have_content("bar") } it { expect(page).not_to have_content("runner-bar") }
end end
end end
...@@ -37,8 +36,8 @@ describe "Admin Runners" do ...@@ -37,8 +36,8 @@ describe "Admin Runners" do
let(:runner) { FactoryGirl.create :ci_runner } let(:runner) { FactoryGirl.create :ci_runner }
before do before do
FactoryGirl.create(:ci_project, name: "foo") @project1 = FactoryGirl.create(:ci_project)
FactoryGirl.create(:ci_project, name: "bar") @project2 = FactoryGirl.create(:ci_project)
visit ci_admin_runner_path(runner) visit ci_admin_runner_path(runner)
end end
...@@ -47,19 +46,19 @@ describe "Admin Runners" do ...@@ -47,19 +46,19 @@ describe "Admin Runners" do
end end
describe 'projects' do describe 'projects' do
it { expect(page).to have_content("foo") } it { expect(page).to have_content(@project1.name_with_namespace) }
it { expect(page).to have_content("bar") } it { expect(page).to have_content(@project2.name_with_namespace) }
end end
describe 'search' do describe 'search' do
before do before do
search_form = find('#runner-projects-search') search_form = find('#runner-projects-search')
search_form.fill_in 'search', with: 'foo' search_form.fill_in 'search', with: @project1.gl_project.name
search_form.click_button 'Search' search_form.click_button 'Search'
end end
it { expect(page).to have_content("foo") } it { expect(page).to have_content(@project1.name_with_namespace) }
it { expect(page).not_to have_content("bar") } it { expect(page).not_to have_content(@project2.name_with_namespace) }
end end
end end
end end
require 'spec_helper' require 'spec_helper'
feature 'Password reset', feature: true do feature 'Password reset', feature: true do
def forgot_password
click_on 'Forgot your password?'
fill_in 'Email', with: user.email
click_button 'Reset password'
user.reload
end
def get_reset_token
mail = ActionMailer::Base.deliveries.last
body = mail.body.encoded
body.scan(/reset_password_token=(.+)\"/).flatten.first
end
def reset_password(password = 'password')
visit edit_user_password_path(reset_password_token: get_reset_token)
fill_in 'New password', with: password
fill_in 'Confirm new password', with: password
click_button 'Change your password'
end
describe 'with two-factor authentication' do describe 'with two-factor authentication' do
let(:user) { create(:user, :two_factor) } let(:user) { create(:user, :two_factor) }
...@@ -40,14 +19,35 @@ feature 'Password reset', feature: true do ...@@ -40,14 +19,35 @@ feature 'Password reset', feature: true do
describe 'without two-factor authentication' do describe 'without two-factor authentication' do
let(:user) { create(:user) } let(:user) { create(:user) }
it 'automatically logs in after password reset' do it 'requires login after password reset' do
visit root_path visit root_path
forgot_password forgot_password
reset_password reset_password
expect(current_path).to eq root_path expect(page).to have_content("Your password was changed successfully.")
expect(page).to have_content("Your password was changed successfully. You are now signed in.") expect(current_path).to eq new_user_session_path
end end
end end
def forgot_password
click_on 'Forgot your password?'
fill_in 'Email', with: user.email
click_button 'Reset password'
user.reload
end
def get_reset_token
mail = ActionMailer::Base.deliveries.last
body = mail.body.encoded
body.scan(/reset_password_token=(.+)\"/).flatten.first
end
def reset_password(password = 'password')
visit edit_user_password_path(reset_password_token: get_reset_token)
fill_in 'New password', with: password
fill_in 'Confirm new password', with: password
click_button 'Change your password'
end
end end
require 'spec_helper'
describe MergeRequestsHelper do
describe :issues_sentence do
subject { issues_sentence(issues) }
let(:issues) do
[build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
end
it { is_expected.to eq('#1, #2, and #3') }
end
end
require 'spec_helper'
describe MergeRequestsHelper do
describe "#issues_sentence" do
subject { issues_sentence(issues) }
let(:issues) do
[build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
end
it { is_expected.to eq('#1, #2, and #3') }
end
describe "#format_mr_branch_names" do
describe "within the same project" do
let(:merge_request) { create(:merge_request) }
subject { format_mr_branch_names(merge_request) }
it { is_expected.to eq([merge_request.source_branch, merge_request.target_branch]) }
end
describe "within different projects" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
subject { format_mr_branch_names(merge_request) }
let(:source_title) { "#{fork_project.path_with_namespace}:#{merge_request.source_branch}" }
let(:target_title) { "#{project.path_with_namespace}:#{merge_request.target_branch}" }
it { is_expected.to eq([source_title, target_title]) }
end
end
end
...@@ -399,7 +399,7 @@ describe Notify do ...@@ -399,7 +399,7 @@ describe Notify do
describe 'project was moved' do describe 'project was moved' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { Notify.project_was_moved_email(project.id, user.id) } subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
it_behaves_like 'an email sent from GitLab' it_behaves_like 'an email sent from GitLab'
......
...@@ -29,7 +29,8 @@ require 'spec_helper' ...@@ -29,7 +29,8 @@ require 'spec_helper'
describe Ci::Project do describe Ci::Project do
let(:gl_project) { FactoryGirl.create :empty_project } let(:gl_project) { FactoryGirl.create :empty_project }
subject { FactoryGirl.create :ci_project, gl_project: gl_project } let(:project) { FactoryGirl.create :ci_project, gl_project: gl_project }
subject { project }
it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runner_projects) }
it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:runners) }
...@@ -40,6 +41,7 @@ describe Ci::Project do ...@@ -40,6 +41,7 @@ describe Ci::Project do
it { is_expected.to have_many(:services) } it { is_expected.to have_many(:services) }
it { is_expected.to validate_presence_of :timeout } it { is_expected.to validate_presence_of :timeout }
it { is_expected.to validate_presence_of :gitlab_id }
describe 'before_validation' do describe 'before_validation' do
it 'should set an random token if none provided' do it 'should set an random token if none provided' do
...@@ -53,6 +55,66 @@ describe Ci::Project do ...@@ -53,6 +55,66 @@ describe Ci::Project do
end end
end end
describe :name_with_namespace do
subject { project.name_with_namespace }
it { is_expected.to eq(project.name) }
it { is_expected.to eq(gl_project.name_with_namespace) }
end
describe :path_with_namespace do
subject { project.path_with_namespace }
it { is_expected.to eq(project.path) }
it { is_expected.to eq(gl_project.path_with_namespace) }
end
describe :path_with_namespace do
subject { project.web_url }
it { is_expected.to eq(gl_project.web_url) }
end
describe :web_url do
subject { project.web_url }
it { is_expected.to eq(project.gitlab_url) }
it { is_expected.to eq(gl_project.web_url) }
end
describe :http_url_to_repo do
subject { project.http_url_to_repo }
it { is_expected.to eq(gl_project.http_url_to_repo) }
end
describe :ssh_url_to_repo do
subject { project.ssh_url_to_repo }
it { is_expected.to eq(gl_project.ssh_url_to_repo) }
end
describe :commits do
subject { project.commits }
before do
FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
end
it { is_expected.to eq(gl_project.ci_commits) }
end
describe :builds do
subject { project.builds }
before do
commit = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
FactoryGirl.create :ci_build, commit: commit
end
it { is_expected.to eq(gl_project.ci_builds) }
end
describe "ordered_by_last_commit_date" do describe "ordered_by_last_commit_date" do
it "returns ordered projects" do it "returns ordered projects" do
newest_project = FactoryGirl.create :empty_project newest_project = FactoryGirl.create :empty_project
...@@ -174,13 +236,6 @@ describe Ci::Project do ...@@ -174,13 +236,6 @@ describe Ci::Project do
it { is_expected.to include(project.gitlab_url[7..-1]) } it { is_expected.to include(project.gitlab_url[7..-1]) }
end end
describe :search do
let!(:project) { FactoryGirl.create(:ci_project, name: "foo") }
it { expect(Ci::Project.search('fo')).to include(project) }
it { expect(Ci::Project.search('bar')).to be_empty }
end
describe :any_runners do describe :any_runners do
it "there are no runners available" do it "there are no runners available" do
project = FactoryGirl.create(:ci_project) project = FactoryGirl.create(:ci_project)
......
...@@ -134,7 +134,7 @@ describe Ci::API::API do ...@@ -134,7 +134,7 @@ describe Ci::API::API do
describe "PUT /projects/:id" do describe "PUT /projects/:id" do
let!(:project) { FactoryGirl.create(:ci_project) } let!(:project) { FactoryGirl.create(:ci_project) }
let!(:project_info) { { name: "An updated name!" } } let!(:project_info) { { default_ref: "develop" } }
before do before do
options.merge!(project_info) options.merge!(project_info)
...@@ -144,7 +144,7 @@ describe Ci::API::API do ...@@ -144,7 +144,7 @@ describe Ci::API::API do
project.gl_project.team << [user, :master] project.gl_project.team << [user, :master]
put ci_api("/projects/#{project.id}"), options put ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response["name"]).to eq(project_info[:name]) expect(json_response["default_ref"]).to eq(project_info[:default_ref])
end end
it "fails to update a non-existing project" do it "fails to update a non-existing project" do
...@@ -181,12 +181,10 @@ describe Ci::API::API do ...@@ -181,12 +181,10 @@ describe Ci::API::API do
end end
describe "POST /projects" do describe "POST /projects" do
let(:gl_project) { FactoryGirl.create :empty_project }
let(:project_info) do let(:project_info) do
{ {
name: "My project", gitlab_id: gl_project.id
gitlab_id: 1,
path: "testing/testing",
ssh_url_to_repo: "ssh://example.com/testing/testing.git"
} }
end end
...@@ -200,7 +198,7 @@ describe Ci::API::API do ...@@ -200,7 +198,7 @@ describe Ci::API::API do
it "should create a project with valid data" do it "should create a project with valid data" do
post ci_api("/projects"), options post ci_api("/projects"), options
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['name']).to eq(project_info[:name]) expect(json_response['name']).to eq(gl_project.name_with_namespace)
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Ci::EventService do describe Ci::EventService do
let(:project) { FactoryGirl.create :ci_project, name: "GitLab / gitlab-shell" } let(:project) { FactoryGirl.create :ci_project }
let(:user) { double(username: "root", id: 1) } let(:user) { double(username: "root", id: 1) }
before do before do
...@@ -12,7 +12,7 @@ describe Ci::EventService do ...@@ -12,7 +12,7 @@ describe Ci::EventService do
it "creates event" do it "creates event" do
Ci::EventService.new.remove_project(user, project) Ci::EventService.new.remove_project(user, project)
expect(Ci::Event.admin.last.description).to eq("Project \"GitLab / gitlab-shell\" has been removed by root") expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been removed by root")
end end
end end
...@@ -20,7 +20,7 @@ describe Ci::EventService do ...@@ -20,7 +20,7 @@ describe Ci::EventService do
it "creates event" do it "creates event" do
Ci::EventService.new.create_project(user, project) Ci::EventService.new.create_project(user, project)
expect(Ci::Event.admin.last.description).to eq("Project \"GitLab / gitlab-shell\" has been created by root") expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been created by root")
end end
end end
......
...@@ -35,5 +35,19 @@ describe MergeRequests::MergeService do ...@@ -35,5 +35,19 @@ describe MergeRequests::MergeService do
expect(note.note).to include 'Status changed to merged' expect(note.note).to include 'Status changed to merged'
end end
end end
context "error handling" do
let(:service) { MergeRequests::MergeService.new(project, user, {}) }
it 'saves error if there is an exception' do
allow(service).to receive(:repository).and_raise("error")
allow(service).to receive(:execute_hooks)
service.execute(merge_request, 'Awesome message')
expect(merge_request.merge_error).to eq("Something went wrong during merge")
end
end
end end
end end
...@@ -427,15 +427,15 @@ describe NotificationService do ...@@ -427,15 +427,15 @@ describe NotificationService do
should_email(@u_watcher.id) should_email(@u_watcher.id)
should_email(@u_participating.id) should_email(@u_participating.id)
should_not_email(@u_disabled.id) should_not_email(@u_disabled.id)
notification.project_was_moved(project) notification.project_was_moved(project, "gitlab/gitlab")
end end
def should_email(user_id) def should_email(user_id)
expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id) expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
end end
def should_not_email(user_id) def should_not_email(user_id)
expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id) expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
end end
end end
end end
......
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